From fddb2aecb2c6263be2b533d999462e2b26b25d7f Mon Sep 17 00:00:00 2001 From: garethgeorge Date: Tue, 23 Jan 2024 22:51:42 -0800 Subject: [PATCH] fix: sftp support using public key authentication --- internal/api/server.go | 3 ++- internal/orchestrator/repo.go | 2 +- pkg/restic/restic.go | 16 ++++++++++++---- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/internal/api/server.go b/internal/api/server.go index 6ec90703..a810f89e 100644 --- a/internal/api/server.go +++ b/internal/api/server.go @@ -100,8 +100,9 @@ func (s *Server) AddRepo(ctx context.Context, req *connect.Request[v1.Repo]) (*c } r := restic.NewRepo(bin, req.Msg) + // use background context such that the init op can try to complete even if the connection is closed. - if err := r.Init(context.Background()); err != nil { + if err := r.Init(context.Background(), restic.WithPropagatedEnvVars(restic.EnvToPropagate...)); err != nil { return nil, fmt.Errorf("failed to init repo: %w", err) } diff --git a/internal/orchestrator/repo.go b/internal/orchestrator/repo.go index 9f65b2b4..75cd928f 100644 --- a/internal/orchestrator/repo.go +++ b/internal/orchestrator/repo.go @@ -58,7 +58,7 @@ func (r *RepoOrchestrator) Backup(ctx context.Context, plan *v1.Plan, progressCa defer r.mu.Unlock() if !r.initialized { - if err := r.repo.Init(ctx); err != nil { + if err := r.repo.Init(ctx, restic.WithPropagatedEnvVars(restic.EnvToPropagate...)); err != nil { return nil, fmt.Errorf("failed to initialize repo: %w", err) } r.initialized = true diff --git a/pkg/restic/restic.go b/pkg/restic/restic.go index 6c02b771..f1b17a20 100644 --- a/pkg/restic/restic.go +++ b/pkg/restic/restic.go @@ -9,6 +9,7 @@ import ( "io" "os" "os/exec" + "slices" "strings" "sync" @@ -20,7 +21,6 @@ var ErrPartialBackup = errors.New("incomplete backup") var ErrBackupFailed = errors.New("backup failed") type Repo struct { - mu sync.Mutex cmd string repo *v1.Repo initialized bool @@ -36,6 +36,10 @@ func NewRepo(resticBin string, repo *v1.Repo, opts ...GenericOption) *Repo { o(opt) } + if slices.Index(opt.extraArgs, "sftp.args") == -1 { + opt.extraArgs = append(opt.extraArgs, "-o", "sftp.args=-oBatchMode=yes") + } + return &Repo{ cmd: resticBin, // TODO: configurable binary path repo: repo, @@ -56,16 +60,20 @@ func (r *Repo) buildEnv() []string { } // init initializes the repo, the command will be cancelled with the context. -func (r *Repo) init(ctx context.Context) error { +func (r *Repo) init(ctx context.Context, opts ...GenericOption) error { if r.initialized { return nil } + opt := resolveOpts(opts) + var args = []string{"init", "--json"} args = append(args, r.extraArgs...) + args = append(args, opt.extraArgs...) cmd := exec.CommandContext(ctx, r.cmd, args...) cmd.Env = append(cmd.Env, r.buildEnv()...) + cmd.Env = append(cmd.Env, opt.extraEnv...) if output, err := cmd.CombinedOutput(); err != nil { if strings.Contains(string(output), "config file already exists") || strings.Contains(string(output), "already initialized") { @@ -78,8 +86,8 @@ func (r *Repo) init(ctx context.Context) error { return nil } -func (r *Repo) Init(ctx context.Context) error { - if err := r.init(ctx); err != nil && !errors.Is(err, errAlreadyInitialized) { +func (r *Repo) Init(ctx context.Context, opts ...GenericOption) error { + if err := r.init(ctx, opts...); err != nil && !errors.Is(err, errAlreadyInitialized) { return fmt.Errorf("init failed: %w", err) } return nil