2 Restore Mrrmlabapp DB
SurfaceScratcher edited this page 2026-05-18 15:35:00 +02:00

Restore: mrrmlabapp PostgreSQL-DB

Backup- und Restore-Pfad für die mrrmlabapp-Produktions-DB auf prod-alt.

Backup-Setup

  • Tool: prodrigestivill/postgres-backup-local:17-alpine als db-backup-Service im mrrmlabapp-Stack
  • Schedule: @daily (00:00, env: MRRMLABAPP_BACKUP_SCHEDULE)
  • Format: pg_dump | gzipmrrmlabapp-YYYYMMDD.sql.gz
  • Volume: mrrmlabapp_mrrmlabapp-db-backups → gemountet auf /backups im Container
  • Retention (env-konfigurierbar):
    • BACKUP_KEEP_DAYS=7 (täglich)
    • BACKUP_KEEP_WEEKS=4 (wöchentlich)
    • BACKUP_KEEP_MONTHS=6 (monatlich)
  • Healthcheck-Port: 8080 (Container-intern); fail wenn letztes Backup älter als erwartet
  • Restic: Volume /var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data ist in nas_backup_dirs (prod-alt) → tägliche restic-Sync auf QNAP
  • Monitoring: Uptime-Kuma-Push-Monitor mrrmlabapp-db-backup-prod auf status.mrrm.de (vault_mrrmlabapp_backup_webhook_urlMRRMLABAPP_BACKUP_WEBHOOK_URL); Container pingt nach jedem erfolgreichen Dump, Kuma alerted bei Ausbleiben (heartbeat 86400s, retry 300s)
  • Wichtig: WEBHOOK_EXTRA_ARGS=-X GET setzen, weil prodrigestivill/postgres-backup-local's /hooks/00-webhook hartcodiert --request POST macht, Kuma-Push aber GET erwartet

Backup-Schichten (Defense in Depth)

prod-alt postgres → db-backup Container → /backups Volume (lokal)
                                              ↓
                                        restic → QNAP-NAS (offsite-ish)

Restore-Test (Hash-Verify)

Verifiziert das Backup ohne Live-Stack zu berühren. Läuft auf dev-neu gegen den prod-alt-Snapshot.

Achtung: Das QNAP-NAS ist tagsüber ausgeschaltet (siehe Infrastruktur#Backup--Restore). Restore-Tests nur im Nacht-Fenster (ab ca. 03:30) oder NAS manuell hochfahren.

# 1. Neuesten prod-alt-Snapshot mit DB-Backups finden
. /etc/restic.env
SNAP=$(resticq snapshots \
  --host prod-alt \
  --path /var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data \
  --json 2>/dev/null \
  | python3 -c 'import json,sys; print(json.load(sys.stdin)[-1]["id"])')
echo "Snapshot: $SNAP"

# 2. Restore in Temp-Dir
TARGET=/tmp/restore-test-mrrmlabapp-db-$(date +%Y%m%d-%H%M%S)
resticq restore "$SNAP" \
  --target "$TARGET" \
  --include /var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data

BACKUP_DIR="$TARGET/var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data"
LATEST=$(ls -t "$BACKUP_DIR/daily/"*.sql.gz | head -1)
echo "Neuestes Dump: $LATEST ($(du -h "$LATEST" | cut -f1))"

# 3. Dump-Integrität: gzip + sha256
gunzip -t "$LATEST" && echo "✓ gzip intakt"
sha256sum "$LATEST"

# 4. Lade in temporären Postgres-Container
NET=$(docker network ls --filter name=mrrmlabapp -q | head -1)  # reuse Postgres-Netz falls vorhanden, sonst eigenes
docker run -d --name pg-restore-test \
  -e POSTGRES_PASSWORD=test \
  -e POSTGRES_DB=mrrmlabapp_restore \
  postgres:17-alpine
sleep 5

gunzip -c "$LATEST" | docker exec -i pg-restore-test \
  psql -U postgres -d mrrmlabapp_restore -v ON_ERROR_STOP=1 2>&1 | tail -5

# 5. Verifikations-Queries
docker exec pg-restore-test psql -U postgres -d mrrmlabapp_restore -t -c "
  SELECT 'tables: ' || count(*) FROM pg_tables WHERE schemaname='public';
  SELECT 'lists: ' || count(*) FROM lists;
  SELECT 'list_items: ' || count(*) FROM list_items;
  SELECT 'stores: ' || count(*) FROM stores;
  SELECT 'mail_accounts: ' || count(*) FROM mail_accounts;
  SELECT 'parcel_trackings: ' || count(*) FROM parcel_trackings;
"

# 6. Cleanup
docker rm -f pg-restore-test
rm -rf "$TARGET"

Erwartung: ≥10 Tabellen (lists, list_items, mail_accounts, mail_folders, mail_messages_cache, mail_tags, stores, order_infos, parcel_trackings, drizzle-Migrations-Meta), Row-Counts > 0 für aktive Tabellen.

Letzte erfolgreiche Restore-Tests

Datum Snapshot Dump-Datum Tabellen Lists/Items Ergebnis
(noch ausstehend, NAS-Fenster abwarten)

Echter Restore in Produktion

Wenn die prod-alt-DB verloren geht:

  1. API-Container stoppen (damit nichts schreibt):

    cd /opt/server-stack/hosts/prod-alt/mrrmlabapp
    docker compose stop api
    
  2. Neuesten Dump aus restic restoren (NAS-Fenster!):

    . /etc/restic.env
    SNAP=$(resticq snapshots --host prod-alt \
      --path /var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data \
      --json | python3 -c 'import json,sys; print(json.load(sys.stdin)[-1]["id"])')
    resticq restore "$SNAP" --target /tmp/db-restore \
      --include /var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data
    LATEST=$(ls -t /tmp/db-restore/var/lib/docker/volumes/mrrmlabapp_mrrmlabapp-db-backups/_data/daily/*.sql.gz | head -1)
    
  3. DB droppen + neu anlegen (Vorsicht, destruktiv!):

    docker exec mrrmlabapp-db psql -U mrrmlabapp -d postgres -c "DROP DATABASE mrrmlabapp;"
    docker exec mrrmlabapp-db psql -U mrrmlabapp -d postgres -c "CREATE DATABASE mrrmlabapp;"
    
  4. Dump einspielen:

    gunzip -c "$LATEST" | docker exec -i mrrmlabapp-db psql -U mrrmlabapp -d mrrmlabapp
    
  5. API wieder starten + verifizieren:

    docker compose start api
    docker compose logs -f api  # auf Migrations + Boot-Errors achten
    

Referenzen