From 6239e57a20199071e434cb88963f9c454a482b4a Mon Sep 17 00:00:00 2001 From: Renz Date: Wed, 12 Nov 2025 08:31:14 +0000 Subject: [PATCH] Fix: Interactive cluster restore cleanup no longer requires database connection Issue: When enabling cluster cleanup (Option C) in interactive restore mode, the tool tried to connect to the database to drop existing databases. This was confusing because: - Cluster restore itself doesn't use database connections - It uses CLI tools (psql, pg_restore) directly - Connection errors were misleading to users Solution: Changed cleanup to use psql command directly (dropDatabaseCLI) - Matches how cluster restore works (CLI tools, not connections) - No confusing connection errors - Cleaner, more consistent behavior - Uses postgres maintenance DB for DROP DATABASE commands Files changed: - internal/tui/restore_exec.go: Added dropDatabaseCLI() helper function - Removed dbClient.Connect() requirement for cleanup - Cleanup now works exactly like cluster restore operations --- internal/tui/restore_exec.go | 46 +++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/internal/tui/restore_exec.go b/internal/tui/restore_exec.go index 7dc5587..492ebe5 100644 --- a/internal/tui/restore_exec.go +++ b/internal/tui/restore_exec.go @@ -3,6 +3,7 @@ package tui import ( "context" "fmt" + "os/exec" "strings" "time" @@ -115,19 +116,11 @@ func executeRestoreWithTUIProgress(cfg *config.Config, log logger.Logger, archiv if restoreType == "restore-cluster" && cleanClusterFirst && len(existingDBs) > 0 { log.Info("Dropping existing user databases before cluster restore", "count", len(existingDBs)) - // Connect to database for cleanup - if err := dbClient.Connect(ctx); err != nil { - return restoreCompleteMsg{ - result: "", - err: fmt.Errorf("failed to connect for cleanup: %w", err), - elapsed: time.Since(start), - } - } - - // Drop each database + // Drop databases using command-line psql (no connection required) + // This matches how cluster restore works - uses CLI tools, not database connections droppedCount := 0 for _, dbName := range existingDBs { - if err := dbClient.DropDatabase(ctx, dbName); err != nil { + if err := dropDatabaseCLI(ctx, cfg, dbName); err != nil { log.Warn("Failed to drop database", "name", dbName, "error", err) // Continue with other databases } else { @@ -318,3 +311,34 @@ func formatDuration(d time.Duration) string { minutes := int(d.Minutes()) % 60 return fmt.Sprintf("%dh %dm", hours, minutes) } + +// dropDatabaseCLI drops a database using command-line psql +// This avoids needing an active database connection +func dropDatabaseCLI(ctx context.Context, cfg *config.Config, dbName string) error { + args := []string{ + "-p", fmt.Sprintf("%d", cfg.Port), + "-U", cfg.User, + "-d", "postgres", // Connect to postgres maintenance DB + "-c", fmt.Sprintf("DROP DATABASE IF EXISTS %s", dbName), + } + + // Only add -h flag if host is not localhost (to use Unix socket for peer auth) + if cfg.Host != "localhost" && cfg.Host != "127.0.0.1" && cfg.Host != "" { + args = append([]string{"-h", cfg.Host}, args...) + } + + cmd := exec.CommandContext(ctx, "psql", args...) + + // Set password if provided + if cfg.Password != "" { + cmd.Env = append(cmd.Environ(), fmt.Sprintf("PGPASSWORD=%s", cfg.Password)) + } + + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to drop database %s: %w\nOutput: %s", dbName, err, string(output)) + } + + return nil +} +