diff --git a/README.md b/README.md index d35c184..edfb6a7 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,24 @@ A self-hosted web app for organizing your 3D-printing files (STL, 3MF, OBJ, FBX, 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): + +| 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 | + +A backup needs `data/pgdata` (or a `pg_dump`) and `data/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 +data across, then start it again. The script only copies — the old volumes stay until you remove them yourself. + ## Configuration Settings live in the database (editable in the app's **Settings** menu) and fall back to environment variables: diff --git a/docker-compose.yml b/docker-compose.yml index 1ae9204..968d491 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,7 +6,7 @@ services: POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-printhive} POSTGRES_DB: printhive volumes: - - pgdata:/var/lib/postgresql/data + - ${DATA_DIR:-./data}/pgdata:/var/lib/postgresql/data healthcheck: test: ["CMD-SHELL", "pg_isready -U printhive -d printhive"] interval: 5s @@ -33,10 +33,12 @@ services: MAKERWORLD_TOKEN: ${MAKERWORLD_TOKEN:-} PRINTABLES_TOKEN: ${PRINTABLES_TOKEN:-} volumes: - - uploads:/app/uploads - - thumbnails:/app/thumbnails - - glb:/app/glb - - extracted:/app/extracted # cache of files unpacked from archives + # Folders on the host, under ./data (set DATA_DIR to put them elsewhere). + # uploads and pgdata hold everything that matters; the other two rebuild. + - ${DATA_DIR:-./data}/uploads:/app/uploads + - ${DATA_DIR:-./data}/thumbnails:/app/thumbnails + - ${DATA_DIR:-./data}/glb:/app/glb + - ${DATA_DIR:-./data}/extracted:/app/extracted # cache of files unpacked from archives # No host port published — Traefik auto-discovers the app on the shared network. networks: - default # talk to the db @@ -52,13 +54,6 @@ services: - "traefik.http.routers.printhive.tls.certresolver=${TRAEFIK_CERTRESOLVER:-le}" - "traefik.http.services.printhive.loadbalancer.server.port=3000" -volumes: - pgdata: - uploads: - thumbnails: - glb: - extracted: - networks: default: # The existing Traefik network (the `proxy` network Traefik runs on). diff --git a/tools/volumes-to-folders.sh b/tools/volumes-to-folders.sh new file mode 100755 index 0000000..cbef307 --- /dev/null +++ b/tools/volumes-to-folders.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash +# Copies Printhive's data out of Docker's named volumes into ./data, for the +# switch to host folders. Run it on the server, from the folder holding +# docker-compose.yml, with the stack stopped. +# +# It only copies: the named volumes are left exactly as they were, so if +# anything looks wrong you can put the old docker-compose.yml back and start +# again with nothing lost. +set -euo pipefail + +DATA_DIR="${DATA_DIR:-./data}" +VOLUMES=(pgdata uploads thumbnails glb extracted) + +# Compose names volumes "_", the project being this folder's name +# unless it's set elsewhere. +PROJECT="${COMPOSE_PROJECT_NAME:-$(basename "$(pwd)")}" + +if docker compose ps --status running --quiet 2>/dev/null | grep -q .; then + echo "Printhive is still running. Stop it first: docker compose down" >&2 + exit 1 +fi + +echo "Project: $PROJECT" +echo "Copying into: $DATA_DIR" +echo + +for name in "${VOLUMES[@]}"; do + volume="${PROJECT}_${name}" + target="$DATA_DIR/$name" + + if ! docker volume inspect "$volume" >/dev/null 2>&1; then + echo "· $volume — no such volume, skipping (nothing to copy)" + mkdir -p "$target" + continue + fi + if [ -d "$target" ] && [ -n "$(ls -A "$target" 2>/dev/null)" ]; then + echo "· $target already has files in it — skipping, so nothing is overwritten" >&2 + continue + fi + + mkdir -p "$target" + # -a keeps ownership and timestamps, which Postgres's data folder needs. + docker run --rm \ + -v "$volume":/from:ro \ + -v "$(cd "$(dirname "$target")" && pwd)/$(basename "$target")":/to \ + alpine sh -c 'cp -a /from/. /to/' + echo "· $volume → $target ($(du -sh "$target" | cut -f1))" +done + +echo +echo "Done. Start it up again with: docker compose up -d" +echo "Once you're happy, the old volumes can be removed with:" +for name in "${VOLUMES[@]}"; do echo " docker volume rm ${PROJECT}_${name}"; done