# BAK-03 – Prüfprotokoll: Restore-Verfahren Voraussetzungen BAK-01, BAK-02 – erledigt, siehe eigene Protokolle. ## Grundsatzentscheidung: Atomarität über Temp-Verzeichnis + Rename `internal/restore.AtomicRestore` kennt weder Postgres noch restic — reine Ablaufsteuerung: Wiederherstellung IMMER in ein frisches temporäres Verzeichnis (nie direkt in das Ziel), Übernahme erst bei Erfolg per `os.Rename` (selbes Dateisystem wie das Ziel, daher atomar). Ist das Ziel nicht leer und keine Bestätigung (`-force`) gegeben, wird NICHT einmal die Wiederherstellungsfunktion aufgerufen — Abbruch vor jeder Berührung des Ziels (Akzeptanzkriterium 2). Jeder Aufruf — Erfolg, Abbruch oder Fehler — erzeugt genau einen JSONL-Protokolleintrag (Akzeptanzkriterium 3). Die eigentliche Wiederherstellung bleibt in `internal/backup.Restore` (Datenbank) und `internal/objectbackup.Restore` (Objekt-Storage) — `internal/restore` orchestriert nur. ## Drei reale Defekte während der Implementierung gefunden und behoben Alle drei erst durch den ECHTEN Restore-Test (Postgres-Instanz tatsächlich gestartet, nicht nur Dateien verglichen) aufgedeckt: 1. **`pg_combinebackup` braucht Plain-Format, BAK-01 liefert Tar+Gzip.** `Restore` extrahiert jetzt jede Sicherungsstufe zunächst in ein temporäres Plain-Verzeichnis (inkl. `backup_manifest`-Kopie) und speist erst DIESE in `pg_combinebackup`. BAK-01s Speicherformat selbst unverändert (kleinere, leichter prüfbare Dateien). 2. **`pg_wal.tar.gz` wurde nie verifiziert oder wiederhergestellt.** `pg_basebackup`s Standard-WAL-Methode (`stream`) erzeugt bei `-Ft -z` eine ZWEITE Archivdatei neben `base.tar.gz` — ohne sie ist keine crash-konsistente Wiederherstellung möglich (Postgres findet sonst keinen gültigen Checkpoint). `backup.Verify` prüft jetzt BEIDE Archive vollständig; `backup.Restore` extrahiert das WAL der ZULETZT gezogenen Stufe (nicht aller Stufen) nach `pg_wal/` im Wiederherstellungsziel. Ergänzung zu BAK-01s Prüfung, keine Umstellung des Sicherungsverfahrens. 3. **Go-`exec`-Hänger bei `pg_ctl start`:** `pg_ctl` startet Postgres als Hintergrundprozess, der die geerbten stdout/stderr-Pipes NICHT schließt — `CombinedOutput()` (wartet auf Pipe-EOF) hängt sich dadurch auf, obwohl `pg_ctl` selbst längst zurückgekehrt ist. Fix nur im Testcode: echte Logdatei (`-l`) statt Pipe, Standard-`pg_ctl`-Muster. Zusätzlich real (nicht Ticket-relevant, aber notiert): auf diesem Debian-Postgres liegen `postgresql.conf`/`pg_hba.conf` NICHT in PGDATA (sondern `/etc/postgresql/17/main/`) — ein echtes Restore-Runbook muss diese Dateien separat mitführen/rekonstruieren, `pg_basebackup` sichert sie nicht. Für den Testnachweis minimal nachgereicht, kein Produktcode. ## Umsetzung - `internal/restore.AtomicRestore` — Ablaufsteuerung, JSONL-Protokoll (`Timestamp`, `Kind`, `Source`, `Target`, `Result`). - `internal/backup.Restore` — `pg_combinebackup` gegen extrahierte Plain-Verzeichnisse + WAL-Wiederherstellung der letzten Stufe. - `internal/backup.extractTarGz` (in `verify.go`, neben der bestehenden Tar/Gzip-Leselogik) — vollständige Extraktion, kein Kopf-only-Read. - `internal/backup.Verify` — prüft jetzt `base.tar.gz` UND `pg_wal.tar.gz` vollständig. - `internal/objectbackup.Restore` — `restic restore --target`. - `cmd/restore-cli` — `database`/`objects`-Unterbefehle, `-force`-Flag (vor Positionsargumenten, Go-`flag`-Konvention), kein systemd-Timer (Notfall-/Einzelfall-Werkzeug, kein Zeitplan). ## Prüfungen | # | Prüfung | Ergebnis | |---|---|---| | 1 | Restore auf leerem System vollständig und erfolgreich durchgeführt | **bestanden** — real: `TestRestore_CombinesIntoStartablePostgresInstance` (Postgres tatsächlich aus wiederhergestelltem Verzeichnis gestartet, `SELECT 1` über echte Verbindung beantwortet); zusätzlich real per `restore-cli database` auf 131 ausgeführt (PG_VERSION/base/pg_wal vorhanden, Exit 0); `TestRestore_RecoversRealContentFromSnapshot` (Objekt-Storage, echter Dateiinhalt verglichen) UND real per `restore-cli objects` auf 131 (echter restic-Snapshot wiederhergestellt) | | 2 | Restore auf nicht-leeres Zielverzeichnis lässt bei Abbruch den ursprünglichen Inhalt unverändert | **bestanden** — `TestAtomicRestore_NonEmptyTargetWithoutForce_LeavesOriginalUntouched` (restoreFn wird nachweislich NIE aufgerufen) und `TestAtomicRestore_FailedRestoreFn_LeavesTargetUntouched`; real auf 131: `restore-cli database` gegen nicht-leeres Ziel ohne `-force` → Abbruch, bestehende Testdatei unverändert vorhanden | | 3 | Protokolleintrag zum Restore ist vollständig und nachvollziehbar | **bestanden** — `TestAtomicRestore_LogsCompleteEntry` (Quelle, Ziel, Zeitpunkt, Ergebnis für Erfolgs- UND Abbruchfall in derselben Datei); real auf 131: `/tmp/restore-cli-test.log` zeigt alle vier realen Läufe (Abbruch, Fehler mangels PATH, Erfolg, Force-Überschreiben) korrekt protokolliert | Zusätzlich: `TestAtomicRestore_EmptyTarget_Succeeds`, `TestAtomicRestore_ForceOverwritesNonEmptyTarget` (bewusste Bestätigung ersetzt bestehenden Inhalt real, alter Inhalt nachweislich weg, neuer da). ## Echte Verdrahtung auf 192.168.1.131 - `restore-cli` gebaut nach `/opt/nexarch-archive/bin/` - Kein systemd-Timer (bewusst — Notfall-/Einzelfall-Werkzeug) - Vier reale CLI-Läufe durchgeführt und protokolliert: Abbruch bei nicht-leerem Ziel ohne `-force`, Fehler mangels `pg_combinebackup` im PATH (zeigt: Fehler wird korrekt erkannt UND protokolliert, kein stiller Fehlschlag), erfolgreicher Restore einer echten Generation (`20260829T222054Z`) in leeres Ziel, erfolgreiches Überschreiben mit `-force`. Objekt-Storage-Restore real gegen echten restic-Snapshot (`43da36bf`) — Inhalt (`.placeholder`-Datei) tatsächlich vorhanden. - Alle Testartefakte (`/tmp/restore-*`, Testlog) nach Prüfung entfernt. ## Build/Test-Ergebnis ``` go build ./... -> clean go vet ./... -> clean golangci-lint run ./... -> 0 issues ``` `internal/restore`: 5/5 Tests (reine Funktionen, kein Setup nötig). `internal/objectbackup`: `TestRestore_RecoversRealContentFromSnapshot` bestanden (zusammen mit den bestehenden BAK-02-Tests). `internal/backup`: `TestRestore_CombinesIntoStartablePostgresInstance` bestanden — **Hinweis**: dieser eine Test läuft NICHT als root (`pg_ctl: cannot be run as root`) und braucht `pg_combinebackup`/ `pg_ctl` im PATH (`/usr/lib/postgresql/17/bin` auf Debian, dort nicht standardmäßig verlinkt) — daher separat als `postgres`-Systemnutzer mit entsprechendem PATH ausgeführt, nicht Teil des root-`make check`-Laufs; dort wird er mit klarer Meldung übersprungen (`pg_combinebackup nicht installiert`), kein stiller Fehlschlag. Alle übrigen BAK-01-Tests (inkl. der um `pg_wal.tar.gz` erweiterten `Verify`) liefen unverändert grün im normalen `make check`-Lauf. ## Gesamtergebnis **Bestanden.** Alle drei Akzeptanzkriterien und alle drei Pflichtprüfungen real erfüllt — beide Restore-Pfade (Datenbank, Objekt-Storage) sowohl testgetrieben als auch manuell über `restore-cli` auf echter Infrastruktur nachgewiesen. Drei während der Implementierung gefundene reale Defekte (Tar-vs-Plain-Format, fehlende WAL-Sicherung/ -Verifikation, Go-exec-Pipe-Hänger) behoben und dokumentiert, nicht stillschweigend umgangen.