internal/backup.PruneTiered: reine GFS-Funktion (Tag/Woche/Monat) fuer Datenbank-Generationen, behaelt strukturell immer die neueste Generation (Sicherheitsnetz gegen Legal-Hold-Kollision). internal/objectbackup. PruneTiered: duenner Wrapper um restics native --keep-daily/-weekly/ -monthly-Staffelung. Beide CLIs nutzen die Staffelung, wenn konfiguriert, bleiben sonst abwaertskompatibel zur flachen "letzte N"-Regel. Real gegen zeitversetzt erzeugte restic-Snapshots getestet (Fund: restics --time-Flag erwartet eigenes Format, nicht RFC3339), Pruning-Sicherheit nach dem Loeschen alter Snapshots ueber vollstaendigen Restore + restic check --read-data bewiesen. Beide Rotationswege real ueber systemd auf 131 ausgeloest.
101 lines
3.0 KiB
Go
101 lines
3.0 KiB
Go
// objectbackup-cli ist der Aufrufpunkt für BAK-02, für systemd-Timer
|
|
// gedacht (siehe deploy/systemd/) — "automatisiert nach Zeitplan" entsteht
|
|
// durch die Timer-Definition, kein eigener Dauerprozess (dieselbe
|
|
// Begründung wie BAK-01 / cmd/backup-cli).
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/archive/internal/objectbackup"
|
|
)
|
|
|
|
func loadConfig() objectbackup.Config {
|
|
cfg := objectbackup.Config{
|
|
RepoDir: os.Getenv("NEXARCH_OBJECTBACKUP_REPO_DIR"),
|
|
Password: os.Getenv("NEXARCH_OBJECTBACKUP_PASSWORD"),
|
|
}
|
|
if cfg.RepoDir == "" || cfg.Password == "" {
|
|
log.Fatal("NEXARCH_OBJECTBACKUP_REPO_DIR und NEXARCH_OBJECTBACKUP_PASSWORD muessen gesetzt sein")
|
|
}
|
|
return cfg
|
|
}
|
|
|
|
func envInt(name string, def int) int {
|
|
v := os.Getenv(name)
|
|
if v == "" {
|
|
return def
|
|
}
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
return n
|
|
}
|
|
return def
|
|
}
|
|
|
|
func main() {
|
|
if len(os.Args) < 2 {
|
|
log.Fatal("aufruf: objectbackup-cli <backup <quellverzeichnis>|check|rotate>")
|
|
}
|
|
cfg := loadConfig()
|
|
ctx := context.Background()
|
|
|
|
if err := objectbackup.InitRepo(ctx, cfg); err != nil {
|
|
log.Fatalf("repository initialisieren: %v", err)
|
|
}
|
|
|
|
switch os.Args[1] {
|
|
case "backup":
|
|
if len(os.Args) < 3 {
|
|
log.Fatal("aufruf: objectbackup-cli backup <quellverzeichnis>")
|
|
}
|
|
summary, err := objectbackup.Backup(ctx, cfg, os.Args[2])
|
|
if err != nil {
|
|
log.Fatalf("sicherung fehlgeschlagen: %v", err)
|
|
}
|
|
if err := objectbackup.Check(ctx, cfg, false); err != nil {
|
|
log.Fatalf("verifikation nach sicherung fehlgeschlagen: %v", err)
|
|
}
|
|
fmt.Printf("sicherung %q erstellt und verifiziert (neu=%d geaendert=%d unveraendert=%d)\n",
|
|
summary.SnapshotID, summary.FilesNew, summary.FilesChanged, summary.FilesUnmodified)
|
|
|
|
case "check":
|
|
if err := objectbackup.Check(ctx, cfg, true); err != nil {
|
|
log.Fatalf("verifikation fehlgeschlagen: %v", err)
|
|
}
|
|
fmt.Println("verifikation (mit vollstaendigem lesen) erfolgreich")
|
|
|
|
case "rotate":
|
|
// BAK-07: gestaffelte Aufbewahrungsfrist, falls konfiguriert -
|
|
// ersetzt die flache "letzte N"-Regel, bleibt abwaertskompatibel.
|
|
policy := objectbackup.RetentionPolicy{
|
|
KeepDaily: envInt("NEXARCH_OBJECTBACKUP_KEEP_DAILY", 0),
|
|
KeepWeekly: envInt("NEXARCH_OBJECTBACKUP_KEEP_WEEKLY", 0),
|
|
KeepMonthly: envInt("NEXARCH_OBJECTBACKUP_KEEP_MONTHLY", 0),
|
|
}
|
|
if policy.KeepDaily > 0 || policy.KeepWeekly > 0 || policy.KeepMonthly > 0 {
|
|
if err := objectbackup.PruneTiered(ctx, cfg, policy); err != nil {
|
|
log.Fatalf("gestaffelte rotation fehlgeschlagen: %v", err)
|
|
}
|
|
fmt.Println("gestaffelte rotation abgeschlossen")
|
|
return
|
|
}
|
|
keep := 7
|
|
if v := os.Getenv("NEXARCH_OBJECTBACKUP_KEEP_SNAPSHOTS"); v != "" {
|
|
if n, err := strconv.Atoi(v); err == nil {
|
|
keep = n
|
|
}
|
|
}
|
|
if err := objectbackup.Forget(ctx, cfg, keep); err != nil {
|
|
log.Fatalf("rotation fehlgeschlagen: %v", err)
|
|
}
|
|
fmt.Println("rotation abgeschlossen")
|
|
|
|
default:
|
|
log.Fatalf("unbekannter befehl %q", os.Args[1])
|
|
}
|
|
}
|