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
This commit is contained in:
@@ -4,8 +4,8 @@ This directory contains pre-compiled binaries for the DB Backup Tool across mult
|
|||||||
|
|
||||||
## Build Information
|
## Build Information
|
||||||
- **Version**: 3.42.50
|
- **Version**: 3.42.50
|
||||||
- **Build Time**: 2026-01-17_15:15:55_UTC
|
- **Build Time**: 2026-01-17_15:26:14_UTC
|
||||||
- **Git Commit**: 0e050b2
|
- **Git Commit**: df1ab2f
|
||||||
|
|
||||||
## Recent Updates (v1.1.0)
|
## Recent Updates (v1.1.0)
|
||||||
- ✅ Fixed TUI progress display with line-by-line output
|
- ✅ Fixed TUI progress display with line-by-line output
|
||||||
|
|||||||
@@ -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),
|
"-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)
|
// Always add -h flag for explicit host connection (required for password auth)
|
||||||
if s.cfg.Host != "localhost" && s.cfg.Host != "127.0.0.1" && s.cfg.Host != "" {
|
host := s.cfg.Host
|
||||||
args = append([]string{"-h", s.cfg.Host}, args...)
|
if host == "" {
|
||||||
|
host = "localhost"
|
||||||
}
|
}
|
||||||
|
args = append([]string{"-h", host}, args...)
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "psql", 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))
|
cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", s.cfg.Password))
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := cmd.Output()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
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
|
return strings.TrimSpace(string(output)) == "1", nil
|
||||||
@@ -405,10 +407,13 @@ func (s *Safety) listPostgresUserDatabases(ctx context.Context) ([]string, error
|
|||||||
"-c", query,
|
"-c", query,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only add -h flag if host is not localhost (to use Unix socket for peer auth)
|
// Always add -h flag for explicit host connection (required for password auth)
|
||||||
if s.cfg.Host != "localhost" && s.cfg.Host != "127.0.0.1" && s.cfg.Host != "" {
|
// Empty or unset host defaults to localhost
|
||||||
args = append([]string{"-h", s.cfg.Host}, args...)
|
host := s.cfg.Host
|
||||||
|
if host == "" {
|
||||||
|
host = "localhost"
|
||||||
}
|
}
|
||||||
|
args = append([]string{"-h", host}, args...)
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "psql", 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))
|
cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", s.cfg.Password))
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := cmd.Output()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
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
|
// Parse output
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ type RestorePreviewModel struct {
|
|||||||
cleanClusterFirst bool // For cluster restore: drop all user databases first
|
cleanClusterFirst bool // For cluster restore: drop all user databases first
|
||||||
existingDBCount int // Number of existing user databases
|
existingDBCount int // Number of existing user databases
|
||||||
existingDBs []string // List of existing user databases
|
existingDBs []string // List of existing user databases
|
||||||
|
existingDBError string // Error message if database listing failed
|
||||||
safetyChecks []SafetyCheck
|
safetyChecks []SafetyCheck
|
||||||
checking bool
|
checking bool
|
||||||
canProceed bool
|
canProceed bool
|
||||||
@@ -102,6 +103,7 @@ type safetyCheckCompleteMsg struct {
|
|||||||
canProceed bool
|
canProceed bool
|
||||||
existingDBCount int
|
existingDBCount int
|
||||||
existingDBs []string
|
existingDBs []string
|
||||||
|
existingDBError string
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSafetyChecks(cfg *config.Config, log logger.Logger, archive ArchiveInfo, targetDB string) tea.Cmd {
|
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}
|
check = SafetyCheck{Name: "Existing databases", Status: "checking", Critical: false}
|
||||||
|
|
||||||
// Get list of existing user databases (exclude templates and system DBs)
|
// Get list of existing user databases (exclude templates and system DBs)
|
||||||
|
var existingDBError string
|
||||||
dbList, err := safety.ListUserDatabases(ctx)
|
dbList, err := safety.ListUserDatabases(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
check.Status = "warning"
|
check.Status = "warning"
|
||||||
check.Message = fmt.Sprintf("Cannot list databases: %v", err)
|
check.Message = fmt.Sprintf("Cannot list databases: %v", err)
|
||||||
|
existingDBError = err.Error()
|
||||||
} else {
|
} else {
|
||||||
existingDBCount = len(dbList)
|
existingDBCount = len(dbList)
|
||||||
existingDBs = dbList
|
existingDBs = dbList
|
||||||
@@ -238,6 +242,14 @@ func runSafetyChecks(cfg *config.Config, log logger.Logger, archive ArchiveInfo,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
checks = append(checks, check)
|
checks = append(checks, check)
|
||||||
|
|
||||||
|
return safetyCheckCompleteMsg{
|
||||||
|
checks: checks,
|
||||||
|
canProceed: canProceed,
|
||||||
|
existingDBCount: existingDBCount,
|
||||||
|
existingDBs: existingDBs,
|
||||||
|
existingDBError: existingDBError,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return safetyCheckCompleteMsg{
|
return safetyCheckCompleteMsg{
|
||||||
@@ -257,6 +269,7 @@ func (m RestorePreviewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.canProceed = msg.canProceed
|
m.canProceed = msg.canProceed
|
||||||
m.existingDBCount = msg.existingDBCount
|
m.existingDBCount = msg.existingDBCount
|
||||||
m.existingDBs = msg.existingDBs
|
m.existingDBs = msg.existingDBs
|
||||||
|
m.existingDBError = msg.existingDBError
|
||||||
// Auto-forward in auto-confirm mode
|
// Auto-forward in auto-confirm mode
|
||||||
if m.config.TUIAutoConfirm {
|
if m.config.TUIAutoConfirm {
|
||||||
return m.parent, tea.Quit
|
return m.parent, tea.Quit
|
||||||
@@ -275,12 +288,17 @@ func (m RestorePreviewModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
case "c":
|
case "c":
|
||||||
if m.mode == "restore-cluster" {
|
if m.mode == "restore-cluster" {
|
||||||
// Toggle cluster cleanup
|
// Prevent toggle if we couldn't detect existing databases
|
||||||
m.cleanClusterFirst = !m.cleanClusterFirst
|
if m.existingDBError != "" {
|
||||||
if m.cleanClusterFirst {
|
m.message = checkWarningStyle.Render("[WARN] Cannot enable cleanup - database detection failed")
|
||||||
m.message = checkWarningStyle.Render(fmt.Sprintf("[WARN] Will drop %d existing database(s) before restore", m.existingDBCount))
|
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
// Toggle create if missing
|
// Toggle create if missing
|
||||||
@@ -382,7 +400,11 @@ func (m RestorePreviewModel) View() string {
|
|||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
s.WriteString(fmt.Sprintf(" Host: %s:%d\n", m.config.Host, m.config.Port))
|
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))
|
s.WriteString(fmt.Sprintf(" Existing Databases: %d found\n", m.existingDBCount))
|
||||||
|
|
||||||
// Show first few database names
|
// Show first few database names
|
||||||
|
|||||||
Reference in New Issue
Block a user