320 lines
7.2 KiB
Go
320 lines
7.2 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type CommandOptions interface {
|
|
ToArgs() []string
|
|
}
|
|
|
|
type NoOpts struct{}
|
|
|
|
func (NoOpts) ToArgs() []string {
|
|
return []string{}
|
|
}
|
|
|
|
type ResticGlobalOpts struct {
|
|
CaCertFile string `hcl:"CaCertFile,optional"`
|
|
CacheDir string `hcl:"CacheDir,optional"`
|
|
PasswordFile string `hcl:"PasswordFile,optional"`
|
|
TLSClientCertFile string `hcl:"TlsClientCertFile,optional"`
|
|
LimitDownload int `hcl:"LimitDownload,optional"`
|
|
LimitUpload int `hcl:"LimitUpload,optional"`
|
|
VerboseLevel int `hcl:"VerboseLevel,optional"`
|
|
CleanupCache bool `hcl:"CleanupCache,optional"`
|
|
NoCache bool `hcl:"NoCache,optional"`
|
|
NoLock bool `hcl:"NoLock,optional"`
|
|
}
|
|
|
|
// nolint:cyclop
|
|
func (glo ResticGlobalOpts) ToArgs() (args []string) {
|
|
if glo.CaCertFile != "" {
|
|
args = append(args, "--cacert", glo.CaCertFile)
|
|
}
|
|
|
|
if glo.CacheDir != "" {
|
|
args = append(args, "--cache-dir", glo.CacheDir)
|
|
}
|
|
|
|
if glo.PasswordFile != "" {
|
|
args = append(args, "--password-file", glo.PasswordFile)
|
|
}
|
|
|
|
if glo.TLSClientCertFile != "" {
|
|
args = append(args, "--tls-client-cert", glo.TLSClientCertFile)
|
|
}
|
|
|
|
if glo.LimitDownload > 0 {
|
|
args = append(args, "--limit-download", fmt.Sprint(glo.LimitDownload))
|
|
}
|
|
|
|
if glo.LimitUpload > 0 {
|
|
args = append(args, "--limit-upload", fmt.Sprint(glo.LimitUpload))
|
|
}
|
|
|
|
if glo.VerboseLevel > 0 {
|
|
args = append(args, "--verbose", fmt.Sprint(glo.VerboseLevel))
|
|
}
|
|
|
|
if glo.CleanupCache {
|
|
args = append(args, "--cleanup-cache")
|
|
}
|
|
|
|
if glo.NoCache {
|
|
args = append(args, "--no-cache")
|
|
}
|
|
|
|
if glo.NoLock {
|
|
args = append(args, "--no-lock")
|
|
}
|
|
|
|
return args
|
|
}
|
|
|
|
type ResticCmd struct {
|
|
Logger *log.Logger
|
|
Repo string
|
|
Env map[string]string
|
|
Passphrase string
|
|
GlobalOpts *ResticGlobalOpts
|
|
Cwd string
|
|
}
|
|
|
|
func (rcmd ResticCmd) BuildEnv() []string {
|
|
if rcmd.Env == nil {
|
|
rcmd.Env = map[string]string{}
|
|
}
|
|
|
|
if rcmd.Passphrase != "" {
|
|
rcmd.Env["RESTIC_PASSWORD"] = rcmd.Passphrase
|
|
}
|
|
|
|
envList := os.Environ()
|
|
|
|
for name, value := range rcmd.Env {
|
|
envList = append(envList, fmt.Sprintf("%s=%s", name, value))
|
|
}
|
|
|
|
return envList
|
|
}
|
|
|
|
func (rcmd ResticCmd) RunRestic(command string, options CommandOptions, commandArgs ...string) error {
|
|
args := []string{}
|
|
if rcmd.GlobalOpts != nil {
|
|
args = rcmd.GlobalOpts.ToArgs()
|
|
}
|
|
|
|
args = append(args, "--repo", rcmd.Repo, command)
|
|
args = append(args, options.ToArgs()...)
|
|
args = append(args, commandArgs...)
|
|
|
|
cmd := exec.Command("restic", args...)
|
|
|
|
cmd.Stdout = NewLogWriter(rcmd.Logger)
|
|
cmd.Stderr = cmd.Stdout
|
|
cmd.Env = rcmd.BuildEnv()
|
|
cmd.Dir = rcmd.Cwd
|
|
|
|
if err := cmd.Run(); err != nil {
|
|
return fmt.Errorf("error running restic %s: %w", command, err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type BackupOpts struct {
|
|
Exclude []string `hcl:"Exclude,optional"`
|
|
Include []string `hcl:"Include,optional"`
|
|
Tags []string `hcl:"Tags,optional"`
|
|
Host string `hcl:"Host,optional"`
|
|
}
|
|
|
|
func (bo BackupOpts) ToArgs() (args []string) {
|
|
for _, exclude := range bo.Exclude {
|
|
args = append(args, "--exclude", exclude)
|
|
}
|
|
|
|
for _, include := range bo.Include {
|
|
args = append(args, "--include", include)
|
|
}
|
|
|
|
for _, tag := range bo.Tags {
|
|
args = append(args, "--tag", tag)
|
|
}
|
|
|
|
if bo.Host != "" {
|
|
args = append(args, "--host", bo.Host)
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (rcmd ResticCmd) Backup(files []string, opts BackupOpts) error {
|
|
return rcmd.RunRestic("backup", opts, files...)
|
|
}
|
|
|
|
type RestoreOpts struct {
|
|
Exclude []string `hcl:"Exclude,optional"`
|
|
Include []string `hcl:"Include,optional"`
|
|
Host []string `hcl:"Host,optional"`
|
|
Tags []string `hcl:"Tags,optional"`
|
|
Path string `hcl:"Path,optional"`
|
|
Target string `hcl:"Target,optional"`
|
|
Verify bool `hcl:"Verify,optional"`
|
|
}
|
|
|
|
func (ro RestoreOpts) ToArgs() (args []string) {
|
|
for _, exclude := range ro.Exclude {
|
|
args = append(args, "--exclude", exclude)
|
|
}
|
|
|
|
for _, include := range ro.Include {
|
|
args = append(args, "--include", include)
|
|
}
|
|
|
|
for _, host := range ro.Host {
|
|
args = append(args, "--host", host)
|
|
}
|
|
|
|
for _, tag := range ro.Tags {
|
|
args = append(args, "--tag", tag)
|
|
}
|
|
|
|
if ro.Path != "" {
|
|
args = append(args, "--path", ro.Path)
|
|
}
|
|
|
|
if ro.Target != "" {
|
|
args = append(args, "--target", ro.Target)
|
|
}
|
|
|
|
if ro.Verify {
|
|
args = append(args, "--verify")
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (rcmd ResticCmd) Restore(snapshot string, opts RestoreOpts) error {
|
|
return rcmd.RunRestic("restore", opts, snapshot)
|
|
}
|
|
|
|
type TagList []string
|
|
|
|
func (t TagList) String() string {
|
|
return strings.Join(t, ",")
|
|
}
|
|
|
|
type ForgetOpts struct {
|
|
KeepLast int `hcl:"KeepLast,optional"`
|
|
KeepHourly int `hcl:"KeepHourly,optional"`
|
|
KeepDaily int `hcl:"KeepDaily,optional"`
|
|
KeepWeekly int `hcl:"KeepWeekly,optional"`
|
|
KeepMonthly int `hcl:"KeepMonthly,optional"`
|
|
KeepYearly int `hcl:"KeepYearly,optional"`
|
|
|
|
KeepWithin time.Duration `hcl:"KeepWithin,optional"`
|
|
KeepWithinHourly time.Duration `hcl:"KeepWithinHourly,optional"`
|
|
KeepWithinDaily time.Duration `hcl:"KeepWithinDaily,optional"`
|
|
KeepWithinWeekly time.Duration `hcl:"KeepWithinWeekly,optional"`
|
|
KeepWithinMonthly time.Duration `hcl:"KeepWithinMonthly,optional"`
|
|
KeepWithinYearly time.Duration `hcl:"KeepWithinYearly,optional"`
|
|
|
|
Tags []TagList `hcl:"Tags,optional"`
|
|
KeepTags []TagList `hcl:"KeepTags,optional"`
|
|
|
|
Prune bool `hcl:"Prune,optional"`
|
|
}
|
|
|
|
// nolint:funlen,cyclop
|
|
func (fo ForgetOpts) ToArgs() (args []string) {
|
|
// Add keep-*
|
|
if fo.KeepLast > 0 {
|
|
args = append(args, "--keep-last", fmt.Sprint(fo.KeepLast))
|
|
}
|
|
|
|
if fo.KeepHourly > 0 {
|
|
args = append(args, "--keep-hourly", fmt.Sprint(fo.KeepHourly))
|
|
}
|
|
|
|
if fo.KeepDaily > 0 {
|
|
args = append(args, "--keep-daily", fmt.Sprint(fo.KeepDaily))
|
|
}
|
|
|
|
if fo.KeepWeekly > 0 {
|
|
args = append(args, "--keep-weekly", fmt.Sprint(fo.KeepWeekly))
|
|
}
|
|
|
|
if fo.KeepMonthly > 0 {
|
|
args = append(args, "--keep-monthly", fmt.Sprint(fo.KeepMonthly))
|
|
}
|
|
|
|
if fo.KeepYearly > 0 {
|
|
args = append(args, "--keep-yearly", fmt.Sprint(fo.KeepYearly))
|
|
}
|
|
|
|
// Add keep-within-*
|
|
|
|
if fo.KeepWithin > 0 {
|
|
args = append(args, "--keep-within", fo.KeepWithin.String())
|
|
}
|
|
|
|
if fo.KeepWithinHourly > 0 {
|
|
args = append(args, "--keep-within-hourly", fo.KeepWithinHourly.String())
|
|
}
|
|
|
|
if fo.KeepWithinDaily > 0 {
|
|
args = append(args, "--keep-within-daily", fo.KeepWithinDaily.String())
|
|
}
|
|
|
|
if fo.KeepWithinWeekly > 0 {
|
|
args = append(args, "--keep-within-weekly", fo.KeepWithinWeekly.String())
|
|
}
|
|
|
|
if fo.KeepWithinMonthly > 0 {
|
|
args = append(args, "--keep-within-monthly", fo.KeepWithinMonthly.String())
|
|
}
|
|
|
|
if fo.KeepWithinYearly > 0 {
|
|
args = append(args, "--keep-within-yearly", fo.KeepWithinYearly.String())
|
|
}
|
|
|
|
// Add tags
|
|
for _, tagList := range fo.Tags {
|
|
args = append(args, "--tag", tagList.String())
|
|
}
|
|
|
|
for _, tagList := range fo.KeepTags {
|
|
args = append(args, "--keep-tag", tagList.String())
|
|
}
|
|
|
|
// Add prune options
|
|
if fo.Prune {
|
|
args = append(args, "--prune")
|
|
}
|
|
|
|
return args
|
|
}
|
|
|
|
func (rcmd ResticCmd) Forget(forgetOpts ForgetOpts) error {
|
|
return rcmd.RunRestic("forget", forgetOpts)
|
|
}
|
|
|
|
func (rcmd ResticCmd) Check() error {
|
|
return rcmd.RunRestic("check", NoOpts{})
|
|
}
|
|
|
|
func (rcmd ResticCmd) EnsureInit() error {
|
|
if err := rcmd.RunRestic("snapshots", NoOpts{}); err != nil {
|
|
return rcmd.RunRestic("init", NoOpts{})
|
|
}
|
|
|
|
return nil
|
|
}
|