mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2025-09-06 18:40:40 +00:00
Compare commits
60 Commits
Author | SHA1 | Date | |
---|---|---|---|
5afad86e37 | |||
ba9e090695 | |||
|
05def04770 | ||
d85470459f | |||
1f69a7974a | |||
db9f5dea66 | |||
75160d4d6a | |||
92feaef5bb | |||
2a7e233cdb | |||
a373c07fb0 | |||
ec9e2aebcd | |||
7d87160706 | |||
8e1fe6af65 | |||
|
65ba1f6ac1 | ||
|
6bf4953003 | ||
27758a03fa | |||
bbdae05199 | |||
389490c4ea | |||
21b83b4c89 | |||
982f9e0b5c | |||
0c37af5588 | |||
e3c378f2a1 | |||
3b541665ae | |||
e0b2c99ccd | |||
2b14e6b1af | |||
1810af8d02 | |||
252968e15e | |||
26de4385ea | |||
0c71bea93e | |||
3029259d82 | |||
|
389f7c25dd | ||
e055e28cfe | |||
ed3c17d678 | |||
8802b74b47 | |||
e94e7030fc | |||
c55e91b8ff | |||
170bdb81ad | |||
4126436f7f | |||
113a97c283 | |||
c250391f67 | |||
d3b4915d25 | |||
cd7a5cbc13 | |||
3dd3956d64 | |||
59035da46a | |||
90914d2078 | |||
0ae374cd45 | |||
3665cea62d | |||
cf92d400c2 | |||
696bd14ac7 | |||
a68e3e426e | |||
27e82c8529 | |||
4fe241e6f3 | |||
d0b1b86fdd | |||
14dd41d60d | |||
bcfc734cd1 | |||
6817f494ef | |||
2c46f0da0c | |||
ac756dfbde | |||
7ad6f7ce9e | |||
c2f9ed9204 |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
*
|
||||||
|
!**/*.go
|
||||||
|
!build
|
||||||
|
!cmd
|
||||||
|
!internal
|
||||||
|
!go.*
|
36
.github/workflows/build.yml
vendored
36
.github/workflows/build.yml
vendored
@@ -3,16 +3,44 @@ name: Main
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
- 'v*.*.*'
|
- "v*.*.*"
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
docker:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v1
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v1
|
||||||
|
- name: Docker Labels
|
||||||
|
id: meta
|
||||||
|
uses: crazy-max/ghaction-docker-meta@v2
|
||||||
|
with:
|
||||||
|
images: cupcakearmy/autorestic
|
||||||
|
tags: |
|
||||||
|
type=semver,pattern={{version}}
|
||||||
|
type=semver,pattern={{major}}.{{minor}}
|
||||||
|
type=semver,pattern={{major}}
|
||||||
|
- name: Login to DockerHub
|
||||||
|
uses: docker/login-action@v1
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v2
|
||||||
|
with:
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
|
||||||
|
release:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: actions/setup-go@v2
|
- uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: '^1.16.3'
|
go-version: "^1.16.3"
|
||||||
- name: Build
|
- name: Build
|
||||||
run: go run build/build.go
|
run: go run build/build.go
|
||||||
|
|
||||||
@@ -25,4 +53,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
files: dist/*
|
files: dist/*
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
111
CHANGELOG.md
111
CHANGELOG.md
@@ -5,9 +5,76 @@ 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.5.6] - 2022-03-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Add bash in docker image for hooks. @fariszr
|
||||||
|
|
||||||
|
## [1.5.5] - 2022-02-16
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- Go version was updated from `1.16` to `1.17`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Home directory was not being taken into account for loading configs.
|
||||||
|
|
||||||
|
## [1.5.4] - 2022-02-16
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Lean flag not omitting all output.
|
||||||
|
|
||||||
|
## [1.5.3] - 2022-02-16
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Error throwing not finding config even it's not being used.
|
||||||
|
|
||||||
|
## [1.5.2] - 2022-02-13
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Config loading @jjromannet
|
||||||
|
- Making a backup of the file @jjromannet
|
||||||
|
|
||||||
|
## [1.5.1] - 2021-12-06
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- use official docker image instead of installing rclone every time docker is used.
|
||||||
|
- docker docs
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- lock file not always next to the config file.
|
||||||
|
- update / install bugs.
|
||||||
|
- lock docker image tag to the current autorestic version
|
||||||
|
- better error logging
|
||||||
|
|
||||||
|
## [1.5.0] - 2021-11-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for multiple paths.
|
||||||
|
- Improved error handling.
|
||||||
|
- Allow for specific snapshot to be restored.
|
||||||
|
- Docker image.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- rclone in docker volumes.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- [Breaking Change] Declaration of docker volumes. See: https://autorestic.vercel.app/migration/1.4_1.5.
|
||||||
|
- [Breaking Change] Hooks default executing directory now defaults to the config file directory. See: https://autorestic.vercel.app/migration/1.4_1.5.
|
||||||
|
|
||||||
## [1.4.1] - 2021-10-31
|
## [1.4.1] - 2021-10-31
|
||||||
|
|
||||||
### Fixes
|
### Fixed
|
||||||
|
|
||||||
- Numeric values from config files not being passed to env.
|
- Numeric values from config files not being passed to env.
|
||||||
|
|
||||||
@@ -15,26 +82,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Allow specify to specify a backend for location backup
|
- Allow specify to specify a backend for location backup.
|
||||||
- Global restic flags
|
- Global restic flags.
|
||||||
- Generic ENV support for backends
|
- Generic ENV support for backends.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Install now only requires `wget`
|
- Install now only requires `wget`.
|
||||||
- Env variable for the `KEY` has been renamed from `AUTORESTIC_[BACKEND NAME]_KEY` -> `AUTORESTIC_[BACKEND NAME]_RESTIC_PASSWORD`
|
- Env variable for the `KEY` has been renamed from `AUTORESTIC_[BACKEND NAME]_KEY` -> `AUTORESTIC_[BACKEND NAME]_RESTIC_PASSWORD`.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Error handling during upgrade & uninstall
|
- Error handling during upgrade & uninstall.
|
||||||
|
|
||||||
## [1.3.0] - 2021-10-26
|
## [1.3.0] - 2021-10-26
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Pass restic backup metadata as ENV to hooks
|
- Pass restic backup metadata as ENV to hooks.
|
||||||
- Support for `XDG_CONFIG_HOME` and `${HOME}/.config` as default locations for `.autorestic.yaml` file.
|
- Support for `XDG_CONFIG_HOME` and `${HOME}/.config` as default locations for `.autorestic.yaml` file.
|
||||||
- Binary restic flags are now supported
|
- Binary restic flags are now supported.
|
||||||
- Pass encryption keys from env variables or files.
|
- Pass encryption keys from env variables or files.
|
||||||
|
|
||||||
## [1.2.0] - 2021-08-05
|
## [1.2.0] - 2021-08-05
|
||||||
@@ -42,12 +109,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Community page
|
- Community page
|
||||||
- Support for yaml references and aliases
|
- Support for yaml references and aliases.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Better verbose output for hooks
|
- Better verbose output for hooks.
|
||||||
- Better error message for bad formatted configs
|
- Better error message for bad formatted configs.
|
||||||
|
|
||||||
## [1.1.2] - 2021-07-11
|
## [1.1.2] - 2021-07-11
|
||||||
|
|
||||||
@@ -59,24 +126,24 @@ Don't check all backend when running `forget` or `exec` commands.
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Options for backends
|
- Options for backends.
|
||||||
|
|
||||||
## [1.1.0] - 2021-05-06
|
## [1.1.0] - 2021-05-06
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- use custom restic binary
|
- use custom restic binary.
|
||||||
- success & failure hooks
|
- success & failure hooks.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- don't skip other locations on failure
|
- don't skip other locations on failure.
|
||||||
|
|
||||||
## [1.0.9] - 2021-05-01
|
## [1.0.9] - 2021-05-01
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Validation for docker volumes
|
- Validation for docker volumes.
|
||||||
|
|
||||||
## [1.0.8] - 2021-04-28
|
## [1.0.8] - 2021-04-28
|
||||||
|
|
||||||
@@ -99,7 +166,7 @@ Don't check all backend when running `forget` or `exec` commands.
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Support for rclone
|
- Support for rclone.
|
||||||
|
|
||||||
## [1.0.5] - 2021-04-24
|
## [1.0.5] - 2021-04-24
|
||||||
|
|
||||||
@@ -112,17 +179,17 @@ Don't check all backend when running `forget` or `exec` commands.
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Options to add rest username and password in config
|
- Options to add rest username and password in config.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Don't add empty strings when saving config
|
- Don't add empty strings when saving config.
|
||||||
|
|
||||||
## [1.0.3] - 2021-04-20
|
## [1.0.3] - 2021-04-20
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
- Auto upgrade script was not working on linux as linux does not support writing to the binary that is being executed
|
- 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
|
## [1.0.2] - 2021-04-20
|
||||||
|
|
||||||
@@ -138,7 +205,7 @@ Don't check all backend when running `forget` or `exec` commands.
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- Completion command for various shells
|
- Completion command for various shells.
|
||||||
|
|
||||||
## [1.0.0] - 2021-04-17
|
## [1.0.0] - 2021-04-17
|
||||||
|
|
||||||
|
12
Dockerfile
Normal file
12
Dockerfile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
FROM golang:1.17-alpine as builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY go.* .
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
RUN go build
|
||||||
|
|
||||||
|
FROM alpine
|
||||||
|
RUN apk add --no-cache restic rclone bash
|
||||||
|
COPY --from=builder /app/autorestic /usr/bin/autorestic
|
||||||
|
CMD [ "autorestic" ]
|
@@ -30,8 +30,8 @@ var backupCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
location, _ := internal.GetLocation(splitted[0])
|
location, _ := internal.GetLocation(splitted[0])
|
||||||
errs := location.Backup(false, specificBackend)
|
errs := location.Backup(false, specificBackend)
|
||||||
for err := range errs {
|
for _, err := range errs {
|
||||||
colors.Error.Println(err)
|
colors.Error.Printf("%s\n\n", err)
|
||||||
errors++
|
errors++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -11,6 +11,7 @@ var checkCmd = &cobra.Command{
|
|||||||
Use: "check",
|
Use: "check",
|
||||||
Short: "Check if everything is setup",
|
Short: "Check if everything is setup",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
internal.GetConfig()
|
||||||
err := lock.Lock()
|
err := lock.Lock()
|
||||||
CheckErr(err)
|
CheckErr(err)
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
|
@@ -2,6 +2,7 @@ package cmd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cupcakearmy/autorestic/internal"
|
"github.com/cupcakearmy/autorestic/internal"
|
||||||
|
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -11,7 +12,8 @@ var cronCmd = &cobra.Command{
|
|||||||
Short: "Run cron job for automated backups",
|
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.`,
|
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) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
internal.CRON_LEAN, _ = cmd.Flags().GetBool("lean")
|
internal.GetConfig()
|
||||||
|
flags.CRON_LEAN, _ = cmd.Flags().GetBool("lean")
|
||||||
err := lock.Lock()
|
err := lock.Lock()
|
||||||
CheckErr(err)
|
CheckErr(err)
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
|
@@ -9,9 +9,11 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var restoreCmd = &cobra.Command{
|
var restoreCmd = &cobra.Command{
|
||||||
Use: "restore",
|
Use: "restore [snapshot id]",
|
||||||
Short: "Restore backup for location",
|
Short: "Restore backup for location",
|
||||||
|
Args: cobra.MaximumNArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
internal.GetConfig()
|
||||||
err := lock.Lock()
|
err := lock.Lock()
|
||||||
CheckErr(err)
|
CheckErr(err)
|
||||||
defer lock.Unlock()
|
defer lock.Unlock()
|
||||||
@@ -24,7 +26,11 @@ var restoreCmd = &cobra.Command{
|
|||||||
target, _ := cmd.Flags().GetString("to")
|
target, _ := cmd.Flags().GetString("to")
|
||||||
from, _ := cmd.Flags().GetString("from")
|
from, _ := cmd.Flags().GetString("from")
|
||||||
force, _ := cmd.Flags().GetBool("force")
|
force, _ := cmd.Flags().GetBool("force")
|
||||||
err = l.Restore(target, from, force)
|
snapshot := ""
|
||||||
|
if len(args) > 0 {
|
||||||
|
snapshot = args[0]
|
||||||
|
}
|
||||||
|
err = l.Restore(target, from, force, snapshot)
|
||||||
CheckErr(err)
|
CheckErr(err)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
36
cmd/root.go
36
cmd/root.go
@@ -3,9 +3,11 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/cupcakearmy/autorestic/internal"
|
"github.com/cupcakearmy/autorestic/internal"
|
||||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||||
|
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
@@ -27,7 +29,7 @@ var rootCmd = &cobra.Command{
|
|||||||
Version: internal.VERSION,
|
Version: internal.VERSION,
|
||||||
Use: "autorestic",
|
Use: "autorestic",
|
||||||
Short: "CLI Wrapper for restic",
|
Short: "CLI Wrapper for restic",
|
||||||
Long: "Documentation: https://autorestic.vercel.app",
|
Long: "Documentation:\thttps://autorestic.vercel.app\nSupport:\thttps://discord.gg/wS7RpYTYd2",
|
||||||
}
|
}
|
||||||
|
|
||||||
func Execute() {
|
func Execute() {
|
||||||
@@ -36,8 +38,8 @@ func Execute() {
|
|||||||
|
|
||||||
func init() {
|
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(&flags.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(&flags.VERBOSE, "verbose", "v", false, "verbose mode")
|
||||||
rootCmd.PersistentFlags().StringVar(&internal.RESTIC_BIN, "restic-bin", "restic", "specify custom restic binary")
|
rootCmd.PersistentFlags().StringVar(&internal.RESTIC_BIN, "restic-bin", "restic", "specify custom restic binary")
|
||||||
cobra.OnInitialize(initConfig)
|
cobra.OnInitialize(initConfig)
|
||||||
}
|
}
|
||||||
@@ -45,17 +47,22 @@ func init() {
|
|||||||
func initConfig() {
|
func initConfig() {
|
||||||
if ci, _ := rootCmd.Flags().GetBool("ci"); ci {
|
if ci, _ := rootCmd.Flags().GetBool("ci"); ci {
|
||||||
colors.DisableColors(true)
|
colors.DisableColors(true)
|
||||||
internal.VERBOSE = true
|
flags.VERBOSE = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfgFile != "" {
|
if cfgFile != "" {
|
||||||
viper.SetConfigFile(cfgFile)
|
viper.SetConfigFile(cfgFile)
|
||||||
|
viper.AutomaticEnv()
|
||||||
|
if viper.ConfigFileUsed() == "" {
|
||||||
|
colors.Error.Println("cannot read config file %s\n", cfgFile)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
viper.AddConfigPath(".")
|
configPaths := []string{"."}
|
||||||
|
|
||||||
// Home
|
// Home
|
||||||
if home, err := homedir.Dir(); err != nil {
|
if home, err := homedir.Dir(); err == nil {
|
||||||
viper.AddConfigPath(home)
|
configPaths = append(configPaths, home)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XDG_CONFIG_HOME
|
// XDG_CONFIG_HOME
|
||||||
@@ -66,10 +73,17 @@ func initConfig() {
|
|||||||
prefix = filepath.Join(home, ".config")
|
prefix = filepath.Join(home, ".config")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viper.AddConfigPath(filepath.Join(prefix, "autorestic"))
|
xdgConfig := filepath.Join(prefix, "autorestic")
|
||||||
|
configPaths = append(configPaths, xdgConfig)
|
||||||
}
|
}
|
||||||
|
for _, cfgPath := range configPaths {
|
||||||
viper.SetConfigName(".autorestic")
|
viper.AddConfigPath(cfgPath)
|
||||||
|
}
|
||||||
|
if flags.VERBOSE {
|
||||||
|
colors.Faint.Printf("Using config paths: %s\n", strings.Join(configPaths, " "))
|
||||||
|
}
|
||||||
|
cfgFileName := ".autorestic"
|
||||||
|
viper.SetConfigName(cfgFileName)
|
||||||
|
viper.AutomaticEnv()
|
||||||
}
|
}
|
||||||
viper.AutomaticEnv()
|
|
||||||
}
|
}
|
||||||
|
@@ -40,7 +40,13 @@
|
|||||||
> [Uninstall](/cli/uninstall)
|
> [Uninstall](/cli/uninstall)
|
||||||
> [Upgrade](/cli/upgrade)
|
> [Upgrade](/cli/upgrade)
|
||||||
|
|
||||||
|
> :Collapse label=Migration
|
||||||
|
>
|
||||||
|
> [0.x → 1.0](/migration/0.x_1.0)
|
||||||
|
> [1.4 → 1.5](/migration/1.4_1.5)
|
||||||
|
|
||||||
[Examples](/examples)
|
[Examples](/examples)
|
||||||
|
[Docker](/docker)
|
||||||
[QA](/qa)
|
[QA](/qa)
|
||||||
[Community](/community)
|
[Community](/community)
|
||||||
[Contributors](/contrib)
|
[Contributors](/contrib)
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
Backends are the outputs 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
|
```yaml | .autorestic.yml
|
||||||
|
version: 2
|
||||||
|
|
||||||
backends:
|
backends:
|
||||||
name-of-backend:
|
name-of-backend:
|
||||||
type: local
|
type: local
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
# Restore
|
# Restore
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
autorestic restore [-l, --location] [--from backend] [--to <out dir>] [-f, --force]
|
autorestic restore [-l, --location] [--from backend] [--to <out dir>] [-f, --force] [snapshot]
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
This will restore the location to the selected target. If for one location there are more than one backends specified autorestic will take the first one. If no specific snapshot is specified `autorestic` will use `latest`.
|
||||||
|
|
||||||
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.
|
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
|
## Example
|
||||||
|
|
||||||
|
@@ -16,6 +16,8 @@ You can also specify a custom file with the `-c path/to/some/config.yml`
|
|||||||
## Example configuration
|
## Example configuration
|
||||||
|
|
||||||
```yaml | .autorestic.yml
|
```yaml | .autorestic.yml
|
||||||
|
version: 2
|
||||||
|
|
||||||
locations:
|
locations:
|
||||||
home:
|
home:
|
||||||
from: /home/me
|
from: /home/me
|
||||||
@@ -49,6 +51,8 @@ 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.
|
The following example shows how the locations `a` and `b` share the same hooks and forget policies.
|
||||||
|
|
||||||
```yaml | .autorestic.yml
|
```yaml | .autorestic.yml
|
||||||
|
version: 2
|
||||||
|
|
||||||
extras:
|
extras:
|
||||||
hooks: &foo
|
hooks: &foo
|
||||||
before:
|
before:
|
||||||
|
@@ -2,16 +2,19 @@
|
|||||||
|
|
||||||
This amazing people helped the project!
|
This amazing people helped the project!
|
||||||
|
|
||||||
- @agateblue - Docs, Pruning, S3
|
- @agateblue - Docs, Pruning, S3.
|
||||||
- @david-boles - Docs
|
- @g-a-c - Update/Install bugs.
|
||||||
- @SebDanielsson - Brew
|
- @jjromannet - Bug fixes.
|
||||||
- @n194 - AUR Package
|
- @fariszr - Bug fixes.
|
||||||
- @jin-park-dev - Typos
|
- @david-boles - Docs.
|
||||||
- @sumnerboy12 - Typos
|
- @SebDanielsson - Brew.
|
||||||
- @FuzzyMistborn - Typos
|
- @n194 - AUR Package.
|
||||||
- @ChanceM - Typos
|
- @jin-park-dev - Typos.
|
||||||
- @TheForcer - Typos
|
- @sumnerboy12 - Typos.
|
||||||
- @themorlan - Typos
|
- @FuzzyMistborn - Typos.
|
||||||
- @somebox - Typos
|
- @ChanceM - Typos.
|
||||||
|
- @TheForcer - Typos.
|
||||||
|
- @themorlan - Typos.
|
||||||
|
- @somebox - Typos.
|
||||||
|
|
||||||
> :ToCPrevNext
|
> :ToCPrevNext
|
||||||
|
28
docs/markdown/docker.md
Normal file
28
docs/markdown/docker.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 🐳 Docker
|
||||||
|
|
||||||
|
The docker image is build with rclone and restic already included. It's ment more as a utility image.
|
||||||
|
|
||||||
|
## Remote hosts
|
||||||
|
|
||||||
|
For remote backups (S3, B2, GCS, etc.) it's quite easy, as you only need to mount the config file and the data to backup.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm \\
|
||||||
|
-v $(pwd):/data \\
|
||||||
|
cupcakearmy/autorestic \\
|
||||||
|
autorestic backup -va -c /data/.autorestic.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rclone
|
||||||
|
|
||||||
|
For rclone you will have to also mount the rclone config file to `/root/.config/rclone/rclone.conf`.
|
||||||
|
|
||||||
|
To check where it is located you can run the following command: `rclone config file`.
|
||||||
|
|
||||||
|
**Example**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run \\
|
||||||
|
-v /home/user/.config/rclone/rclone.conf:/root/.config/rclone/rclone.conf:ro \\
|
||||||
|
...
|
||||||
|
```
|
@@ -10,6 +10,12 @@ wget -qO - https://raw.githubusercontent.com/CupCakeArmy/autorestic/master/insta
|
|||||||
|
|
||||||
## Alternatives
|
## Alternatives
|
||||||
|
|
||||||
|
### Docker
|
||||||
|
|
||||||
|
There is an official docker image over at [cupcakearmy/autorestic](https://hub.docker.com/r/cupcakearmy/autorestic).
|
||||||
|
|
||||||
|
For some examples see [here](/docker).
|
||||||
|
|
||||||
### Manual
|
### 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.
|
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.
|
||||||
@@ -20,6 +26,6 @@ If you are on macOS you can install through brew: `brew install autorestic`.
|
|||||||
|
|
||||||
### AUR
|
### AUR
|
||||||
|
|
||||||
If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/) (looking for maintainers).
|
~~If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/) (looking for maintainers).~~ - Deprecated
|
||||||
|
|
||||||
> :ToCPrevNext
|
> :ToCPrevNext
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
autorestic supports docker volumes directly, without needing them to be mounted to the host filesystem.
|
autorestic supports docker volumes directly, without needing them to be mounted to the host filesystem.
|
||||||
|
|
||||||
```yaml | docker-compose.yml
|
```yaml | docker-compose.yml
|
||||||
version: '3.7'
|
version: '3.8'
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
data:
|
data:
|
||||||
@@ -18,13 +18,9 @@ services:
|
|||||||
|
|
||||||
```yaml | .autorestic.yml
|
```yaml | .autorestic.yml
|
||||||
locations:
|
locations:
|
||||||
- name: hello
|
foo:
|
||||||
from: volume:my-data
|
from: my-data
|
||||||
to:
|
type: volume
|
||||||
- remote
|
|
||||||
|
|
||||||
backends:
|
|
||||||
- name: remote
|
|
||||||
# ...
|
# ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -7,6 +7,8 @@ This is based on [Restic's snapshots policies](https://restic.readthedocs.io/en/
|
|||||||
> **Note** This is a full example, of course you also can specify only one of them
|
> **Note** This is a full example, of course you also can specify only one of them
|
||||||
|
|
||||||
```yaml | .autorestic.yml
|
```yaml | .autorestic.yml
|
||||||
|
version: 2
|
||||||
|
|
||||||
locations:
|
locations:
|
||||||
etc:
|
etc:
|
||||||
from: /etc
|
from: /etc
|
||||||
@@ -22,4 +24,17 @@ locations:
|
|||||||
keep-within: '2w' # keep snapshots from the last 2 weeks
|
keep-within: '2w' # keep snapshots from the last 2 weeks
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Globally
|
||||||
|
|
||||||
|
You can specify global forget policies that would be applied to all locations:
|
||||||
|
|
||||||
|
```yaml | .autorestic.yml
|
||||||
|
version: 2
|
||||||
|
|
||||||
|
global:
|
||||||
|
forget:
|
||||||
|
keep-daily: 30
|
||||||
|
keep-weekly: 52
|
||||||
|
```
|
||||||
|
|
||||||
> :ToCPrevNext
|
> :ToCPrevNext
|
||||||
|
@@ -26,7 +26,7 @@ locations:
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
|
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag foo --tag bar` to the native command.
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
locations:
|
locations:
|
||||||
@@ -40,6 +40,12 @@ locations:
|
|||||||
- bar
|
- bar
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Priority
|
||||||
|
|
||||||
|
Options can be set globally, on the backends or on the locations.
|
||||||
|
|
||||||
|
The priority is as follows: `location > backend > global`.
|
||||||
|
|
||||||
## Global Options
|
## Global Options
|
||||||
|
|
||||||
It is possible to specify global flags that will be run every time restic is invoked. To do so specify them under `global` in your config file.
|
It is possible to specify global flags that will be run every time restic is invoked. To do so specify them under `global` in your config file.
|
||||||
|
@@ -4,9 +4,15 @@ Locations can be seen as the input to the backup process. Generally this is simp
|
|||||||
The paths can be relative from the config file. A location can have multiple backends, so that the data is secured across multiple servers.
|
The paths can be relative from the config file. A location can have multiple backends, so that the data is secured across multiple servers.
|
||||||
|
|
||||||
```yaml | .autorestic.yml
|
```yaml | .autorestic.yml
|
||||||
|
version: 2
|
||||||
|
|
||||||
locations:
|
locations:
|
||||||
my-location-name:
|
my-location-name:
|
||||||
from: path/to/backup
|
from: path/to/backup
|
||||||
|
# Or multiple
|
||||||
|
# from:
|
||||||
|
# - /a
|
||||||
|
# - /b
|
||||||
to:
|
to:
|
||||||
- name-of-backend
|
- name-of-backend
|
||||||
- also-backup-to-this-backend
|
- also-backup-to-this-backend
|
||||||
@@ -14,7 +20,7 @@ locations:
|
|||||||
|
|
||||||
## `from`
|
## `from`
|
||||||
|
|
||||||
This is the source of the location.
|
This is the source of the location. Can be an `array` for multiple sources.
|
||||||
|
|
||||||
#### How are paths resolved?
|
#### How are paths resolved?
|
||||||
|
|
||||||
|
@@ -1,6 +1,4 @@
|
|||||||
# Upgrade
|
# From `0.x` to `1.0`
|
||||||
|
|
||||||
## From `0.x` to `1.0`
|
|
||||||
|
|
||||||
Most of the config file is remained compatible, however to clean up the backends custom environment variables were moved from the root object to an `env` object.
|
Most of the config file is remained compatible, however to clean up the backends custom environment variables were moved from the root object to an `env` object.
|
||||||
|
|
60
docs/markdown/migration/1.4_1.5.md
Normal file
60
docs/markdown/migration/1.4_1.5.md
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
# Migration from `1.4` to `1.5`
|
||||||
|
|
||||||
|
## ⚠️ Important notes
|
||||||
|
|
||||||
|
The way snapshots are referenced in the `restore` and `prune` commands has been changed. Before they were referenced by the path. Now every backup is tagged and those tags are then referenced in the cli. This means that when running restore and forget commands old backups are not taken into account anymore.
|
||||||
|
|
||||||
|
## Config files
|
||||||
|
|
||||||
|
- The config file now required to have a version number. This has to be added with `version: 2` at the root.
|
||||||
|
- Hooks now optionally support `dir: /some/dir` in the [options object](https://pkg.go.dev/github.com/cupcakearmy/autorestic/internal#Hooks).
|
||||||
|
- Docker volumes don't get prefixed with `volume:` anymore, rather you have to set the `type: volume` in the [location config](https://pkg.go.dev/github.com/cupcakearmy/autorestic/internal#Hooks).
|
||||||
|
|
||||||
|
See detailed instructions below.
|
||||||
|
|
||||||
|
## Config Version
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
version: 2 # Added
|
||||||
|
|
||||||
|
backends:
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
Since `1.5` multiple sources for a location are possible.
|
||||||
|
For this reason, while before hooks where executed in the folder of the source, now they are executed in the directory of the config `.autorestic.yaml`.
|
||||||
|
|
||||||
|
You can overwrite this behavior with the new `dir` option in the hook section of the config.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
locations:
|
||||||
|
l1:
|
||||||
|
# ...
|
||||||
|
from: /foo/bar
|
||||||
|
hooks:
|
||||||
|
dir: /foo/bar
|
||||||
|
before: pwd
|
||||||
|
```
|
||||||
|
|
||||||
|
## Docker volumes
|
||||||
|
|
||||||
|
The syntax with docker volumes has changed and needs to be adjusted.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# Before
|
||||||
|
locations:
|
||||||
|
foo:
|
||||||
|
from: volume:my-data
|
||||||
|
```
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# After
|
||||||
|
locations:
|
||||||
|
foo:
|
||||||
|
from: my-data
|
||||||
|
type: volume
|
||||||
|
```
|
||||||
|
|
||||||
|
> :ToCPrevNext
|
4
docs/markdown/migration/index.md
Normal file
4
docs/markdown/migration/index.md
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
# Migration
|
||||||
|
|
||||||
|
- [From 0.x to 1.0](/migration/0.x_1.0)
|
||||||
|
- [From 1.4 to 1.5](/migration/1.4_1.5)
|
@@ -25,9 +25,15 @@ For a quick overview:
|
|||||||
> Note that the data is automatically encrypted on the server. The key will be generated and added to your config file. Every backend will have a separate key. **You should keep a copy of the keys or config file somewhere in case your server dies**. Otherwise DATA IS LOST!
|
> Note that the data is automatically encrypted on the server. The key will be generated and added to your config file. Every backend will have a separate key. **You should keep a copy of the keys or config file somewhere in case your server dies**. Otherwise DATA IS LOST!
|
||||||
|
|
||||||
```yaml | .autorestic.yml
|
```yaml | .autorestic.yml
|
||||||
|
version: 2
|
||||||
|
|
||||||
locations:
|
locations:
|
||||||
home:
|
home:
|
||||||
from: /home/me
|
from: /home
|
||||||
|
# Or multiple
|
||||||
|
# from:
|
||||||
|
# - /foo
|
||||||
|
# - /bar
|
||||||
to: remote
|
to: remote
|
||||||
|
|
||||||
important:
|
important:
|
||||||
|
22
go.mod
22
go.mod
@@ -1,6 +1,6 @@
|
|||||||
module github.com/cupcakearmy/autorestic
|
module github.com/cupcakearmy/autorestic
|
||||||
|
|
||||||
go 1.16
|
go 1.17
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/blang/semver/v4 v4.0.0
|
github.com/blang/semver/v4 v4.0.0
|
||||||
@@ -12,3 +12,23 @@ require (
|
|||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
github.com/spf13/viper v1.7.1
|
github.com/spf13/viper v1.7.1
|
||||||
)
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fsnotify/fsnotify v1.4.7 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.1 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.8 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.12 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||||
|
github.com/pelletier/go-toml v1.2.0 // indirect
|
||||||
|
github.com/spf13/afero v1.1.2 // indirect
|
||||||
|
github.com/spf13/cast v1.3.0 // indirect
|
||||||
|
github.com/spf13/jwalterweatherman v1.0.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.2.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54 // indirect
|
||||||
|
golang.org/x/text v0.3.2 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.51.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
)
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||||
|
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||||
)
|
)
|
||||||
|
|
||||||
type BackendRest struct {
|
type BackendRest struct {
|
||||||
@@ -128,7 +129,7 @@ func (b Backend) validate() error {
|
|||||||
// If not initialize
|
// If not initialize
|
||||||
colors.Body.Printf("Initializing backend \"%s\"...\n", b.name)
|
colors.Body.Printf("Initializing backend \"%s\"...\n", b.name)
|
||||||
out, err := ExecuteResticCommand(options, "init")
|
out, err := ExecuteResticCommand(options, "init")
|
||||||
if VERBOSE {
|
if flags.VERBOSE {
|
||||||
colors.Faint.Println(out)
|
colors.Faint.Println(out)
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
@@ -146,7 +147,7 @@ func (b Backend) Exec(args []string) error {
|
|||||||
colors.Error.Println(out)
|
colors.Error.Println(out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if VERBOSE {
|
if flags.VERBOSE {
|
||||||
colors.Faint.Println(out)
|
colors.Faint.Println(out)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@@ -157,30 +158,50 @@ func (b Backend) ExecDocker(l Location, args []string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
volume := l.getVolumeName()
|
volume := l.From[0]
|
||||||
path, _ := l.getPath()
|
|
||||||
options := ExecuteOptions{
|
options := ExecuteOptions{
|
||||||
Command: "docker",
|
Command: "docker",
|
||||||
Envs: env,
|
Envs: env,
|
||||||
}
|
}
|
||||||
|
dir := "/data"
|
||||||
|
args = append([]string{"restic"}, args...)
|
||||||
docker := []string{
|
docker := []string{
|
||||||
"run", "--rm",
|
"run", "--rm",
|
||||||
|
"--pull", "always",
|
||||||
"--entrypoint", "ash",
|
"--entrypoint", "ash",
|
||||||
"--workdir", path,
|
"--workdir", dir,
|
||||||
"--volume", volume + ":" + path,
|
"--volume", volume + ":" + dir,
|
||||||
}
|
}
|
||||||
|
// Use of docker host, not the container host
|
||||||
if hostname, err := os.Hostname(); err == nil {
|
if hostname, err := os.Hostname(); err == nil {
|
||||||
docker = append(docker, "--hostname", hostname)
|
docker = append(docker, "--hostname", hostname)
|
||||||
}
|
}
|
||||||
if b.Type == "local" {
|
switch b.Type {
|
||||||
|
case "local":
|
||||||
actual := env["RESTIC_REPOSITORY"]
|
actual := env["RESTIC_REPOSITORY"]
|
||||||
docker = append(docker, "--volume", actual+":"+"/repo")
|
docker = append(docker, "--volume", actual+":"+"/repo")
|
||||||
env["RESTIC_REPOSITORY"] = "/repo"
|
env["RESTIC_REPOSITORY"] = "/repo"
|
||||||
|
case "b2":
|
||||||
|
case "s3":
|
||||||
|
case "azure":
|
||||||
|
case "gs":
|
||||||
|
// No additional setup needed
|
||||||
|
case "rclone":
|
||||||
|
// Read host rclone config and mount it into the container
|
||||||
|
configFile, err := ExecuteCommand(ExecuteOptions{Command: "rclone"}, "config", "file")
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
splitted := strings.Split(strings.TrimSpace(configFile), "\n")
|
||||||
|
configFilePath := splitted[len(splitted)-1]
|
||||||
|
docker = append(docker, "--volume", configFilePath+":"+"/root/.config/rclone/rclone.conf:ro")
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("Backend type \"%s\" is not supported as volume endpoint", b.Type)
|
||||||
}
|
}
|
||||||
for key, value := range env {
|
for key, value := range env {
|
||||||
docker = append(docker, "--env", key+"="+value)
|
docker = append(docker, "--env", key+"="+value)
|
||||||
}
|
}
|
||||||
docker = append(docker, "restic/restic", "-c", "restic "+strings.Join(args, " "))
|
docker = append(docker, "cupcakearmy/autorestic:"+VERSION, "-c", strings.Join(args, " "))
|
||||||
out, err := ExecuteCommand(options, docker...)
|
out, err := ExecuteCommand(options, docker...)
|
||||||
return out, err
|
return out, err
|
||||||
}
|
}
|
||||||
|
@@ -87,9 +87,23 @@ func downloadAndInstallAsset(body GithubRelease, name string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
to := path.Join(INSTALL_PATH, name)
|
to := path.Join(INSTALL_PATH, name)
|
||||||
defer os.Remove(to) // Delete if current, ignore error if file does not exits.
|
defer os.Remove(tmp.Name()) // Cleanup temporary file after thread exits
|
||||||
if err := os.Rename(tmp.Name(), to); err != nil {
|
if err := os.Rename(tmp.Name(), to); err != nil {
|
||||||
return nil
|
colors.Error.Printf("os.Rename() failed (%v), retrying with io.Copy()\n", err.Error())
|
||||||
|
var src *os.File
|
||||||
|
var dst *os.File
|
||||||
|
if src, err = os.Open(tmp.Name()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if dst, err = os.Create(to); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := io.Copy(dst, src); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := os.Chmod(to, 0755); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
colors.Success.Printf("Successfully installed '%s' under %s\n", name, INSTALL_PATH)
|
colors.Success.Printf("Successfully installed '%s' under %s\n", name, INSTALL_PATH)
|
||||||
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||||
|
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/mitchellh/go-homedir"
|
"github.com/mitchellh/go-homedir"
|
||||||
@@ -16,16 +17,13 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "1.4.1"
|
const VERSION = "1.5.6"
|
||||||
|
|
||||||
var CI bool = false
|
|
||||||
var VERBOSE bool = false
|
|
||||||
var CRON_LEAN bool = false
|
|
||||||
|
|
||||||
type OptionMap map[string][]interface{}
|
type OptionMap map[string][]interface{}
|
||||||
type Options map[string]OptionMap
|
type Options map[string]OptionMap
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
|
Version string `yaml:"version"`
|
||||||
Extras interface{} `yaml:"extras"`
|
Extras interface{} `yaml:"extras"`
|
||||||
Locations map[string]Location `yaml:"locations"`
|
Locations map[string]Location `yaml:"locations"`
|
||||||
Backends map[string]Backend `yaml:"backends"`
|
Backends map[string]Backend `yaml:"backends"`
|
||||||
@@ -35,29 +33,59 @@ type Config struct {
|
|||||||
var once sync.Once
|
var once sync.Once
|
||||||
var config *Config
|
var config *Config
|
||||||
|
|
||||||
|
func exitConfig(err error, msg string) {
|
||||||
|
if err != nil {
|
||||||
|
colors.Error.Println(err)
|
||||||
|
}
|
||||||
|
if msg != "" {
|
||||||
|
colors.Error.Println(msg)
|
||||||
|
}
|
||||||
|
lock.Unlock()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
func GetConfig() *Config {
|
func GetConfig() *Config {
|
||||||
|
|
||||||
if config == nil {
|
if config == nil {
|
||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
if err := viper.ReadInConfig(); err == nil {
|
if err := viper.ReadInConfig(); err == nil {
|
||||||
if !CRON_LEAN {
|
absConfig, _ := filepath.Abs(viper.ConfigFileUsed())
|
||||||
absConfig, _ := filepath.Abs(viper.ConfigFileUsed())
|
if !flags.CRON_LEAN {
|
||||||
colors.Faint.Println("Using config: \t", absConfig)
|
colors.Faint.Println("Using config: \t", absConfig)
|
||||||
// Load env file
|
}
|
||||||
envFile := filepath.Join(filepath.Dir(absConfig), ".autorestic.env")
|
// Load env file
|
||||||
err = godotenv.Load(envFile)
|
envFile := filepath.Join(filepath.Dir(absConfig), ".autorestic.env")
|
||||||
if err == nil {
|
err = godotenv.Load(envFile)
|
||||||
colors.Faint.Println("Using env:\t", envFile)
|
if err == nil {
|
||||||
}
|
colors.Faint.Println("Using env:\t", envFile)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return
|
cfgFileName := ".autorestic"
|
||||||
|
colors.Error.Println(
|
||||||
|
fmt.Sprintf(
|
||||||
|
"cannot find configuration file '%s.yml' or '%s.yaml'.",
|
||||||
|
cfgFileName, cfgFileName))
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var versionConfig interface{}
|
||||||
|
viper.UnmarshalKey("version", &versionConfig)
|
||||||
|
if versionConfig == nil {
|
||||||
|
exitConfig(nil, "no version specified in config file. please see docs on how to migrate")
|
||||||
|
}
|
||||||
|
version, ok := versionConfig.(int)
|
||||||
|
if !ok {
|
||||||
|
exitConfig(nil, "version specified in config file is not an int")
|
||||||
|
} else {
|
||||||
|
// Check for version
|
||||||
|
if version != 2 {
|
||||||
|
exitConfig(nil, "unsupported config version number. please check the docs for migration\nhttps://autorestic.vercel.app/migration/")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
config = &Config{}
|
config = &Config{}
|
||||||
if err := viper.UnmarshalExact(config); err != nil {
|
if err := viper.UnmarshalExact(config); err != nil {
|
||||||
colors.Error.Println("Could not parse config file!")
|
exitConfig(err, "Could not parse config file!")
|
||||||
lock.Unlock()
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -81,7 +109,11 @@ func (c *Config) Describe() {
|
|||||||
var tmp string
|
var tmp string
|
||||||
colors.PrimaryPrint(`Location: "%s"`, name)
|
colors.PrimaryPrint(`Location: "%s"`, name)
|
||||||
|
|
||||||
colors.PrintDescription("From", l.From)
|
tmp = ""
|
||||||
|
for _, path := range l.From {
|
||||||
|
tmp += fmt.Sprintf("\t%s %s\n", colors.Success.Sprint("←"), path)
|
||||||
|
}
|
||||||
|
colors.PrintDescription("From", tmp)
|
||||||
|
|
||||||
tmp = ""
|
tmp = ""
|
||||||
for _, to := range l.To {
|
for _, to := range l.To {
|
||||||
@@ -229,7 +261,7 @@ func (c *Config) SaveConfig() error {
|
|||||||
if err := CopyFile(file, file+".old"); err != nil {
|
if err := CopyFile(file, file+".old"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
colors.Secondary.Println("Saved a backup copy of your file next the the original.")
|
colors.Secondary.Println("Saved a backup copy of your file next to the original.")
|
||||||
|
|
||||||
viper.Set("backends", c.Backends)
|
viper.Set("backends", c.Backends)
|
||||||
viper.Set("locations", c.Locations)
|
viper.Set("locations", c.Locations)
|
||||||
@@ -269,3 +301,15 @@ func getOptions(options Options, key string) []string {
|
|||||||
}
|
}
|
||||||
return selected
|
return selected
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func combineOptions(key string, l Location, b Backend) []string {
|
||||||
|
// Priority: location > backend > global
|
||||||
|
var options []string
|
||||||
|
gFlags := getOptions(GetConfig().Global, key)
|
||||||
|
bFlags := getOptions(b.Options, key)
|
||||||
|
lFlags := getOptions(l.Options, key)
|
||||||
|
options = append(options, gFlags...)
|
||||||
|
options = append(options, bFlags...)
|
||||||
|
options = append(options, lFlags...)
|
||||||
|
return options
|
||||||
|
}
|
||||||
|
5
internal/flags/flags.go
Normal file
5
internal/flags/flags.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package flags
|
||||||
|
|
||||||
|
var CI bool = false
|
||||||
|
var VERBOSE bool = false
|
||||||
|
var CRON_LEAN bool = false
|
@@ -9,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||||
|
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||||
"github.com/cupcakearmy/autorestic/internal/metadata"
|
"github.com/cupcakearmy/autorestic/internal/metadata"
|
||||||
"github.com/robfig/cron"
|
"github.com/robfig/cron"
|
||||||
@@ -17,14 +18,14 @@ import (
|
|||||||
type LocationType string
|
type LocationType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TypeLocal LocationType = "local"
|
TypeLocal LocationType = "local"
|
||||||
TypeVolume LocationType = "volume"
|
TypeVolume LocationType = "volume"
|
||||||
VolumePrefix string = "volume:"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type HookArray = []string
|
type HookArray = []string
|
||||||
|
|
||||||
type Hooks struct {
|
type Hooks struct {
|
||||||
|
Dir string `yaml:"dir"`
|
||||||
Before HookArray `yaml:"before,omitempty"`
|
Before HookArray `yaml:"before,omitempty"`
|
||||||
After HookArray `yaml:"after,omitempty"`
|
After HookArray `yaml:"after,omitempty"`
|
||||||
Success HookArray `yaml:"success,omitempty"`
|
Success HookArray `yaml:"success,omitempty"`
|
||||||
@@ -33,7 +34,8 @@ type Hooks struct {
|
|||||||
|
|
||||||
type Location struct {
|
type Location struct {
|
||||||
name string `yaml:",omitempty"`
|
name string `yaml:",omitempty"`
|
||||||
From string `yaml:"from,omitempty"`
|
From []string `yaml:"from,omitempty"`
|
||||||
|
Type string `yaml:"type,omitempty"`
|
||||||
To []string `yaml:"to,omitempty"`
|
To []string `yaml:"to,omitempty"`
|
||||||
Hooks Hooks `yaml:"hooks,omitempty"`
|
Hooks Hooks `yaml:"hooks,omitempty"`
|
||||||
Cron string `yaml:"cron,omitempty"`
|
Cron string `yaml:"cron,omitempty"`
|
||||||
@@ -47,21 +49,32 @@ func GetLocation(name string) (Location, bool) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (l Location) validate() error {
|
func (l Location) validate() error {
|
||||||
if l.From == "" {
|
if len(l.From) == 0 {
|
||||||
return fmt.Errorf(`Location "%s" is missing "from" key`, l.name)
|
return fmt.Errorf(`Location "%s" is missing "from" key`, l.name)
|
||||||
}
|
}
|
||||||
if l.getType() == TypeLocal {
|
t, err := l.getType()
|
||||||
if from, err := GetPathRelativeToConfig(l.From); err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
}
|
||||||
if stat, err := os.Stat(from); err != nil {
|
switch t {
|
||||||
|
case TypeLocal:
|
||||||
|
for _, path := range l.From {
|
||||||
|
if from, err := GetPathRelativeToConfig(path); err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
||||||
if !stat.IsDir() {
|
if stat, err := os.Stat(from); err != nil {
|
||||||
return fmt.Errorf("\"%s\" is not valid directory for location \"%s\"", from, l.name)
|
return err
|
||||||
|
} else {
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return fmt.Errorf("\"%s\" is not valid directory for location \"%s\"", from, l.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
case TypeVolume:
|
||||||
|
if len(l.From) > 1 {
|
||||||
|
return fmt.Errorf(`location "%s" has more than one docker volume`, l.name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(l.To) == 0 {
|
if len(l.To) == 0 {
|
||||||
@@ -77,10 +90,17 @@ func (l Location) validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecuteHooks(commands []string, options ExecuteOptions) error {
|
func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error {
|
||||||
if len(commands) == 0 {
|
if len(commands) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
if l.Hooks.Dir != "" {
|
||||||
|
if dir, err := GetPathRelativeToConfig(l.Hooks.Dir); err != nil {
|
||||||
|
return err
|
||||||
|
} else {
|
||||||
|
options.Dir = dir
|
||||||
|
}
|
||||||
|
}
|
||||||
colors.Secondary.Println("\nRunning hooks")
|
colors.Secondary.Println("\nRunning hooks")
|
||||||
for _, command := range commands {
|
for _, command := range commands {
|
||||||
colors.Body.Println("> " + command)
|
colors.Body.Println("> " + command)
|
||||||
@@ -89,7 +109,7 @@ func ExecuteHooks(commands []string, options ExecuteOptions) error {
|
|||||||
colors.Error.Println(out)
|
colors.Error.Println(out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if VERBOSE {
|
if flags.VERBOSE {
|
||||||
colors.Faint.Println(out)
|
colors.Faint.Println(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,39 +117,38 @@ func ExecuteHooks(commands []string, options ExecuteOptions) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Location) getType() LocationType {
|
func (l Location) getType() (LocationType, error) {
|
||||||
if strings.HasPrefix(l.From, VolumePrefix) {
|
t := strings.ToLower(l.Type)
|
||||||
return TypeVolume
|
if t == "" || t == "local" {
|
||||||
|
return TypeLocal, nil
|
||||||
|
} else if t == "volume" {
|
||||||
|
return TypeVolume, nil
|
||||||
}
|
}
|
||||||
return TypeLocal
|
return "", fmt.Errorf("invalid location type \"%s\"", l.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Location) getVolumeName() string {
|
func buildTag(parts ...string) string {
|
||||||
return strings.TrimPrefix(l.From, VolumePrefix)
|
parts = append([]string{"ar"}, parts...)
|
||||||
|
return strings.Join(parts, ":")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Location) getPath() (string, error) {
|
func (l Location) getLocationTags() string {
|
||||||
t := l.getType()
|
return buildTag("location", l.name)
|
||||||
switch t {
|
|
||||||
case TypeLocal:
|
|
||||||
if path, err := GetPathRelativeToConfig(l.From); err != nil {
|
|
||||||
return "", err
|
|
||||||
} else {
|
|
||||||
return path, nil
|
|
||||||
}
|
|
||||||
case TypeVolume:
|
|
||||||
return "/volume/" + l.name + "/" + l.getVolumeName(), nil
|
|
||||||
}
|
|
||||||
return "", fmt.Errorf("could not get path for location \"%s\"", l.name)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Location) Backup(cron bool, specificBackend string) []error {
|
func (l Location) Backup(cron bool, specificBackend string) []error {
|
||||||
var errors []error
|
var errors []error
|
||||||
var backends []string
|
var backends []string
|
||||||
colors.PrimaryPrint(" Backing up location \"%s\" ", l.name)
|
colors.PrimaryPrint(" Backing up location \"%s\" ", l.name)
|
||||||
t := l.getType()
|
t, err := l.getType()
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
cwd, _ := GetPathRelativeToConfig(".")
|
||||||
options := ExecuteOptions{
|
options := ExecuteOptions{
|
||||||
Command: "bash",
|
Command: "bash",
|
||||||
|
Dir: cwd,
|
||||||
Envs: map[string]string{
|
Envs: map[string]string{
|
||||||
"AUTORESTIC_LOCATION": l.name,
|
"AUTORESTIC_LOCATION": l.name,
|
||||||
},
|
},
|
||||||
@@ -137,18 +156,11 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
|
|||||||
|
|
||||||
if err := l.validate(); err != nil {
|
if err := l.validate(); err != nil {
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
colors.Error.Print(err)
|
|
||||||
goto after
|
goto after
|
||||||
}
|
}
|
||||||
|
|
||||||
if t == TypeLocal {
|
|
||||||
dir, _ := GetPathRelativeToConfig(l.From)
|
|
||||||
colors.Faint.Printf("Executing under: \"%s\"\n", dir)
|
|
||||||
options.Dir = dir
|
|
||||||
}
|
|
||||||
|
|
||||||
// Hooks
|
// Hooks
|
||||||
if err := ExecuteHooks(l.Hooks.Before, options); err != nil {
|
if err := l.ExecuteHooks(l.Hooks.Before, options); err != nil {
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
goto after
|
goto after
|
||||||
}
|
}
|
||||||
@@ -173,31 +185,40 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
lFlags := getOptions(l.Options, "backup")
|
|
||||||
bFlags := getOptions(backend.Options, "backup")
|
|
||||||
cmd := []string{"backup"}
|
cmd := []string{"backup"}
|
||||||
cmd = append(cmd, lFlags...)
|
cmd = append(cmd, combineOptions("backup", l, backend)...)
|
||||||
cmd = append(cmd, bFlags...)
|
|
||||||
if cron {
|
if cron {
|
||||||
cmd = append(cmd, "--tag", "cron")
|
cmd = append(cmd, "--tag", buildTag("cron"))
|
||||||
}
|
}
|
||||||
cmd = append(cmd, ".")
|
cmd = append(cmd, "--tag", l.getLocationTags())
|
||||||
backupOptions := ExecuteOptions{
|
backupOptions := ExecuteOptions{
|
||||||
Dir: options.Dir,
|
|
||||||
Envs: env,
|
Envs: env,
|
||||||
}
|
}
|
||||||
|
|
||||||
var out string
|
var out string
|
||||||
|
|
||||||
switch t {
|
switch t {
|
||||||
case TypeLocal:
|
case TypeLocal:
|
||||||
|
for _, from := range l.From {
|
||||||
|
path, err := GetPathRelativeToConfig(from)
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, err)
|
||||||
|
goto after
|
||||||
|
}
|
||||||
|
cmd = append(cmd, path)
|
||||||
|
}
|
||||||
out, err = ExecuteResticCommand(backupOptions, cmd...)
|
out, err = ExecuteResticCommand(backupOptions, cmd...)
|
||||||
case TypeVolume:
|
case TypeVolume:
|
||||||
|
ok := CheckIfVolumeExists(l.From[0])
|
||||||
|
if !ok {
|
||||||
|
errors = append(errors, fmt.Errorf("volume \"%s\" does not exist", l.From[0]))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cmd = append(cmd, "/data")
|
||||||
out, err = backend.ExecDocker(l, cmd)
|
out, err = backend.ExecDocker(l, cmd)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
colors.Error.Println(out)
|
colors.Error.Println(out)
|
||||||
errors = append(errors, err)
|
errors = append(errors, fmt.Errorf("%s@%s:\n%s%s", l.name, backend.name, out, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,13 +228,13 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
|
|||||||
options.Envs[k+"_"+fmt.Sprint(i)] = v
|
options.Envs[k+"_"+fmt.Sprint(i)] = v
|
||||||
options.Envs[k+"_"+strings.ToUpper(backend.name)] = v
|
options.Envs[k+"_"+strings.ToUpper(backend.name)] = v
|
||||||
}
|
}
|
||||||
if VERBOSE {
|
if flags.VERBOSE {
|
||||||
colors.Faint.Println(out)
|
colors.Faint.Println(out)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After hooks
|
// After hooks
|
||||||
if err := ExecuteHooks(l.Hooks.After, options); err != nil {
|
if err := l.ExecuteHooks(l.Hooks.After, options); err != nil {
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,22 +245,19 @@ after:
|
|||||||
} else {
|
} else {
|
||||||
commands = l.Hooks.Success
|
commands = l.Hooks.Success
|
||||||
}
|
}
|
||||||
if err := ExecuteHooks(commands, options); err != nil {
|
if err := l.ExecuteHooks(commands, options); err != nil {
|
||||||
errors = append(errors, err)
|
errors = append(errors, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
colors.Success.Println("Done")
|
if len(errors) == 0 {
|
||||||
|
colors.Success.Println("Done")
|
||||||
|
}
|
||||||
return errors
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Location) Forget(prune bool, dry bool) error {
|
func (l Location) Forget(prune bool, dry bool) error {
|
||||||
colors.PrimaryPrint("Forgetting for location \"%s\"", l.name)
|
colors.PrimaryPrint("Forgetting for location \"%s\"", l.name)
|
||||||
|
|
||||||
path, err := l.getPath()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, to := range l.To {
|
for _, to := range l.To {
|
||||||
backend, _ := GetBackend(to)
|
backend, _ := GetBackend(to)
|
||||||
colors.Secondary.Printf("For backend \"%s\"\n", backend.name)
|
colors.Secondary.Printf("For backend \"%s\"\n", backend.name)
|
||||||
@@ -250,19 +268,16 @@ func (l Location) Forget(prune bool, dry bool) error {
|
|||||||
options := ExecuteOptions{
|
options := ExecuteOptions{
|
||||||
Envs: env,
|
Envs: env,
|
||||||
}
|
}
|
||||||
lFlags := getOptions(l.Options, "forget")
|
cmd := []string{"forget", "--tag", l.getLocationTags()}
|
||||||
bFlags := getOptions(backend.Options, "forget")
|
|
||||||
cmd := []string{"forget", "--path", path}
|
|
||||||
if prune {
|
if prune {
|
||||||
cmd = append(cmd, "--prune")
|
cmd = append(cmd, "--prune")
|
||||||
}
|
}
|
||||||
if dry {
|
if dry {
|
||||||
cmd = append(cmd, "--dry-run")
|
cmd = append(cmd, "--dry-run")
|
||||||
}
|
}
|
||||||
cmd = append(cmd, lFlags...)
|
cmd = append(cmd, combineOptions("forget", l, backend)...)
|
||||||
cmd = append(cmd, bFlags...)
|
|
||||||
out, err := ExecuteResticCommand(options, cmd...)
|
out, err := ExecuteResticCommand(options, cmd...)
|
||||||
if VERBOSE {
|
if flags.VERBOSE {
|
||||||
colors.Faint.Println(out)
|
colors.Faint.Println(out)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -282,7 +297,7 @@ func (l Location) hasBackend(backend string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l Location) Restore(to, from string, force bool) error {
|
func (l Location) Restore(to, from string, force bool, snapshot string) error {
|
||||||
if from == "" {
|
if from == "" {
|
||||||
from = l.To[0]
|
from = l.To[0]
|
||||||
} else if !l.hasBackend(from) {
|
} else if !l.hasBackend(from) {
|
||||||
@@ -293,16 +308,20 @@ func (l Location) Restore(to, from string, force bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
colors.PrimaryPrint("Restoring location \"%s\"", l.name)
|
|
||||||
|
|
||||||
backend, _ := GetBackend(from)
|
if snapshot == "" {
|
||||||
path, err := l.getPath()
|
snapshot = "latest"
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
colors.Secondary.Println("Restoring lastest snapshot")
|
|
||||||
colors.Body.Printf("%s → %s.\n", from, path)
|
colors.PrimaryPrint("Restoring location \"%s\"", l.name)
|
||||||
switch l.getType() {
|
backend, _ := GetBackend(from)
|
||||||
|
colors.Secondary.Printf("Restoring %s@%s → %s\n", snapshot, backend.name, to)
|
||||||
|
|
||||||
|
t, err := l.getType()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
case TypeLocal:
|
case TypeLocal:
|
||||||
// Check if target is empty
|
// Check if target is empty
|
||||||
if !force {
|
if !force {
|
||||||
@@ -322,9 +341,9 @@ func (l Location) Restore(to, from string, force bool) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
err = backend.Exec([]string{"restore", "--target", to, "--path", path, "latest"})
|
err = backend.Exec([]string{"restore", "--target", to, "--tag", l.getLocationTags(), snapshot})
|
||||||
case TypeVolume:
|
case TypeVolume:
|
||||||
_, err = backend.ExecDocker(l, []string{"restore", "--target", ".", "--path", path, "latest"})
|
_, err = backend.ExecDocker(l, []string{"restore", "--target", "/", "--tag", l.getLocationTags(), snapshot})
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -349,7 +368,7 @@ func (l Location) RunCron() error {
|
|||||||
lock.SetCron(l.name, now.Unix())
|
lock.SetCron(l.name, now.Unix())
|
||||||
l.Backup(true, "")
|
l.Backup(true, "")
|
||||||
} else {
|
} else {
|
||||||
if !CRON_LEAN {
|
if !flags.CRON_LEAN {
|
||||||
colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name)
|
colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -6,6 +6,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||||
|
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -19,8 +20,15 @@ func getLock() *viper.Viper {
|
|||||||
once.Do(func() {
|
once.Do(func() {
|
||||||
lock = viper.New()
|
lock = viper.New()
|
||||||
lock.SetDefault("running", false)
|
lock.SetDefault("running", false)
|
||||||
p := path.Dir(viper.ConfigFileUsed())
|
p := viper.ConfigFileUsed()
|
||||||
file = path.Join(p, ".autorestic.lock.yml")
|
if p == "" {
|
||||||
|
colors.Error.Println("cannot lock before reading config location")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
file = path.Join(path.Dir(p), ".autorestic.lock.yml")
|
||||||
|
if !flags.CRON_LEAN {
|
||||||
|
colors.Faint.Println("Using lock:\t", file)
|
||||||
|
}
|
||||||
lock.SetConfigFile(file)
|
lock.SetConfigFile(file)
|
||||||
lock.SetConfigType("yml")
|
lock.SetConfigType("yml")
|
||||||
lock.ReadInConfig()
|
lock.ReadInConfig()
|
||||||
|
@@ -8,6 +8,7 @@ import (
|
|||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||||
|
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||||
)
|
)
|
||||||
|
|
||||||
var RESTIC_BIN string
|
var RESTIC_BIN string
|
||||||
@@ -36,7 +37,7 @@ func ExecuteCommand(options ExecuteOptions, args ...string) (string, error) {
|
|||||||
cmd.Env = env
|
cmd.Env = env
|
||||||
cmd.Dir = options.Dir
|
cmd.Dir = options.Dir
|
||||||
|
|
||||||
if VERBOSE {
|
if flags.VERBOSE {
|
||||||
colors.Faint.Printf("> Executing: %s\n", cmd)
|
colors.Faint.Printf("> Executing: %s\n", cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,13 +61,13 @@ func ExecuteResticCommand(options ExecuteOptions, args ...string) (string, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CopyFile(from, to string) error {
|
func CopyFile(from, to string) error {
|
||||||
original, err := os.Open("original.txt")
|
original, err := os.Open(from)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
defer original.Close()
|
defer original.Close()
|
||||||
|
|
||||||
new, err := os.Create("new.txt")
|
new, err := os.Create(to)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -77,3 +78,8 @@ func CopyFile(from, to string) error {
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckIfVolumeExists(volume string) bool {
|
||||||
|
_, err := ExecuteCommand(ExecuteOptions{Command: "docker"}, "volume", "inspect", volume)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
Reference in New Issue
Block a user