diff --git a/controller/internal/backup/backup.go b/controller/internal/backup/backup.go index c8632c9..cda10a6 100644 --- a/controller/internal/backup/backup.go +++ b/controller/internal/backup/backup.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "path/filepath" "strings" "sync" "time" @@ -420,7 +421,8 @@ func (m *Manager) RefreshCache(nextDBDump, nextBackup time.Time) { if stats, err := m.restic.Stats(); err == nil { status.RepoStats = stats } - if files, err := ListDumpFiles(m.cfg.Paths.DBDumpDir); err == nil { + files, filesErr := ListDumpFiles(m.cfg.Paths.DBDumpDir) + if filesErr == nil { status.DumpFiles = files } ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) @@ -429,6 +431,25 @@ func (m *Manager) RefreshCache(nextDBDump, nextBackup time.Time) { status.DiscoveredDBs = dbs } + // Cross-check: if LastDBDump results have empty validation but files exist, + // re-validate from disk. This handles controller restarts and race conditions. + if m.lastDBDump != nil && filesErr == nil { + fileValidation := make(map[string]DumpValidation) // keyed by filename + for _, f := range files { + fileValidation[f.FileName] = f.Validation + } + for i, r := range m.lastDBDump.Results { + if !r.Validation.Valid && r.Validation.Error == "" && r.FilePath != "" { + filename := filepath.Base(r.FilePath) + if fv, ok := fileValidation[filename]; ok { + m.lastDBDump.Results[i].Validation = fv + m.logger.Printf("[INFO] Re-validated %s from disk: valid=%v tables=%d", + filename, fv.Valid, fv.TableCount) + } + } + } + } + // Fill in dynamic fields under lock m.mu.Lock() status.Running = m.running diff --git a/controller/internal/backup/dbdump.go b/controller/internal/backup/dbdump.go index 1582c3e..d8c19bc 100644 --- a/controller/internal/backup/dbdump.go +++ b/controller/internal/backup/dbdump.go @@ -231,6 +231,7 @@ func DumpOne(ctx context.Context, db DiscoveredDB, dumpDir string, logger *log.L // ValidateDump checks a SQL dump file for basic structural integrity. func ValidateDump(filePath string, dbType DBType) DumpValidation { + log.Printf("[DEBUG] ValidateDump: %s (type=%s)", filePath, dbType) stat, err := os.Stat(filePath) if err != nil { return DumpValidation{Error: fmt.Sprintf("stat failed: %v", err)} @@ -243,12 +244,14 @@ func ValidateDump(filePath string, dbType DBType) DumpValidation { if stat.Size() < 100 { v.Error = "dump file too small (< 100 bytes)" + log.Printf("[WARN] ValidateDump FAIL: %s — %s", filePath, v.Error) return v } data, err := os.ReadFile(filePath) if err != nil { v.Error = fmt.Sprintf("read failed: %v", err) + log.Printf("[WARN] ValidateDump FAIL: %s — %s", filePath, v.Error) return v } @@ -295,15 +298,18 @@ func ValidateDump(filePath string, dbType DBType) DumpValidation { case DBTypePostgres: v.Error = "PostgreSQL dump missing comment header" } + log.Printf("[WARN] ValidateDump FAIL: %s — %s", filePath, v.Error) return v } if tableCount == 0 { v.Error = "no CREATE TABLE statements found" + log.Printf("[WARN] ValidateDump FAIL: %s — %s (header was found, scanned %d lines)", filePath, v.Error, len(strings.Split(content, "\n"))) return v } v.Valid = true + log.Printf("[DEBUG] ValidateDump OK: %s — %d tables, header found", filePath, tableCount) return v } diff --git a/controller/internal/web/templates/backups.html b/controller/internal/web/templates/backups.html index c6a9030..2e03190 100644 --- a/controller/internal/web/templates/backups.html +++ b/controller/internal/web/templates/backups.html @@ -136,8 +136,10 @@ {{else if .Validation.Valid}} {{.Validation.TableCount}} tábla - {{else}} + {{else if .Validation.Error}} Hiba + {{else}} + {{end}}