mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2025-09-06 10:30:39 +00:00
Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
30
CHANGELOG.md
30
CHANGELOG.md
@@ -5,6 +5,36 @@ 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.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
|
||||
|
@@ -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"
|
||||
)
|
||||
@@ -18,9 +21,17 @@ var backupCmd = &cobra.Command{
|
||||
|
||||
selected, err := internal.GetAllOrSelected(cmd, false)
|
||||
CheckErr(err)
|
||||
errors := 0
|
||||
for _, name := range selected {
|
||||
location, _ := internal.GetLocation(name)
|
||||
location.Backup(false)
|
||||
err := location.Backup(false)
|
||||
if err != nil {
|
||||
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")
|
||||
}
|
||||
|
@@ -31,6 +31,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")
|
||||
}
|
||||
|
@@ -57,5 +57,4 @@ func initConfig() {
|
||||
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")
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@
|
||||
> [General](/cli/general)
|
||||
> [Info](/cli/info)
|
||||
> [Check](/cli/check)
|
||||
> [Completion](/cli/completion)
|
||||
> [Backup](/cli/backup)
|
||||
> [Restore](/cli/restore)
|
||||
> [Forget](/cli/forget)
|
||||
|
@@ -19,16 +19,17 @@ backends:
|
||||
backends:
|
||||
name-of-backend:
|
||||
type: b2
|
||||
path: 'myAccount:myBucket/my/path'
|
||||
path: 'backblaze_bucketID'
|
||||
# Or With a path
|
||||
# path: 'backblaze_bucketID:/some/path'
|
||||
env:
|
||||
B2_ACCOUNT_ID: backblaze_account_id
|
||||
B2_ACCOUNT_KEY: backblaze_account_key
|
||||
B2_ACCOUNT_ID: 'backblaze_keyID'
|
||||
B2_ACCOUNT_KEY: 'backblaze_applicationKey'
|
||||
```
|
||||
|
||||
#### API Keys gotcha
|
||||
|
||||
When creating API make sure you check _Allow List All Bucket Names_ if you allow access to a single bucket only.
|
||||
Also make sure that the _File name prefix_ (if used) does not includes a leading slash.
|
||||
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: 'backblaze_bucketID:my/path'`).
|
||||
|
||||
## S3 / Minio
|
||||
|
||||
|
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 avery handy command which enables you to run any native restic command on desired backends. Generally 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.
|
||||
|
@@ -3,9 +3,11 @@
|
||||
This amazing people helped the project!
|
||||
|
||||
- @agateblue - Docs, Pruning, S3
|
||||
- @david-boles - Docs
|
||||
- @jin-park-dev - Typos
|
||||
- @sumnerboy12 - Typos
|
||||
- @FuzzyMistborn - Typos
|
||||
- @ChanceM - Typos
|
||||
- @TheForcer - Typos
|
||||
|
||||
> :ToCPrevNext
|
||||
|
@@ -30,14 +30,14 @@ 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 --ci cron
|
||||
```
|
||||
|
||||
> The `--ci` option is not required, but recommended
|
||||
|
@@ -9,7 +9,7 @@ curl -s https://raw.githubusercontent.com/CupCakeArmy/autorestic/master/install.
|
||||
## Write a simple config file
|
||||
|
||||
```bash
|
||||
vim .autorestic.yml
|
||||
vim ~/.autorestic.yml
|
||||
```
|
||||
|
||||
For a quick overview:
|
||||
@@ -35,7 +35,7 @@ locations:
|
||||
- hdd
|
||||
|
||||
backends:
|
||||
- name: remote
|
||||
remote:
|
||||
type: s3
|
||||
path: 's3.amazonaws.com/bucket_name'
|
||||
key: some-random-password-198rc79r8y1029c8yfewj8f1u0ef87yh198uoieufy
|
||||
@@ -43,7 +43,7 @@ backends:
|
||||
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'
|
||||
@@ -52,7 +52,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.
|
||||
|
@@ -16,9 +16,11 @@ 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
|
||||
else
|
||||
|
@@ -48,7 +48,7 @@ func (b Backend) generateRepo() (string, error) {
|
||||
}
|
||||
}
|
||||
return fmt.Sprintf("%s:%s", b.Type, parsed.String()), nil
|
||||
case "b2", "azure", "gs", "s3", "sftp":
|
||||
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)
|
||||
@@ -121,16 +121,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()
|
||||
@@ -157,8 +161,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
|
||||
}
|
||||
|
@@ -120,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()
|
||||
}
|
||||
|
||||
|
@@ -12,10 +12,11 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const VERSION = "1.0.4"
|
||||
const VERSION = "1.0.8"
|
||||
|
||||
var CI bool = false
|
||||
var VERBOSE bool = false
|
||||
var CRON_LEAN bool = false
|
||||
|
||||
type Config struct {
|
||||
Locations map[string]Location `yaml:"locations"`
|
||||
@@ -29,7 +30,9 @@ func GetConfig() *Config {
|
||||
if config == nil {
|
||||
once.Do(func() {
|
||||
if err := viper.ReadInConfig(); err == nil {
|
||||
if !CRON_LEAN {
|
||||
colors.Faint.Println("Using config file:", viper.ConfigFileUsed())
|
||||
}
|
||||
} else {
|
||||
return
|
||||
}
|
||||
@@ -190,11 +193,17 @@ 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")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -49,6 +49,18 @@ func (l Location) validate(c *Config) error {
|
||||
if l.From == "" {
|
||||
return fmt.Errorf(`Location "%s" is missing "from" key`, l.name)
|
||||
}
|
||||
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)
|
||||
}
|
||||
@@ -81,12 +93,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
|
||||
@@ -161,16 +174,16 @@ func (l Location) Backup(cron bool) error {
|
||||
switch t {
|
||||
case TypeLocal:
|
||||
out, err = ExecuteResticCommand(backupOptions, cmd...)
|
||||
case TypeVolume:
|
||||
out, err = backend.ExecDocker(l, cmd)
|
||||
}
|
||||
if err != nil {
|
||||
colors.Error.Println(out)
|
||||
return err
|
||||
}
|
||||
if VERBOSE {
|
||||
colors.Faint.Println(out)
|
||||
}
|
||||
case TypeVolume:
|
||||
err = backend.ExecDocker(l, cmd)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// After hooks
|
||||
@@ -271,7 +284,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
|
||||
@@ -296,7 +309,9 @@ func (l Location) RunCron() error {
|
||||
lock.SetCron(l.name, now.Unix())
|
||||
l.Backup(true)
|
||||
} else {
|
||||
if !CRON_LEAN {
|
||||
colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user