Files
nexarch/archive/internal/backup/verify.go
T
sysopsandClaude Sonnet 5 5e4b91c4e9 BAK-01: datenbank-backup-strategie
Neues Modul-Verzeichnis code/archive/ (Monorepo-Muster wie code/dms/).
PostgreSQL-17-natives inkrementelles Backup (pg_basebackup --incremental,
WAL-Summarization) statt WAL-Archiving, um den geteilten Testhost ohne
Neustart umzustellen (summarize_wal=on per pg_reload_conf). Rolle
nexarch_backup mit REPLICATION-Attribut angelegt.

internal/backup: FullBackup/IncrementalBackup (pg_basebackup-Wrapper),
Verify (vollstaendiges Lesen von base.tar.gz, gzip+tar, nicht nur Header),
Rotate/ListGenerations (generationsbasiert, aeltere zuerst entfernt).
cmd/backup-cli fuer systemd-Timer-Aufruf (deploy/systemd/
nexarch-archive-backup-*.timer, taeglich/stuendlich/taeglich).

Auf 192.168.1.131 verifiziert: 4/4 Tests gegen echte Postgres-17-Instanz
(kein Mock) - inkrementelle Sicherung real kleiner als Vollsicherung,
Verifikation erkennt absichtlich beschaedigte Datei, Rotation entfernt nur
die aeltesten Generationen. Zusaetzlich ECHT verdrahtet: backup-cli gebaut,
3 systemd-Timer installiert+aktiviert, jeder der drei Dienste einmal ueber
systemctl start end-to-end ausgeloest (status=0/SUCCESS je Dienst) - nicht
nur go test, sondern der reale Automatisierungspfad selbst geprueft.

Siehe archive/docs/BAK-01-PRUEFPROTOKOLL.md fuer alle Pruefungsergebnisse.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhgFcLS8tYMhDJpP74C6AQ
2026-08-29 22:49:13 +02:00

55 lines
1.5 KiB
Go

package backup
import (
"archive/tar"
"compress/gzip"
"fmt"
"io"
"os"
"path/filepath"
)
// ErrCorrupted wird geliefert, wenn eine Sicherungsdatei nicht lesbar ist
// (Akzeptanzkriterium 2: Verifikation, nicht nur Erstellungs-Prüfung).
var ErrCorrupted = fmt.Errorf("backup: sicherungsdatei ist beschaedigt oder unvollstaendig")
// Verify prüft, dass base.tar.gz im gegebenen Sicherungsverzeichnis
// vollständig lesbar ist — öffnet gzip- UND tar-Stream und liest JEDEN
// Eintrag bis zum Ende durch (nicht nur die Kopfdaten), damit ein
// abgeschnittener oder mit kaputten Bytes überschriebener Inhalt
// zuverlässig auffällt, nicht nur ein defekter Tar-Header.
func Verify(backupDir string) error {
path := filepath.Join(backupDir, BaseTarGzFile)
f, err := os.Open(path)
if err != nil {
return fmt.Errorf("%w: %s nicht lesbar: %v", ErrCorrupted, path, err)
}
defer func() { _ = f.Close() }()
gz, err := gzip.NewReader(f)
if err != nil {
return fmt.Errorf("%w: gzip-header ungueltig: %v", ErrCorrupted, err)
}
defer func() { _ = gz.Close() }()
tr := tar.NewReader(gz)
entries := 0
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return fmt.Errorf("%w: tar-eintrag ungueltig: %v", ErrCorrupted, err)
}
if _, err := io.Copy(io.Discard, tr); err != nil {
return fmt.Errorf("%w: inhalt von %q nicht vollstaendig lesbar: %v", ErrCorrupted, hdr.Name, err)
}
entries++
}
if entries == 0 {
return fmt.Errorf("%w: archiv enthaelt keine eintraege", ErrCorrupted)
}
return nil
}