Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e2cf9adc62 | |||
| 29e089fe3b | |||
| 9396c8e605 |
@@ -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:26:14_UTC
|
- **Build Time**: 2026-01-17_16:00:43_UTC
|
||||||
- **Git Commit**: df1ab2f
|
- **Git Commit**: 29e089f
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -417,10 +417,14 @@ func (s *Safety) listPostgresUserDatabases(ctx context.Context) ([]string, error
|
|||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "psql", args...)
|
cmd := exec.CommandContext(ctx, "psql", args...)
|
||||||
|
|
||||||
// Set password if provided
|
// Set password - check config first, then environment
|
||||||
|
env := os.Environ()
|
||||||
if s.cfg.Password != "" {
|
if s.cfg.Password != "" {
|
||||||
cmd.Env = append(os.Environ(), fmt.Sprintf("PGPASSWORD=%s", s.cfg.Password))
|
env = append(env, fmt.Sprintf("PGPASSWORD=%s", s.cfg.Password))
|
||||||
}
|
}
|
||||||
|
cmd.Env = env
|
||||||
|
|
||||||
|
s.log.Debug("Listing PostgreSQL databases", "host", host, "port", s.cfg.Port, "user", s.cfg.User)
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -438,6 +442,8 @@ func (s *Safety) listPostgresUserDatabases(ctx context.Context) ([]string, error
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
s.log.Debug("Found user databases", "count", len(databases), "databases", databases, "raw_output", string(output))
|
||||||
|
|
||||||
return databases, nil
|
return databases, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -273,26 +273,42 @@ func executeRestoreWithTUIProgress(parentCtx context.Context, cfg *config.Config
|
|||||||
defer dbClient.Close()
|
defer dbClient.Close()
|
||||||
|
|
||||||
// STEP 1: Clean cluster if requested (drop all existing user databases)
|
// STEP 1: Clean cluster if requested (drop all existing user databases)
|
||||||
if restoreType == "restore-cluster" && cleanClusterFirst && len(existingDBs) > 0 {
|
if restoreType == "restore-cluster" && cleanClusterFirst {
|
||||||
log.Info("Dropping existing user databases before cluster restore", "count", len(existingDBs))
|
// Re-detect databases at execution time to get current state
|
||||||
|
// The preview list may be stale or detection may have failed earlier
|
||||||
// Drop databases using command-line psql (no connection required)
|
safety := restore.NewSafety(cfg, log)
|
||||||
// This matches how cluster restore works - uses CLI tools, not database connections
|
currentDBs, err := safety.ListUserDatabases(ctx)
|
||||||
droppedCount := 0
|
if err != nil {
|
||||||
for _, dbName := range existingDBs {
|
log.Warn("Failed to list databases for cleanup, using preview list", "error", err)
|
||||||
// Create timeout context for each database drop (5 minutes per DB - large DBs take time)
|
currentDBs = existingDBs // Fall back to preview list
|
||||||
dropCtx, dropCancel := context.WithTimeout(ctx, 5*time.Minute)
|
} else if len(currentDBs) > 0 {
|
||||||
if err := dropDatabaseCLI(dropCtx, cfg, dbName); err != nil {
|
log.Info("Re-detected user databases for cleanup", "count", len(currentDBs), "databases", currentDBs)
|
||||||
log.Warn("Failed to drop database", "name", dbName, "error", err)
|
existingDBs = currentDBs // Update with fresh list
|
||||||
// Continue with other databases
|
|
||||||
} else {
|
|
||||||
droppedCount++
|
|
||||||
log.Info("Dropped database", "name", dbName)
|
|
||||||
}
|
|
||||||
dropCancel() // Clean up context
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("Cluster cleanup completed", "dropped", droppedCount, "total", len(existingDBs))
|
if len(existingDBs) > 0 {
|
||||||
|
log.Info("Dropping existing user databases before cluster restore", "count", len(existingDBs))
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
// Create timeout context for each database drop (5 minutes per DB - large DBs take time)
|
||||||
|
dropCtx, dropCancel := context.WithTimeout(ctx, 5*time.Minute)
|
||||||
|
if err := dropDatabaseCLI(dropCtx, cfg, dbName); err != nil {
|
||||||
|
log.Warn("Failed to drop database", "name", dbName, "error", err)
|
||||||
|
// Continue with other databases
|
||||||
|
} else {
|
||||||
|
droppedCount++
|
||||||
|
log.Info("Dropped database", "name", dbName)
|
||||||
|
}
|
||||||
|
dropCancel() // Clean up context
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Cluster cleanup completed", "dropped", droppedCount, "total", len(existingDBs))
|
||||||
|
} else {
|
||||||
|
log.Info("No user databases to clean up")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// STEP 2: Create restore engine with silent progress (no stdout interference with TUI)
|
// STEP 2: Create restore engine with silent progress (no stdout interference with TUI)
|
||||||
|
|||||||
@@ -288,17 +288,19 @@ 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" {
|
||||||
// Prevent toggle if we couldn't detect existing databases
|
// Toggle cluster cleanup - databases will be re-detected at execution time
|
||||||
if m.existingDBError != "" {
|
m.cleanClusterFirst = !m.cleanClusterFirst
|
||||||
m.message = checkWarningStyle.Render("[WARN] Cannot enable cleanup - database detection failed")
|
if m.cleanClusterFirst {
|
||||||
} else {
|
if m.existingDBError != "" {
|
||||||
// Toggle cluster cleanup
|
// Detection failed in preview - will re-detect at execution
|
||||||
m.cleanClusterFirst = !m.cleanClusterFirst
|
m.message = checkWarningStyle.Render("[WARN] Will clean existing databases before restore (detection pending)")
|
||||||
if m.cleanClusterFirst {
|
} else if m.existingDBCount > 0 {
|
||||||
m.message = checkWarningStyle.Render(fmt.Sprintf("[WARN] Will drop %d existing database(s) before restore", m.existingDBCount))
|
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")
|
m.message = infoStyle.Render("[INFO] Cleanup enabled (no databases currently detected)")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
m.message = fmt.Sprintf("Clean cluster first: disabled")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Toggle create if missing
|
// Toggle create if missing
|
||||||
@@ -401,9 +403,10 @@ func (m RestorePreviewModel) View() string {
|
|||||||
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.existingDBError != "" {
|
if m.existingDBError != "" {
|
||||||
// Show error when database listing failed
|
// Show warning when database listing failed - but still allow cleanup toggle
|
||||||
s.WriteString(checkWarningStyle.Render(fmt.Sprintf(" Existing Databases: Unable to detect (%s)\n", m.existingDBError)))
|
s.WriteString(checkWarningStyle.Render(" Existing Databases: Detection failed\n"))
|
||||||
s.WriteString(infoStyle.Render(" (Cleanup option disabled - cannot verify database status)\n"))
|
s.WriteString(infoStyle.Render(fmt.Sprintf(" (%s)\n", m.existingDBError)))
|
||||||
|
s.WriteString(infoStyle.Render(" (Will re-detect at restore time)\n"))
|
||||||
} else if m.existingDBCount > 0 {
|
} 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))
|
||||||
|
|
||||||
@@ -417,17 +420,20 @@ func (m RestorePreviewModel) View() string {
|
|||||||
}
|
}
|
||||||
s.WriteString(fmt.Sprintf(" - %s\n", db))
|
s.WriteString(fmt.Sprintf(" - %s\n", db))
|
||||||
}
|
}
|
||||||
|
|
||||||
cleanIcon := "[N]"
|
|
||||||
cleanStyle := infoStyle
|
|
||||||
if m.cleanClusterFirst {
|
|
||||||
cleanIcon = "[Y]"
|
|
||||||
cleanStyle = checkWarningStyle
|
|
||||||
}
|
|
||||||
s.WriteString(cleanStyle.Render(fmt.Sprintf(" Clean All First: %s %v (press 'c' to toggle)\n", cleanIcon, m.cleanClusterFirst)))
|
|
||||||
} else {
|
} else {
|
||||||
s.WriteString(" Existing Databases: None (clean slate)\n")
|
s.WriteString(" Existing Databases: None (clean slate)\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Always show cleanup toggle for cluster restore
|
||||||
|
cleanIcon := "[N]"
|
||||||
|
cleanStyle := infoStyle
|
||||||
|
if m.cleanClusterFirst {
|
||||||
|
cleanIcon := "[Y]"
|
||||||
|
cleanStyle = checkWarningStyle
|
||||||
|
s.WriteString(cleanStyle.Render(fmt.Sprintf(" Clean All First: %s enabled (press 'c' to toggle)\n", cleanIcon)))
|
||||||
|
} else {
|
||||||
|
s.WriteString(cleanStyle.Render(fmt.Sprintf(" Clean All First: %s disabled (press 'c' to toggle)\n", cleanIcon)))
|
||||||
|
}
|
||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,10 +481,18 @@ func (m RestorePreviewModel) View() string {
|
|||||||
s.WriteString(infoStyle.Render(" All existing data in target database will be dropped!"))
|
s.WriteString(infoStyle.Render(" All existing data in target database will be dropped!"))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
}
|
}
|
||||||
if m.cleanClusterFirst && m.existingDBCount > 0 {
|
if m.cleanClusterFirst {
|
||||||
s.WriteString(checkWarningStyle.Render("[DANGER] WARNING: Cluster cleanup enabled"))
|
s.WriteString(checkWarningStyle.Render("[DANGER] WARNING: Cluster cleanup enabled"))
|
||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
s.WriteString(checkWarningStyle.Render(fmt.Sprintf(" %d existing database(s) will be DROPPED before restore!", m.existingDBCount)))
|
if m.existingDBError != "" {
|
||||||
|
s.WriteString(checkWarningStyle.Render(" Existing databases will be DROPPED before restore!"))
|
||||||
|
s.WriteString("\n")
|
||||||
|
s.WriteString(infoStyle.Render(" (Database count will be detected at restore time)"))
|
||||||
|
} else if m.existingDBCount > 0 {
|
||||||
|
s.WriteString(checkWarningStyle.Render(fmt.Sprintf(" %d existing database(s) will be DROPPED before restore!", m.existingDBCount)))
|
||||||
|
} else {
|
||||||
|
s.WriteString(infoStyle.Render(" No databases currently detected - cleanup will verify at restore time"))
|
||||||
|
}
|
||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
s.WriteString(infoStyle.Render(" This ensures a clean disaster recovery scenario"))
|
s.WriteString(infoStyle.Render(" This ensures a clean disaster recovery scenario"))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
|
|||||||
Reference in New Issue
Block a user