add readme/todo/license, resilience as timeout when e.g. sftp connection fails
This commit is contained in:
parent
bb4079fa99
commit
c5e596a9d5
|
@ -0,0 +1,14 @@
|
|||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
Version 2, December 2004
|
||||
|
||||
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
|
||||
|
||||
Everyone is permitted to copy and distribute verbatim or modified
|
||||
copies of this license document, and changing it is allowed as long
|
||||
as the name is changed.
|
||||
|
||||
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. You just DO WHAT THE FUCK YOU WANT TO.
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
# Restictray
|
||||
|
||||
A macOS system tray wrapper around [Restic](https://restic.net/), the Go-powered cmd-line backup tool.
|
||||
|
||||
With Restictray, you can monitor and trigger backups from the system tray:
|
||||
|
||||
![](img/restictray.jpg)
|
||||
|
||||
Restictray is designed in such a way that it checks whether or not a backup is needed every hour by looking at the latest date in the `restic snapshot` output that's also part of the menu. If the backup command fails, the SFTP network goes down, or your laptop is offline, it will resume next time it's booted.
|
||||
|
||||
It's also possible to trigger a backup manually.
|
||||
|
||||
For convenience, browsing backups in Finder is done through `restic mount`, which means you will need to **install MacFUSE 4.x** through https://osxfuse.github.io/ for it to work!
|
||||
|
||||
This was designed for my wife to access backups with a button press.
|
||||
|
||||
## Configuration
|
||||
|
||||
Restictray currently expects the following files in `~/.restic/`:
|
||||
|
||||
1. `password.txt` as hardcoded `--password-file` argument
|
||||
2. `excludes.txt` as hardcoded `--exclude-file` argument
|
||||
3. `config.json` that configures the repository, the folder(s)/file(s) to backup, and the interval in hours:
|
||||
|
||||
```json
|
||||
{
|
||||
"repository": "sftp:user@server:/somewhere/resticdir",
|
||||
"backup": "/Users/username",
|
||||
"backupTimeInHours": 24
|
||||
}
|
||||
```
|
||||
|
||||
Where `repository` is the restic `-r` argument and `backup` the folder(s)/file(s) fed into the `backup` command.
|
||||
|
||||
**The repository should already be initialized!** You'll have to do this yourself using `restic -r [repo] init`.
|
||||
|
||||
If `backupTimeInHours` is absent, it will default to **24**: backup once a day.
|
||||
|
||||
For more information on how the restic arguments themselves work, please see the restic docs at https://restic.readthedocs.io/en/stable/.
|
||||
|
||||
### Dev config
|
||||
|
||||
If environment variable `RESTICTRAY_DEV` is set, Restictray configures Zerolog to use stdout and the prettyprint formatter instead of the external log, plus it relies on the `restic` command in `$PATH` instead of looking for it in the currently executing folder.
|
||||
|
||||
## Deploying
|
||||
|
||||
Restictray can be wrapped as a macOS `.app` folder that can be distributed. See `build.sh` on how to do this---I've used `fyne package`: see docs at https://developer.fyne.io/started/packaging.
|
||||
|
||||
![](img/restictray-app.jpg)
|
||||
|
||||
The app also wraps the `restic` binary so no local install is needed.
|
||||
|
||||
Please note that the current supplied one in `build/` is an ARM64 macOS-specific binary for that very reason.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
Restictray uses Lumberjack and Zerolog to log info to `~/.restic/log.txt`. If a command fails, it should be logged there.
|
4
TODO.md
4
TODO.md
|
@ -7,8 +7,8 @@
|
|||
- [ ] Create default files if not existing?
|
||||
- [ ] Create a config dialog in https://developer.fyne.io/
|
||||
- [ ] Add additional resilience:
|
||||
- [ ] What if SSH backup and network goes down? Do a `ping` before backup? Is there a timeout from the `restic` command itself?
|
||||
- [ ] If something goes wrong, menu shows error and app becomes unusable. Perhaps not all errors (e.g. above one) have to be this way.
|
||||
- [X] What if SSH backup and network goes down? Do a `ping` before backup? Is there a timeout from the `restic` command itself?
|
||||
- [X] If something goes wrong, menu shows error and app becomes unusable. Perhaps not all errors (e.g. above one) have to be this way.
|
||||
- [X] Is backing up while mounted ok? => yes
|
||||
- [ ] `restic backup` can result in `Warning: at least one source file could not be read\n: exit status 3`. This returns an error, but the backup itself seems to be all right.
|
||||
- [X] exit code 3: https://restic.readthedocs.io/en/stable/040_backup.html likely to be a permission problem, perhaps ignoring instead of panicking as of now?
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cmd := neverReturningThing()
|
||||
//cmd := returningThingWithinThreeSecs()
|
||||
//cmd := returningThingAfterThreeSecs()
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
|
||||
busy := make(chan bool, 1)
|
||||
cmd.Start()
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
go func() {
|
||||
fmt.Println("starting scanner.scan (blocking)")
|
||||
for scanner.Scan() {
|
||||
fmt.Println("scanned")
|
||||
m := scanner.Text()
|
||||
fmt.Println(m)
|
||||
if busy != nil {
|
||||
busy <- true
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
fmt.Println("starting select chans")
|
||||
var err error
|
||||
select {
|
||||
case <-busy:
|
||||
fmt.Println("busy chan")
|
||||
busy = nil
|
||||
case <-time.After(3 * time.Second):
|
||||
pkill := cmd.Process.Kill()
|
||||
fmt.Println("timeout triggered? err: %w", pkill)
|
||||
}
|
||||
|
||||
fmt.Println("starting Wait()")
|
||||
err = cmd.Wait()
|
||||
fmt.Printf("done? here's an error: %w", err)
|
||||
}
|
||||
|
||||
func returningThingWithinThreeSecs() *exec.Cmd {
|
||||
return exec.Command("echo", "sup")
|
||||
}
|
||||
|
||||
func returningThingAfterThreeSecs() *exec.Cmd {
|
||||
return exec.Command("ping", "google.com")
|
||||
}
|
||||
|
||||
func neverReturningThing() *exec.Cmd {
|
||||
return exec.Command("ssh", "user@unknown.local")
|
||||
}
|
4
go.mod
4
go.mod
|
@ -5,13 +5,17 @@ go 1.19
|
|||
require (
|
||||
fyne.io/systray v1.10.0
|
||||
github.com/rs/zerolog v1.29.0
|
||||
github.com/stretchr/testify v1.8.2
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // 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/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/tevino/abool v1.2.0 // indirect
|
||||
golang.org/x/sys v0.6.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
|
17
go.sum
17
go.sum
|
@ -1,6 +1,9 @@
|
|||
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/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
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=
|
||||
|
@ -8,9 +11,18 @@ github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb
|
|||
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
|
||||
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
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=
|
||||
|
@ -18,5 +30,10 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 79 KiB |
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
136
main.go
136
main.go
|
@ -14,7 +14,7 @@ import (
|
|||
// I'm ignoring go threading issues here; assume no clicks happen at the same time.
|
||||
var wrapper *restic.Wrapper
|
||||
|
||||
func addMenuQuit() {
|
||||
func addMenuQuit() *systray.MenuItem {
|
||||
mQuit := systray.AddMenuItem("Quit", "Quit Restictray")
|
||||
go func() {
|
||||
<-mQuit.ClickedCh
|
||||
|
@ -22,6 +22,7 @@ func addMenuQuit() {
|
|||
wrapper.Cleanup()
|
||||
systray.Quit()
|
||||
}()
|
||||
return mQuit
|
||||
}
|
||||
|
||||
func addMenuError(err error) {
|
||||
|
@ -62,7 +63,7 @@ func main() {
|
|||
}
|
||||
|
||||
// Updates snapshot information in restic wrapper and menu labels. This is a blocking call.
|
||||
func updateSnapshots(cnf *restic.Config, mnuLatest *systray.MenuItem, mnuNext *systray.MenuItem, mnuBackupNow *systray.MenuItem) {
|
||||
func updateSnapshots(cnf *restic.Config, mnu *resticmenu) {
|
||||
err := wrapper.UpdateLatestSnapshots(cnf)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
|
@ -75,10 +76,9 @@ func updateSnapshots(cnf *restic.Config, mnuLatest *systray.MenuItem, mnuNext *s
|
|||
msg = "⚠️ Overdue - " + msg
|
||||
}
|
||||
|
||||
mnuLatest.SetTitle("Latest: " + snapshot.Id + " @ " + snapshot.ShortTime())
|
||||
mnuNext.SetTitle(msg)
|
||||
mnuBackupNow.Enable()
|
||||
mnuBackupNow.SetTitle("Backup now")
|
||||
mnu.latestSnapshot.SetTitle("Latest: " + snapshot.Id + " @ " + snapshot.ShortTime())
|
||||
mnu.nextSnapshot.SetTitle(msg)
|
||||
mnu.backupNowSucceeded()
|
||||
}
|
||||
|
||||
func isBackupNeeded(cnf *restic.Config) bool {
|
||||
|
@ -86,6 +86,26 @@ func isBackupNeeded(cnf *restic.Config) bool {
|
|||
return time.Now().After(nextTime)
|
||||
}
|
||||
|
||||
type resticmenu struct {
|
||||
latestSnapshot *systray.MenuItem
|
||||
nextSnapshot *systray.MenuItem
|
||||
backupNow *systray.MenuItem
|
||||
browse *systray.MenuItem
|
||||
logs *systray.MenuItem
|
||||
config *systray.MenuItem
|
||||
quit *systray.MenuItem
|
||||
}
|
||||
|
||||
func (mnu *resticmenu) backupNowFailed(err error) {
|
||||
mnu.backupNow.Enable()
|
||||
mnu.backupNow.SetTitle("‼️ Backup now - latest failed!")
|
||||
}
|
||||
|
||||
func (mnu *resticmenu) backupNowSucceeded() {
|
||||
mnu.backupNow.Enable()
|
||||
mnu.backupNow.SetTitle("Backup now")
|
||||
}
|
||||
|
||||
// See https://github.com/fyne-io/systray/tree/master/example for more examples
|
||||
func onSystrayReady() {
|
||||
systray.SetTemplateIcon(restic.TimeMachineIcon, restic.TimeMachineIcon)
|
||||
|
@ -97,51 +117,80 @@ func onSystrayReady() {
|
|||
return
|
||||
}
|
||||
|
||||
mnuLatestSnapshot := systray.AddMenuItem("Latest: (Fetching...)", "Latest Restic snapshot")
|
||||
mnuNextSnapshot := systray.AddMenuItem("Next @ (Unknown)", "Future Restic snapshot")
|
||||
systray.AddSeparator()
|
||||
mnuBackupNow := systray.AddMenuItem("Backup now", "Backup now")
|
||||
mnuBackupNow.Disable()
|
||||
mnuBrowse := systray.AddMenuItem("Browse backups in Finder...", "Mount and browse backups")
|
||||
mnuLogs := systray.AddMenuItem("Open logfile...", "Open logging file")
|
||||
mnuConfig := systray.AddMenuItem("Open config file...", "Open config file")
|
||||
systray.AddSeparator()
|
||||
addMenuQuit()
|
||||
mnu := buildMenu()
|
||||
|
||||
go updateSnapshots(cnf, mnuLatestSnapshot, mnuNextSnapshot, mnuBackupNow)
|
||||
go updateSnapshots(cnf, mnu)
|
||||
backupCheckTime := make(chan bool, 1)
|
||||
backupCheckFn := func() {
|
||||
hourlyBackupCheckFn := func() {
|
||||
time.Sleep(1 * time.Hour)
|
||||
backupCheckTime <- true
|
||||
}
|
||||
go backupCheckFn()
|
||||
go hourlyBackupCheckFn()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-mnuConfig.ClickedCh:
|
||||
restic.OpenConfigFile()
|
||||
case <-mnuLogs.ClickedCh:
|
||||
restic.OpenLogs()
|
||||
case <-mnu.config.ClickedCh:
|
||||
onOpenConfig()
|
||||
case <-mnu.logs.ClickedCh:
|
||||
onOpenLogs()
|
||||
case <-backupCheckTime:
|
||||
if !mnuBackupNow.Disabled() && isBackupNeeded(cnf) {
|
||||
go backupNow(mnuBackupNow, cnf, func() {
|
||||
updateSnapshots(cnf, mnuLatestSnapshot, mnuNextSnapshot, mnuBackupNow)
|
||||
go backupCheckFn()
|
||||
onBackupCheck(mnu, cnf, hourlyBackupCheckFn)
|
||||
case <-mnu.backupNow.ClickedCh:
|
||||
onBackupNow(mnu, cnf)
|
||||
case <-mnu.browse.ClickedCh:
|
||||
onBroseBackups(cnf)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func onBroseBackups(cnf *restic.Config) {
|
||||
go mountBackups(cnf)
|
||||
}
|
||||
|
||||
func onOpenLogs() error {
|
||||
return restic.OpenLogs()
|
||||
}
|
||||
|
||||
func onOpenConfig() error {
|
||||
return restic.OpenConfigFile()
|
||||
}
|
||||
|
||||
func onBackupNow(mnu *resticmenu, cnf *restic.Config) {
|
||||
go backupNow(mnu, cnf, func() {
|
||||
updateSnapshots(cnf, mnu)
|
||||
}, mnu.backupNowFailed)
|
||||
}
|
||||
|
||||
func onBackupCheck(mnu *resticmenu, cnf *restic.Config, hourlyBackupCheckFn func()) {
|
||||
if !mnu.backupNow.Disabled() && isBackupNeeded(cnf) {
|
||||
go backupNow(mnu, cnf, func() {
|
||||
updateSnapshots(cnf, mnu)
|
||||
go hourlyBackupCheckFn()
|
||||
}, func(err error) {
|
||||
mnu.backupNowFailed(err)
|
||||
go hourlyBackupCheckFn()
|
||||
})
|
||||
} else {
|
||||
log.Info().Msg("Backup not yet needed/in progress/impossible")
|
||||
go backupCheckFn()
|
||||
}
|
||||
case <-mnuBackupNow.ClickedCh:
|
||||
go backupNow(mnuBackupNow, cnf, func() {
|
||||
updateSnapshots(cnf, mnuLatestSnapshot, mnuNextSnapshot, mnuBackupNow)
|
||||
})
|
||||
case <-mnuBrowse.ClickedCh:
|
||||
go mountBackups(cnf)
|
||||
}
|
||||
go hourlyBackupCheckFn()
|
||||
}
|
||||
}
|
||||
|
||||
func buildMenu() *resticmenu {
|
||||
mnu := &resticmenu{}
|
||||
mnu.latestSnapshot = systray.AddMenuItem("Latest: (Fetching...)", "Latest Restic snapshot")
|
||||
mnu.nextSnapshot = systray.AddMenuItem("Next @ (Unknown)", "Future Restic snapshot")
|
||||
systray.AddSeparator()
|
||||
mnu.backupNow = systray.AddMenuItem("Backup now", "Backup now")
|
||||
mnu.backupNow.Disable()
|
||||
mnu.browse = systray.AddMenuItem("Browse backups in Finder...", "Mount and browse backups")
|
||||
mnu.logs = systray.AddMenuItem("Open logfile...", "Open logging file")
|
||||
mnu.config = systray.AddMenuItem("Open config file...", "Open config file")
|
||||
systray.AddSeparator()
|
||||
mnu.quit = addMenuQuit()
|
||||
return mnu
|
||||
}
|
||||
|
||||
// Allows for browsing inside Restic backups by the "mount" command, using MacFuse internally. This is a NON-blocking call.
|
||||
// Restic allows backing up and consulting snapshots while mounted
|
||||
func mountBackups(cnf *restic.Config) {
|
||||
|
@ -152,14 +201,17 @@ func mountBackups(cnf *restic.Config) {
|
|||
}
|
||||
|
||||
// Disables the menu and triggers a restic backup. This is a blocking call.
|
||||
func backupNow(mnu *systray.MenuItem, cnf *restic.Config, onDoneFn func()) {
|
||||
func backupNow(mnu *resticmenu, cnf *restic.Config, onSuccessFn func(), onErrFn func(error)) {
|
||||
log.Debug().Msg("Backup triggered")
|
||||
mnu.SetTitle("🔄 Backup in progress...")
|
||||
mnu.Disable()
|
||||
err := wrapper.Backup(cnf)
|
||||
mnu.backupNow.SetTitle("🔄 Backup in progress...")
|
||||
mnu.backupNow.Disable()
|
||||
|
||||
err := wrapper.Backup(cnf)
|
||||
if err != nil {
|
||||
handleError(err)
|
||||
log.Err(err).Msg("backup error")
|
||||
onErrFn(err)
|
||||
return
|
||||
}
|
||||
onDoneFn()
|
||||
|
||||
onSuccessFn()
|
||||
}
|
||||
|
|
|
@ -144,18 +144,33 @@ func openFolder(folder string) error {
|
|||
// Backup uses "restic backup" to create a new snapshot. This is a blocking call.
|
||||
func (w *Wrapper) Backup(c *Config) error {
|
||||
cmd := resticCmd("--password-file", PasswordFile(), "-r", c.Repository, "--exclude-file", ExcludeFile(), "backup", c.Backup, "--no-scan")
|
||||
|
||||
stdout, _ := cmd.StdoutPipe()
|
||||
|
||||
busy := make(chan bool, 1)
|
||||
cmd.Start()
|
||||
|
||||
scanner := bufio.NewScanner(stdout)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
|
||||
go func() {
|
||||
for scanner.Scan() {
|
||||
m := scanner.Text()
|
||||
log.Info().Str("out", m).Msg("backup")
|
||||
if busy != nil {
|
||||
busy <- true
|
||||
}
|
||||
err := cmd.Wait()
|
||||
}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-busy:
|
||||
busy = nil // It's alive! continue with cmd.Wait()
|
||||
case <-time.After(3 * time.Second):
|
||||
killErr := cmd.Process.Kill()
|
||||
return fmt.Errorf("backup output timeout, repository server down?: %w", killErr)
|
||||
}
|
||||
|
||||
err := cmd.Wait()
|
||||
if err != nil {
|
||||
var exitErr *exec.ExitError
|
||||
if errors.As(err, &exitErr) && exitErr.ExitCode() == 3 {
|
||||
|
|
Loading…
Reference in New Issue