mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2024-12-23 08:46:25 +00:00
Compare commits
3 Commits
9697e080e6
...
5b46897feb
Author | SHA1 | Date | |
---|---|---|---|
|
5b46897feb | ||
|
530b1b646c | ||
|
8e24286ca3 |
@ -6,7 +6,7 @@ RUN go mod download
|
||||
COPY . .
|
||||
RUN go build
|
||||
|
||||
FROM restic/restic:0.15.1
|
||||
FROM restic/restic:0.16.0
|
||||
RUN apk add --no-cache rclone bash
|
||||
COPY --from=builder /app/autorestic /usr/bin/autorestic
|
||||
ENTRYPOINT []
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal"
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||
"github.com/cupcakearmy/autorestic/internal/version"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
homedir "github.com/mitchellh/go-homedir"
|
||||
@ -26,7 +26,7 @@ func CheckErr(err error) {
|
||||
var cfgFile string
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Version: internal.VERSION,
|
||||
Version: version.VERSION,
|
||||
Use: "autorestic",
|
||||
Short: "CLI Wrapper for restic",
|
||||
Long: "Documentation:\thttps://autorestic.vercel.app\nSupport:\thttps://discord.gg/wS7RpYTYd2",
|
||||
@ -41,7 +41,7 @@ func init() {
|
||||
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(&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")
|
||||
rootCmd.PersistentFlags().StringVar(&flags.DOCKER_IMAGE, "docker-image", "cupcakearmy/autorestic:"+version.VERSION, "specify a custom docker image")
|
||||
cobra.OnInitialize(initConfig)
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||
"github.com/cupcakearmy/autorestic/internal/options"
|
||||
"github.com/cupcakearmy/autorestic/internal/utils"
|
||||
)
|
||||
|
||||
type BackendRest struct {
|
||||
@ -24,7 +26,7 @@ type Backend struct {
|
||||
Key string `mapstructure:"key,omitempty"`
|
||||
Env map[string]string `mapstructure:"env,omitempty"`
|
||||
Rest BackendRest `mapstructure:"rest,omitempty"`
|
||||
Options Options `mapstructure:"options,omitempty"`
|
||||
Options options.Options `mapstructure:"options,omitempty"`
|
||||
}
|
||||
|
||||
func GetBackend(name string) (Backend, bool) {
|
||||
@ -120,11 +122,15 @@ func (b Backend) validate() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options := ExecuteOptions{Envs: env, Silent: true}
|
||||
options := utils.ExecuteOptions{
|
||||
Envs: env,
|
||||
Silent: true,
|
||||
Global: GetConfig().Global,
|
||||
}
|
||||
// Check if already initialized
|
||||
cmd := []string{"check"}
|
||||
cmd = append(cmd, combineBackendOptions("check", b)...)
|
||||
_, _, err = ExecuteResticCommand(options, cmd...)
|
||||
_, _, err = utils.ExecuteResticCommand(options, cmd...)
|
||||
if err == nil {
|
||||
return nil
|
||||
} else {
|
||||
@ -132,7 +138,7 @@ func (b Backend) validate() error {
|
||||
colors.Body.Printf("Initializing backend \"%s\"...\n", b.name)
|
||||
cmd := []string{"init"}
|
||||
cmd = append(cmd, combineBackendOptions("init", b)...)
|
||||
_, _, err := ExecuteResticCommand(options, cmd...)
|
||||
_, _, err := utils.ExecuteResticCommand(options, cmd...)
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -142,8 +148,11 @@ func (b Backend) Exec(args []string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options := ExecuteOptions{Envs: env}
|
||||
_, out, err := ExecuteResticCommand(options, args...)
|
||||
options := utils.ExecuteOptions{
|
||||
Envs: env,
|
||||
Global: GetConfig().Global,
|
||||
}
|
||||
_, out, err := utils.ExecuteResticCommand(options, args...)
|
||||
if err != nil {
|
||||
colors.Error.Println(out)
|
||||
return err
|
||||
@ -157,7 +166,7 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) {
|
||||
return -1, "", err
|
||||
}
|
||||
volume := l.From[0]
|
||||
options := ExecuteOptions{
|
||||
options := utils.ExecuteOptions{
|
||||
Command: "docker",
|
||||
Envs: env,
|
||||
}
|
||||
@ -185,7 +194,7 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) {
|
||||
// No additional setup needed
|
||||
case "rclone":
|
||||
// Read host rclone config and mount it into the container
|
||||
code, configFile, err := ExecuteCommand(ExecuteOptions{Command: "rclone"}, "config", "file")
|
||||
code, configFile, err := utils.ExecuteCommand(utils.ExecuteOptions{Command: "rclone"}, "config", "file")
|
||||
if err != nil {
|
||||
return code, "", err
|
||||
}
|
||||
@ -200,5 +209,5 @@ func (b Backend) ExecDocker(l Location, args []string) (int, string, error) {
|
||||
}
|
||||
|
||||
docker = append(docker, flags.DOCKER_IMAGE, "-c", strings.Join(args, " "))
|
||||
return ExecuteCommand(options, docker...)
|
||||
return utils.ExecuteCommand(options, docker...)
|
||||
}
|
||||
|
@ -14,9 +14,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/blang/semver/v4"
|
||||
"github.com/cupcakearmy/autorestic/internal"
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||
"github.com/cupcakearmy/autorestic/internal/utils"
|
||||
"github.com/cupcakearmy/autorestic/internal/version"
|
||||
)
|
||||
|
||||
const INSTALL_PATH = "/usr/local/bin"
|
||||
@ -115,7 +116,7 @@ func downloadAndInstallAsset(body GithubRelease, name string) error {
|
||||
}
|
||||
|
||||
func InstallRestic() error {
|
||||
installed := internal.CheckIfCommandIsCallable("restic")
|
||||
installed := utils.CheckIfCommandIsCallable("restic")
|
||||
if installed {
|
||||
colors.Body.Println("restic already installed")
|
||||
return nil
|
||||
@ -129,7 +130,7 @@ func InstallRestic() error {
|
||||
}
|
||||
|
||||
func upgradeRestic() error {
|
||||
_, _, err := internal.ExecuteCommand(internal.ExecuteOptions{
|
||||
_, _, err := utils.ExecuteCommand(utils.ExecuteOptions{
|
||||
Command: flags.RESTIC_BIN,
|
||||
}, "self-update")
|
||||
return err
|
||||
@ -147,7 +148,7 @@ func Upgrade(restic bool) error {
|
||||
}
|
||||
|
||||
// Upgrade self
|
||||
current, err := semver.ParseTolerant(internal.VERSION)
|
||||
current, err := semver.ParseTolerant(version.VERSION)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -11,23 +11,20 @@ import (
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||
"github.com/cupcakearmy/autorestic/internal/options"
|
||||
"github.com/cupcakearmy/autorestic/internal/utils"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const VERSION = "1.7.7"
|
||||
|
||||
type OptionMap map[string][]interface{}
|
||||
type Options map[string]OptionMap
|
||||
|
||||
type Config struct {
|
||||
Version string `mapstructure:"version"`
|
||||
Extras interface{} `mapstructure:"extras"`
|
||||
Locations map[string]Location `mapstructure:"locations"`
|
||||
Backends map[string]Backend `mapstructure:"backends"`
|
||||
Global Options `mapstructure:"global"`
|
||||
Global options.Options `mapstructure:"global"`
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
@ -184,7 +181,7 @@ func CheckConfig() error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("config could not be loaded/found")
|
||||
}
|
||||
if !CheckIfResticIsCallable() {
|
||||
if !utils.CheckIfResticIsCallable() {
|
||||
return fmt.Errorf(`%s was not found. Install either with "autorestic install" or manually`, flags.RESTIC_BIN)
|
||||
}
|
||||
for name, backend := range c.Backends {
|
||||
@ -263,7 +260,7 @@ func AddFlagsToCommand(cmd *cobra.Command, backend bool) {
|
||||
|
||||
func (c *Config) SaveConfig() error {
|
||||
file := viper.ConfigFileUsed()
|
||||
if err := CopyFile(file, file+".old"); err != nil {
|
||||
if err := utils.CopyFile(file, file+".old"); err != nil {
|
||||
return err
|
||||
}
|
||||
colors.Secondary.Println("Saved a backup copy of your file next to the original.")
|
||||
@ -274,40 +271,11 @@ func (c *Config) SaveConfig() error {
|
||||
return viper.WriteConfig()
|
||||
}
|
||||
|
||||
func optionToString(option string) string {
|
||||
if !strings.HasPrefix(option, "-") {
|
||||
return "--" + option
|
||||
}
|
||||
return option
|
||||
}
|
||||
|
||||
func appendOptionsToSlice(str *[]string, options OptionMap) {
|
||||
for key, values := range options {
|
||||
for _, value := range values {
|
||||
// Bool
|
||||
asBool, ok := value.(bool)
|
||||
if ok && asBool {
|
||||
*str = append(*str, optionToString(key))
|
||||
continue
|
||||
}
|
||||
*str = append(*str, optionToString(key), fmt.Sprint(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getOptions(options Options, keys []string) []string {
|
||||
var selected []string
|
||||
for _, key := range keys {
|
||||
appendOptionsToSlice(&selected, options[key])
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
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})
|
||||
gFlags := GetConfig().Global.GetOptions([]string{key})
|
||||
bFlags := b.Options.GetOptions([]string{"all", key})
|
||||
options = append(options, gFlags...)
|
||||
options = append(options, bFlags...)
|
||||
return options
|
||||
@ -316,9 +284,9 @@ func combineBackendOptions(key string, b Backend) []string {
|
||||
func combineAllOptions(key string, l Location, b Backend) []string {
|
||||
// Priority: location > backend > global
|
||||
var options []string
|
||||
gFlags := getOptions(GetConfig().Global, []string{key})
|
||||
bFlags := getOptions(b.Options, []string{"all", key})
|
||||
lFlags := getOptions(l.Options, []string{"all", key})
|
||||
gFlags := GetConfig().Global.GetOptions([]string{key})
|
||||
bFlags := b.Options.GetOptions([]string{"all", key})
|
||||
lFlags := l.Options.GetOptions([]string{"all", key})
|
||||
options = append(options, gFlags...)
|
||||
options = append(options, bFlags...)
|
||||
options = append(options, lFlags...)
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||
"github.com/cupcakearmy/autorestic/internal/metadata"
|
||||
"github.com/cupcakearmy/autorestic/internal/utils"
|
||||
"github.com/cupcakearmy/autorestic/internal/options"
|
||||
"github.com/robfig/cron"
|
||||
)
|
||||
|
||||
@ -49,7 +51,7 @@ type Location struct {
|
||||
To []string `mapstructure:"to,omitempty"`
|
||||
Hooks Hooks `mapstructure:"hooks,omitempty"`
|
||||
Cron string `mapstructure:"cron,omitempty"`
|
||||
Options Options `mapstructure:"options,omitempty"`
|
||||
Options options.Options `mapstructure:"options,omitempty"`
|
||||
ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"`
|
||||
CopyOption LocationCopy `mapstructure:"copy,omitempty"`
|
||||
}
|
||||
@ -101,14 +103,14 @@ func (l Location) validate() error {
|
||||
if _, ok := GetBackend(copyFrom); !ok {
|
||||
return fmt.Errorf(`location "%s" has an invalid backend "%s" in copy option`, l.name, copyFrom)
|
||||
}
|
||||
if !ArrayContains(l.To, copyFrom) {
|
||||
if !utils.ArrayContains(l.To, copyFrom) {
|
||||
return fmt.Errorf(`location "%s" has an invalid copy from "%s"`, l.name, copyFrom)
|
||||
}
|
||||
for _, copyToTarget := range copyTo {
|
||||
if _, ok := GetBackend(copyToTarget); !ok {
|
||||
return fmt.Errorf(`location "%s" has an invalid backend "%s" in copy option`, l.name, copyToTarget)
|
||||
}
|
||||
if ArrayContains(l.To, copyToTarget) {
|
||||
if utils.ArrayContains(l.To, copyToTarget) {
|
||||
return fmt.Errorf(`location "%s" cannot copy to "%s" as it's already a target`, l.name, copyToTarget)
|
||||
}
|
||||
}
|
||||
@ -123,7 +125,7 @@ func (l Location) validate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error {
|
||||
func (l Location) ExecuteHooks(commands []string, options utils.ExecuteOptions) error {
|
||||
if len(commands) == 0 {
|
||||
return nil
|
||||
}
|
||||
@ -137,7 +139,7 @@ func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error
|
||||
colors.Secondary.Println("\nRunning hooks")
|
||||
for _, command := range commands {
|
||||
colors.Body.Println("> " + command)
|
||||
_, out, err := ExecuteCommand(options, "-c", command)
|
||||
_, out, err := utils.ExecuteCommand(options, "-c", command)
|
||||
if err != nil {
|
||||
colors.Error.Println(out)
|
||||
return err
|
||||
@ -176,7 +178,7 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
|
||||
return errors
|
||||
}
|
||||
cwd, _ := GetPathRelativeToConfig(".")
|
||||
options := ExecuteOptions{
|
||||
options := utils.ExecuteOptions{
|
||||
Command: "bash",
|
||||
Dir: cwd,
|
||||
Envs: map[string]string{
|
||||
@ -221,8 +223,9 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
|
||||
cmd = append(cmd, "--tag", buildTag("cron"))
|
||||
}
|
||||
cmd = append(cmd, "--tag", l.getLocationTags())
|
||||
backupOptions := ExecuteOptions{
|
||||
backupOptions := utils.ExecuteOptions{
|
||||
Envs: env,
|
||||
Global: GetConfig().Global,
|
||||
}
|
||||
|
||||
var code int = 0
|
||||
@ -237,9 +240,9 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
|
||||
}
|
||||
cmd = append(cmd, path)
|
||||
}
|
||||
code, out, err = ExecuteResticCommand(backupOptions, cmd...)
|
||||
code, out, err = utils.ExecuteResticCommand(backupOptions, cmd...)
|
||||
case TypeVolume:
|
||||
ok := CheckIfVolumeExists(l.From[0])
|
||||
ok := utils.CheckIfVolumeExists(l.From[0])
|
||||
if !ok {
|
||||
errors = append(errors, fmt.Errorf("volume \"%s\" does not exist", l.From[0]))
|
||||
continue
|
||||
@ -277,8 +280,9 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
|
||||
for k, v := range env2 {
|
||||
env[k+"2"] = v
|
||||
}
|
||||
_, _, err := ExecuteResticCommand(ExecuteOptions{
|
||||
_, _, err := utils.ExecuteResticCommand(utils.ExecuteOptions{
|
||||
Envs: env,
|
||||
Global: GetConfig().Global,
|
||||
}, "copy", md.SnapshotID)
|
||||
|
||||
if err != nil {
|
||||
@ -335,8 +339,9 @@ func (l Location) Forget(prune bool, dry bool) error {
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
options := ExecuteOptions{
|
||||
options := utils.ExecuteOptions{
|
||||
Envs: env,
|
||||
Global: GetConfig().Global,
|
||||
}
|
||||
cmd := []string{"forget", "--tag", l.getLocationTags()}
|
||||
if prune {
|
||||
@ -346,7 +351,7 @@ func (l Location) Forget(prune bool, dry bool) error {
|
||||
cmd = append(cmd, "--dry-run")
|
||||
}
|
||||
cmd = append(cmd, combineAllOptions("forget", l, backend)...)
|
||||
_, _, err = ExecuteResticCommand(options, cmd...)
|
||||
_, _, err = utils.ExecuteResticCommand(options, cmd...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
38
internal/options/options.go
Normal file
38
internal/options/options.go
Normal file
@ -0,0 +1,38 @@
|
||||
package options
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type OptionMap map[string][]interface{}
|
||||
type Options map[string]OptionMap
|
||||
|
||||
func (o Options) GetOptions(keys []string) []string {
|
||||
var selected []string
|
||||
for _, key := range keys {
|
||||
o[key].AppendOptionsToSlice(&selected)
|
||||
}
|
||||
return selected
|
||||
}
|
||||
|
||||
func (m OptionMap) AppendOptionsToSlice(str *[]string) {
|
||||
for key, values := range m {
|
||||
for _, value := range values {
|
||||
// Bool
|
||||
asBool, ok := value.(bool)
|
||||
if ok && asBool {
|
||||
*str = append(*str, optionToString(key))
|
||||
continue
|
||||
}
|
||||
*str = append(*str, optionToString(key), fmt.Sprint(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func optionToString(option string) string {
|
||||
if !strings.HasPrefix(option, "-") {
|
||||
return "--" + option
|
||||
}
|
||||
return option
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
package internal
|
||||
package utils
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/cupcakearmy/autorestic/internal/flags"
|
||||
"github.com/cupcakearmy/autorestic/internal/options"
|
||||
"github.com/fatih/color"
|
||||
)
|
||||
|
||||
@ -26,6 +27,7 @@ type ExecuteOptions struct {
|
||||
Envs map[string]string
|
||||
Dir string
|
||||
Silent bool
|
||||
Global options.Options
|
||||
}
|
||||
|
||||
type ColoredWriter struct {
|
||||
@ -78,8 +80,7 @@ func ExecuteCommand(options ExecuteOptions, args ...string) (int, string, error)
|
||||
|
||||
func ExecuteResticCommand(options ExecuteOptions, args ...string) (int, string, error) {
|
||||
options.Command = flags.RESTIC_BIN
|
||||
var c = GetConfig()
|
||||
var optionsAsString = getOptions(c.Global, []string{"all"})
|
||||
var optionsAsString = options.Global.GetOptions([]string{"all"})
|
||||
args = append(optionsAsString, args...)
|
||||
return ExecuteCommand(options, args...)
|
||||
}
|
3
internal/version/version.go
Normal file
3
internal/version/version.go
Normal file
@ -0,0 +1,3 @@
|
||||
package version
|
||||
|
||||
const VERSION = "1.7.7"
|
Loading…
Reference in New Issue
Block a user