// backup-cli ist der Aufrufpunkt für BAK-01, gedacht für systemd-Timer // (siehe deploy/systemd/) — "automatisiert nach Zeitplan" (Akzeptanzkriterium // 1) entsteht durch die Zeitplan-Definition im Timer-Unit, nicht durch // einen eigenen In-Prozess-Scheduler (kein zusätzlicher Dauerprozess nötig, // passt zur Produkt-DNA "kein Anwendungsserver mit unnötigem // Ressourcenverbrauch"). package main import ( "context" "fmt" "log" "os" "time" "gitea.perlbach24.de/scripte/nexarch/archive/internal/backup" ) func loadConfig() backup.Config { 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"), } if cfg.Host == "" { cfg.Host = "localhost" } if cfg.Port == "" { cfg.Port = "5432" } if cfg.User == "" || cfg.Password == "" || cfg.BackupDir == "" { log.Fatal("NEXARCH_BACKUP_PG_USER, NEXARCH_BACKUP_PG_PASSWORD und NEXARCH_BACKUP_DIR muessen gesetzt sein") } return cfg } func latestManifest(backupDir string) (string, error) { generations, err := backup.ListGenerations(backupDir) if err != nil { return "", err } if len(generations) == 0 { return "", fmt.Errorf("keine vorhandene generation fuer inkrementelle sicherung gefunden - zuerst 'full' ausfuehren") } latest := generations[len(generations)-1] full := backupDir + "/" + latest + "/" + backup.FullBackupDirName + "/" + backup.BackupManifestFile if _, err := os.Stat(full); err == nil { return full, nil } return "", fmt.Errorf("kein backup_manifest in der neuesten generation %q gefunden", latest) } func main() { if len(os.Args) < 2 { log.Fatal("aufruf: backup-cli [args]") } cfg := loadConfig() ctx := context.Background() switch os.Args[1] { case "full": genID := backup.NewGenerationID(time.Now()) manifest, err := backup.FullBackup(ctx, cfg, genID) if err != nil { log.Fatalf("vollsicherung fehlgeschlagen: %v", err) } dir := manifest[:len(manifest)-len("/"+backup.BackupManifestFile)] if err := backup.Verify(dir); err != nil { log.Fatalf("verifikation der vollsicherung fehlgeschlagen: %v", err) } fmt.Printf("vollsicherung %q erstellt und verifiziert: %s\n", genID, manifest) case "incremental": manifest, err := latestManifest(cfg.BackupDir) if err != nil { log.Fatal(err) } generations, _ := backup.ListGenerations(cfg.BackupDir) genID := generations[len(generations)-1] incID := backup.NewGenerationID(time.Now()) newManifest, err := backup.IncrementalBackup(ctx, cfg, genID, incID, manifest) if err != nil { log.Fatalf("inkrementelle sicherung fehlgeschlagen: %v", err) } dir := newManifest[:len(newManifest)-len("/"+backup.BackupManifestFile)] if err := backup.Verify(dir); err != nil { log.Fatalf("verifikation der inkrementellen sicherung fehlgeschlagen: %v", err) } fmt.Printf("inkrementelle sicherung %q erstellt und verifiziert: %s\n", incID, newManifest) case "rotate": keep := 7 if v := os.Getenv("NEXARCH_BACKUP_KEEP_GENERATIONS"); v != "" { _, _ = fmt.Sscanf(v, "%d", &keep) } removed, err := backup.Rotate(cfg.BackupDir, keep) if err != nil { log.Fatalf("rotation fehlgeschlagen: %v", err) } fmt.Printf("rotation abgeschlossen, %d generation(en) entfernt: %v\n", len(removed), removed) default: log.Fatalf("unbekannter befehl %q", os.Args[1]) } }