diff --git a/core/events.go b/core/events.go index 8f1fd446..8971dc8a 100644 --- a/core/events.go +++ b/core/events.go @@ -5,12 +5,12 @@ import ( "github.com/safing/portbase/log" "github.com/safing/portbase/modules" + "github.com/safing/portmaster/updates" ) const ( eventShutdown = "shutdown" eventRestart = "restart" - restartCode = 23 ) func registerEvents() { @@ -43,8 +43,6 @@ func shutdown(ctx context.Context, _ interface{}) error { // restart restarts the Portmaster. func restart(ctx context.Context, data interface{}) error { log.Info("core: user requested restart") - modules.SetExitStatusCode(restartCode) - // Do not use a worker, as this would block itself here. - go modules.Shutdown() //nolint:errcheck + updates.RestartNow() return nil } diff --git a/updates/restart.go b/updates/restart.go new file mode 100644 index 00000000..a6d5a34d --- /dev/null +++ b/updates/restart.go @@ -0,0 +1,65 @@ +package updates + +import ( + "context" + "time" + + "github.com/safing/portbase/log" + "github.com/safing/portbase/modules" + "github.com/tevino/abool" +) + +const ( + // RestartExitCode will instruct portmaster-start to restart the process immediately, potentially with a new version. + RestartExitCode = 23 +) + +var ( + restartTask *modules.Task + restartPending *abool.AtomicBool + restartTriggered *abool.AtomicBool +) + +func init() { + restartTask = module.NewTask("automatic restart", automaticRestart).MaxDelay(10 * time.Minute) +} + +// DelayedRestart triggers a restart of the application by shutting down the +// module system gracefully and returning with RestartExitCode. The restart +// may be further delayed by up to 10 minutes by the internal task scheduling +// system. This only works if the process is managed by portmaster-start. +func DelayedRestart(delay time.Duration) { + log.Warningf("updates: restart triggered, will execute in %s", delay) + + // This enables TriggerRestartIfPending. + // Subsequent calls to TriggerRestart should be able to set a new delay. + restartPending.Set() + + // Schedule the restart task. + restartTask.Schedule(time.Now().Add(delay)) +} + +// TriggerRestartIfPending triggers an automatic restart, if one is pending. +// This can be used to prepone a scheduled restart if the conditions are preferable. +func TriggerRestartIfPending() { + if restartPending.IsSet() { + _ = automaticRestart(module.Ctx, nil) + } +} + +// RestartNow immediately executes a restart. +// This only works if the process is managed by portmaster-start. +func RestartNow() { + _ = automaticRestart(module.Ctx, nil) +} + +func automaticRestart(_ context.Context, _ *modules.Task) error { + if restartTriggered.SetToIf(false, true) { + log.Info("updates: initiating automatic restart") + modules.SetExitStatusCode(RestartExitCode) + // Do not use a worker, as this would block itself here. + go modules.Shutdown() //nolint:errcheck + } + + return nil +} diff --git a/updates/upgrader.go b/updates/upgrader.go index e8862528..e03a4155 100644 --- a/updates/upgrader.go +++ b/updates/upgrader.go @@ -11,16 +11,15 @@ import ( "strings" "time" - "github.com/tevino/abool" - "github.com/google/renameio" + processInfo "github.com/shirou/gopsutil/process" + "github.com/tevino/abool" "github.com/safing/portbase/info" "github.com/safing/portbase/log" "github.com/safing/portbase/notifications" + "github.com/safing/portbase/rng" "github.com/safing/portbase/updater" - - processInfo "github.com/shirou/gopsutil/process" ) const ( @@ -29,12 +28,13 @@ const ( ) var ( - // UpgradeCore specifies if portmaster-core should be upgraded. - UpgradeCore = true - upgraderActive = abool.NewBool(false) - pmCtrlUpdate *updater.File - pmCoreUpdate *updater.File + + pmCtrlUpdate *updater.File + pmCoreUpdate *updater.File + + spnHubUpdate *updater.File + hubUpgradeStarted bool rawVersionRegex = regexp.MustCompile(`^[0-9]+\.[0-9]+\.[0-9]+b?\*?$`) ) @@ -61,34 +61,43 @@ func upgrader(_ context.Context, _ interface{}) error { log.Warningf("updates: failed to upgrade portmaster-start: %s", err) } - if UpgradeCore { + binBaseName := strings.Split(filepath.Base(os.Args[0]), "_")[0] + switch binBaseName { + case "portmaster-core": err = upgradeCoreNotify() if err != nil { log.Warningf("updates: failed to notify about core upgrade: %s", err) } + + case "spn-hub": + err = upgradeHub() + if err != nil { + log.Warningf("updates: failed to initiate hub upgrade: %s", err) + } } return nil } func upgradeCoreNotify() error { + if pmCoreUpdate != nil && !pmCoreUpdate.UpgradeAvailable() { + return nil + } + + // make identifier identifier := "core/portmaster-core" // identifier, use forward slash! if onWindows { identifier += exeExt } - // check if we can upgrade - if pmCoreUpdate == nil || pmCoreUpdate.UpgradeAvailable() { - // get newest portmaster-core - new, err := GetPlatformFile(identifier) - if err != nil { - return err - } - pmCoreUpdate = new - } else { - return nil + // get newest portmaster-core + new, err := GetPlatformFile(identifier) + if err != nil { + return err } + pmCoreUpdate = new + // check for new version if info.GetInfo().Version != pmCoreUpdate.Version() { n := notifications.NotifyInfo( "updates:core-update-available", @@ -128,6 +137,42 @@ func upgradeCoreNotifyActionHandler(n *notifications.Notification) { } } +func upgradeHub() error { + if hubUpgradeStarted { + return nil + } + if spnHubUpdate != nil && !spnHubUpdate.UpgradeAvailable() { + return nil + } + + // make identifier + identifier := "hub/spn-hub" // identifier, use forward slash! + if onWindows { + identifier += exeExt + } + + // get newest spn-hub + new, err := GetPlatformFile(identifier) + if err != nil { + return err + } + spnHubUpdate = new + + // check for new version + if info.GetInfo().Version != spnHubUpdate.Version() { + // get random delay with up to three hours + delayMinutes, err := rng.Number(3 * 60) + if err != nil { + return err + } + + DelayedRestart(time.Duration(delayMinutes) * time.Minute) + hubUpgradeStarted = true + } + + return nil +} + func upgradePortmasterStart() error { filename := "portmaster-start" if onWindows {