package strategy import ( "backea/internal/backup/config" "backea/internal/backup/strategy/kopia" "fmt" "os" "path/filepath" "strings" "time" ) // Factory handles backup strategy creation type Factory struct { Config *config.Configuration ConfigPath string } // NewFactory initializes and returns a Factory func NewFactory(configPath string) (*Factory, error) { cfg, err := config.LoadConfig(configPath) if err != nil { return nil, fmt.Errorf("failed to load config: %w", err) } return &Factory{Config: cfg, ConfigPath: configPath}, nil } // CreateBackupStrategyForService returns a backup strategy based on config func (f *Factory) CreateBackupStrategyForService(groupName, serviceIndex string) (Strategy, error) { serviceConfig, err := f.getServiceConfig(groupName, serviceIndex) if err != nil { return nil, err } sourcePath, err := f.getServicePath(groupName) if err != nil { return nil, err } switch serviceConfig.BackupStrategy.Type { case "kopia": return f.createKopiaStrategy(serviceConfig, sourcePath) default: return nil, fmt.Errorf("unknown strategy type: %s", serviceConfig.BackupStrategy.Type) } } // createKopiaStrategy initializes a Kopia strategy func (f *Factory) createKopiaStrategy(serviceConfig *config.BackupConfig, sourcePath string) (Strategy, error) { provider, err := f.createKopiaProvider(serviceConfig.BackupStrategy) if err != nil { return nil, fmt.Errorf("failed to create kopia provider: %w", err) } retention := kopia.Retention(serviceConfig.BackupStrategy.Retention) configPath := generateTempConfigPath(provider.GetProviderType()) return kopia.NewStrategy(retention, provider, configPath, sourcePath), nil } // createKopiaProvider returns a Kopia storage provider func (f *Factory) createKopiaProvider(strategyConfig config.StrategyConfig) (kopia.Provider, error) { switch strategyConfig.Provider { case "local": return kopia.NewLocalProvider(getDefaultPath(strategyConfig.Destination.Path)), nil case "b2", "backblaze": return kopia.NewB2Provider(), nil // case "sftp": // return createSFTPProvider(strategyConfig.Destination) default: return nil, fmt.Errorf("unknown kopia provider type: %s", strategyConfig.Provider) } } // createSFTPProvider handles SFTP provider creation func createSFTPProvider(dest DestinationConfig) (kopia.Provider, error) { host, username := parseSFTPHost(dest.Host) if host == "" { return nil, fmt.Errorf("SFTP host not specified") } return kopia.NewSFTPProvider(host, getDefaultPath(dest.Path), username, getSSHKeyPath(dest.SSHKey)), nil } // parseSFTPHost extracts the username and host from "user@host" func parseSFTPHost(host string) (string, string) { if atIndex := strings.Index(host, "@"); atIndex > 0 { return host[atIndex+1:], host[:atIndex] } return host, getEnvUser() } // getDefaultPath returns the given path or a default func getDefaultPath(path string) string { if path != "" { return path } home, _ := os.UserHomeDir() return filepath.Join(home, ".backea", "repos") } // getEnvUser returns the system username func getEnvUser() string { if user := os.Getenv("USER"); user != "" { return user } return "root" } // getSSHKeyPath returns the SSH key path func getSSHKeyPath(key string) string { if key != "" { return key } home, _ := os.UserHomeDir() return filepath.Join(home, ".ssh", "id_rsa") } // getServicePath returns a service's path func (f *Factory) getServicePath(groupName string) (string, error) { if serviceGroup, exists := f.Config.Services[groupName]; exists { return serviceGroup.Source.Path, nil } return "", fmt.Errorf("service group not found: %s", groupName) } // getServiceConfig returns a service's config func (f *Factory) getServiceConfig(groupName, serviceIndex string) (*config.BackupConfig, error) { if serviceGroup, exists := f.Config.Services[groupName]; exists { if backupConfig, exists := serviceGroup.BackupConfigs[serviceIndex]; exists { return &backupConfig, nil } } return nil, fmt.Errorf("backup config not found: %s.%s", groupName, serviceIndex) } // ReloadConfig reloads configuration func (f *Factory) ReloadConfig() error { cfg, err := config.LoadConfig(f.ConfigPath) if err != nil { return fmt.Errorf("could not reload config: %w", err) } f.Config = cfg return nil } // generateTempConfigPath creates a temp Kopia config path func generateTempConfigPath(providerType string) string { tmpConfigDir := filepath.Join(os.TempDir(), "kopia_configs") _ = os.MkdirAll(tmpConfigDir, 0755) // Ensure directory exists return filepath.Join(tmpConfigDir, fmt.Sprintf("kopia_%s_%d.config", providerType, time.Now().UnixNano())) }