diff --git a/ROADMAP.md b/ROADMAP.md new file mode 100644 index 0000000..4a99aaa --- /dev/null +++ b/ROADMAP.md @@ -0,0 +1,18 @@ +# Roadmap + +## Todo + +- implement commands + +## Packages + +- https://github.com/fatih/color +- https://github.com/manifoldco/promptui +- https://github.com/AlecAivazis/survey + +## To ask + +- union types for options config +- utility library + - keys of map + - includes array diff --git a/cmd/backup.go b/cmd/backup.go index 235b356..ac3a515 100644 --- a/cmd/backup.go +++ b/cmd/backup.go @@ -28,24 +28,20 @@ var backupCmd = &cobra.Command{ Use: "backup", Short: "Create backups for given locations", Run: func(cmd *cobra.Command, args []string) { - config := internal.GetConfig() - { - err := config.CheckConfig() - cobra.CheckErr(err) - } - { - err := lock.Lock() - cobra.CheckErr(err) - } + err := lock.Lock() + CheckErr(err) defer lock.Unlock() - { - selected, err := internal.GetAllOrSelected(cmd, false) - cobra.CheckErr(err) - for _, name := range selected { - location := config.Locations[name] - fmt.Printf("Backing up: `%s`", name) - location.Backup() - } + + config := internal.GetConfig() + err = config.CheckConfig() + CheckErr(err) + + selected, err := internal.GetAllOrSelected(cmd, false) + CheckErr(err) + for _, name := range selected { + location, _ := internal.GetLocation(name) + fmt.Printf("Backing up: `%s`", name) + location.Backup() } }, } diff --git a/cmd/check.go b/cmd/check.go index 7d1cf56..f081913 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/lock" "github.com/spf13/cobra" ) @@ -29,11 +30,16 @@ var checkCmd = &cobra.Command{ Short: "Check if everything is setup", Run: func(cmd *cobra.Command, args []string) { if !internal.CheckIfResticIsCallable() { - cobra.CheckErr(errors.New("restic is not callable. Install: https://restic.readthedocs.io/en/stable/020_installation.html")) + CheckErr(errors.New("restic is not callable. Install: https://restic.readthedocs.io/en/stable/020_installation.html")) } + + err := lock.Lock() + CheckErr(err) + defer lock.Unlock() + config := internal.GetConfig() - err := config.CheckConfig() - cobra.CheckErr(err) + err = config.CheckConfig() + CheckErr(err) fmt.Println("Everyting is fine.") }, } diff --git a/cmd/cron.go b/cmd/cron.go index c3eecb0..e460059 100644 --- a/cmd/cron.go +++ b/cmd/cron.go @@ -17,6 +17,7 @@ package cmd import ( "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/lock" "github.com/spf13/cobra" ) @@ -26,8 +27,12 @@ var cronCmd = &cobra.Command{ Short: "Run cron job for automated backups", Long: `Intended to be mainly triggered by an automated system like systemd or crontab. For each location checks if a cron backup is due and runs it.`, Run: func(cmd *cobra.Command, args []string) { - err := internal.RunCron() - cobra.CheckErr(err) + err := lock.Lock() + CheckErr(err) + defer lock.Unlock() + + err = internal.RunCron() + CheckErr(err) }, } diff --git a/cmd/exec.go b/cmd/exec.go index 31de2c9..c01dcbc 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/lock" "github.com/spf13/cobra" ) @@ -27,16 +28,20 @@ var execCmd = &cobra.Command{ Use: "exec", Short: "Execute arbitrary native restic commands for given backends", Run: func(cmd *cobra.Command, args []string) { + err := lock.Lock() + CheckErr(err) + defer lock.Unlock() + config := internal.GetConfig() if err := config.CheckConfig(); err != nil { panic(err) } { selected, err := internal.GetAllOrSelected(cmd, true) - cobra.CheckErr(err) + CheckErr(err) for _, name := range selected { fmt.Println(name) - backend := config.Backends[name] + backend, _ := internal.GetBackend(name) backend.Exec(args) } } diff --git a/cmd/forget.go b/cmd/forget.go index 32e18fd..43bb8b7 100644 --- a/cmd/forget.go +++ b/cmd/forget.go @@ -17,6 +17,7 @@ package cmd import ( "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/lock" "github.com/spf13/cobra" ) @@ -25,19 +26,23 @@ var forgetCmd = &cobra.Command{ Use: "forget", Short: "Forget and optionally prune snapshots according the specified policies", Run: func(cmd *cobra.Command, args []string) { + err := lock.Lock() + CheckErr(err) + defer lock.Unlock() + config := internal.GetConfig() if err := config.CheckConfig(); err != nil { panic(err) } { selected, err := internal.GetAllOrSelected(cmd, false) - cobra.CheckErr(err) + CheckErr(err) prune, _ := cmd.Flags().GetBool("prune") dry, _ := cmd.Flags().GetBool("dry-run") for _, name := range selected { - location := config.Locations[name] + location, _ := internal.GetLocation(name) err := location.Forget(prune, dry) - cobra.CheckErr(err) + CheckErr(err) } } }, diff --git a/cmd/install.go b/cmd/install.go index ca72923..8b98939 100644 --- a/cmd/install.go +++ b/cmd/install.go @@ -25,7 +25,7 @@ var installCmd = &cobra.Command{ Short: "Install restic if missing", Run: func(cmd *cobra.Command, args []string) { err := bins.InstallRestic() - cobra.CheckErr(err) + CheckErr(err) }, } diff --git a/cmd/restore.go b/cmd/restore.go index 4b7c91a..1fc67a7 100644 --- a/cmd/restore.go +++ b/cmd/restore.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/lock" "github.com/spf13/cobra" ) @@ -27,17 +28,20 @@ var restoreCmd = &cobra.Command{ Use: "restore", Short: "Restore backup for location", Run: func(cmd *cobra.Command, args []string) { - config := internal.GetConfig() + err := lock.Lock() + CheckErr(err) + defer lock.Unlock() + location, _ := cmd.Flags().GetString("location") - l, ok := config.Locations[location] + l, ok := internal.GetLocation(location) if !ok { - cobra.CheckErr(fmt.Errorf("invalid location \"%s\"", location)) + CheckErr(fmt.Errorf("invalid location \"%s\"", location)) } target, _ := cmd.Flags().GetString("to") from, _ := cmd.Flags().GetString("from") force, _ := cmd.Flags().GetBool("force") - err := l.Restore(target, from, force) - cobra.CheckErr(err) + err = l.Restore(target, from, force) + CheckErr(err) }, } diff --git a/cmd/root.go b/cmd/root.go index 4cf1724..70d1538 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,12 +20,21 @@ import ( "os" "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/lock" "github.com/spf13/cobra" homedir "github.com/mitchellh/go-homedir" "github.com/spf13/viper" ) +func CheckErr(err error) { + if err != nil { + fmt.Fprintln(os.Stderr, "Error:", err) + lock.Unlock() + os.Exit(1) + } +} + var cfgFile string // rootCmd represents the base command when called without any subcommands @@ -38,7 +47,7 @@ var rootCmd = &cobra.Command{ // Execute adds all child commands to the root command and sets flags appropriately. // This is called by main.main(). It only needs to happen once to the rootCmd. func Execute() { - cobra.CheckErr(rootCmd.Execute()) + CheckErr(rootCmd.Execute()) } func init() { @@ -63,7 +72,7 @@ func initConfig() { } else { // Find home directory. home, err := homedir.Dir() - cobra.CheckErr(err) + CheckErr(err) viper.AddConfigPath(".") viper.AddConfigPath(home) diff --git a/cmd/upgrade.go b/cmd/upgrade.go index 910c005..38c7971 100644 --- a/cmd/upgrade.go +++ b/cmd/upgrade.go @@ -26,7 +26,7 @@ var upgradeCmd = &cobra.Command{ Run: func(cmd *cobra.Command, args []string) { noRestic, _ := cmd.Flags().GetBool("no-restic") err := bins.Upgrade(!noRestic) - cobra.CheckErr(err) + CheckErr(err) }, } diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..4b4dfe0 --- /dev/null +++ b/install.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +OUT_FILE=/usr/local/bin/autorestic + +if [[ "$OSTYPE" == "linux-gnu" ]]; then + TYPE=linux +elif [[ "$OSTYPE" == "darwin"* ]]; then + TYPE=macos +else + echo "Unsupported OS" + exit 1 +fi + +curl -s https://api.github.com/repos/cupcakearmy/autorestic/releases/latest \ +| grep "browser_download_url.*_${TYPE}" \ +| cut -d : -f 2,3 \ +| tr -d \" \ +| wget -O ${OUT_FILE} -i - +chmod +x ${OUT_FILE} + +autorestic install +echo "Succefsully installed autorestic" \ No newline at end of file diff --git a/internal/backend.go b/internal/backend.go index c847954..4e1b38c 100644 --- a/internal/backend.go +++ b/internal/backend.go @@ -5,12 +5,23 @@ import ( ) type Backend struct { + Name string `mapstructure:"name"` Type string `mapstructure:"type"` Path string `mapstructure:"path"` Key string `mapstructure:"key"` Env map[string]string `mapstructure:"env"` } +func GetBackend(name string) (Backend, bool) { + c := GetConfig() + for _, b := range c.Backends { + if b.Name == name { + return b, true + } + } + return Backend{}, false +} + func (b Backend) generateRepo() (string, error) { switch b.Type { case "local": diff --git a/internal/config.go b/internal/config.go index 7045144..db280f7 100644 --- a/internal/config.go +++ b/internal/config.go @@ -15,8 +15,8 @@ import ( const VERSION = "1.0.0" type Config struct { - Locations map[string]Location `mapstructure:"locations"` - Backends map[string]Backend `mapstructure:"backends"` + Locations []Location `mapstructure:"locations"` + Backends []Backend `mapstructure:"backends"` } var once sync.Once @@ -65,12 +65,12 @@ func (c Config) CheckConfig() error { func GetAllOrSelected(cmd *cobra.Command, backends bool) ([]string, error) { var list []string if backends { - for key := range config.Backends { - list = append(list, key) + for _, b := range config.Backends { + list = append(list, b.Name) } } else { - for key := range config.Locations { - list = append(list, key) + for _, l := range config.Locations { + list = append(list, l.Name) } } all, _ := cmd.Flags().GetBool("all") diff --git a/internal/location.go b/internal/location.go index 898fd01..c630eb0 100644 --- a/internal/location.go +++ b/internal/location.go @@ -21,6 +21,7 @@ type Hooks struct { type Options map[string]map[string][]string type Location struct { + Name string `mapstructure:"name"` From string `mapstructure:"from"` To []string `mapstructure:"to"` Hooks Hooks `mapstructure:"hooks"` @@ -28,10 +29,20 @@ type Location struct { Options Options `mapstructure:"options"` } +func GetLocation(name string) (Location, bool) { + c := GetConfig() + for _, b := range c.Locations { + if b.Name == name { + return b, true + } + } + return Location{}, false +} + func (l Location) validate(c Config) error { // Check if backends are all valid for _, to := range l.To { - _, ok := c.Backends[to] + _, ok := GetBackend(to) if !ok { return fmt.Errorf("invalid backend `%s`", to) } @@ -60,10 +71,9 @@ func ExecuteHooks(commands []string, options ExecuteOptions) error { } func (l Location) Backup() error { - c := GetConfig() from := GetPathRelativeToConfig(l.From) for _, to := range l.To { - backend := c.Backends[to] + backend, _ := GetBackend(to) options := ExecuteOptions{ Command: "bash", Envs: backend.getEnv(), @@ -92,10 +102,9 @@ func (l Location) Backup() error { } func (l Location) Forget(prune bool, dry bool) error { - c := GetConfig() from := GetPathRelativeToConfig(l.From) for _, to := range l.To { - backend := c.Backends[to] + backend, _ := GetBackend(to) options := ExecuteOptions{ Envs: backend.getEnv(), Dir: from, @@ -159,8 +168,7 @@ func (l Location) Restore(to, from string, force bool) error { } } - c := GetConfig() - backend := c.Backends[from] + backend, _ := GetBackend(from) err = backend.Exec([]string{"restore", "--target", to, "--path", GetPathRelativeToConfig(l.From), "latest"}) if err != nil { return err