mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2025-09-06 10:30:39 +00:00
Compare commits
107 Commits
Author | SHA1 | Date | |
---|---|---|---|
ddc3accb30 | |||
7874512ec0 | |||
0b5f4017e4 | |||
88198c4fcb | |||
8cd759105f | |||
6137e31c3b | |||
2789502c89 | |||
b87381cd3b | |||
048a5ffed8 | |||
02a8e461d4 | |||
a1abe13a39 | |||
ddf287f6f5 | |||
56f82ae656 | |||
95b1ca3297 | |||
|
7a8830cb2f | ||
959d19cbdb | |||
9dc7763445 | |||
|
50984f6771 | ||
e80f200873 | |||
86ae70672a | |||
5c0788900f | |||
20334a7e83 | |||
7bebd04482 | |||
b9b8857bf4 | |||
dd6e618161 | |||
a4b54f9f64 | |||
c2e88193cd | |||
a2ef69d96d | |||
|
d45949b028 | ||
77d47cc697 | |||
a3239c0f3b | |||
90cd3171e5 | |||
61673bd88b | |||
41736ea3c4 | |||
a7779e04bd | |||
|
1326e7e53c | ||
478e193d78 | |||
11d4c67dce | |||
1643309957 | |||
e05386b0b5 | |||
aebaf0a225 | |||
c090013bf5 | |||
88c6949208 | |||
9256cdc38c | |||
a8c611e8ce | |||
d4522c7ffe | |||
21185d894e | |||
b119fc7ea5 | |||
|
73f4bcfd48 | ||
|
3567319314 | ||
|
bf19c983d1 | ||
|
12e4de110b | ||
c9f425ef64 | |||
974f555dff | |||
1688c1f3c3 | |||
29e46d3b5c | |||
0335abb669 | |||
b2f9b9a54e | |||
2b13a6e13d | |||
cc293ea256 | |||
a4ddd5bbcb | |||
7cbf43b75d | |||
bae77c4673 | |||
12adeb2b06 | |||
37b26dfc31 | |||
c1795b2acc | |||
b8d12e518c | |||
50060cf539 | |||
c33aac42dc | |||
c359053e0e | |||
c16340ab26 | |||
edc85c4ac3 | |||
68682777f2 | |||
|
b6c7922df5 | ||
|
991b8bec22 | ||
bbc32568ad | |||
f3c038c716 | |||
59612a97b6 | |||
33319a00ef | |||
8eb14ea14f | |||
70eb9e441f | |||
be25af2d76 | |||
1c436369f0 | |||
6efcce07b7 | |||
|
dc6dd2e712 | ||
|
68628d3776 | ||
40988ef3b4 | |||
fad33fcdaa | |||
|
8cf8a77558 | ||
36998cfd3b | |||
cf03562ea2 | |||
188560395d | |||
bacbd0f806 | |||
93bf0388a4 | |||
ec8fdbd135 | |||
420934489c | |||
|
2ba767c8c3 | ||
b489c662c7 | |||
6862529a89 | |||
aa96a95600 | |||
89e32c298c | |||
873170c6d1 | |||
ea82fea8e1 | |||
a35edcaea5 | |||
86d44eafad | |||
e927fd5a64 | |||
d5e13d4e27 |
5
.github/workflows/build.yml
vendored
5
.github/workflows/build.yml
vendored
@@ -15,6 +15,11 @@ jobs:
|
||||
go-version: '^1.16.3'
|
||||
- name: Build
|
||||
run: go run build/build.go
|
||||
|
||||
- name: Sign
|
||||
uses: tristan-weil/ghaction-checksum-sign-artifact@v1.0.1
|
||||
with:
|
||||
path: dist/*
|
||||
- name: Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
with:
|
||||
|
122
CHANGELOG.md
Normal file
122
CHANGELOG.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.3.0] - 2021-10-26
|
||||
|
||||
### Added
|
||||
|
||||
- Pass restic backup metadata as ENV to hooks
|
||||
- Support for `XDG_CONFIG_HOME` and `${HOME}/.config` as default locations for `.autorestic.yaml` file.
|
||||
- Binary restic flags are now supported
|
||||
- Pass encryption keys from env variables or files.
|
||||
|
||||
## [1.2.0] - 2021-08-05
|
||||
|
||||
### Added
|
||||
|
||||
- Community page
|
||||
- Support for yaml references and aliases
|
||||
|
||||
### Fixed
|
||||
|
||||
- Better verbose output for hooks
|
||||
- Better error message for bad formatted configs
|
||||
|
||||
## [1.1.2] - 2021-07-11
|
||||
|
||||
### Fixes
|
||||
|
||||
Don't check all backend when running `forget` or `exec` commands.
|
||||
|
||||
## [1.1.1] - 2021-05-17
|
||||
|
||||
### Added
|
||||
|
||||
- Options for backends
|
||||
|
||||
## [1.1.0] - 2021-05-06
|
||||
|
||||
### Added
|
||||
|
||||
- use custom restic binary
|
||||
- success & failure hooks
|
||||
|
||||
### Fixed
|
||||
|
||||
- don't skip other locations on failure
|
||||
|
||||
## [1.0.9] - 2021-05-01
|
||||
|
||||
### Fixed
|
||||
|
||||
- Validation for docker volumes
|
||||
|
||||
## [1.0.8] - 2021-04-28
|
||||
|
||||
### Added
|
||||
|
||||
- `--lean` flag to cron command for less output about skipping backups.
|
||||
|
||||
### Fixed
|
||||
|
||||
- consistent lower casing in usage descriptions.
|
||||
|
||||
## [1.0.7] - 2021-04-26
|
||||
|
||||
### Added
|
||||
|
||||
- Support for `darwin/arm64` aka Apple Silicon.
|
||||
- Added support for `arm64` and `aarch64` in install scripts.
|
||||
|
||||
## [1.0.6] - 2021-04-24
|
||||
|
||||
### Added
|
||||
|
||||
- Support for rclone
|
||||
|
||||
## [1.0.5] - 2021-04-24
|
||||
|
||||
### Fixed
|
||||
|
||||
- Correct exit code on backup failure and better logging/output/feedback.
|
||||
- Check if `from` key is an actual directory.
|
||||
|
||||
## [1.0.4] - 2021-04-23
|
||||
|
||||
### Added
|
||||
|
||||
- Options to add rest username and password in config
|
||||
|
||||
### Fixed
|
||||
|
||||
- Don't add empty strings when saving config
|
||||
|
||||
## [1.0.3] - 2021-04-20
|
||||
|
||||
### Fixed
|
||||
|
||||
- Auto upgrade script was not working on linux as linux does not support writing to the binary that is being executed
|
||||
|
||||
## [1.0.2] - 2021-04-20
|
||||
|
||||
### Added
|
||||
|
||||
- Add the `cron` tag to backup to backups made with cron.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Don't unlock lockfile if process is already running.
|
||||
|
||||
## [1.0.1] - 2021-04-17
|
||||
|
||||
### Added
|
||||
|
||||
- Completion command for various shells
|
||||
|
||||
## [1.0.0] - 2021-04-17
|
||||
|
||||
- Rewrite in go. See https://autorestic.vercel.app/upgrade for migration.
|
30
DEVELOPMENT.md
Normal file
30
DEVELOPMENT.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Development
|
||||
|
||||
## Coding
|
||||
|
||||
The easiest way (imo) is to run [`gowatch`](https://github.com/silenceper/gowatch) in a separate terminal and the simply run `./autorestic ...`. `gowatch` will watch the code and automatically rebuild the binary when changes are saved to disk.
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
go run build/build.go
|
||||
```
|
||||
|
||||
This will build and compress binaries for multiple platforms. The output will be put in the `dist` folder.
|
||||
|
||||
## Releasing
|
||||
|
||||
Releases are automatically built by the github workflow and uploaded to the release.
|
||||
|
||||
1. Bump `VERSION` in `internal/config.go`.
|
||||
2. Update `CHANGELOG.md`
|
||||
3. Commit to master
|
||||
4. Create a new release with the `v1.2.3` tag and mark as pre-release.
|
||||
5. The Github action will build the binaries, upload and mark the release as ready when done.
|
||||
|
||||
### Brew
|
||||
|
||||
1. Download the latest release.
|
||||
2. Check the checksum with `shasum -a 256 autorestic-1.2.3.tar.gz`
|
||||
3. Update `url` and `sha256` in the brew repo.
|
||||
4. Submit PR
|
16
README.md
16
README.md
@@ -10,6 +10,13 @@
|
||||
Config driven, easy backup cli for <a href="https://restic.net/">restic</a>.
|
||||
<br>
|
||||
<strong><a href="https://autorestic.vercel.app/">»»» Docs & Getting Started »»»</a></strong>
|
||||
<br><br>
|
||||
<a target="_blank" href="https://discord.gg/wS7RpYTYd2">
|
||||
<img src="https://img.shields.io/discord/252403122348097536" alt="discord badge" />
|
||||
<img src="https://img.shields.io/github/contributors/cupcakearmy/autorestic" alt="contributor badge" />
|
||||
<img src="https://img.shields.io/github/downloads/cupcakearmy/autorestic/total" alt="downloads badge" />
|
||||
<img src="https://img.shields.io/github/v/release/cupcakearmy/autorestic" alt="version badge" />
|
||||
</a>
|
||||
</p>
|
||||
</p>
|
||||
|
||||
@@ -18,7 +25,7 @@
|
||||
|
||||
### 💭 Why / What?
|
||||
|
||||
Autorestic is a wrapper around the amazing [restic](https://restic.net/). While being amazing the restic cli can be a bit overwhelming and difficult to manage if you have many different location that you want to backup to multiple locations. This utility is aimed at making this easier 🙂
|
||||
Autorestic is a wrapper around the amazing [restic](https://restic.net/). While being amazing the restic cli can be a bit overwhelming and difficult to manage if you have many different locations that you want to backup to multiple locations. This utility is aimed at making this easier 🙂.
|
||||
|
||||
### 🌈 Features
|
||||
|
||||
@@ -35,4 +42,9 @@ Autorestic is a wrapper around the amazing [restic](https://restic.net/). While
|
||||
|
||||
### ❓ Questions / Support
|
||||
|
||||
Check the [discussions page](https://github.com/cupcakearmy/autorestic/discussions)
|
||||
Check the [discussions page](https://github.com/cupcakearmy/autorestic/discussions) or [join on discord](https://discord.gg/wS7RpYTYd2)
|
||||
|
||||
## Contributing / Developing
|
||||
|
||||
PRs, feature requests, etc. are welcomed :)
|
||||
Have a look at [the dev docs](./DEVELOPMENT.md)
|
||||
|
@@ -1,9 +0,0 @@
|
||||
# Releasing
|
||||
|
||||
Releases are handled by the CD server with includes checksums for each binary.
|
||||
|
||||
```bash
|
||||
git tag 0.x
|
||||
git push
|
||||
git push origin --tags
|
||||
```
|
@@ -9,6 +9,7 @@ import (
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal"
|
||||
)
|
||||
@@ -16,7 +17,7 @@ import (
|
||||
var DIR, _ = filepath.Abs("./dist")
|
||||
|
||||
var targets = map[string][]string{
|
||||
"darwin": {"amd64"},
|
||||
"darwin": {"amd64", "arm64"},
|
||||
"freebsd": {"386", "amd64", "arm"},
|
||||
"linux": {"386", "amd64", "arm", "arm64"},
|
||||
"netbsd": {"386", "amd64"},
|
||||
@@ -27,7 +28,7 @@ type buildOptions struct {
|
||||
Target, Arch, Version string
|
||||
}
|
||||
|
||||
func build(options buildOptions) error {
|
||||
func build(options buildOptions, wg *sync.WaitGroup) {
|
||||
fmt.Printf("Building %s %s\n", options.Target, options.Arch)
|
||||
out := fmt.Sprintf("autorestic_%s_%s_%s", options.Version, options.Target, options.Arch)
|
||||
out = path.Join(DIR, out)
|
||||
@@ -46,7 +47,7 @@ func build(options buildOptions) error {
|
||||
)
|
||||
err := c.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,26 +59,25 @@ func build(options buildOptions) error {
|
||||
c.Stderr = os.Stderr
|
||||
err := c.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
wg.Done()
|
||||
}
|
||||
|
||||
func main() {
|
||||
os.RemoveAll(DIR)
|
||||
v := internal.VERSION
|
||||
var wg sync.WaitGroup
|
||||
for target, archs := range targets {
|
||||
for _, arch := range archs {
|
||||
err := build(buildOptions{
|
||||
wg.Add(1)
|
||||
build(buildOptions{
|
||||
Target: target,
|
||||
Arch: arch,
|
||||
Version: v,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}, &wg)
|
||||
}
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
@@ -1,7 +1,10 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal"
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
@@ -10,17 +13,24 @@ var backupCmd = &cobra.Command{
|
||||
Use: "backup",
|
||||
Short: "Create backups for given locations",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
internal.GetConfig()
|
||||
err := lock.Lock()
|
||||
CheckErr(err)
|
||||
defer lock.Unlock()
|
||||
|
||||
CheckErr(internal.CheckConfig())
|
||||
|
||||
selected, err := internal.GetAllOrSelected(cmd, false)
|
||||
CheckErr(err)
|
||||
errors := 0
|
||||
for _, name := range selected {
|
||||
location, _ := internal.GetLocation(name)
|
||||
location.Backup()
|
||||
errs := location.Backup(false)
|
||||
for err := range errs {
|
||||
colors.Error.Println(err)
|
||||
errors++
|
||||
}
|
||||
}
|
||||
if errors > 0 {
|
||||
CheckErr(fmt.Errorf("%d errors were found", errors))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@@ -17,7 +17,7 @@ var checkCmd = &cobra.Command{
|
||||
|
||||
CheckErr(internal.CheckConfig())
|
||||
|
||||
colors.Success.Println("Everyting is fine.")
|
||||
colors.Success.Println("Everything is fine.")
|
||||
},
|
||||
}
|
||||
|
||||
|
@@ -11,6 +11,7 @@ var cronCmd = &cobra.Command{
|
||||
Short: "Run cron job for automated backups",
|
||||
Long: `Intended to be mainly triggered by an automated system like systemd or crontab. For each location checks if a cron backup is due and runs it.`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
internal.CRON_LEAN, _ = cmd.Flags().GetBool("lean")
|
||||
err := lock.Lock()
|
||||
CheckErr(err)
|
||||
defer lock.Unlock()
|
||||
@@ -22,4 +23,5 @@ var cronCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(cronCmd)
|
||||
cronCmd.Flags().Bool("lean", false, "only output information about actual backups")
|
||||
}
|
||||
|
@@ -11,12 +11,11 @@ var execCmd = &cobra.Command{
|
||||
Use: "exec",
|
||||
Short: "Execute arbitrary native restic commands for given backends",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
internal.GetConfig()
|
||||
err := lock.Lock()
|
||||
CheckErr(err)
|
||||
defer lock.Unlock()
|
||||
|
||||
CheckErr(internal.CheckConfig())
|
||||
|
||||
selected, err := internal.GetAllOrSelected(cmd, true)
|
||||
CheckErr(err)
|
||||
for _, name := range selected {
|
||||
|
@@ -10,12 +10,11 @@ var forgetCmd = &cobra.Command{
|
||||
Use: "forget",
|
||||
Short: "Forget and optionally prune snapshots according the specified policies",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
internal.GetConfig()
|
||||
err := lock.Lock()
|
||||
CheckErr(err)
|
||||
defer lock.Unlock()
|
||||
|
||||
CheckErr(internal.CheckConfig())
|
||||
|
||||
selected, err := internal.GetAllOrSelected(cmd, false)
|
||||
CheckErr(err)
|
||||
prune, _ := cmd.Flags().GetBool("prune")
|
||||
@@ -31,6 +30,6 @@ var forgetCmd = &cobra.Command{
|
||||
func init() {
|
||||
rootCmd.AddCommand(forgetCmd)
|
||||
internal.AddFlagsToCommand(forgetCmd, false)
|
||||
forgetCmd.Flags().Bool("prune", false, "Also prune repository")
|
||||
forgetCmd.Flags().Bool("dry-run", false, "Do not write changes, show what would be affected")
|
||||
forgetCmd.Flags().Bool("prune", false, "also prune repository")
|
||||
forgetCmd.Flags().Bool("dry-run", false, "do not write changes, show what would be affected")
|
||||
}
|
||||
|
24
cmd/root.go
24
cmd/root.go
@@ -2,6 +2,7 @@ package cmd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal"
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
@@ -37,6 +38,7 @@ func init() {
|
||||
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.autorestic.yml or ./.autorestic.yml)")
|
||||
rootCmd.PersistentFlags().BoolVar(&internal.CI, "ci", false, "CI mode disabled interactive mode and colors and enables verbosity")
|
||||
rootCmd.PersistentFlags().BoolVarP(&internal.VERBOSE, "verbose", "v", false, "verbose mode")
|
||||
rootCmd.PersistentFlags().StringVar(&internal.RESTIC_BIN, "restic-bin", "restic", "specify custom restic binary")
|
||||
cobra.OnInitialize(initConfig)
|
||||
}
|
||||
|
||||
@@ -49,13 +51,25 @@ func initConfig() {
|
||||
if cfgFile != "" {
|
||||
viper.SetConfigFile(cfgFile)
|
||||
} else {
|
||||
home, err := homedir.Dir()
|
||||
CheckErr(err)
|
||||
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath(home)
|
||||
|
||||
// Home
|
||||
if home, err := homedir.Dir(); err != nil {
|
||||
viper.AddConfigPath(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")
|
||||
}
|
||||
}
|
||||
viper.AddConfigPath(filepath.Join(prefix, "autorestic"))
|
||||
}
|
||||
|
||||
viper.SetConfigName(".autorestic")
|
||||
}
|
||||
viper.AutomaticEnv()
|
||||
internal.GetConfig()
|
||||
}
|
||||
|
@@ -16,5 +16,5 @@ var uninstallCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(uninstallCmd)
|
||||
uninstallCmd.Flags().Bool("no-restic", false, "Do not uninstall restic.")
|
||||
uninstallCmd.Flags().Bool("no-restic", false, "do not uninstall restic.")
|
||||
}
|
||||
|
@@ -17,5 +17,5 @@ var upgradeCmd = &cobra.Command{
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(upgradeCmd)
|
||||
upgradeCmd.Flags().Bool("no-restic", false, "Also update restic. Default: true")
|
||||
upgradeCmd.Flags().Bool("no-restic", false, "also update restic")
|
||||
}
|
||||
|
3209
docs/.codedoc/package-lock.json
generated
3209
docs/.codedoc/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@codedoc/core": "^0.2.15"
|
||||
"@codedoc/core": "^0.2.23"
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,6 @@
|
||||
[Home](/)
|
||||
[Quick Start](/quick)
|
||||
[Installation](/installation)
|
||||
[Configuration](/config)
|
||||
[Upgrade](/upgrade)
|
||||
|
||||
@@ -7,8 +8,13 @@
|
||||
>
|
||||
> [Overview](/location/overview)
|
||||
> [Hooks](/location/hooks)
|
||||
> [Excluding Files](/location/exclude)
|
||||
> [Forget Policy](/location/forget)
|
||||
>
|
||||
> > :Collapse label=Options
|
||||
> >
|
||||
> > [Overview](/location/options)
|
||||
> > [Excluding Files](/location/exclude)
|
||||
> > [Forget Policy](/location/forget)
|
||||
>
|
||||
> [Cron](/location/cron)
|
||||
> [Docker Volumes](/location/docker)
|
||||
|
||||
@@ -16,12 +22,15 @@
|
||||
>
|
||||
> [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)
|
||||
@@ -29,10 +38,10 @@
|
||||
> [Exec](/cli/exec)
|
||||
> [Install](/cli/install)
|
||||
> [Uninstall](/cli/uninstall)
|
||||
> [Update](/cli/update)
|
||||
> [Upgrade](/cli/upgrade)
|
||||
|
||||
[Examples](/examples)
|
||||
|
||||
[QA](/qa)
|
||||
|
||||
[Community](/community)
|
||||
[Contributors](/contrib)
|
||||
|
||||
|
@@ -19,11 +19,18 @@ backends:
|
||||
backends:
|
||||
name-of-backend:
|
||||
type: b2
|
||||
path: 'myAccount:myBucket/my/path'
|
||||
B2_ACCOUNT_ID: backblaze_account_id
|
||||
B2_ACCOUNT_KEY: backblaze_account_key
|
||||
path: 'bucket_name'
|
||||
# Or With a path
|
||||
# path: 'bucket_name:/some/path'
|
||||
env:
|
||||
B2_ACCOUNT_ID: 'backblaze_keyID'
|
||||
B2_ACCOUNT_KEY: 'backblaze_applicationKey'
|
||||
```
|
||||
|
||||
#### API Keys gotcha
|
||||
|
||||
If you use a _File name prefix_ when making the application key, do not include a leading slash. Make sure to include this prefix in the path (e.g. `path: 'bucket_name:my/path'`).
|
||||
|
||||
## S3 / Minio
|
||||
|
||||
```yaml
|
||||
@@ -33,8 +40,9 @@ backends:
|
||||
path: s3.amazonaws.com/bucket_name
|
||||
# Minio
|
||||
# path: http://localhost:9000/bucket_name
|
||||
AWS_ACCESS_KEY_ID: my_key
|
||||
AWS_SECRET_ACCESS_KEY: my_secret
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: my_key
|
||||
AWS_SECRET_ACCESS_KEY: my_secret
|
||||
```
|
||||
|
||||
## SFTP
|
||||
@@ -57,6 +65,21 @@ backends:
|
||||
name-of-backend:
|
||||
type: rest
|
||||
path: http://localhost:8000/repo_name
|
||||
# Or authenticated
|
||||
path: https://user:pass@host:6969/path
|
||||
```
|
||||
|
||||
Optionally you can set user and password separately
|
||||
|
||||
```yaml
|
||||
backends:
|
||||
rest:
|
||||
type: rest
|
||||
path: http://localhost:6969/path
|
||||
key: ...
|
||||
rest:
|
||||
user: user
|
||||
password: pass
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
||||
|
36
docs/markdown/backend/env.md
Normal file
36
docs/markdown/backend/env.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Environment
|
||||
|
||||
> ⚠ Available since version `v1.3.0`
|
||||
|
||||
Sometimes it's favorable not having the encryption keys in the config files.
|
||||
For that `autorestic` allows passing the backend keys as `ENV` variables, or through an env file.
|
||||
|
||||
The syntax for the `ENV` variables is as follows: `AUTORESTIC_[BACKEND NAME]_KEY`.
|
||||
|
||||
```yaml | autorestic.yaml
|
||||
backend:
|
||||
foo:
|
||||
type: ...
|
||||
path: ...
|
||||
key: secret123 # => AUTORESTIC_FOO_KEY=secret123
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
This means we could remove `key: secret123` from `.autorestic.yaml` and execute as follows:
|
||||
|
||||
```bash
|
||||
AUTORESTIC_FOO_KEY=secret123 autorestic backup ...
|
||||
```
|
||||
|
||||
## Env file
|
||||
|
||||
Alternatively `autorestic` can load an env file, located next to `autorestic.yml` called `.autorestic.env`.
|
||||
|
||||
```| .autorestic.env
|
||||
AUTORESTIC_FOO_KEY=secret123
|
||||
```
|
||||
|
||||
after that you can simply use `autorestic` as your are used to.
|
||||
|
||||
> :ToCPrevNext
|
25
docs/markdown/backend/options.md
Normal file
25
docs/markdown/backend/options.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Options
|
||||
|
||||
For the `backup` and `forget` commands you can pass any native flags to `restic`.
|
||||
|
||||
> It is also possible to set options for an [a specific location](/location/options).
|
||||
|
||||
```yaml
|
||||
backend:
|
||||
foo:
|
||||
type: ...
|
||||
path: ...
|
||||
options:
|
||||
backup:
|
||||
tag:
|
||||
- foo
|
||||
- bar
|
||||
```
|
||||
|
||||
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
|
||||
|
||||
For more detail see the [location docs](/location/options) for options, as they are the same.
|
||||
|
||||
> For flags without arguments you can set them to `true`. They will be handled accordingly.
|
||||
|
||||
> :ToCPrevNext
|
@@ -1,6 +1,6 @@
|
||||
# 💽 Backends
|
||||
|
||||
Backends are the ouputs of the backup process. Each location needs at least one.
|
||||
Backends are the outputs of the backup process. Each location needs at least one.
|
||||
|
||||
```yaml | .autorestic.yml
|
||||
backends:
|
||||
|
17
docs/markdown/cli/completion.md
Normal file
17
docs/markdown/cli/completion.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Completion
|
||||
|
||||
```bash
|
||||
autorestic completion [bash|zsh|fish|powershell]
|
||||
```
|
||||
|
||||
Autorestic can generate shell completions automatically to make the experience even easier.
|
||||
Supported shells are
|
||||
|
||||
- bash
|
||||
- zsh
|
||||
- fish
|
||||
- powershell
|
||||
|
||||
To see how to install run `autorestic help completion` and follow the instructions for your specific shell
|
||||
|
||||
> :ToCPrevNext
|
@@ -1,11 +1,13 @@
|
||||
# Cron
|
||||
|
||||
```bash
|
||||
autorestic cron
|
||||
autorestic cron [--lean]
|
||||
```
|
||||
|
||||
This command is mostly intended to be triggered by an automated system like systemd or crontab.
|
||||
|
||||
It will run cron jobs es [specified in the cron section](/locations/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.
|
||||
|
||||
> :ToCPrevNext
|
||||
|
@@ -4,10 +4,10 @@
|
||||
autorestic exec [-b, --backend] [-a, --all] <command> -- [native options]
|
||||
```
|
||||
|
||||
This is avery handy command which enables you to run any native restic command on desired backends. An example would be listing all the snapshots of all your backends:
|
||||
This is a very handy command which enables you to run any native restic command on desired backends. Generally you will want to include the verbose flag `-v, --verbose` to see the output. An example would be listing all the snapshots of all your backends:
|
||||
|
||||
```bash
|
||||
autorestic exec -a -- snapshots
|
||||
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.
|
||||
|
@@ -4,7 +4,7 @@
|
||||
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/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.
|
||||
|
||||
|
@@ -26,3 +26,13 @@ Verbose mode will show the output of the native restic commands that are otherwi
|
||||
```bash
|
||||
autorestic --verbose backup -a
|
||||
```
|
||||
|
||||
## `--restic-bin`
|
||||
|
||||
With `--restic-bin` you can specify to run a specific restic binary. This can be useful if you want to [create a custom binary with root access that can be executed by any user](https://restic.readthedocs.io/en/stable/080_examples.html#full-backup-without-root).
|
||||
|
||||
```bash
|
||||
autorestic --restic-bin /some/path/to/my/custom/restic/binary
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
||||
|
@@ -1,11 +1,13 @@
|
||||
# Restore
|
||||
|
||||
```bash
|
||||
autorestic restore [-l, --location] [--from backend] [--to <out dir>]
|
||||
autorestic restore [-l, --location] [--from backend] [--to <out dir>] [-f, --force]
|
||||
```
|
||||
|
||||
This will restore all the locations to the selected target. If for one location there are more than one backends specified autorestic will take the first one.
|
||||
|
||||
The `--to` path has to be empty as no data will be overwritten by default. If you are sure you can pass the `-f, --force` flag and the data will be overwritten in the destination. However note that this will overwrite all the data existent in the backup, not only the 1 file that is missing e.g.
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Uninstall
|
||||
|
||||
Installs both restic and autorestic from `/usr/local/bin`.
|
||||
Uninstalls both restic and autorestic from `/usr/local/bin`.
|
||||
|
||||
```bash
|
||||
autorestic uninstall
|
||||
|
@@ -1,11 +0,0 @@
|
||||
# Update
|
||||
|
||||
Autorestic can update itself! Super handy right? Simply run autorestic update and we will check for you if there are updates for restic and autorestic and install them if necessary.
|
||||
|
||||
```bash
|
||||
autorestic update
|
||||
```
|
||||
|
||||
Updates both restic and autorestic automagically.
|
||||
|
||||
> :ToCPrevNext
|
11
docs/markdown/cli/upgrade.md
Normal file
11
docs/markdown/cli/upgrade.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# Upgrade
|
||||
|
||||
Autorestic can upgrade itself! Super handy right? Simply run autorestic upgrade and we will check for you if there are updates for restic and autorestic and install them if necessary.
|
||||
|
||||
```bash
|
||||
autorestic upgrade
|
||||
```
|
||||
|
||||
Updates both restic and autorestic automagically.
|
||||
|
||||
> :ToCPrevNext
|
11
docs/markdown/community.md
Normal file
11
docs/markdown/community.md
Normal file
@@ -0,0 +1,11 @@
|
||||
# 🏘 Community
|
||||
|
||||
A list of community driven projects. (No official affiliation)
|
||||
|
||||
- SystemD Units: https://gitlab.com/py_crash/autorestic-systemd-units
|
||||
- Docker image: https://github.com/pascaliske/docker-autorestic
|
||||
- Ansible Role: https://github.com/adsanz/ansible-restic-role
|
||||
- Ansible Role: https://github.com/ItsNotGoodName/ansible-role-autorestic
|
||||
- Ansible Role: https://github.com/FuzzyMistborn/ansible-role-autorestic
|
||||
|
||||
> :ToCPrevNext
|
@@ -31,12 +31,53 @@ backends:
|
||||
remote:
|
||||
type: b2
|
||||
path: 'myBucket:backup/home'
|
||||
B2_ACCOUNT_ID: account_id
|
||||
B2_ACCOUNT_KEY: account_key
|
||||
env:
|
||||
B2_ACCOUNT_ID: account_id
|
||||
B2_ACCOUNT_KEY: account_key
|
||||
|
||||
hdd:
|
||||
type: local
|
||||
path: /mnt/my_external_storage
|
||||
```
|
||||
|
||||
## Aliases
|
||||
|
||||
A handy tool for more advanced configurations is to use yaml aliases.
|
||||
These must be specified under the global `extras` key in the `.autorestic.yml` config file.
|
||||
Aliases allow to reuse snippets of config throughout the same file.
|
||||
|
||||
The following example shows how the locations `a` and `b` share the same hooks and forget policies.
|
||||
|
||||
```yaml | .autorestic.yml
|
||||
extras:
|
||||
hooks: &foo
|
||||
before:
|
||||
- echo "Hello"
|
||||
after:
|
||||
- echo "kthxbye"
|
||||
policies: &bar
|
||||
keep-daily: 14
|
||||
keep-weekly: 52
|
||||
|
||||
backends:
|
||||
# ...
|
||||
locations:
|
||||
a:
|
||||
from: /data/a
|
||||
to: some
|
||||
hooks:
|
||||
<<: *foo
|
||||
options:
|
||||
forget:
|
||||
<<: *bar
|
||||
b:
|
||||
from: data/b
|
||||
to: some
|
||||
hooks:
|
||||
<<: *foo
|
||||
options:
|
||||
forget:
|
||||
<<: *bar
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
||||
|
@@ -2,7 +2,16 @@
|
||||
|
||||
This amazing people helped the project!
|
||||
|
||||
- @ChanceM [Docs]
|
||||
- @EliotBerriot [Docs, Pruning, S3]
|
||||
- @agateblue - Docs, Pruning, S3
|
||||
- @david-boles - Docs
|
||||
- @SebDanielsson - Brew
|
||||
- @n194 - AUR Package
|
||||
- @jin-park-dev - Typos
|
||||
- @sumnerboy12 - Typos
|
||||
- @FuzzyMistborn - Typos
|
||||
- @ChanceM - Typos
|
||||
- @TheForcer - Typos
|
||||
- @themorlan - Typos
|
||||
- @somebox - Typos
|
||||
|
||||
> :ToCPrevNext
|
||||
|
@@ -3,12 +3,12 @@
|
||||
## List all the snapshots for all the backends
|
||||
|
||||
```bash
|
||||
autorestic exec -a -- snapshots
|
||||
autorestic exec -av -- snapshots
|
||||
```
|
||||
|
||||
## Unlock a locked repository
|
||||
|
||||
This can come in handy if a backup process crashed or if it was accidentally cancelled. Then the repository would still be locked without an actual process using it. Only do this if you know what you are sure no other process is actually reading/writing to the repository of course.
|
||||
This can come in handy if a backup process crashed or if it was accidentally cancelled. Then the repository would still be locked without an actual process using it. Only do this if you know what you are doing and are sure no other process is actually reading/writing to the repository of course.
|
||||
|
||||
```bash
|
||||
autorestic exec -b my-backend -- unlock
|
||||
|
@@ -8,4 +8,18 @@ Autorestic requires `curl`, `wget` and `bzip2` to be installed. For most systems
|
||||
curl -s https://raw.githubusercontent.com/CupCakeArmy/autorestic/master/install.sh | bash
|
||||
```
|
||||
|
||||
## Alternatives
|
||||
|
||||
### Manual
|
||||
|
||||
You can download the right binary from the release page and simply copy it to `/usr/local/bin` or whatever path you prefer. Autoupdates will still work.
|
||||
|
||||
### Brew
|
||||
|
||||
If you are on macOS you can install through brew: `brew install autorestic`.
|
||||
|
||||
### AUR
|
||||
|
||||
If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/) by @n194.
|
||||
|
||||
> :ToCPrevNext
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Cron
|
||||
|
||||
Often it is usefull to trigger backups autmatically. 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
|
||||
locations:
|
||||
@@ -14,11 +14,11 @@ Here is a awesome website with [some examples](https://crontab.guru/examples.htm
|
||||
|
||||
## Installing the cron
|
||||
|
||||
**This has to be done only once, regadless of now many cros 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 shedule.
|
||||
Note that the shedule has nothing to do with the `cron` attribute in each location.
|
||||
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 shedule it once a day.
|
||||
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.
|
||||
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
|
||||
|
||||
@@ -30,18 +30,24 @@ First, open your crontab in edit mode
|
||||
crontab -e
|
||||
```
|
||||
|
||||
Then paste this at the bottom of the file and save it. Note that in this specific example the `.autorestic.yml` is located in `/srv/`. You need to modify that part of course to fit your config file.
|
||||
Then paste this at the bottom of the file and save it. Note that in this specific example the config file is located at one of the default locations (e.g. `~/.autorestic.yml`). If your config is somewhere else you'll need to specify it using the `-c` option.
|
||||
|
||||
```bash
|
||||
# This is required, as it otherwise cannot find restic as a command.
|
||||
PATH="/usr/local/bin:/usr/bin:/bin"
|
||||
|
||||
# Example running every 5 minutes
|
||||
*/5 * * * * autorestic -c /srv/.autorestic.yml --ci cron
|
||||
*/5 * * * * autorestic -c /path/to/my/.autorestic.yml --ci cron
|
||||
```
|
||||
|
||||
> The `--ci` option is not required, but recommended
|
||||
|
||||
To debug a cron job you can use
|
||||
|
||||
```bash
|
||||
*/5 * * * * autorestic -c /path/to/my/.autorestic.yml --ci cron > /tmp/autorestic.log 2>&1
|
||||
```
|
||||
|
||||
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 willingly not linked.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Excluding files
|
||||
|
||||
If you want to exclude certain files or folders it done easily by specifiyng the right flags in the location you desire to filter.
|
||||
If you want to exclude certain files or folders it done easily by specifying the right flags in the location you desire to filter.
|
||||
|
||||
The flags are taken straight from the [restic cli exclude rules](https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files) so you can use any flag used there.
|
||||
|
||||
|
@@ -14,11 +14,11 @@ locations:
|
||||
options:
|
||||
forget:
|
||||
keep-last: 5 # always keep at least 5 snapshots
|
||||
keep-hourly: 3 # keep 3 last hourly shapshots
|
||||
keep-daily: 4 # keep 4 last daily shapshots
|
||||
keep-weekly: 1 # keep 1 last weekly shapshots
|
||||
keep-monthly: 12 # keep 12 last monthly shapshots
|
||||
keep-yearly: 7 # keep 7 last yearly shapshots
|
||||
keep-hourly: 3 # keep 3 last hourly snapshots
|
||||
keep-daily: 4 # keep 4 last daily snapshots
|
||||
keep-weekly: 1 # keep 1 last weekly snapshots
|
||||
keep-monthly: 12 # keep 12 last monthly snapshots
|
||||
keep-yearly: 7 # keep 7 last yearly snapshots
|
||||
keep-within: '2w' # keep snapshots from the last 2 weeks
|
||||
```
|
||||
|
||||
|
@@ -2,7 +2,14 @@
|
||||
|
||||
If you want to perform some commands before and/or after a backup, you can use hooks.
|
||||
|
||||
They consist of a list of `before`/`after` 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 target `from`.
|
||||
|
||||
The following hooks groups are supported, none are required:
|
||||
|
||||
- `before`
|
||||
- `after`
|
||||
- `failure`
|
||||
- `success`
|
||||
|
||||
```yml | .autorestic.yml
|
||||
locations:
|
||||
@@ -11,10 +18,63 @@ locations:
|
||||
to: my-backend
|
||||
hooks:
|
||||
before:
|
||||
- echo "Hello"
|
||||
- echo "Human"
|
||||
- echo "One"
|
||||
- echo "Two"
|
||||
- echo "Three"
|
||||
after:
|
||||
- echo "kthxbye"
|
||||
- echo "Byte"
|
||||
failure:
|
||||
- echo "Something went wrong"
|
||||
success:
|
||||
- echo "Well done!"
|
||||
```
|
||||
|
||||
## Flowchart
|
||||
|
||||
1. `before` hook
|
||||
2. Run backup
|
||||
3. `after` hook
|
||||
4. - `success` hook if no errors were found
|
||||
- `failure` hook if at least one error was encountered
|
||||
|
||||
If the `before` hook encounters errors the backup and `after` hooks will be skipped and only the `failed` hooks will run.
|
||||
|
||||
## Environment variables
|
||||
|
||||
All hooks are exposed to the `AUTORESTIC_LOCATION` environment variable, which contains the location name.
|
||||
|
||||
The `after` and `success` hooks have access to additional information with the following syntax:
|
||||
|
||||
```bash
|
||||
AUTORESTIC_[TYPE]_[I]
|
||||
AUTORESTIC_[TYPE]_[BACKEND_NAME]
|
||||
```
|
||||
|
||||
Every type of metadata is appended with both the name of the backend associated with and the number in which the backends where executed.
|
||||
|
||||
### Available Metadata Types
|
||||
|
||||
- `SNAPSHOT_ID`
|
||||
- `PARENT_SNAPSHOT_ID`
|
||||
- `FILES_ADDED`
|
||||
- `FILES_CHANGED`
|
||||
- `FILES_UNMODIFIED`
|
||||
- `DIRS_ADDED`
|
||||
- `DIRS_CHANGED`
|
||||
- `DIRS_UNMODIFIED`
|
||||
- `ADDED_SIZE`
|
||||
- `PROCESSED_FILES`
|
||||
- `PROCESSED_SIZE`
|
||||
- `PROCESSED_DURATION`
|
||||
|
||||
#### Example
|
||||
|
||||
Assuming you have a location `bar` that backs up to a single backend named `foo` you could expect the following env variables:
|
||||
|
||||
```bash
|
||||
AUTORESTIC_LOCATION=bar
|
||||
AUTORESTIC_FILES_ADDED_0=42
|
||||
AUTORESTIC_FILES_ADDED_FOO=42
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
||||
|
23
docs/markdown/location/options.md
Normal file
23
docs/markdown/location/options.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Options
|
||||
|
||||
For the `backup` and `forget` commands you can pass any native flags to `restic`.
|
||||
|
||||
> It is also possible to set options for an [entire backend](/backend/options).
|
||||
|
||||
```yaml
|
||||
locations:
|
||||
foo:
|
||||
path: ...
|
||||
to: ...
|
||||
options:
|
||||
backup:
|
||||
tag:
|
||||
- foo
|
||||
- bar
|
||||
```
|
||||
|
||||
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
|
||||
|
||||
> For flags without arguments you can set them to `true`. They will be handled accordingly.
|
||||
|
||||
> :ToCPrevNext
|
@@ -22,6 +22,6 @@ Paths can be absolute or relative. If relative they are resolved relative to the
|
||||
|
||||
## `to`
|
||||
|
||||
This is einther 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
|
||||
|
@@ -6,16 +6,18 @@
|
||||
curl -s https://raw.githubusercontent.com/CupCakeArmy/autorestic/master/install.sh | bash
|
||||
```
|
||||
|
||||
See [installation](/installation) for alternative options.
|
||||
|
||||
## Write a simple config file
|
||||
|
||||
```bash
|
||||
vim .autorestic.yml
|
||||
vim ~/.autorestic.yml
|
||||
```
|
||||
|
||||
For a quick overview:
|
||||
|
||||
- `locations` can be seen as the inputs and `backends` the output where the data is stored and backed up.
|
||||
- One `location` can have one or multiple `backends` for redudancy.
|
||||
- One `location` can have one or multiple `backends` for redundancy.
|
||||
- One `backend` can also be the target for multiple `locations`.
|
||||
|
||||
> **⚠️ WARNING ⚠️**
|
||||
@@ -24,25 +26,26 @@ For a quick overview:
|
||||
|
||||
```yaml | .autorestic.yml
|
||||
locations:
|
||||
- name: home
|
||||
home:
|
||||
from: /home/me
|
||||
to: remote
|
||||
|
||||
- name: important
|
||||
important:
|
||||
from: /path/to/important/stuff
|
||||
to:
|
||||
- remote
|
||||
- hdd
|
||||
|
||||
backends:
|
||||
- name: remote
|
||||
remote:
|
||||
type: s3
|
||||
path: 's3.amazonaws.com/bucket_name'
|
||||
key: some-random-password-198rc79r8y1029c8yfewj8f1u0ef87yh198uoieufy
|
||||
AWS_ACCESS_KEY_ID: account_id
|
||||
AWS_SECRET_ACCESS_KEY: account_key
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: account_id
|
||||
AWS_SECRET_ACCESS_KEY: account_key
|
||||
|
||||
- name: hdd
|
||||
hdd:
|
||||
type: local
|
||||
path: /mnt/my_external_storage
|
||||
key: 'if not key is set it will be generated for you'
|
||||
@@ -51,7 +54,7 @@ backends:
|
||||
## Check
|
||||
|
||||
```bash
|
||||
autorestic check -a
|
||||
autorestic check
|
||||
```
|
||||
|
||||
This checks if the config file has any issues. If this is the first time this can take longer as autorestic will setup the backends.
|
||||
|
@@ -1,4 +1,4 @@
|
||||
# Update
|
||||
# Upgrade
|
||||
|
||||
## From `0.x` to `1.0`
|
||||
|
||||
|
878
docs/package-lock.json
generated
878
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
1
go.mod
1
go.mod
@@ -6,6 +6,7 @@ require (
|
||||
github.com/blang/semver/v4 v4.0.0
|
||||
github.com/buger/goterm v1.0.0
|
||||
github.com/fatih/color v1.10.0
|
||||
github.com/joho/godotenv v1.4.0
|
||||
github.com/mitchellh/go-homedir v1.1.0
|
||||
github.com/robfig/cron v1.2.0
|
||||
github.com/spf13/cobra v1.1.3
|
||||
|
2
go.sum
2
go.sum
@@ -101,6 +101,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
|
||||
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
|
||||
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
|
||||
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
|
||||
|
@@ -16,11 +16,15 @@ else
|
||||
fi
|
||||
echo $OS
|
||||
|
||||
NATIVE_ARCH=$(uname -m)
|
||||
NATIVE_ARCH=$(uname -m | tr '[:upper:]' '[:lower:]')
|
||||
if [[ $NATIVE_ARCH == *"x86_64"* ]]; then
|
||||
ARCH=amd64
|
||||
elif [[ $NATIVE_ARCH == *"arm64"* || $NATIVE_ARCH == *"aarch64"* ]]; then
|
||||
ARCH=arm64
|
||||
elif [[ $NATIVE_ARCH == *"x86"* ]]; then
|
||||
ARCH=386
|
||||
elif [[ $NATIVE_ARCH == *"armv7"* ]]; then
|
||||
ARCH=arm
|
||||
else
|
||||
echo "Could not determine Architecure automatically, please check the release page manually: https://github.com/cupcakearmy/autorestic/releases"
|
||||
exit 1
|
||||
@@ -36,4 +40,4 @@ bzip2 -fd "${OUT_FILE}.bz2"
|
||||
chmod +x ${OUT_FILE}
|
||||
|
||||
autorestic install
|
||||
echo "Succefsully installed autorestic"
|
||||
echo "Successfully installed autorestic"
|
||||
|
@@ -4,19 +4,26 @@ import (
|
||||
"crypto/rand"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type BackendRest struct {
|
||||
User string `yaml:"user,omitempty"`
|
||||
Password string `yaml:"password,omitempty"`
|
||||
}
|
||||
|
||||
type Backend struct {
|
||||
name string
|
||||
Type string `mapstructure:"type,omitempty"`
|
||||
Path string `mapstructure:"path,omitempty"`
|
||||
Key string `mapstructure:"key,omitempty"`
|
||||
Env map[string]string `mapstructure:"env,omitempty"`
|
||||
name string
|
||||
Type string `yaml:"type,omitempty"`
|
||||
Path string `yaml:"path,omitempty"`
|
||||
Key string `yaml:"key,omitempty"`
|
||||
Env map[string]string `yaml:"env,omitempty"`
|
||||
Rest BackendRest `yaml:"rest,omitempty"`
|
||||
Options Options `yaml:"options,omitempty"`
|
||||
}
|
||||
|
||||
func GetBackend(name string) (Backend, bool) {
|
||||
@@ -29,7 +36,20 @@ func (b Backend) generateRepo() (string, error) {
|
||||
switch b.Type {
|
||||
case "local":
|
||||
return GetPathRelativeToConfig(b.Path)
|
||||
case "b2", "azure", "gs", "s3", "sftp", "rest":
|
||||
case "rest":
|
||||
parsed, err := url.Parse(b.Path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if b.Rest.User != "" {
|
||||
if b.Rest.Password == "" {
|
||||
parsed.User = url.User(b.Rest.User)
|
||||
} else {
|
||||
parsed.User = url.UserPassword(b.Rest.User, b.Rest.Password)
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", b.Type, parsed.String()), nil
|
||||
case "b2", "azure", "gs", "s3", "sftp", "rclone":
|
||||
return fmt.Sprintf("%s:%s", b.Type, b.Path), nil
|
||||
default:
|
||||
return "", fmt.Errorf("backend type \"%s\" is invalid", b.Type)
|
||||
@@ -38,7 +58,15 @@ func (b Backend) generateRepo() (string, error) {
|
||||
|
||||
func (b Backend) getEnv() (map[string]string, error) {
|
||||
env := make(map[string]string)
|
||||
env["RESTIC_PASSWORD"] = b.Key
|
||||
if b.Key != "" {
|
||||
env["RESTIC_PASSWORD"] = b.Key
|
||||
} else {
|
||||
key, err := b.getKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
env["RESTIC_PASSWORD"] = key
|
||||
}
|
||||
repo, err := b.generateRepo()
|
||||
env["RESTIC_REPOSITORY"] = repo
|
||||
for key, value := range b.Env {
|
||||
@@ -57,6 +85,17 @@ func generateRandomKey() string {
|
||||
return key
|
||||
}
|
||||
|
||||
func (b Backend) getKey() (string, error) {
|
||||
if b.Key != "" {
|
||||
return b.Key, nil
|
||||
}
|
||||
keyName := "AUTORESTIC_" + strings.ToUpper(b.name) + "_KEY"
|
||||
if key, found := os.LookupEnv(keyName); found {
|
||||
return key, nil
|
||||
}
|
||||
return "", fmt.Errorf("no key found for backend \"%s\"", b.name)
|
||||
}
|
||||
|
||||
func (b Backend) validate() error {
|
||||
if b.Type == "" {
|
||||
return fmt.Errorf(`Backend "%s" has no "type"`, b.name)
|
||||
@@ -65,20 +104,19 @@ func (b Backend) validate() error {
|
||||
return fmt.Errorf(`Backend "%s" has no "path"`, b.name)
|
||||
}
|
||||
if b.Key == "" {
|
||||
key := generateRandomKey()
|
||||
b.Key = key
|
||||
c := GetConfig()
|
||||
tmp := c.Backends[b.name]
|
||||
tmp.Key = key
|
||||
tmp.name = ""
|
||||
c.Backends[b.name] = tmp
|
||||
file := viper.ConfigFileUsed()
|
||||
if err := CopyFile(file, file+".old"); err != nil {
|
||||
return err
|
||||
// Check if key is set in environment
|
||||
if _, err := b.getKey(); err != nil {
|
||||
// If not generate a new one
|
||||
key := generateRandomKey()
|
||||
b.Key = key
|
||||
c := GetConfig()
|
||||
tmp := c.Backends[b.name]
|
||||
tmp.Key = key
|
||||
c.Backends[b.name] = tmp
|
||||
if err := c.SaveConfig(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
colors.Secondary.Println("Saved a backup copy of your file next the the original.")
|
||||
viper.Set("backends", c.Backends)
|
||||
viper.WriteConfig()
|
||||
}
|
||||
env, err := b.getEnv()
|
||||
if err != nil {
|
||||
@@ -107,16 +145,20 @@ func (b Backend) Exec(args []string) error {
|
||||
}
|
||||
options := ExecuteOptions{Envs: env}
|
||||
out, err := ExecuteResticCommand(options, args...)
|
||||
if err != nil {
|
||||
colors.Error.Println(out)
|
||||
return err
|
||||
}
|
||||
if VERBOSE {
|
||||
colors.Faint.Println(out)
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b Backend) ExecDocker(l Location, args []string) error {
|
||||
func (b Backend) ExecDocker(l Location, args []string) (string, error) {
|
||||
env, err := b.getEnv()
|
||||
if err != nil {
|
||||
return err
|
||||
return "", err
|
||||
}
|
||||
volume := l.getVolumeName()
|
||||
path, _ := l.getPath()
|
||||
@@ -143,8 +185,5 @@ func (b Backend) ExecDocker(l Location, args []string) error {
|
||||
}
|
||||
docker = append(docker, "restic/restic", "-c", "restic "+strings.Join(args, " "))
|
||||
out, err := ExecuteCommand(options, docker...)
|
||||
if VERBOSE {
|
||||
colors.Faint.Println(out)
|
||||
}
|
||||
return err
|
||||
return out, err
|
||||
}
|
||||
|
@@ -72,14 +72,21 @@ func downloadAndInstallAsset(body GithubRelease, name string) error {
|
||||
// Uncompress
|
||||
bz := bzip2.NewReader(resp.Body)
|
||||
|
||||
// Save binary
|
||||
file, err := os.Create(path.Join(INSTALL_PATH, name))
|
||||
// Save to tmp
|
||||
// Linux does not support overwriting the file that is currently being overwritten, but it can be deleted and a new one moved in its place.
|
||||
tmp, err := ioutil.TempFile(os.TempDir(), "autorestic-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file.Chmod(0755)
|
||||
defer file.Close()
|
||||
io.Copy(file, bz)
|
||||
defer tmp.Close()
|
||||
tmp.Chmod(0755)
|
||||
io.Copy(tmp, bz)
|
||||
|
||||
to := path.Join(INSTALL_PATH, name)
|
||||
os.Remove(to) // Delete if current, ignore error if file does not exits.
|
||||
if err := os.Rename(tmp.Name(), to); err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
colors.Success.Printf("Successfully installed '%s' under %s\n", name, INSTALL_PATH)
|
||||
return nil
|
||||
@@ -113,7 +120,9 @@ func upgradeRestic() error {
|
||||
func Upgrade(restic bool) error {
|
||||
// Upgrade restic
|
||||
if restic {
|
||||
InstallRestic()
|
||||
if err := InstallRestic(); err != nil {
|
||||
colors.Error.Println(err)
|
||||
}
|
||||
upgradeRestic()
|
||||
}
|
||||
|
||||
|
@@ -2,24 +2,30 @@ package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const VERSION = "1.0.1"
|
||||
const VERSION = "1.3.0"
|
||||
|
||||
var CI bool = false
|
||||
var VERBOSE bool = false
|
||||
var CRON_LEAN bool = false
|
||||
|
||||
type Config struct {
|
||||
Locations map[string]Location `mapstructure:"locations"`
|
||||
Backends map[string]Backend `mapstructure:"backends"`
|
||||
Extras interface{} `yaml:"extras"`
|
||||
Locations map[string]Location `yaml:"locations"`
|
||||
Backends map[string]Backend `yaml:"backends"`
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
@@ -29,14 +35,25 @@ func GetConfig() *Config {
|
||||
if config == nil {
|
||||
once.Do(func() {
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
colors.Faint.Println("Using config file:", viper.ConfigFileUsed())
|
||||
if !CRON_LEAN {
|
||||
absConfig, _ := filepath.Abs(viper.ConfigFileUsed())
|
||||
colors.Faint.Println("Using config: \t", absConfig)
|
||||
// Load env file
|
||||
envFile := filepath.Join(filepath.Dir(absConfig), ".autorestic.env")
|
||||
err = godotenv.Load(envFile)
|
||||
if err == nil {
|
||||
colors.Faint.Println("Using env:\t", envFile)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
|
||||
config = &Config{}
|
||||
if err := viper.UnmarshalExact(config); err != nil {
|
||||
panic(err)
|
||||
colors.Error.Println("Could not parse config file!")
|
||||
lock.Unlock()
|
||||
os.Exit(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -72,21 +89,22 @@ func (c *Config) Describe() {
|
||||
colors.PrintDescription("Cron", l.Cron)
|
||||
}
|
||||
|
||||
after, before := len(l.Hooks.After), len(l.Hooks.Before)
|
||||
if after+before > 0 {
|
||||
tmp = ""
|
||||
if before > 0 {
|
||||
tmp += "\tBefore"
|
||||
for _, cmd := range l.Hooks.Before {
|
||||
tmp += colors.Faint.Sprintf("\n\t ▶ %s", cmd)
|
||||
}
|
||||
}
|
||||
if after > 0 {
|
||||
tmp += "\n\tAfter"
|
||||
for _, cmd := range l.Hooks.After {
|
||||
tmp = ""
|
||||
hooks := map[string][]string{
|
||||
"Before": l.Hooks.Before,
|
||||
"After": l.Hooks.After,
|
||||
"Failure": l.Hooks.Failure,
|
||||
"Success": l.Hooks.Success,
|
||||
}
|
||||
for hook, commands := range hooks {
|
||||
if len(commands) > 0 {
|
||||
tmp += "\n\t" + hook
|
||||
for _, cmd := range commands {
|
||||
tmp += colors.Faint.Sprintf("\n\t ▶ %s", cmd)
|
||||
}
|
||||
}
|
||||
}
|
||||
if tmp != "" {
|
||||
colors.PrintDescription("Hooks", tmp)
|
||||
}
|
||||
|
||||
@@ -126,7 +144,7 @@ func CheckConfig() error {
|
||||
return fmt.Errorf("config could not be loaded/found")
|
||||
}
|
||||
if !CheckIfResticIsCallable() {
|
||||
return fmt.Errorf(`restic was not found. Install either with "autorestic install" or manually`)
|
||||
return fmt.Errorf(`%s was not found. Install either with "autorestic install" or manually`, RESTIC_BIN)
|
||||
}
|
||||
for name, backend := range c.Backends {
|
||||
backend.name = name
|
||||
@@ -136,7 +154,7 @@ func CheckConfig() error {
|
||||
}
|
||||
for name, location := range c.Locations {
|
||||
location.name = name
|
||||
if err := location.validate(c); err != nil {
|
||||
if err := location.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -190,10 +208,50 @@ func GetAllOrSelected(cmd *cobra.Command, backends bool) ([]string, error) {
|
||||
}
|
||||
|
||||
func AddFlagsToCommand(cmd *cobra.Command, backend bool) {
|
||||
cmd.PersistentFlags().BoolP("all", "a", false, "Backup all locations")
|
||||
var usage string
|
||||
if backend {
|
||||
cmd.PersistentFlags().StringSliceP("backend", "b", []string{}, "backends")
|
||||
usage = "all backends"
|
||||
} else {
|
||||
cmd.PersistentFlags().StringSliceP("location", "l", []string{}, "Locations")
|
||||
usage = "all locations"
|
||||
}
|
||||
cmd.PersistentFlags().BoolP("all", "a", false, usage)
|
||||
if backend {
|
||||
cmd.PersistentFlags().StringSliceP("backend", "b", []string{}, "select backends")
|
||||
} else {
|
||||
cmd.PersistentFlags().StringSliceP("location", "l", []string{}, "select locations")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Config) SaveConfig() error {
|
||||
file := viper.ConfigFileUsed()
|
||||
if err := CopyFile(file, file+".old"); err != nil {
|
||||
return err
|
||||
}
|
||||
colors.Secondary.Println("Saved a backup copy of your file next the the original.")
|
||||
|
||||
viper.Set("backends", c.Backends)
|
||||
viper.Set("locations", c.Locations)
|
||||
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
func getOptions(options Options, key string) []string {
|
||||
var selected []string
|
||||
for k, values := range options[key] {
|
||||
for _, value := range values {
|
||||
// Bool
|
||||
asBool, ok := value.(bool)
|
||||
if ok && asBool {
|
||||
selected = append(selected, fmt.Sprintf("--%s", k))
|
||||
continue
|
||||
}
|
||||
// String
|
||||
asString, ok := value.(string)
|
||||
if ok {
|
||||
selected = append(selected, fmt.Sprintf("--%s", k), asString)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ import (
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||
"github.com/cupcakearmy/autorestic/internal/metadata"
|
||||
"github.com/robfig/cron"
|
||||
)
|
||||
|
||||
@@ -24,19 +25,21 @@ const (
|
||||
type HookArray = []string
|
||||
|
||||
type Hooks struct {
|
||||
Before HookArray `mapstructure:"before"`
|
||||
After HookArray `mapstructure:"after"`
|
||||
Before HookArray `yaml:"before,omitempty"`
|
||||
After HookArray `yaml:"after,omitempty"`
|
||||
Success HookArray `yaml:"success,omitempty"`
|
||||
Failure HookArray `yaml:"failure,omitempty"`
|
||||
}
|
||||
|
||||
type Options map[string]map[string][]string
|
||||
type Options map[string]map[string][]interface{}
|
||||
|
||||
type Location struct {
|
||||
name string `mapstructure:",omitempty"`
|
||||
From string `mapstructure:"from,omitempty"`
|
||||
To []string `mapstructure:"to,omitempty"`
|
||||
Hooks Hooks `mapstructure:"hooks,omitempty"`
|
||||
Cron string `mapstructure:"cron,omitempty"`
|
||||
Options Options `mapstructure:"options,omitempty"`
|
||||
name string `yaml:",omitempty"`
|
||||
From string `yaml:"from,omitempty"`
|
||||
To []string `yaml:"to,omitempty"`
|
||||
Hooks Hooks `yaml:"hooks,omitempty"`
|
||||
Cron string `yaml:"cron,omitempty"`
|
||||
Options Options `yaml:"options,omitempty"`
|
||||
}
|
||||
|
||||
func GetLocation(name string) (Location, bool) {
|
||||
@@ -45,10 +48,24 @@ func GetLocation(name string) (Location, bool) {
|
||||
return l, ok
|
||||
}
|
||||
|
||||
func (l Location) validate(c *Config) error {
|
||||
func (l Location) validate() error {
|
||||
if l.From == "" {
|
||||
return fmt.Errorf(`Location "%s" is missing "from" key`, l.name)
|
||||
}
|
||||
if l.getType() == TypeLocal {
|
||||
if from, err := GetPathRelativeToConfig(l.From); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if stat, err := os.Stat(from); err != nil {
|
||||
return err
|
||||
} else {
|
||||
if !stat.IsDir() {
|
||||
return fmt.Errorf("\"%s\" is not valid directory for location \"%s\"", from, l.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(l.To) == 0 {
|
||||
return fmt.Errorf(`Location "%s" has no "to" targets`, l.name)
|
||||
}
|
||||
@@ -62,17 +79,6 @@ func (l Location) validate(c *Config) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l Location) getOptions(key string) []string {
|
||||
var options []string
|
||||
saved := l.Options[key]
|
||||
for k, values := range saved {
|
||||
for _, value := range values {
|
||||
options = append(options, fmt.Sprintf("--%s", k), value)
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func ExecuteHooks(commands []string, options ExecuteOptions) error {
|
||||
if len(commands) == 0 {
|
||||
return nil
|
||||
@@ -81,12 +87,13 @@ func ExecuteHooks(commands []string, options ExecuteOptions) error {
|
||||
for _, command := range commands {
|
||||
colors.Body.Println("> " + command)
|
||||
out, err := ExecuteCommand(options, "-c", command)
|
||||
if err != nil {
|
||||
colors.Error.Println(out)
|
||||
return err
|
||||
}
|
||||
if VERBOSE {
|
||||
colors.Faint.Println(out)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
colors.Body.Println("")
|
||||
return nil
|
||||
@@ -118,35 +125,53 @@ func (l Location) getPath() (string, error) {
|
||||
return "", fmt.Errorf("could not get path for location \"%s\"", l.name)
|
||||
}
|
||||
|
||||
func (l Location) Backup() error {
|
||||
func (l Location) Backup(cron bool) []error {
|
||||
var errors []error
|
||||
colors.PrimaryPrint(" Backing up location \"%s\" ", l.name)
|
||||
t := l.getType()
|
||||
options := ExecuteOptions{
|
||||
Command: "bash",
|
||||
Envs: map[string]string{
|
||||
"AUTORESTIC_LOCATION": l.name,
|
||||
},
|
||||
}
|
||||
|
||||
if err := l.validate(); err != nil {
|
||||
errors = append(errors, err)
|
||||
colors.Error.Print(err)
|
||||
goto after
|
||||
}
|
||||
|
||||
if t == TypeLocal {
|
||||
dir, _ := GetPathRelativeToConfig(l.From)
|
||||
colors.Faint.Printf("Executing under: \"%s\"\n", dir)
|
||||
options.Dir = dir
|
||||
}
|
||||
|
||||
// Hooks
|
||||
if err := ExecuteHooks(l.Hooks.Before, options); err != nil {
|
||||
return err
|
||||
errors = append(errors, err)
|
||||
goto after
|
||||
}
|
||||
|
||||
// Backup
|
||||
for _, to := range l.To {
|
||||
for i, to := range l.To {
|
||||
backend, _ := GetBackend(to)
|
||||
colors.Secondary.Printf("Backend: %s\n", backend.name)
|
||||
env, err := backend.getEnv()
|
||||
if err != nil {
|
||||
return nil
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
flags := l.getOptions("backup")
|
||||
lFlags := getOptions(l.Options, "backup")
|
||||
bFlags := getOptions(backend.Options, "backup")
|
||||
cmd := []string{"backup"}
|
||||
cmd = append(cmd, flags...)
|
||||
cmd = append(cmd, lFlags...)
|
||||
cmd = append(cmd, bFlags...)
|
||||
if cron {
|
||||
cmd = append(cmd, "--tag", "cron")
|
||||
}
|
||||
cmd = append(cmd, ".")
|
||||
backupOptions := ExecuteOptions{
|
||||
Dir: options.Dir,
|
||||
@@ -158,24 +183,44 @@ func (l Location) Backup() error {
|
||||
switch t {
|
||||
case TypeLocal:
|
||||
out, err = ExecuteResticCommand(backupOptions, cmd...)
|
||||
if VERBOSE {
|
||||
colors.Faint.Println(out)
|
||||
}
|
||||
case TypeVolume:
|
||||
err = backend.ExecDocker(l, cmd)
|
||||
out, err = backend.ExecDocker(l, cmd)
|
||||
}
|
||||
if err != nil {
|
||||
colors.Error.Println(out)
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
md := metadata.ExtractMetadataFromBackupLog(out)
|
||||
mdEnv := metadata.MakeEnvFromMetadata(&md)
|
||||
for k, v := range mdEnv {
|
||||
options.Envs[k+"_"+fmt.Sprint(i)] = v
|
||||
options.Envs[k+"_"+strings.ToUpper(backend.name)] = v
|
||||
}
|
||||
if VERBOSE {
|
||||
colors.Faint.Println(out)
|
||||
}
|
||||
}
|
||||
|
||||
// After hooks
|
||||
if err := ExecuteHooks(l.Hooks.After, options); err != nil {
|
||||
return err
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
after:
|
||||
var commands []string
|
||||
if len(errors) > 0 {
|
||||
commands = l.Hooks.Failure
|
||||
} else {
|
||||
commands = l.Hooks.Success
|
||||
}
|
||||
if err := ExecuteHooks(commands, options); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
colors.Success.Println("Done")
|
||||
return nil
|
||||
return errors
|
||||
}
|
||||
|
||||
func (l Location) Forget(prune bool, dry bool) error {
|
||||
@@ -196,7 +241,8 @@ func (l Location) Forget(prune bool, dry bool) error {
|
||||
options := ExecuteOptions{
|
||||
Envs: env,
|
||||
}
|
||||
flags := l.getOptions("forget")
|
||||
lFlags := getOptions(l.Options, "forget")
|
||||
bFlags := getOptions(backend.Options, "forget")
|
||||
cmd := []string{"forget", "--path", path}
|
||||
if prune {
|
||||
cmd = append(cmd, "--prune")
|
||||
@@ -204,7 +250,8 @@ func (l Location) Forget(prune bool, dry bool) error {
|
||||
if dry {
|
||||
cmd = append(cmd, "--dry-run")
|
||||
}
|
||||
cmd = append(cmd, flags...)
|
||||
cmd = append(cmd, lFlags...)
|
||||
cmd = append(cmd, bFlags...)
|
||||
out, err := ExecuteResticCommand(options, cmd...)
|
||||
if VERBOSE {
|
||||
colors.Faint.Println(out)
|
||||
@@ -268,7 +315,7 @@ func (l Location) Restore(to, from string, force bool) error {
|
||||
}
|
||||
err = backend.Exec([]string{"restore", "--target", to, "--path", path, "latest"})
|
||||
case TypeVolume:
|
||||
err = backend.ExecDocker(l, []string{"restore", "--target", ".", "--path", path, "latest"})
|
||||
_, err = backend.ExecDocker(l, []string{"restore", "--target", ".", "--path", path, "latest"})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -291,9 +338,11 @@ func (l Location) RunCron() error {
|
||||
now := time.Now()
|
||||
if now.After(next) {
|
||||
lock.SetCron(l.name, now.Unix())
|
||||
l.Backup()
|
||||
l.Backup(true)
|
||||
} else {
|
||||
colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name)
|
||||
if !CRON_LEAN {
|
||||
colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -1,10 +1,11 @@
|
||||
package lock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
@@ -33,7 +34,8 @@ func setLock(locked bool) error {
|
||||
if locked {
|
||||
running := lock.GetBool("running")
|
||||
if running {
|
||||
return errors.New("an instance is already running")
|
||||
colors.Error.Println("an instance is already running. exiting")
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
lock.Set("running", locked)
|
||||
|
22
internal/metadata/extractor_added.go
Normal file
22
internal/metadata/extractor_added.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type addedExtractor struct {
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
func (e addedExtractor) Matches(line string) bool {
|
||||
return e.re.MatchString(line)
|
||||
}
|
||||
func (e addedExtractor) Extract(metadata *BackupLogMetadata, line string) {
|
||||
// Sample line: "Added to the repo: 0 B"
|
||||
metadata.AddedSize = strings.TrimSpace(e.re.ReplaceAllString(line, ""))
|
||||
}
|
||||
|
||||
func NewAddedExtractor() MetadatExtractor {
|
||||
return addedExtractor{regexp.MustCompile(`(?i)^Added to the repo:`)}
|
||||
}
|
57
internal/metadata/extractor_changeset.go
Normal file
57
internal/metadata/extractor_changeset.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type ChangeSetExtractor struct {
|
||||
re *regexp.Regexp
|
||||
cleaner *regexp.Regexp
|
||||
saver changeSetSaver
|
||||
}
|
||||
|
||||
func (e ChangeSetExtractor) Matches(line string) bool {
|
||||
return e.re.MatchString(line)
|
||||
}
|
||||
func (e ChangeSetExtractor) Extract(metadata *BackupLogMetadata, line string) {
|
||||
// Sample line: "Files: 0 new, 0 changed, 2 unmodified"
|
||||
trimmed := strings.TrimSpace(e.re.ReplaceAllString(line, ""))
|
||||
splitted := strings.Split(trimmed, ",")
|
||||
var changeset BackupLogMetadataChangeset = BackupLogMetadataChangeset{}
|
||||
changeset.Added = e.cleaner.ReplaceAllString(splitted[0], "")
|
||||
changeset.Changed = e.cleaner.ReplaceAllString(splitted[1], "")
|
||||
changeset.Unmodified = e.cleaner.ReplaceAllString(splitted[2], "")
|
||||
e.saver.Save(metadata, changeset)
|
||||
}
|
||||
|
||||
type changeSetSaver interface {
|
||||
Save(metadata *BackupLogMetadata, changeset BackupLogMetadataChangeset)
|
||||
}
|
||||
|
||||
type fileSaver struct{}
|
||||
|
||||
func (f fileSaver) Save(metadata *BackupLogMetadata, changeset BackupLogMetadataChangeset) {
|
||||
metadata.Files = changeset
|
||||
}
|
||||
|
||||
type dirsSaver struct{}
|
||||
|
||||
func (d dirsSaver) Save(metadata *BackupLogMetadata, changeset BackupLogMetadataChangeset) {
|
||||
metadata.Dirs = changeset
|
||||
}
|
||||
|
||||
func NewFilesExtractor() MetadatExtractor {
|
||||
return ChangeSetExtractor{
|
||||
re: regexp.MustCompile(`(?i)^Files:`),
|
||||
cleaner: regexp.MustCompile(`[^\d]`),
|
||||
saver: fileSaver{},
|
||||
}
|
||||
}
|
||||
func NewDirsExtractor() MetadatExtractor {
|
||||
return ChangeSetExtractor{
|
||||
re: regexp.MustCompile(`(?i)^Dirs:`),
|
||||
cleaner: regexp.MustCompile(`[^\d]`),
|
||||
saver: dirsSaver{},
|
||||
}
|
||||
}
|
22
internal/metadata/extractor_parent.go
Normal file
22
internal/metadata/extractor_parent.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type parentSnapshotIDExtractor struct {
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
func (e parentSnapshotIDExtractor) Matches(line string) bool {
|
||||
return e.re.MatchString(line)
|
||||
}
|
||||
func (e parentSnapshotIDExtractor) Extract(metadata *BackupLogMetadata, line string) {
|
||||
// Sample line: "using parent snapshot c65d9310"
|
||||
metadata.ParentSnapshotID = strings.TrimSpace(e.re.ReplaceAllString(line, ""))
|
||||
}
|
||||
|
||||
func NewParentSnapshotIDExtractor() MetadatExtractor {
|
||||
return parentSnapshotIDExtractor{regexp.MustCompile(`(?i)^using parent snapshot`)}
|
||||
}
|
32
internal/metadata/extractor_processed.go
Normal file
32
internal/metadata/extractor_processed.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type processedExtractor struct {
|
||||
re *regexp.Regexp
|
||||
cleaner *regexp.Regexp
|
||||
}
|
||||
|
||||
func (e processedExtractor) Matches(line string) bool {
|
||||
return e.re.MatchString(line)
|
||||
}
|
||||
func (e processedExtractor) Extract(metadata *BackupLogMetadata, line string) {
|
||||
// Sample line: "processed 2 files, 24 B in 0:00"
|
||||
var processed = BackupLogMetadataProcessed{}
|
||||
split := strings.Split(line, "in")
|
||||
processed.Duration = strings.TrimSpace(split[1])
|
||||
split = strings.Split(split[0], ",")
|
||||
processed.Files = e.cleaner.ReplaceAllString(split[0], "")
|
||||
processed.Size = strings.TrimSpace(split[1])
|
||||
metadata.Processed = processed
|
||||
}
|
||||
|
||||
func NewProcessedExtractor() MetadatExtractor {
|
||||
return processedExtractor{
|
||||
regexp.MustCompile(`(?i)^processed \d* files`),
|
||||
regexp.MustCompile(`(?i)[^\d]`),
|
||||
}
|
||||
}
|
22
internal/metadata/extractor_snapshot.go
Normal file
22
internal/metadata/extractor_snapshot.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type snapshotExtractor struct {
|
||||
re *regexp.Regexp
|
||||
}
|
||||
|
||||
func (e snapshotExtractor) Matches(line string) bool {
|
||||
return e.re.MatchString(line)
|
||||
}
|
||||
func (e snapshotExtractor) Extract(metadata *BackupLogMetadata, line string) {
|
||||
// Sample line: "snapshot 917c7691 saved"
|
||||
metadata.SnapshotID = strings.Split(line, " ")[1]
|
||||
}
|
||||
|
||||
func NewSnapshotExtractor() MetadatExtractor {
|
||||
return snapshotExtractor{regexp.MustCompile(`(?i)^snapshot \w+ saved`)}
|
||||
}
|
72
internal/metadata/metadata.go
Normal file
72
internal/metadata/metadata.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package metadata
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type BackupLogMetadataChangeset struct {
|
||||
Added string
|
||||
Changed string
|
||||
Unmodified string
|
||||
}
|
||||
type BackupLogMetadataProcessed struct {
|
||||
Files string
|
||||
Size string
|
||||
Duration string
|
||||
}
|
||||
type BackupLogMetadata struct {
|
||||
ParentSnapshotID string
|
||||
Files BackupLogMetadataChangeset
|
||||
Dirs BackupLogMetadataChangeset
|
||||
AddedSize string
|
||||
Processed BackupLogMetadataProcessed
|
||||
SnapshotID string
|
||||
}
|
||||
|
||||
type MetadatExtractor interface {
|
||||
Matches(line string) bool
|
||||
Extract(metadata *BackupLogMetadata, line string)
|
||||
}
|
||||
|
||||
var extractors = []MetadatExtractor{
|
||||
NewParentSnapshotIDExtractor(),
|
||||
NewFilesExtractor(),
|
||||
NewDirsExtractor(),
|
||||
NewAddedExtractor(),
|
||||
NewProcessedExtractor(),
|
||||
NewSnapshotExtractor(),
|
||||
}
|
||||
|
||||
func ExtractMetadataFromBackupLog(log string) BackupLogMetadata {
|
||||
var md BackupLogMetadata
|
||||
for _, line := range strings.Split(log, "\n") {
|
||||
line = strings.TrimSpace(line)
|
||||
for _, extractor := range extractors {
|
||||
if extractor.Matches(line) {
|
||||
extractor.Extract(&md, line)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return md
|
||||
}
|
||||
|
||||
func MakeEnvFromMetadata(metadata *BackupLogMetadata) map[string]string {
|
||||
env := make(map[string]string)
|
||||
var prefix = "AUTORESTIC_"
|
||||
|
||||
env[prefix+"SNAPSHOT_ID"] = metadata.SnapshotID
|
||||
env[prefix+"PARENT_SNAPSHOT_ID"] = metadata.ParentSnapshotID
|
||||
env[prefix+"FILES_ADDED"] = metadata.Files.Added
|
||||
env[prefix+"FILES_CHANGED"] = metadata.Files.Changed
|
||||
env[prefix+"FILES_UNMODIFIED"] = metadata.Files.Unmodified
|
||||
env[prefix+"DIRS_ADDED"] = metadata.Dirs.Added
|
||||
env[prefix+"DIRS_CHANGED"] = metadata.Dirs.Changed
|
||||
env[prefix+"DIRS_UNMODIFIED"] = metadata.Dirs.Unmodified
|
||||
env[prefix+"ADDED_SIZE"] = metadata.AddedSize
|
||||
env[prefix+"PROCESSED_FILES"] = metadata.Processed.Files
|
||||
env[prefix+"PROCESSED_SIZE"] = metadata.Processed.Size
|
||||
env[prefix+"PROCESSED_DURATION"] = metadata.Processed.Duration
|
||||
|
||||
return env
|
||||
}
|
@@ -10,13 +10,15 @@ import (
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
)
|
||||
|
||||
var RESTIC_BIN string
|
||||
|
||||
func CheckIfCommandIsCallable(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func CheckIfResticIsCallable() bool {
|
||||
return CheckIfCommandIsCallable("restic")
|
||||
return CheckIfCommandIsCallable(RESTIC_BIN)
|
||||
}
|
||||
|
||||
type ExecuteOptions struct {
|
||||
@@ -50,7 +52,7 @@ func ExecuteCommand(options ExecuteOptions, args ...string) (string, error) {
|
||||
}
|
||||
|
||||
func ExecuteResticCommand(options ExecuteOptions, args ...string) (string, error) {
|
||||
options.Command = "restic"
|
||||
options.Command = RESTIC_BIN
|
||||
return ExecuteCommand(options, args...)
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user