Skip to content

Commit 5ed1af3

Browse files
committed
feat(backend): add init option to backend config
When this option is set to `true`, the backend will automatically get initialized during the backup process. This applies to invoking `autorestic backup` or when `autorestic cron` actualy performs a backup.
1 parent 8108c52 commit 5ed1af3

File tree

4 files changed

+125
-8
lines changed

4 files changed

+125
-8
lines changed

docs/pages/backend/index.md

+16
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,19 @@ backends:
3838
```
3939
4040
With this setting, if a key is missing, `autorestic` will crash instead of generating a new key and updating your config file.
41+
42+
## Automatic Backend Initialization
43+
44+
`autorestic` is able to automatically initialize backends for you. This is done by setting `init: true` in the config for a given backend. For example:
45+
46+
```yaml | .autorestic.yml
47+
backend:
48+
foo:
49+
type: ...
50+
path: ...
51+
init: true
52+
```
53+
54+
When you set `init: true` on a backend config, `autorestic` will automatically initialize the underlying `restic` repository that powers the backend if it's not already initialized. In practice, this means that the backend will be initialized the first time it is being backed up to.
55+
56+
This option is helpful in cases where you want to automate the configuration of `autorestic`. This means that instead of running `autorestic exec init -b ...` manually when you create a new backend, you can let `autorestic` initialize it for you.

internal/backend.go

+33-8
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type Backend struct {
2424
Path string `mapstructure:"path,omitempty"`
2525
Key string `mapstructure:"key,omitempty"`
2626
RequireKey bool `mapstructure:"requireKey,omitempty"`
27+
Init bool `mapstructure:"init,omitempty"`
2728
Env map[string]string `mapstructure:"env,omitempty"`
2829
Rest BackendRest `mapstructure:"rest,omitempty"`
2930
Options Options `mapstructure:"options,omitempty"`
@@ -130,20 +131,44 @@ func (b Backend) validate() error {
130131
return err
131132
}
132133
options := ExecuteOptions{Envs: env, Silent: true}
133-
// Check if already initialized
134+
135+
err = b.EnsureInit()
136+
if err != nil {
137+
return err
138+
}
139+
134140
cmd := []string{"check"}
135141
cmd = append(cmd, combineBackendOptions("check", b)...)
136142
_, _, err = ExecuteResticCommand(options, cmd...)
137-
if err == nil {
138-
return nil
139-
} else {
140-
// If not initialize
143+
return err
144+
}
145+
146+
// EnsureInit initializes the backend if it is not already initialized
147+
func (b Backend) EnsureInit() error {
148+
env, err := b.getEnv()
149+
if err != nil {
150+
return err
151+
}
152+
options := ExecuteOptions{Envs: env, Silent: true}
153+
154+
checkInitCmd := []string{"cat", "config"}
155+
checkInitCmd = append(checkInitCmd, combineBackendOptions("cat", b)...)
156+
_, _, err = ExecuteResticCommand(options, checkInitCmd...)
157+
158+
// Note that `restic` has a special exit code (10) to indicate that the
159+
// repository does not exist. This exit code was introduced in `[email protected]`
160+
// on 2024-07-26. We're not using it here because this is a too recent and
161+
// people on older versions of `restic` won't have this feature work correctly.
162+
// See: https://restic.readthedocs.io/en/latest/075_scripting.html#exit-codes
163+
if err != nil {
141164
colors.Body.Printf("Initializing backend \"%s\"...\n", b.name)
142-
cmd := []string{"init"}
143-
cmd = append(cmd, combineBackendOptions("init", b)...)
144-
_, _, err := ExecuteResticCommand(options, cmd...)
165+
initCmd := []string{"init"}
166+
initCmd = append(initCmd, combineBackendOptions("init", b)...)
167+
_, _, err := ExecuteResticCommand(options, initCmd...)
145168
return err
146169
}
170+
171+
return err
147172
}
148173

149174
func (b Backend) Exec(args []string) error {

internal/backend_test.go

+68
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@ package internal
33
import (
44
"fmt"
55
"os"
6+
"path"
67
"testing"
78

9+
"github.com/cupcakearmy/autorestic/internal/flags"
810
"github.com/spf13/viper"
911
"github.com/stretchr/testify/assert"
1012
)
@@ -263,3 +265,69 @@ func TestValidate(t *testing.T) {
263265
assert.EqualError(t, err, "backend foo requires a key but none was provided")
264266
})
265267
}
268+
269+
func TestValidateInitsRepo(t *testing.T) {
270+
// This is normally initialized by the cobra commands but they don't run in
271+
// this test so we do it ourselves.
272+
flags.RESTIC_BIN = "restic"
273+
274+
workDir := t.TempDir()
275+
276+
b := Backend{
277+
name: "test",
278+
Type: "local",
279+
Path: path.Join(workDir, "backend"),
280+
Key: "supersecret",
281+
}
282+
283+
config = &Config{Backends: map[string]Backend{"test": b}}
284+
defer func() { config = nil }()
285+
286+
// Check should fail because the repo doesn't exist
287+
err := b.Exec([]string{"check"})
288+
assert.Error(t, err)
289+
290+
err = b.validate()
291+
assert.NoError(t, err)
292+
293+
// Check should pass now
294+
err = b.Exec([]string{"check"})
295+
assert.NoError(t, err)
296+
}
297+
298+
func TestEnsureInit(t *testing.T) {
299+
// This is normally initialized by the cobra commands but they don't run in
300+
// this test so we do it ourselves.
301+
flags.RESTIC_BIN = "restic"
302+
303+
workDir := t.TempDir()
304+
305+
b := Backend{
306+
name: "test",
307+
Type: "local",
308+
Path: path.Join(workDir, "backend"),
309+
Key: "supersecret",
310+
}
311+
312+
config = &Config{Backends: map[string]Backend{"test": b}}
313+
defer func() { config = nil }()
314+
315+
// Check should fail because the repo doesn't exist
316+
err := b.Exec([]string{"check"})
317+
assert.Error(t, err)
318+
319+
err = b.EnsureInit()
320+
assert.NoError(t, err)
321+
322+
// Check should pass now
323+
err = b.Exec([]string{"check"})
324+
assert.NoError(t, err)
325+
326+
// Run again to make sure it's idempotent
327+
err = b.EnsureInit()
328+
assert.NoError(t, err)
329+
330+
// Check should still pass
331+
err = b.Exec([]string{"check"})
332+
assert.NoError(t, err)
333+
}

internal/location.go

+8
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,14 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
222222
continue
223223
}
224224

225+
if backend.Init {
226+
err = backend.EnsureInit()
227+
if err != nil {
228+
errors = append(errors, err)
229+
continue
230+
}
231+
}
232+
225233
cmd := []string{"backup"}
226234
cmd = append(cmd, combineAllOptions("backup", l, backend)...)
227235
if cron {

0 commit comments

Comments
 (0)