183 lines
3.4 KiB
Go
Executable File
183 lines
3.4 KiB
Go
Executable File
package lastn
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path"
|
|
"path/filepath"
|
|
"sort"
|
|
"time"
|
|
|
|
"gitea.bel.blue/local/storage"
|
|
|
|
"github.com/google/uuid"
|
|
)
|
|
|
|
type Config struct {
|
|
N int
|
|
RcloneConf string
|
|
RcloneAlias string
|
|
Ns string
|
|
Root string
|
|
Store string
|
|
StoreAddr string
|
|
StoreUser string
|
|
StorePass string
|
|
Cmd string
|
|
Tmp string
|
|
}
|
|
|
|
type LastN struct {
|
|
store storage.DB
|
|
conf Config
|
|
}
|
|
|
|
func New(conf Config) (*LastN, error) {
|
|
var store storage.DB
|
|
var err error
|
|
switch conf.Store {
|
|
case "rclone":
|
|
store, err = storage.New(storage.TypeFromString(conf.Store), conf.RcloneConf, path.Join(conf.RcloneAlias+":"))
|
|
default:
|
|
store, err = storage.New(storage.TypeFromString(conf.Store), conf.StoreAddr, conf.StoreUser, conf.StorePass)
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
root, err := filepath.Abs(conf.Root)
|
|
conf.Root = root
|
|
return &LastN{
|
|
store: store,
|
|
conf: conf,
|
|
}, err
|
|
}
|
|
|
|
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(
|
|
lastN.conf.Tmp,
|
|
fmt.Sprintf(
|
|
"%s.%s.tar",
|
|
time.Now().Format("2006.01.02.15.04.05"),
|
|
uuid.New().String(),
|
|
),
|
|
)
|
|
defer func() {
|
|
err := recover()
|
|
os.Remove(archive)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}()
|
|
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)
|
|
}
|
|
key := path.Base(archive)
|
|
if storeStream, ok := store.(storage.DBStream); ok {
|
|
f, err := os.Open(archive)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
return storeStream.SetStream(key, f, lastN.conf.Ns)
|
|
}
|
|
b, err := ioutil.ReadFile(archive)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Println("Created backup", key)
|
|
return store.Set(key, b, lastN.conf.Ns)
|
|
}
|
|
|
|
func (lastN *LastN) Clean() error {
|
|
n := lastN.conf.N
|
|
store := lastN.store
|
|
backups, err := store.List([]string{lastN.conf.Ns})
|
|
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, lastN.conf.Ns)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (lastN *LastN) List() error {
|
|
store := lastN.store
|
|
backups, err := store.List([]string{lastN.conf.Ns})
|
|
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.Stat(root); os.IsNotExist(err) {
|
|
if err := os.MkdirAll(root, os.ModePerm); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
store := lastN.store
|
|
backups, err := store.List([]string{lastN.conf.Ns})
|
|
if err != nil {
|
|
return fmt.Errorf("cannot list: %v", err)
|
|
}
|
|
sort.Strings(backups)
|
|
backup := backups[len(backups)-1]
|
|
b, err := store.Get(backup, lastN.conf.Ns)
|
|
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
|
|
}
|