Sftp strategy work

This commit is contained in:
sirir 2025-03-21 20:10:29 +01:00
parent 1d06437fe9
commit 3d1a9f5438
4 changed files with 56 additions and 44 deletions

View File

@ -7,6 +7,16 @@ defaults:
keep_monthly: 12 keep_monthly: 12
keep_yearly: 3 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: services:
backealocal: backealocal:
source: source:
@ -28,7 +38,27 @@ services:
provider: "local" provider: "local"
destination: destination:
path: "/home/gevo/backup/images2" 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: imageslocal:
source: source:
host: "local" host: "local"
@ -42,4 +72,4 @@ services:
type: "kopia" type: "kopia"
provider: "local" provider: "local"
destination: destination:
host: "local" host: "local"

View File

@ -65,9 +65,10 @@ type RetentionConfig struct {
// DestConfig represents destination configuration for remote backups // DestConfig represents destination configuration for remote backups
type DestConfig struct { type DestConfig struct {
Host string `mapstructure:"host,omitempty"` Host string `mapstructure:"host,omitempty"`
Path string `mapstructure:"path"` Path string `mapstructure:"path"`
SSHKey string `mapstructure:"ssh_key,omitempty"` SSHKey string `mapstructure:"ssh_key,omitempty"`
Username string `mapstructure:"username"`
} }
// LoadConfig loads the application configuration from a file // LoadConfig loads the application configuration from a file

View File

@ -6,7 +6,6 @@ import (
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"time" "time"
) )
@ -64,30 +63,17 @@ func (f *Factory) createKopiaProvider(strategyConfig models.StrategyConfig) (kop
return kopia.NewLocalProvider(getDefaultPath(strategyConfig.Destination.Path)), nil return kopia.NewLocalProvider(getDefaultPath(strategyConfig.Destination.Path)), nil
case "b2", "backblaze": case "b2", "backblaze":
return kopia.NewB2Provider(), nil return kopia.NewB2Provider(), nil
// case "sftp": case "sftp":
// return createSFTPProvider(strategyConfig.Destination) 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: default:
return nil, fmt.Errorf("unknown kopia provider type: %s", strategyConfig.Provider) 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 // getDefaultPath returns the given path or a default
func getDefaultPath(path string) string { func getDefaultPath(path string) string {
if path != "" { if path != "" {
@ -97,23 +83,6 @@ func getDefaultPath(path string) string {
return filepath.Join(home, ".backea", "repos") 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 // getServicePath returns a service's path
func (f *Factory) getServicePath(groupName string) (string, error) { func (f *Factory) getServicePath(groupName string) (string, error) {
if serviceGroup, exists := f.Config.Services[groupName]; exists { if serviceGroup, exists := f.Config.Services[groupName]; exists {

View File

@ -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 { 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 // Try to connect to existing repository with config file
connectCmd := exec.CommandContext( connectCmd := exec.CommandContext(
@ -38,10 +44,13 @@ func (p *SFTPProvider) Connect(ctx context.Context, serviceName string, password
"--config-file", configPath, "--config-file", configPath,
"repository", "connect", "sftp", "repository", "connect", "sftp",
"--path", repoPath, "--path", repoPath,
"--host", p.Host,
"--username", p.Username,
"--keyfile", p.KeyFile, "--keyfile", p.KeyFile,
"--known-hosts", filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"), "--known-hosts", filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"),
"--password", password, "--password", password,
) )
err := connectCmd.Run() err := connectCmd.Run()
if err != nil { if err != nil {
// Connection failed, create new repository // Connection failed, create new repository
@ -52,10 +61,13 @@ func (p *SFTPProvider) Connect(ctx context.Context, serviceName string, password
"--config-file", configPath, "--config-file", configPath,
"repository", "create", "sftp", "repository", "create", "sftp",
"--path", repoPath, "--path", repoPath,
"--host", p.Host,
"--username", p.Username,
"--keyfile", p.KeyFile, "--keyfile", p.KeyFile,
"--known-hosts", filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"), "--known-hosts", filepath.Join(os.Getenv("HOME"), ".ssh", "known_hosts"),
"--password", password, "--password", password,
) )
createOutput, err := createCmd.CombinedOutput() createOutput, err := createCmd.CombinedOutput()
if err != nil { if err != nil {
return fmt.Errorf("failed to create repository: %w\nOutput: %s", err, createOutput) return fmt.Errorf("failed to create repository: %w\nOutput: %s", err, createOutput)