diff --git a/config.yml b/config.yml index 19313b6..59bbc6b 100644 --- a/config.yml +++ b/config.yml @@ -7,6 +7,16 @@ defaults: keep_monthly: 12 keep_yearly: 3 +common: + sftp_raid_strategy: &sftp_raid_strategy + type: "kopia" + provider: "sftp" + destination: + username: "gevo" + host: "maric.ro" + path: "/raid/backups/" + ssh_key: "/home/gevo/.ssh/kopia_key" + services: backealocal: source: @@ -28,7 +38,27 @@ services: provider: "local" destination: path: "/home/gevo/backup/images2" - + 3: + backup_strategy: + type: "kopia" + provider: "sftp" + destination: + username: "gevo" + host: "maric.ro" + path: "/home/gevo/backups/backealocal" + ssh_key: "/home/gevo/.ssh/kopia_key" + 4: + backup_strategy: *sftp_raid_strategy + backeaworking: + source: + host: "local" + path: "/home/gevo/Images" + hooks: + before_hook: "ls" + after_hook: "" + backup_config: + 1: + backup_strategy: *sftp_raid_strategy imageslocal: source: host: "local" @@ -42,4 +72,4 @@ services: type: "kopia" provider: "local" destination: - host: "local" + host: "local" \ No newline at end of file diff --git a/internal/backup/models/config.go b/internal/backup/models/config.go index 49a476c..d4fe65b 100644 --- a/internal/backup/models/config.go +++ b/internal/backup/models/config.go @@ -65,9 +65,10 @@ type RetentionConfig struct { // DestConfig represents destination configuration for remote backups type DestConfig struct { - Host string `mapstructure:"host,omitempty"` - Path string `mapstructure:"path"` - SSHKey string `mapstructure:"ssh_key,omitempty"` + Host string `mapstructure:"host,omitempty"` + Path string `mapstructure:"path"` + SSHKey string `mapstructure:"ssh_key,omitempty"` + Username string `mapstructure:"username"` } // LoadConfig loads the application configuration from a file diff --git a/internal/backup/strategy/factory.go b/internal/backup/strategy/factory.go index c897bed..081f2ed 100644 --- a/internal/backup/strategy/factory.go +++ b/internal/backup/strategy/factory.go @@ -6,7 +6,6 @@ import ( "fmt" "os" "path/filepath" - "strings" "time" ) @@ -64,30 +63,17 @@ func (f *Factory) createKopiaProvider(strategyConfig models.StrategyConfig) (kop return kopia.NewLocalProvider(getDefaultPath(strategyConfig.Destination.Path)), nil case "b2", "backblaze": return kopia.NewB2Provider(), nil - // case "sftp": - // return createSFTPProvider(strategyConfig.Destination) + case "sftp": + host := strategyConfig.Destination.Host + basepath := strategyConfig.Destination.Path + username := strategyConfig.Destination.Username + keyFile := strategyConfig.Destination.SSHKey + return kopia.NewSFTPProvider(host, basepath, username, keyFile), nil 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 != "" { @@ -97,23 +83,6 @@ func getDefaultPath(path string) string { 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 { diff --git a/internal/backup/strategy/kopia/sftp.go b/internal/backup/strategy/kopia/sftp.go index 26be234..5e8dfc9 100644 --- a/internal/backup/strategy/kopia/sftp.go +++ b/internal/backup/strategy/kopia/sftp.go @@ -27,9 +27,15 @@ func NewSFTPProvider(host, basePath, username, keyFile string) *SFTPProvider { } } -// Connect connects to an SFTP repository +// kopia repository connect sftp \ +// --path="/home/gevo/kopiatemp" \ +// --host="maric.ro" \ +// --username="gevo" \ +// --keyfile="$(realpath ~/.ssh/kopia_key)" \ +// --known-hosts="$(realpath ~/.ssh/known_hosts)" func (p *SFTPProvider) Connect(ctx context.Context, serviceName string, password string, configPath string) error { - repoPath := fmt.Sprintf("%s@%s:%s/%s", p.Username, p.Host, p.BasePath, serviceName) + // Just use the path on the remote server, not the full URI + repoPath := fmt.Sprintf("%s/%s", p.BasePath, serviceName) // Try to connect to existing repository with config file connectCmd := exec.CommandContext( @@ -38,10 +44,13 @@ func (p *SFTPProvider) Connect(ctx context.Context, serviceName string, password "--config-file", configPath, "repository", "connect", "sftp", "--path", repoPath, + "--host", p.Host, + "--username", p.Username, "--keyfile", p.KeyFile, "--known-hosts", filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"), "--password", password, ) + err := connectCmd.Run() if err != nil { // Connection failed, create new repository @@ -52,10 +61,13 @@ func (p *SFTPProvider) Connect(ctx context.Context, serviceName string, password "--config-file", configPath, "repository", "create", "sftp", "--path", repoPath, + "--host", p.Host, + "--username", p.Username, "--keyfile", p.KeyFile, "--known-hosts", filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"), "--password", password, ) + createOutput, err := createCmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to create repository: %w\nOutput: %s", err, createOutput)