This commit is contained in:
cupcakearmy 2021-04-11 13:04:11 +02:00
parent 43244302be
commit 335724cce7
No known key found for this signature in database
GPG Key ID: D28129AE5654D9D9
12 changed files with 315 additions and 34 deletions

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ node_modules
test test
autorestic autorestic
data data
dist

View File

@ -39,25 +39,18 @@ var backupCmd = &cobra.Command{
} }
defer lock.Unlock() defer lock.Unlock()
{ {
backup(internal.GetAllOrLocation(cmd, false), config) 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()
}
} }
}, },
} }
func init() { func init() {
rootCmd.AddCommand(backupCmd) rootCmd.AddCommand(backupCmd)
backupCmd.PersistentFlags().StringSliceP("location", "l", []string{}, "Locations") internal.AddFlagsToCommand(backupCmd, false)
backupCmd.PersistentFlags().BoolP("all", "a", false, "Backup all locations")
}
func backup(locations []string, config *internal.Config) {
for _, name := range locations {
location, ok := config.Locations[name]
if !ok {
fmt.Println(fmt.Errorf("location `%s` does not exist", name))
} else {
fmt.Printf("Backing up: `%s`", name)
location.Backup()
}
}
} }

View File

@ -31,20 +31,19 @@ var execCmd = &cobra.Command{
if err := config.CheckConfig(); err != nil { if err := config.CheckConfig(); err != nil {
panic(err) panic(err)
} }
exec(internal.GetAllOrLocation(cmd, true), config, args) {
}, selected, err := internal.GetAllOrSelected(cmd, true)
} cobra.CheckErr(err)
for _, name := range selected {
func init() {
rootCmd.AddCommand(execCmd)
execCmd.PersistentFlags().StringSliceP("backend", "b", []string{}, "backends")
execCmd.PersistentFlags().BoolP("all", "a", false, "Exec in all backends")
}
func exec(backends []string, config *internal.Config, args []string) {
for _, name := range backends {
fmt.Println(name) fmt.Println(name)
backend := config.Backends[name] backend := config.Backends[name]
backend.Exec(args) backend.Exec(args)
} }
} }
},
}
func init() {
rootCmd.AddCommand(execCmd)
internal.AddFlagsToCommand(execCmd, true)
}

49
cmd/forget.go Normal file
View File

@ -0,0 +1,49 @@
/*
Copyright © 2021 NAME HERE <EMAIL ADDRESS>
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
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"github.com/cupcakearmy/autorestic/internal"
"github.com/spf13/cobra"
)
// forgetCmd represents the forget command
var forgetCmd = &cobra.Command{
Use: "forget",
Short: "Forget and optionally prune snapshots according the specified policies",
Run: func(cmd *cobra.Command, args []string) {
config := internal.GetConfig()
if err := config.CheckConfig(); err != nil {
panic(err)
}
{
selected, err := internal.GetAllOrSelected(cmd, false)
cobra.CheckErr(err)
prune, _ := cmd.Flags().GetBool("prune")
for _, name := range selected {
location := config.Locations[name]
err := location.Forget(prune)
cobra.CheckErr(err)
}
}
},
}
func init() {
rootCmd.AddCommand(forgetCmd)
internal.AddFlagsToCommand(forgetCmd, false)
forgetCmd.Flags().Bool("prune", false, "Also prune repository")
}

34
cmd/install.go Normal file
View File

@ -0,0 +1,34 @@
/*
Copyright © 2021 NAME HERE <EMAIL ADDRESS>
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
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"github.com/cupcakearmy/autorestic/internal/bins"
"github.com/spf13/cobra"
)
var installCmd = &cobra.Command{
Use: "install",
Short: "A brief description of your command",
Run: func(cmd *cobra.Command, args []string) {
err := bins.InstallRestic()
cobra.CheckErr(err)
},
}
func init() {
rootCmd.AddCommand(installCmd)
}

View File

@ -30,6 +30,7 @@ var cfgFile string
// rootCmd represents the base command when called without any subcommands // rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Version: internal.VERSION,
Use: "autorestic", Use: "autorestic",
Short: "CLI Wrapper for restic", Short: "CLI Wrapper for restic",
} }

36
cmd/upgrade.go Normal file
View File

@ -0,0 +1,36 @@
/*
Copyright © 2021 NAME HERE <EMAIL ADDRESS>
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
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
import (
"github.com/cupcakearmy/autorestic/internal/bins"
"github.com/spf13/cobra"
)
var upgradeCmd = &cobra.Command{
Use: "upgrade",
Short: "A brief description of your command",
Run: func(cmd *cobra.Command, args []string) {
noRestic, _ := cmd.Flags().GetBool("no-restic")
err := bins.Upgrade(!noRestic)
cobra.CheckErr(err)
},
}
func init() {
rootCmd.AddCommand(upgradeCmd)
upgradeCmd.Flags().Bool("no-restic", false, "Also update restic. Default: true")
}

1
go.mod
View File

@ -3,6 +3,7 @@ module github.com/cupcakearmy/autorestic
go 1.16 go 1.16
require ( require (
github.com/blang/semver/v4 v4.0.0
github.com/buger/goterm v1.0.0 github.com/buger/goterm v1.0.0
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v1.1.3 github.com/spf13/cobra v1.1.3

3
go.sum
View File

@ -24,6 +24,8 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/buger/goterm v1.0.0 h1:ZB6uUlY8+sjJyFGzz2WpRqX2XYPeXVgtZAOJMwOsTWM= github.com/buger/goterm v1.0.0 h1:ZB6uUlY8+sjJyFGzz2WpRqX2XYPeXVgtZAOJMwOsTWM=
github.com/buger/goterm v1.0.0/go.mod h1:16STi3LquiscTIHA8SXUNKEa/Cnu4ZHBH8NsCaWgso0= github.com/buger/goterm v1.0.0/go.mod h1:16STi3LquiscTIHA8SXUNKEa/Cnu4ZHBH8NsCaWgso0=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
@ -248,7 +250,6 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54 h1:rF3Ohx8DRyl8h2zw9qojyLHLhrJpEMgyPOImREEryf0= golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54 h1:rF3Ohx8DRyl8h2zw9qojyLHLhrJpEMgyPOImREEryf0=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

127
internal/bins/bins.go Normal file
View File

@ -0,0 +1,127 @@
package bins
import (
"compress/bzip2"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path"
"runtime"
"strings"
"github.com/blang/semver/v4"
"github.com/cupcakearmy/autorestic/internal"
)
const INSTALL_PATH = "/usr/local/bin"
type GithubReleaseAsset struct {
Name string `json:"name"`
Link string `json:"browser_download_url"`
}
type GithubRelease struct {
Tag string `json:"tag_name"`
Assets []GithubReleaseAsset `json:"assets"`
}
func dlJSON(url string) (GithubRelease, error) {
var parsed GithubRelease
resp, err := http.Get(url)
if err != nil {
return parsed, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return parsed, err
}
json.Unmarshal(body, &parsed)
return parsed, nil
}
func InstallRestic() error {
installed := internal.CheckIfCommandIsCallable("restic")
if installed {
fmt.Println("restic already installed")
return nil
} else {
body, err := dlJSON("https://api.github.com/repos/restic/restic/releases/latest")
if err != nil {
return err
}
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
resp, err := http.Get(asset.Link)
if err != nil {
return err
}
defer resp.Body.Close()
// Uncompress
bz := bzip2.NewReader(resp.Body)
// Save binary
file, err := os.Create(path.Join(INSTALL_PATH, "restic"))
if err != nil {
return err
}
file.Chmod(0755)
defer file.Close()
io.Copy(file, bz)
fmt.Printf("Successfully installed restic under %s\n", INSTALL_PATH)
return nil
}
}
return errors.New("could not find right binary for your system, please install restic manually. https://bit.ly/2Y1Rzai")
}
}
func upgradeRestic() error {
out, err := internal.ExecuteCommand(internal.ExecuteOptions{
Command: "restic",
}, "self-update")
fmt.Println(out)
return err
}
func Upgrade(restic bool) error {
// Upgrade restic
if restic {
InstallRestic()
upgradeRestic()
}
// Upgrade self
current, err := semver.ParseTolerant(internal.VERSION)
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
}
latest, err := semver.ParseTolerant(body.Tag)
if err != nil {
return err
}
if current.GT(latest) {
fmt.Println("Updated autorestic")
} else {
fmt.Println("Already up to date")
}
return nil
}

View File

@ -12,6 +12,8 @@ import (
"github.com/spf13/viper" "github.com/spf13/viper"
) )
const VERSION = "1.0.0"
type Config struct { type Config struct {
Locations map[string]Location `mapstructure:"locations"` Locations map[string]Location `mapstructure:"locations"`
Backends map[string]Backend `mapstructure:"backends"` Backends map[string]Backend `mapstructure:"backends"`
@ -60,7 +62,7 @@ func (c Config) CheckConfig() error {
return nil return nil
} }
func GetAllOrLocation(cmd *cobra.Command, backends bool) []string { func GetAllOrSelected(cmd *cobra.Command, backends bool) ([]string, error) {
var list []string var list []string
if backends { if backends {
for key := range config.Backends { for key := range config.Backends {
@ -73,7 +75,7 @@ func GetAllOrLocation(cmd *cobra.Command, backends bool) []string {
} }
all, _ := cmd.Flags().GetBool("all") all, _ := cmd.Flags().GetBool("all")
if all { if all {
return list return list, nil
} else { } else {
var selected []string var selected []string
if backends { if backends {
@ -92,9 +94,22 @@ func GetAllOrLocation(cmd *cobra.Command, backends bool) []string {
} }
} }
if !found { if !found {
panic("invalid key") if backends {
return nil, fmt.Errorf("invalid backend \"%s\"", s)
} else {
return nil, fmt.Errorf("invalid location \"%s\"", s)
} }
} }
return selected }
return selected, nil
}
}
func AddFlagsToCommand(cmd *cobra.Command, backend bool) {
cmd.PersistentFlags().BoolP("all", "a", false, "Backup all locations")
if backend {
cmd.PersistentFlags().StringSliceP("backend", "b", []string{}, "backends")
} else {
cmd.PersistentFlags().StringSliceP("location", "l", []string{}, "Locations")
} }
} }

View File

@ -83,3 +83,27 @@ func (l Location) Backup() error {
} }
return nil return nil
} }
func (l Location) Forget(prune bool) error {
c := GetConfig()
from := GetPathRelativeToConfig(l.From)
for _, to := range l.To {
backend := c.Backends[to]
options := ExecuteOptions{
Envs: backend.getEnv(),
Dir: from,
}
flags := l.getOptions("forget")
cmd := []string{"forget", "--path", from}
if prune {
cmd = append(cmd, "--prune")
}
cmd = append(cmd, flags...)
out, err := ExecuteResticCommand(options, cmd...)
fmt.Println(out)
if err != nil {
return err
}
}
return nil
}