Compare commits

..

56 Commits

Author SHA1 Message Date
b5604b8b9f update deps (#353) 2024-02-09 14:18:24 +01:00
Alexander Zhang
24220f6b62 Fix broken link in docs (#350) 2024-02-09 14:18:06 +01:00
ced20801c1 bump version 2024-01-11 19:17:53 +01:00
Christoph Loy
ce9140fa1e Fix handling of XDG_CONFIG_HOME (#347) 2024-01-11 19:01:59 +01:00
Matthias Liffers
046c79fd15 Add curl to docker image (#344) 2024-01-11 18:56:35 +01:00
Skye J
f8603425d1 Update installation.md to add AUR back (#348)
I have been maintaining the AUR package, so it is no longer deprecated.
2024-01-11 18:52:23 +01:00
068121d722 adjust base url 2023-10-11 21:50:43 +02:00
8eea7d33f8 delete old docs (#327)
* delete old docs

* new docs
2023-10-08 19:24:04 +02:00
Vladimir Mikryukov
fc8b5fdbe2 fix typos in cron.md (#316) 2023-10-02 14:10:51 +02:00
Christoph Loy
f67bb7f73c Bump version to 1.7.9 (#325) 2023-10-02 14:09:34 +02:00
Christoph Loy
530b1b646c Update restic to 0.16.0 (#324) 2023-10-02 11:08:28 +02:00
Major Hayden
3b57602fe8 Docs: Add Fedora installation choice (#320)
Fedora now has an autorestic package and users can install it directly
from Fedora's repositories.

Signed-off-by: Major Hayden <major@mhtx.net>
2023-08-01 13:10:12 +02:00
Mikel Olasagasti Uranga
045513234f Use Printf instead of Println (#318)
Testing in Fedora reports:

./root.go:58:4: (*github.com/fatih/color.Color).Println call has
possible Printf formatting directive %s
2023-07-31 10:03:18 +02:00
dependabot[bot]
78b0db50e0 Bump webpack from 5.75.0 to 5.76.1 in /docs/.codedoc (#295)
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-22 15:25:32 +01:00
dependabot[bot]
62dd371d51 Bump webpack from 5.75.0 to 5.76.1 in /docs (#296)
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-22 15:25:20 +01:00
dependabot[bot]
08766b75db Bump prismjs from 1.23.0 to 1.29.0 in /docs/.codedoc (#269)
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.23.0 to 1.29.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.23.0...v1.29.0)

---
updated-dependencies:
- dependency-name: prismjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-22 15:25:05 +01:00
b3b7c8df95 update versions 2023-03-05 17:52:40 +01:00
2c5266c9a0 fix build system 2023-03-05 17:22:03 +01:00
087b293c39 version bump 2023-03-02 15:03:57 +01:00
Andreas
112a69d743 Allow env variables in path for rest backend (#280) 2023-03-02 15:00:39 +01:00
dependabot[bot]
37d55c691f Bump golang.org/x/text from 0.3.7 to 0.3.8 (#289)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.3.7...v0.3.8)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 20:22:18 +01:00
dependabot[bot]
715b6f791c Bump golang from 1.19-alpine to 1.20-alpine (#287)
Bumps golang from 1.19-alpine to 1.20-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 20:22:07 +01:00
dependabot[bot]
38b38c6805 Bump restic/restic from 0.15.0 to 0.15.1 (#286)
Bumps restic/restic from 0.15.0 to 0.15.1.

---
updated-dependencies:
- dependency-name: restic/restic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 20:21:49 +01:00
c82db56069 Update config.go 2023-01-19 09:44:05 +01:00
27821dc3ef changelog 2023-01-18 22:45:46 +01:00
866975d32d update deps 2023-01-18 22:43:56 +01:00
dependabot[bot]
955ac0e323 Bump restic/restic from 0.14.0 to 0.15.0 (#283)
Bumps restic/restic from 0.14.0 to 0.15.0.

---
updated-dependencies:
- dependency-name: restic/restic
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 22:39:38 +01:00
Mariusz Kozakowski
1512db5b55 Fix regexp for AddedExtractor (#284)
Co-authored-by: mariom <11mariom@gmail.com>
2023-01-18 22:39:12 +01:00
dependabot[bot]
046331748c Bump golang from 1.18-alpine to 1.19-alpine (#268)
Bumps golang from 1.18-alpine to 1.19-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-09 17:31:54 +01:00
72a40eaaa2 update deps 2022-12-09 17:29:07 +01:00
ec61effe22 Merge branch 'master' of https://github.com/cupcakearmy/autorestic 2022-12-09 17:24:07 +01:00
d6acab94a5 use restic as base image 2022-12-09 17:24:03 +01:00
Daniel Brennand
d0d2fcf0f2 docs: add dbrennand.autorestic role (#250) 2022-10-29 21:32:48 +02:00
58fd41fafb consistent casing (#247) 2022-10-18 16:23:27 +02:00
Šimon Woidig
d0c4a32879 Update install.sh (#246)
Add check for bzip2 command existance
2022-10-18 16:17:35 +02:00
74979e9a2a Update config.go 2022-10-17 15:03:17 +02:00
Romain de Laage
3732dcf6ff Check for errors on forget after having backuped (#241) 2022-10-17 15:02:47 +02:00
Andreas Wagner
874ed52e3b add more architectures for linux build process (#243)
* add more architectures for linux build process

equivalent to 7d665fa1f4/helpers/build-release-binaries/main.go

* add solaris and s390x

Co-authored-by: Nicco <hi@nicco.io>
2022-10-06 15:41:01 +01:00
4d9a2b828e Update config.go 2022-09-13 15:16:21 +02:00
Chosto
b830667264 Use options when calling check or init (#199) 2022-09-13 15:15:02 +02:00
John Burkhardt
83eeb847ac Add command line flag to override docker image. (#233) 2022-09-13 15:04:12 +02:00
shahvirb
a89ba5a40a Update forget.md keep-within argument to be '14d' (#236)
keep-within duration can only have units 'y', 'm', 'd', and 'h'. The current documentation of '2w' yields:
```invalid argument "2w" for "--keep-within" flag: invalid unit 'w' found after number 2```

https://restic.readthedocs.io/en/latest/060_forget.html#removing-snapshots-according-to-a-policy
2022-09-13 14:59:46 +02:00
Romain de Laage
6990bf6adc Check for errors and forward on exec command (#227)
fix #226
2022-08-26 17:09:26 +02:00
Romain de Laage
2f407cf211 Add forget for copy backend (#223)
fix #221
2022-08-24 12:09:32 +02:00
489f3078fe bump version 2022-08-24 12:03:11 +02:00
Romain de Laage
e07dd0d991 Don't check if path is a directory (#220)
We only run a stat on the path to check there is no error
2022-08-22 17:28:38 +02:00
kenc
2b9dc9f17c Add test for locking behaviour (#211) 2022-06-27 09:03:34 +02:00
kenc
465bc037c2 Add lock tests (#209)
* Add lock tests

* Refactor setLock to accept key value pairs

This allows SetCron and Lock to use the same function setLockValue. It
also removes the need to call getLock explicitly in tests by returning
the lock object.
2022-06-06 12:59:47 +02:00
kenc
37a043afff Add location unit tests (#208) 2022-06-02 17:05:44 +02:00
kenc
e91b632181 Add backend unit tests (#207) 2022-06-01 14:58:38 +02:00
kenc
2b30998b9a Add options parsing unit tests (#205)
* Add options parsing unit tests

* Refactor into subtests
2022-05-30 12:56:25 +02:00
Varac
49b37a0a9a Add varacs ansible role (#201)
Co-authored-by: Nicco <hi@nicco.io>
2022-05-24 13:34:10 +02:00
3bc091f826 lean flag 2022-04-27 00:55:31 +02:00
5bcf5c9217 1.7.0 (#188)
* stream the output (#186)

* dont duplicate global flags (#187)

* docs for tagging

* fix self update path (#190)

* version bump & changelog
2022-04-27 00:48:52 +02:00
ff2e3714d1 1.6.2 2022-04-14 17:44:54 +02:00
2e6764223d bump go version in docker 2022-04-14 12:05:19 +02:00
89 changed files with 4059 additions and 12468 deletions

8
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -3,19 +3,19 @@ name: Main
on:
push:
tags:
- "v*.*.*"
- 'v*.*.*'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
uses: docker/setup-buildx-action@v2
- name: Docker Labels
id: meta
uses: crazy-max/ghaction-docker-meta@v2
uses: crazy-max/ghaction-docker-meta@v4
with:
images: cupcakearmy/autorestic
tags: |
@@ -23,12 +23,12 @@ jobs:
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Login to DockerHub
uses: docker/login-action@v1
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v2
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64
push: true
@@ -37,17 +37,12 @@ jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: "^1.16.3"
go-version: '^1.21'
- name: Build
run: go run build/build.go
- name: Sign
uses: tristan-weil/ghaction-checksum-sign-artifact@v1.0.1
with:
path: dist/*
- name: Release
uses: softprops/action-gh-release@v1
with:

View File

@@ -5,6 +5,47 @@ 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.7.4] - 2023-01-18
### Fixed
- Transformer for extracting information. @11mariom
### Changed
- Bump docker restic version.
- Docs dependencies updated.
## [1.7.1] - 2022-04-27
### Fixed
- #178 Lean flag not working properly.
## [1.7.0] - 2022-04-27
### Changed
- #147 Stream output instead of buffering.
### Fixed
- #184 duplicate global options.
- #154 add docs for migration.
- #182 fix bug with upgrading custom restic with custom path.
## [1.6.2] - 2022-04-14
### Fixed
- Version bump in code.
## [1.6.1] - 2022-04-14
### Fixed
- Bump go version in docker file to 18.
## [1.6.0] - 2022-04-14
### Added

View File

@@ -1,4 +1,4 @@
FROM golang:1.17-alpine as builder
FROM golang:1.21-alpine as builder
WORKDIR /app
COPY go.* .
@@ -6,7 +6,8 @@ RUN go mod download
COPY . .
RUN go build
FROM alpine
RUN apk add --no-cache restic rclone bash openssh
FROM restic/restic:0.16.4
RUN apk add --no-cache rclone bash curl
COPY --from=builder /app/autorestic /usr/bin/autorestic
ENTRYPOINT []
CMD [ "autorestic" ]

View File

@@ -4,7 +4,11 @@
package main
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
@@ -17,23 +21,51 @@ import (
var DIR, _ = filepath.Abs("./dist")
var targets = map[string][]string{
// "aix": {"ppc64"}, // Not supported by fsnotify
"darwin": {"amd64", "arm64"},
"freebsd": {"386", "amd64", "arm"},
"linux": {"386", "amd64", "arm", "arm64"},
"linux": {"386", "amd64", "arm", "arm64", "ppc64le", "mips", "mipsle", "mips64", "mips64le", "s390x"},
"netbsd": {"386", "amd64"},
"openbsd": {"386", "amd64"},
// "windows": {"386", "amd64"}, // Not supported by autorestic
"solaris": {"amd64"},
}
type buildOptions struct {
Target, Arch, Version string
}
func build(options buildOptions, wg *sync.WaitGroup) {
fmt.Printf("Building %s %s\n", options.Target, options.Arch)
const (
CHECKSUM_MD5 = "MD5SUMS"
CHECKSUM_SHA_1 = "SHA1SUMS"
CHECKSUM_SHA_256 = "SHA256SUMS"
)
type Checksums struct {
filename, md5, sha1, sha256 string
}
func writeChecksums(checksums *[]Checksums) {
FILE_MD5, _ := os.OpenFile(path.Join(DIR, CHECKSUM_MD5), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
defer FILE_MD5.Close()
FILE_SHA1, _ := os.OpenFile(path.Join(DIR, CHECKSUM_SHA_1), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
defer FILE_SHA1.Close()
FILE_SHA256, _ := os.OpenFile(path.Join(DIR, CHECKSUM_SHA_256), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
defer FILE_SHA256.Close()
for _, checksum := range *checksums {
fmt.Fprintf(FILE_MD5, "%s %s\n", checksum.md5, checksum.filename)
fmt.Fprintf(FILE_SHA1, "%s %s\n", checksum.sha1, checksum.filename)
fmt.Fprintf(FILE_SHA256, "%s %s\n", checksum.sha256, checksum.filename)
}
}
func build(options buildOptions, wg *sync.WaitGroup, checksums *[]Checksums) {
defer wg.Done()
fmt.Printf("Building: %s %s\n", options.Target, options.Arch)
out := fmt.Sprintf("autorestic_%s_%s_%s", options.Version, options.Target, options.Arch)
out = path.Join(DIR, out)
out, _ = filepath.Abs(out)
fmt.Println(out)
// Build
{
@@ -62,22 +94,39 @@ func build(options buildOptions, wg *sync.WaitGroup) {
panic(err)
}
}
wg.Done()
// Checksum
{
file := out + ".bz2"
content, _ := ioutil.ReadFile(file)
*checksums = append(*checksums, Checksums{
filename: path.Base(file),
md5: fmt.Sprintf("%x", md5.Sum(content)),
sha1: fmt.Sprintf("%x", sha1.Sum(content)),
sha256: fmt.Sprintf("%x", sha256.Sum256(content)),
})
}
fmt.Printf("Built: %s\n", path.Base(out))
}
func main() {
os.RemoveAll(DIR)
v := internal.VERSION
checksums := []Checksums{}
// Build async
var wg sync.WaitGroup
for target, archs := range targets {
for _, arch := range archs {
wg.Add(1)
build(buildOptions{
go build(buildOptions{
Target: target,
Arch: arch,
Version: v,
}, &wg)
}, &wg, &checksums)
}
}
wg.Wait()
writeChecksums(&checksums)
}

View File

@@ -13,7 +13,6 @@ var cronCmd = &cobra.Command{
Long: `Intended to be mainly triggered by an automated system like systemd or crontab. For each location checks if a cron backup is due and runs it.`,
Run: func(cmd *cobra.Command, args []string) {
internal.GetConfig()
flags.CRON_LEAN, _ = cmd.Flags().GetBool("lean")
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()
@@ -25,5 +24,5 @@ var cronCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(cronCmd)
cronCmd.Flags().Bool("lean", false, "only output information about actual backups")
cronCmd.Flags().BoolVar(&flags.CRON_LEAN, "lean", false, "only output information about actual backups")
}

View File

@@ -1,6 +1,8 @@
package cmd
import (
"fmt"
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/lock"
@@ -18,10 +20,23 @@ var execCmd = &cobra.Command{
selected, err := internal.GetAllOrSelected(cmd, true)
CheckErr(err)
var errors []error
for _, name := range selected {
colors.PrimaryPrint(" Executing on \"%s\" ", name)
backend, _ := internal.GetBackend(name)
backend.Exec(args)
err := backend.Exec(args)
if err != nil {
errors = append(errors, err)
}
}
if len(errors) > 0 {
for _, err := range errors {
colors.Error.Printf("%s\n\n", err)
}
CheckErr(fmt.Errorf("%d errors were found", len(errors)))
}
},
}

View File

@@ -40,7 +40,8 @@ func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.autorestic.yml or ./.autorestic.yml)")
rootCmd.PersistentFlags().BoolVar(&flags.CI, "ci", false, "CI mode disabled interactive mode and colors and enables verbosity")
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(&flags.RESTIC_BIN, "restic-bin", "restic", "specify custom restic binary")
rootCmd.PersistentFlags().StringVar(&flags.DOCKER_IMAGE, "docker-image", "cupcakearmy/autorestic:"+internal.VERSION, "specify a custom docker image")
cobra.OnInitialize(initConfig)
}
@@ -54,28 +55,11 @@ func initConfig() {
viper.SetConfigFile(cfgFile)
viper.AutomaticEnv()
if viper.ConfigFileUsed() == "" {
colors.Error.Println("cannot read config file %s\n", cfgFile)
colors.Error.Printf("cannot read config file %s\n", cfgFile)
os.Exit(1)
}
} else {
configPaths := []string{"."}
// Home
if home, err := homedir.Dir(); err == nil {
configPaths = append(configPaths, home)
}
// XDG_CONFIG_HOME
{
prefix, found := os.LookupEnv("XDG_CONFIG_HOME")
if !found {
if home, err := homedir.Dir(); err != nil {
prefix = filepath.Join(home, ".config")
}
}
xdgConfig := filepath.Join(prefix, "autorestic")
configPaths = append(configPaths, xdgConfig)
}
configPaths := getConfigPaths()
for _, cfgPath := range configPaths {
viper.AddConfigPath(cfgPath)
}
@@ -87,3 +71,22 @@ func initConfig() {
viper.AutomaticEnv()
}
}
func getConfigPaths() []string {
result := []string{"."}
if home, err := homedir.Dir(); err == nil {
result = append(result, home)
}
{
xdgConfigHome, found := os.LookupEnv("XDG_CONFIG_HOME")
if !found {
if home, err := homedir.Dir(); err == nil {
xdgConfigHome = filepath.Join(home, ".config")
}
}
xdgConfig := filepath.Join(xdgConfigHome, "autorestic")
result = append(result, xdgConfig)
}
return result
}

36
cmd/root_test.go Normal file
View File

@@ -0,0 +1,36 @@
package cmd
import (
"github.com/mitchellh/go-homedir"
"os"
"path/filepath"
"slices"
"testing"
)
const xdgConfigHome = "XDG_CONFIG_HOME"
func assertContains(t *testing.T, array []string, element string) {
if !slices.Contains(array, element) {
t.Errorf("Expected %s to be contained in %s", element, array)
}
}
func TestConfigResolving(t *testing.T) {
t.Run("~/.config/autorestic is used if XDG_CONFIG_HOME is not set", func(t *testing.T) {
// Override env using testing so that env gets restored after test
t.Setenv(xdgConfigHome, "")
_ = os.Unsetenv("XDG_CONFIG_HOME")
configPaths := getConfigPaths()
homeDir, _ := homedir.Dir()
expectedConfigPath := filepath.Join(homeDir, ".config/autorestic")
assertContains(t, configPaths, expectedConfigPath)
})
t.Run("XDG_CONFIG_HOME is respected if set", func(t *testing.T) {
t.Setenv(xdgConfigHome, "/foo/bar")
configPaths := getConfigPaths()
assertContains(t, configPaths, filepath.Join("/", "foo", "bar", "autorestic"))
})
}

View File

@@ -1,15 +0,0 @@
import { build } from '@codedoc/core';
import { config } from './config';
import { installTheme$ } from './content/theme';
import { content } from './content';
build(config, content, installTheme$, {
resolve: {
modules: ['.codedoc/node_modules']
},
resolveLoader: {
modules: ['.codedoc/node_modules']
}
});

View File

@@ -1,24 +0,0 @@
import { configuration } from '@codedoc/core'
export const config = configuration({
src: {
base: 'markdown',
},
dest: {
html: './build',
assets: './build',
bundle: './_',
styles: './_',
},
page: {
title: {
base: 'Autorestic',
},
},
misc: {
github: {
user: 'cupcakearmy',
repo: 'autorestic',
},
},
})

View File

@@ -1,19 +0,0 @@
import { CodedocConfig } from '@codedoc/core';
import { Footer as _Footer, GitterToggle$, Watermark} from '@codedoc/core/components';
export function Footer(config: CodedocConfig, renderer: any) {
let github$;
if (config.misc?.github)
github$ = <a href={`https://github.com/${config.misc.github.user}/${config.misc.github.repo}/`}
target="_blank">GitHub</a>;
let community$;
if (config.misc?.gitter)
community$ = <GitterToggle$ room={config.misc.gitter.room}/>
if (github$ && community$) return <_Footer>{github$}<hr/>{community$}</_Footer>;
else if (github$) return <_Footer>{github$}</_Footer>;
else if (community$) return <_Footer>{community$}</_Footer>;
else return <_Footer><Watermark/></_Footer>;
}

View File

@@ -1,21 +0,0 @@
import { CodedocConfig } from '@codedoc/core';
import { Header as _Header, GithubButton, Watermark } from '@codedoc/core/components';
export function Header(config: CodedocConfig, renderer: any) {
return (
<_Header>{config.misc?.github ?
<fragment>
<GithubButton action={config.misc.github.action || 'Star'}
repo={config.misc.github.repo}
user={config.misc.github.user}
large={config.misc.github.large === true}
count={config.misc.github.count !== false}
standardIcon={config.misc.github.standardIcon !== false}/>
<br/><br/>
</fragment>
: ''}
<Watermark/>
</_Header>
)
}

View File

@@ -1,57 +0,0 @@
import { RendererLike } from '@connectv/html'
import { File } from 'rxline/fs'
import {
Page,
Meta,
ContentNav,
Fonts,
ToC,
GithubSearch$,
} from '@codedoc/core/components'
import { config } from '../config'
import { Header } from './header'
import { Footer } from './footer'
export function content(
_content: HTMLElement,
toc: HTMLElement,
renderer: RendererLike<any, any>,
file: File<string>
) {
return (
<Page
title={config.page.title.extractor(_content, config, file)}
favicon={config.page.favicon}
meta={<Meta {...config.page.meta} />}
fonts={<Fonts {...config.page.fonts} />}
scripts={config.page.scripts}
stylesheets={config.page.stylesheets}
header={<Header {...config} />}
footer={<Footer {...config} />}
toc={
<ToC
default={'open'}
search={
config.misc?.github ? (
<GithubSearch$
repo={config.misc.github.repo}
user={config.misc.github.user}
root={config.src.base}
pick={config.src.pick.source}
drop={config.src.drop.source}
/>
) : (
false
)
}
>
{toc}
</ToC>
}
>
{_content}
<ContentNav content={_content} />
</Page>
)
}

View File

@@ -1,8 +0,0 @@
import { funcTransport } from '@connectv/sdh/transport';
import { useTheme } from '@codedoc/core/transport';
import { theme } from '../theme';
export function installTheme() { useTheme(theme); }
export const installTheme$ = /*#__PURE__*/funcTransport(installTheme);

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +0,0 @@
{
"dependencies": {
"@codedoc/core": "^0.2.24"
}
}

View File

@@ -1,18 +0,0 @@
import { join } from 'path';
import { serve } from '@codedoc/core';
import { config } from './config';
import { content } from './content';
import { installTheme$ } from './content/theme';
const root = join(__dirname, '../');
serve(root, config, content, installTheme$, {
resolve: {
modules: ['.codedoc/node_modules']
},
resolveLoader: {
modules: ['.codedoc/node_modules']
}
});

View File

@@ -1,11 +0,0 @@
import { createTheme } from '@codedoc/core/transport';
export const theme = /*#__PURE__*/createTheme({
light: {
primary: '#1eb2a6'
},
dark: {
primary: '#1eb2a6'
}
});

View File

@@ -1,26 +0,0 @@
{
"compilerOptions": {
"target": "es6",
"noImplicitAny": true,
"declaration": false,
"strictNullChecks": true,
"strictFunctionTypes": true,
"noImplicitThis": true,
"alwaysStrict": true,
"sourceMap": true,
"moduleResolution": "node",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"jsx": "react",
"jsxFactory": "renderer.create",
"lib": [
"es2017",
"dom"
]
},
"include": [
"./**/*"
]
}

View File

@@ -1,22 +0,0 @@
import { exec, spawn } from 'child_process';
import { config } from './config';
const cmd = 'ts-node-dev';
const params = `--project .codedoc/tsconfig.json`
+ ` -T --watch ${config.src.base},.codedoc`
+ ` --ignore-watch .codedoc/node_modules`
+ ` .codedoc/serve`;
if (process.platform === 'win32') {
const child = exec(cmd + ' ' + params);
child.stdout?.pipe(process.stdout);
child.stderr?.pipe(process.stderr);
child.on('close', () => {});
}
else {
const child = spawn(cmd, [params], { stdio: 'inherit', shell: 'bash' });
child.on('close', () => {});
}

2
docs/.gitignore vendored
View File

@@ -1,2 +1,2 @@
node_modules
build
.next

1
docs/.nvmrc Normal file
View File

@@ -0,0 +1 @@
v20.8.0

View File

@@ -1,54 +0,0 @@
[Home](/)
[Quick Start](/quick)
[Installation](/installation)
[Configuration](/config)
[Upgrade](/upgrade)
> :Collapse label=Locations
>
> [Overview](/location/overview)
> [Hooks](/location/hooks)
>
> > :Collapse label=Options
> >
> > [Overview](/location/options)
> > [Excluding Files](/location/exclude)
> > [Forget Policy](/location/forget)
> > [Copy](/location/copy)
>
> [Cron](/location/cron)
> [Docker Volumes](/location/docker)
> :Collapse label=Backend
>
> [Overview](/backend/overview)
> [Available Backends](/backend/available)
> [Options](/backend/options)
> [Environment](/backend/env)
> :Collapse label=CLI
>
> [General](/cli/general)
> [Info](/cli/info)
> [Check](/cli/check)
> [Completion](/cli/completion)
> [Backup](/cli/backup)
> [Restore](/cli/restore)
> [Forget](/cli/forget)
> [Cron](/cli/cron)
> [Exec](/cli/exec)
> [Install](/cli/install)
> [Uninstall](/cli/uninstall)
> [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)
[Docker](/docker)
[QA](/qa)
[Community](/community)
[Contributors](/contrib)

View File

@@ -1,11 +0,0 @@
# 🏘 Community
A list of community driven projects. (No official affiliation)
- SystemD Units: https://gitlab.com/py_crash/autorestic-systemd-units
- Docker image: https://github.com/pascaliske/docker-autorestic
- Ansible Role: https://github.com/adsanz/ansible-restic-role
- Ansible Role: https://github.com/ItsNotGoodName/ansible-role-autorestic
- Ansible Role: https://github.com/FuzzyMistborn/ansible-role-autorestic
> :ToCPrevNext

6
docs/next.config.js Normal file
View File

@@ -0,0 +1,6 @@
const withNextra = require('nextra')({
theme: 'nextra-theme-docs',
themeConfig: './theme.config.jsx',
})
module.exports = withNextra()

1149
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,10 +1,14 @@
{
"private": true,
"scripts": {
"build": "codedoc install && codedoc build",
"dev": "codedoc serve"
"build": "NEXT_TELEMETRY_DISABLED=1 next build",
"dev": "NEXT_TELEMETRY_DISABLED=1 next"
},
"dependencies": {
"@codedoc/cli": "^0.2.8"
}
"next": "^13.5.3",
"nextra": "^2.13.1",
"nextra-theme-docs": "^2.13.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"packageManager": "pnpm@8.8.0"
}

10
docs/pages/_meta.json Normal file
View File

@@ -0,0 +1,10 @@
{
"index": "Home",
"quick": "Quick Start",
"installation": "Installation",
"config": "Configuration",
"location": "Locations",
"backend": "Backend",
"cli": "CLI",
"migration": "Migration"
}

View File

@@ -0,0 +1,6 @@
{
"index": "Overview",
"available": "Available backends",
"options": "Options",
"env": "Environment"
}

View File

@@ -83,5 +83,3 @@ backends:
user: user
password: pass
```
> :ToCPrevNext

View File

@@ -63,5 +63,3 @@ backends:
type: b2
path: myBucket
```
> :ToCPrevNext

View File

@@ -16,5 +16,3 @@ backends:
## Types
We restic supports multiple types of backends. See the [full list](/backend/available) for details.
> :ToCPrevNext

View File

@@ -15,5 +15,3 @@ backend:
```
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
> :ToCPrevNext

View File

@@ -0,0 +1,3 @@
{
"general": "General"
}

View File

@@ -21,5 +21,3 @@ autorestic backup -l foo -l bar
```bash
autorestic backup -l location@backend
```
> :ToCPrevNext

View File

@@ -7,5 +7,3 @@ autorestic check
Checks locations and backends are configured properly and initializes them if they are not already.
This is mostly an internal command, but useful to verify if a backend is configured correctly.
> :ToCPrevNext

View File

@@ -13,5 +13,3 @@ Supported shells are
- powershell
To see how to install run `autorestic help completion` and follow the instructions for your specific shell
> :ToCPrevNext

View File

@@ -9,5 +9,3 @@ This command is mostly intended to be triggered by an automated system like syst
It will run cron jobs as [specified in the cron section](/location/cron) of a specific location.
The `--lean` flag will omit output like _skipping location x: not due yet_. This can be useful if you are dumping the output of the cron job to a log file and don't want to be overwhelmed by the output log.
> :ToCPrevNext

View File

@@ -11,5 +11,3 @@ autorestic exec -av -- snapshots
```
With `exec` you can basically run every cli command that you would be able to run with the restic cli. It only pre-fills path, key, etc.
> :ToCPrevNext

View File

@@ -4,10 +4,8 @@
autorestic forget [-l, --location] [-a, --all] [--dry-run] [--prune]
```
This will prune and remove old data form the backends according to the [keep policy you have specified for the location](/location/forget).
This will prune and remove old data form the backends according to the [keep policy you have specified for the location](/location/options/forget).
The `--dry-run` flag will do a dry run showing what would have been deleted, but won't touch the actual data.
The `--prune` flag will also [prune the data](https://restic.readthedocs.io/en/latest/060_forget.html#removing-backup-snapshots). This is a costly operation that can take longer, however it will free up the actual space.
> :ToCPrevNext

View File

@@ -34,5 +34,3 @@ With `--restic-bin` you can specify to run a specific restic binary. This can be
```bash
autorestic --restic-bin /some/path/to/my/custom/restic/binary
```
> :ToCPrevNext

View File

@@ -14,5 +14,3 @@ autorestic info
```bash
autorestic -c path/to/some/config.yml info
```
> :ToCPrevNext

View File

@@ -5,5 +5,3 @@ Installs both restic and autorestic to `/usr/local/bin`.
```bash
autorestic install
```
> :ToCPrevNext

View File

@@ -15,5 +15,3 @@ autorestic restore -l home --from hdd --to /path/where/to/restore
```
This will restore the location `home` to the `/path/where/to/restore` folder and taking the data from the backend `hdd`
> :ToCPrevNext

View File

@@ -5,5 +5,3 @@ Uninstalls both restic and autorestic from `/usr/local/bin`.
```bash
autorestic uninstall
```
> :ToCPrevNext

View File

@@ -7,5 +7,3 @@ autorestic upgrade
```
Updates both restic and autorestic automagically.
> :ToCPrevNext

11
docs/pages/community.md Normal file
View File

@@ -0,0 +1,11 @@
# 🏘 Community
A list of community driven projects. (No official affiliation)
- SystemD Units: <https://gitlab.com/py_crash/autorestic-systemd-units>
- Docker image: <https://github.com/pascaliske/docker-autorestic>
- Ansible Role: <https://github.com/adsanz/ansible-restic-role>
- Ansible Role: <https://github.com/ItsNotGoodName/ansible-role-autorestic>
- Ansible Role: <https://github.com/FuzzyMistborn/ansible-role-autorestic>
- Ansible Role: <https://0xacab.org/varac-projects/ansible-role-autorestic>
- Ansible Role: <https://github.com/dbrennand/ansible-role-autorestic>

View File

@@ -2,10 +2,11 @@
## Path
By default autorestic searches for a `.autorestic.yml` file in the current directory and your home folder.
By default autorestic searches for a `.autorestic.yml` file in the current directory, your home folder and your XDG config folder (`~/.config/` by default):
- `./.autorestic.yml`
- `~/.autorestic.yml`
- `~/.config/autorestic/.autorestic.yml`
You can also specify a custom file with the `-c path/to/some/config.yml`
@@ -83,5 +84,3 @@ locations:
forget:
<<: *bar
```
> :ToCPrevNext

View File

@@ -17,5 +17,3 @@ This amazing people helped the project!
- @TheForcer - Typos.
- @themorlan - Typos.
- @somebox - Typos.
> :ToCPrevNext

View File

@@ -36,5 +36,3 @@ locations:
to:
- somewhere-else
```
> :ToCPrevNext

View File

@@ -18,5 +18,3 @@ Autorestic is a wrapper around the amazing [restic](https://restic.net/). While
- Cron jobs for automatic backup
- Backup & Restore docker volumes
- Generated completions for `[bash|zsh|fish|powershell]`
> :ToCPrevNext

View File

@@ -5,7 +5,7 @@ Linux & macOS. Windows is not supported. If you have problems installing please
Autorestic requires `bash`, `wget` and `bzip2` to be installed. For most systems these should be already installed.
```bash
wget -qO - https://raw.githubusercontent.com/CupCakeArmy/autorestic/master/install.sh | bash
wget -qO - https://raw.githubusercontent.com/cupcakearmy/autorestic/master/install.sh | bash
```
## Alternatives
@@ -24,8 +24,10 @@ You can download the right binary from the release page and simply copy it to `/
If you are on macOS you can install through brew: `brew install autorestic`.
### Fedora
Fedora users can install the [autorestic](https://src.fedoraproject.org/rpms/autorestic/) package with `dnf install autorestic`.
### AUR
~~If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/) (looking for maintainers).~~ - Deprecated
> :ToCPrevNext
If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/)

View File

@@ -0,0 +1,7 @@
{
"index": "Overview",
"hooks": "Hooks",
"options": "Options",
"cron": "Cronjobs",
"docker": "Docker volumes"
}

View File

@@ -1,6 +1,6 @@
# Cron
Often it is usefully to trigger backups automatically. For this we can specify a `cron` attribute to each location.
Often it is useful to trigger backups automatically. For this, we can specify a `cron` attribute to each location.
```yaml | .autorestic.yml
locations:
@@ -10,15 +10,15 @@ locations:
cron: '0 3 * * 0' # Every Sunday at 3:00
```
Here is a awesome website with [some examples](https://crontab.guru/examples.html) and an [explorer](https://crontab.guru/)
Here is an awesome website with [some examples](https://crontab.guru/examples.html) and an [explorer](https://crontab.guru/).
## Installing the cron
**This has to be done only once, regardless of now many cron jobs you have in your config file.**
**This has to be done only once, regardless of how many cron jobs you have in your config file.**
To actually enable cron jobs you need something to call `autorestic cron` on a timed schedule.
Note that the schedule has nothing to do with the `cron` attribute in each location.
My advise would be to trigger the command every 5min, but if you have a cronjob that runs only once a week, it's probably enough to schedule it once a day.
My advice 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
@@ -50,6 +50,4 @@ To debug a cron job you can use
Now you can add as many `cron` attributes as you wish in the config file ⏱
> Also note that manually triggered backups with `autorestic backup` will not influence the cron timeline, they are willingly not linked.
> :ToCPrevNext
> Also note that manually triggered backups with `autorestic backup` will not influence the cron timeline, they are intentionally not linked.

View File

@@ -35,5 +35,3 @@ autorestic restore -l hello
```
The volume has to exists whenever backing up or restoring.
> :ToCPrevNext

View File

@@ -76,5 +76,3 @@ AUTORESTIC_LOCATION=bar
AUTORESTIC_FILES_ADDED_0=42
AUTORESTIC_FILES_ADDED_FOO=42
```
> :ToCPrevNext

View File

@@ -4,6 +4,7 @@ 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.
Note: names of locations MUST be lower case!
```yaml | .autorestic.yml
version: 2
@@ -30,5 +31,3 @@ Paths can be absolute or relative. If relative they are resolved relative to the
## `to`
This is either a single backend or an array of backends. The backends have to be configured in the same config file.
> :ToCPrevNext

View File

@@ -0,0 +1,3 @@
{
"index": "Overview"
}

View File

@@ -16,5 +16,3 @@ locations:
- '*.abc'
exclude-file: .gitignore
```
> :ToCPrevNext

View File

@@ -21,7 +21,7 @@ locations:
keep-weekly: 1 # keep 1 last weekly snapshots
keep-monthly: 12 # keep 12 last monthly snapshots
keep-yearly: 7 # keep 7 last yearly snapshots
keep-within: '2w' # keep snapshots from the last 2 weeks
keep-within: '14d' # keep snapshots from the last 14 days
```
## Globally
@@ -53,5 +53,3 @@ locations:
forget:
keep-last: 5
```
> :ToCPrevNext

View File

@@ -63,5 +63,3 @@ backends:
locations:
# ...
```
> :ToCPrevNext

View File

@@ -22,5 +22,3 @@ remote:
```
Other than the config file there is a new `-v, --verbose` flag which shows the output of native commands, which are now hidden by default.
> :ToCPrevNext

View File

@@ -57,4 +57,10 @@ locations:
type: volume
```
> :ToCPrevNext
## Tagging
Autorestic changed the way backups are referenced. Before we took the paths as the identifying information. Now autorestic uses native restic tags to reference them. This means that old backups are not referenced. You can the old snapshots manually. An example can be shown below.
```bash
autorestic exec -va -- tag --add ar:location:LOCATION_NAME # Only if you have only one location
```

View File

@@ -0,0 +1,4 @@
{
"0.x_1.0": "0.x → 1.0",
"1.4_1.5": "1.4 → 1.5"
}

View File

@@ -6,5 +6,3 @@ This happens when autorestic needs to write to the config file: e.g. when we are
Unfortunately during this process formatting and comments are lost because the `yaml` library used is not comment and/or format aware.
That is why autorestic will place a copy of your old config next to the one we are writing to.
> :ToCPrevNext

View File

@@ -3,7 +3,7 @@
## Installation
```bash
wget -qO - https://raw.githubusercontent.com/CupCakeArmy/autorestic/master/install.sh | bash
wget -qO - https://raw.githubusercontent.com/cupcakearmy/autorestic/master/install.sh | bash
```
See [installation](/installation) for alternative options.
@@ -82,5 +82,3 @@ autorestic restore -l home --from hdd --to /path/where/to/restore
```
This will restore the location `home` from the backend `hdd` to the given path.
> :ToCPrevNext

3044
docs/pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

24
docs/theme.config.jsx Normal file
View File

@@ -0,0 +1,24 @@
export default {
logo: <span>Autorestic</span>,
docsRepositoryBase: 'https://github.com/cupcakearmy/autorestic/tree/master/docs',
project: {
link: 'https://github.com/cupcakearmy/autorestic',
},
sidebar: {
defaultMenuCollapseLevel: 1,
},
feedback: {
content: 'Question? An error? Give feedback →',
},
footer: {
text: (
<span>
MIT {new Date().getFullYear()} ©{' '}
<a href="https://github.com/cupcakearmy" target="_blank">
cupcakearmy
</a>
.
</span>
),
},
}

View File

@@ -1,3 +0,0 @@
{
"cleanUrls": true
}

6
go.mod
View File

@@ -1,6 +1,6 @@
module github.com/cupcakearmy/autorestic
go 1.18
go 1.21
require (
github.com/blang/semver/v4 v4.0.0
@@ -28,8 +28,8 @@ require (
github.com/spf13/jwalterweatherman v1.1.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-20220412211240-33da011f77ad // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.3.8 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect

8
go.sum
View File

@@ -324,8 +324,8 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad h1:ntjMns5wyP/fN65tdBD4g8J5w8n015+iIIs9rtjXkY0=
golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -333,8 +333,8 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=

View File

@@ -31,6 +31,11 @@ else
fi
echo $ARCH
if ! command -v bzip2 &>/dev/null; then
echo "Missing bzip2 command. Please install the bzip2 package for your system."
exit 1
fi
wget -qO - https://api.github.com/repos/cupcakearmy/autorestic/releases/latest \
| grep "browser_download_url.*_${OS}_${ARCH}" \
| cut -d : -f 2,3 \

View File

@@ -38,7 +38,7 @@ func (b Backend) generateRepo() (string, error) {
case "local":
return GetPathRelativeToConfig(b.Path)
case "rest":
parsed, err := url.Parse(b.Path)
parsed, err := url.Parse(os.ExpandEnv(b.Path))
if err != nil {
return "", err
}
@@ -120,18 +120,19 @@ func (b Backend) validate() error {
if err != nil {
return err
}
options := ExecuteOptions{Envs: env}
options := ExecuteOptions{Envs: env, Silent: true}
// Check if already initialized
_, _, err = ExecuteResticCommand(options, "snapshots")
cmd := []string{"check"}
cmd = append(cmd, combineBackendOptions("check", b)...)
_, _, err = ExecuteResticCommand(options, cmd...)
if err == nil {
return nil
} else {
// If not initialize
colors.Body.Printf("Initializing backend \"%s\"...\n", b.name)
_, out, err := ExecuteResticCommand(options, "init")
if flags.VERBOSE {
colors.Faint.Println(out)
}
cmd := []string{"init"}
cmd = append(cmd, combineBackendOptions("init", b)...)
_, _, err := ExecuteResticCommand(options, cmd...)
return err
}
}
@@ -147,9 +148,6 @@ func (b Backend) Exec(args []string) error {
colors.Error.Println(out)
return err
}
if flags.VERBOSE {
colors.Faint.Println(out)
}
return nil
}
@@ -167,7 +165,6 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) {
args = append([]string{"restic"}, args...)
docker := []string{
"run", "--rm",
"--pull", "always",
"--entrypoint", "ash",
"--workdir", dir,
"--volume", volume + ":" + dir,
@@ -201,6 +198,7 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) {
for key, value := range env {
docker = append(docker, "--env", key+"="+value)
}
docker = append(docker, "cupcakearmy/autorestic:"+VERSION, "-c", strings.Join(args, " "))
docker = append(docker, flags.DOCKER_IMAGE, "-c", strings.Join(args, " "))
return ExecuteCommand(options, docker...)
}

225
internal/backend_test.go Normal file
View File

@@ -0,0 +1,225 @@
package internal
import (
"fmt"
"os"
"testing"
"github.com/spf13/viper"
)
func TestGenerateRepo(t *testing.T) {
t.Run("empty backend", func(t *testing.T) {
b := Backend{
name: "empty backend",
Type: "",
}
_, err := b.generateRepo()
if err == nil {
t.Errorf("Error expected for empty backend type")
}
})
t.Run("local backend", func(t *testing.T) {
b := Backend{
name: "local backend",
Type: "local",
Path: "/foo/bar",
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "/foo/bar")
})
t.Run("local backend with homedir prefix", func(t *testing.T) {
b := Backend{
name: "local backend",
Type: "local",
Path: "~/foo/bar",
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, fmt.Sprintf("%s/foo/bar", os.Getenv("HOME")))
})
t.Run("local backend with config file", func(t *testing.T) {
// config file path should always be present from initConfig
viper.SetConfigFile("/tmp/.autorestic.yml")
defer viper.Reset()
b := Backend{
name: "local backend",
Type: "local",
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "/tmp")
})
t.Run("rest backend with valid path", func(t *testing.T) {
b := Backend{
name: "rest backend",
Type: "rest",
Path: "http://localhost:8000/foo",
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "rest:http://localhost:8000/foo")
})
t.Run("rest backend with user", func(t *testing.T) {
b := Backend{
name: "rest backend",
Type: "rest",
Path: "http://localhost:8000/foo",
Rest: BackendRest{
User: "user",
Password: "",
},
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "rest:http://user@localhost:8000/foo")
})
t.Run("rest backend with user and password", func(t *testing.T) {
b := Backend{
name: "rest backend",
Type: "rest",
Path: "http://localhost:8000/foo",
Rest: BackendRest{
User: "user",
Password: "pass",
},
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "rest:http://user:pass@localhost:8000/foo")
})
backendTests := []struct {
name string
backend Backend
want string
}{
{name: "b2 backend", backend: Backend{name: "b2", Type: "b2", Path: "foo"}, want: "b2:foo"},
{name: "azure backend", backend: Backend{name: "azure", Type: "azure", Path: "foo"}, want: "azure:foo"},
{name: "gs backend", backend: Backend{name: "gs", Type: "gs", Path: "foo"}, want: "gs:foo"},
{name: "s3 backend", backend: Backend{name: "s3", Type: "s3", Path: "foo"}, want: "s3:foo"},
{name: "sftp backend", backend: Backend{name: "sftp", Type: "sftp", Path: "foo"}, want: "sftp:foo"},
{name: "rclone backend", backend: Backend{name: "rclone", Type: "rclone", Path: "foo"}, want: "rclone:foo"},
}
for _, tt := range backendTests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.backend.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, got, tt.want)
})
}
}
func TestGetEnv(t *testing.T) {
t.Run("env in key field", func(t *testing.T) {
b := Backend{
name: "",
Type: "local",
Path: "/foo/bar",
Key: "secret123",
}
result, err := b.getEnv()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result["RESTIC_REPOSITORY"], "/foo/bar")
assertEqual(t, result["RESTIC_PASSWORD"], "secret123")
})
t.Run("env in config file", func(t *testing.T) {
b := Backend{
name: "",
Type: "local",
Path: "/foo/bar",
Env: map[string]string{
"B2_ACCOUNT_ID": "foo123",
"B2_ACCOUNT_KEY": "foo456",
},
}
result, err := b.getEnv()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result["RESTIC_REPOSITORY"], "/foo/bar")
assertEqual(t, result["RESTIC_PASSWORD"], "")
assertEqual(t, result["B2_ACCOUNT_ID"], "foo123")
assertEqual(t, result["B2_ACCOUNT_KEY"], "foo456")
})
t.Run("env in Envfile or env vars", func(t *testing.T) {
// generate env variables
// TODO better way to teardown
defer os.Unsetenv("AUTORESTIC_FOO_RESTIC_PASSWORD")
defer os.Unsetenv("AUTORESTIC_FOO_B2_ACCOUNT_ID")
defer os.Unsetenv("AUTORESTIC_FOO_B2_ACCOUNT_KEY")
os.Setenv("AUTORESTIC_FOO_RESTIC_PASSWORD", "secret123")
os.Setenv("AUTORESTIC_FOO_B2_ACCOUNT_ID", "foo123")
os.Setenv("AUTORESTIC_FOO_B2_ACCOUNT_KEY", "foo456")
b := Backend{
name: "foo",
Type: "local",
Path: "/foo/bar",
}
result, err := b.getEnv()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result["RESTIC_REPOSITORY"], "/foo/bar")
assertEqual(t, result["RESTIC_PASSWORD"], "secret123")
assertEqual(t, result["B2_ACCOUNT_ID"], "foo123")
assertEqual(t, result["B2_ACCOUNT_KEY"], "foo456")
})
}
func TestValidate(t *testing.T) {
t.Run("no type given", func(t *testing.T) {
b := Backend{
name: "foo",
Type: "",
Path: "/foo/bar",
}
err := b.validate()
if err == nil {
t.Error("expected to get error")
}
assertEqual(t, err.Error(), "Backend \"foo\" has no \"type\"")
})
t.Run("no path given", func(t *testing.T) {
b := Backend{
name: "foo",
Type: "local",
Path: "",
}
err := b.validate()
if err == nil {
t.Error("expected to get error")
}
assertEqual(t, err.Error(), "Backend \"foo\" has no \"path\"")
})
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/blang/semver/v4"
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags"
)
const INSTALL_PATH = "/usr/local/bin"
@@ -128,10 +129,9 @@ func InstallRestic() error {
}
func upgradeRestic() error {
_, out, err := internal.ExecuteCommand(internal.ExecuteOptions{
Command: "restic",
_, _, err := internal.ExecuteCommand(internal.ExecuteOptions{
Command: flags.RESTIC_BIN,
}, "self-update")
colors.Faint.Println(out)
return err
}

View File

@@ -17,7 +17,7 @@ import (
"github.com/spf13/viper"
)
const VERSION = "1.6.0"
const VERSION = "1.7.11"
type OptionMap map[string][]interface{}
type Options map[string]OptionMap
@@ -185,7 +185,7 @@ func CheckConfig() error {
return fmt.Errorf("config could not be loaded/found")
}
if !CheckIfResticIsCallable() {
return fmt.Errorf(`%s was not found. Install either with "autorestic install" or manually`, RESTIC_BIN)
return fmt.Errorf(`%s was not found. Install either with "autorestic install" or manually`, flags.RESTIC_BIN)
}
for name, backend := range c.Backends {
backend.name = name
@@ -295,24 +295,30 @@ func appendOptionsToSlice(str *[]string, options OptionMap) {
}
}
func getOptions(options Options, key string) []string {
func getOptions(options Options, keys []string) []string {
var selected []string
var keys = []string{"all"}
if key != "" {
keys = append(keys, key)
}
for _, key := range keys {
appendOptionsToSlice(&selected, options[key])
}
return selected
}
func combineOptions(key string, l Location, b Backend) []string {
func combineBackendOptions(key string, b Backend) []string {
// Priority: backend > global
var options []string
gFlags := getOptions(GetConfig().Global, []string{key})
bFlags := getOptions(b.Options, []string{"all", key})
options = append(options, gFlags...)
options = append(options, bFlags...)
return options
}
func combineAllOptions(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)
gFlags := getOptions(GetConfig().Global, []string{key})
bFlags := getOptions(b.Options, []string{"all", key})
lFlags := getOptions(l.Options, []string{"all", key})
options = append(options, gFlags...)
options = append(options, bFlags...)
options = append(options, lFlags...)

164
internal/config_test.go Normal file
View File

@@ -0,0 +1,164 @@
package internal
import (
"reflect"
"strconv"
"strings"
"testing"
)
func TestOptionToString(t *testing.T) {
t.Run("no prefix", func(t *testing.T) {
opt := "test"
result := optionToString(opt)
assertEqual(t, result, "--test")
})
t.Run("single prefix", func(t *testing.T) {
opt := "-test"
result := optionToString(opt)
assertEqual(t, result, "-test")
})
t.Run("double prefix", func(t *testing.T) {
opt := "--test"
result := optionToString(opt)
assertEqual(t, result, "--test")
})
}
func TestAppendOneOptionToSlice(t *testing.T) {
t.Run("string flag", func(t *testing.T) {
result := []string{}
optionMap := OptionMap{"string-flag": []interface{}{"/root"}}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--string-flag", "/root",
}
assertSliceEqual(t, result, expected)
})
t.Run("bool flag", func(t *testing.T) {
result := []string{}
optionMap := OptionMap{"boolean-flag": []interface{}{true}}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--boolean-flag",
}
assertSliceEqual(t, result, expected)
})
t.Run("int flag", func(t *testing.T) {
result := []string{}
optionMap := OptionMap{"int-flag": []interface{}{123}}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--int-flag", "123",
}
assertSliceEqual(t, result, expected)
})
}
func TestAppendMultipleOptionsToSlice(t *testing.T) {
result := []string{}
optionMap := OptionMap{
"string-flag": []interface{}{"/root"},
"int-flag": []interface{}{123},
}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--string-flag", "/root",
"--int-flag", "123",
}
if len(result) != len(expected) {
t.Errorf("got length %d, want length %d", len(result), len(expected))
}
// checks that expected option comes after flag, regardless of key order in map
for i, v := range expected {
v = strings.TrimPrefix(v, "--")
if value, ok := optionMap[v]; ok {
if val, ok := value[0].(int); ok {
if expected[i+1] != strconv.Itoa(val) {
t.Errorf("Flags and options order are mismatched. got %v, want %v", result, expected)
}
}
}
}
}
func TestAppendOptionWithMultipleValuesToSlice(t *testing.T) {
result := []string{}
optionMap := OptionMap{
"string-flag": []interface{}{"/root", "/bin"},
}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--string-flag", "/root",
"--string-flag", "/bin",
}
assertSliceEqual(t, result, expected)
}
func TestGetOptionsOneKey(t *testing.T) {
optionMap := OptionMap{
"string-flag": []interface{}{"/root"},
}
options := Options{"backend": optionMap}
keys := []string{"backend"}
result := getOptions(options, keys)
expected := []string{
"--string-flag", "/root",
}
assertSliceEqual(t, result, expected)
}
func TestGetOptionsMultipleKeys(t *testing.T) {
firstOptionMap := OptionMap{
"string-flag": []interface{}{"/root"},
}
secondOptionMap := OptionMap{
"boolean-flag": []interface{}{true},
"int-flag": []interface{}{123},
}
options := Options{
"all": firstOptionMap,
"forget": secondOptionMap,
}
keys := []string{"all", "forget"}
result := getOptions(options, keys)
expected := []string{
"--string-flag", "/root",
"--boolean-flag",
"--int-flag", "123",
}
reflect.DeepEqual(result, expected)
}
func assertEqual[T comparable](t testing.TB, result, expected T) {
t.Helper()
if result != expected {
t.Errorf("got %v, want %v", result, expected)
}
}
func assertSliceEqual(t testing.TB, result, expected []string) {
t.Helper()
if len(result) != len(expected) {
t.Errorf("got length %d, want length %d", len(result), len(expected))
}
for i := range result {
assertEqual(t, result[i], expected[i])
}
}

View File

@@ -1,5 +1,9 @@
package flags
var CI bool = false
var VERBOSE bool = false
var CRON_LEAN bool = false
var (
CI bool = false
VERBOSE bool = false
CRON_LEAN bool = false
RESTIC_BIN string
DOCKER_IMAGE string
)

View File

@@ -74,12 +74,8 @@ func (l Location) validate() error {
if from, err := GetPathRelativeToConfig(path); err != nil {
return err
} else {
if stat, err := os.Stat(from); err != nil {
if _, 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)
}
}
}
}
@@ -146,9 +142,6 @@ func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error
colors.Error.Println(out)
return err
}
if flags.VERBOSE {
colors.Faint.Println(out)
}
}
colors.Body.Println("")
return nil
@@ -223,7 +216,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
}
cmd := []string{"backup"}
cmd = append(cmd, combineOptions("backup", l, backend)...)
cmd = append(cmd, combineAllOptions("backup", l, backend)...)
if cron {
cmd = append(cmd, "--tag", buildTag("cron"))
}
@@ -284,24 +277,16 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
for k, v := range env2 {
env[k+"2"] = v
}
_, out, err := ExecuteResticCommand(ExecuteOptions{
_, _, err := ExecuteResticCommand(ExecuteOptions{
Envs: env,
}, "copy", md.SnapshotID)
if flags.VERBOSE {
colors.Faint.Println(out)
}
if err != nil {
errors = append(errors, err)
}
}
}
}
if flags.VERBOSE {
colors.Faint.Println(out)
}
}
// After hooks
@@ -323,7 +308,10 @@ after:
// Forget and optionally prune
if isSuccess && l.ForgetOption != "" && l.ForgetOption != LocationForgetNo {
l.Forget(l.ForgetOption == LocationForgetPrune, false)
err := l.Forget(l.ForgetOption == LocationForgetPrune, false)
if err != nil {
errors = append(errors, err)
}
}
if len(errors) == 0 {
@@ -335,7 +323,12 @@ after:
func (l Location) Forget(prune bool, dry bool) error {
colors.PrimaryPrint("Forgetting for location \"%s\"", l.name)
for _, to := range l.To {
backendsToForget := l.To
for _, copyBackends := range l.CopyOption {
backendsToForget = append(backendsToForget, copyBackends...)
}
for _, to := range backendsToForget {
backend, _ := GetBackend(to)
colors.Secondary.Printf("For backend \"%s\"\n", backend.name)
env, err := backend.getEnv()
@@ -352,11 +345,8 @@ func (l Location) Forget(prune bool, dry bool) error {
if dry {
cmd = append(cmd, "--dry-run")
}
cmd = append(cmd, combineOptions("forget", l, backend)...)
_, out, err := ExecuteResticCommand(options, cmd...)
if flags.VERBOSE {
colors.Faint.Println(out)
}
cmd = append(cmd, combineAllOptions("forget", l, backend)...)
_, _, err = ExecuteResticCommand(options, cmd...)
if err != nil {
return err
}

93
internal/location_test.go Normal file
View File

@@ -0,0 +1,93 @@
package internal
import "testing"
func TestGetType(t *testing.T) {
t.Run("TypeLocal", func(t *testing.T) {
l := Location{
Type: "local",
}
result, err := l.getType()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
assertEqual(t, result, TypeLocal)
})
t.Run("TypeVolume", func(t *testing.T) {
l := Location{
Type: "volume",
}
result, err := l.getType()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
assertEqual(t, result, TypeVolume)
})
t.Run("Empty type", func(t *testing.T) {
l := Location{
Type: "",
}
result, err := l.getType()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
assertEqual(t, result, TypeLocal)
})
t.Run("Invalid type", func(t *testing.T) {
l := Location{
Type: "foo",
}
_, err := l.getType()
if err == nil {
t.Error("expected error")
}
})
}
func TestBuildTag(t *testing.T) {
result := buildTag("foo", "bar")
expected := "ar:foo:bar"
assertEqual(t, result, expected)
}
func TestGetLocationTags(t *testing.T) {
l := Location{
name: "foo",
}
result := l.getLocationTags()
expected := "ar:location:foo"
assertEqual(t, result, expected)
}
func TestHasBackend(t *testing.T) {
t.Run("backend present", func(t *testing.T) {
l := Location{
name: "foo",
To: []string{"foo", "bar"},
}
result := l.hasBackend("foo")
assertEqual(t, result, true)
})
t.Run("backend absent", func(t *testing.T) {
l := Location{
name: "foo",
To: []string{"bar", "baz"},
}
result := l.hasBackend("foo")
assertEqual(t, result, false)
})
}
func TestBuildRestoreCommand(t *testing.T) {
l := Location{
name: "foo",
}
result := buildRestoreCommand(l, "to", "snapshot", []string{"options"})
expected := []string{"restore", "--target", "to", "--tag", "ar:location:foo", "snapshot", "options"}
assertSliceEqual(t, result, expected)
}

View File

@@ -14,6 +14,10 @@ var lock *viper.Viper
var file string
var once sync.Once
const (
RUNNING = "running"
)
func getLock() *viper.Viper {
if lock == nil {
@@ -37,36 +41,38 @@ func getLock() *viper.Viper {
return lock
}
func setLock(locked bool) error {
func setLockValue(key string, value interface{}) (*viper.Viper, error) {
lock := getLock()
if locked {
running := lock.GetBool("running")
if running {
if key == RUNNING {
value := value.(bool)
if value && lock.GetBool(key) {
colors.Error.Println("an instance is already running. exiting")
os.Exit(1)
}
}
lock.Set("running", locked)
lock.Set(key, value)
if err := lock.WriteConfigAs(file); err != nil {
return err
return nil, err
}
return nil
return lock, nil
}
func GetCron(location string) int64 {
lock := getLock()
return lock.GetInt64("cron." + location)
return getLock().GetInt64("cron." + location)
}
func SetCron(location string, value int64) {
lock.Set("cron."+location, value)
lock.WriteConfigAs(file)
setLockValue("cron."+location, value)
}
func Lock() error {
return setLock(true)
_, err := setLockValue(RUNNING, true)
return err
}
func Unlock() error {
return setLock(false)
_, err := setLockValue(RUNNING, false)
return err
}

114
internal/lock/lock_test.go Normal file
View File

@@ -0,0 +1,114 @@
package lock
import (
"log"
"os"
"os/exec"
"strconv"
"testing"
"github.com/spf13/viper"
)
var testDirectory = "autorestic_test_tmp"
// All tests must share the same lock file as it is only initialized once
func setup(t *testing.T) {
d, err := os.MkdirTemp("", testDirectory)
if err != nil {
log.Fatalf("error creating temp dir: %v", err)
return
}
// set config file location
viper.SetConfigFile(d + "/.autorestic.yml")
t.Cleanup(func() {
os.RemoveAll(d)
viper.Reset()
})
}
func TestLock(t *testing.T) {
setup(t)
t.Run("getLock", func(t *testing.T) {
result := getLock().GetBool(RUNNING)
if result {
t.Errorf("got %v, want %v", result, false)
}
})
t.Run("lock", func(t *testing.T) {
lock, err := setLockValue(RUNNING, true)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
result := lock.GetBool(RUNNING)
if !result {
t.Errorf("got %v, want %v", result, true)
}
})
t.Run("unlock", func(t *testing.T) {
lock, err := setLockValue(RUNNING, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
result := lock.GetBool(RUNNING)
if result {
t.Errorf("got %v, want %v", result, false)
}
})
// locking a locked instance exits the instance
// this trick to capture os.Exit(1) is discussed here:
// https://talks.golang.org/2014/testing.slide#23
t.Run("lock twice", func(t *testing.T) {
if os.Getenv("CRASH") == "1" {
err := Lock()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
// should fail
Lock()
}
cmd := exec.Command(os.Args[0], "-test.run=TestLock/lock_twice")
cmd.Env = append(os.Environ(), "CRASH=1")
err := cmd.Run()
err, ok := err.(*exec.ExitError)
if !ok {
t.Error("unexpected error")
}
expected := "exit status 1"
if err.Error() != expected {
t.Errorf("got %q, want %q", err.Error(), expected)
}
})
t.Run("set cron", func(t *testing.T) {
expected := int64(5)
SetCron("foo", expected)
result, err := strconv.ParseInt(getLock().GetString("cron.foo"), 10, 64)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if result != expected {
t.Errorf("got %d, want %d", result, expected)
}
})
t.Run("get cron", func(t *testing.T) {
expected := int64(5)
result := GetCron("foo")
if result != expected {
t.Errorf("got %d, want %d", result, expected)
}
})
}

View File

@@ -14,9 +14,9 @@ func (e addedExtractor) Matches(line string) bool {
}
func (e addedExtractor) Extract(metadata *BackupLogMetadata, line string) {
// Sample line: "Added to the repo: 0 B"
metadata.AddedSize = strings.TrimSpace(e.re.ReplaceAllString(line, ""))
metadata.AddedSize = strings.TrimSpace(e.re.ReplaceAllString(line, "$2"))
}
func NewAddedExtractor() MetadatExtractor {
return addedExtractor{regexp.MustCompile(`(?i)^Added to the repo:`)}
return addedExtractor{regexp.MustCompile(`(?i)^Added to the repo(sitory)?: ([\d\.]+ \w+)( \([\d\.]+[\w\s]+\))?`)}
}

View File

@@ -9,23 +9,34 @@ import (
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags"
"github.com/fatih/color"
)
var RESTIC_BIN string
func CheckIfCommandIsCallable(cmd string) bool {
_, err := exec.LookPath(cmd)
return err == nil
}
func CheckIfResticIsCallable() bool {
return CheckIfCommandIsCallable(RESTIC_BIN)
return CheckIfCommandIsCallable(flags.RESTIC_BIN)
}
type ExecuteOptions struct {
Command string
Envs map[string]string
Dir string
Silent bool
}
type ColoredWriter struct {
target io.Writer
color *color.Color
}
func (w ColoredWriter) Write(p []byte) (n int, err error) {
colored := []byte(w.color.Sprint(string(p)))
w.target.Write(colored)
return len(p), nil
}
func ExecuteCommand(options ExecuteOptions, args ...string) (int, string, error) {
@@ -43,23 +54,32 @@ func ExecuteCommand(options ExecuteOptions, args ...string) (int, string, error)
var out bytes.Buffer
var error bytes.Buffer
cmd.Stdout = &out
if flags.VERBOSE && !options.Silent {
var colored ColoredWriter = ColoredWriter{
target: os.Stdout,
color: colors.Faint,
}
mw := io.MultiWriter(colored, &out)
cmd.Stdout = mw
} else {
cmd.Stdout = &out
}
cmd.Stderr = &error
err := cmd.Run()
if err != nil {
code := -1
if exitError, ok := err.(*exec.ExitError); ok {
return exitError.ExitCode(), error.String(), err
} else {
return -1, error.String(), err
code = exitError.ExitCode()
}
return code, error.String(), err
}
return 0, out.String(), nil
}
func ExecuteResticCommand(options ExecuteOptions, args ...string) (int, string, error) {
options.Command = RESTIC_BIN
options.Command = flags.RESTIC_BIN
var c = GetConfig()
var optionsAsString = getOptions(c.Global, "")
var optionsAsString = getOptions(c.Global, []string{"all"})
args = append(optionsAsString, args...)
return ExecuteCommand(options, args...)
}

View File

@@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,