From 41e4e4a5f32a46f78879c5daacd781309546c3c2 Mon Sep 17 00:00:00 2001 From: Boris Bera Date: Thu, 17 Oct 2024 07:49:45 -0400 Subject: [PATCH 1/4] fix(cron): crash when errors are encountered during a backup (#403) --- internal/cron.go | 12 +++++++++++- internal/location.go | 6 +++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/internal/cron.go b/internal/cron.go index 56ae3d5..055e192 100644 --- a/internal/cron.go +++ b/internal/cron.go @@ -1,12 +1,22 @@ package internal +import ( + "errors" + "fmt" +) + func RunCron() error { c := GetConfig() + var errs []error for name, l := range c.Locations { l.name = name if err := l.RunCron(); err != nil { - return err + errs = append(errs, err) } } + + if len(errs) > 0 { + return fmt.Errorf("Encountered errors during cron process:\n%w", errors.Join(errs...)) + } return nil } diff --git a/internal/location.go b/internal/location.go index a20efa7..ce970ec 100644 --- a/internal/location.go +++ b/internal/location.go @@ -1,6 +1,7 @@ package internal import ( + "errors" "fmt" "io/ioutil" "os" @@ -446,7 +447,10 @@ func (l Location) RunCron() error { now := time.Now() if now.After(next) { lock.SetCron(l.name, now.Unix()) - l.Backup(true, "") + errs := l.Backup(true, "") + if len(errs) > 0 { + return fmt.Errorf("Failed to backup location \"%s\":\n%w", l.name, errors.Join(errs...)) + } } else { if !flags.CRON_LEAN { colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name) From 8a773856dee5f614e3fc7ac02f3b12ac0a8d9191 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Mon, 21 Oct 2024 07:06:49 -0700 Subject: [PATCH 2/4] Improve error handling in install.sh (#404) Prior to this change, running the example from the docs without root privs produces this misleading/confusing output that claims that the software was installed when it wasn't: ```console $ wget -qO - https://raw.githubusercontent.com/cupcakearmy/autorestic/master/install.sh | bash linux amd64 /usr/local/bin/autorestic.bz2: Permission denied bzip2: Can't open input file /usr/local/bin/autorestic.bz2: No such file or directory. chmod: cannot access '/usr/local/bin/autorestic': No such file or directory bash: line 49: autorestic: command not found Successfully installed autorestic ``` With this change, the errors stop the script much earlier and produce this output instead: ``` linux amd64 /usr/local/bin/autorestic.bz2: Permission denied ``` --- install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install.sh b/install.sh index f70a584..68d70f4 100755 --- a/install.sh +++ b/install.sh @@ -1,5 +1,5 @@ #!/bin/bash - +set -e -o pipefail shopt -s nocaseglob OUT_FILE=/usr/local/bin/autorestic From 6895df1c8330c4efe26e7a044ad7e9ad356b7481 Mon Sep 17 00:00:00 2001 From: Boris Bera Date: Mon, 4 Nov 2024 09:20:42 -0500 Subject: [PATCH 3/4] fix(config): fix config marshaling producing unreadable config file (#402) There are two practical changes when the config gets updated: - The `forgetoption` and `configoption` bug is now gone - Superfluous config keys no longer get written out --- internal/backend.go | 18 ++++++++-------- internal/config.go | 10 ++++----- internal/config_test.go | 47 +++++++++++++++++++++++++++++++++++++++++ internal/location.go | 30 +++++++++++++------------- 4 files changed, 76 insertions(+), 29 deletions(-) diff --git a/internal/backend.go b/internal/backend.go index 669935a..26eadac 100644 --- a/internal/backend.go +++ b/internal/backend.go @@ -14,19 +14,19 @@ import ( ) type BackendRest struct { - User string `mapstructure:"user,omitempty"` - Password string `mapstructure:"password,omitempty"` + User string `mapstructure:"user,omitempty" yaml:"user,omitempty"` + Password string `mapstructure:"password,omitempty" yaml:"password,omitempty"` } type Backend struct { name string - Type string `mapstructure:"type,omitempty"` - Path string `mapstructure:"path,omitempty"` - Key string `mapstructure:"key,omitempty"` - RequireKey bool `mapstructure:"requireKey,omitempty"` - Env map[string]string `mapstructure:"env,omitempty"` - Rest BackendRest `mapstructure:"rest,omitempty"` - Options Options `mapstructure:"options,omitempty"` + Type string `mapstructure:"type,omitempty" yaml:"type,omitempty"` + Path string `mapstructure:"path,omitempty" yaml:"path,omitempty"` + Key string `mapstructure:"key,omitempty" yaml:"key,omitempty"` + RequireKey bool `mapstructure:"requireKey,omitempty" yaml:"requireKey,omitempty"` + Env map[string]string `mapstructure:"env,omitempty" yaml:"env,omitempty"` + Rest BackendRest `mapstructure:"rest,omitempty" yaml:"rest,omitempty"` + Options Options `mapstructure:"options,omitempty" yaml:"options,omitempty"` } func GetBackend(name string) (Backend, bool) { diff --git a/internal/config.go b/internal/config.go index 24d948c..c5430d3 100644 --- a/internal/config.go +++ b/internal/config.go @@ -23,11 +23,11 @@ type OptionMap map[string][]interface{} type Options map[string]OptionMap type Config struct { - Version string `mapstructure:"version"` - Extras interface{} `mapstructure:"extras"` - Locations map[string]Location `mapstructure:"locations"` - Backends map[string]Backend `mapstructure:"backends"` - Global Options `mapstructure:"global"` + Version string `mapstructure:"version" yaml:"version"` + Extras interface{} `mapstructure:"extras" yaml:"extras"` + Locations map[string]Location `mapstructure:"locations" yaml:"locations"` + Backends map[string]Backend `mapstructure:"backends" yaml:"backends"` + Global Options `mapstructure:"global" yaml:"global"` } var once sync.Once diff --git a/internal/config_test.go b/internal/config_test.go index 0d55f8e..99185e8 100644 --- a/internal/config_test.go +++ b/internal/config_test.go @@ -1,10 +1,15 @@ package internal import ( + "path" "reflect" "strconv" "strings" + "sync" "testing" + + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" ) func TestOptionToString(t *testing.T) { @@ -143,6 +148,48 @@ func TestGetOptionsMultipleKeys(t *testing.T) { reflect.DeepEqual(result, expected) } +func TestSaveConfigProducesReadableConfig(t *testing.T) { + workDir := t.TempDir() + viper.SetConfigFile(path.Join(workDir, ".autorestic.yml")) + + // Required to appease the config reader + viper.Set("version", 2) + + c := Config{ + Version: "2", + Locations: map[string]Location{ + "test": { + Type: "local", + name: "test", + From: []string{"in-dir"}, + To: []string{"test"}, + // ForgetOption & ConfigOption have previously marshalled in a way that + // can't get read correctly + ForgetOption: "foo", + CopyOption: map[string][]string{"foo": {"bar"}}, + }, + }, + Backends: map[string]Backend{ + "test": { + name: "test", + Type: "local", + Path: "backup-target", + Key: "supersecret", + }, + }, + } + + err := c.SaveConfig() + assert.NoError(t, err) + + // Ensure we the config reading logic actually runs + config = nil + once = sync.Once{} + readConfig := GetConfig() + assert.NotNil(t, readConfig) + assert.Equal(t, c, *readConfig) +} + func assertEqual[T comparable](t testing.TB, result, expected T) { t.Helper() diff --git a/internal/location.go b/internal/location.go index ce970ec..221c5f3 100644 --- a/internal/location.go +++ b/internal/location.go @@ -34,26 +34,26 @@ const ( ) type Hooks struct { - Dir string `mapstructure:"dir"` - PreValidate HookArray `mapstructure:"prevalidate,omitempty"` - Before HookArray `mapstructure:"before,omitempty"` - After HookArray `mapstructure:"after,omitempty"` - Success HookArray `mapstructure:"success,omitempty"` - Failure HookArray `mapstructure:"failure,omitempty"` + Dir string `mapstructure:"dir" yaml:"dir"` + PreValidate HookArray `mapstructure:"prevalidate,omitempty" yaml:"prevalidate,omitempty"` + Before HookArray `mapstructure:"before,omitempty" yaml:"before,omitempty"` + After HookArray `mapstructure:"after,omitempty" yaml:"after,omitempty"` + Success HookArray `mapstructure:"success,omitempty" yaml:"success,omitempty"` + Failure HookArray `mapstructure:"failure,omitempty" yaml:"failure,omitempty"` } type LocationCopy = map[string][]string type Location struct { - name string `mapstructure:",omitempty"` - From []string `mapstructure:"from,omitempty"` - Type string `mapstructure:"type,omitempty"` - To []string `mapstructure:"to,omitempty"` - Hooks Hooks `mapstructure:"hooks,omitempty"` - Cron string `mapstructure:"cron,omitempty"` - Options Options `mapstructure:"options,omitempty"` - ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"` - CopyOption LocationCopy `mapstructure:"copy,omitempty"` + name string `mapstructure:",omitempty" yaml:",omitempty"` + From []string `mapstructure:"from,omitempty" yaml:"from,omitempty"` + Type string `mapstructure:"type,omitempty" yaml:"type,omitempty"` + To []string `mapstructure:"to,omitempty" yaml:"to,omitempty"` + Hooks Hooks `mapstructure:"hooks,omitempty" yaml:"hooks,omitempty"` + Cron string `mapstructure:"cron,omitempty" yaml:"cron,omitempty"` + Options Options `mapstructure:"options,omitempty" yaml:"options,omitempty"` + ForgetOption LocationForgetOption `mapstructure:"forget,omitempty" yaml:"forget,omitempty"` + CopyOption LocationCopy `mapstructure:"copy,omitempty" yaml:"copy,omitempty"` } func GetLocation(name string) (Location, bool) { From f7d28b486c68c2cc078bdcfd7fa386dec969dfee Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 15:21:16 +0100 Subject: [PATCH 4/4] Bump restic/restic from 0.17.1 to 0.17.2 (#407) Bumps restic/restic from 0.17.1 to 0.17.2. --- updated-dependencies: - dependency-name: restic/restic dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index b0a9234..5fa1a9b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ RUN go mod download COPY . . RUN go build -FROM restic/restic:0.17.1 +FROM restic/restic:0.17.2 RUN apk add --no-cache rclone bash curl docker-cli COPY --from=builder /app/autorestic /usr/bin/autorestic ENTRYPOINT []