Compare commits

...

7 Commits

Author SHA1 Message Date
478e193d78 forgot version bump 2021-05-06 16:17:45 +02:00
11d4c67dce docs 2021-05-06 16:14:29 +02:00
1643309957 changelog 2021-05-06 15:57:28 +02:00
e05386b0b5 add failure and success hooks 2021-05-06 15:55:32 +02:00
aebaf0a225 Merge branch 'master' of https://github.com/cupcakearmy/autorestic 2021-05-06 15:12:37 +02:00
c090013bf5 Update README.md 2021-05-06 15:12:11 +02:00
88c6949208 custom restic binary 2021-05-06 15:04:35 +02:00
10 changed files with 101 additions and 34 deletions

View File

@@ -5,6 +5,17 @@ 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/), 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). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [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 ## [1.0.9] - 2021-05-01
### Fixed ### Fixed

View File

@@ -24,6 +24,7 @@ Releases are automatically built by the github workflow and uploaded to the rele
### Brew ### Brew
1. Check the checksum with `shasum -a 256 autorestic-1.2.3.tar.gz` 1. Download the latest release.
2. Update `url` and `sha256` in the brew repo. 2. Check the checksum with `shasum -a 256 autorestic-1.2.3.tar.gz`
3. Submit PR 3. Update `url` and `sha256` in the brew repo.
4. Submit PR

View File

@@ -13,6 +13,9 @@
<br><br> <br><br>
<a target="_blank" href="https://discord.gg/wS7RpYTYd2"> <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/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> </a>
</p> </p>
</p> </p>

View File

@@ -17,15 +17,15 @@ var backupCmd = &cobra.Command{
CheckErr(err) CheckErr(err)
defer lock.Unlock() defer lock.Unlock()
CheckErr(internal.CheckConfig()) internal.GetConfig()
selected, err := internal.GetAllOrSelected(cmd, false) selected, err := internal.GetAllOrSelected(cmd, false)
CheckErr(err) CheckErr(err)
errors := 0 errors := 0
for _, name := range selected { for _, name := range selected {
location, _ := internal.GetLocation(name) location, _ := internal.GetLocation(name)
err := location.Backup(false) errs := location.Backup(false)
if err != nil { for err := range errs {
colors.Error.Println(err) colors.Error.Println(err)
errors++ errors++
} }

View File

@@ -37,6 +37,7 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.autorestic.yml or ./.autorestic.yml)") 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().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().BoolVarP(&internal.VERBOSE, "verbose", "v", false, "verbose mode")
rootCmd.PersistentFlags().StringVar(&internal.RESTIC_BIN, "restic-bin", "restic", "specify custom restic binary")
cobra.OnInitialize(initConfig) cobra.OnInitialize(initConfig)
} }

View File

@@ -27,4 +27,12 @@ Verbose mode will show the output of the native restic commands that are otherwi
autorestic --verbose backup -a 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 > :ToCPrevNext

View File

@@ -2,7 +2,14 @@
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, 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 ```yml | .autorestic.yml
locations: locations:
@@ -11,10 +18,25 @@ locations:
to: my-backend to: my-backend
hooks: hooks:
before: before:
- echo "Hello" - echo "One"
- echo "Human" - echo "Two"
- echo "Three"
after: 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 error was encountered
If the `before` hook encounters errors the backup and `after` hooks will be skipped and only the `failed` hooks will run.
> :ToCPrevNext > :ToCPrevNext

View File

@@ -12,7 +12,7 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const VERSION = "1.0.9" const VERSION = "1.1.0"
var CI bool = false var CI bool = false
var VERBOSE bool = false var VERBOSE bool = false
@@ -75,21 +75,22 @@ func (c *Config) Describe() {
colors.PrintDescription("Cron", l.Cron) colors.PrintDescription("Cron", l.Cron)
} }
after, before := len(l.Hooks.After), len(l.Hooks.Before)
if after+before > 0 {
tmp = "" tmp = ""
if before > 0 { hooks := map[string][]string{
tmp += "\tBefore" "Before": l.Hooks.Before,
for _, cmd := range 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) tmp += colors.Faint.Sprintf("\n\t ▶ %s", cmd)
} }
} }
if after > 0 {
tmp += "\n\tAfter"
for _, cmd := range l.Hooks.After {
tmp += colors.Faint.Sprintf("\n\t ▶ %s", cmd)
}
} }
if tmp != "" {
colors.PrintDescription("Hooks", tmp) colors.PrintDescription("Hooks", tmp)
} }
@@ -129,7 +130,7 @@ func CheckConfig() error {
return fmt.Errorf("config could not be loaded/found") return fmt.Errorf("config could not be loaded/found")
} }
if !CheckIfResticIsCallable() { 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 { for name, backend := range c.Backends {
backend.name = name backend.name = name

View File

@@ -24,8 +24,10 @@ const (
type HookArray = []string type HookArray = []string
type Hooks struct { type Hooks struct {
Before HookArray `yaml:"before"` Before HookArray `yaml:"before,omitempty"`
After HookArray `yaml:"after"` 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][]string
@@ -133,7 +135,8 @@ func (l Location) getPath() (string, error) {
return "", fmt.Errorf("could not get path for location \"%s\"", l.name) return "", fmt.Errorf("could not get path for location \"%s\"", l.name)
} }
func (l Location) Backup(cron bool) error { func (l Location) Backup(cron bool) []error {
var errors []error
colors.PrimaryPrint(" Backing up location \"%s\" ", l.name) colors.PrimaryPrint(" Backing up location \"%s\" ", l.name)
t := l.getType() t := l.getType()
options := ExecuteOptions{ options := ExecuteOptions{
@@ -147,7 +150,8 @@ func (l Location) Backup(cron bool) error {
// Hooks // Hooks
if err := ExecuteHooks(l.Hooks.Before, options); err != nil { if err := ExecuteHooks(l.Hooks.Before, options); err != nil {
return err errors = append(errors, err)
goto after
} }
// Backup // Backup
@@ -156,7 +160,8 @@ func (l Location) Backup(cron bool) error {
colors.Secondary.Printf("Backend: %s\n", backend.name) colors.Secondary.Printf("Backend: %s\n", backend.name)
env, err := backend.getEnv() env, err := backend.getEnv()
if err != nil { if err != nil {
return nil errors = append(errors, err)
continue
} }
flags := l.getOptions("backup") flags := l.getOptions("backup")
@@ -181,7 +186,8 @@ func (l Location) Backup(cron bool) error {
} }
if err != nil { if err != nil {
colors.Error.Println(out) colors.Error.Println(out)
return err errors = append(errors, err)
continue
} }
if VERBOSE { if VERBOSE {
colors.Faint.Println(out) colors.Faint.Println(out)
@@ -190,10 +196,22 @@ func (l Location) Backup(cron bool) error {
// After hooks // After hooks
if err := ExecuteHooks(l.Hooks.After, options); err != nil { 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") colors.Success.Println("Done")
return nil return errors
} }
func (l Location) Forget(prune bool, dry bool) error { func (l Location) Forget(prune bool, dry bool) error {

View File

@@ -10,13 +10,15 @@ import (
"github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/colors"
) )
var RESTIC_BIN string
func CheckIfCommandIsCallable(cmd string) bool { func CheckIfCommandIsCallable(cmd string) bool {
_, err := exec.LookPath(cmd) _, err := exec.LookPath(cmd)
return err == nil return err == nil
} }
func CheckIfResticIsCallable() bool { func CheckIfResticIsCallable() bool {
return CheckIfCommandIsCallable("restic") return CheckIfCommandIsCallable(RESTIC_BIN)
} }
type ExecuteOptions struct { type ExecuteOptions struct {
@@ -50,7 +52,7 @@ func ExecuteCommand(options ExecuteOptions, args ...string) (string, error) {
} }
func ExecuteResticCommand(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...) return ExecuteCommand(options, args...)
} }