deploy/truenas/compose.yaml:
- 4 services: db (postgres:17), api (.NET), frontend (nginx), backup sidecar
- Named volumes as NAS bind-mounts (paths configured in .env)
- Healthchecks: db pg_isready, api /health, frontend waits on api
- ANTHROPIC_API_KEY placeholder for FEAT-12a AI stub
deploy/truenas/.env.example: POSTGRES_PASSWORD, PORT, REGISTRY, dataset paths
deploy/truenas/scripts/:
- entrypoint.sh: installs daily 03:00 cron, starts crond in foreground
- backup.sh: pg_dump -> .sql + tar czf photos -> .tar.gz + rotation
- restore.sh: psql < dump + tar xzf photos; confirmation prompt (-f to skip)
Predecessor logic (commit 6a37f9e) translated from PowerShell to POSIX sh
for Alpine containers. Same pg_dump/restore semantics, same KEEP_DAYS rotation.
.gitea/workflows/ci.yml (DRAFT -- inactive until Julian confirms Actions):
dotnet test + npm test/build on push/PR to main;
docker build+push to Gitea registry on main push only
docs/ops.md:
German ops guide -- architecture diagram, first-install checklist,
start/stop/update commands, backup runbook (manual + auto), ZFS-snapshot
layering strategy, CI activation steps, open questions, troubleshooting table
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
78 lines
2.5 KiB
Bash
78 lines
2.5 KiB
Bash
#!/bin/sh
|
|
# GerbilManager Wiederherstellung (Container-Welt)
|
|
# Aufruf: docker compose -f deploy/truenas/compose.yaml exec backup /bin/sh /scripts/restore.sh
|
|
# Optional: .../restore.sh 2026-06-06_03-00 (bestimmtes Backup)
|
|
#
|
|
# WARNUNG: Ueberschreibt alle aktuellen Datenbankdaten und Fotos!
|
|
set -e
|
|
|
|
HOST="${POSTGRES_HOST:-db}"
|
|
USER="${POSTGRES_USER:-postgres}"
|
|
DB="${POSTGRES_DB:-gerbilmanager}"
|
|
BACKUP_ROOT=/backups
|
|
|
|
log() { echo "[$(date +'%H:%M:%S')] $1"; }
|
|
|
|
# --- Backup-Verzeichnis bestimmen ---
|
|
if [ -n "$1" ]; then
|
|
BACKUP_DIR="$BACKUP_ROOT/$1"
|
|
else
|
|
BACKUP_DIR=$(find "$BACKUP_ROOT" -maxdepth 1 -type d -name '????-??-??_??-??' \
|
|
| sort -r | head -1)
|
|
fi
|
|
|
|
if [ -z "$BACKUP_DIR" ] || [ ! -d "$BACKUP_DIR" ]; then
|
|
log "FEHLER: Backup-Verzeichnis nicht gefunden: ${BACKUP_DIR:-$BACKUP_ROOT}"
|
|
log "Verfuegbare Backups:"
|
|
find "$BACKUP_ROOT" -maxdepth 1 -type d -name '????-??-??_??-??' | sort -r
|
|
exit 1
|
|
fi
|
|
|
|
SQL_FILE=$(find "$BACKUP_DIR" -name '*.sql' | head -1)
|
|
PHOTO_ARCHIVE=$(find "$BACKUP_DIR" -name '*.tar.gz' | head -1)
|
|
|
|
if [ -z "$SQL_FILE" ]; then
|
|
log "FEHLER: Kein SQL-Dump in $BACKUP_DIR gefunden."
|
|
exit 1
|
|
fi
|
|
|
|
log "Wiederherstellung aus: $BACKUP_DIR"
|
|
log " Datenbank : $(basename $SQL_FILE)"
|
|
[ -n "$PHOTO_ARCHIVE" ] && log " Fotos : $(basename $PHOTO_ARCHIVE)"
|
|
|
|
# --- Bestaetigung (interaktiv; mit -f ueberspringen) ---
|
|
if [ "$1" != "-f" ] && [ "$2" != "-f" ]; then
|
|
printf "WARNUNG: Aktuelle Daten werden unwiderruflich ueberschrieben!\nFortfahren? (ja/nein): "
|
|
read -r CONFIRM
|
|
if [ "$CONFIRM" != "ja" ]; then
|
|
log "Abgebrochen."
|
|
exit 0
|
|
fi
|
|
fi
|
|
|
|
# --- Datenbankwiederherstellung ---
|
|
log "Trenne laufende DB-Verbindungen..."
|
|
psql -h "$HOST" -U "$USER" -d postgres -c \
|
|
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname='$DB' AND pid<>pg_backend_pid();" \
|
|
> /dev/null 2>&1 || true
|
|
|
|
log "Spiele SQL-Dump ein..."
|
|
if ! psql -h "$HOST" -U "$USER" -d "$DB" < "$SQL_FILE" > /tmp/restore_out 2>&1; then
|
|
log "FEHLER bei der DB-Wiederherstellung:"
|
|
cat /tmp/restore_out
|
|
exit 1
|
|
fi
|
|
log "Datenbankwiederherstellung erfolgreich."
|
|
|
|
# --- Fotos wiederherstellen ---
|
|
if [ -n "$PHOTO_ARCHIVE" ]; then
|
|
log "Stelle Fotos wieder her..."
|
|
rm -rf /data/photos/* 2>/dev/null || true
|
|
tar xzf "$PHOTO_ARCHIVE" -C /data
|
|
PHOTO_COUNT=$(find /data/photos -type f 2>/dev/null | wc -l)
|
|
log "Fotos wiederhergestellt: $PHOTO_COUNT Dateien."
|
|
fi
|
|
|
|
log "Wiederherstellung abgeschlossen."
|
|
log "Starte API-Container neu: docker compose ... restart api"
|