diff --git a/.env.example b/.env.example index 172b887..7d09efe 100644 --- a/.env.example +++ b/.env.example @@ -2,5 +2,13 @@ DATABASE_URL=postgres://USER@localhost:5432/stl_library 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_TOKEN= diff --git a/README.md b/README.md index edfb6a7..36fcc64 100644 --- a/README.md +++ b/README.md @@ -59,17 +59,22 @@ Run the unit tests with `npm test`. ### 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 | |---|---| -| `data/pgdata` | 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/thumbnails` | Generated pictures, including plate images | -| `data/glb` | Converted 3D previews for the viewer | -| `data/extracted` | Files unpacked from archives — a cache | +| `$PGDATA_DIR` | The database: models, tags, collections, watches, spools, printers, settings (tokens and printer access codes live here) | +| `$DATA_DIR/uploads` | Your model files, as uploaded or downloaded | +| `$DATA_DIR/thumbnails` | Generated pictures, including plate images | +| `$DATA_DIR/glb` | Converted 3D previews for the viewer | +| `$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. Upgrading from a version that used Docker named volumes: stop the stack, run `tools/volumes-to-folders.sh` to copy the diff --git a/docker-compose.yml b/docker-compose.yml index 968d491..1c34ddd 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,11 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-printhive} POSTGRES_DB: printhive 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: test: ["CMD-SHELL", "pg_isready -U printhive -d printhive"] interval: 5s @@ -33,7 +37,7 @@ services: MAKERWORLD_TOKEN: ${MAKERWORLD_TOKEN:-} PRINTABLES_TOKEN: ${PRINTABLES_TOKEN:-} 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. - ${DATA_DIR:-./data}/uploads:/app/uploads - ${DATA_DIR:-./data}/thumbnails:/app/thumbnails diff --git a/tools/volumes-to-folders.sh b/tools/volumes-to-folders.sh index cbef307..ab9b55f 100755 --- a/tools/volumes-to-folders.sh +++ b/tools/volumes-to-folders.sh @@ -8,9 +8,23 @@ # again with nothing lost. 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}" +PGDATA_DIR="${PGDATA_DIR:-./data/pgdata}" 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 "_", the project being this folder's name # unless it's set elsewhere. PROJECT="${COMPOSE_PROJECT_NAME:-$(basename "$(pwd)")}" @@ -20,13 +34,14 @@ if docker compose ps --status running --quiet 2>/dev/null | grep -q .; then exit 1 fi -echo "Project: $PROJECT" -echo "Copying into: $DATA_DIR" +echo "Project: $PROJECT" +echo "Files: $DATA_DIR" +echo "Database: $PGDATA_DIR" echo for name in "${VOLUMES[@]}"; do volume="${PROJECT}_${name}" - target="$DATA_DIR/$name" + target="$(target_for "$name")" if ! docker volume inspect "$volume" >/dev/null 2>&1; then echo "· $volume — no such volume, skipping (nothing to copy)"