internal/restore: Atomarer Restore ueber Temp-Verzeichnis + Rename, nicht-leeres Ziel ohne -force bricht VOR jeder Beruehrung ab, JSONL- Protokoll jedes Laufs. Drei reale Defekte beim Bau gefunden und behoben: pg_combinebackup braucht Plain- statt Tar-Format (Extraktionsschritt ergaenzt), pg_wal.tar.gz wurde nie verifiziert/wiederhergestellt (BAK-01s Verify jetzt erweitert), Go-exec haengt bei pg_ctl start wegen vererbter Pipes (Testfix: echte Logdatei statt CombinedOutput). Beide Restore-Pfade real auf 131 ueber restore-cli nachgewiesen, inkl. echtem Postgres-Start aus wiederhergestelltem Verzeichnis.
123 lines
7.2 KiB
Markdown
123 lines
7.2 KiB
Markdown
# 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.
|