Add phase 6: backup/restore scripts and full README

- backup.sh: pg_dump (custom format), media volume and .env in one
  archive with retention (KEEP), cron example
- restore.sh: confirmation, restores .env if missing, pg_restore
  --clean in one transaction, replaces media, restarts the app
- Fix: MEDIA_ROOT=/data/media in the image so photos land on the volume
- README: installation, Nginx/Cloudflare client-IP setup, configuration
  table, AI and Instagram notes, update, backup, troubleshooting, security
- Test settings independent of the local .env

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Kyle Dammann
2026-10-30 21:56:01 +01:00
co-authored by Claude Opus 5.5
parent 60785e2437
commit f96cbe965e
6 changed files with 305 additions and 31 deletions
+37
View File
@@ -0,0 +1,37 @@
#!/bin/sh
# Backup of database, photos and .env into one archive.
#
# ./scripts/backup.sh [target-dir] default target: ./backups
# KEEP=30 ./scripts/backup.sh keep the newest 30 archives (default 14)
#
# Cron example (daily 03:30):
# 30 3 * * * cd /opt/kochbuch && ./scripts/backup.sh /mnt/nas/kochbuch >> backups/backup.log 2>&1
set -eu
cd "$(dirname "$0")/.."
TARGET=${1:-./backups}
KEEP=${KEEP:-14}
STAMP=$(date +%Y%m%d-%H%M%S)
umask 077
mkdir -p "$TARGET"
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
echo "[$(date)] Sichere Datenbank …"
docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" --format=custom' > "$WORK/db.dump"
echo "[$(date)] Sichere Fotos …"
docker compose exec -T app tar -C /data -czf - media > "$WORK/media.tar.gz"
# .env contains the secrets needed to bring the backup up on a new server.
cp .env "$WORK/env.backup"
ARCHIVE="$TARGET/kochbuch-$STAMP.tar.gz"
tar -C "$WORK" -czf "$ARCHIVE" db.dump media.tar.gz env.backup
echo "[$(date)] Fertig: $ARCHIVE ($(du -h "$ARCHIVE" | cut -f1))"
# Retention: delete everything but the newest $KEEP archives.
ls -1t "$TARGET"/kochbuch-*.tar.gz 2>/dev/null | tail -n +"$((KEEP + 1))" | while read -r old; do
rm -f -- "$old"
echo "Altes Backup gelöscht: $old"
done
+44
View File
@@ -0,0 +1,44 @@
#!/bin/sh
# Restores an archive created by backup.sh. ALL current data is replaced.
#
# ./scripts/restore.sh backups/kochbuch-20261030-033000.tar.gz [--yes]
set -eu
cd "$(dirname "$0")/.."
ARCHIVE=${1:-}
if [ -z "$ARCHIVE" ] || [ ! -f "$ARCHIVE" ]; then
echo "Aufruf: $0 <backup.tar.gz> [--yes]" >&2
exit 1
fi
if [ "${2:-}" != "--yes" ]; then
printf "Alle aktuellen Rezepte, Nutzer und Fotos werden durch das Backup ersetzt. Fortfahren? [j/N] "
read -r answer
case "$answer" in j|J|ja|Ja) ;; *) echo "Abgebrochen."; exit 1 ;; esac
fi
WORK=$(mktemp -d)
trap 'rm -rf "$WORK"' EXIT
tar -C "$WORK" -xzf "$ARCHIVE"
if [ ! -f .env ]; then
cp "$WORK/env.backup" .env
chmod 600 .env
echo ".env aus dem Backup wiederhergestellt."
fi
echo "Starte Datenbank …"
docker compose up -d db
until docker compose exec -T db sh -c 'pg_isready -U "$POSTGRES_USER" -d "$POSTGRES_DB"' >/dev/null 2>&1; do sleep 2; done
echo "Stoppe App …"
docker compose stop app || true
echo "Spiele Datenbank ein …"
docker compose exec -T db sh -c 'pg_restore -U "$POSTGRES_USER" -d "$POSTGRES_DB" --clean --if-exists --no-owner --single-transaction' < "$WORK/db.dump"
echo "Spiele Fotos ein …"
docker compose run --rm --no-deps -T --entrypoint sh app -c 'find /data/media -mindepth 1 -delete && tar -C /data -xzf -' < "$WORK/media.tar.gz"
echo "Starte App …"
docker compose up -d app
echo "Wiederherstellung abgeschlossen."