feat(archive): BAK-03 Restore-Verfahren fuer Datenbank und Objekt-Storage
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.
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
// restore-cli ist der Aufrufpunkt fuer BAK-03: dokumentiertes,
|
||||
// wiederholbares Restore-Verfahren fuer Datenbank (BAK-01) und
|
||||
// Objekt-Storage (BAK-02). Kein systemd-Timer (Notfall-/Einzelfall-
|
||||
// Werkzeug, kein Zeitplan) - manueller Aufruf mit expliziter Bestaetigung
|
||||
// bei nicht-leerem Ziel.
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"flag"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/backup"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/objectbackup"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/restore"
|
||||
)
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
|
||||
logPath := os.Getenv("NEXARCH_RESTORE_LOG")
|
||||
if logPath == "" {
|
||||
logPath = "/var/nexarch-archiv/restore.log"
|
||||
}
|
||||
|
||||
switch os.Args[1] {
|
||||
case "database":
|
||||
runDatabase(logPath, os.Args[2:])
|
||||
case "objects":
|
||||
runObjects(logPath, os.Args[2:])
|
||||
default:
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
}
|
||||
|
||||
func usage() {
|
||||
fmt.Fprintln(os.Stderr, "usage: restore-cli database [-force] <generation-id> <ziel-verzeichnis>")
|
||||
fmt.Fprintln(os.Stderr, " restore-cli objects [-force] <snapshot-id> <ziel-verzeichnis>")
|
||||
fmt.Fprintln(os.Stderr, "(Flag -force MUSS vor den Positionsargumenten stehen, Standardverhalten des Go-flag-Pakets)")
|
||||
}
|
||||
|
||||
func runDatabase(logPath string, args []string) {
|
||||
fs := flag.NewFlagSet("database", flag.ExitOnError)
|
||||
force := fs.Bool("force", false, "bestehenden, nicht-leeren Inhalt im Zielverzeichnis ersetzen")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
if fs.NArg() != 2 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
generationID, target := fs.Arg(0), fs.Arg(1)
|
||||
|
||||
cfg := backup.Config{
|
||||
Host: os.Getenv("NEXARCH_BACKUP_PG_HOST"),
|
||||
Port: os.Getenv("NEXARCH_BACKUP_PG_PORT"),
|
||||
User: os.Getenv("NEXARCH_BACKUP_PG_USER"),
|
||||
Password: os.Getenv("NEXARCH_BACKUP_PG_PASSWORD"),
|
||||
BackupDir: os.Getenv("NEXARCH_BACKUP_DIR"),
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
entry, err := restore.AtomicRestore(restore.KindDatabase, generationID, target, *force, logPath, func(tempDir string) error {
|
||||
return backup.Restore(ctx, cfg, generationID, tempDir)
|
||||
})
|
||||
report(entry, err)
|
||||
}
|
||||
|
||||
func runObjects(logPath string, args []string) {
|
||||
fs := flag.NewFlagSet("objects", flag.ExitOnError)
|
||||
force := fs.Bool("force", false, "bestehenden, nicht-leeren Inhalt im Zielverzeichnis ersetzen")
|
||||
if err := fs.Parse(args); err != nil {
|
||||
os.Exit(2)
|
||||
}
|
||||
if fs.NArg() != 2 {
|
||||
usage()
|
||||
os.Exit(2)
|
||||
}
|
||||
snapshotID, target := fs.Arg(0), fs.Arg(1)
|
||||
|
||||
cfg := objectbackup.Config{
|
||||
RepoDir: os.Getenv("NEXARCH_OBJECTBACKUP_REPO_DIR"),
|
||||
Password: os.Getenv("NEXARCH_OBJECTBACKUP_PASSWORD"),
|
||||
}
|
||||
ctx := context.Background()
|
||||
|
||||
entry, err := restore.AtomicRestore(restore.KindObjects, snapshotID, target, *force, logPath, func(tempDir string) error {
|
||||
return objectbackup.Restore(ctx, cfg, snapshotID, tempDir)
|
||||
})
|
||||
report(entry, err)
|
||||
}
|
||||
|
||||
func report(entry restore.LogEntry, err error) {
|
||||
fmt.Printf("restore %s: quelle=%s ziel=%s ergebnis=%s\n", entry.Kind, entry.Source, entry.Target, entry.Result)
|
||||
if err != nil {
|
||||
log.Fatalf("restore fehlgeschlagen: %v", err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user