mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2025-09-02 00:30:40 +00:00
go rewrite
This commit is contained in:
55
internal/backend.go
Normal file
55
internal/backend.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Backend struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Path string `mapstructure:"path"`
|
||||
Key string `mapstructure:"key"`
|
||||
Env map[string]string `mapstructure:"env"`
|
||||
}
|
||||
|
||||
func (b Backend) generateRepo() (string, error) {
|
||||
switch b.Type {
|
||||
case "local":
|
||||
return GetPathRelativeToConfig(b.Path), nil
|
||||
case "b2", "azure", "gs", "s3", "sftp", "rest":
|
||||
return fmt.Sprintf("%s:%s", b.Type, b.Path), nil
|
||||
default:
|
||||
return "", fmt.Errorf("backend type \"%s\" is invalid", b.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func (b Backend) getEnv() map[string]string {
|
||||
env := make(map[string]string)
|
||||
env["RESTIC_PASSWORD"] = b.Key
|
||||
repo, err := b.generateRepo()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
env["RESTIC_REPOSITORY"] = repo
|
||||
return env
|
||||
}
|
||||
|
||||
func (b Backend) validate() error {
|
||||
options := ExecuteOptions{Envs: b.getEnv()}
|
||||
// Check if already initialized
|
||||
_, err := ExecuteResticCommand(options, "snapshots")
|
||||
if err == nil {
|
||||
return nil
|
||||
} else {
|
||||
// If not initialize
|
||||
out, err := ExecuteResticCommand(options, "init")
|
||||
fmt.Println(out)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func (b Backend) Exec(args []string) error {
|
||||
options := ExecuteOptions{Envs: b.getEnv()}
|
||||
out, err := ExecuteResticCommand(options, args...)
|
||||
fmt.Println(out)
|
||||
return err
|
||||
}
|
100
internal/config.go
Normal file
100
internal/config.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"path"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Locations map[string]Location `mapstructure:"locations"`
|
||||
Backends map[string]Backend `mapstructure:"backends"`
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
var config *Config
|
||||
|
||||
func GetConfig() *Config {
|
||||
if config == nil {
|
||||
once.Do(func() {
|
||||
config = &Config{}
|
||||
if err := viper.UnmarshalExact(config); err != nil {
|
||||
log.Fatal("Nope ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func GetPathRelativeToConfig(p string) string {
|
||||
if path.IsAbs(p) {
|
||||
return p
|
||||
} else if strings.HasPrefix(p, "~") {
|
||||
home, err := homedir.Dir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return path.Join(home, strings.TrimPrefix(p, "~"))
|
||||
} else {
|
||||
return path.Join(path.Dir(viper.ConfigFileUsed()), p)
|
||||
}
|
||||
}
|
||||
|
||||
func (c Config) CheckConfig() error {
|
||||
for name, backend := range c.Backends {
|
||||
if err := backend.validate(); err != nil {
|
||||
return fmt.Errorf("backend \"%s\": %s", name, err)
|
||||
}
|
||||
}
|
||||
for name, location := range c.Locations {
|
||||
if err := location.validate(c); err != nil {
|
||||
return fmt.Errorf("location \"%s\": %s", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAllOrLocation(cmd *cobra.Command, backends bool) []string {
|
||||
var list []string
|
||||
if backends {
|
||||
for key := range config.Backends {
|
||||
list = append(list, key)
|
||||
}
|
||||
} else {
|
||||
for key := range config.Locations {
|
||||
list = append(list, key)
|
||||
}
|
||||
}
|
||||
all, _ := cmd.Flags().GetBool("all")
|
||||
if all {
|
||||
return list
|
||||
} else {
|
||||
var selected []string
|
||||
if backends {
|
||||
tmp, _ := cmd.Flags().GetStringSlice("backend")
|
||||
selected = tmp
|
||||
} else {
|
||||
tmp, _ := cmd.Flags().GetStringSlice("location")
|
||||
selected = tmp
|
||||
}
|
||||
for _, s := range selected {
|
||||
found := false
|
||||
for _, l := range list {
|
||||
if l == s {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
panic("invalid key")
|
||||
}
|
||||
}
|
||||
return selected
|
||||
}
|
||||
}
|
85
internal/location.go
Normal file
85
internal/location.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type HookArray = []string
|
||||
|
||||
type Hooks struct {
|
||||
Before HookArray `mapstructure:"before"`
|
||||
After HookArray `mapstructure:"after"`
|
||||
}
|
||||
|
||||
type Options map[string]map[string][]string
|
||||
|
||||
type Location struct {
|
||||
From string `mapstructure:"from"`
|
||||
To []string `mapstructure:"to"`
|
||||
Hooks Hooks `mapstructure:"hooks"`
|
||||
Cron string `mapstructure:"cron"`
|
||||
Options Options `mapstructure:"options"`
|
||||
}
|
||||
|
||||
func (l Location) validate(c Config) error {
|
||||
// Check if backends are all valid
|
||||
for _, to := range l.To {
|
||||
_, ok := c.Backends[to]
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid backend `%s`", to)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l Location) getOptions(key string) []string {
|
||||
var options []string
|
||||
saved := l.Options[key]
|
||||
for k, values := range saved {
|
||||
for _, value := range values {
|
||||
options = append(options, fmt.Sprintf("--%s", k), value)
|
||||
}
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
func ExecuteHooks(commands []string, options ExecuteOptions) error {
|
||||
for _, command := range commands {
|
||||
out, err := ExecuteCommand(options, "-c", command)
|
||||
fmt.Println(out)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l Location) Backup() error {
|
||||
c := GetConfig()
|
||||
from := GetPathRelativeToConfig(l.From)
|
||||
for _, to := range l.To {
|
||||
backend := c.Backends[to]
|
||||
options := ExecuteOptions{
|
||||
Command: "bash",
|
||||
Envs: backend.getEnv(),
|
||||
Dir: from,
|
||||
}
|
||||
|
||||
if err := ExecuteHooks(l.Hooks.Before, options); err != nil {
|
||||
return nil
|
||||
}
|
||||
{
|
||||
flags := l.getOptions("backup")
|
||||
cmd := []string{"backup"}
|
||||
cmd = append(cmd, flags...)
|
||||
cmd = append(cmd, ".")
|
||||
out, err := ExecuteResticCommand(options, cmd...)
|
||||
fmt.Println(out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := ExecuteHooks(l.Hooks.After, options); err != nil {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
52
internal/lock/lock.go
Normal file
52
internal/lock/lock.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package lock
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path"
|
||||
"sync"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var lock *viper.Viper
|
||||
var file string
|
||||
var once sync.Once
|
||||
|
||||
func getLock() *viper.Viper {
|
||||
if lock == nil {
|
||||
|
||||
once.Do(func() {
|
||||
lock = viper.New()
|
||||
lock.SetDefault("running", false)
|
||||
p := path.Dir(viper.ConfigFileUsed())
|
||||
file = path.Join(p, ".autorestic.lock.yml")
|
||||
lock.SetConfigFile(file)
|
||||
lock.SetConfigType("yml")
|
||||
lock.ReadInConfig()
|
||||
})
|
||||
}
|
||||
return lock
|
||||
}
|
||||
|
||||
func set(locked bool) error {
|
||||
lock := getLock()
|
||||
if locked {
|
||||
running := lock.GetBool("running")
|
||||
if running {
|
||||
return errors.New("an instance is already running")
|
||||
}
|
||||
}
|
||||
lock.Set("running", locked)
|
||||
if err := lock.WriteConfigAs(file); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Lock() error {
|
||||
return set(true)
|
||||
}
|
||||
|
||||
func Unlock() error {
|
||||
return set(false)
|
||||
}
|
20
internal/terminal/main.go
Normal file
20
internal/terminal/main.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package terminal
|
||||
|
||||
import (
|
||||
tm "github.com/buger/goterm"
|
||||
)
|
||||
|
||||
func Clear() {
|
||||
tm.Clear()
|
||||
}
|
||||
|
||||
func Append(line string) {
|
||||
tm.Println(line)
|
||||
tm.Flush()
|
||||
}
|
||||
|
||||
func Replace(line string) {
|
||||
tm.MoveCursorUp(1)
|
||||
tm.Print("\033[K")
|
||||
Append(line)
|
||||
}
|
48
internal/utils.go
Normal file
48
internal/utils.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
func CheckIfCommandIsCallable(cmd string) bool {
|
||||
_, err := exec.LookPath(cmd)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func CheckIfResticIsCallable() bool {
|
||||
return CheckIfCommandIsCallable("restic")
|
||||
}
|
||||
|
||||
type ExecuteOptions struct {
|
||||
Command string
|
||||
Envs map[string]string
|
||||
Dir string
|
||||
}
|
||||
|
||||
func ExecuteCommand(options ExecuteOptions, args ...string) (string, error) {
|
||||
cmd := exec.Command(options.Command, args...)
|
||||
env := os.Environ()
|
||||
for k, v := range options.Envs {
|
||||
env = append(env, fmt.Sprintf("%s=%s", k, v))
|
||||
}
|
||||
cmd.Env = env
|
||||
cmd.Dir = options.Dir
|
||||
|
||||
var out bytes.Buffer
|
||||
var error bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &error
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return error.String(), err
|
||||
}
|
||||
return out.String(), nil
|
||||
}
|
||||
|
||||
func ExecuteResticCommand(options ExecuteOptions, args ...string) (string, error) {
|
||||
options.Command = "restic"
|
||||
return ExecuteCommand(options, args...)
|
||||
}
|
Reference in New Issue
Block a user