docs(PROJ-66): Backup & Restore Runbook schreiben
docs/BACKUP_RESTORE_RUNBOOK.md deckt beide Sicherungsebenen ab: App-eigenes CLI-Backup (archivmail backup/restore, PROJ-66) und Infrastruktur-Ebene (PBS + Sync auf Zweitserver, pct restore). Enthaelt Bitwarden-Keyfile- Escrow-Anleitung, Pflicht-Verifikationsschritte (reconcile/reindex/ Stichprobe), Rotation-vs-DSGVO-Hinweis und den offenen Punkt "noch kein Restore-Test durchgefuehrt" als groesste verbleibende Luecke. AC 8 aus PROJ-66 damit erfuellt; AC 6 (Restore-Test) und AC 7 (Alerting) bleiben offen.
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
# Backup & Restore Runbook – archivmail
|
||||
|
||||
Bezug: `features/PROJ-66-backup-strategie.md` (Spec + Entscheidungsverlauf),
|
||||
`docs/GOBD_DSGVO_CHECKLIST.md` Punkt 7 (Löschsperre) und 15 (Mandantentrennung).
|
||||
Ziel: belegen können, dass ein Totalausfall von Store, Keyfile oder
|
||||
PostgreSQL nicht zu unwiederbringlichem Mail-Verlust führt — und dass ein
|
||||
Restore im Ernstfall tatsächlich funktioniert, nicht nur theoretisch existiert.
|
||||
|
||||
Gilt für: `archivmail backup`/`archivmail restore` (PROJ-66, App-eigenes
|
||||
CLI-Backup) und ergänzend die Proxmox Backup Server (PBS) + Sync-auf-
|
||||
Zweitserver-Sicherung auf Infrastruktur-Ebene.
|
||||
|
||||
## Grundsatz
|
||||
|
||||
Store, Keyfile und PostgreSQL-Metadaten sind **nur gemeinsam** nutzbar:
|
||||
- Store ohne DB: Dateien vorhanden, aber keine Zuordnung zu Mail/Tenant.
|
||||
- Keyfile ohne Store: irrelevant, nichts zu entschlüsseln.
|
||||
- DB ohne Keyfile: Metadaten da, Mail-Inhalte nicht mehr lesbar (GoBD-Verstoß).
|
||||
|
||||
Ein Backup, das nur eine dieser drei Komponenten sichert, ist wertlos. Ein
|
||||
Backup, das nie zurückgespielt wurde, ist nicht verifiziert — siehe
|
||||
"Regelmäßiger Restore-Test" unten.
|
||||
|
||||
## Zwei unabhängige Sicherungsebenen
|
||||
|
||||
1. **Infrastruktur-Ebene (PBS + Sync auf Zweitserver)** — läuft vom
|
||||
Proxmox-Host aus, sichert den kompletten Container (Store+Keyfile+
|
||||
Config+PostgreSQL-Datendir+Audit-Log in einem Snapshot). Von innerhalb
|
||||
des Containers nicht sichtbar/steuerbar. Siehe Memory
|
||||
`project_backup_infra_pbs` für CLI-Befehle (`vzdump`, `pct restore`).
|
||||
2. **App-eigenes Backup (`archivmail backup`/`restore`)** — ergänzt Ebene 1
|
||||
um einen anwendungskonsistenten `pg_dump` (statt reinem Crash-Consistency-
|
||||
Snapshot) und einen selektiven, von PBS unabhängigen Restore-Weg.
|
||||
|
||||
Keine der beiden Ebenen ersetzt die andere. Ein vollständiger Restore-Test
|
||||
sollte mindestens einmal **beide** Wege durchlaufen haben (siehe unten).
|
||||
|
||||
## Backup erstellen
|
||||
|
||||
```bash
|
||||
archivmail backup --config /etc/archivmail/config.yml --dest <ziel> --keep 14
|
||||
```
|
||||
|
||||
- `<ziel>` MUSS auf einem vom Produktivsystem physisch getrennten Ziel
|
||||
liegen (zweiter Host, NAS, o.ä.) — siehe PROJ-66-Spec, Abschnitt
|
||||
"Backup-Ziel: On-Site vs. Off-Site". Ein Backup auf derselben Platte
|
||||
schützt nicht vor Hardware-Ausfall.
|
||||
- Schreibt `<ziel>/<timestamp>/`: `postgres.dump` (`pg_dump -Fc`), `store/`
|
||||
(Hardlink-erhaltende Kopie, PROJ-65-Tenant-Ordner bleiben ohne
|
||||
Speicherplatz-Verdopplung erhalten), `keyfile`, `config.yml`, `audit.log`
|
||||
(best-effort).
|
||||
- `--keep N` behält die letzten N Backup-Verzeichnisse, löscht ältere —
|
||||
**nur nach erfolgreichem Lauf**. Ein fehlgeschlagener Lauf lässt den
|
||||
partiellen Ordner stehen und rotiert nichts weg (Diagnose-Möglichkeit,
|
||||
kein Verlust eines guten alten Backups).
|
||||
- Cron-Aktivierung: `deploy/cron.d/archivmail` enthält eine auskommentierte
|
||||
Beispielzeile. Erst aktivieren, wenn ein konkretes `--dest`-Ziel feststeht.
|
||||
|
||||
## Keyfile-Escrow (Bitwarden)
|
||||
|
||||
Zusätzlich zum Backup: das Keyfile als unabhängige Kopie in Bitwarden
|
||||
hinterlegen (Secure Note, eigene Collection mit eingeschränktem Zugriff —
|
||||
das Keyfile entschlüsselt das **gesamte Archiv aller Mandanten**).
|
||||
|
||||
```bash
|
||||
cat /etc/archivmail/keyfile
|
||||
# → Wert 1:1 in eine neue Bitwarden Secure Note kopieren, Metadaten
|
||||
# ergänzen (Server/Installation, Datum, wer hinterlegt hat).
|
||||
```
|
||||
|
||||
Zweck: unabhängiger Wiederherstellungsweg, falls PBS/Snapshot-Infrastruktur
|
||||
selbst nicht verfügbar ist. Ersetzt nicht das App-Backup — beide parallel
|
||||
pflegen. Bei künftiger Keyfile-Rotation (aktuell nicht vorgesehen) den
|
||||
Bitwarden-Eintrag im selben Schritt aktualisieren.
|
||||
|
||||
## Restore durchführen
|
||||
|
||||
**Vorbereitung:**
|
||||
1. Zielsystem: frischer Server/Container oder vorhandenes System im
|
||||
Wartungsmodus. `archivmail`/`archivmail-web` müssen gestoppt sein —
|
||||
`archivmail restore` stoppt/startet die Dienste NICHT selbst.
|
||||
2. Backup-Verzeichnis identifizieren: `<ziel>/<timestamp>/`.
|
||||
|
||||
**Durchführung:**
|
||||
|
||||
```bash
|
||||
systemctl stop archivmail archivmail-web
|
||||
|
||||
archivmail restore --config /etc/archivmail/config.yml \
|
||||
--source <ziel>/<timestamp> [--force]
|
||||
```
|
||||
|
||||
- Ohne `--force`: bricht ab, wenn `store_path`/Keyfile bereits Inhalt haben
|
||||
— Schutz gegen versehentliches Überschreiben eines laufenden Systems.
|
||||
`--force` nur auf einem bewusst freigeräumten/frischen Zielsystem nutzen.
|
||||
- `--skip-db`: überspringt `pg_restore`, falls die Datenbank bereits separat
|
||||
wiederhergestellt wurde (z.B. via PBS-Snapshot der DB-Ebene).
|
||||
- Stellt Store (Hardlinks erhalten), Keyfile, Config und PostgreSQL
|
||||
(`pg_restore --clean --if-exists`) wieder her. Gibt am Ende die
|
||||
Pflicht-Verifikationsschritte aus (siehe unten).
|
||||
|
||||
**Verifikation (Pflicht, nicht optional):**
|
||||
|
||||
```bash
|
||||
systemctl start archivmail archivmail-web
|
||||
# Health-Check wie im Standard-Deploy-Workflow (Backend ✓, Frontend ✓)
|
||||
|
||||
archivmail reconcile --days 7 # PROJ-52: Zählvergleich Store vs. DB muss aufgehen
|
||||
archivmail reindex # Manticore aus Store+DB neu aufbauen
|
||||
# Stichprobe: 3-5 zufällige Mails über die UI öffnen und lesbar entschlüsseln
|
||||
# lassen — beweist, dass Keyfile+Store+DB zueinander passen.
|
||||
```
|
||||
|
||||
Restore-Ergebnis (Mail-Zahl, Zeitstempel, Dauer, welche Verifikationsschritte
|
||||
liefen) dokumentieren — Prinzip "Zählen, Prüfen, Belegen" wie im
|
||||
Migrations-Runbook.
|
||||
|
||||
## Restore aus einem PBS-Snapshot (Infrastruktur-Ebene)
|
||||
|
||||
Auf dem Proxmox-**Host** (nicht im Container):
|
||||
|
||||
```bash
|
||||
pct restore <neue-id> <pbs-storage>:backup/ct/<quell-id>/<timestamp> --storage <ziel-storage>
|
||||
```
|
||||
|
||||
Restored als neuer Container mit `<neue-id>` — der Quell-Container bleibt
|
||||
unangetastet. Danach dieselbe Verifikation wie oben (`reconcile`, `reindex`,
|
||||
Stichprobe) im neuen Container durchführen. PostgreSQL kam dabei aus einem
|
||||
Crash-Consistency-Snapshot, nicht aus einem `pg_dump` — WAL-Replay beim
|
||||
Start ist der Normalfall, sollte aber beim Test explizit beobachtet werden
|
||||
(`journalctl -u postgresql` auf Recovery-Meldungen prüfen).
|
||||
|
||||
## Regelmäßiger Restore-Test
|
||||
|
||||
Ein Backup, das nie zurückgespielt wurde, ist nicht verifiziert. Empfehlung:
|
||||
mindestens vierteljährlich einen vollständigen Restore auf einen isolierten
|
||||
Test-Container durchführen (**nicht auf 131/132!**) — einmal über
|
||||
`archivmail restore` (App-Backup) und mindestens einmal über `pct restore`
|
||||
(PBS-Snapshot), jeweils mit vollständiger Verifikation. Bislang (Stand
|
||||
2026-07-05) wurde noch kein Restore-Test durchgeführt — das ist die
|
||||
kritischste offene Lücke in der Backup-Strategie, nicht das Fehlen eines
|
||||
Backup-Ziels.
|
||||
|
||||
## Rotation vs. DSGVO-Löschung
|
||||
|
||||
Backup-Rotation (`--keep N`, bzw. PBS-Retention) ist unabhängig von der
|
||||
GoBD-Aufbewahrungsfrist der Mails selbst (PROJ-51). Eine in Produktion
|
||||
bereits DSGVO-gelöschte Mail (PROJ-50) kann in einem älteren Backup noch
|
||||
enthalten sein. Rotation muss kurz genug sein, dass gelöschte Daten nach
|
||||
spätestens N Zyklen auch aus Backups verschwinden — Zeitraum mit
|
||||
Datenschutzbeauftragtem/Nutzer abstimmen, kein rein technisches Thema.
|
||||
|
||||
## Monitoring / Alerting (noch offen)
|
||||
|
||||
Größtes Risiko bei Cron-Backups: der Job schlägt still fehl, niemand merkt
|
||||
es bis zum Ernstfall (Lehre aus PROJ-58: Cron-Zeilen fehlten wochenlang
|
||||
trotz aktivem Code). Aktuell **nicht gebaut**: ein sichtbarer Alarm, wenn
|
||||
das letzte erfolgreiche Backup älter als ein Schwellenwert ist. Bis dahin:
|
||||
Log-Datei des Backup-Cron-Jobs (`/var/log/archivmail/backup.log`, sobald der
|
||||
Cron aktiviert ist) manuell im Rahmen der regulären Betriebs-Checks prüfen.
|
||||
|
||||
## Verweis
|
||||
|
||||
- `features/PROJ-66-backup-strategie.md` — vollständige Spec, Optionsabwägung,
|
||||
QA-Ergebnisse.
|
||||
- `docs/MIGRATION_RUNBOOK.md` — Vorbild für den "Zählen, Prüfen, Belegen"-Stil.
|
||||
- Memory `project_backup_infra_pbs` — PBS/Sync-Infrastruktur-Details,
|
||||
`vzdump`/`pct restore`-Befehle.
|
||||
- Memory `project_132_teilproduktiv` — 132 ist kein reiner Testserver,
|
||||
Backup-Sorgfalt gilt dort genauso wie auf 131.
|
||||
Reference in New Issue
Block a user