Zuletzt aktualisiert:

Runbook: Off-Site-Backup (restic + backrest → Strato HiDrive S3)

Ziel: Bei VPS-Totalverlust ist alles aus einer verschlüsselten, versionierten, externen Quelle wiederherstellbar. Eingeführt mit SILV-185 (11.06.2026).

Additiv, nicht ersetzend. Die lokalen Backups bleiben der schnelle Restore-Pfad: silverscale.db .backup 03:30 (7-Tage-Wochentag-Rotation) + Plane pg_dump+MinIO-Tar 03:35 (app/agents/plane-backup.sh). restic/backrest ist der DR-Pfad off-site.

Architektur (privilegienarm — kein docker.sock)

03:30 silverscale.db  →  app/data/backups/silverscale-{1..7}.db   (lokal, konsistent)
03:35 plane-backup.sh →  /home/dennis/plane/backups/*.sql.gz,*.tar.gz (lokal, atomar)
03:50 backrest (im Container, eigener Scheduler)
        → restic backup /userdata  →  s3:s3.hidrive.strato.com/silverscale-backup/main
        → Hook (CONDITION_SNAPSHOT_SUCCESS): app/data/offsite-last-backup.txt (ISO-Heartbeat)

backrest spiegelt die bereits konsistent erzeugten lokalen Dumps + Secrets als read-only File-Mounts (kein direkter DB-Zugriff, kein docker.sock). restic verschlüsselt + dedupliziert + inkrementell. Retention 7d/4w/12m, Prune + Check monatlich (1. des Monats 04:00/05:00).

Was gesichert wird (Container-Pfad /userdata)

Quelle (Host) Mount Inhalt
./app/data (rw¹) /userdata/app-data DB-Day-Files (backups/), HF-/eGYM-/Garmin-Marker, APNs-.p8
/home/dennis/plane/backups (ro) /userdata/plane-backups Plane Postgres-Dump + MinIO-Uploads
./pocket-id-data (ro) /userdata/pocket-id Pocket-ID-DB + Uploads
./.env (ro) /userdata/secrets/root.env POCKET_ID_ENCRYPTION_KEY, VAPID, alle Secrets
./lidl-grocy/.env (ro) /userdata/secrets/lidl.env LIDL_REFRESH_TOKEN
/home/dennis/plane/plane.env (ro) /userdata/secrets/plane.env Plane-Postgres-Passwort
./traefik/acme.json (ro) /userdata/secrets/acme.json Let's-Encrypt-Zertifikate

¹ app/data rw nur, damit der Erfolgs-Hook den Heartbeat schreiben kann. Die Live-DB (silverscale.db/-wal/-shm) ist im Plan exkludiert — gesichert wird das konsistente 03:30-Day-File, nicht die offene Datei.

Secrets / Zugänge (NICHT im Repo)

UI / Zugriff

backrest-Web-UI: http://127.0.0.1:9898 bzw. über WireGuard http://192.168.3.8:9898 (kein öffentlicher Traefik-Router — Admin-Tool, WG/127.0.0.1-only). Login: dennis.

Health / Observability (kein blindes Silo)

Der Erfolgs-Hook schreibt app/data/offsite-last-backup.txt (ISO). /api/status liefert last_offsite_backup_at + last_offsite_backup_age_s + Schwelle offsite_backup_s (36 h). Der Maintenance-Sweep (03:40, SILV-139) alarmiert bei Stillstand (>30 h) per Push + Bus — deckt S3-Ausfall, abgelaufene Creds und einen toten backrest gleichermaßen ab.

Restore (getestet 11.06.2026 — integrity_check: ok, .env byte-identisch)

# Passwort aus der .env; Repo + Creds kommen aus dem backrest-Container-Env.
RPW=$(grep '^RESTIC_PASSWORD=' /home/dennis/silverscale/.env | cut -d= -f2-)
REPO="s3:s3.hidrive.strato.com/silverscale-backup/main"

# Snapshots listen
docker exec backrest sh -c "RESTIC_PASSWORD='$RPW' RESTIC_REPOSITORY='$REPO' /bin/restic snapshots"

# Kompletten letzten Snapshot zurückholen
docker exec backrest sh -c "RESTIC_PASSWORD='$RPW' RESTIC_REPOSITORY='$REPO' \
  /bin/restic restore latest --target /tmp/restore"

# Einzelne Datei (z. B. die DB von Dienstag) gezielt holen
docker exec backrest sh -c "RESTIC_PASSWORD='$RPW' RESTIC_REPOSITORY='$REPO' \
  /bin/restic restore latest --target /tmp/restore --include /userdata/app-data/backups/silverscale-2.db"

Wiederanlauf-Reihenfolge bei VPS-Tod (Skizze → SILV-134 macht daraus das volle DR-Runbook): 1. Neuer Host, Docker, Repo klonen. RESTIC_PASSWORD + S3-Creds aus dem Passwort-Manager → .env. 2. restic restore latestsecrets/* zurück (.env, plane.env, acme.json, lidl). 3. pocket-id/ + app-data/ zurücklegen; jüngstes silverscale-{DOW}.dbapp/data/silverscale.db. 4. Plane: jüngsten plane-pg-*.sql.gz in eine frische Plane-PG laden, plane-uploads-*.tar.gz ins MinIO-Volume. 5. docker compose up -d; Pocket-ID prüft Login; App /healthz grün.

Troubleshooting