Compare commits

..

3 Commits

Author SHA1 Message Date
rdelaage f7a20a1210 Merge f43cc32ac3 into 3b57602fe8 2023-08-12 23:36:25 +02:00
Romain de Laage f43cc32ac3 Update docs to explain restore hooks feature and add a migration guide 2022-09-14 13:08:30 +02:00
Romain de Laage 4375a38bba Add restore hooks feature 2022-09-14 11:18:26 +02:00
73 changed files with 8221 additions and 3250 deletions
+2 -2
View File
@@ -3,7 +3,7 @@ name: Main
on: on:
push: push:
tags: tags:
- 'v*.*.*' - "v*.*.*"
jobs: jobs:
docker: docker:
@@ -40,7 +40,7 @@ jobs:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: actions/setup-go@v3 - uses: actions/setup-go@v3
with: with:
go-version: '^1.21' go-version: "^1.20"
- name: Build - name: Build
run: go run build/build.go run: go run build/build.go
- name: Release - name: Release
+3 -3
View File
@@ -1,4 +1,4 @@
FROM golang:1.21-alpine as builder FROM golang:1.20-alpine as builder
WORKDIR /app WORKDIR /app
COPY go.* . COPY go.* .
@@ -6,8 +6,8 @@ RUN go mod download
COPY . . COPY . .
RUN go build RUN go build
FROM restic/restic:0.16.4 FROM restic/restic:0.15.1
RUN apk add --no-cache rclone bash curl RUN apk add --no-cache rclone bash
COPY --from=builder /app/autorestic /usr/bin/autorestic COPY --from=builder /app/autorestic /usr/bin/autorestic
ENTRYPOINT [] ENTRYPOINT []
CMD [ "autorestic" ] CMD [ "autorestic" ]
+8 -2
View File
@@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"github.com/cupcakearmy/autorestic/internal" "github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/lock" "github.com/cupcakearmy/autorestic/internal/lock"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
@@ -42,8 +43,13 @@ var restoreCmd = &cobra.Command{
} }
} }
err = l.Restore(target, from, force, snapshot, optional) errs := l.Restore(target, from, force, snapshot, optional)
CheckErr(err) for _, err := range errs {
colors.Error.Printf("%s\n\n", err)
}
if len(errs) > 0 {
CheckErr(fmt.Errorf("%d errors were found", len(errs)))
}
}, },
} }
+18 -20
View File
@@ -59,7 +59,24 @@ func initConfig() {
os.Exit(1) os.Exit(1)
} }
} else { } else {
configPaths := getConfigPaths() configPaths := []string{"."}
// Home
if home, err := homedir.Dir(); err == nil {
configPaths = append(configPaths, home)
}
// XDG_CONFIG_HOME
{
prefix, found := os.LookupEnv("XDG_CONFIG_HOME")
if !found {
if home, err := homedir.Dir(); err != nil {
prefix = filepath.Join(home, ".config")
}
}
xdgConfig := filepath.Join(prefix, "autorestic")
configPaths = append(configPaths, xdgConfig)
}
for _, cfgPath := range configPaths { for _, cfgPath := range configPaths {
viper.AddConfigPath(cfgPath) viper.AddConfigPath(cfgPath)
} }
@@ -71,22 +88,3 @@ func initConfig() {
viper.AutomaticEnv() viper.AutomaticEnv()
} }
} }
func getConfigPaths() []string {
result := []string{"."}
if home, err := homedir.Dir(); err == nil {
result = append(result, home)
}
{
xdgConfigHome, found := os.LookupEnv("XDG_CONFIG_HOME")
if !found {
if home, err := homedir.Dir(); err == nil {
xdgConfigHome = filepath.Join(home, ".config")
}
}
xdgConfig := filepath.Join(xdgConfigHome, "autorestic")
result = append(result, xdgConfig)
}
return result
}
-36
View File
@@ -1,36 +0,0 @@
package cmd
import (
"github.com/mitchellh/go-homedir"
"os"
"path/filepath"
"slices"
"testing"
)
const xdgConfigHome = "XDG_CONFIG_HOME"
func assertContains(t *testing.T, array []string, element string) {
if !slices.Contains(array, element) {
t.Errorf("Expected %s to be contained in %s", element, array)
}
}
func TestConfigResolving(t *testing.T) {
t.Run("~/.config/autorestic is used if XDG_CONFIG_HOME is not set", func(t *testing.T) {
// Override env using testing so that env gets restored after test
t.Setenv(xdgConfigHome, "")
_ = os.Unsetenv("XDG_CONFIG_HOME")
configPaths := getConfigPaths()
homeDir, _ := homedir.Dir()
expectedConfigPath := filepath.Join(homeDir, ".config/autorestic")
assertContains(t, configPaths, expectedConfigPath)
})
t.Run("XDG_CONFIG_HOME is respected if set", func(t *testing.T) {
t.Setenv(xdgConfigHome, "/foo/bar")
configPaths := getConfigPaths()
assertContains(t, configPaths, filepath.Join("/", "foo", "bar", "autorestic"))
})
}
+15
View File
@@ -0,0 +1,15 @@
import { build } from '@codedoc/core';
import { config } from './config';
import { installTheme$ } from './content/theme';
import { content } from './content';
build(config, content, installTheme$, {
resolve: {
modules: ['.codedoc/node_modules']
},
resolveLoader: {
modules: ['.codedoc/node_modules']
}
});
+24
View File
@@ -0,0 +1,24 @@
import { configuration } from '@codedoc/core'
export const config = configuration({
src: {
base: 'markdown',
},
dest: {
html: './build',
assets: './build',
bundle: './_',
styles: './_',
},
page: {
title: {
base: 'Autorestic',
},
},
misc: {
github: {
user: 'cupcakearmy',
repo: 'autorestic',
},
},
})
+19
View File
@@ -0,0 +1,19 @@
import { CodedocConfig } from '@codedoc/core';
import { Footer as _Footer, GitterToggle$, Watermark} from '@codedoc/core/components';
export function Footer(config: CodedocConfig, renderer: any) {
let github$;
if (config.misc?.github)
github$ = <a href={`https://github.com/${config.misc.github.user}/${config.misc.github.repo}/`}
target="_blank">GitHub</a>;
let community$;
if (config.misc?.gitter)
community$ = <GitterToggle$ room={config.misc.gitter.room}/>
if (github$ && community$) return <_Footer>{github$}<hr/>{community$}</_Footer>;
else if (github$) return <_Footer>{github$}</_Footer>;
else if (community$) return <_Footer>{community$}</_Footer>;
else return <_Footer><Watermark/></_Footer>;
}
+21
View File
@@ -0,0 +1,21 @@
import { CodedocConfig } from '@codedoc/core';
import { Header as _Header, GithubButton, Watermark } from '@codedoc/core/components';
export function Header(config: CodedocConfig, renderer: any) {
return (
<_Header>{config.misc?.github ?
<fragment>
<GithubButton action={config.misc.github.action || 'Star'}
repo={config.misc.github.repo}
user={config.misc.github.user}
large={config.misc.github.large === true}
count={config.misc.github.count !== false}
standardIcon={config.misc.github.standardIcon !== false}/>
<br/><br/>
</fragment>
: ''}
<Watermark/>
</_Header>
)
}
+57
View File
@@ -0,0 +1,57 @@
import { RendererLike } from '@connectv/html'
import { File } from 'rxline/fs'
import {
Page,
Meta,
ContentNav,
Fonts,
ToC,
GithubSearch$,
} from '@codedoc/core/components'
import { config } from '../config'
import { Header } from './header'
import { Footer } from './footer'
export function content(
_content: HTMLElement,
toc: HTMLElement,
renderer: RendererLike<any, any>,
file: File<string>
) {
return (
<Page
title={config.page.title.extractor(_content, config, file)}
favicon={config.page.favicon}
meta={<Meta {...config.page.meta} />}
fonts={<Fonts {...config.page.fonts} />}
scripts={config.page.scripts}
stylesheets={config.page.stylesheets}
header={<Header {...config} />}
footer={<Footer {...config} />}
toc={
<ToC
default={'open'}
search={
config.misc?.github ? (
<GithubSearch$
repo={config.misc.github.repo}
user={config.misc.github.user}
root={config.src.base}
pick={config.src.pick.source}
drop={config.src.drop.source}
/>
) : (
false
)
}
>
{toc}
</ToC>
}
>
{_content}
<ContentNav content={_content} />
</Page>
)
}
+8
View File
@@ -0,0 +1,8 @@
import { funcTransport } from '@connectv/sdh/transport';
import { useTheme } from '@codedoc/core/transport';
import { theme } from '../theme';
export function installTheme() { useTheme(theme); }
export const installTheme$ = /*#__PURE__*/funcTransport(installTheme);
+4916
View File
File diff suppressed because it is too large Load Diff
+5
View File
@@ -0,0 +1,5 @@
{
"dependencies": {
"@codedoc/core": "^0.3.2"
}
}
+18
View File
@@ -0,0 +1,18 @@
import { join } from 'path';
import { serve } from '@codedoc/core';
import { config } from './config';
import { content } from './content';
import { installTheme$ } from './content/theme';
const root = join(__dirname, '../');
serve(root, config, content, installTheme$, {
resolve: {
modules: ['.codedoc/node_modules']
},
resolveLoader: {
modules: ['.codedoc/node_modules']
}
});
+11
View File
@@ -0,0 +1,11 @@
import { createTheme } from '@codedoc/core/transport';
export const theme = /*#__PURE__*/createTheme({
light: {
primary: '#1eb2a6'
},
dark: {
primary: '#1eb2a6'
}
});
+26
View File
@@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es6",
"noImplicitAny": true,
"declaration": false,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"alwaysStrict": true,
"sourceMap": true,
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"jsx": "react",
"jsxFactory": "renderer.create",
"lib": [
"es2017",
"dom"
]
},
"include": [
"./**/*"
]
}
+22
View File
@@ -0,0 +1,22 @@
import { exec, spawn } from 'child_process';
import { config } from './config';
const cmd = 'ts-node-dev';
const params = `--project .codedoc/tsconfig.json`
+ ` -T --watch ${config.src.base},.codedoc`
+ ` --ignore-watch .codedoc/node_modules`
+ ` .codedoc/serve`;
if (process.platform === 'win32') {
const child = exec(cmd + ' ' + params);
child.stdout?.pipe(process.stdout);
child.stderr?.pipe(process.stderr);
child.on('close', () => {});
}
else {
const child = spawn(cmd, [params], { stdio: 'inherit', shell: 'bash' });
child.on('close', () => {});
}
+1 -1
View File
@@ -1,2 +1,2 @@
node_modules node_modules
.next build
-1
View File
@@ -1 +0,0 @@
v20.8.0
+55
View File
@@ -0,0 +1,55 @@
[Home](/)
[Quick Start](/quick)
[Installation](/installation)
[Configuration](/config)
[Upgrade](/upgrade)
> :Collapse label=Locations
>
> [Overview](/location/overview)
> [Hooks](/location/hooks)
>
> > :Collapse label=Options
> >
> > [Overview](/location/options)
> > [Excluding Files](/location/exclude)
> > [Forget Policy](/location/forget)
> > [Copy](/location/copy)
>
> [Cron](/location/cron)
> [Docker Volumes](/location/docker)
> :Collapse label=Backend
>
> [Overview](/backend/overview)
> [Available Backends](/backend/available)
> [Options](/backend/options)
> [Environment](/backend/env)
> :Collapse label=CLI
>
> [General](/cli/general)
> [Info](/cli/info)
> [Check](/cli/check)
> [Completion](/cli/completion)
> [Backup](/cli/backup)
> [Restore](/cli/restore)
> [Forget](/cli/forget)
> [Cron](/cli/cron)
> [Exec](/cli/exec)
> [Install](/cli/install)
> [Uninstall](/cli/uninstall)
> [Upgrade](/cli/upgrade)
> :Collapse label=Migration
>
> [0.x → 1.0](/migration/0.x_1.0)
> [1.4 → 1.5](/migration/1.4_1.5)
> [1.7 → 1.8](/migration/1.7_1.8)
[Examples](/examples)
[Docker](/docker)
[QA](/qa)
[Community](/community)
[Contributors](/contrib)
@@ -83,3 +83,5 @@ backends:
user: user user: user
password: pass password: pass
``` ```
> :ToCPrevNext
@@ -63,3 +63,5 @@ backends:
type: b2 type: b2
path: myBucket path: myBucket
``` ```
> :ToCPrevNext
@@ -15,3 +15,5 @@ backend:
``` ```
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command. In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
> :ToCPrevNext
@@ -16,3 +16,5 @@ backends:
## Types ## Types
We restic supports multiple types of backends. See the [full list](/backend/available) for details. We restic supports multiple types of backends. See the [full list](/backend/available) for details.
> :ToCPrevNext
@@ -21,3 +21,5 @@ autorestic backup -l foo -l bar
```bash ```bash
autorestic backup -l location@backend autorestic backup -l location@backend
``` ```
> :ToCPrevNext
@@ -7,3 +7,5 @@ autorestic check
Checks locations and backends are configured properly and initializes them if they are not already. Checks locations and backends are configured properly and initializes them if they are not already.
This is mostly an internal command, but useful to verify if a backend is configured correctly. This is mostly an internal command, but useful to verify if a backend is configured correctly.
> :ToCPrevNext
@@ -13,3 +13,5 @@ Supported shells are
- powershell - powershell
To see how to install run `autorestic help completion` and follow the instructions for your specific shell To see how to install run `autorestic help completion` and follow the instructions for your specific shell
> :ToCPrevNext
@@ -9,3 +9,5 @@ This command is mostly intended to be triggered by an automated system like syst
It will run cron jobs as [specified in the cron section](/location/cron) of a specific location. It will run cron jobs as [specified in the cron section](/location/cron) of a specific location.
The `--lean` flag will omit output like _skipping location x: not due yet_. This can be useful if you are dumping the output of the cron job to a log file and don't want to be overwhelmed by the output log. The `--lean` flag will omit output like _skipping location x: not due yet_. This can be useful if you are dumping the output of the cron job to a log file and don't want to be overwhelmed by the output log.
> :ToCPrevNext
@@ -11,3 +11,5 @@ autorestic exec -av -- snapshots
``` ```
With `exec` you can basically run every cli command that you would be able to run with the restic cli. It only pre-fills path, key, etc. With `exec` you can basically run every cli command that you would be able to run with the restic cli. It only pre-fills path, key, etc.
> :ToCPrevNext
@@ -4,8 +4,10 @@
autorestic forget [-l, --location] [-a, --all] [--dry-run] [--prune] autorestic forget [-l, --location] [-a, --all] [--dry-run] [--prune]
``` ```
This will prune and remove old data form the backends according to the [keep policy you have specified for the location](/location/options/forget). This will prune and remove old data form the backends according to the [keep policy you have specified for the location](/location/forget).
The `--dry-run` flag will do a dry run showing what would have been deleted, but won't touch the actual data. The `--dry-run` flag will do a dry run showing what would have been deleted, but won't touch the actual data.
The `--prune` flag will also [prune the data](https://restic.readthedocs.io/en/latest/060_forget.html#removing-backup-snapshots). This is a costly operation that can take longer, however it will free up the actual space. The `--prune` flag will also [prune the data](https://restic.readthedocs.io/en/latest/060_forget.html#removing-backup-snapshots). This is a costly operation that can take longer, however it will free up the actual space.
> :ToCPrevNext
@@ -34,3 +34,5 @@ With `--restic-bin` you can specify to run a specific restic binary. This can be
```bash ```bash
autorestic --restic-bin /some/path/to/my/custom/restic/binary autorestic --restic-bin /some/path/to/my/custom/restic/binary
``` ```
> :ToCPrevNext
@@ -14,3 +14,5 @@ autorestic info
```bash ```bash
autorestic -c path/to/some/config.yml info autorestic -c path/to/some/config.yml info
``` ```
> :ToCPrevNext
@@ -5,3 +5,5 @@ Installs both restic and autorestic to `/usr/local/bin`.
```bash ```bash
autorestic install autorestic install
``` ```
> :ToCPrevNext
@@ -15,3 +15,5 @@ autorestic restore -l home --from hdd --to /path/where/to/restore
``` ```
This will restore the location `home` to the `/path/where/to/restore` folder and taking the data from the backend `hdd` This will restore the location `home` to the `/path/where/to/restore` folder and taking the data from the backend `hdd`
> :ToCPrevNext
@@ -5,3 +5,5 @@ Uninstalls both restic and autorestic from `/usr/local/bin`.
```bash ```bash
autorestic uninstall autorestic uninstall
``` ```
> :ToCPrevNext
@@ -7,3 +7,5 @@ autorestic upgrade
``` ```
Updates both restic and autorestic automagically. Updates both restic and autorestic automagically.
> :ToCPrevNext
@@ -9,3 +9,5 @@ A list of community driven projects. (No official affiliation)
- Ansible Role: <https://github.com/FuzzyMistborn/ansible-role-autorestic> - Ansible Role: <https://github.com/FuzzyMistborn/ansible-role-autorestic>
- Ansible Role: <https://0xacab.org/varac-projects/ansible-role-autorestic> - Ansible Role: <https://0xacab.org/varac-projects/ansible-role-autorestic>
- Ansible Role: <https://github.com/dbrennand/ansible-role-autorestic> - Ansible Role: <https://github.com/dbrennand/ansible-role-autorestic>
> :ToCPrevNext
@@ -2,11 +2,10 @@
## Path ## Path
By default autorestic searches for a `.autorestic.yml` file in the current directory, your home folder and your XDG config folder (`~/.config/` by default): By default autorestic searches for a `.autorestic.yml` file in the current directory and your home folder.
- `./.autorestic.yml` - `./.autorestic.yml`
- `~/.autorestic.yml` - `~/.autorestic.yml`
- `~/.config/autorestic/.autorestic.yml`
You can also specify a custom file with the `-c path/to/some/config.yml` You can also specify a custom file with the `-c path/to/some/config.yml`
@@ -56,10 +55,11 @@ version: 2
extras: extras:
hooks: &foo hooks: &foo
before: backup:
- echo "Hello" before:
after: - echo "Hello"
- echo "kthxbye" after:
- echo "kthxbye"
policies: &bar policies: &bar
keep-daily: 14 keep-daily: 14
keep-weekly: 52 keep-weekly: 52
@@ -84,3 +84,5 @@ locations:
forget: forget:
<<: *bar <<: *bar
``` ```
> :ToCPrevNext
@@ -17,3 +17,5 @@ This amazing people helped the project!
- @TheForcer - Typos. - @TheForcer - Typos.
- @themorlan - Typos. - @themorlan - Typos.
- @somebox - Typos. - @somebox - Typos.
> :ToCPrevNext
@@ -22,12 +22,13 @@ autorestic exec -b my-backend -- unlock
extras: extras:
healthchecks: &healthchecks healthchecks: &healthchecks
hooks: hooks:
before: backup:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Starting backup for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/start' before:
failure: - 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Starting backup for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/start'
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup failed for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/fail' failure:
success: - 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup failed for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/fail'
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup successful for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>' success:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup successful for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>'
locations: locations:
something: something:
@@ -36,3 +37,5 @@ locations:
to: to:
- somewhere-else - somewhere-else
``` ```
> :ToCPrevNext
@@ -18,3 +18,5 @@ Autorestic is a wrapper around the amazing [restic](https://restic.net/). While
- Cron jobs for automatic backup - Cron jobs for automatic backup
- Backup & Restore docker volumes - Backup & Restore docker volumes
- Generated completions for `[bash|zsh|fish|powershell]` - Generated completions for `[bash|zsh|fish|powershell]`
> :ToCPrevNext
@@ -30,4 +30,6 @@ Fedora users can install the [autorestic](https://src.fedoraproject.org/rpms/aut
### AUR ### AUR
If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/) ~~If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/) (looking for maintainers).~~ - Deprecated
> :ToCPrevNext
@@ -1,6 +1,6 @@
# Cron # Cron
Often it is useful to trigger backups automatically. For this, we can specify a `cron` attribute to each location. Often it is usefully to trigger backups automatically. For this we can specify a `cron` attribute to each location.
```yaml | .autorestic.yml ```yaml | .autorestic.yml
locations: locations:
@@ -10,15 +10,15 @@ locations:
cron: '0 3 * * 0' # Every Sunday at 3:00 cron: '0 3 * * 0' # Every Sunday at 3:00
``` ```
Here is an awesome website with [some examples](https://crontab.guru/examples.html) and an [explorer](https://crontab.guru/). Here is a awesome website with [some examples](https://crontab.guru/examples.html) and an [explorer](https://crontab.guru/)
## Installing the cron ## Installing the cron
**This has to be done only once, regardless of how many cron jobs you have in your config file.** **This has to be done only once, regardless of now many cron jobs you have in your config file.**
To actually enable cron jobs you need something to call `autorestic cron` on a timed schedule. To actually enable cron jobs you need something to call `autorestic cron` on a timed schedule.
Note that the schedule has nothing to do with the `cron` attribute in each location. Note that the schedule has nothing to do with the `cron` attribute in each location.
My advice would be to trigger the command every 5min, but if you have a cronjob that runs only once a week, it's probably enough to schedule it once a day. My advise would be to trigger the command every 5min, but if you have a cronjob that runs only once a week, it's probably enough to schedule it once a day.
### Crontab ### Crontab
@@ -50,4 +50,6 @@ To debug a cron job you can use
Now you can add as many `cron` attributes as you wish in the config file ⏱ Now you can add as many `cron` attributes as you wish in the config file ⏱
> Also note that manually triggered backups with `autorestic backup` will not influence the cron timeline, they are intentionally not linked. > Also note that manually triggered backups with `autorestic backup` will not influence the cron timeline, they are willingly not linked.
> :ToCPrevNext
@@ -35,3 +35,5 @@ autorestic restore -l hello
``` ```
The volume has to exists whenever backing up or restoring. The volume has to exists whenever backing up or restoring.
> :ToCPrevNext
@@ -16,3 +16,5 @@ locations:
- '*.abc' - '*.abc'
exclude-file: .gitignore exclude-file: .gitignore
``` ```
> :ToCPrevNext
@@ -53,3 +53,5 @@ locations:
forget: forget:
keep-last: 5 keep-last: 5
``` ```
> :ToCPrevNext
@@ -1,8 +1,8 @@
# Hooks # Hooks
If you want to perform some commands before and/or after a backup, you can use hooks. If you want to perform some commands before and/or after a backup or restore, you can use hooks.
They consist of a list of commands that will be executed in the same directory as the target `from`. They consist of a list of commands that will be executed in the same directory as the config file or in the `dir` directory if configured.
The following hooks groups are supported, none are required: The following hooks groups are supported, none are required:
@@ -17,16 +17,27 @@ locations:
from: /data from: /data
to: my-backend to: my-backend
hooks: hooks:
before: backup:
- echo "One" before:
- echo "Two" - echo "One"
- echo "Three" - echo "Two"
after: - echo "Three"
- echo "Byte" after:
failure: - echo "Byte"
- echo "Something went wrong" failure:
success: - echo "Something went wrong"
- echo "Well done!" success:
- echo "Well done!"
restore:
dir: /var/www/html
before:
- echo "Let's restore this backup!"
after:
- echo "Finished to restore"
failure:
- echo "A problem has been encountered :("
success:
- echo "Successfully restored!"
``` ```
## Flowchart ## Flowchart
@@ -76,3 +87,5 @@ AUTORESTIC_LOCATION=bar
AUTORESTIC_FILES_ADDED_0=42 AUTORESTIC_FILES_ADDED_0=42
AUTORESTIC_FILES_ADDED_FOO=42 AUTORESTIC_FILES_ADDED_FOO=42
``` ```
> :ToCPrevNext
@@ -63,3 +63,5 @@ backends:
locations: locations:
# ... # ...
``` ```
> :ToCPrevNext
@@ -4,7 +4,6 @@ Locations can be seen as the input to the backup process. Generally this is simp
The paths can be relative from the config file. A location can have multiple backends, so that the data is secured across multiple servers. The paths can be relative from the config file. A location can have multiple backends, so that the data is secured across multiple servers.
Note: names of locations MUST be lower case! Note: names of locations MUST be lower case!
```yaml | .autorestic.yml ```yaml | .autorestic.yml
version: 2 version: 2
@@ -31,3 +30,5 @@ Paths can be absolute or relative. If relative they are resolved relative to the
## `to` ## `to`
This is either a single backend or an array of backends. The backends have to be configured in the same config file. This is either a single backend or an array of backends. The backends have to be configured in the same config file.
> :ToCPrevNext
@@ -22,3 +22,5 @@ remote:
``` ```
Other than the config file there is a new `-v, --verbose` flag which shows the output of native commands, which are now hidden by default. Other than the config file there is a new `-v, --verbose` flag which shows the output of native commands, which are now hidden by default.
> :ToCPrevNext
@@ -64,3 +64,5 @@ Autorestic changed the way backups are referenced. Before we took the paths as t
```bash ```bash
autorestic exec -va -- tag --add ar:location:LOCATION_NAME # Only if you have only one location autorestic exec -va -- tag --add ar:location:LOCATION_NAME # Only if you have only one location
``` ```
> :ToCPrevNext
+45
View File
@@ -0,0 +1,45 @@
# Migration from `1.7` to `1.8`
## Config files
- The config version have been changed
- You can now configure restore hooks
See detailed instructions below.
## Config Version
The version field of the config file has been changed from `2` to `3`.
## Hooks
Since `1.8` both backup and restore hooks are possible.
For this reason, backup hooks have been moved one layer deeper, you have to move them in a `backup` object.
Before:
```yaml
locations:
l1:
# ...
from: /foo/bar
hooks:
before:
- pwd
```
After:
```yaml
locations:
l1:
# ...
from: /foo/bar
hooks:
backup:
before:
- pwd
restore:
after:
- echo "My super restore hook"
```
@@ -2,3 +2,4 @@
- [From 0.x to 1.0](/migration/0.x_1.0) - [From 0.x to 1.0](/migration/0.x_1.0)
- [From 1.4 to 1.5](/migration/1.4_1.5) - [From 1.4 to 1.5](/migration/1.4_1.5)
- [From 1.7 to 1.8](/migration/1.7_1.8)
+2
View File
@@ -6,3 +6,5 @@ This happens when autorestic needs to write to the config file: e.g. when we are
Unfortunately during this process formatting and comments are lost because the `yaml` library used is not comment and/or format aware. Unfortunately during this process formatting and comments are lost because the `yaml` library used is not comment and/or format aware.
That is why autorestic will place a copy of your old config next to the one we are writing to. That is why autorestic will place a copy of your old config next to the one we are writing to.
> :ToCPrevNext
@@ -82,3 +82,5 @@ autorestic restore -l home --from hdd --to /path/where/to/restore
``` ```
This will restore the location `home` from the backend `hdd` to the given path. This will restore the location `home` from the backend `hdd` to the given path.
> :ToCPrevNext
-6
View File
@@ -1,6 +0,0 @@
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.jsx',
})
module.exports = withNextra()
+2741
View File
File diff suppressed because it is too large Load Diff
+5 -9
View File
@@ -1,14 +1,10 @@
{ {
"private": true,
"scripts": { "scripts": {
"build": "NEXT_TELEMETRY_DISABLED=1 next build", "build": "codedoc install && codedoc build",
"dev": "NEXT_TELEMETRY_DISABLED=1 next" "dev": "codedoc serve"
}, },
"dependencies": { "dependencies": {
"next": "^13.5.3", "@codedoc/cli": "^0.3.0"
"nextra": "^2.13.1", }
"nextra-theme-docs": "^2.13.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"packageManager": "pnpm@8.8.0"
} }
-10
View File
@@ -1,10 +0,0 @@
{
"index": "Home",
"quick": "Quick Start",
"installation": "Installation",
"config": "Configuration",
"location": "Locations",
"backend": "Backend",
"cli": "CLI",
"migration": "Migration"
}
-6
View File
@@ -1,6 +0,0 @@
{
"index": "Overview",
"available": "Available backends",
"options": "Options",
"env": "Environment"
}
-3
View File
@@ -1,3 +0,0 @@
{
"general": "General"
}
-7
View File
@@ -1,7 +0,0 @@
{
"index": "Overview",
"hooks": "Hooks",
"options": "Options",
"cron": "Cronjobs",
"docker": "Docker volumes"
}
-3
View File
@@ -1,3 +0,0 @@
{
"index": "Overview"
}
-4
View File
@@ -1,4 +0,0 @@
{
"0.x_1.0": "0.x → 1.0",
"1.4_1.5": "1.4 → 1.5"
}
-3044
View File
File diff suppressed because it is too large Load Diff
-24
View File
@@ -1,24 +0,0 @@
export default {
logo: <span>Autorestic</span>,
docsRepositoryBase: 'https://github.com/cupcakearmy/autorestic/tree/master/docs',
project: {
link: 'https://github.com/cupcakearmy/autorestic',
},
sidebar: {
defaultMenuCollapseLevel: 1,
},
feedback: {
content: 'Question? An error? Give feedback →',
},
footer: {
text: (
<span>
MIT {new Date().getFullYear()} ©{' '}
<a href="https://github.com/cupcakearmy" target="_blank">
cupcakearmy
</a>
.
</span>
),
},
}
+3
View File
@@ -0,0 +1,3 @@
{
"cleanUrls": true
}
+1 -1
View File
@@ -1,6 +1,6 @@
module github.com/cupcakearmy/autorestic module github.com/cupcakearmy/autorestic
go 1.21 go 1.20
require ( require (
github.com/blang/semver/v4 v4.0.0 github.com/blang/semver/v4 v4.0.0
+10 -6
View File
@@ -17,7 +17,7 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const VERSION = "1.7.11" const VERSION = "1.7.7"
type OptionMap map[string][]interface{} type OptionMap map[string][]interface{}
type Options map[string]OptionMap type Options map[string]OptionMap
@@ -83,7 +83,7 @@ func GetConfig() *Config {
exitConfig(nil, "version specified in config file is not an int") exitConfig(nil, "version specified in config file is not an int")
} else { } else {
// Check for version // Check for version
if version != 2 { if version != 3 {
exitConfig(nil, "unsupported config version number. please check the docs for migration\nhttps://autorestic.vercel.app/migration/") exitConfig(nil, "unsupported config version number. please check the docs for migration\nhttps://autorestic.vercel.app/migration/")
} }
} }
@@ -132,10 +132,14 @@ func (c *Config) Describe() {
tmp = "" tmp = ""
hooks := map[string][]string{ hooks := map[string][]string{
"Before": l.Hooks.Before, "Before backup": l.Hooks.BackupOption.Before,
"After": l.Hooks.After, "After backup": l.Hooks.BackupOption.After,
"Failure": l.Hooks.Failure, "Failure backup": l.Hooks.BackupOption.Failure,
"Success": l.Hooks.Success, "Success backup": l.Hooks.BackupOption.Success,
"Before restore": l.Hooks.RestoreOption.Before,
"After restore": l.Hooks.RestoreOption.After,
"Failure restore": l.Hooks.RestoreOption.Failure,
"Success restore": l.Hooks.RestoreOption.Success,
} }
for hook, commands := range hooks { for hook, commands := range hooks {
if len(commands) > 0 { if len(commands) > 0 {
+76 -29
View File
@@ -33,6 +33,13 @@ const (
) )
type Hooks struct { type Hooks struct {
RestoreOption HooksList `mapstructure:"restore,omitempty"`
BackupOption HooksList `mapstructure:"backup,omitempty"`
}
type LocationCopy = map[string][]string
type HooksList struct {
Dir string `mapstructure:"dir"` Dir string `mapstructure:"dir"`
Before HookArray `mapstructure:"before,omitempty"` Before HookArray `mapstructure:"before,omitempty"`
After HookArray `mapstructure:"after,omitempty"` After HookArray `mapstructure:"after,omitempty"`
@@ -40,18 +47,16 @@ type Hooks struct {
Failure HookArray `mapstructure:"failure,omitempty"` Failure HookArray `mapstructure:"failure,omitempty"`
} }
type LocationCopy = map[string][]string
type Location struct { type Location struct {
name string `mapstructure:",omitempty"` name string `mapstructure:",omitempty"`
From []string `mapstructure:"from,omitempty"` From []string `mapstructure:"from,omitempty"`
Type string `mapstructure:"type,omitempty"` Type string `mapstructure:"type,omitempty"`
To []string `mapstructure:"to,omitempty"` To []string `mapstructure:"to,omitempty"`
Hooks Hooks `mapstructure:"hooks,omitempty"` Hooks Hooks `mapstructure:"hooks,omitempty"`
Cron string `mapstructure:"cron,omitempty"` Cron string `mapstructure:"cron,omitempty"`
Options Options `mapstructure:"options,omitempty"` Options Options `mapstructure:"options,omitempty"`
ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"` ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"`
CopyOption LocationCopy `mapstructure:"copy,omitempty"` CopyOption LocationCopy `mapstructure:"copy,omitempty"`
} }
func GetLocation(name string) (Location, bool) { func GetLocation(name string) (Location, bool) {
@@ -123,12 +128,12 @@ func (l Location) validate() error {
return nil return nil
} }
func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error { func (l Location) ExecuteHooks(commands []string, directory string, options ExecuteOptions) error {
if len(commands) == 0 { if len(commands) == 0 {
return nil return nil
} }
if l.Hooks.Dir != "" { if directory != "" {
if dir, err := GetPathRelativeToConfig(l.Hooks.Dir); err != nil { if dir, err := GetPathRelativeToConfig(directory); err != nil {
return err return err
} else { } else {
options.Dir = dir options.Dir = dir
@@ -190,7 +195,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
} }
// Hooks // Hooks
if err := l.ExecuteHooks(l.Hooks.Before, options); err != nil { if err := l.ExecuteHooks(l.Hooks.BackupOption.Before, l.Hooks.BackupOption.Dir, options); err != nil {
errors = append(errors, err) errors = append(errors, err)
goto after goto after
} }
@@ -290,7 +295,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
} }
// After hooks // After hooks
if err := l.ExecuteHooks(l.Hooks.After, options); err != nil { if err := l.ExecuteHooks(l.Hooks.BackupOption.After, l.Hooks.BackupOption.Dir, options); err != nil {
errors = append(errors, err) errors = append(errors, err)
} }
@@ -298,11 +303,11 @@ after:
var commands []string var commands []string
var isSuccess = len(errors) == 0 var isSuccess = len(errors) == 0
if isSuccess { if isSuccess {
commands = l.Hooks.Success commands = l.Hooks.BackupOption.Success
} else { } else {
commands = l.Hooks.Failure commands = l.Hooks.BackupOption.Failure
} }
if err := l.ExecuteHooks(commands, options); err != nil { if err := l.ExecuteHooks(commands, l.Hooks.BackupOption.Dir, options); err != nil {
errors = append(errors, err) errors = append(errors, err)
} }
@@ -370,11 +375,35 @@ func buildRestoreCommand(l Location, to string, snapshot string, options []strin
return base return base
} }
func (l Location) Restore(to, from string, force bool, snapshot string, options []string) error { func (l Location) Restore(to, from string, force bool, snapshot string, options []string) (errors []error) {
cwd, _ := GetPathRelativeToConfig(".")
hooksOptions := ExecuteOptions{
Command: "bash",
Dir: cwd,
Envs: map[string]string{
"AUTORESTIC_LOCATION": l.name,
},
}
defer func() {
var commands []string
var isSuccess = len(errors) == 0
if isSuccess {
commands = l.Hooks.RestoreOption.Success
} else {
commands = l.Hooks.RestoreOption.Failure
}
if err := l.ExecuteHooks(commands, l.Hooks.RestoreOption.Dir, hooksOptions); err != nil {
errors = append(errors, err)
}
colors.Success.Println("Done")
}()
if from == "" { if from == "" {
from = l.To[0] from = l.To[0]
} else if !l.hasBackend(from) { } else if !l.hasBackend(from) {
return fmt.Errorf("invalid backend: \"%s\"", from) errors = append(errors, fmt.Errorf("invalid backend: \"%s\"", from))
} }
if snapshot == "" { if snapshot == "" {
@@ -385,15 +414,23 @@ func (l Location) Restore(to, from string, force bool, snapshot string, options
backend, _ := GetBackend(from) backend, _ := GetBackend(from)
colors.Secondary.Printf("Restoring %s@%s → %s\n", snapshot, backend.name, to) colors.Secondary.Printf("Restoring %s@%s → %s\n", snapshot, backend.name, to)
// Before Hooks for restore
if err := l.ExecuteHooks(l.Hooks.RestoreOption.Before, l.Hooks.RestoreOption.Dir, hooksOptions); err != nil {
errors = append(errors, err)
return
}
t, err := l.getType() t, err := l.getType()
if err != nil { if err != nil {
return err errors = append(errors, err)
return
} }
switch t { switch t {
case TypeLocal: case TypeLocal:
to, err = filepath.Abs(to) to, err = filepath.Abs(to)
if err != nil { if err != nil {
return err errors = append(errors, err)
return
} }
// Check if target is empty // Check if target is empty
if !force { if !force {
@@ -402,14 +439,17 @@ func (l Location) Restore(to, from string, force bool, snapshot string, options
if err == nil { if err == nil {
files, err := ioutil.ReadDir(to) files, err := ioutil.ReadDir(to)
if err != nil { if err != nil {
return err errors = append(errors, err)
return
} }
if len(files) > 0 { if len(files) > 0 {
return notEmptyError errors = append(errors, notEmptyError)
return
} }
} else { } else {
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
return err errors = append(errors, err)
return
} }
} }
} }
@@ -418,10 +458,17 @@ func (l Location) Restore(to, from string, force bool, snapshot string, options
_, _, err = backend.ExecDocker(l, buildRestoreCommand(l, "/", snapshot, options)) _, _, err = backend.ExecDocker(l, buildRestoreCommand(l, "/", snapshot, options))
} }
if err != nil { if err != nil {
return err errors = append(errors, err)
return
} }
colors.Success.Println("Done")
return nil // After Hooks for restore
if err := l.ExecuteHooks(l.Hooks.RestoreOption.After, l.Hooks.RestoreOption.Dir, hooksOptions); err != nil {
errors = append(errors, err)
return
}
return
} }
func (l Location) RunCron() error { func (l Location) RunCron() error {
+1 -1
View File
@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,