feat: add dry-run mode, GFS retention policies, and notifications

- Add --dry-run/-n flag for backup commands with comprehensive preflight checks
  - Database connectivity validation
  - Required tools availability check
  - Storage target and permissions verification
  - Backup size estimation
  - Encryption and cloud storage configuration validation

- Implement GFS (Grandfather-Father-Son) retention policies
  - Daily/Weekly/Monthly/Yearly tier classification
  - Configurable retention counts per tier
  - Custom weekly day and monthly day settings
  - ISO week handling for proper week boundaries

- Add notification system with SMTP and webhook support
  - SMTP email notifications with TLS/STARTTLS
  - Webhook HTTP notifications with HMAC-SHA256 signing
  - Slack-compatible webhook payload format
  - Event types: backup/restore started/completed/failed, cleanup, verify, PITR
  - Configurable severity levels and retry logic

- Update README.md with documentation for all new features
This commit is contained in:
2025-12-13 19:00:54 +01:00
parent 2becde8077
commit d0d83b61ef
15 changed files with 3080 additions and 5 deletions

View File

@@ -9,6 +9,7 @@ import (
"time"
"dbbackup/internal/backup"
"dbbackup/internal/checks"
"dbbackup/internal/config"
"dbbackup/internal/database"
"dbbackup/internal/security"
@@ -28,6 +29,11 @@ func runClusterBackup(ctx context.Context) error {
return fmt.Errorf("configuration error: %w", err)
}
// Handle dry-run mode
if backupDryRun {
return runBackupPreflight(ctx, "")
}
// Check privileges
privChecker := security.NewPrivilegeChecker(log)
if err := privChecker.CheckAndWarn(cfg.AllowRoot); err != nil {
@@ -124,6 +130,16 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
// Update config from environment
cfg.UpdateFromEnvironment()
// Validate configuration
if err := cfg.Validate(); err != nil {
return fmt.Errorf("configuration error: %w", err)
}
// Handle dry-run mode
if backupDryRun {
return runBackupPreflight(ctx, databaseName)
}
// Get backup type and base backup from command line flags (set via global vars in PreRunE)
// These are populated by cobra flag binding in cmd/backup.go
backupType := "full" // Default to full backup if not specified
@@ -148,11 +164,6 @@ func runSingleBackup(ctx context.Context, databaseName string) error {
}
}
// Validate configuration
if err := cfg.Validate(); err != nil {
return fmt.Errorf("configuration error: %w", err)
}
// Check privileges
privChecker := security.NewPrivilegeChecker(log)
if err := privChecker.CheckAndWarn(cfg.AllowRoot); err != nil {
@@ -306,6 +317,11 @@ func runSampleBackup(ctx context.Context, databaseName string) error {
return fmt.Errorf("configuration error: %w", err)
}
// Handle dry-run mode
if backupDryRun {
return runBackupPreflight(ctx, databaseName)
}
// Check privileges
privChecker := security.NewPrivilegeChecker(log)
if err := privChecker.CheckAndWarn(cfg.AllowRoot); err != nil {
@@ -536,3 +552,25 @@ func findLatestClusterBackup(backupDir string) (string, error) {
return latestPath, nil
}
// runBackupPreflight runs preflight checks without executing backup
func runBackupPreflight(ctx context.Context, databaseName string) error {
checker := checks.NewPreflightChecker(cfg, log)
defer checker.Close()
result, err := checker.RunAllChecks(ctx, databaseName)
if err != nil {
return fmt.Errorf("preflight check error: %w", err)
}
// Format and print report
report := checks.FormatPreflightReport(result, databaseName, true)
fmt.Print(report)
// Return appropriate exit code
if !result.AllPassed {
return fmt.Errorf("preflight checks failed")
}
return nil
}