restictray/restic/wrapper.go

119 lines
2.9 KiB
Go

package restic
import (
"encoding/json"
"fmt"
"github.com/rs/zerolog/log"
"os/exec"
"time"
)
type ResticSnapshot struct {
Time time.Time `json:"time"` // format 2023-03-01T16:15:34.111513+01:00
Tree string `json:"tree"`
Paths []string `json:"paths"`
Id string `json:"short_id"`
}
func (rs ResticSnapshot) ShortTime() string {
return rs.Time.Format(ShortTimeFormat)
}
type Wrapper struct {
LatestSnapshots []ResticSnapshot
mountCommand *exec.Cmd
}
func (w *Wrapper) HasSnapshots() bool {
return len(w.LatestSnapshots) > 0
}
func (w *Wrapper) LastSnapshot() ResticSnapshot {
if !w.HasSnapshots() {
return ResticSnapshot{
Id: "(no snapshots yet)",
Time: time.Time{},
}
}
return w.LatestSnapshots[len(w.LatestSnapshots)-1]
}
func resticCmd(args ...string) *exec.Cmd {
cmd := exec.Command("restic", args...)
log.Debug().Msg(cmd.String())
return cmd
}
/*
Expected JSON format:
[
{
"time": "2023-03-01T16:15:34.111513+01:00",
"tree": "d603aa4c6ce2bdef784dbdcd36461970d7f0cc8083d31f46d23cdb9bef172f0a",
"paths": [
"/Users/wgroeneveld"
],
"hostname": "Wouters-M1-Air.local",
"username": "wgroeneveld",
"uid": 501,
"gid": 20,
"id": "31ae2a213c5750c4f86ebe8a8e989a5d4de2963c911e7513f47ca227723a0d95",
"short_id": "31ae2a21"
}
]
*/
func (w *Wrapper) UpdateLatestSnapshots(c *Config) error {
cmd := resticCmd("--json", "--password-file", c.PasswordFile(), "-r", c.Repository, "snapshots")
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("restic snapshots cmd: %s: %w", string(out), err)
}
err = json.Unmarshal(out, &w.LatestSnapshots)
if err != nil {
return err
}
log.Debug().Any("snapshots", w.LatestSnapshots).Msg("update")
return nil
}
func (w *Wrapper) MountBackups(c *Config) error {
c.CreateMountDirIfDoesntExist()
if w.mountCommand != nil && w.mountCommand.Process != nil {
// could be killed or terminted due to manual unmount, ignore errors and retry anyway
w.mountCommand.Process.Kill()
}
// Open the folder first: MacFuse could take a second; results in occasional weird errors otherwise.
openFolder(c.MountDir())
w.mountCommand = resticCmd("--password-file", c.PasswordFile(), "-r", c.Repository, "mount", c.MountDir())
err := w.mountCommand.Start() // restic's mount is a blocking call
if err != nil {
return fmt.Errorf("restic mount cmd: %w", err)
}
return nil
}
func openFolder(folder string) error {
cmd := exec.Command("open", folder)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("open mount dir: %s: %w", string(out), err)
}
return nil
}
func (w *Wrapper) Backup(c *Config) error {
cmd := resticCmd("--json", "--password-file", c.PasswordFile(), "-r", c.Repository, "--exclude-file", c.ExcludeFile(), "backup", c.Backup)
out, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("restic backup cmd: %s: %w", string(out), err)
}
log.Debug().Str("out", string(out)).Msg("backup")
return nil
}