-
-
Notifications
You must be signed in to change notification settings - Fork 274
/
main.go
287 lines (233 loc) Β· 6.92 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
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
}