Compare commits

...

12 Commits

Author SHA1 Message Date
fliespl
b11ea8e0fa Merge d332f4476b into 48fa20b482 2024-11-26 10:53:10 +01:00
dependabot[bot]
48fa20b482 Bump restic/restic from 0.17.2 to 0.17.3 (#410)
Bumps restic/restic from 0.17.2 to 0.17.3.

---
updated-dependencies:
- dependency-name: restic/restic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-14 17:35:03 +01:00
dependabot[bot]
f7d28b486c 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] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-11-04 15:21:16 +01:00
Boris Bera
6895df1c83 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
2024-11-04 15:20:42 +01:00
Wez Furlong
8a773856de 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
```
2024-10-21 16:06:49 +02:00
Boris Bera
41e4e4a5f3 fix(cron): crash when errors are encountered during a backup (#403) 2024-10-17 13:49:45 +02:00
Chao
6424c64304 Update docker.md, fix incorrect location name (#399) 2024-09-27 21:55:02 +02:00
dependabot[bot]
d39dafaef1 Bump golang from 1.22-alpine to 1.23-alpine (#391)
Bumps golang from 1.22-alpine to 1.23-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-21 22:56:08 +02:00
dependabot[bot]
5a0f7e94f4 Bump restic/restic from 0.17.0 to 0.17.1 (#393)
Bumps restic/restic from 0.17.0 to 0.17.1.

---
updated-dependencies:
- dependency-name: restic/restic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-21 22:55:52 +02:00
Boris Bera
6a60d02759 chore(CI): add basic CI (build & test) for PRs and master pushes (#396) 2024-09-21 22:55:23 +02:00
1f7240c6a0 add start command 2024-08-29 17:09:45 +02:00
fliespl
d332f4476b do not replace all shell paths in the example 2023-10-24 22:10:41 +02:00
11 changed files with 123 additions and 37 deletions

24
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: CI
on:
pull_request:
push:
branches: [master]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '^1.21'
- run: go test -v ./...
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: '^1.21'
- run: go build -v .

View File

@@ -1,4 +1,4 @@
FROM golang:1.22-alpine as builder FROM golang:1.23-alpine as builder
WORKDIR /app WORKDIR /app
COPY go.* . COPY go.* .
@@ -6,7 +6,7 @@ RUN go mod download
COPY . . COPY . .
RUN go build RUN go build
FROM restic/restic:0.17.0 FROM restic/restic:0.17.3
RUN apk add --no-cache rclone bash curl docker-cli RUN apk add --no-cache rclone bash curl docker-cli
COPY --from=builder /app/autorestic /usr/bin/autorestic COPY --from=builder /app/autorestic /usr/bin/autorestic
ENTRYPOINT [] ENTRYPOINT []

View File

@@ -1,7 +1,8 @@
{ {
"scripts": { "scripts": {
"build": "NEXT_TELEMETRY_DISABLED=1 next build", "build": "NEXT_TELEMETRY_DISABLED=1 next build",
"dev": "NEXT_TELEMETRY_DISABLED=1 next" "dev": "NEXT_TELEMETRY_DISABLED=1 next",
"start": "NEXT_TELEMETRY_DISABLED=1 next start"
}, },
"dependencies": { "dependencies": {
"next": "^14.2.7", "next": "^14.2.7",

View File

@@ -34,7 +34,7 @@ Then paste this at the bottom of the file and save it. Note that in this specifi
```bash ```bash
# This is required, as it otherwise cannot find restic as a command. # This is required, as it otherwise cannot find restic as a command.
PATH="/usr/local/bin:/usr/bin:/bin" PATH="/usr/local/bin:$PATH"
# Example running every 5 minutes # Example running every 5 minutes
*/5 * * * * autorestic -c /path/to/my/.autorestic.yml --ci cron */5 * * * * autorestic -c /path/to/my/.autorestic.yml --ci cron

View File

@@ -18,7 +18,7 @@ services:
```yaml | .autorestic.yml ```yaml | .autorestic.yml
locations: locations:
foo: hello:
from: my-data from: my-data
type: volume type: volume
# ... # ...

View File

@@ -1,5 +1,5 @@
#!/bin/bash #!/bin/bash
set -e -o pipefail
shopt -s nocaseglob shopt -s nocaseglob
OUT_FILE=/usr/local/bin/autorestic OUT_FILE=/usr/local/bin/autorestic

View File

@@ -14,19 +14,19 @@ import (
) )
type BackendRest struct { type BackendRest struct {
User string `mapstructure:"user,omitempty"` User string `mapstructure:"user,omitempty" yaml:"user,omitempty"`
Password string `mapstructure:"password,omitempty"` Password string `mapstructure:"password,omitempty" yaml:"password,omitempty"`
} }
type Backend struct { type Backend struct {
name string name string
Type string `mapstructure:"type,omitempty"` Type string `mapstructure:"type,omitempty" yaml:"type,omitempty"`
Path string `mapstructure:"path,omitempty"` Path string `mapstructure:"path,omitempty" yaml:"path,omitempty"`
Key string `mapstructure:"key,omitempty"` Key string `mapstructure:"key,omitempty" yaml:"key,omitempty"`
RequireKey bool `mapstructure:"requireKey,omitempty"` RequireKey bool `mapstructure:"requireKey,omitempty" yaml:"requireKey,omitempty"`
Env map[string]string `mapstructure:"env,omitempty"` Env map[string]string `mapstructure:"env,omitempty" yaml:"env,omitempty"`
Rest BackendRest `mapstructure:"rest,omitempty"` Rest BackendRest `mapstructure:"rest,omitempty" yaml:"rest,omitempty"`
Options Options `mapstructure:"options,omitempty"` Options Options `mapstructure:"options,omitempty" yaml:"options,omitempty"`
} }
func GetBackend(name string) (Backend, bool) { func GetBackend(name string) (Backend, bool) {

View File

@@ -23,11 +23,11 @@ type OptionMap map[string][]interface{}
type Options map[string]OptionMap type Options map[string]OptionMap
type Config struct { type Config struct {
Version string `mapstructure:"version"` Version string `mapstructure:"version" yaml:"version"`
Extras interface{} `mapstructure:"extras"` Extras interface{} `mapstructure:"extras" yaml:"extras"`
Locations map[string]Location `mapstructure:"locations"` Locations map[string]Location `mapstructure:"locations" yaml:"locations"`
Backends map[string]Backend `mapstructure:"backends"` Backends map[string]Backend `mapstructure:"backends" yaml:"backends"`
Global Options `mapstructure:"global"` Global Options `mapstructure:"global" yaml:"global"`
} }
var once sync.Once var once sync.Once

View File

@@ -1,10 +1,15 @@
package internal package internal
import ( import (
"path"
"reflect" "reflect"
"strconv" "strconv"
"strings" "strings"
"sync"
"testing" "testing"
"github.com/spf13/viper"
"github.com/stretchr/testify/assert"
) )
func TestOptionToString(t *testing.T) { func TestOptionToString(t *testing.T) {
@@ -143,6 +148,48 @@ func TestGetOptionsMultipleKeys(t *testing.T) {
reflect.DeepEqual(result, expected) 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) { func assertEqual[T comparable](t testing.TB, result, expected T) {
t.Helper() t.Helper()

View File

@@ -1,12 +1,22 @@
package internal package internal
import (
"errors"
"fmt"
)
func RunCron() error { func RunCron() error {
c := GetConfig() c := GetConfig()
var errs []error
for name, l := range c.Locations { for name, l := range c.Locations {
l.name = name l.name = name
if err := l.RunCron(); err != nil { 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 return nil
} }

View File

@@ -1,6 +1,7 @@
package internal package internal
import ( import (
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
@@ -33,26 +34,26 @@ const (
) )
type Hooks struct { type Hooks struct {
Dir string `mapstructure:"dir"` Dir string `mapstructure:"dir" yaml:"dir"`
PreValidate HookArray `mapstructure:"prevalidate,omitempty"` PreValidate HookArray `mapstructure:"prevalidate,omitempty" yaml:"prevalidate,omitempty"`
Before HookArray `mapstructure:"before,omitempty"` Before HookArray `mapstructure:"before,omitempty" yaml:"before,omitempty"`
After HookArray `mapstructure:"after,omitempty"` After HookArray `mapstructure:"after,omitempty" yaml:"after,omitempty"`
Success HookArray `mapstructure:"success,omitempty"` Success HookArray `mapstructure:"success,omitempty" yaml:"success,omitempty"`
Failure HookArray `mapstructure:"failure,omitempty"` Failure HookArray `mapstructure:"failure,omitempty" yaml:"failure,omitempty"`
} }
type LocationCopy = map[string][]string type LocationCopy = map[string][]string
type Location struct { type Location struct {
name string `mapstructure:",omitempty"` name string `mapstructure:",omitempty" yaml:",omitempty"`
From []string `mapstructure:"from,omitempty"` From []string `mapstructure:"from,omitempty" yaml:"from,omitempty"`
Type string `mapstructure:"type,omitempty"` Type string `mapstructure:"type,omitempty" yaml:"type,omitempty"`
To []string `mapstructure:"to,omitempty"` To []string `mapstructure:"to,omitempty" yaml:"to,omitempty"`
Hooks Hooks `mapstructure:"hooks,omitempty"` Hooks Hooks `mapstructure:"hooks,omitempty" yaml:"hooks,omitempty"`
Cron string `mapstructure:"cron,omitempty"` Cron string `mapstructure:"cron,omitempty" yaml:"cron,omitempty"`
Options Options `mapstructure:"options,omitempty"` Options Options `mapstructure:"options,omitempty" yaml:"options,omitempty"`
ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"` ForgetOption LocationForgetOption `mapstructure:"forget,omitempty" yaml:"forget,omitempty"`
CopyOption LocationCopy `mapstructure:"copy,omitempty"` CopyOption LocationCopy `mapstructure:"copy,omitempty" yaml:"copy,omitempty"`
} }
func GetLocation(name string) (Location, bool) { func GetLocation(name string) (Location, bool) {
@@ -446,7 +447,10 @@ func (l Location) RunCron() error {
now := time.Now() now := time.Now()
if now.After(next) { if now.After(next) {
lock.SetCron(l.name, now.Unix()) 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 { } else {
if !flags.CRON_LEAN { if !flags.CRON_LEAN {
colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name) colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name)