Let the data folders be set per install, database separately

DATA_DIR takes the files and PGDATA_DIR the database, so the database can
stay on a filesystem that supports ownership when the files live on a disk
that doesn't. The example .env points both at /Volumes/Data/printhive.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
dlawler489 2026-09-20 16:01:43 +10:00
parent 985b81d1de
commit 6c10cf6b37
4 changed files with 44 additions and 12 deletions

View file

@ -2,5 +2,13 @@
DATABASE_URL=postgres://USER@localhost:5432/stl_library DATABASE_URL=postgres://USER@localhost:5432/stl_library
PORT=3000 PORT=3000
# Docker only: where data lives on the host. Both default to ./data beside
# docker-compose.yml. DATA_DIR takes the model files, thumbnails and previews.
DATA_DIR=/Volumes/Data/printhive
# The database is set separately, because Postgres needs a filesystem with real
# ownership and permissions — leave it on local disk if DATA_DIR is exFAT, FAT
# or a network share.
PGDATA_DIR=/Volumes/Data/printhive/pgdata
# Thingiverse import — get a free app token at https://www.thingiverse.com/developers # Thingiverse import — get a free app token at https://www.thingiverse.com/developers
THINGIVERSE_TOKEN= THINGIVERSE_TOKEN=

View file

@ -59,17 +59,22 @@ Run the unit tests with `npm test`.
### Where your data lives ### Where your data lives
With Docker, everything sits in folders next to `docker-compose.yml` (set `DATA_DIR` to put them elsewhere): With Docker, everything sits in folders on the host — `./data` beside `docker-compose.yml` unless `.env` says
otherwise (`DATA_DIR` for the files, `PGDATA_DIR` for the database):
| Folder | Holds | | Folder | Holds |
|---|---| |---|---|
| `data/pgdata` | The database: models, tags, collections, watches, spools, printers, settings (tokens and printer access codes live here) | | `$PGDATA_DIR` | The database: models, tags, collections, watches, spools, printers, settings (tokens and printer access codes live here) |
| `data/uploads` | Your model files, as uploaded or downloaded | | `$DATA_DIR/uploads` | Your model files, as uploaded or downloaded |
| `data/thumbnails` | Generated pictures, including plate images | | `$DATA_DIR/thumbnails` | Generated pictures, including plate images |
| `data/glb` | Converted 3D previews for the viewer | | `$DATA_DIR/glb` | Converted 3D previews for the viewer |
| `data/extracted` | Files unpacked from archives — a cache | | `$DATA_DIR/extracted` | Files unpacked from archives — a cache |
A backup needs `data/pgdata` (or a `pg_dump`) and `data/uploads`; the other three rebuild themselves. Everything else in The database is set apart from the rest because Postgres needs a filesystem with real ownership and permissions —
ext4, xfs, APFS and the like. exFAT, FAT and most network shares won't do, so keep `PGDATA_DIR` on local disk if the
files live on one of those.
A backup needs the database (or a `pg_dump`) and `uploads`; the other three rebuild themselves. Everything else in
the container is replaced on each update, so nothing outside these folders is kept. the container is replaced on each update, so nothing outside these folders is kept.
Upgrading from a version that used Docker named volumes: stop the stack, run `tools/volumes-to-folders.sh` to copy the Upgrading from a version that used Docker named volumes: stop the stack, run `tools/volumes-to-folders.sh` to copy the

View file

@ -6,7 +6,11 @@ services:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-printhive} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-printhive}
POSTGRES_DB: printhive POSTGRES_DB: printhive
volumes: volumes:
- ${DATA_DIR:-./data}/pgdata:/var/lib/postgresql/data # Set separately from DATA_DIR: Postgres needs a filesystem with real
# ownership and permissions (ext4, xfs, APFS...), which exFAT, FAT and
# most network shares don't provide. Keep it on local disk if the files
# live somewhere like that.
- ${PGDATA_DIR:-./data/pgdata}:/var/lib/postgresql/data
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U printhive -d printhive"] test: ["CMD-SHELL", "pg_isready -U printhive -d printhive"]
interval: 5s interval: 5s
@ -33,7 +37,7 @@ services:
MAKERWORLD_TOKEN: ${MAKERWORLD_TOKEN:-} MAKERWORLD_TOKEN: ${MAKERWORLD_TOKEN:-}
PRINTABLES_TOKEN: ${PRINTABLES_TOKEN:-} PRINTABLES_TOKEN: ${PRINTABLES_TOKEN:-}
volumes: volumes:
# Folders on the host, under ./data (set DATA_DIR to put them elsewhere). # Folders on the host: ./data unless DATA_DIR says otherwise.
# uploads and pgdata hold everything that matters; the other two rebuild. # uploads and pgdata hold everything that matters; the other two rebuild.
- ${DATA_DIR:-./data}/uploads:/app/uploads - ${DATA_DIR:-./data}/uploads:/app/uploads
- ${DATA_DIR:-./data}/thumbnails:/app/thumbnails - ${DATA_DIR:-./data}/thumbnails:/app/thumbnails

View file

@ -8,9 +8,23 @@
# again with nothing lost. # again with nothing lost.
set -euo pipefail set -euo pipefail
# Same settings Compose uses, so the copy lands where the stack will look.
if [ -f .env ]; then
DATA_DIR="${DATA_DIR:-$(grep -E '^DATA_DIR=' .env | tail -1 | cut -d= -f2-)}"
PGDATA_DIR="${PGDATA_DIR:-$(grep -E '^PGDATA_DIR=' .env | tail -1 | cut -d= -f2-)}"
fi
DATA_DIR="${DATA_DIR:-./data}" DATA_DIR="${DATA_DIR:-./data}"
PGDATA_DIR="${PGDATA_DIR:-./data/pgdata}"
VOLUMES=(pgdata uploads thumbnails glb extracted) VOLUMES=(pgdata uploads thumbnails glb extracted)
# Where each volume's contents should end up.
target_for() {
case "$1" in
pgdata) echo "$PGDATA_DIR" ;;
*) echo "$DATA_DIR/$1" ;;
esac
}
# Compose names volumes "<project>_<name>", the project being this folder's name # Compose names volumes "<project>_<name>", the project being this folder's name
# unless it's set elsewhere. # unless it's set elsewhere.
PROJECT="${COMPOSE_PROJECT_NAME:-$(basename "$(pwd)")}" PROJECT="${COMPOSE_PROJECT_NAME:-$(basename "$(pwd)")}"
@ -21,12 +35,13 @@ if docker compose ps --status running --quiet 2>/dev/null | grep -q .; then
fi fi
echo "Project: $PROJECT" echo "Project: $PROJECT"
echo "Copying into: $DATA_DIR" echo "Files: $DATA_DIR"
echo "Database: $PGDATA_DIR"
echo echo
for name in "${VOLUMES[@]}"; do for name in "${VOLUMES[@]}"; do
volume="${PROJECT}_${name}" volume="${PROJECT}_${name}"
target="$DATA_DIR/$name" target="$(target_for "$name")"
if ! docker volume inspect "$volume" >/dev/null 2>&1; then if ! docker volume inspect "$volume" >/dev/null 2>&1; then
echo "· $volume — no such volume, skipping (nothing to copy)" echo "· $volume — no such volume, skipping (nothing to copy)"