// 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] ") fmt.Fprintln(os.Stderr, " restore-cli objects [-force] ") 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) } }