Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| fec2652cd0 | |||
| b7498745f9 | |||
| 79f2efaaac | |||
| 19f44749b1 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -37,3 +37,6 @@ CRITICAL_BUGS_FIXED.md
|
||||
LEGAL_DOCUMENTATION.md
|
||||
LEGAL_*.md
|
||||
legal/
|
||||
|
||||
# Release binaries (uploaded via gh release, not git)
|
||||
release/dbbackup_*
|
||||
|
||||
46
CHANGELOG.md
46
CHANGELOG.md
@ -5,6 +5,52 @@ All notable changes to dbbackup will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [4.1.4] - 2026-01-29
|
||||
|
||||
### Added
|
||||
- **New `turbo` restore profile** 🚀 - Maximum restore speed, matches native `pg_restore -j8`
|
||||
- `ClusterParallelism = 2` (restore 2 DBs concurrently)
|
||||
- `Jobs = 8` (8 parallel pg_restore jobs)
|
||||
- `BufferedIO = true` (32KB write buffers for faster extraction)
|
||||
- Works on 16GB+ RAM, 4+ cores
|
||||
- Usage: `dbbackup restore cluster backup.tar.gz --profile=turbo --confirm`
|
||||
|
||||
- **Restore startup performance logging** - Shows actual parallelism settings at restore start
|
||||
- Logs profile name, cluster_parallelism, pg_restore_jobs, buffered_io
|
||||
- Helps verify settings before long restore operations
|
||||
|
||||
- **Buffered I/O optimization** - 32KB write buffers during tar extraction (turbo profile)
|
||||
- Reduces system call overhead
|
||||
- Improves I/O throughput for large archives
|
||||
|
||||
### Fixed
|
||||
- **TUI now respects saved profile settings** - Previously TUI forced `conservative` profile on every launch, ignoring user's saved configuration. Now properly loads and respects saved settings.
|
||||
|
||||
### Changed
|
||||
- TUI default profile changed from forced `conservative` to `balanced` (only when no profile configured)
|
||||
- `LargeDBMode` no longer forced on TUI startup - user controls it via settings
|
||||
|
||||
## [4.1.3] - 2026-01-27
|
||||
|
||||
### Added
|
||||
- **`--config` / `-c` global flag** - Specify config file path from anywhere
|
||||
- Example: `dbbackup --config /opt/dbbackup/.dbbackup.conf backup single mydb`
|
||||
- No longer need to `cd` to config directory before running commands
|
||||
- Works with all subcommands (backup, restore, verify, etc.)
|
||||
|
||||
## [4.1.2] - 2026-01-27
|
||||
|
||||
### Added
|
||||
- **`--socket` flag for MySQL/MariaDB** - Connect via Unix socket instead of TCP/IP
|
||||
- Usage: `dbbackup backup single mydb --db-type mysql --socket /var/run/mysqld/mysqld.sock`
|
||||
- Works for both backup and restore operations
|
||||
- Supports socket auth (no password required with proper permissions)
|
||||
|
||||
### Fixed
|
||||
- **Socket path as --host now works** - If `--host` starts with `/`, it's auto-detected as a socket path
|
||||
- Example: `--host /var/run/mysqld/mysqld.sock` now works correctly instead of DNS lookup error
|
||||
- Auto-converts to `--socket` internally
|
||||
|
||||
## [4.1.1] - 2026-01-25
|
||||
|
||||
### Added
|
||||
|
||||
3
QUICK.md
3
QUICK.md
@ -14,6 +14,9 @@ dbbackup backup single myapp
|
||||
# MySQL
|
||||
dbbackup backup single gitea --db-type mysql --host 127.0.0.1 --port 3306
|
||||
|
||||
# MySQL/MariaDB with Unix socket
|
||||
dbbackup backup single myapp --db-type mysql --socket /var/run/mysqld/mysqld.sock
|
||||
|
||||
# With compression level (0-9, default 6)
|
||||
dbbackup backup cluster --compression 9
|
||||
|
||||
|
||||
@ -66,14 +66,21 @@ TUI Automation Flags (for testing and CI/CD):
|
||||
cfg.TUIVerbose, _ = cmd.Flags().GetBool("verbose-tui")
|
||||
cfg.TUILogFile, _ = cmd.Flags().GetString("tui-log-file")
|
||||
|
||||
// Set conservative profile as default for TUI mode (safer for interactive users)
|
||||
if cfg.ResourceProfile == "" || cfg.ResourceProfile == "balanced" {
|
||||
cfg.ResourceProfile = "conservative"
|
||||
cfg.LargeDBMode = true
|
||||
// FIXED: Only set default profile if user hasn't configured one
|
||||
// Previously this forced conservative mode, ignoring user's saved settings
|
||||
if cfg.ResourceProfile == "" {
|
||||
// No profile configured at all - use balanced as sensible default
|
||||
cfg.ResourceProfile = "balanced"
|
||||
if cfg.Debug {
|
||||
log.Info("TUI mode: using conservative profile by default")
|
||||
log.Info("TUI mode: no profile configured, using 'balanced' default")
|
||||
}
|
||||
} else {
|
||||
// User has a configured profile - RESPECT IT!
|
||||
if cfg.Debug {
|
||||
log.Info("TUI mode: respecting user-configured profile", "profile", cfg.ResourceProfile)
|
||||
}
|
||||
}
|
||||
// Note: LargeDBMode is no longer forced - user controls it via settings
|
||||
|
||||
// Check authentication before starting TUI
|
||||
if cfg.IsPostgreSQL() {
|
||||
|
||||
33
cmd/root.go
33
cmd/root.go
@ -3,6 +3,7 @@ package cmd
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"dbbackup/internal/config"
|
||||
"dbbackup/internal/logger"
|
||||
@ -54,9 +55,26 @@ For help with specific commands, use: dbbackup [command] --help`,
|
||||
|
||||
// Load local config if not disabled
|
||||
if !cfg.NoLoadConfig {
|
||||
if localCfg, err := config.LoadLocalConfig(); err != nil {
|
||||
log.Warn("Failed to load local config", "error", err)
|
||||
} else if localCfg != nil {
|
||||
// Use custom config path if specified, otherwise default to current directory
|
||||
var localCfg *config.LocalConfig
|
||||
var err error
|
||||
if cfg.ConfigPath != "" {
|
||||
localCfg, err = config.LoadLocalConfigFromPath(cfg.ConfigPath)
|
||||
if err != nil {
|
||||
log.Warn("Failed to load config from specified path", "path", cfg.ConfigPath, "error", err)
|
||||
} else if localCfg != nil {
|
||||
log.Info("Loaded configuration", "path", cfg.ConfigPath)
|
||||
}
|
||||
} else {
|
||||
localCfg, err = config.LoadLocalConfig()
|
||||
if err != nil {
|
||||
log.Warn("Failed to load local config", "error", err)
|
||||
} else if localCfg != nil {
|
||||
log.Info("Loaded configuration from .dbbackup.conf")
|
||||
}
|
||||
}
|
||||
|
||||
if localCfg != nil {
|
||||
// Save current flag values that were explicitly set
|
||||
savedBackupDir := cfg.BackupDir
|
||||
savedHost := cfg.Host
|
||||
@ -71,7 +89,6 @@ For help with specific commands, use: dbbackup [command] --help`,
|
||||
|
||||
// Apply config from file
|
||||
config.ApplyLocalConfig(cfg, localCfg)
|
||||
log.Info("Loaded configuration from .dbbackup.conf")
|
||||
|
||||
// Restore explicitly set flag values (flags have priority)
|
||||
if flagsSet["backup-dir"] {
|
||||
@ -107,6 +124,12 @@ For help with specific commands, use: dbbackup [command] --help`,
|
||||
}
|
||||
}
|
||||
|
||||
// Auto-detect socket from --host path (if host starts with /)
|
||||
if strings.HasPrefix(cfg.Host, "/") && cfg.Socket == "" {
|
||||
cfg.Socket = cfg.Host
|
||||
cfg.Host = "localhost" // Reset host for socket connections
|
||||
}
|
||||
|
||||
return cfg.SetDatabaseType(cfg.DatabaseType)
|
||||
},
|
||||
}
|
||||
@ -134,8 +157,10 @@ func Execute(ctx context.Context, config *config.Config, logger logger.Logger) e
|
||||
cfg.Version, cfg.BuildTime, cfg.GitCommit)
|
||||
|
||||
// Add persistent flags
|
||||
rootCmd.PersistentFlags().StringVarP(&cfg.ConfigPath, "config", "c", "", "Path to config file (default: .dbbackup.conf in current directory)")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Host, "host", cfg.Host, "Database host")
|
||||
rootCmd.PersistentFlags().IntVar(&cfg.Port, "port", cfg.Port, "Database port")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Socket, "socket", cfg.Socket, "Unix socket path for MySQL/MariaDB (e.g., /var/run/mysqld/mysqld.sock)")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.User, "user", cfg.User, "Database user")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Database, "database", cfg.Database, "Database name")
|
||||
rootCmd.PersistentFlags().StringVar(&cfg.Password, "password", cfg.Password, "Database password")
|
||||
|
||||
@ -70,15 +70,43 @@ dbbackup restore cluster backup.tar.gz --profile=aggressive --confirm
|
||||
### Potato Profile (`--profile=potato`) 🥔
|
||||
**Easter egg:** Same as conservative, for servers running on a potato.
|
||||
|
||||
### Turbo Profile (`--profile=turbo`) 🚀
|
||||
**NEW! Best for:** Maximum restore speed - matches native pg_restore -j8 performance.
|
||||
|
||||
**Settings:**
|
||||
- Parallel databases: 2 (balanced I/O)
|
||||
- pg_restore jobs: 8 (like `pg_restore -j8`)
|
||||
- Buffered I/O: 32KB write buffers for faster extraction
|
||||
- Optimized for large databases
|
||||
|
||||
**When to use:**
|
||||
- Dedicated database server
|
||||
- Need fastest possible restore (DR scenarios)
|
||||
- Server has 16GB+ RAM, 4+ cores
|
||||
- Large databases (100GB+)
|
||||
- You want dbbackup to match pg_restore speed
|
||||
|
||||
**Example:**
|
||||
```bash
|
||||
dbbackup restore cluster backup.tar.gz --profile=turbo --confirm
|
||||
```
|
||||
|
||||
**TUI Usage:**
|
||||
1. Go to Settings → Resource Profile
|
||||
2. Press Enter to cycle until you see "turbo"
|
||||
3. Save settings and run restore
|
||||
|
||||
## Profile Comparison
|
||||
|
||||
| Setting | Conservative | Balanced | Aggressive |
|
||||
|---------|-------------|----------|-----------|
|
||||
| Parallel DBs | 1 (sequential) | Auto (2-4) | Auto (all CPUs) |
|
||||
| Jobs (decompression) | 1 | Auto (2-4) | Auto (all CPUs) |
|
||||
| Memory Usage | Minimal | Moderate | Maximum |
|
||||
| Speed | Slowest | Medium | Fastest |
|
||||
| Stability | Most stable | Stable | Requires resources |
|
||||
| Setting | Conservative | Balanced | Performance | Turbo 🚀 |
|
||||
|---------|-------------|----------|-------------|----------|
|
||||
| Parallel DBs | 1 | 2 | 4 | 2 |
|
||||
| pg_restore Jobs | 1 | 2 | 4 | 8 |
|
||||
| Buffered I/O | No | No | No | Yes (32KB) |
|
||||
| Memory Usage | Minimal | Moderate | High | Moderate |
|
||||
| Speed | Slowest | Medium | Fast | **Fastest** |
|
||||
| Stability | Most stable | Stable | Good | Good |
|
||||
| Best For | Small VMs | General use | Powerful servers | DR/Large DBs |
|
||||
|
||||
## Overriding Profile Settings
|
||||
|
||||
|
||||
@ -17,12 +17,16 @@ type Config struct {
|
||||
BuildTime string
|
||||
GitCommit string
|
||||
|
||||
// Config file path (--config flag)
|
||||
ConfigPath string
|
||||
|
||||
// Database connection
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Database string
|
||||
Password string
|
||||
Socket string // Unix socket path for MySQL/MariaDB
|
||||
DatabaseType string // "postgres" or "mysql"
|
||||
SSLMode string
|
||||
Insecure bool
|
||||
@ -37,8 +41,10 @@ type Config struct {
|
||||
CPUWorkloadType string // "cpu-intensive", "io-intensive", "balanced"
|
||||
|
||||
// Resource profile for backup/restore operations
|
||||
ResourceProfile string // "conservative", "balanced", "performance", "max-performance"
|
||||
ResourceProfile string // "conservative", "balanced", "performance", "max-performance", "turbo"
|
||||
LargeDBMode bool // Enable large database mode (reduces parallelism, increases max_locks)
|
||||
BufferedIO bool // Use 32KB buffered I/O for faster extraction (turbo profile)
|
||||
ParallelExtract bool // Enable parallel file extraction where possible (turbo profile)
|
||||
|
||||
// CPU detection
|
||||
CPUDetector *cpu.Detector
|
||||
@ -433,7 +439,7 @@ func (c *Config) ApplyResourceProfile(profileName string) error {
|
||||
return &ConfigError{
|
||||
Field: "resource_profile",
|
||||
Value: profileName,
|
||||
Message: "unknown profile. Valid profiles: conservative, balanced, performance, max-performance",
|
||||
Message: "unknown profile. Valid profiles: conservative, balanced, performance, max-performance, turbo",
|
||||
}
|
||||
}
|
||||
|
||||
@ -456,6 +462,10 @@ func (c *Config) ApplyResourceProfile(profileName string) error {
|
||||
c.Jobs = profile.Jobs
|
||||
c.DumpJobs = profile.DumpJobs
|
||||
|
||||
// Apply turbo mode optimizations
|
||||
c.BufferedIO = profile.BufferedIO
|
||||
c.ParallelExtract = profile.ParallelExtract
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@ -42,8 +42,11 @@ type LocalConfig struct {
|
||||
|
||||
// LoadLocalConfig loads configuration from .dbbackup.conf in current directory
|
||||
func LoadLocalConfig() (*LocalConfig, error) {
|
||||
configPath := filepath.Join(".", ConfigFileName)
|
||||
return LoadLocalConfigFromPath(filepath.Join(".", ConfigFileName))
|
||||
}
|
||||
|
||||
// LoadLocalConfigFromPath loads configuration from a specific path
|
||||
func LoadLocalConfigFromPath(configPath string) (*LocalConfig, error) {
|
||||
data, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
|
||||
@ -35,6 +35,8 @@ type ResourceProfile struct {
|
||||
RecommendedForLarge bool `json:"recommended_for_large"` // Suitable for large DBs?
|
||||
MinMemoryGB int `json:"min_memory_gb"` // Minimum memory for this profile
|
||||
MinCores int `json:"min_cores"` // Minimum cores for this profile
|
||||
BufferedIO bool `json:"buffered_io"` // Use 32KB buffered I/O for extraction
|
||||
ParallelExtract bool `json:"parallel_extract"` // Enable parallel file extraction
|
||||
}
|
||||
|
||||
// Predefined resource profiles
|
||||
@ -95,12 +97,31 @@ var (
|
||||
MinCores: 16,
|
||||
}
|
||||
|
||||
// ProfileTurbo - TURBO MODE: Optimized for fastest possible restore
|
||||
// Based on real-world testing: matches pg_restore -j8 performance
|
||||
// Uses buffered I/O, parallel extraction, and aggressive pg_restore parallelism
|
||||
ProfileTurbo = ResourceProfile{
|
||||
Name: "turbo",
|
||||
Description: "TURBO: Fastest restore mode. Matches native pg_restore -j8 speed. Use on dedicated DB servers.",
|
||||
ClusterParallelism: 2, // Restore 2 DBs concurrently (I/O balanced)
|
||||
Jobs: 8, // pg_restore -j8 (matches your pg_dump test)
|
||||
DumpJobs: 8, // Fast dumps too
|
||||
MaintenanceWorkMem: "2GB",
|
||||
MaxLocksPerTxn: 4096, // High for large schemas
|
||||
RecommendedForLarge: true, // Optimized for large DBs
|
||||
MinMemoryGB: 16, // Works on 16GB+ servers
|
||||
MinCores: 4, // Works on 4+ cores
|
||||
BufferedIO: true, // Enable 32KB buffered writes
|
||||
ParallelExtract: true, // Parallel tar extraction where possible
|
||||
}
|
||||
|
||||
// AllProfiles contains all available profiles (VM resource-based)
|
||||
AllProfiles = []ResourceProfile{
|
||||
ProfileConservative,
|
||||
ProfileBalanced,
|
||||
ProfilePerformance,
|
||||
ProfileMaxPerformance,
|
||||
ProfileTurbo,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@ -278,8 +278,12 @@ func (m *MySQL) GetTableRowCount(ctx context.Context, database, table string) (i
|
||||
func (m *MySQL) BuildBackupCommand(database, outputFile string, options BackupOptions) []string {
|
||||
cmd := []string{"mysqldump"}
|
||||
|
||||
// Connection parameters - handle localhost vs remote differently
|
||||
if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// Connection parameters - socket takes priority, then localhost vs remote
|
||||
if m.cfg.Socket != "" {
|
||||
// Explicit socket path provided
|
||||
cmd = append(cmd, "-S", m.cfg.Socket)
|
||||
cmd = append(cmd, "-u", m.cfg.User)
|
||||
} else if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// For localhost, use socket connection (don't specify host/port)
|
||||
cmd = append(cmd, "-u", m.cfg.User)
|
||||
} else {
|
||||
@ -338,8 +342,12 @@ func (m *MySQL) BuildBackupCommand(database, outputFile string, options BackupOp
|
||||
func (m *MySQL) BuildRestoreCommand(database, inputFile string, options RestoreOptions) []string {
|
||||
cmd := []string{"mysql"}
|
||||
|
||||
// Connection parameters - handle localhost vs remote differently
|
||||
if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// Connection parameters - socket takes priority, then localhost vs remote
|
||||
if m.cfg.Socket != "" {
|
||||
// Explicit socket path provided
|
||||
cmd = append(cmd, "-S", m.cfg.Socket)
|
||||
cmd = append(cmd, "-u", m.cfg.User)
|
||||
} else if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// For localhost, use socket connection (don't specify host/port)
|
||||
cmd = append(cmd, "-u", m.cfg.User)
|
||||
} else {
|
||||
@ -417,8 +425,11 @@ func (m *MySQL) buildDSN() string {
|
||||
|
||||
dsn += "@"
|
||||
|
||||
// Handle localhost with Unix socket vs TCP/IP
|
||||
if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// Explicit socket takes priority
|
||||
if m.cfg.Socket != "" {
|
||||
dsn += "unix(" + m.cfg.Socket + ")"
|
||||
} else if m.cfg.Host == "" || m.cfg.Host == "localhost" {
|
||||
// Handle localhost with Unix socket vs TCP/IP
|
||||
// Try common socket paths for localhost connections
|
||||
socketPaths := []string{
|
||||
"/run/mysqld/mysqld.sock",
|
||||
|
||||
@ -345,8 +345,10 @@ func (e *MySQLDumpEngine) Restore(ctx context.Context, opts *RestoreOptions) err
|
||||
// Build mysql command
|
||||
args := []string{}
|
||||
|
||||
// Connection parameters
|
||||
if e.config.Host != "" && e.config.Host != "localhost" {
|
||||
// Connection parameters - socket takes priority over host
|
||||
if e.config.Socket != "" {
|
||||
args = append(args, "-S", e.config.Socket)
|
||||
} else if e.config.Host != "" && e.config.Host != "localhost" {
|
||||
args = append(args, "-h", e.config.Host)
|
||||
args = append(args, "-P", strconv.Itoa(e.config.Port))
|
||||
}
|
||||
@ -494,8 +496,10 @@ func (e *MySQLDumpEngine) BackupToWriter(ctx context.Context, w io.Writer, opts
|
||||
func (e *MySQLDumpEngine) buildArgs(database string) []string {
|
||||
args := []string{}
|
||||
|
||||
// Connection parameters
|
||||
if e.config.Host != "" && e.config.Host != "localhost" {
|
||||
// Connection parameters - socket takes priority over host
|
||||
if e.config.Socket != "" {
|
||||
args = append(args, "-S", e.config.Socket)
|
||||
} else if e.config.Host != "" && e.config.Host != "localhost" {
|
||||
args = append(args, "-h", e.config.Host)
|
||||
args = append(args, "-P", strconv.Itoa(e.config.Port))
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package restore
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
@ -952,6 +953,29 @@ func (e *Engine) RestoreSingleFromCluster(ctx context.Context, clusterArchivePat
|
||||
func (e *Engine) RestoreCluster(ctx context.Context, archivePath string, preExtractedPath ...string) error {
|
||||
operation := e.log.StartOperation("Cluster Restore")
|
||||
|
||||
// 🚀 LOG ACTUAL PERFORMANCE SETTINGS - helps debug slow restores
|
||||
profile := e.cfg.GetCurrentProfile()
|
||||
if profile != nil {
|
||||
e.log.Info("🚀 RESTORE PERFORMANCE SETTINGS",
|
||||
"profile", profile.Name,
|
||||
"cluster_parallelism", profile.ClusterParallelism,
|
||||
"pg_restore_jobs", profile.Jobs,
|
||||
"large_db_mode", e.cfg.LargeDBMode,
|
||||
"buffered_io", profile.BufferedIO)
|
||||
} else {
|
||||
e.log.Info("🚀 RESTORE PERFORMANCE SETTINGS (raw config)",
|
||||
"profile", e.cfg.ResourceProfile,
|
||||
"cluster_parallelism", e.cfg.ClusterParallelism,
|
||||
"pg_restore_jobs", e.cfg.Jobs,
|
||||
"large_db_mode", e.cfg.LargeDBMode)
|
||||
}
|
||||
|
||||
// Also show in progress bar for TUI visibility
|
||||
if !e.silentMode {
|
||||
fmt.Printf("\n⚡ Performance: profile=%s, parallel_dbs=%d, pg_restore_jobs=%d\n\n",
|
||||
e.cfg.ResourceProfile, e.cfg.ClusterParallelism, e.cfg.Jobs)
|
||||
}
|
||||
|
||||
// Validate and sanitize archive path
|
||||
validArchivePath, pathErr := security.ValidateArchivePath(archivePath)
|
||||
if pathErr != nil {
|
||||
@ -1798,10 +1822,22 @@ func (e *Engine) extractArchiveWithProgress(ctx context.Context, archivePath, de
|
||||
return fmt.Errorf("failed to create file %s: %w", targetPath, err)
|
||||
}
|
||||
|
||||
// Copy file contents
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
outFile.Close()
|
||||
return fmt.Errorf("failed to write file %s: %w", targetPath, err)
|
||||
// Copy file contents - use buffered I/O for turbo mode (32KB buffer)
|
||||
if e.cfg.BufferedIO {
|
||||
bufferedWriter := bufio.NewWriterSize(outFile, 32*1024) // 32KB buffer for faster writes
|
||||
if _, err := io.Copy(bufferedWriter, tarReader); err != nil {
|
||||
outFile.Close()
|
||||
return fmt.Errorf("failed to write file %s: %w", targetPath, err)
|
||||
}
|
||||
if err := bufferedWriter.Flush(); err != nil {
|
||||
outFile.Close()
|
||||
return fmt.Errorf("failed to flush buffer for %s: %w", targetPath, err)
|
||||
}
|
||||
} else {
|
||||
if _, err := io.Copy(outFile, tarReader); err != nil {
|
||||
outFile.Close()
|
||||
return fmt.Errorf("failed to write file %s: %w", targetPath, err)
|
||||
}
|
||||
}
|
||||
outFile.Close()
|
||||
case tar.TypeSymlink:
|
||||
|
||||
@ -112,7 +112,8 @@ func NewSettingsModel(cfg *config.Config, log logger.Logger, parent tea.Model) S
|
||||
return c.ResourceProfile
|
||||
},
|
||||
Update: func(c *config.Config, v string) error {
|
||||
profiles := []string{"conservative", "balanced", "performance", "max-performance"}
|
||||
// UPDATED: Added 'turbo' profile for maximum restore speed
|
||||
profiles := []string{"conservative", "balanced", "performance", "max-performance", "turbo"}
|
||||
currentIdx := 0
|
||||
for i, p := range profiles {
|
||||
if c.ResourceProfile == p {
|
||||
@ -124,7 +125,7 @@ func NewSettingsModel(cfg *config.Config, log logger.Logger, parent tea.Model) S
|
||||
return c.ApplyResourceProfile(profiles[nextIdx])
|
||||
},
|
||||
Type: "selector",
|
||||
Description: "Resource profile for VM capacity. Toggle 'l' for Large DB Mode on any profile.",
|
||||
Description: "Resource profile. 'turbo' = fastest (matches pg_restore -j8). Press Enter to cycle.",
|
||||
},
|
||||
{
|
||||
Key: "large_db_mode",
|
||||
|
||||
2
main.go
2
main.go
@ -16,7 +16,7 @@ import (
|
||||
|
||||
// Build information (set by ldflags)
|
||||
var (
|
||||
version = "4.1.1"
|
||||
version = "4.1.4"
|
||||
buildTime = "unknown"
|
||||
gitCommit = "unknown"
|
||||
)
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user