diff --git a/cmd/check.go b/cmd/check.go index 02ed6e1..292cd0e 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -16,9 +16,8 @@ limitations under the License. package cmd import ( - "fmt" - "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/lock" "github.com/spf13/cobra" ) @@ -35,7 +34,7 @@ var checkCmd = &cobra.Command{ config := internal.GetConfig() err = config.CheckConfig() CheckErr(err) - fmt.Println("Everyting is fine.") + colors.Success.Println("Everyting is fine.") }, } diff --git a/cmd/exec.go b/cmd/exec.go index 17a57fc..4d5382e 100644 --- a/cmd/exec.go +++ b/cmd/exec.go @@ -16,9 +16,8 @@ limitations under the License. package cmd import ( - "fmt" - "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/colors" "github.com/cupcakearmy/autorestic/internal/lock" "github.com/spf13/cobra" ) @@ -39,7 +38,7 @@ var execCmd = &cobra.Command{ selected, err := internal.GetAllOrSelected(cmd, true) CheckErr(err) for _, name := range selected { - fmt.Println(name) + colors.PrimaryPrint(" Executing on \"%s\" ", name) backend, _ := internal.GetBackend(name) backend.Exec(args) } diff --git a/cmd/root.go b/cmd/root.go index 8e2a987..080f3cc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,7 +16,6 @@ limitations under the License. package cmd import ( - "fmt" "os" "github.com/cupcakearmy/autorestic/internal" @@ -30,7 +29,7 @@ import ( func CheckErr(err error) { if err != nil { - fmt.Fprintln(os.Stderr, "Error:", err) + colors.Error.Fprintln(os.Stderr, "Error:", err) lock.Unlock() os.Exit(1) } @@ -38,40 +37,33 @@ func CheckErr(err error) { var cfgFile string -// rootCmd represents the base command when called without any subcommands var rootCmd = &cobra.Command{ Version: internal.VERSION, Use: "autorestic", Short: "CLI Wrapper for restic", + Long: "Documentation: https://autorestic.vercel.app", } -// 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() { CheckErr(rootCmd.Execute()) } func init() { - cobra.OnInitialize(initConfig) - - // Here you will define your flags and configuration settings. - // Cobra supports persistent flags, which, if defined here, - // will be global for your application. - rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.autorestic.yml or ./.autorestic.yml)") - - // Cobra also supports local flags, which will only run - // when this action is called directly. - rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") + rootCmd.PersistentFlags().BoolVar(&internal.CI, "ci", false, "CI mode disabled interactive mode and colors and enables verbosity") + rootCmd.PersistentFlags().BoolVar(&internal.VERBOSE, "verbose", false, "verbose mode") + cobra.OnInitialize(initConfig) } -// initConfig reads in config file and ENV variables if set. func initConfig() { + if ci, _ := rootCmd.Flags().GetBool("ci"); ci { + colors.DisableColors(true) + internal.VERBOSE = true + } + if cfgFile != "" { - // Use config file from the flag. viper.SetConfigFile(cfgFile) } else { - // Find home directory. home, err := homedir.Dir() CheckErr(err) @@ -79,13 +71,6 @@ func initConfig() { viper.AddConfigPath(home) viper.SetConfigName(".autorestic") } - - viper.AutomaticEnv() // read in environment variables that match - - // If a config file is found, read it in. - if err := viper.ReadInConfig(); err == nil { - colors.Faint.Println("Using config file:", viper.ConfigFileUsed()) - } - + viper.AutomaticEnv() internal.GetConfig() } diff --git a/internal/backend.go b/internal/backend.go index 7fe7001..cfd1457 100644 --- a/internal/backend.go +++ b/internal/backend.go @@ -6,6 +6,7 @@ import ( "fmt" "strings" + "github.com/cupcakearmy/autorestic/internal/colors" "github.com/spf13/viper" ) @@ -80,7 +81,7 @@ func (b Backend) validate() error { if err := CopyFile(file, file+".old"); err != nil { return err } - fmt.Println("Saved a backup copy of your file next the the original.") + colors.Secondary.Println("Saved a backup copy of your file next the the original.") viper.Set("backends", c.Backends) viper.WriteConfig() } @@ -96,7 +97,7 @@ func (b Backend) validate() error { } else { // If not initialize out, err := ExecuteResticCommand(options, "init") - fmt.Println(out) + colors.Faint.Println(out) return err } } @@ -108,6 +109,8 @@ func (b Backend) Exec(args []string) error { } options := ExecuteOptions{Envs: env} out, err := ExecuteResticCommand(options, args...) - fmt.Println(out) + if VERBOSE { + colors.Faint.Println(out) + } return err } diff --git a/internal/bins/bins.go b/internal/bins/bins.go index 64ac1f6..10c2076 100644 --- a/internal/bins/bins.go +++ b/internal/bins/bins.go @@ -15,6 +15,7 @@ import ( "github.com/blang/semver/v4" "github.com/cupcakearmy/autorestic/internal" + "github.com/cupcakearmy/autorestic/internal/colors" ) const INSTALL_PATH = "/usr/local/bin" @@ -46,11 +47,11 @@ func dlJSON(url string) (GithubRelease, error) { func Uninstall(restic bool) error { if err := os.Remove(path.Join(INSTALL_PATH, "autorestic")); err != nil { - fmt.Println(err) + colors.Error.Println(err) } if restic { if err := os.Remove(path.Join(INSTALL_PATH, "restic")); err != nil { - fmt.Println(err) + colors.Error.Println(err) } } return nil @@ -59,7 +60,7 @@ func Uninstall(restic bool) error { func InstallRestic() error { installed := internal.CheckIfCommandIsCallable("restic") if installed { - fmt.Println("restic already installed") + colors.Body.Println("restic already installed") return nil } else { body, err := dlJSON("https://api.github.com/repos/restic/restic/releases/latest") @@ -69,10 +70,8 @@ func InstallRestic() error { ending := fmt.Sprintf("_%s_%s.bz2", runtime.GOOS, runtime.GOARCH) for _, asset := range body.Assets { if strings.HasSuffix(asset.Name, ending) { - // Found - fmt.Println(asset.Link) - // Download archive + colors.Faint.Println("Downloading:", asset.Link) resp, err := http.Get(asset.Link) if err != nil { return err @@ -91,7 +90,7 @@ func InstallRestic() error { defer file.Close() io.Copy(file, bz) - fmt.Printf("Successfully installed restic under %s\n", INSTALL_PATH) + colors.Success.Printf("Successfully installed restic under %s\n", INSTALL_PATH) return nil } } @@ -103,7 +102,7 @@ func upgradeRestic() error { out, err := internal.ExecuteCommand(internal.ExecuteOptions{ Command: "restic", }, "self-update") - fmt.Println(out) + colors.Faint.Println(out) return err } @@ -119,8 +118,6 @@ func Upgrade(restic bool) error { if err != nil { return err } - fmt.Println(current) - body, err := dlJSON("https://api.github.com/repos/cupcakearmy/autorestic/releases/latest") if err != nil { return err @@ -130,10 +127,10 @@ func Upgrade(restic bool) error { return err } if current.GT(latest) { - - fmt.Println("Updated autorestic") + // TODO: Actually download and install + colors.Success.Println("Updated autorestic") } else { - fmt.Println("Already up to date") + colors.Body.Println("Already up to date") } return nil } diff --git a/internal/colors/colors.go b/internal/colors/colors.go index c67e8fb..c27f8fc 100644 --- a/internal/colors/colors.go +++ b/internal/colors/colors.go @@ -1,12 +1,22 @@ package colors import ( + "fmt" + "github.com/fatih/color" ) var Body = color.New() -var Primary = color.New(color.Underline, color.Bold, color.BgBlue) +var Primary = color.New(color.Bold, color.BgBlue, color.FgHiWhite) var Secondary = color.New(color.Bold, color.FgCyan) var Success = color.New(color.FgGreen) var Error = color.New(color.FgRed, color.Bold) var Faint = color.New(color.Faint) + +func PrimaryPrint(msg string, args ...interface{}) { + fmt.Printf("\n\n%s\n\n", Primary.Sprintf(" "+msg+" ", args...)) +} + +func DisableColors(state bool) { + color.NoColor = state +} diff --git a/internal/config.go b/internal/config.go index ea7cce4..57f24b0 100644 --- a/internal/config.go +++ b/internal/config.go @@ -6,6 +6,7 @@ import ( "strings" "sync" + "github.com/cupcakearmy/autorestic/internal/colors" "github.com/mitchellh/go-homedir" "github.com/spf13/cobra" "github.com/spf13/viper" @@ -13,6 +14,9 @@ import ( const VERSION = "1.0.0" +var CI bool = false +var VERBOSE bool = false + type Config struct { Locations []Location `mapstructure:"locations"` Backends []Backend `mapstructure:"backends"` @@ -24,6 +28,13 @@ var config *Config func GetConfig() *Config { if config == nil { once.Do(func() { + if err := viper.ReadInConfig(); err == nil { + colors.Faint.Println("Using config file:", viper.ConfigFileUsed()) + } else { + + return + } + config = &Config{} if err := viper.UnmarshalExact(config); err != nil { panic(err) @@ -45,6 +56,9 @@ func GetPathRelativeToConfig(p string) (string, error) { } func (c *Config) CheckConfig() error { + if c == nil { + return fmt.Errorf("config could not be loaded/found") + } if !CheckIfResticIsCallable() { return fmt.Errorf(`restic was not found. Install either with "autorestic install" or manually`) } diff --git a/internal/location.go b/internal/location.go index 76db271..b61f967 100644 --- a/internal/location.go +++ b/internal/location.go @@ -75,44 +75,23 @@ func ExecuteHooks(commands []string, options ExecuteOptions) error { if len(commands) == 0 { return nil } - colors.Secondary.Println("🪝 Running hooks") + colors.Secondary.Println("\nRunning hooks") for _, command := range commands { - colors.Body.Println(command) + colors.Body.Println("> " + command) out, err := ExecuteCommand(options, "-c", command) - colors.Faint.Print(out) - return err - } - fmt.Println("") - return nil -} - -func (l Location) forEachBackend(fn func(ExecuteOptions) error) error { - from, err := GetPathRelativeToConfig(l.From) - if err != nil { - return err - } - for _, to := range l.To { - backend, _ := GetBackend(to) - env, err := backend.getEnv() - if err != nil { - return nil - } - options := ExecuteOptions{ - Command: "bash", - Envs: env, - Dir: from, + if VERBOSE { + colors.Faint.Println(out) } - if err := fn(options); err != nil { + if err != nil { return err } } + colors.Body.Println("") return nil } func (l Location) Backup() error { - fmt.Printf("\n\n") - colors.Primary.Printf("💽 Backing up location \"%s\"", l.Name) - fmt.Printf("\n") + colors.PrimaryPrint(" Backing up location \"%s\" ", l.Name) from, err := GetPathRelativeToConfig(l.From) if err != nil { return err @@ -141,7 +120,9 @@ func (l Location) Backup() error { cmd = append(cmd, flags...) cmd = append(cmd, ".") out, err := ExecuteResticCommand(options, cmd...) - colors.Faint.Print(out) + if VERBOSE { + colors.Faint.Println(out) + } if err != nil { return err } @@ -149,12 +130,29 @@ func (l Location) Backup() error { if err := ExecuteHooks(l.Hooks.After, options); err != nil { return nil } - colors.Success.Println("✅ Done") + colors.Success.Println("Done") return err } func (l Location) Forget(prune bool, dry bool) error { - return l.forEachBackend(func(options ExecuteOptions) error { + colors.PrimaryPrint("Forgetting for location \"%s\"", l.Name) + + from, err := GetPathRelativeToConfig(l.From) + if err != nil { + return err + } + for _, to := range l.To { + backend, _ := GetBackend(to) + colors.Secondary.Printf("For backend \"%s\"\n", backend.Name) + env, err := backend.getEnv() + if err != nil { + return nil + } + options := ExecuteOptions{ + Command: "bash", + Envs: env, + Dir: from, + } flags := l.getOptions("forget") cmd := []string{"forget", "--path", options.Dir} if prune { @@ -165,12 +163,15 @@ func (l Location) Forget(prune bool, dry bool) error { } cmd = append(cmd, flags...) out, err := ExecuteResticCommand(options, cmd...) - fmt.Println(out) + if VERBOSE { + colors.Faint.Println(out) + } if err != nil { return err } - return nil - }) + } + colors.Success.Println("Done") + return nil } func (l Location) hasBackend(backend string) bool { @@ -193,7 +194,9 @@ func (l Location) Restore(to, from string, force bool) error { if err != nil { return err } - fmt.Printf("Restoring location to %s using %s.\n", to, from) + colors.PrimaryPrint("Restoring location \"%s\"", l.Name) + colors.Secondary.Println("Restoring lastest snapshot") + colors.Body.Printf("%s → %s.\n", from, to) // Check if target is empty if !force { @@ -223,6 +226,7 @@ func (l Location) Restore(to, from string, force bool) error { if err != nil { return err } + colors.Success.Println("Done") return nil } @@ -242,7 +246,7 @@ func (l Location) RunCron() error { lock.SetCron(l.Name, now.Unix()) l.Backup() } else { - fmt.Printf("Skipping \"%s\", not due yet.\n", l.Name) + colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.Name) } return nil } diff --git a/main.go b/main.go index 1f6b84c..66b47bf 100644 --- a/main.go +++ b/main.go @@ -15,8 +15,28 @@ limitations under the License. */ package main -import "github.com/cupcakearmy/autorestic/cmd" +import ( + "fmt" + "os" + "os/signal" + "syscall" + + "github.com/cupcakearmy/autorestic/cmd" + "github.com/cupcakearmy/autorestic/internal/lock" +) + +func handleCtrlC() { + c := make(chan os.Signal) + signal.Notify(c, os.Interrupt, syscall.SIGTERM) + go func() { + sig := <-c + fmt.Println("Signal:", sig) + lock.Unlock() + os.Exit(0) + }() +} func main() { + handleCtrlC() cmd.Execute() } diff --git a/tmp/hello.txt b/tmp/hello.txt new file mode 100644 index 0000000..2b0171d --- /dev/null +++ b/tmp/hello.txt @@ -0,0 +1 @@ +Mhh \ No newline at end of file