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
|
||||
|
||||
[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
|
||||
CPU Workload Type: balanced
|
||||
Backup Directory: /root/db_backups
|
||||
Work Directory: /tmp
|
||||
Resource Profile: balanced (P:2 J:4)
|
||||
Cluster Parallelism: 2
|
||||
Backup Directory: /var/backups/postgres
|
||||
Work Directory: (system temp)
|
||||
Compression Level: 6
|
||||
Parallel Jobs: 16
|
||||
Dump Jobs: 8
|
||||
Parallel Jobs: 4
|
||||
Dump Jobs: 4
|
||||
Database Host: localhost
|
||||
Database Port: 5432
|
||||
Database User: root
|
||||
Database User: postgres
|
||||
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 & Health Check
|
||||
|
||||
@@ -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_16:07:42_UTC
|
||||
- **Git Commit**: e2cf9ad
|
||||
- **Build Time**: 2026-01-18_11:06:11_UTC
|
||||
- **Git Commit**: ea4337e
|
||||
|
||||
## Recent Updates (v1.1.0)
|
||||
- ✅ Fixed TUI progress display with line-by-line output
|
||||
|
||||
@@ -200,11 +200,11 @@ func New() *Config {
|
||||
SSLMode: sslMode,
|
||||
Insecure: getEnvBool("INSECURE", false),
|
||||
|
||||
// Backup defaults
|
||||
// Backup defaults - use recommended profile's settings for small VMs
|
||||
BackupDir: backupDir,
|
||||
CompressionLevel: getEnvInt("COMPRESS_LEVEL", 6),
|
||||
Jobs: getEnvInt("JOBS", getDefaultJobs(cpuInfo)),
|
||||
DumpJobs: getEnvInt("DUMP_JOBS", getDefaultDumpJobs(cpuInfo)),
|
||||
Jobs: getEnvInt("JOBS", recommendedProfile.Jobs),
|
||||
DumpJobs: getEnvInt("DUMP_JOBS", recommendedProfile.DumpJobs),
|
||||
MaxCores: getEnvInt("MAX_CORES", getDefaultMaxCores(cpuInfo)),
|
||||
AutoDetectCores: getEnvBool("AUTO_DETECT_CORES", true),
|
||||
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
|
||||
ClusterTimeoutMinutes: getEnvInt("CLUSTER_TIMEOUT_MIN", 1440),
|
||||
|
||||
// Cluster parallelism (default: 2 concurrent operations for faster cluster backup/restore)
|
||||
ClusterParallelism: getEnvInt("CLUSTER_PARALLELISM", 2),
|
||||
// Cluster parallelism - use recommended profile's setting for small VMs
|
||||
ClusterParallelism: getEnvInt("CLUSTER_PARALLELISM", recommendedProfile.ClusterParallelism),
|
||||
|
||||
// Working directory for large operations (default: system temp)
|
||||
WorkDir: getEnvString("WORK_DIR", ""),
|
||||
|
||||
@@ -659,7 +659,13 @@ func (m RestoreExecutionModel) View() string {
|
||||
s.WriteString("\n")
|
||||
s.WriteString(errorStyle.Render("╚══════════════════════════════════════════════════════════════╝"))
|
||||
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")
|
||||
} else {
|
||||
s.WriteString(successStyle.Render("╔══════════════════════════════════════════════════════════════╗"))
|
||||
@@ -1005,3 +1011,188 @@ func dropDatabaseCLI(ctx context.Context, cfg *config.Config, dbName string) err
|
||||
|
||||
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(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 != "" {
|
||||
// Show warning when database listing failed - but still allow cleanup toggle
|
||||
s.WriteString(checkWarningStyle.Render(" Existing Databases: Detection failed\n"))
|
||||
|
||||
Reference in New Issue
Block a user