Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
commit
4956eabbf4
2 changed files with 40 additions and 15 deletions
|
|
@ -10,7 +10,7 @@ services:
|
||||||
# ownership and permissions (ext4, xfs, APFS...), which exFAT, FAT and
|
# ownership and permissions (ext4, xfs, APFS...), which exFAT, FAT and
|
||||||
# most network shares don't provide. Keep it on local disk if the files
|
# most network shares don't provide. Keep it on local disk if the files
|
||||||
# live somewhere like that.
|
# live somewhere like that.
|
||||||
- ${PGDATA_DIR:-./data/pgdata}:/var/lib/postgresql/data
|
- ${PGDATA_DIR:-/Volumes/Data/printhive/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
|
||||||
|
|
@ -37,12 +37,14 @@ services:
|
||||||
MAKERWORLD_TOKEN: ${MAKERWORLD_TOKEN:-}
|
MAKERWORLD_TOKEN: ${MAKERWORLD_TOKEN:-}
|
||||||
PRINTABLES_TOKEN: ${PRINTABLES_TOKEN:-}
|
PRINTABLES_TOKEN: ${PRINTABLES_TOKEN:-}
|
||||||
volumes:
|
volumes:
|
||||||
# Folders on the host: ./data unless DATA_DIR says otherwise.
|
# Absolute on purpose: a relative path resolves against whatever folder
|
||||||
|
# runs compose, which isn't this one when a stack manager deploys it —
|
||||||
|
# and an empty folder makes a working install look wiped.
|
||||||
# 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:-/Volumes/Data/printhive}/uploads:/app/uploads
|
||||||
- ${DATA_DIR:-./data}/thumbnails:/app/thumbnails
|
- ${DATA_DIR:-/Volumes/Data/printhive}/thumbnails:/app/thumbnails
|
||||||
- ${DATA_DIR:-./data}/glb:/app/glb
|
- ${DATA_DIR:-/Volumes/Data/printhive}/glb:/app/glb
|
||||||
- ${DATA_DIR:-./data}/extracted:/app/extracted # cache of files unpacked from archives
|
- ${DATA_DIR:-/Volumes/Data/printhive}/extracted:/app/extracted # cache of files unpacked from archives
|
||||||
# No host port published — Traefik auto-discovers the app on the shared network.
|
# No host port published — Traefik auto-discovers the app on the shared network.
|
||||||
networks:
|
networks:
|
||||||
- default # talk to the db
|
- default # talk to the db
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
#
|
#
|
||||||
# Settings (in .env beside docker-compose.yml, or the environment):
|
# Settings (in .env beside docker-compose.yml, or the environment):
|
||||||
# BACKUP_DIR where backups go (default: ./backups)
|
# BACKUP_DIR where backups go (default: ./backups)
|
||||||
# DATA_DIR where the data lives (default: ./data)
|
# UPLOADS_DIR the model files (default: asked of the running container)
|
||||||
# KEEP_DAYS dumps to keep, in days (default: 14)
|
# KEEP_DAYS dumps to keep, in days (default: 14)
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
|
@ -21,7 +21,7 @@ setting() { # name, fallback
|
||||||
if [ -n "$from_env" ]; then echo "$from_env"; return; fi
|
if [ -n "$from_env" ]; then echo "$from_env"; return; fi
|
||||||
if [ -f .env ]; then
|
if [ -f .env ]; then
|
||||||
local line
|
local line
|
||||||
line="$(sed -e 's/\r$//' .env \
|
line="$(tr -d '\r' < .env \
|
||||||
| grep -E "^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=" \
|
| grep -E "^[[:space:]]*(export[[:space:]]+)?$1[[:space:]]*=" \
|
||||||
| tail -1 | cut -d= -f2- \
|
| tail -1 | cut -d= -f2- \
|
||||||
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'$/\1/")"
|
| sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/^"\(.*\)"$/\1/' -e "s/^'\(.*\)'$/\1/")"
|
||||||
|
|
@ -31,16 +31,31 @@ setting() { # name, fallback
|
||||||
}
|
}
|
||||||
|
|
||||||
BACKUP_DIR="$(setting BACKUP_DIR ./backups)"
|
BACKUP_DIR="$(setting BACKUP_DIR ./backups)"
|
||||||
DATA_DIR="$(setting DATA_DIR ./data)"
|
|
||||||
KEEP_DAYS="$(setting KEEP_DAYS 14)"
|
KEEP_DAYS="$(setting KEEP_DAYS 14)"
|
||||||
stamp="$(date +%Y-%m-%d)"
|
stamp="$(date +%Y-%m-%d)"
|
||||||
|
|
||||||
echo "$(date '+%F %T') data: $DATA_DIR → backups: $BACKUP_DIR"
|
# Where the model files really are: ask the running container, rather than
|
||||||
|
# trusting a setting that may have drifted from how the stack was deployed.
|
||||||
|
UPLOADS_DIR="${UPLOADS_DIR:-}"
|
||||||
|
if [ -z "$UPLOADS_DIR" ]; then
|
||||||
|
UPLOADS_DIR="$(docker inspect printhive-app \
|
||||||
|
--format '{{range .Mounts}}{{if eq .Destination "/app/uploads"}}{{.Source}}{{end}}{{end}}' 2>/dev/null || true)"
|
||||||
|
fi
|
||||||
|
if [ -z "$UPLOADS_DIR" ]; then
|
||||||
|
echo "$(date '+%F %T') FAILED: couldn’t find where /app/uploads comes from — is printhive-app running?" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
# Checked before anything is written: on an external disk that isn't mounted,
|
echo "$(date '+%F %T') files: $UPLOADS_DIR → backups: $BACKUP_DIR"
|
||||||
# the folder is simply missing, and a backup of nothing is worse than none.
|
|
||||||
if [ ! -d "$DATA_DIR/uploads" ]; then
|
# A path the container can see but this machine can't (a stack manager deploying
|
||||||
echo "$(date '+%F %T') FAILED: no $DATA_DIR/uploads — is the disk mounted, and is DATA_DIR set in .env?" >&2
|
# from inside a container does that) would back up nothing at all.
|
||||||
|
if [ ! -d "$UPLOADS_DIR" ]; then
|
||||||
|
echo "$(date '+%F %T') FAILED: $UPLOADS_DIR isn’t reachable from here — set UPLOADS_DIR to its path on this machine" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "$(ls -A "$UPLOADS_DIR" 2>/dev/null)" ]; then
|
||||||
|
echo "$(date '+%F %T') FAILED: $UPLOADS_DIR is empty — refusing to mirror nothing over a good backup" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
@ -49,6 +64,14 @@ mkdir -p "$BACKUP_DIR/db"
|
||||||
# --- the database -----------------------------------------------------------
|
# --- the database -----------------------------------------------------------
|
||||||
# Written to a .part file first, so an interrupted run never leaves behind
|
# Written to a .part file first, so an interrupted run never leaves behind
|
||||||
# something that looks like a good backup.
|
# something that looks like a good backup.
|
||||||
|
# An empty library where backups already exist means something is wrong with
|
||||||
|
# the stack, not that you deleted everything: don't write that over good ones.
|
||||||
|
models="$(docker compose exec -T db psql -U printhive -d printhive -tAc 'select count(*) from models' 2>/dev/null | tr -d '[:space:]' || true)"
|
||||||
|
if [ "$models" = "0" ] && ls "$BACKUP_DIR"/db/printhive-*.dump >/dev/null 2>&1; then
|
||||||
|
echo "$(date '+%F %T') FAILED: the database has no models, but older backups do — leaving them alone" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
dump="$BACKUP_DIR/db/printhive-$stamp.dump"
|
dump="$BACKUP_DIR/db/printhive-$stamp.dump"
|
||||||
|
|
||||||
# Dumped inside the container and read back there — a dump that can't be listed
|
# Dumped inside the container and read back there — a dump that can't be listed
|
||||||
|
|
@ -76,5 +99,5 @@ find "$BACKUP_DIR/db" -name 'printhive-*.dump' -type f -mtime "+$KEEP_DAYS" -del
|
||||||
# --- the model files --------------------------------------------------------
|
# --- the model files --------------------------------------------------------
|
||||||
# A mirror, not a history: it matches what's there now, deletions included.
|
# A mirror, not a history: it matches what's there now, deletions included.
|
||||||
# Thumbnails, previews and extracted archives are left out — they rebuild.
|
# Thumbnails, previews and extracted archives are left out — they rebuild.
|
||||||
rsync -a --delete "$DATA_DIR/uploads/" "$BACKUP_DIR/uploads/"
|
rsync -a --delete "$UPLOADS_DIR/" "$BACKUP_DIR/uploads/"
|
||||||
echo "$(date '+%F %T') files → $BACKUP_DIR/uploads ($(du -sh "$BACKUP_DIR/uploads" | cut -f1))"
|
echo "$(date '+%F %T') files → $BACKUP_DIR/uploads ($(du -sh "$BACKUP_DIR/uploads" | cut -f1))"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue