Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 490a12f858 | |||
| ea4337e298 | |||
| bbd4f0ceac | |||
| f6f8b04785 |
40
README.md
40
README.md
@@ -194,21 +194,49 @@ r: Restore | v: Verify | i: Info | d: Diagnose | D: Delete | R: Refresh | Esc: B
|
|||||||
```
|
```
|
||||||
Configuration Settings
|
Configuration Settings
|
||||||
|
|
||||||
|
[SYSTEM] Detected Resources
|
||||||
|
CPU: 8 physical cores, 16 logical cores
|
||||||
|
Memory: 32GB total, 28GB available
|
||||||
|
Recommended Profile: balanced
|
||||||
|
→ 8 cores and 32GB RAM supports moderate parallelism
|
||||||
|
|
||||||
|
[CONFIG] Current Settings
|
||||||
|
Target DB: PostgreSQL (postgres)
|
||||||
|
Database: postgres@localhost:5432
|
||||||
|
Backup Dir: /var/backups/postgres
|
||||||
|
Compression: Level 6
|
||||||
|
Profile: balanced | Cluster: 2 parallel | Jobs: 4
|
||||||
|
|
||||||
> Database Type: postgres
|
> Database Type: postgres
|
||||||
CPU Workload Type: balanced
|
CPU Workload Type: balanced
|
||||||
Backup Directory: /root/db_backups
|
Resource Profile: balanced (P:2 J:4)
|
||||||
Work Directory: /tmp
|
Cluster Parallelism: 2
|
||||||
|
Backup Directory: /var/backups/postgres
|
||||||
|
Work Directory: (system temp)
|
||||||
Compression Level: 6
|
Compression Level: 6
|
||||||
Parallel Jobs: 16
|
Parallel Jobs: 4
|
||||||
Dump Jobs: 8
|
Dump Jobs: 4
|
||||||
Database Host: localhost
|
Database Host: localhost
|
||||||
Database Port: 5432
|
Database Port: 5432
|
||||||
Database User: root
|
Database User: postgres
|
||||||
SSL Mode: prefer
|
SSL Mode: prefer
|
||||||
|
|
||||||
s: Save | r: Reset | q: Menu
|
[KEYS] ↑↓ navigate | Enter edit | 'l' large-db | 'c' conservative | 'p' recommend | 's' save | 'q' menu
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Resource Profiles for Large Databases:**
|
||||||
|
|
||||||
|
When restoring large databases on VMs with limited resources, use the resource profile settings to prevent "out of shared memory" errors:
|
||||||
|
|
||||||
|
| Profile | Cluster Parallel | Jobs | Best For |
|
||||||
|
|---------|------------------|------|----------|
|
||||||
|
| conservative | 1 | 1 | Small VMs (<16GB RAM) |
|
||||||
|
| balanced | 2 | 2-4 | Medium VMs (16-32GB RAM) |
|
||||||
|
| performance | 4 | 4-8 | Large servers (32GB+ RAM) |
|
||||||
|
| large-db | 1 | 2 | Large databases on any hardware |
|
||||||
|
|
||||||
|
**Quick shortcuts:** Press `l` to apply large-db profile, `c` for conservative, `p` to show recommendation.
|
||||||
|
|
||||||
**Database Status:**
|
**Database Status:**
|
||||||
```
|
```
|
||||||
Database Status & Health Check
|
Database Status & Health Check
|
||||||
|
|||||||
@@ -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_16:07:42_UTC
|
- **Build Time**: 2026-01-18_11:06:11_UTC
|
||||||
- **Git Commit**: e2cf9ad
|
- **Git Commit**: ea4337e
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -200,11 +200,11 @@ func New() *Config {
|
|||||||
SSLMode: sslMode,
|
SSLMode: sslMode,
|
||||||
Insecure: getEnvBool("INSECURE", false),
|
Insecure: getEnvBool("INSECURE", false),
|
||||||
|
|
||||||
// Backup defaults
|
// Backup defaults - use recommended profile's settings for small VMs
|
||||||
BackupDir: backupDir,
|
BackupDir: backupDir,
|
||||||
CompressionLevel: getEnvInt("COMPRESS_LEVEL", 6),
|
CompressionLevel: getEnvInt("COMPRESS_LEVEL", 6),
|
||||||
Jobs: getEnvInt("JOBS", getDefaultJobs(cpuInfo)),
|
Jobs: getEnvInt("JOBS", recommendedProfile.Jobs),
|
||||||
DumpJobs: getEnvInt("DUMP_JOBS", getDefaultDumpJobs(cpuInfo)),
|
DumpJobs: getEnvInt("DUMP_JOBS", recommendedProfile.DumpJobs),
|
||||||
MaxCores: getEnvInt("MAX_CORES", getDefaultMaxCores(cpuInfo)),
|
MaxCores: getEnvInt("MAX_CORES", getDefaultMaxCores(cpuInfo)),
|
||||||
AutoDetectCores: getEnvBool("AUTO_DETECT_CORES", true),
|
AutoDetectCores: getEnvBool("AUTO_DETECT_CORES", true),
|
||||||
CPUWorkloadType: getEnvString("CPU_WORKLOAD_TYPE", "balanced"),
|
CPUWorkloadType: getEnvString("CPU_WORKLOAD_TYPE", "balanced"),
|
||||||
@@ -233,8 +233,8 @@ func New() *Config {
|
|||||||
// Timeouts - default 24 hours (1440 min) to handle very large databases with large objects
|
// Timeouts - default 24 hours (1440 min) to handle very large databases with large objects
|
||||||
ClusterTimeoutMinutes: getEnvInt("CLUSTER_TIMEOUT_MIN", 1440),
|
ClusterTimeoutMinutes: getEnvInt("CLUSTER_TIMEOUT_MIN", 1440),
|
||||||
|
|
||||||
// Cluster parallelism (default: 2 concurrent operations for faster cluster backup/restore)
|
// Cluster parallelism - use recommended profile's setting for small VMs
|
||||||
ClusterParallelism: getEnvInt("CLUSTER_PARALLELISM", 2),
|
ClusterParallelism: getEnvInt("CLUSTER_PARALLELISM", recommendedProfile.ClusterParallelism),
|
||||||
|
|
||||||
// Working directory for large operations (default: system temp)
|
// Working directory for large operations (default: system temp)
|
||||||
WorkDir: getEnvString("WORK_DIR", ""),
|
WorkDir: getEnvString("WORK_DIR", ""),
|
||||||
|
|||||||
@@ -659,7 +659,13 @@ func (m RestoreExecutionModel) View() string {
|
|||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
s.WriteString(errorStyle.Render("╚══════════════════════════════════════════════════════════════╝"))
|
s.WriteString(errorStyle.Render("╚══════════════════════════════════════════════════════════════╝"))
|
||||||
s.WriteString("\n\n")
|
s.WriteString("\n\n")
|
||||||
s.WriteString(errorStyle.Render(fmt.Sprintf(" Error: %v", m.err)))
|
|
||||||
|
// Parse and display error in a clean, structured format
|
||||||
|
errStr := m.err.Error()
|
||||||
|
|
||||||
|
// Extract key parts from the error message
|
||||||
|
errDisplay := formatRestoreError(errStr)
|
||||||
|
s.WriteString(errDisplay)
|
||||||
s.WriteString("\n")
|
s.WriteString("\n")
|
||||||
} else {
|
} else {
|
||||||
s.WriteString(successStyle.Render("╔══════════════════════════════════════════════════════════════╗"))
|
s.WriteString(successStyle.Render("╔══════════════════════════════════════════════════════════════╗"))
|
||||||
@@ -1005,3 +1011,188 @@ func dropDatabaseCLI(ctx context.Context, cfg *config.Config, dbName string) err
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// formatRestoreError formats a restore error message for clean TUI display
|
||||||
|
func formatRestoreError(errStr string) string {
|
||||||
|
var s strings.Builder
|
||||||
|
maxLineWidth := 60
|
||||||
|
|
||||||
|
// Common patterns to extract
|
||||||
|
patterns := []struct {
|
||||||
|
key string
|
||||||
|
pattern string
|
||||||
|
}{
|
||||||
|
{"Error Type", "ERROR:"},
|
||||||
|
{"Hint", "HINT:"},
|
||||||
|
{"Last Error", "last error:"},
|
||||||
|
{"Total Errors", "total errors:"},
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, try to extract a clean error summary
|
||||||
|
errLines := strings.Split(errStr, "\n")
|
||||||
|
|
||||||
|
// Find the main error message (first line or first ERROR:)
|
||||||
|
mainError := ""
|
||||||
|
hint := ""
|
||||||
|
totalErrors := ""
|
||||||
|
dbsFailed := []string{}
|
||||||
|
|
||||||
|
for _, line := range errLines {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract ERROR messages
|
||||||
|
if strings.Contains(line, "ERROR:") {
|
||||||
|
if mainError == "" {
|
||||||
|
// Get just the ERROR part
|
||||||
|
idx := strings.Index(line, "ERROR:")
|
||||||
|
if idx >= 0 {
|
||||||
|
mainError = strings.TrimSpace(line[idx:])
|
||||||
|
// Truncate if too long
|
||||||
|
if len(mainError) > maxLineWidth {
|
||||||
|
mainError = mainError[:maxLineWidth-3] + "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract HINT
|
||||||
|
if strings.Contains(line, "HINT:") {
|
||||||
|
idx := strings.Index(line, "HINT:")
|
||||||
|
if idx >= 0 {
|
||||||
|
hint = strings.TrimSpace(line[idx+5:])
|
||||||
|
if len(hint) > maxLineWidth {
|
||||||
|
hint = hint[:maxLineWidth-3] + "..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract total errors count
|
||||||
|
if strings.Contains(line, "total errors:") {
|
||||||
|
idx := strings.Index(line, "total errors:")
|
||||||
|
if idx >= 0 {
|
||||||
|
totalErrors = strings.TrimSpace(line[idx+13:])
|
||||||
|
// Just extract the number
|
||||||
|
parts := strings.Fields(totalErrors)
|
||||||
|
if len(parts) > 0 {
|
||||||
|
totalErrors = parts[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract failed database names (for cluster restore)
|
||||||
|
if strings.Contains(line, ": restore failed:") {
|
||||||
|
parts := strings.SplitN(line, ":", 2)
|
||||||
|
if len(parts) > 0 {
|
||||||
|
dbName := strings.TrimSpace(parts[0])
|
||||||
|
if dbName != "" && !strings.HasPrefix(dbName, "Error") {
|
||||||
|
dbsFailed = append(dbsFailed, dbName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no structured error found, use the first line
|
||||||
|
if mainError == "" {
|
||||||
|
firstLine := errStr
|
||||||
|
if idx := strings.Index(errStr, "\n"); idx > 0 {
|
||||||
|
firstLine = errStr[:idx]
|
||||||
|
}
|
||||||
|
if len(firstLine) > maxLineWidth*2 {
|
||||||
|
firstLine = firstLine[:maxLineWidth*2-3] + "..."
|
||||||
|
}
|
||||||
|
mainError = firstLine
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build structured error display
|
||||||
|
s.WriteString(infoStyle.Render(" ─── Error Details ─────────────────────────────────────────"))
|
||||||
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
|
// Error type detection
|
||||||
|
errorType := "critical"
|
||||||
|
if strings.Contains(errStr, "out of shared memory") || strings.Contains(errStr, "max_locks_per_transaction") {
|
||||||
|
errorType = "critical"
|
||||||
|
} else if strings.Contains(errStr, "connection") {
|
||||||
|
errorType = "connection"
|
||||||
|
} else if strings.Contains(errStr, "permission") || strings.Contains(errStr, "access") {
|
||||||
|
errorType = "permission"
|
||||||
|
}
|
||||||
|
|
||||||
|
s.WriteString(fmt.Sprintf(" Type: %s\n", errorType))
|
||||||
|
s.WriteString(fmt.Sprintf(" Message: %s\n", mainError))
|
||||||
|
|
||||||
|
if hint != "" {
|
||||||
|
s.WriteString(fmt.Sprintf(" Hint: %s\n", hint))
|
||||||
|
}
|
||||||
|
|
||||||
|
if totalErrors != "" {
|
||||||
|
s.WriteString(fmt.Sprintf(" Total Errors: %s\n", totalErrors))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show failed databases (max 5)
|
||||||
|
if len(dbsFailed) > 0 {
|
||||||
|
s.WriteString("\n")
|
||||||
|
s.WriteString(" Failed Databases:\n")
|
||||||
|
for i, db := range dbsFailed {
|
||||||
|
if i >= 5 {
|
||||||
|
s.WriteString(fmt.Sprintf(" ... and %d more\n", len(dbsFailed)-5))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
s.WriteString(fmt.Sprintf(" • %s\n", db))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
s.WriteString("\n")
|
||||||
|
s.WriteString(infoStyle.Render(" ─── Diagnosis ─────────────────────────────────────────────"))
|
||||||
|
s.WriteString("\n\n")
|
||||||
|
|
||||||
|
// Provide specific recommendations based on error
|
||||||
|
if strings.Contains(errStr, "out of shared memory") || strings.Contains(errStr, "max_locks_per_transaction") {
|
||||||
|
s.WriteString(errorStyle.Render(" • Cannot access file: stat : no such file or directory\n"))
|
||||||
|
s.WriteString("\n")
|
||||||
|
s.WriteString(infoStyle.Render(" ─── [HINT] Recommendations ────────────────────────────────"))
|
||||||
|
s.WriteString("\n\n")
|
||||||
|
s.WriteString(" Lock table exhausted. Total capacity = max_locks_per_transaction\n")
|
||||||
|
s.WriteString(" × (max_connections + max_prepared_transactions).\n\n")
|
||||||
|
s.WriteString(" If you reduced VM size or max_connections, you need higher\n")
|
||||||
|
s.WriteString(" max_locks_per_transaction to compensate.\n\n")
|
||||||
|
s.WriteString(successStyle.Render(" FIX OPTIONS:\n"))
|
||||||
|
s.WriteString(" 1. Use 'conservative' or 'large-db' profile in Settings\n")
|
||||||
|
s.WriteString(" (press 'l' for large-db, 'c' for conservative)\n\n")
|
||||||
|
s.WriteString(" 2. Increase PostgreSQL locks:\n")
|
||||||
|
s.WriteString(" ALTER SYSTEM SET max_locks_per_transaction = 4096;\n")
|
||||||
|
s.WriteString(" Then RESTART PostgreSQL.\n\n")
|
||||||
|
s.WriteString(" 3. Reduce parallel jobs:\n")
|
||||||
|
s.WriteString(" Set Cluster Parallelism = 1 in Settings\n")
|
||||||
|
} else if strings.Contains(errStr, "connection") || strings.Contains(errStr, "refused") {
|
||||||
|
s.WriteString(" • Database connection failed\n\n")
|
||||||
|
s.WriteString(infoStyle.Render(" ─── [HINT] Recommendations ────────────────────────────────"))
|
||||||
|
s.WriteString("\n\n")
|
||||||
|
s.WriteString(" 1. Check database is running\n")
|
||||||
|
s.WriteString(" 2. Verify host, port, and credentials in Settings\n")
|
||||||
|
s.WriteString(" 3. Check firewall/network connectivity\n")
|
||||||
|
} else if strings.Contains(errStr, "permission") || strings.Contains(errStr, "denied") {
|
||||||
|
s.WriteString(" • Permission denied\n\n")
|
||||||
|
s.WriteString(infoStyle.Render(" ─── [HINT] Recommendations ────────────────────────────────"))
|
||||||
|
s.WriteString("\n\n")
|
||||||
|
s.WriteString(" 1. Verify database user has sufficient privileges\n")
|
||||||
|
s.WriteString(" 2. Grant CREATE/DROP DATABASE permissions if restoring cluster\n")
|
||||||
|
s.WriteString(" 3. Check file system permissions on backup directory\n")
|
||||||
|
} else {
|
||||||
|
s.WriteString(" See error message above for details.\n\n")
|
||||||
|
s.WriteString(infoStyle.Render(" ─── [HINT] General Recommendations ────────────────────────"))
|
||||||
|
s.WriteString("\n\n")
|
||||||
|
s.WriteString(" 1. Check the full error log for details\n")
|
||||||
|
s.WriteString(" 2. Try restoring with 'conservative' profile (press 'c')\n")
|
||||||
|
s.WriteString(" 3. For large databases, use 'large-db' profile (press 'l')\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.WriteString("\n")
|
||||||
|
|
||||||
|
// Suppress the pattern variable since we don't use it but defined it
|
||||||
|
_ = patterns
|
||||||
|
|
||||||
|
return s.String()
|
||||||
|
}
|
||||||
|
|||||||
@@ -402,6 +402,17 @@ 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))
|
||||||
|
|
||||||
|
// Show Resource Profile and CPU Workload settings
|
||||||
|
profile := m.config.GetCurrentProfile()
|
||||||
|
if profile != nil {
|
||||||
|
s.WriteString(fmt.Sprintf(" Resource Profile: %s (Parallel:%d, Jobs:%d)\n",
|
||||||
|
profile.Name, profile.ClusterParallelism, profile.Jobs))
|
||||||
|
} else {
|
||||||
|
s.WriteString(fmt.Sprintf(" Resource Profile: %s\n", m.config.ResourceProfile))
|
||||||
|
}
|
||||||
|
s.WriteString(fmt.Sprintf(" CPU Workload: %s\n", m.config.CPUWorkloadType))
|
||||||
|
s.WriteString(fmt.Sprintf(" Cluster Parallelism: %d databases\n", m.config.ClusterParallelism))
|
||||||
|
|
||||||
if m.existingDBError != "" {
|
if m.existingDBError != "" {
|
||||||
// Show warning when database listing failed - but still allow cleanup toggle
|
// Show warning when database listing failed - but still allow cleanup toggle
|
||||||
s.WriteString(checkWarningStyle.Render(" Existing Databases: Detection failed\n"))
|
s.WriteString(checkWarningStyle.Render(" Existing Databases: Detection failed\n"))
|
||||||
|
|||||||
Reference in New Issue
Block a user