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_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"

View File

@ -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

View File

@ -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 {

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 {
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)