lastn/main.go

196 lines
4.0 KiB
Go
Executable File

package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"local/args"
"local/storage"
"log"
"os"
"os/exec"
"path"
"path/filepath"
"sort"
"time"
)
type Config struct {
n int
conf string
rclone string
ns string
root string
store string
cmd string
}
type LastN struct {
store storage.DB
conf Config
}
func main() {
conf := config()
log.Println(conf)
storage, err := storage.New(storage.TypeFromString(conf.store), conf.conf, path.Join(conf.rclone+":", conf.ns))
if err != nil {
panic(err)
}
lastn := &LastN{
conf: conf,
store: storage,
}
actions := []func() error{}
switch conf.cmd {
case "backup":
actions = append(actions, lastn.push, lastn.clean)
case "list":
actions = append(actions, lastn.list)
case "clean":
actions = append(actions, lastn.clean)
case "restore":
actions = append(actions, lastn.restore)
default:
panic(fmt.Sprintf("not impl: %s"))
}
for _, action := range actions {
if err := action(); err != nil {
panic(err)
}
}
}
func config() Config {
as := args.NewArgSet()
as.Append(args.INT, "n", "number of backups to retain", 5)
as.Append(args.STRING, "conf", "path to rclone conf", path.Join(os.Getenv("HOME"), "/.config/rclone/rclone.conf"))
as.Append(args.STRING, "root", "path to root", "./public")
as.Append(args.STRING, "ns", "ns for backups", path.Join("lastn", "dev"))
as.Append(args.STRING, "rclone", "rclone backend name", "blapointe-drive-enc")
as.Append(args.STRING, "store", "type of store, like [map rclone]", "map")
as.Append(args.STRING, "cmd", "[backup, restore, list, clean]", "backup")
if err := as.Parse(); err != nil {
panic(err)
}
root, err := filepath.Abs(as.Get("root").GetString())
if err != nil {
panic(err)
}
return Config{
n: as.Get("n").GetInt(),
conf: as.Get("conf").GetString(),
rclone: as.Get("rclone").GetString(),
root: root,
ns: as.Get("ns").GetString(),
store: as.Get("store").GetString(),
cmd: as.Get("cmd").GetString(),
}
}
func (lastN *LastN) push() error {
root := lastN.conf.root
store := lastN.store
root, err := filepath.Abs(root)
if err != nil {
return err
}
archive := path.Join(
os.TempDir(),
fmt.Sprintf(
"%s.tar",
time.Now().Format("2006.01.02.15.04.05"),
),
)
cmd := exec.Command(
"tar",
"-czf",
archive,
"-C",
path.Dir(root),
path.Base(root),
)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("%v: %s", err, out)
}
b, err := ioutil.ReadFile(archive)
if err != nil {
return err
}
log.Println("Created backup", path.Base(archive))
return store.Set(path.Base(archive), b)
}
func (lastN *LastN) clean() error {
n := lastN.conf.n
store := lastN.store
backups, err := store.List(nil)
if err != nil {
return err
}
sort.Strings(backups)
for i := 0; i < len(backups)-n; i++ {
log.Println("Pruning old backup", backups[i])
err := store.Set(backups[i], nil)
if err != nil {
return err
}
}
return nil
}
func (lastN *LastN) list() error {
store := lastN.store
backups, err := store.List(nil)
if err != nil {
return err
}
sort.Strings(backups)
for _, backup := range backups {
log.Println(backup)
}
return nil
}
func (lastN *LastN) restore() error {
root := lastN.conf.root + "-restore"
os.RemoveAll(root)
if err := os.MkdirAll(root, os.ModePerm); err != nil {
return err
}
store := lastN.store
backups, err := store.List(nil)
if err != nil {
return fmt.Errorf("cannot list: %v", err)
}
sort.Strings(backups)
backup := backups[len(backups)-1]
b, err := store.Get(backup)
if err != nil {
return fmt.Errorf("cannot get %s: %v", backup, err)
}
log.Printf("restoring %s (%v) in %s", backup, len(b), root)
cmd := exec.Command(
"tar",
"-C",
root,
"-xzf",
"-",
)
stdin, err := cmd.StdinPipe()
if err != nil {
return fmt.Errorf("cannot get stdin: %v", err)
}
go func() {
defer stdin.Close()
io.Copy(stdin, bytes.NewReader(b))
}()
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("failed tar -xf: %v: %s", err, out)
}
return nil
}