mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2025-09-06 10:30:39 +00:00
Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
70eb9e441f | |||
be25af2d76 | |||
1c436369f0 | |||
6efcce07b7 | |||
|
dc6dd2e712 | ||
|
68628d3776 | ||
40988ef3b4 | |||
fad33fcdaa | |||
|
8cf8a77558 | ||
36998cfd3b | |||
cf03562ea2 | |||
188560395d | |||
bacbd0f806 | |||
93bf0388a4 | |||
ec8fdbd135 | |||
420934489c | |||
|
2ba767c8c3 | ||
b489c662c7 | |||
6862529a89 | |||
aa96a95600 | |||
89e32c298c | |||
873170c6d1 | |||
ea82fea8e1 | |||
a35edcaea5 | |||
86d44eafad | |||
e927fd5a64 | |||
d5e13d4e27 | |||
824c90904c | |||
58fb5e073a | |||
541a7c2a72 |
55
CHANGELOG.md
Normal file
55
CHANGELOG.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [1.0.6] - 2021-04-24
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Support for rclone
|
||||||
|
|
||||||
|
## [1.0.5] - 2021-04-24
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Correct exit code on backup failure and better logging/output/feedback.
|
||||||
|
- Check if `from` key is an actual directory.
|
||||||
|
|
||||||
|
## [1.0.4] - 2021-04-23
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Options to add rest username and password in config
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Don't add empty strings when saving config
|
||||||
|
|
||||||
|
## [1.0.3] - 2021-04-20
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Auto upgrade script was not working on linux as linux does not support writing to the binary that is being executed
|
||||||
|
|
||||||
|
## [1.0.2] - 2021-04-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Add the `cron` tag to backup to backups made with cron.
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- Don't unlock lockfile if process is already running.
|
||||||
|
|
||||||
|
## [1.0.1] - 2021-04-17
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- Completion command for various shells
|
||||||
|
|
||||||
|
## [1.0.0] - 2021-04-17
|
||||||
|
|
||||||
|
- Rewrite in go. See https://autorestic.vercel.app/upgrade for migration.
|
23
DEVELOPMENT.md
Normal file
23
DEVELOPMENT.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# Development
|
||||||
|
|
||||||
|
## Coding
|
||||||
|
|
||||||
|
The easiest way (imo) is to run [`gowatch`](https://github.com/silenceper/gowatch) in a separate terminal and the simply run `./autorestic ...`. `gowatch` will watch the code and automatically rebuild the binary when changes are saved to disk.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go run build/build.go
|
||||||
|
```
|
||||||
|
|
||||||
|
This will build and compress binaries for multiple platforms. The output will be put in the `dist` folder.
|
||||||
|
|
||||||
|
## Releasing
|
||||||
|
|
||||||
|
Releases are automatically built by the github workflow and uploaded to the release.
|
||||||
|
|
||||||
|
1. Bump `VERSION` in `internal/config.go`.
|
||||||
|
2. Update `CHANGELOG.md`
|
||||||
|
3. Commit to master
|
||||||
|
4. Create a new release with the `v1.2.3` tag and mark as draft.
|
||||||
|
5. The Github action will build the binaries, upload and mark the release as ready when done.
|
22
README.md
22
README.md
@@ -16,10 +16,28 @@
|
|||||||
<br>
|
<br>
|
||||||
<br>
|
<br>
|
||||||
|
|
||||||
### Why / What?
|
### 💭 Why / What?
|
||||||
|
|
||||||
Autorestic is a wrapper around the amazing [restic](https://restic.net/). While being amazing the restic cli can be a bit overwhelming and difficult to manage if you have many different location that you want to backup to multiple locations. This utility is aimed at making this easier 🙂
|
Autorestic is a wrapper around the amazing [restic](https://restic.net/). While being amazing the restic cli can be a bit overwhelming and difficult to manage if you have many different location that you want to backup to multiple locations. This utility is aimed at making this easier 🙂
|
||||||
|
|
||||||
### Questions / Support
|
### 🌈 Features
|
||||||
|
|
||||||
|
- YAML config files, no CLI
|
||||||
|
- Incremental -> Minimal space is used
|
||||||
|
- Backup locations to multiple backends
|
||||||
|
- Snapshot policies and pruning
|
||||||
|
- Fully encrypted
|
||||||
|
- Pre/After hooks
|
||||||
|
- Exclude pattern/files
|
||||||
|
- Cron jobs for automatic backup
|
||||||
|
- Backup & Restore docker volume
|
||||||
|
- Generated completions for `[bash|zsh|fish|powershell]`
|
||||||
|
|
||||||
|
### ❓ Questions / Support
|
||||||
|
|
||||||
Check the [discussions page](https://github.com/cupcakearmy/autorestic/discussions)
|
Check the [discussions page](https://github.com/cupcakearmy/autorestic/discussions)
|
||||||
|
|
||||||
|
## Contributing / Developing
|
||||||
|
|
||||||
|
PRs, feature requests, etc. are welcomed :)
|
||||||
|
Have a look at [the dev docs](./DEVELOPMENT.md)
|
||||||
|
@@ -1,9 +0,0 @@
|
|||||||
# Releasing
|
|
||||||
|
|
||||||
Releases are handled by the CD server with includes checksums for each binary.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git tag 0.x
|
|
||||||
git push
|
|
||||||
git push origin --tags
|
|
||||||
```
|
|
@@ -1,7 +1,10 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/cupcakearmy/autorestic/internal"
|
"github.com/cupcakearmy/autorestic/internal"
|
||||||
|
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@@ -18,9 +21,17 @@ var backupCmd = &cobra.Command{
|
|||||||
|
|
||||||
selected, err := internal.GetAllOrSelected(cmd, false)
|
selected, err := internal.GetAllOrSelected(cmd, false)
|
||||||
CheckErr(err)
|
CheckErr(err)
|
||||||
|
errors := 0
|
||||||
for _, name := range selected {
|
for _, name := range selected {
|
||||||
location, _ := internal.GetLocation(name)
|
location, _ := internal.GetLocation(name)
|
||||||
location.Backup()
|
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())
|
CheckErr(internal.CheckConfig())
|
||||||
|
|
||||||
colors.Success.Println("Everyting is fine.")
|
colors.Success.Println("Everything is fine.")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
70
cmd/completion.go
Normal file
70
cmd/completion.go
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var completionCmd = &cobra.Command{
|
||||||
|
Use: "completion [bash|zsh|fish|powershell]",
|
||||||
|
Short: "Generate completion script",
|
||||||
|
Long: `To load completions:
|
||||||
|
|
||||||
|
Bash:
|
||||||
|
|
||||||
|
$ source <(autorestic completion bash)
|
||||||
|
|
||||||
|
# To load completions for each session, execute once:
|
||||||
|
# Linux:
|
||||||
|
$ autorestic completion bash > /etc/bash_completion.d/autorestic
|
||||||
|
# macOS:
|
||||||
|
$ autorestic completion bash > /usr/local/etc/bash_completion.d/autorestic
|
||||||
|
|
||||||
|
Zsh:
|
||||||
|
|
||||||
|
# If shell completion is not already enabled in your environment,
|
||||||
|
# you will need to enable it. You can execute the following once:
|
||||||
|
|
||||||
|
$ echo "autoload -U compinit; compinit" >> ~/.zshrc
|
||||||
|
|
||||||
|
# To load completions for each session, execute once:
|
||||||
|
$ autorestic completion zsh > "${fpath[1]}/_autorestic"
|
||||||
|
|
||||||
|
# You will need to start a new shell for this setup to take effect.
|
||||||
|
|
||||||
|
fish:
|
||||||
|
|
||||||
|
$ autorestic completion fish | source
|
||||||
|
|
||||||
|
# To load completions for each session, execute once:
|
||||||
|
$ autorestic completion fish > ~/.config/fish/completions/autorestic.fish
|
||||||
|
|
||||||
|
PowerShell:
|
||||||
|
|
||||||
|
PS> autorestic completion powershell | Out-String | Invoke-Expression
|
||||||
|
|
||||||
|
# To load completions for every new session, run:
|
||||||
|
PS> autorestic completion powershell > autorestic.ps1
|
||||||
|
# and source this file from your PowerShell profile.
|
||||||
|
`,
|
||||||
|
DisableFlagsInUseLine: true,
|
||||||
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||||
|
Args: cobra.ExactValidArgs(1),
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
switch args[0] {
|
||||||
|
case "bash":
|
||||||
|
cmd.Root().GenBashCompletion(os.Stdout)
|
||||||
|
case "zsh":
|
||||||
|
cmd.Root().GenZshCompletion(os.Stdout)
|
||||||
|
case "fish":
|
||||||
|
cmd.Root().GenFishCompletion(os.Stdout, true)
|
||||||
|
case "powershell":
|
||||||
|
cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(completionCmd)
|
||||||
|
}
|
3209
docs/.codedoc/package-lock.json
generated
3209
docs/.codedoc/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codedoc/core": "^0.2.15"
|
"@codedoc/core": "^0.2.23"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,10 +20,16 @@ backends:
|
|||||||
name-of-backend:
|
name-of-backend:
|
||||||
type: b2
|
type: b2
|
||||||
path: 'myAccount:myBucket/my/path'
|
path: 'myAccount:myBucket/my/path'
|
||||||
B2_ACCOUNT_ID: backblaze_account_id
|
env:
|
||||||
B2_ACCOUNT_KEY: backblaze_account_key
|
B2_ACCOUNT_ID: backblaze_account_id
|
||||||
|
B2_ACCOUNT_KEY: backblaze_account_key
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 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.
|
||||||
|
|
||||||
## S3 / Minio
|
## S3 / Minio
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
@@ -33,8 +39,9 @@ backends:
|
|||||||
path: s3.amazonaws.com/bucket_name
|
path: s3.amazonaws.com/bucket_name
|
||||||
# Minio
|
# Minio
|
||||||
# path: http://localhost:9000/bucket_name
|
# path: http://localhost:9000/bucket_name
|
||||||
AWS_ACCESS_KEY_ID: my_key
|
env:
|
||||||
AWS_SECRET_ACCESS_KEY: my_secret
|
AWS_ACCESS_KEY_ID: my_key
|
||||||
|
AWS_SECRET_ACCESS_KEY: my_secret
|
||||||
```
|
```
|
||||||
|
|
||||||
## SFTP
|
## SFTP
|
||||||
@@ -57,6 +64,21 @@ backends:
|
|||||||
name-of-backend:
|
name-of-backend:
|
||||||
type: rest
|
type: rest
|
||||||
path: http://localhost:8000/repo_name
|
path: http://localhost:8000/repo_name
|
||||||
|
# Or authenticated
|
||||||
|
path: https://user:pass@host:6969/path
|
||||||
|
```
|
||||||
|
|
||||||
|
Optionally you can set user and password separately
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
backends:
|
||||||
|
rest:
|
||||||
|
type: rest
|
||||||
|
path: http://localhost:6969/path
|
||||||
|
key: ...
|
||||||
|
rest:
|
||||||
|
user: user
|
||||||
|
password: pass
|
||||||
```
|
```
|
||||||
|
|
||||||
> :ToCPrevNext
|
> :ToCPrevNext
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# 💽 Backends
|
# 💽 Backends
|
||||||
|
|
||||||
Backends are the ouputs of the backup process. Each location needs at least one.
|
Backends are the outputs of the backup process. Each location needs at least one.
|
||||||
|
|
||||||
```yaml | .autorestic.yml
|
```yaml | .autorestic.yml
|
||||||
backends:
|
backends:
|
||||||
|
@@ -6,6 +6,6 @@ autorestic cron
|
|||||||
|
|
||||||
This command is mostly intended to be triggered by an automated system like systemd or crontab.
|
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.
|
||||||
|
|
||||||
> :ToCPrevNext
|
> :ToCPrevNext
|
||||||
|
@@ -26,3 +26,5 @@ Verbose mode will show the output of the native restic commands that are otherwi
|
|||||||
```bash
|
```bash
|
||||||
autorestic --verbose backup -a
|
autorestic --verbose backup -a
|
||||||
```
|
```
|
||||||
|
|
||||||
|
> :ToCPrevNext
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
# Restore
|
# Restore
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
autorestic restore [-l, --location] [--from backend] [--to <out dir>]
|
autorestic restore [-l, --location] [--from backend] [--to <out dir>] [-f, --force]
|
||||||
```
|
```
|
||||||
|
|
||||||
This will restore all the locations to the selected target. If for one location there are more than one backends specified autorestic will take the first one.
|
This will restore all the locations to the selected target. If for one location there are more than one backends specified autorestic will take the first one.
|
||||||
|
|
||||||
|
The `--to` path das to be empty as no data will be overwritten by default. If you are sure you can pass the `-f, --force` flag and the data will be overwritten in the destination. However note that this will overwrite all the data existent in the backup, not only the 1 file that is missing e.g.
|
||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
This amazing people helped the project!
|
This amazing people helped the project!
|
||||||
|
|
||||||
- @ChanceM [Docs]
|
- @agateblue - Docs, Pruning, S3
|
||||||
- @EliotBerriot [Docs, Pruning, S3]
|
- @jin-park-dev - Typos
|
||||||
|
- @sumnerboy12 - Typos
|
||||||
|
- @FuzzyMistborn - Typos
|
||||||
|
- @ChanceM - Typos
|
||||||
|
- @TheForcer - Typos
|
||||||
|
|
||||||
> :ToCPrevNext
|
> :ToCPrevNext
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
## List all the snapshots for all the backends
|
## List all the snapshots for all the backends
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
autorestic exec -a -- snapshots
|
autorestic exec -av -- snapshots
|
||||||
```
|
```
|
||||||
|
|
||||||
## Unlock a locked repository
|
## Unlock a locked repository
|
||||||
|
@@ -17,5 +17,6 @@ Autorestic is a wrapper around the amazing [restic](https://restic.net/). While
|
|||||||
- Exclude pattern/files
|
- Exclude pattern/files
|
||||||
- Cron jobs for automatic backup
|
- Cron jobs for automatic backup
|
||||||
- Backup & Restore docker volumes
|
- Backup & Restore docker volumes
|
||||||
|
- Generated completions for `[bash|zsh|fish|powershell]`
|
||||||
|
|
||||||
> :ToCPrevNext
|
> :ToCPrevNext
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Cron
|
# Cron
|
||||||
|
|
||||||
Often it is usefull to trigger backups autmatically. For this we can specify a `cron` attribute to each location.
|
Often it is usefully to trigger backups automatically. For this we can specify a `cron` attribute to each location.
|
||||||
|
|
||||||
```yaml | .autorestic.yml
|
```yaml | .autorestic.yml
|
||||||
locations:
|
locations:
|
||||||
@@ -14,11 +14,11 @@ Here is a awesome website with [some examples](https://crontab.guru/examples.htm
|
|||||||
|
|
||||||
## Installing the cron
|
## Installing the cron
|
||||||
|
|
||||||
**This has to be done only once, regadless of now many cros you have in your config file.**
|
**This has to be done only once, regardless of now many cron jobs you have in your config file.**
|
||||||
|
|
||||||
To actually enable cron jobs you need something to call `autorestic cron` on a timed shedule.
|
To actually enable cron jobs you need something to call `autorestic cron` on a timed schedule.
|
||||||
Note that the shedule has nothing to do with the `cron` attribute in each location.
|
Note that the schedule has nothing to do with the `cron` attribute in each location.
|
||||||
My advise would be to trigger the command every 5min, but if you have a cronjob that runs only once a week, it's probably enough to shedule it once a day.
|
My advise would be to trigger the command every 5min, but if you have a cronjob that runs only once a week, it's probably enough to schedule it once a day.
|
||||||
|
|
||||||
### Crontab
|
### Crontab
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Excluding files
|
# Excluding files
|
||||||
|
|
||||||
If you want to exclude certain files or folders it done easily by specifiyng the right flags in the location you desire to filter.
|
If you want to exclude certain files or folders it done easily by specifying the right flags in the location you desire to filter.
|
||||||
|
|
||||||
The flags are taken straight from the [restic cli exclude rules](https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files) so you can use any flag used there.
|
The flags are taken straight from the [restic cli exclude rules](https://restic.readthedocs.io/en/latest/040_backup.html#excluding-files) so you can use any flag used there.
|
||||||
|
|
||||||
|
@@ -14,11 +14,11 @@ locations:
|
|||||||
options:
|
options:
|
||||||
forget:
|
forget:
|
||||||
keep-last: 5 # always keep at least 5 snapshots
|
keep-last: 5 # always keep at least 5 snapshots
|
||||||
keep-hourly: 3 # keep 3 last hourly shapshots
|
keep-hourly: 3 # keep 3 last hourly snapshots
|
||||||
keep-daily: 4 # keep 4 last daily shapshots
|
keep-daily: 4 # keep 4 last daily snapshots
|
||||||
keep-weekly: 1 # keep 1 last weekly shapshots
|
keep-weekly: 1 # keep 1 last weekly snapshots
|
||||||
keep-monthly: 12 # keep 12 last monthly shapshots
|
keep-monthly: 12 # keep 12 last monthly snapshots
|
||||||
keep-yearly: 7 # keep 7 last yearly shapshots
|
keep-yearly: 7 # keep 7 last yearly snapshots
|
||||||
keep-within: '2w' # keep snapshots from the last 2 weeks
|
keep-within: '2w' # keep snapshots from the last 2 weeks
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -22,6 +22,6 @@ Paths can be absolute or relative. If relative they are resolved relative to the
|
|||||||
|
|
||||||
## `to`
|
## `to`
|
||||||
|
|
||||||
This is einther a single backend or an array of backends. The backends have to be configured in the same config file.
|
This is either a single backend or an array of backends. The backends have to be configured in the same config file.
|
||||||
|
|
||||||
> :ToCPrevNext
|
> :ToCPrevNext
|
||||||
|
@@ -15,7 +15,7 @@ vim .autorestic.yml
|
|||||||
For a quick overview:
|
For a quick overview:
|
||||||
|
|
||||||
- `locations` can be seen as the inputs and `backends` the output where the data is stored and backed up.
|
- `locations` can be seen as the inputs and `backends` the output where the data is stored and backed up.
|
||||||
- One `location` can have one or multiple `backends` for redudancy.
|
- One `location` can have one or multiple `backends` for redundancy.
|
||||||
- One `backend` can also be the target for multiple `locations`.
|
- One `backend` can also be the target for multiple `locations`.
|
||||||
|
|
||||||
> **⚠️ WARNING ⚠️**
|
> **⚠️ WARNING ⚠️**
|
||||||
@@ -24,11 +24,11 @@ For a quick overview:
|
|||||||
|
|
||||||
```yaml | .autorestic.yml
|
```yaml | .autorestic.yml
|
||||||
locations:
|
locations:
|
||||||
- name: home
|
home:
|
||||||
from: /home/me
|
from: /home/me
|
||||||
to: remote
|
to: remote
|
||||||
|
|
||||||
- name: important
|
important:
|
||||||
from: /path/to/important/stuff
|
from: /path/to/important/stuff
|
||||||
to:
|
to:
|
||||||
- remote
|
- remote
|
||||||
@@ -39,8 +39,9 @@ backends:
|
|||||||
type: s3
|
type: s3
|
||||||
path: 's3.amazonaws.com/bucket_name'
|
path: 's3.amazonaws.com/bucket_name'
|
||||||
key: some-random-password-198rc79r8y1029c8yfewj8f1u0ef87yh198uoieufy
|
key: some-random-password-198rc79r8y1029c8yfewj8f1u0ef87yh198uoieufy
|
||||||
AWS_ACCESS_KEY_ID: account_id
|
env:
|
||||||
AWS_SECRET_ACCESS_KEY: account_key
|
AWS_ACCESS_KEY_ID: account_id
|
||||||
|
AWS_SECRET_ACCESS_KEY: account_key
|
||||||
|
|
||||||
- name: hdd
|
- name: hdd
|
||||||
type: local
|
type: local
|
||||||
|
12
docs/package-lock.json
generated
12
docs/package-lock.json
generated
@@ -131,9 +131,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/chalk": {
|
"node_modules/chalk": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
||||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"ansi-styles": "^4.1.0",
|
"ansi-styles": "^4.1.0",
|
||||||
"supports-color": "^7.1.0"
|
"supports-color": "^7.1.0"
|
||||||
@@ -1153,9 +1153,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"chalk": {
|
"chalk": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
|
||||||
"integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
|
"integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ansi-styles": "^4.1.0",
|
"ansi-styles": "^4.1.0",
|
||||||
"supports-color": "^7.1.0"
|
"supports-color": "^7.1.0"
|
||||||
|
@@ -36,4 +36,4 @@ bzip2 -fd "${OUT_FILE}.bz2"
|
|||||||
chmod +x ${OUT_FILE}
|
chmod +x ${OUT_FILE}
|
||||||
|
|
||||||
autorestic install
|
autorestic install
|
||||||
echo "Succefsully installed autorestic"
|
echo "Successfully installed autorestic"
|
||||||
|
@@ -4,19 +4,25 @@ import (
|
|||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||||
"github.com/spf13/viper"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type BackendRest struct {
|
||||||
|
User string `yaml:"user,omitempty"`
|
||||||
|
Password string `yaml:"password,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type Backend struct {
|
type Backend struct {
|
||||||
name string
|
name string
|
||||||
Type string `mapstructure:"type,omitempty"`
|
Type string `yaml:"type,omitempty"`
|
||||||
Path string `mapstructure:"path,omitempty"`
|
Path string `yaml:"path,omitempty"`
|
||||||
Key string `mapstructure:"key,omitempty"`
|
Key string `yaml:"key,omitempty"`
|
||||||
Env map[string]string `mapstructure:"env,omitempty"`
|
Env map[string]string `yaml:"env,omitempty"`
|
||||||
|
Rest BackendRest `yaml:"rest,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBackend(name string) (Backend, bool) {
|
func GetBackend(name string) (Backend, bool) {
|
||||||
@@ -29,7 +35,20 @@ func (b Backend) generateRepo() (string, error) {
|
|||||||
switch b.Type {
|
switch b.Type {
|
||||||
case "local":
|
case "local":
|
||||||
return GetPathRelativeToConfig(b.Path)
|
return GetPathRelativeToConfig(b.Path)
|
||||||
case "b2", "azure", "gs", "s3", "sftp", "rest":
|
case "rest":
|
||||||
|
parsed, err := url.Parse(b.Path)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if b.Rest.User != "" {
|
||||||
|
if b.Rest.Password == "" {
|
||||||
|
parsed.User = url.User(b.Rest.User)
|
||||||
|
} else {
|
||||||
|
parsed.User = url.UserPassword(b.Rest.User, b.Rest.Password)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s:%s", b.Type, parsed.String()), nil
|
||||||
|
case "b2", "azure", "gs", "s3", "sftp", "rclone":
|
||||||
return fmt.Sprintf("%s:%s", b.Type, b.Path), nil
|
return fmt.Sprintf("%s:%s", b.Type, b.Path), nil
|
||||||
default:
|
default:
|
||||||
return "", fmt.Errorf("backend type \"%s\" is invalid", b.Type)
|
return "", fmt.Errorf("backend type \"%s\" is invalid", b.Type)
|
||||||
@@ -70,15 +89,10 @@ func (b Backend) validate() error {
|
|||||||
c := GetConfig()
|
c := GetConfig()
|
||||||
tmp := c.Backends[b.name]
|
tmp := c.Backends[b.name]
|
||||||
tmp.Key = key
|
tmp.Key = key
|
||||||
tmp.name = ""
|
|
||||||
c.Backends[b.name] = tmp
|
c.Backends[b.name] = tmp
|
||||||
file := viper.ConfigFileUsed()
|
if err := c.SaveConfig(); 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.")
|
|
||||||
viper.Set("backends", c.Backends)
|
|
||||||
viper.WriteConfig()
|
|
||||||
}
|
}
|
||||||
env, err := b.getEnv()
|
env, err := b.getEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -107,16 +121,20 @@ func (b Backend) Exec(args []string) error {
|
|||||||
}
|
}
|
||||||
options := ExecuteOptions{Envs: env}
|
options := ExecuteOptions{Envs: env}
|
||||||
out, err := ExecuteResticCommand(options, args...)
|
out, err := ExecuteResticCommand(options, args...)
|
||||||
|
if err != nil {
|
||||||
|
colors.Error.Println(out)
|
||||||
|
return err
|
||||||
|
}
|
||||||
if VERBOSE {
|
if VERBOSE {
|
||||||
colors.Faint.Println(out)
|
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()
|
env, err := b.getEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
volume := l.getVolumeName()
|
volume := l.getVolumeName()
|
||||||
path, _ := l.getPath()
|
path, _ := l.getPath()
|
||||||
@@ -143,8 +161,5 @@ func (b Backend) ExecDocker(l Location, args []string) error {
|
|||||||
}
|
}
|
||||||
docker = append(docker, "restic/restic", "-c", "restic "+strings.Join(args, " "))
|
docker = append(docker, "restic/restic", "-c", "restic "+strings.Join(args, " "))
|
||||||
out, err := ExecuteCommand(options, docker...)
|
out, err := ExecuteCommand(options, docker...)
|
||||||
if VERBOSE {
|
return out, err
|
||||||
colors.Faint.Println(out)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
@@ -72,14 +72,21 @@ func downloadAndInstallAsset(body GithubRelease, name string) error {
|
|||||||
// Uncompress
|
// Uncompress
|
||||||
bz := bzip2.NewReader(resp.Body)
|
bz := bzip2.NewReader(resp.Body)
|
||||||
|
|
||||||
// Save binary
|
// Save to tmp
|
||||||
file, err := os.Create(path.Join(INSTALL_PATH, name))
|
// Linux does not support overwriting the file that is currently being overwritten, but it can be deleted and a new one moved in its place.
|
||||||
|
tmp, err := ioutil.TempFile(os.TempDir(), "autorestic-")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
file.Chmod(0755)
|
defer tmp.Close()
|
||||||
defer file.Close()
|
tmp.Chmod(0755)
|
||||||
io.Copy(file, bz)
|
io.Copy(tmp, bz)
|
||||||
|
|
||||||
|
to := path.Join(INSTALL_PATH, name)
|
||||||
|
os.Remove(to) // Delete if current, ignore error if file does not exits.
|
||||||
|
if err := os.Rename(tmp.Name(), to); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
colors.Success.Printf("Successfully installed '%s' under %s\n", name, INSTALL_PATH)
|
colors.Success.Printf("Successfully installed '%s' under %s\n", name, INSTALL_PATH)
|
||||||
return nil
|
return nil
|
||||||
|
@@ -12,14 +12,14 @@ import (
|
|||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
const VERSION = "1.0.0"
|
const VERSION = "1.0.6"
|
||||||
|
|
||||||
var CI bool = false
|
var CI bool = false
|
||||||
var VERBOSE bool = false
|
var VERBOSE bool = false
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Locations map[string]Location `mapstructure:"locations"`
|
Locations map[string]Location `yaml:"locations"`
|
||||||
Backends map[string]Backend `mapstructure:"backends"`
|
Backends map[string]Backend `yaml:"backends"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var once sync.Once
|
var once sync.Once
|
||||||
@@ -197,3 +197,16 @@ func AddFlagsToCommand(cmd *cobra.Command, backend bool) {
|
|||||||
cmd.PersistentFlags().StringSliceP("location", "l", []string{}, "Locations")
|
cmd.PersistentFlags().StringSliceP("location", "l", []string{}, "Locations")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Config) SaveConfig() error {
|
||||||
|
file := viper.ConfigFileUsed()
|
||||||
|
if err := CopyFile(file, file+".old"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
colors.Secondary.Println("Saved a backup copy of your file next the the original.")
|
||||||
|
|
||||||
|
viper.Set("backends", c.Backends)
|
||||||
|
viper.Set("locations", c.Locations)
|
||||||
|
|
||||||
|
return viper.WriteConfig()
|
||||||
|
}
|
||||||
|
@@ -24,19 +24,19 @@ const (
|
|||||||
type HookArray = []string
|
type HookArray = []string
|
||||||
|
|
||||||
type Hooks struct {
|
type Hooks struct {
|
||||||
Before HookArray `mapstructure:"before"`
|
Before HookArray `yaml:"before"`
|
||||||
After HookArray `mapstructure:"after"`
|
After HookArray `yaml:"after"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Options map[string]map[string][]string
|
type Options map[string]map[string][]string
|
||||||
|
|
||||||
type Location struct {
|
type Location struct {
|
||||||
name string `mapstructure:",omitempty"`
|
name string `yaml:",omitempty"`
|
||||||
From string `mapstructure:"from,omitempty"`
|
From string `yaml:"from,omitempty"`
|
||||||
To []string `mapstructure:"to,omitempty"`
|
To []string `yaml:"to,omitempty"`
|
||||||
Hooks Hooks `mapstructure:"hooks,omitempty"`
|
Hooks Hooks `yaml:"hooks,omitempty"`
|
||||||
Cron string `mapstructure:"cron,omitempty"`
|
Cron string `yaml:"cron,omitempty"`
|
||||||
Options Options `mapstructure:"options,omitempty"`
|
Options Options `yaml:"options,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetLocation(name string) (Location, bool) {
|
func GetLocation(name string) (Location, bool) {
|
||||||
@@ -49,6 +49,18 @@ func (l Location) validate(c *Config) error {
|
|||||||
if l.From == "" {
|
if l.From == "" {
|
||||||
return fmt.Errorf(`Location "%s" is missing "from" key`, l.name)
|
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 {
|
if len(l.To) == 0 {
|
||||||
return fmt.Errorf(`Location "%s" has no "to" targets`, l.name)
|
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 {
|
for _, command := range commands {
|
||||||
colors.Body.Println("> " + command)
|
colors.Body.Println("> " + command)
|
||||||
out, err := ExecuteCommand(options, "-c", command)
|
out, err := ExecuteCommand(options, "-c", command)
|
||||||
|
if err != nil {
|
||||||
|
colors.Error.Println(out)
|
||||||
|
return err
|
||||||
|
}
|
||||||
if VERBOSE {
|
if VERBOSE {
|
||||||
colors.Faint.Println(out)
|
colors.Faint.Println(out)
|
||||||
}
|
}
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
colors.Body.Println("")
|
colors.Body.Println("")
|
||||||
return nil
|
return nil
|
||||||
@@ -118,7 +131,7 @@ 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() error {
|
func (l Location) Backup(cron bool) 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,6 +160,9 @@ func (l Location) Backup() error {
|
|||||||
flags := l.getOptions("backup")
|
flags := l.getOptions("backup")
|
||||||
cmd := []string{"backup"}
|
cmd := []string{"backup"}
|
||||||
cmd = append(cmd, flags...)
|
cmd = append(cmd, flags...)
|
||||||
|
if cron {
|
||||||
|
cmd = append(cmd, "--tag", "cron")
|
||||||
|
}
|
||||||
cmd = append(cmd, ".")
|
cmd = append(cmd, ".")
|
||||||
backupOptions := ExecuteOptions{
|
backupOptions := ExecuteOptions{
|
||||||
Dir: options.Dir,
|
Dir: options.Dir,
|
||||||
@@ -158,16 +174,16 @@ func (l Location) Backup() error {
|
|||||||
switch t {
|
switch t {
|
||||||
case TypeLocal:
|
case TypeLocal:
|
||||||
out, err = ExecuteResticCommand(backupOptions, cmd...)
|
out, err = ExecuteResticCommand(backupOptions, cmd...)
|
||||||
if VERBOSE {
|
|
||||||
colors.Faint.Println(out)
|
|
||||||
}
|
|
||||||
case TypeVolume:
|
case TypeVolume:
|
||||||
err = backend.ExecDocker(l, cmd)
|
out, err = backend.ExecDocker(l, cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
colors.Error.Println(out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if VERBOSE {
|
||||||
|
colors.Faint.Println(out)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// After hooks
|
// After hooks
|
||||||
@@ -268,7 +284,7 @@ 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, "--path", path, "latest"})
|
||||||
case TypeVolume:
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -291,7 +307,7 @@ func (l Location) RunCron() error {
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
if now.After(next) {
|
if now.After(next) {
|
||||||
lock.SetCron(l.name, now.Unix())
|
lock.SetCron(l.name, now.Unix())
|
||||||
l.Backup()
|
l.Backup(true)
|
||||||
} else {
|
} else {
|
||||||
colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name)
|
colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name)
|
||||||
}
|
}
|
||||||
|
@@ -1,10 +1,11 @@
|
|||||||
package lock
|
package lock
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,7 +34,8 @@ func setLock(locked bool) error {
|
|||||||
if locked {
|
if locked {
|
||||||
running := lock.GetBool("running")
|
running := lock.GetBool("running")
|
||||||
if running {
|
if running {
|
||||||
return errors.New("an instance is already running")
|
colors.Error.Println("an instance is already running. exiting")
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
lock.Set("running", locked)
|
lock.Set("running", locked)
|
||||||
|
Reference in New Issue
Block a user