package backup import ( "fmt" "log" "os" "os/exec" "path/filepath" "strings" "time" ) // BackupFactory creates backup strategies and managers type BackupFactory struct { Config *Configuration ConfigPath string } // NewBackupFactory creates a new backup factory func NewBackupFactory(configPath string) (*BackupFactory, error) { // Load configuration config, err := LoadConfig(configPath) if err != nil { return nil, fmt.Errorf("could not load config: %w", err) } return &BackupFactory{ Config: config, ConfigPath: configPath, }, nil } // CreateKopiaStrategy creates a new Kopia backup strategy with specific retention settings and provider func (f *BackupFactory) CreateKopiaStrategy(retention Retention, provider KopiaProvider) *KopiaStrategy { // Create temp directory for Kopia configs if it doesn't exist tmpConfigDir := filepath.Join(os.TempDir(), "kopia_configs") if err := os.MkdirAll(tmpConfigDir, 0755); err != nil { log.Printf("Warning: failed to create temp config directory: %v", err) // Fall back to default config path tmpConfigDir = os.TempDir() } // Generate a unique config file path for this instance configPath := filepath.Join(tmpConfigDir, fmt.Sprintf("kopia_%s_%d.config", provider.GetProviderType(), time.Now().UnixNano())) // Need to update this line to pass configPath return NewKopiaStrategy(retention, provider, configPath) } // CreateKopiaProvider creates the appropriate Kopia provider based on config func (f *BackupFactory) CreateKopiaProvider(strategyConfig StrategyConfig) (KopiaProvider, error) { switch strategyConfig.Provider { case "b2_backblaze": return NewKopiaB2Provider(), nil case "local": // Extract options for local path if specified basePath := "" if strategyConfig.Options != "" { basePath = strategyConfig.Options } else { // Default to ~/.backea/repos if not specified basePath = filepath.Join(os.Getenv("HOME"), ".backea", "repos") } return NewKopiaLocalProvider(basePath), nil case "sftp": // Parse options for SFTP - expected format: "user@host:/path/to/backups" host := strategyConfig.Destination.Host path := strategyConfig.Destination.Path sshKey := strategyConfig.Destination.SSHKey if host == "" { return nil, fmt.Errorf("SFTP provider requires host in destination config") } if path == "" { return nil, fmt.Errorf("SFTP provider requires path in destination config") } // Default SSH key if not specified if sshKey == "" { sshKey = filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa") } // Extract username from host if in format user@host username := "root" // default if hostParts := strings.Split(host, "@"); len(hostParts) > 1 { username = hostParts[0] host = hostParts[1] } return NewKopiaSFTPProvider(host, path, username, sshKey), nil default: return nil, fmt.Errorf("unsupported Kopia provider: %s", strategyConfig.Provider) } } // CreateBackupStrategyForService creates a backup strategy for the specified service within a group func (f *BackupFactory) CreateBackupStrategyForService(groupName string, serviceIndex string) (Strategy, error) { // Find service group serviceGroup, exists := f.Config.Services[groupName] if !exists { return nil, fmt.Errorf("service group not found: %s", groupName) } // Find specific backup config within the group backupConfig, exists := serviceGroup.BackupConfigs[serviceIndex] if !exists { return nil, fmt.Errorf("backup config not found: %s.%s", groupName, serviceIndex) } // Create appropriate strategy based on type strategyConfig := backupConfig.BackupStrategy // Extract retention settings once retention := Retention{ KeepLatest: strategyConfig.Retention.KeepLatest, KeepHourly: strategyConfig.Retention.KeepHourly, KeepDaily: strategyConfig.Retention.KeepDaily, KeepWeekly: strategyConfig.Retention.KeepWeekly, KeepMonthly: strategyConfig.Retention.KeepMonthly, KeepYearly: strategyConfig.Retention.KeepYearly, } switch strategyConfig.Type { case "kopia": // Create appropriate provider based on configuration provider, err := f.CreateKopiaProvider(strategyConfig) if err != nil { return nil, fmt.Errorf("failed to create kopia provider: %w", err) } return f.CreateKopiaStrategy(retention, provider), nil case "rsync": // Uncomment when rsync implementation is ready // return NewRsyncStrategy( // strategyConfig.Options, // Destination{ // Host: strategyConfig.Destination.Host, // Path: strategyConfig.Destination.Path, // SSHKey: strategyConfig.Destination.SSHKey, // }, // ), nil fallthrough default: // Default to local kopia if type is unknown or rsync (temporarily) provider, _ := f.CreateKopiaProvider(StrategyConfig{Provider: "local"}) return f.CreateKopiaStrategy(retention, provider), nil } } // ExecuteGroupHooks runs the hooks for a service group func (f *BackupFactory) ExecuteGroupHooks(groupName string, isBeforeHook bool) error { serviceGroup, exists := f.Config.Services[groupName] if !exists { return fmt.Errorf("service group not found: %s", groupName) } var hook string if isBeforeHook { hook = serviceGroup.Hooks.BeforeHook } else { hook = serviceGroup.Hooks.AfterHook } if hook == "" { return nil } // Execute the hook cmd := exec.Command("sh", "-c", hook) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("hook execution failed: %w, output: %s", err, string(output)) } log.Printf("Hook executed successfully: %s, output: %s", hook, string(output)) return nil } // ReloadConfig refreshes the configuration from the config file func (f *BackupFactory) ReloadConfig() error { config, err := LoadConfig(f.ConfigPath) if err != nil { return fmt.Errorf("could not reload config: %w", err) } f.Config = config return nil }