Compare commits

..

10 Commits

Author SHA1 Message Date
dependabot[bot]
021553b1d8 Bump next from 14.2.7 to 14.2.32 in /docs
Bumps [next](https://github.com/vercel/next.js) from 14.2.7 to 14.2.32.
- [Release notes](https://github.com/vercel/next.js/releases)
- [Changelog](https://github.com/vercel/next.js/blob/canary/release.js)
- [Commits](https://github.com/vercel/next.js/compare/v14.2.7...v14.2.32)

---
updated-dependencies:
- dependency-name: next
  dependency-version: 14.2.32
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-08-31 18:46:59 +00:00
dependabot[bot]
9cf919b42b Bump golang from 1.24-alpine to 1.25-alpine (#454)
Bumps golang from 1.24-alpine to 1.25-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-version: 1.25-alpine
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-31 13:48:49 +02:00
Duru Can Celasun
bb29a98614 feat: Run PreValidate hooks for check cmd (#437)
PreValidate can be used to mount remote directories (e.g. via NFS) so
they must executed first before running any restic commands.

This was done for the backup command in 13aa560, but not for check. This
commit fixes that.
2025-03-22 19:12:36 +01:00
Duru Can Celasun
39f4f87ce3 feat: Add --dry-run to backup command (#438)
Restic supports --dry-run for backups since 0.13.0 [1] and this adds
support for that.

[1] bc97a3d1f9
2025-03-22 19:10:51 +01:00
dependabot[bot]
bd36bbe429 Bump golang from 1.23-alpine to 1.24-alpine (#429)
Bumps golang from 1.23-alpine to 1.24-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>
2025-02-25 13:24:12 +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
16 changed files with 221 additions and 186 deletions

View File

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

View File

@@ -18,9 +18,11 @@ var backupCmd = &cobra.Command{
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()
dry, _ := cmd.Flags().GetBool("dry-run")
selected, err := internal.GetAllOrSelected(cmd, false)
CheckErr(err)
errors := 0
for _, name := range selected {
var splitted = strings.Split(name, "@")
@@ -29,7 +31,7 @@ var backupCmd = &cobra.Command{
specificBackend = splitted[1]
}
location, _ := internal.GetLocation(splitted[0])
errs := location.Backup(false, specificBackend)
errs := location.Backup(false, dry, specificBackend)
for _, err := range errs {
colors.Error.Printf("%s\n\n", err)
errors++
@@ -44,4 +46,5 @@ var backupCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(backupCmd)
internal.AddFlagsToCommand(backupCmd, false)
backupCmd.Flags().Bool("dry-run", false, "do not write changes, show what would be affected")
}

View File

@@ -42,7 +42,6 @@ func init() {
rootCmd.PersistentFlags().BoolVarP(&flags.VERBOSE, "verbose", "v", false, "verbose mode")
rootCmd.PersistentFlags().StringVar(&flags.RESTIC_BIN, "restic-bin", "restic", "specify custom restic binary")
rootCmd.PersistentFlags().StringVar(&flags.DOCKER_IMAGE, "docker-image", "cupcakearmy/autorestic:"+internal.VERSION, "specify a custom docker image")
rootCmd.PersistentFlags().StringVar(&flags.LOCKFILE_PATH, "lockfile-path", "", "specify a custom path for the lockfile (defaults to .autorestic.lock.yml next to the loaded autorestic config file)")
cobra.OnInitialize(initConfig)
}

View File

@@ -5,7 +5,7 @@
"start": "NEXT_TELEMETRY_DISABLED=1 next start"
},
"dependencies": {
"next": "^14.2.7",
"next": "^14.2.32",
"nextra": "^2.13.4",
"nextra-theme-docs": "^2.13.4",
"react": "^18.3.1",

View File

@@ -1,11 +1,14 @@
# Backup
```bash
autorestic backup [-l, --location] [-a, --all]
autorestic backup [-l, --location] [-a, --all] [--dry-run]
```
Performs a backup of all locations if the `-a` flag is passed. To only backup some locations pass one or more `-l` or `--location` flags.
The `--dry-run` flag will do a dry run showing what would have been deleted, but won't touch the actual data.
```bash
# All
autorestic backup -a

View File

@@ -34,11 +34,3 @@ With `--restic-bin` you can specify to run a specific restic binary. This can be
```bash
autorestic --restic-bin /some/path/to/my/custom/restic/binary
```
## `--lockfile-path`
Specify the path for the lockfile used by `autorestic`. If omitted, this will default to `.autorestic.lock.yml` next to the loaded config file.
```bash
autorestic --lockfile-path /path/to/my/.autorestic.lock.yml
```

157
docs/pnpm-lock.yaml generated
View File

@@ -9,14 +9,14 @@ importers:
.:
dependencies:
next:
specifier: ^14.2.7
version: 14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
specifier: ^14.2.32
version: 14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nextra:
specifier: ^2.13.4
version: 2.13.4(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 2.13.4(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nextra-theme-docs:
specifier: ^2.13.4
version: 2.13.4(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
version: 2.13.4(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react:
specifier: ^18.3.1
version: 18.3.1
@@ -136,59 +136,59 @@ packages:
resolution: {integrity: sha512-jMxvwzkKzd3cXo2EB9GM2ic0eYo2rP/BS6gJt6HnWbsDO1O8GSD4k7o2Cpr2YERtMpGF/MGcDfsfj2EbQPtrXw==}
engines: {node: '>= 10'}
'@next/env@14.2.7':
resolution: {integrity: sha512-OTx9y6I3xE/eih+qtthppwLytmpJVPM5PPoJxChFsbjIEFXIayG0h/xLzefHGJviAa3Q5+Fd+9uYojKkHDKxoQ==}
'@next/env@14.2.32':
resolution: {integrity: sha512-n9mQdigI6iZ/DF6pCTwMKeWgF2e8lg7qgt5M7HXMLtyhZYMnf/u905M18sSpPmHL9MKp9JHo56C6jrD2EvWxng==}
'@next/swc-darwin-arm64@14.2.7':
resolution: {integrity: sha512-UhZGcOyI9LE/tZL3h9rs/2wMZaaJKwnpAyegUVDGZqwsla6hMfeSj9ssBWQS9yA4UXun3pPhrFLVnw5KXZs3vw==}
'@next/swc-darwin-arm64@14.2.32':
resolution: {integrity: sha512-osHXveM70zC+ilfuFa/2W6a1XQxJTvEhzEycnjUaVE8kpUS09lDpiDDX2YLdyFCzoUbvbo5r0X1Kp4MllIOShw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-x64@14.2.7':
resolution: {integrity: sha512-ys2cUgZYRc+CbyDeLAaAdZgS7N1Kpyy+wo0b/gAj+SeOeaj0Lw/q+G1hp+DuDiDAVyxLBCJXEY/AkhDmtihUTA==}
'@next/swc-darwin-x64@14.2.32':
resolution: {integrity: sha512-P9NpCAJuOiaHHpqtrCNncjqtSBi1f6QUdHK/+dNabBIXB2RUFWL19TY1Hkhu74OvyNQEYEzzMJCMQk5agjw1Qg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@next/swc-linux-arm64-gnu@14.2.7':
resolution: {integrity: sha512-2xoWtE13sUJ3qrC1lwE/HjbDPm+kBQYFkkiVECJWctRASAHQ+NwjMzgrfqqMYHfMxFb5Wws3w9PqzZJqKFdWcQ==}
'@next/swc-linux-arm64-gnu@14.2.32':
resolution: {integrity: sha512-v7JaO0oXXt6d+cFjrrKqYnR2ubrD+JYP7nQVRZgeo5uNE5hkCpWnHmXm9vy3g6foMO8SPwL0P3MPw1c+BjbAzA==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-arm64-musl@14.2.7':
resolution: {integrity: sha512-+zJ1gJdl35BSAGpkCbfyiY6iRTaPrt3KTl4SF/B1NyELkqqnrNX6cp4IjjjxKpd64/7enI0kf6b9O1Uf3cL0pw==}
'@next/swc-linux-arm64-musl@14.2.32':
resolution: {integrity: sha512-tA6sIKShXtSJBTH88i0DRd6I9n3ZTirmwpwAqH5zdJoQF7/wlJXR8DkPmKwYl5mFWhEKr5IIa3LfpMW9RRwKmQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-x64-gnu@14.2.7':
resolution: {integrity: sha512-m6EBqrskeMUzykBrv0fDX/28lWIBGhMzOYaStp0ihkjzIYJiKUOzVYD1gULHc8XDf5EMSqoH/0/TRAgXqpQwmw==}
'@next/swc-linux-x64-gnu@14.2.32':
resolution: {integrity: sha512-7S1GY4TdnlGVIdeXXKQdDkfDysoIVFMD0lJuVVMeb3eoVjrknQ0JNN7wFlhCvea0hEk0Sd4D1hedVChDKfV2jw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-linux-x64-musl@14.2.7':
resolution: {integrity: sha512-gUu0viOMvMlzFRz1r1eQ7Ql4OE+hPOmA7smfZAhn8vC4+0swMZaZxa9CSIozTYavi+bJNDZ3tgiSdMjmMzRJlQ==}
'@next/swc-linux-x64-musl@14.2.32':
resolution: {integrity: sha512-OHHC81P4tirVa6Awk6eCQ6RBfWl8HpFsZtfEkMpJ5GjPsJ3nhPe6wKAJUZ/piC8sszUkAgv3fLflgzPStIwfWg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-win32-arm64-msvc@14.2.7':
resolution: {integrity: sha512-PGbONHIVIuzWlYmLvuFKcj+8jXnLbx4WrlESYlVnEzDsa3+Q2hI1YHoXaSmbq0k4ZwZ7J6sWNV4UZfx1OeOlbQ==}
'@next/swc-win32-arm64-msvc@14.2.32':
resolution: {integrity: sha512-rORQjXsAFeX6TLYJrCG5yoIDj+NKq31Rqwn8Wpn/bkPNy5rTHvOXkW8mLFonItS7QC6M+1JIIcLe+vOCTOYpvg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@next/swc-win32-ia32-msvc@14.2.7':
resolution: {integrity: sha512-BiSY5umlx9ed5RQDoHcdbuKTUkuFORDqzYKPHlLeS+STUWQKWziVOn3Ic41LuTBvqE0TRJPKpio9GSIblNR+0w==}
'@next/swc-win32-ia32-msvc@14.2.32':
resolution: {integrity: sha512-jHUeDPVHrgFltqoAqDB6g6OStNnFxnc7Aks3p0KE0FbwAvRg6qWKYF5mSTdCTxA3axoSAUwxYdILzXJfUwlHhA==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
'@next/swc-win32-x64-msvc@14.2.7':
resolution: {integrity: sha512-pxsI23gKWRt/SPHFkDEsP+w+Nd7gK37Hpv0ngc5HpWy2e7cKx9zR/+Q2ptAUqICNTecAaGWvmhway7pj/JLEWA==}
'@next/swc-win32-x64-msvc@14.2.32':
resolution: {integrity: sha512-2N0lSoU4GjfLSO50wvKpMQgKd4HdI2UHEhQPPPnlgfBJlOgJxkjpkYBqzk08f1gItBB6xF/n+ykso2hgxuydsA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@@ -319,8 +319,8 @@ packages:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'}
caniuse-lite@1.0.30001653:
resolution: {integrity: sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw==}
caniuse-lite@1.0.30001739:
resolution: {integrity: sha512-y+j60d6ulelrNSwpPyrHdl+9mJnQzHBr08xm48Qno0nSk4h3Qojh+ziv2qE6rXf4k3tadF4o1J/1tAbVm1NtnA==}
ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@@ -789,6 +789,7 @@ packages:
lodash.get@4.4.2:
resolution: {integrity: sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==}
deprecated: This package is deprecated. Use the optional chaining (?.) operator instead.
longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@@ -1000,8 +1001,8 @@ packages:
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
nanoid@3.3.7:
resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==}
nanoid@3.3.11:
resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@@ -1026,8 +1027,8 @@ packages:
react: '*'
react-dom: '*'
next@14.2.7:
resolution: {integrity: sha512-4Qy2aK0LwH4eQiSvQWyKuC7JXE13bIopEQesWE0c/P3uuNRnZCQanI0vsrMLmUQJLAto+A+/8+sve2hd+BQuOQ==}
next@14.2.32:
resolution: {integrity: sha512-fg5g0GZ7/nFc09X8wLe6pNSU8cLWbLRG3TZzPJ1BJvi2s9m7eF991se67wliM9kR5yLHRkyGKU49MMx58s3LJg==}
engines: {node: '>=18.17.0'}
hasBin: true
peerDependencies:
@@ -1101,8 +1102,8 @@ packages:
periscopic@3.1.0:
resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==}
picocolors@1.0.1:
resolution: {integrity: sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==}
picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
postcss@8.4.31:
resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==}
@@ -1210,8 +1211,8 @@ packages:
resolution: {integrity: sha512-Pdz01AvCAottHTPQGzndktFNdbRA75BgOfeT1hH+AMnJFv8lynkPi42rfeEhpx1saTEI3YNMWxfqu0sFD1G8pw==}
engines: {node: '>=12'}
source-map-js@1.2.0:
resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==}
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
source-map@0.7.4:
@@ -1280,8 +1281,8 @@ packages:
resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==}
engines: {node: '>=6.10'}
tslib@2.7.0:
resolution: {integrity: sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
type-fest@1.4.0:
resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==}
@@ -1502,33 +1503,33 @@ snapshots:
'@napi-rs/simple-git-win32-arm64-msvc': 0.1.19
'@napi-rs/simple-git-win32-x64-msvc': 0.1.19
'@next/env@14.2.7': {}
'@next/env@14.2.32': {}
'@next/swc-darwin-arm64@14.2.7':
'@next/swc-darwin-arm64@14.2.32':
optional: true
'@next/swc-darwin-x64@14.2.7':
'@next/swc-darwin-x64@14.2.32':
optional: true
'@next/swc-linux-arm64-gnu@14.2.7':
'@next/swc-linux-arm64-gnu@14.2.32':
optional: true
'@next/swc-linux-arm64-musl@14.2.7':
'@next/swc-linux-arm64-musl@14.2.32':
optional: true
'@next/swc-linux-x64-gnu@14.2.7':
'@next/swc-linux-x64-gnu@14.2.32':
optional: true
'@next/swc-linux-x64-musl@14.2.7':
'@next/swc-linux-x64-musl@14.2.32':
optional: true
'@next/swc-win32-arm64-msvc@14.2.7':
'@next/swc-win32-arm64-msvc@14.2.32':
optional: true
'@next/swc-win32-ia32-msvc@14.2.7':
'@next/swc-win32-ia32-msvc@14.2.32':
optional: true
'@next/swc-win32-x64-msvc@14.2.7':
'@next/swc-win32-x64-msvc@14.2.32':
optional: true
'@popperjs/core@2.11.8': {}
@@ -1538,7 +1539,7 @@ snapshots:
'@swc/helpers@0.5.5':
dependencies:
'@swc/counter': 0.1.3
tslib: 2.7.0
tslib: 2.8.1
'@tanstack/react-virtual@3.10.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
dependencies:
@@ -1650,7 +1651,7 @@ snapshots:
dependencies:
streamsearch: 1.1.0
caniuse-lite@1.0.30001653: {}
caniuse-lite@1.0.30001739: {}
ccount@2.0.1: {}
@@ -2687,7 +2688,7 @@ snapshots:
ms@2.1.2: {}
nanoid@3.3.7: {}
nanoid@3.3.11: {}
next-mdx-remote@4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
@@ -2700,44 +2701,44 @@ snapshots:
transitivePeerDependencies:
- supports-color
next-seo@6.5.0(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
next-seo@6.5.0(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
next: 14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next: 14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
next-themes@0.2.1(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
next-themes@0.2.1(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
next: 14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next: 14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@next/env': 14.2.7
'@next/env': 14.2.32
'@swc/helpers': 0.5.5
busboy: 1.6.0
caniuse-lite: 1.0.30001653
caniuse-lite: 1.0.30001739
graceful-fs: 4.2.11
postcss: 8.4.31
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
styled-jsx: 5.1.1(react@18.3.1)
optionalDependencies:
'@next/swc-darwin-arm64': 14.2.7
'@next/swc-darwin-x64': 14.2.7
'@next/swc-linux-arm64-gnu': 14.2.7
'@next/swc-linux-arm64-musl': 14.2.7
'@next/swc-linux-x64-gnu': 14.2.7
'@next/swc-linux-x64-musl': 14.2.7
'@next/swc-win32-arm64-msvc': 14.2.7
'@next/swc-win32-ia32-msvc': 14.2.7
'@next/swc-win32-x64-msvc': 14.2.7
'@next/swc-darwin-arm64': 14.2.32
'@next/swc-darwin-x64': 14.2.32
'@next/swc-linux-arm64-gnu': 14.2.32
'@next/swc-linux-arm64-musl': 14.2.32
'@next/swc-linux-x64-gnu': 14.2.32
'@next/swc-linux-x64-musl': 14.2.32
'@next/swc-win32-arm64-msvc': 14.2.32
'@next/swc-win32-ia32-msvc': 14.2.32
'@next/swc-win32-x64-msvc': 14.2.32
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros
nextra-theme-docs@2.13.4(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
nextra-theme-docs@2.13.4(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(nextra@2.13.4(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@popperjs/core': 2.11.8
@@ -2748,16 +2749,16 @@ snapshots:
git-url-parse: 13.1.1
intersection-observer: 0.12.2
match-sorter: 6.3.4
next: 14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-seo: 6.5.0(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes: 0.2.1(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nextra: 2.13.4(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next: 14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-seo: 6.5.0(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-themes: 0.2.1(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
nextra: 2.13.4(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
scroll-into-view-if-needed: 3.1.0
zod: 3.23.8
nextra@2.13.4(next@14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
nextra@2.13.4(next@14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
dependencies:
'@headlessui/react': 1.7.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@mdx-js/mdx': 2.3.0
@@ -2771,7 +2772,7 @@ snapshots:
gray-matter: 4.0.3
katex: 0.16.11
lodash.get: 4.4.2
next: 14.2.7(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next: 14.2.32(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
next-mdx-remote: 4.4.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
p-limit: 3.1.0
react: 18.3.1
@@ -2838,13 +2839,13 @@ snapshots:
estree-walker: 3.0.3
is-reference: 3.0.2
picocolors@1.0.1: {}
picocolors@1.1.1: {}
postcss@8.4.31:
dependencies:
nanoid: 3.3.7
picocolors: 1.0.1
source-map-js: 1.2.0
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
property-information@6.5.0: {}
@@ -2980,7 +2981,7 @@ snapshots:
dependencies:
is-plain-obj: 4.1.0
source-map-js@1.2.0: {}
source-map-js@1.2.1: {}
source-map@0.7.4: {}
@@ -3029,7 +3030,7 @@ snapshots:
ts-dedent@2.2.0: {}
tslib@2.7.0: {}
tslib@2.8.1: {}
type-fest@1.4.0: {}

View File

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

View File

@@ -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) {

View File

@@ -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
@@ -188,15 +188,31 @@ func CheckConfig() error {
if !CheckIfResticIsCallable() {
return fmt.Errorf(`%s was not found. Install either with "autorestic install" or manually`, flags.RESTIC_BIN)
}
for name, backend := range c.Backends {
backend.name = name
if err := backend.validate(); err != nil {
cwd, _ := GetPathRelativeToConfig(".")
for name, location := range c.Locations {
location.name = name
// Hooks before location validation
options := ExecuteOptions{
Command: "bash",
Dir: cwd,
Envs: map[string]string{
"AUTORESTIC_LOCATION": location.name,
},
}
if err := location.ExecuteHooks(location.Hooks.PreValidate, options); err != nil {
return err
}
if err := location.validate(); err != nil {
return err
}
}
for name, location := range c.Locations {
location.name = name
if err := location.validate(); err != nil {
for name, backend := range c.Backends {
backend.name = name
if err := backend.validate(); err != nil {
return err
}
}

View File

@@ -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()

View File

@@ -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
}

View File

@@ -1,10 +1,9 @@
package flags
var (
CI bool = false
VERBOSE bool = false
CRON_LEAN bool = false
RESTIC_BIN string
DOCKER_IMAGE string
LOCKFILE_PATH string
CI bool = false
VERBOSE bool = false
CRON_LEAN bool = false
RESTIC_BIN string
DOCKER_IMAGE string
)

View File

@@ -1,6 +1,7 @@
package internal
import (
"errors"
"fmt"
"io/ioutil"
"os"
@@ -33,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) {
@@ -167,7 +168,7 @@ func (l Location) getLocationTags() string {
return buildTag("location", l.name)
}
func (l Location) Backup(cron bool, specificBackend string) []error {
func (l Location) Backup(cron bool, dry bool, specificBackend string) []error {
var errors []error
var backends []string
colors.PrimaryPrint(" Backing up location \"%s\" ", l.name)
@@ -227,6 +228,9 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
if cron {
cmd = append(cmd, "--tag", buildTag("cron"))
}
if dry {
cmd = append(cmd, "--dry-run")
}
cmd = append(cmd, "--tag", l.getLocationTags())
backupOptions := ExecuteOptions{
Envs: env,
@@ -446,7 +450,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, false, "")
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)

View File

@@ -18,28 +18,18 @@ const (
RUNNING = "running"
)
// getLockfilePath returns the path to the lockfile. If flags.LOCKFILE_PATH is
// set, its value is used, otherwise the path is generated relative to the
// config file.
func getLockfilePath() string {
if flags.LOCKFILE_PATH != "" {
return flags.LOCKFILE_PATH
} else {
p := viper.ConfigFileUsed()
if p == "" {
colors.Error.Println("cannot lock before reading config location")
os.Exit(1)
}
return path.Join(path.Dir(p), ".autorestic.lock.yml")
}
}
func getLock() *viper.Viper {
if lock == nil {
once.Do(func() {
lock = viper.New()
lock.SetDefault("running", false)
file = getLockfilePath()
p := viper.ConfigFileUsed()
if p == "" {
colors.Error.Println("cannot lock before reading config location")
os.Exit(1)
}
file = path.Join(path.Dir(p), ".autorestic.lock.yml")
if !flags.CRON_LEAN {
colors.Faint.Println("Using lock:\t", file)
}

View File

@@ -7,7 +7,6 @@ import (
"strconv"
"testing"
"github.com/cupcakearmy/autorestic/internal/flags"
"github.com/spf13/viper"
)
@@ -29,37 +28,6 @@ func setup(t *testing.T) {
})
}
func TestGetLockfilePath(t *testing.T) {
t.Run("when flags.LOCKFILE_PATH is set", func(t *testing.T) {
flags.LOCKFILE_PATH = "/path/to/my/autorestic.lock.yml"
defer func() { flags.LOCKFILE_PATH = "" }()
p := getLockfilePath()
if p != "/path/to/my/autorestic.lock.yml" {
t.Errorf("got %v, want %v", p, "/path/to/my/autorestic.lock.yml")
}
})
t.Run("when flags.LOCKFILE_PATH is set", func(t *testing.T) {
d, err := os.MkdirTemp("", testDirectory)
if err != nil {
log.Fatalf("error creating temp dir: %v", err)
return
}
viper.SetConfigFile(d + "/.autorestic.yml")
defer viper.Reset()
flags.LOCKFILE_PATH = ""
p := getLockfilePath()
if p != d+"/.autorestic.lock.yml" {
t.Errorf("got %v, want %v", p, d+"/.autorestic.lock.yml")
}
})
}
func TestLock(t *testing.T) {
setup(t)