From e363e1937f80117060ff7ea92ce26a11965f5536 Mon Sep 17 00:00:00 2001 From: Alexander Renz Date: Sat, 17 Jan 2026 16:44:44 +0100 Subject: [PATCH] fix: cluster restore database detection and TUI error display - Fixed psql connection for database detection (always use -h flag) - Use CombinedOutput() to capture stderr for better diagnostics - Added existingDBError tracking in restore preview - Show 'Unable to detect' instead of misleading 'None' when listing fails - Disable cleanup toggle when database detection failed --- bin/README.md | 4 ++-- internal/restore/safety.go | 26 +++++++++++++++---------- internal/tui/restore_preview.go | 34 +++++++++++++++++++++++++++------ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/bin/README.md b/bin/README.md index 06961c8..837e333 100644 --- a/bin/README.md +++ b/bin/README.md @@ -4,8 +4,8 @@ This directory contains pre-compiled binaries for the DB Backup Tool across mult ## Build Information - **Version**: 3.42.50 -- **Build Time**: 2026-01-17_15:15:55_UTC -- **Git Commit**: 0e050b2 +- **Build Time**: 2026-01-17_15:26:14_UTC +- **Git Commit**: df1ab2f ## Recent Updates (v1.1.0) - ✅ Fixed TUI progress display with line-by-line output diff --git a/internal/restore/safety.go b/internal/restore/safety.go index 23a9f47..3269cf3 100755 --- a/internal/restore/safety.go +++ b/internal/restore/safety.go @@ -334,10 +334,12 @@ func (s *Safety) checkPostgresDatabaseExists(ctx context.Context, dbName string) "-tAc", fmt.Sprintf("SELECT 1 FROM pg_database WHERE datname='%s'", dbName), } - // Only add -h flag if host is not localhost (to use Unix socket for peer auth) - if s.cfg.Host != "localhost" && s.cfg.Host != "127.0.0.1" && s.cfg.Host != "" { - args = append([]string{"-h", s.cfg.Host}, args...) + // Always add -h flag for explicit host connection (required for password auth) + host := s.cfg.Host + if host == "" { + host = "localhost" } + args = append([]string{"-h", host}, args...) cmd := exec.CommandContext(ctx, "psql", args...) @@ -346,9 +348,9 @@ func (s *Safety) checkPostgresDatabaseExists(ctx context.Context, dbName string) cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", s.cfg.Password)) } - output, err := cmd.Output() + output, err := cmd.CombinedOutput() if err != nil { - return false, fmt.Errorf("failed to check database existence: %w", err) + return false, fmt.Errorf("failed to check database existence: %w (output: %s)", err, strings.TrimSpace(string(output))) } return strings.TrimSpace(string(output)) == "1", nil @@ -405,10 +407,13 @@ func (s *Safety) listPostgresUserDatabases(ctx context.Context) ([]string, error "-c", query, } - // Only add -h flag if host is not localhost (to use Unix socket for peer auth) - if s.cfg.Host != "localhost" && s.cfg.Host != "127.0.0.1" && s.cfg.Host != "" { - args = append([]string{"-h", s.cfg.Host}, args...) + // Always add -h flag for explicit host connection (required for password auth) + // Empty or unset host defaults to localhost + host := s.cfg.Host + if host == "" { + host = "localhost" } + args = append([]string{"-h", host}, args...) cmd := exec.CommandContext(ctx, "psql", args...) @@ -417,9 +422,10 @@ func (s *Safety) listPostgresUserDatabases(ctx context.Context) ([]string, error cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", s.cfg.Password)) } - output, err := cmd.Output() + output, err := cmd.CombinedOutput() if err != nil { - return nil, fmt.Errorf("failed to list databases: %w", err) + // Include psql output in error for debugging + return nil, fmt.Errorf("failed to list databases: %w (output: %s)", err, strings.TrimSpace(string(output))) } // Parse output diff --git a/internal/tui/restore_preview.go b/internal/tui/restore_preview.go index 636d04d..4927380 100755 --- a/internal/tui/restore_preview.go +++ b/internal/tui/restore_preview.go @@ -55,6 +55,7 @@ type RestorePreviewModel struct { cleanClusterFirst bool // For cluster restore: drop all user databases first existingDBCount int // Number of existing user databases existingDBs []string // List of existing user databases + existingDBError string // Error message if database listing failed safetyChecks []SafetyCheck checking bool canProceed bool @@ -102,6 +103,7 @@ type safetyCheckCompleteMsg struct { canProceed bool existingDBCount int existingDBs []string + existingDBError string } func runSafetyChecks(cfg *config.Config, log logger.Logger, archive ArchiveInfo, targetDB string) tea.Cmd { @@ -221,10 +223,12 @@ func runSafetyChecks(cfg *config.Config, log logger.Logger, archive ArchiveInfo, check = SafetyCheck{Name: "Existing databases", Status: "checking", Critical: false} // Get list of existing user databases (exclude templates and system DBs) + var existingDBError string dbList, err := safety.ListUserDatabases(ctx) if err != nil { check.Status = "warning" check.Message = fmt.Sprintf("Cannot list databases: %v", err) + existingDBError = err.Error() } else { existingDBCount = len(dbList) existingDBs = dbList @@ -238,6 +242,14 @@ func runSafetyChecks(cfg *config.Config, log logger.Logger, archive ArchiveInfo, } } checks = append(checks, check) + + return safetyCheckCompleteMsg{ + checks: checks, + canProceed: canProceed, + existingDBCount: existingDBCount, + existingDBs: existingDBs, + existingDBError: existingDBError, + } } return safetyCheckCompleteMsg{ @@ -257,6 +269,7 @@ func (m RestorePreviewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.canProceed = msg.canProceed m.existingDBCount = msg.existingDBCount m.existingDBs = msg.existingDBs + m.existingDBError = msg.existingDBError // Auto-forward in auto-confirm mode if m.config.TUIAutoConfirm { return m.parent, tea.Quit @@ -275,12 +288,17 @@ func (m RestorePreviewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case "c": if m.mode == "restore-cluster" { - // Toggle cluster cleanup - m.cleanClusterFirst = !m.cleanClusterFirst - if m.cleanClusterFirst { - m.message = checkWarningStyle.Render(fmt.Sprintf("[WARN] Will drop %d existing database(s) before restore", m.existingDBCount)) + // Prevent toggle if we couldn't detect existing databases + if m.existingDBError != "" { + m.message = checkWarningStyle.Render("[WARN] Cannot enable cleanup - database detection failed") } else { - m.message = fmt.Sprintf("Clean cluster first: disabled") + // Toggle cluster cleanup + m.cleanClusterFirst = !m.cleanClusterFirst + if m.cleanClusterFirst { + m.message = checkWarningStyle.Render(fmt.Sprintf("[WARN] Will drop %d existing database(s) before restore", m.existingDBCount)) + } else { + m.message = fmt.Sprintf("Clean cluster first: disabled") + } } } else { // Toggle create if missing @@ -382,7 +400,11 @@ func (m RestorePreviewModel) View() string { s.WriteString("\n") s.WriteString(fmt.Sprintf(" Host: %s:%d\n", m.config.Host, m.config.Port)) - if m.existingDBCount > 0 { + if m.existingDBError != "" { + // Show error when database listing failed + s.WriteString(checkWarningStyle.Render(fmt.Sprintf(" Existing Databases: Unable to detect (%s)\n", m.existingDBError))) + s.WriteString(infoStyle.Render(" (Cleanup option disabled - cannot verify database status)\n")) + } else if m.existingDBCount > 0 { s.WriteString(fmt.Sprintf(" Existing Databases: %d found\n", m.existingDBCount)) // Show first few database names