fyne-io systray attempt; crude first restic wrapper
This commit is contained in:
parent
0a4c497fda
commit
3a8e84414e
|
@ -1,2 +1,3 @@
|
|||
.idea/
|
||||
on_exit_*.txt
|
||||
restictray
|
18
go.mod
18
go.mod
|
@ -3,18 +3,14 @@ module brainbaking.com/restictray
|
|||
go 1.19
|
||||
|
||||
require (
|
||||
github.com/getlantern/systray v1.2.1
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966
|
||||
fyne.io/systray v1.10.0
|
||||
github.com/rs/zerolog v1.29.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 // indirect
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 // indirect
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 // indirect
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 // indirect
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 // indirect
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f // indirect
|
||||
github.com/go-stack/stack v1.8.0 // indirect
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c // indirect
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 // indirect
|
||||
github.com/godbus/dbus/v5 v5.0.4 // indirect
|
||||
github.com/mattn/go-colorable v0.1.12 // indirect
|
||||
github.com/mattn/go-isatty v0.0.14 // indirect
|
||||
github.com/tevino/abool v1.2.0 // indirect
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 // indirect
|
||||
)
|
||||
|
|
46
go.sum
46
go.sum
|
@ -1,29 +1,19 @@
|
|||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520 h1:NRUJuo3v3WGC/g5YiyF790gut6oQr5f3FBI88Wv0dx4=
|
||||
github.com/getlantern/context v0.0.0-20190109183933-c447772a6520/go.mod h1:L+mq6/vvYHKjCX2oez0CgEAJmbq1fbb/oNJIWQkBybY=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7 h1:6uJ+sZ/e03gkbqZ0kUG6mfKoqDb4XMAzMIwlajq19So=
|
||||
github.com/getlantern/errors v0.0.0-20190325191628-abdb3e3e36f7/go.mod h1:l+xpFBrCtDLpK9qNjxs+cHU6+BAdlBaxHqikB6Lku3A=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7 h1:guBYzEaLz0Vfc/jv0czrr2z7qyzTOGC9hiQ0VC+hKjk=
|
||||
github.com/getlantern/golog v0.0.0-20190830074920-4ef2e798c2d7/go.mod h1:zx/1xUUeYPy3Pcmet8OSXLbF47l+3y6hIPpyLWoR9oc=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7 h1:micT5vkcr9tOVk1FiH8SWKID8ultN44Z+yzd2y/Vyb0=
|
||||
github.com/getlantern/hex v0.0.0-20190417191902-c6586a6fe0b7/go.mod h1:dD3CgOrwlzca8ed61CsZouQS5h5jIzkK9ZWrTcf0s+o=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55 h1:XYzSdCbkzOC0FDNrgJqGRo8PCMFOBFL9py72DRs7bmc=
|
||||
github.com/getlantern/hidden v0.0.0-20190325191715-f02dbb02be55/go.mod h1:6mmzY2kW1TOOrVy+r41Za2MxXM+hhqTtY3oBKd2AgFA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f h1:wrYrQttPS8FHIRSlsrcuKazukx/xqO/PpLZzZXsF+EA=
|
||||
github.com/getlantern/ops v0.0.0-20190325191751-d70cb0d6f85f/go.mod h1:D5ao98qkA6pxftxoqzibIBBrLSUli+kYnJqrgBf9cIA=
|
||||
github.com/getlantern/systray v1.2.1 h1:udsC2k98v2hN359VTFShuQW6GGprRprw6kD6539JikI=
|
||||
github.com/getlantern/systray v1.2.1/go.mod h1:AecygODWIsBquJCJFop8MEQcJbWFfw/1yWbVabNgpCM=
|
||||
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c h1:rp5dCmg/yLR3mgFuSOe4oEnDDmGLROTvMragMUXpTQw=
|
||||
github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 h1:JIAuq3EEf9cgbU6AtGPK4CTG3Zf6CKMNqf0MHTggAUA=
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9 h1:YTzHMGlqJu67/uEo1lBv0n3wBXhXNeUbB1XfN2vmTm0=
|
||||
fyne.io/systray v1.10.0 h1:Yr1D9Lxeiw3+vSuZWPlaHC8BMjIHZXJKkek706AfYQk=
|
||||
fyne.io/systray v1.10.0/go.mod h1:oM2AQqGJ1AMo4nNqZFYU8xYygSBZkW2hmdJ7n4yjedE=
|
||||
github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/godbus/dbus/v5 v5.0.4 h1:9349emZab16e7zQvpmsbtjc18ykshndd8y2PG3sgJbA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
|
||||
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
|
||||
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
|
||||
github.com/rs/zerolog v1.29.0 h1:Zes4hju04hjbvkVkOhdl2HpZa+0PmVwigmo8XoORE5w=
|
||||
github.com/rs/zerolog v1.29.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
|
||||
github.com/tevino/abool v1.2.0 h1:heAkClL8H6w+mK5md9dzsuohKeXHUpY7Vw0ZCKW+huA=
|
||||
github.com/tevino/abool v1.2.0/go.mod h1:qc66Pna1RiIsPa7O4Egxxs9OqkuxDX55zznh9K07Tzg=
|
||||
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6 h1:foEbQz/B0Oz6YIqu/69kfXPYeFQAuuMYFkjaqXzl5Wo=
|
||||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
205
main.go
205
main.go
|
@ -1,107 +1,130 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"brainbaking.com/restictray/restic"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/getlantern/systray"
|
||||
"github.com/getlantern/systray/example/icon"
|
||||
"github.com/skratchdot/open-golang/open"
|
||||
"fyne.io/systray"
|
||||
"fyne.io/systray/example/icon"
|
||||
)
|
||||
|
||||
// I'm ignoring go threading issues here; assume no clicks happen at the same time.
|
||||
var wrapper *restic.Wrapper
|
||||
|
||||
func main() {
|
||||
onExit := func() {
|
||||
now := time.Now()
|
||||
ioutil.WriteFile(fmt.Sprintf(`on_exit_%d.txt`, now.UnixNano()), []byte(now.String()), 0644)
|
||||
// init and setup logging
|
||||
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr})
|
||||
wrapper = &restic.Wrapper{}
|
||||
|
||||
// bootstrap systray (this is a blocking call, second func is onExit)
|
||||
systray.Run(onSystrayReady, func() {})
|
||||
}
|
||||
|
||||
func addMenuLatestSnapshot() {
|
||||
snapshot := wrapper.LastSnapshot()
|
||||
systray.AddMenuItem("Latest: "+snapshot.Id+" @ "+snapshot.ShortTime(), "Latest Restic snapshot")
|
||||
}
|
||||
|
||||
func addMenuNextTime(cnf *restic.Config) {
|
||||
nextTime := wrapper.LastSnapshot().Time.Add(time.Duration(cnf.BackupTimeInHours) * time.Hour)
|
||||
msg := "Next @ " + nextTime.Format(restic.ShortTimeFormat)
|
||||
if time.Now().After(nextTime) {
|
||||
msg = "⚠️ Overdue - " + msg
|
||||
}
|
||||
|
||||
systray.Run(onReady, onExit)
|
||||
systray.AddMenuItem(msg, "Future Restic snapshot")
|
||||
}
|
||||
|
||||
func onReady() {
|
||||
systray.SetTemplateIcon(icon.Data, icon.Data)
|
||||
systray.SetTitle("Awesome App")
|
||||
systray.SetTooltip("Lantern")
|
||||
mQuitOrig := systray.AddMenuItem("Quit", "Quit the whole app")
|
||||
func addMenuQuit() {
|
||||
systray.AddSeparator()
|
||||
addMenuWithQuitAction("Quit", "Quit Restictray")
|
||||
}
|
||||
|
||||
func addMenuError(err error) {
|
||||
addMenuWithQuitAction("❗ "+err.Error(), "Restictray Error")
|
||||
}
|
||||
|
||||
func handleError(err error) {
|
||||
log.Err(err).Msg("")
|
||||
systray.ResetMenu()
|
||||
addMenuError(err)
|
||||
addMenuQuit()
|
||||
}
|
||||
|
||||
func addMenuWithQuitAction(title string, tooltip string) {
|
||||
mQuit := systray.AddMenuItem(title, tooltip)
|
||||
go func() {
|
||||
<-mQuitOrig.ClickedCh
|
||||
fmt.Println("Requesting quit")
|
||||
<-mQuit.ClickedCh
|
||||
log.Info().Msg("Requesting quit")
|
||||
systray.Quit()
|
||||
fmt.Println("Finished quitting")
|
||||
}()
|
||||
|
||||
// We can manipulate the systray in other goroutines
|
||||
go func() {
|
||||
systray.SetTemplateIcon(icon.Data, icon.Data)
|
||||
systray.SetTitle("Awesome App")
|
||||
systray.SetTooltip("Pretty awesome棒棒嗒")
|
||||
mChange := systray.AddMenuItem("Change Me", "Change Me")
|
||||
mChecked := systray.AddMenuItemCheckbox("Unchecked", "Check Me", true)
|
||||
mEnabled := systray.AddMenuItem("Enabled", "Enabled")
|
||||
// Sets the icon of a menu item. Only available on Mac.
|
||||
mEnabled.SetTemplateIcon(icon.Data, icon.Data)
|
||||
|
||||
systray.AddMenuItem("Ignored", "Ignored")
|
||||
|
||||
subMenuTop := systray.AddMenuItem("SubMenuTop", "SubMenu Test (top)")
|
||||
subMenuMiddle := subMenuTop.AddSubMenuItem("SubMenuMiddle", "SubMenu Test (middle)")
|
||||
subMenuBottom := subMenuMiddle.AddSubMenuItemCheckbox("SubMenuBottom - Toggle Panic!", "SubMenu Test (bottom) - Hide/Show Panic!", false)
|
||||
subMenuBottom2 := subMenuMiddle.AddSubMenuItem("SubMenuBottom - Panic!", "SubMenu Test (bottom)")
|
||||
|
||||
mUrl := systray.AddMenuItem("Open UI", "my home")
|
||||
mQuit := systray.AddMenuItem("退出", "Quit the whole app")
|
||||
|
||||
// Sets the icon of a menu item. Only available on Mac.
|
||||
mQuit.SetIcon(icon.Data)
|
||||
|
||||
systray.AddSeparator()
|
||||
mToggle := systray.AddMenuItem("Toggle", "Toggle the Quit button")
|
||||
shown := true
|
||||
toggle := func() {
|
||||
if shown {
|
||||
subMenuBottom.Check()
|
||||
subMenuBottom2.Hide()
|
||||
mQuitOrig.Hide()
|
||||
mEnabled.Hide()
|
||||
shown = false
|
||||
} else {
|
||||
subMenuBottom.Uncheck()
|
||||
subMenuBottom2.Show()
|
||||
mQuitOrig.Show()
|
||||
mEnabled.Show()
|
||||
shown = true
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mChange.ClickedCh:
|
||||
mChange.SetTitle("I've Changed")
|
||||
case <-mChecked.ClickedCh:
|
||||
if mChecked.Checked() {
|
||||
mChecked.Uncheck()
|
||||
mChecked.SetTitle("Unchecked")
|
||||
} else {
|
||||
mChecked.Check()
|
||||
mChecked.SetTitle("Checked")
|
||||
}
|
||||
case <-mEnabled.ClickedCh:
|
||||
mEnabled.SetTitle("Disabled")
|
||||
mEnabled.Disable()
|
||||
case <-mUrl.ClickedCh:
|
||||
open.Run("https://www.getlantern.org")
|
||||
case <-subMenuBottom2.ClickedCh:
|
||||
panic("panic button pressed")
|
||||
case <-subMenuBottom.ClickedCh:
|
||||
toggle()
|
||||
case <-mToggle.ClickedCh:
|
||||
toggle()
|
||||
case <-mQuit.ClickedCh:
|
||||
systray.Quit()
|
||||
fmt.Println("Quit2 now...")
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func onSystrayReady() {
|
||||
systray.SetTemplateIcon(icon.Data, icon.Data)
|
||||
systray.SetTooltip("Restictray")
|
||||
systray.AddMenuItem("... Initializing", "Initializing, please wait.")
|
||||
addMenuQuit()
|
||||
|
||||
cnf, err := restic.ReadConfig()
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := wrapper.UpdateLatestSnapshots(cnf)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
resetAndBuildMainMenu(cnf)
|
||||
}()
|
||||
}
|
||||
|
||||
// See https://github.com/fyne-io/systray/tree/master/example for more examples
|
||||
func resetAndBuildMainMenu(cnf *restic.Config) {
|
||||
systray.ResetMenu()
|
||||
addMenuLatestSnapshot()
|
||||
addMenuNextTime(cnf)
|
||||
systray.AddSeparator()
|
||||
mnuBackupNow := systray.AddMenuItem("Backup now", "Backup now")
|
||||
mnuBrowse := systray.AddMenuItem("Browse backups in Finder...", "Mount and browse backups")
|
||||
addMenuQuit()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mnuBackupNow.ClickedCh:
|
||||
onClickedMenuBackupNow(mnuBackupNow, cnf)
|
||||
case <-mnuBrowse.ClickedCh:
|
||||
onClickedMenuBrowse(mnuBrowse, cnf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func onClickedMenuBrowse(browse *systray.MenuItem, cnf *restic.Config) {
|
||||
err := wrapper.MountBackups(cnf)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func onClickedMenuBackupNow(mnu *systray.MenuItem, cnf *restic.Config) {
|
||||
mnu.SetTitle("🔄 Backup in progress...")
|
||||
mnu.Disable()
|
||||
// TODO after a backup, reinitialize latest snapshot + latest/next menus
|
||||
// TODO not by calling resetAndBuild again: this is from the for{}?
|
||||
// TODO how does this interop with a future goroutine that auto-backups?
|
||||
// TODO need for separate "backupInProgress" bool?
|
||||
err := wrapper.Backup(cnf)
|
||||
mnu.SetTitle("Backup now")
|
||||
mnu.Enable()
|
||||
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,64 @@
|
|||
package restic
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/rs/zerolog/log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Repository string `json:"repository"`
|
||||
Backup string `json:"backup"`
|
||||
BackupTimeInHours int `json:"backupTimeInHours"`
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultBackupTimeInHours int = 24
|
||||
ShortTimeFormat string = "2006-01-02T15:04:05"
|
||||
)
|
||||
|
||||
var home, _ = os.UserHomeDir()
|
||||
|
||||
func (cnf *Config) PasswordFile() string {
|
||||
return home + "/.restic/password.txt"
|
||||
}
|
||||
|
||||
func (cnf *Config) ExcludeFile() string {
|
||||
return home + "/.restic/excludes.txt"
|
||||
}
|
||||
|
||||
func (cnf *Config) CreateMountDirIfDoesntExist() error {
|
||||
if _, err := os.Stat(cnf.MountDir()); os.IsNotExist(err) {
|
||||
return os.Mkdir(cnf.MountDir(), os.ModePerm)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (cnf *Config) MountDir() string {
|
||||
return home + "/.restic/mnt"
|
||||
}
|
||||
|
||||
func ReadConfig() (*Config, error) {
|
||||
confData, err := os.ReadFile(home + "/.restic/config.json")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("No config.json found: %w", err)
|
||||
}
|
||||
|
||||
conf := &Config{}
|
||||
err = json.Unmarshal(confData, conf)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("config.json malformed JSON: %w", err)
|
||||
}
|
||||
if conf.Repository == "" || conf.Backup == "" {
|
||||
err := errors.New("config.json is missing required keys 'Backup' or 'Repository'")
|
||||
return nil, err
|
||||
}
|
||||
if conf.BackupTimeInHours == 0 {
|
||||
log.Warn().Msg("backupTimeInHours missing in config, reverting to 24hrs")
|
||||
conf.BackupTimeInHours = DefaultBackupTimeInHours
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
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
|
||||
}
|
Loading…
Reference in New Issue