mirror of
https://github.com/safing/portmaster
synced 2025-09-02 18:49:14 +00:00
287 lines
6.9 KiB
Go
287 lines
6.9 KiB
Go
package updates
|
|
|
|
import (
|
|
"context"
|
|
"flag"
|
|
"fmt"
|
|
"runtime"
|
|
"time"
|
|
|
|
"github.com/safing/portmaster/updates/helper"
|
|
|
|
"github.com/safing/portbase/dataroot"
|
|
"github.com/safing/portbase/log"
|
|
"github.com/safing/portbase/modules"
|
|
"github.com/safing/portbase/notifications"
|
|
"github.com/safing/portbase/updater"
|
|
)
|
|
|
|
const (
|
|
onWindows = runtime.GOOS == "windows"
|
|
|
|
enableUpdatesKey = "core/automaticUpdates"
|
|
|
|
// ModuleName is the name of the update module
|
|
// and can be used when declaring module dependencies.
|
|
ModuleName = "updates"
|
|
|
|
// VersionUpdateEvent is emitted every time a new
|
|
// version of a monitored resource is selected.
|
|
// During module initialization VersionUpdateEvent
|
|
// is also emitted.
|
|
VersionUpdateEvent = "active version update"
|
|
|
|
// ResourceUpdateEvent is emitted every time the
|
|
// updater successfully performed a resource update.
|
|
// ResourceUpdateEvent is emitted even if no new
|
|
// versions are available. Subscribers are expected
|
|
// to check if new versions of their resources are
|
|
// available by checking File.UpgradeAvailable().
|
|
ResourceUpdateEvent = "resource update"
|
|
)
|
|
|
|
var (
|
|
module *modules.Module
|
|
registry *updater.ResourceRegistry
|
|
userAgentFromFlag string
|
|
|
|
updateTask *modules.Task
|
|
updateASAP bool
|
|
disableTaskSchedule bool
|
|
|
|
// UserAgent is an HTTP User-Agent that is used to add
|
|
// more context to requests made by the registry when
|
|
// fetching resources from the update server.
|
|
UserAgent = "Core"
|
|
)
|
|
|
|
const (
|
|
updateFailed = "updates:failed"
|
|
updateSuccess = "updates:success"
|
|
)
|
|
|
|
func init() {
|
|
module = modules.Register(ModuleName, prep, start, stop, "base")
|
|
module.RegisterEvent(VersionUpdateEvent, true)
|
|
module.RegisterEvent(ResourceUpdateEvent, true)
|
|
|
|
flag.StringVar(&userAgentFromFlag, "update-agent", "", "set the user agent for requests to the update server")
|
|
|
|
var dummy bool
|
|
flag.BoolVar(&dummy, "staging", false, "deprecated, configure in settings instead")
|
|
}
|
|
|
|
func prep() error {
|
|
if err := registerConfig(); err != nil {
|
|
return err
|
|
}
|
|
|
|
return registerAPIEndpoints()
|
|
}
|
|
|
|
func start() error {
|
|
initConfig()
|
|
|
|
restartTask = module.NewTask("automatic restart", automaticRestart).MaxDelay(10 * time.Minute)
|
|
|
|
if err := module.RegisterEventHook(
|
|
"config",
|
|
"config change",
|
|
"update registry config",
|
|
updateRegistryConfig); err != nil {
|
|
return err
|
|
}
|
|
|
|
// create registry
|
|
registry = &updater.ResourceRegistry{
|
|
Name: ModuleName,
|
|
UpdateURLs: []string{
|
|
"https://updates.safing.io",
|
|
},
|
|
UserAgent: UserAgent,
|
|
MandatoryUpdates: helper.MandatoryUpdates(),
|
|
AutoUnpack: helper.AutoUnpackUpdates(),
|
|
DevMode: devMode(),
|
|
Online: true,
|
|
}
|
|
if userAgentFromFlag != "" {
|
|
// override with flag value
|
|
registry.UserAgent = userAgentFromFlag
|
|
}
|
|
// initialize
|
|
err := registry.Initialize(dataroot.Root().ChildDir("updates", 0755))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Set indexes based on the release channel.
|
|
helper.SetIndexes(registry, initialReleaseChannel)
|
|
|
|
err = registry.LoadIndexes(module.Ctx)
|
|
if err != nil {
|
|
log.Warningf("updates: failed to load indexes: %s", err)
|
|
}
|
|
|
|
err = registry.ScanStorage("")
|
|
if err != nil {
|
|
log.Warningf("updates: error during storage scan: %s", err)
|
|
}
|
|
|
|
registry.SelectVersions()
|
|
module.TriggerEvent(VersionUpdateEvent, nil)
|
|
|
|
if !updatesCurrentlyEnabled {
|
|
createWarningNotification()
|
|
}
|
|
|
|
// Initialize the version export - this requires the registry to be set up.
|
|
err = initVersionExport()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// start updater task
|
|
updateTask = module.NewTask("updater", func(ctx context.Context, task *modules.Task) error {
|
|
return checkForUpdates(ctx)
|
|
})
|
|
|
|
if !disableTaskSchedule {
|
|
updateTask.
|
|
Repeat(1 * time.Hour).
|
|
MaxDelay(30 * time.Minute).
|
|
Schedule(time.Now().Add(10 * time.Second))
|
|
}
|
|
|
|
if updateASAP {
|
|
updateTask.StartASAP()
|
|
}
|
|
|
|
// react to upgrades
|
|
if err := initUpgrader(); err != nil {
|
|
return err
|
|
}
|
|
|
|
warnOnIncorrectParentPath()
|
|
|
|
return nil
|
|
}
|
|
|
|
// TriggerUpdate queues the update task to execute ASAP.
|
|
func TriggerUpdate() error {
|
|
switch {
|
|
case !module.OnlineSoon():
|
|
return fmt.Errorf("updates module is disabled")
|
|
|
|
case !module.Online():
|
|
updateASAP = true
|
|
|
|
case forceUpdate.IsNotSet() && !enableUpdates():
|
|
return fmt.Errorf("automatic updating is disabled")
|
|
|
|
default:
|
|
updateTask.StartASAP()
|
|
}
|
|
|
|
log.Debugf("updates: triggering update to run as soon as possible")
|
|
return nil
|
|
}
|
|
|
|
// DisableUpdateSchedule disables the update schedule.
|
|
// If called, updates are only checked when TriggerUpdate()
|
|
// is called.
|
|
func DisableUpdateSchedule() error {
|
|
if module.OnlineSoon() {
|
|
return fmt.Errorf("module already online")
|
|
}
|
|
|
|
disableTaskSchedule = true
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkForUpdates(ctx context.Context) (err error) {
|
|
if !updatesCurrentlyEnabled && !forceUpdate.IsSet() {
|
|
log.Debugf("updates: automatic updates are disabled")
|
|
return nil
|
|
}
|
|
forceUpdate.UnSet()
|
|
|
|
defer log.Debugf("updates: finished checking for updates")
|
|
|
|
defer func() {
|
|
if err == nil {
|
|
module.Resolve(updateFailed)
|
|
notifications.Notify(¬ifications.Notification{
|
|
EventID: updateSuccess,
|
|
Type: notifications.Info,
|
|
Title: "Update Check Successful",
|
|
Message: "The Portmaster successfully checked for updates and downloaded any available updates. Most updates are applied automatically. You will be notified of important updates that need restarting.",
|
|
Expires: time.Now().Add(1 * time.Minute).Unix(),
|
|
AvailableActions: []*notifications.Action{
|
|
{
|
|
ID: "ack",
|
|
Text: "OK",
|
|
},
|
|
},
|
|
})
|
|
} else {
|
|
notifications.NotifyWarn(
|
|
updateFailed,
|
|
"Update Check Failed",
|
|
"The Portmaster failed to check for updates. This might be a temporary issue of your device, your network or the update servers. The Portmaster will automatically try again later. If you just installed the Portmaster, please try disabling potentially conflicting software, such as other firewalls or VPNs.",
|
|
notifications.Action{
|
|
ID: "retry",
|
|
Text: "Try Again Now",
|
|
Type: notifications.ActionTypeWebhook,
|
|
Payload: ¬ifications.ActionTypeWebhookPayload{
|
|
URL: apiPathCheckForUpdates,
|
|
ResultAction: "display",
|
|
},
|
|
},
|
|
).AttachToModule(module)
|
|
}
|
|
}()
|
|
|
|
if err = registry.UpdateIndexes(ctx); err != nil {
|
|
err = fmt.Errorf("failed to update indexes: %s", err)
|
|
return
|
|
}
|
|
|
|
err = registry.DownloadUpdates(ctx)
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to download updates: %w", err)
|
|
return
|
|
}
|
|
|
|
registry.SelectVersions()
|
|
|
|
// Unpack selected resources.
|
|
err = registry.UnpackResources()
|
|
if err != nil {
|
|
err = fmt.Errorf("failed to unpack updates: %w", err)
|
|
return
|
|
}
|
|
|
|
// Purge old resources
|
|
registry.Purge(3)
|
|
|
|
module.TriggerEvent(ResourceUpdateEvent, nil)
|
|
return nil
|
|
}
|
|
|
|
func stop() error {
|
|
if registry != nil {
|
|
return registry.Cleanup()
|
|
}
|
|
|
|
return stopVersionExport()
|
|
}
|
|
|
|
// RootPath returns the root path used for storing updates.
|
|
func RootPath() string {
|
|
if !module.Online() {
|
|
return ""
|
|
}
|
|
|
|
return registry.StorageDir().Path
|
|
}
|