Files
dbbackup/cmd/verify.go
Alexander Renz 3e41d88445
All checks were successful
CI/CD / Test (push) Successful in 1m13s
CI/CD / Lint (push) Successful in 1m20s
CI/CD / Build & Release (push) Successful in 3m10s
v3.42.11: Replace all Unicode emojis with ASCII text
- Replace all emoji characters with ASCII equivalents throughout codebase
- Replace Unicode box-drawing characters (═║╔╗╚╝━─) with ASCII (+|-=)
- Replace checkmarks (✓✗) with [OK]/[FAIL] markers
- 59 files updated, 741 lines changed
- Improves terminal compatibility and reduces visual noise
2026-01-08 09:42:01 +01:00

237 lines
6.0 KiB
Go

package cmd
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"dbbackup/internal/cloud"
"dbbackup/internal/metadata"
"dbbackup/internal/restore"
"dbbackup/internal/verification"
"github.com/spf13/cobra"
)
var verifyBackupCmd = &cobra.Command{
Use: "verify-backup [backup-file]",
Short: "Verify backup file integrity with checksums",
Long: `Verify the integrity of one or more backup files by comparing their SHA-256 checksums
against the stored metadata. This ensures that backups have not been corrupted.
Examples:
# Verify a single backup
dbbackup verify-backup /backups/mydb_20260115.dump
# Verify all backups in a directory
dbbackup verify-backup /backups/*.dump
# Quick verification (size check only, no checksum)
dbbackup verify-backup /backups/mydb.dump --quick
# Verify and show detailed information
dbbackup verify-backup /backups/mydb.dump --verbose`,
Args: cobra.MinimumNArgs(1),
RunE: runVerifyBackup,
}
var (
quickVerify bool
verboseVerify bool
)
func init() {
rootCmd.AddCommand(verifyBackupCmd)
verifyBackupCmd.Flags().BoolVar(&quickVerify, "quick", false, "Quick verification (size check only)")
verifyBackupCmd.Flags().BoolVarP(&verboseVerify, "verbose", "v", false, "Show detailed information")
}
func runVerifyBackup(cmd *cobra.Command, args []string) error {
// Check if any argument is a cloud URI
hasCloudURI := false
for _, arg := range args {
if isCloudURI(arg) {
hasCloudURI = true
break
}
}
// If cloud URIs detected, handle separately
if hasCloudURI {
return runVerifyCloudBackup(cmd, args)
}
// Expand glob patterns for local files
var backupFiles []string
for _, pattern := range args {
matches, err := filepath.Glob(pattern)
if err != nil {
return fmt.Errorf("invalid pattern %s: %w", pattern, err)
}
if len(matches) == 0 {
// Not a glob, use as-is
backupFiles = append(backupFiles, pattern)
} else {
backupFiles = append(backupFiles, matches...)
}
}
if len(backupFiles) == 0 {
return fmt.Errorf("no backup files found")
}
fmt.Printf("Verifying %d backup file(s)...\n\n", len(backupFiles))
successCount := 0
failureCount := 0
for _, backupFile := range backupFiles {
// Skip metadata files
if strings.HasSuffix(backupFile, ".meta.json") ||
strings.HasSuffix(backupFile, ".sha256") ||
strings.HasSuffix(backupFile, ".info") {
continue
}
fmt.Printf("[FILE] %s\n", filepath.Base(backupFile))
if quickVerify {
// Quick check: size only
err := verification.QuickCheck(backupFile)
if err != nil {
fmt.Printf(" [FAIL] FAILED: %v\n\n", err)
failureCount++
continue
}
fmt.Printf(" [OK] VALID (quick check)\n\n")
successCount++
} else {
// Full verification with SHA-256
result, err := verification.Verify(backupFile)
if err != nil {
return fmt.Errorf("verification error: %w", err)
}
if result.Valid {
fmt.Printf(" [OK] VALID\n")
if verboseVerify {
meta, _ := metadata.Load(backupFile)
fmt.Printf(" Size: %s\n", metadata.FormatSize(meta.SizeBytes))
fmt.Printf(" SHA-256: %s\n", meta.SHA256)
fmt.Printf(" Database: %s (%s)\n", meta.Database, meta.DatabaseType)
fmt.Printf(" Created: %s\n", meta.Timestamp.Format(time.RFC3339))
}
fmt.Println()
successCount++
} else {
fmt.Printf(" [FAIL] FAILED: %v\n", result.Error)
if verboseVerify {
if !result.FileExists {
fmt.Printf(" File does not exist\n")
} else if !result.MetadataExists {
fmt.Printf(" Metadata file missing\n")
} else if !result.SizeMatch {
fmt.Printf(" Size mismatch\n")
} else {
fmt.Printf(" Expected: %s\n", result.ExpectedSHA256)
fmt.Printf(" Got: %s\n", result.CalculatedSHA256)
}
}
fmt.Println()
failureCount++
}
}
}
// Summary
fmt.Println(strings.Repeat("-", 50))
fmt.Printf("Total: %d backups\n", len(backupFiles))
fmt.Printf("[OK] Valid: %d\n", successCount)
if failureCount > 0 {
fmt.Printf("[FAIL] Failed: %d\n", failureCount)
os.Exit(1)
}
return nil
}
// isCloudURI checks if a string is a cloud URI
func isCloudURI(s string) bool {
return cloud.IsCloudURI(s)
}
// verifyCloudBackup downloads and verifies a backup from cloud storage
func verifyCloudBackup(ctx context.Context, uri string, quick, verbose bool) (*restore.DownloadResult, error) {
// Download from cloud with checksum verification
result, err := restore.DownloadFromCloudURI(ctx, uri, restore.DownloadOptions{
VerifyChecksum: !quick, // Skip checksum if quick mode
KeepLocal: false,
})
if err != nil {
return nil, err
}
// If not quick mode, also run full verification
if !quick {
_, err := verification.Verify(result.LocalPath)
if err != nil {
result.Cleanup()
return nil, err
}
}
return result, nil
}
// runVerifyCloudBackup verifies backups from cloud storage
func runVerifyCloudBackup(cmd *cobra.Command, args []string) error {
fmt.Printf("Verifying cloud backup(s)...\n\n")
successCount := 0
failureCount := 0
for _, uri := range args {
if !isCloudURI(uri) {
fmt.Printf("[WARN] Skipping non-cloud URI: %s\n", uri)
continue
}
fmt.Printf("[CLOUD] %s\n", uri)
// Download and verify
result, err := verifyCloudBackup(cmd.Context(), uri, quickVerify, verboseVerify)
if err != nil {
fmt.Printf(" [FAIL] FAILED: %v\n\n", err)
failureCount++
continue
}
// Cleanup temp file
defer result.Cleanup()
fmt.Printf(" [OK] VALID\n")
if verboseVerify && result.MetadataPath != "" {
meta, _ := metadata.Load(result.MetadataPath)
if meta != nil {
fmt.Printf(" Size: %s\n", metadata.FormatSize(meta.SizeBytes))
fmt.Printf(" SHA-256: %s\n", meta.SHA256)
fmt.Printf(" Database: %s (%s)\n", meta.Database, meta.DatabaseType)
fmt.Printf(" Created: %s\n", meta.Timestamp.Format(time.RFC3339))
}
}
fmt.Println()
successCount++
}
fmt.Printf("\n[OK] Summary: %d valid, %d failed\n", successCount, failureCount)
if failureCount > 0 {
os.Exit(1)
}
return nil
}