diff --git a/internal/executor/executor.go b/internal/executor/executor.go new file mode 100644 index 0000000..b90018c --- /dev/null +++ b/internal/executor/executor.go @@ -0,0 +1,11 @@ +package executor + +import ( + "context" + "os/exec" +) + +type Executor interface { + CombinedOutput(context.Context, string, ...string) ([]byte, error) + CommandContext(context.Context, string, ...string) *exec.Cmd +} diff --git a/internal/executor/local.go b/internal/executor/local.go new file mode 100644 index 0000000..4bf440d --- /dev/null +++ b/internal/executor/local.go @@ -0,0 +1,16 @@ +package executor + +import ( + "context" + "os/exec" +) + +type LocalExecutor struct{} + +func (l LocalExecutor) CombinedOutput(ctx context.Context, name string, arg ...string) ([]byte, error) { + return l.CommandContext(ctx, name, arg...).CombinedOutput() +} + +func (l LocalExecutor) CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd { + return exec.CommandContext(ctx, name, arg...) +} diff --git a/internal/executor/ssh.go b/internal/executor/ssh.go new file mode 100644 index 0000000..adfb141 --- /dev/null +++ b/internal/executor/ssh.go @@ -0,0 +1,21 @@ +package executor + +import ( + "context" + "os/exec" +) + +type SSHExecutor struct { + SSHTarget string +} + +func (s SSHExecutor) CombinedOutput(ctx context.Context, name string, arg ...string) ([]byte, error) { + return s.CommandContext(ctx, name, arg...).CombinedOutput() +} + +func (s SSHExecutor) CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd { + newArg := append([]string{s.SSHTarget, name}, arg...) + cmd := exec.CommandContext(ctx, "ssh", newArg...) + + return cmd +} diff --git a/internal/jobs/jobs.go b/internal/jobs/jobs.go index 674dd17..7693ce1 100644 --- a/internal/jobs/jobs.go +++ b/internal/jobs/jobs.go @@ -12,6 +12,7 @@ import ( "strings" "time" + "git.gentoo.party/sam/thanks/internal/executor" "git.gentoo.party/sam/thanks/internal/zfs" ) @@ -35,15 +36,13 @@ type BackupJob struct { Recursive bool `yaml:"recursive"` // create recursive snapshots Prefix string `yaml:"prefix"` // name each snapshot with this prefix Hooks BackupHooks `yaml:"hooks"` // external programs (libnotify, sendmail, etc) to call + + localExecutor executor.Executor + remoteExecutor executor.Executor } -// func (j *BackupJob) getBaseSnap() { -// params := "zfs get -j -d 1 -t snapshot guid,creation %s" -// srcCmd := fmt.Sprintf(params, j.Source) -// dstCmd := fmt.Sprintf(params, j.Target) -// } func (j *BackupJob) listSnapshots(ctx context.Context) ([]zfs.Snapshot, error) { - allSnaps, err := zfs.Snapshots(ctx, j.Source) + allSnaps, err := zfs.Snapshots(ctx, j.localExecutor, j.Source) if err != nil { return nil, err } @@ -60,7 +59,6 @@ func (j *BackupJob) listSnapshots(ctx context.Context) ([]zfs.Snapshot, error) { } func (j *BackupJob) snapName() string { - // ts := time.Now().Format(time.RFC3339) ts := time.Now().UnixMicro() if j.Prefix == "" { return fmt.Sprintf("%d", ts) @@ -72,16 +70,12 @@ func (j *BackupJob) snapName() string { func (j *BackupJob) Snapshot(ctx context.Context) (string, error) { snapName := j.snapName() snap := fmt.Sprintf("%s@%s", j.Source, snapName) - - out, err := zfs.Cmd( + err := zfs.CreateSnapshot( ctx, - "zfs snapshot %s", - snap, + j.localExecutor, + j.Source, + snapName, ) - if err != nil { - log.Printf("zfs-snapshot: error: %s", out) - return snapName, nil - } return snap, err } @@ -137,9 +131,9 @@ func (j *BackupJob) Retain(ctx context.Context) error { return nil } -// func (j *BackupJob) findCommonAnscestor(ctx context.Context) { -// localSnapshots, err := j.listSnapshots(ctx) -// } +func (j *BackupJob) findCommonAnscestor(ctx context.Context) { + localSnapshots, err := j.listSnapshots(ctx) +} type HookErr struct { message string diff --git a/internal/zfs/zfs.go b/internal/zfs/zfs.go index cc8c076..9d2f840 100644 --- a/internal/zfs/zfs.go +++ b/internal/zfs/zfs.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "git.gentoo.party/sam/thanks/internal/executor" "git.gentoo.party/sam/thanks/internal/runner" ) @@ -37,6 +38,20 @@ func Cmd(ctx context.Context, arg string, a ...any) ([]byte, error) { return out, err } +func CreateSnapshot(ctx context.Context, e executor.Executor, dataset, name string) error { + _, err := e.CombinedOutput( + ctx, + "zfs", + "snapshot", + fmt.Sprintf("%s@%s", dataset, name), + ) + if err != nil { + return fmt.Errorf("zfs-snapshot error: %w", err) + } + + return nil +} + func ParseSnapshots(reader io.Reader) ([]Snapshot, error) { scanner := bufio.NewScanner(reader) @@ -78,33 +93,8 @@ func ParseSnapshots(reader io.Reader) ([]Snapshot, error) { return snaps, nil } -type Host struct { - SSH string - ZFSPath string -} - -func (h *Host) CommandContext(ctx context.Context, name string, arg ...string) *exec.Cmd { - var args []string - if h.SSH != "" { - name = "ssh" - args = append([]string{"ssh", h.SSH}, arg...) - } else { - args = arg - } - - cmd := exec.CommandContext( - ctx, - name, - args..., - ) - - return cmd -} - -// h.Comman - -func Snapshots(ctx context.Context, target string) ([]Snapshot, error) { - cmd := exec.CommandContext( +func Snapshots(ctx context.Context, e executor.Executor, target string) ([]Snapshot, error) { + cmd := e.CommandContext( ctx, "zfs", "list", @@ -182,7 +172,6 @@ func IncrementalSend(ctx context.Context, prevSnapName, newSnapName, sshTarget, ) } -func Destroy(ctx context.Context, target string) ([]byte, error) { - cmd := exec.CommandContext(ctx, "zfs", "destroy", target) - return cmd.CombinedOutput() +func Destroy(ctx context.Context, e executor.Executor, target string) ([]byte, error) { + return e.CombinedOutput(ctx, "zfs", target) } diff --git a/thanks.yaml.example b/thanks.yaml.example new file mode 100644 index 0000000..500380c --- /dev/null +++ b/thanks.yaml.example @@ -0,0 +1,14 @@ +jobs: + - source: "zroot/home/sam/thanks" + target: "zrust/backup/weller/thanks" + targetHost: "backup@woodford.gentoo.party" + keep: 30 + prefix: "thanks-" + recursive: false + hooks: + completed: + cmd: "backup-success.sh" + args: [] + error: + cmd: "backup-error.sh" + args: []