-
-
Notifications
You must be signed in to change notification settings - Fork 274
/
restart.go
143 lines (117 loc) Β· 3.49 KB
/
restart.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
package updates
import (
"context"
"os/exec"
"runtime"
"sync"
"time"
"github.com/tevino/abool"
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
)
const (
// RestartExitCode will instruct portmaster-start to restart the process immediately, potentially with a new version.
RestartExitCode = 23
)
var (
// RebootOnRestart defines whether the whole system, not just the service,
// should be restarted automatically when triggering a restart internally.
RebootOnRestart bool
restartTask *modules.Task
restartPending = abool.New()
restartTriggered = abool.New()
restartTime time.Time
restartTimeLock sync.Mutex
)
// IsRestarting returns whether a restart has been triggered.
func IsRestarting() bool {
return restartTriggered.IsSet()
}
// RestartIsPending returns whether a restart is pending.
func RestartIsPending() (pending bool, restartAt time.Time) {
if restartPending.IsNotSet() {
return false, time.Time{}
}
restartTimeLock.Lock()
defer restartTimeLock.Unlock()
return true, restartTime
}
// 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) {
// Check if restart is already pending.
if !restartPending.SetToIf(false, true) {
return
}
// Schedule the restart task.
log.Warningf("updates: restart triggered, will execute in %s", delay)
restartAt := time.Now().Add(delay)
restartTask.Schedule(restartAt)
// Set restartTime.
restartTimeLock.Lock()
defer restartTimeLock.Unlock()
restartTime = restartAt
}
// AbortRestart aborts a (delayed) restart.
func AbortRestart() {
if restartPending.SetToIf(true, false) {
log.Warningf("updates: restart aborted")
// Cancel schedule.
restartTask.Schedule(time.Time{})
}
}
// 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() {
restartTask.StartASAP()
}
}
// RestartNow immediately executes a restart.
// This only works if the process is managed by portmaster-start.
func RestartNow() {
restartPending.Set()
restartTask.StartASAP()
}
func automaticRestart(_ context.Context, _ *modules.Task) error {
// Check if the restart is still scheduled.
if restartPending.IsNotSet() {
return nil
}
// Trigger restart.
if restartTriggered.SetToIf(false, true) {
log.Warning("updates: initiating (automatic) restart")
// Check if we should reboot instead.
var rebooting bool
if RebootOnRestart {
// Trigger system reboot and record success.
rebooting = triggerSystemReboot()
if !rebooting {
log.Warningf("updates: rebooting failed, only restarting service instead")
}
}
// Set restart exit code.
if !rebooting {
modules.SetExitStatusCode(RestartExitCode)
}
// Do not use a worker, as this would block itself here.
go modules.Shutdown() //nolint:errcheck
}
return nil
}
func triggerSystemReboot() (success bool) {
switch runtime.GOOS {
case "linux":
err := exec.Command("systemctl", "reboot").Run()
if err != nil {
log.Errorf("updates: triggering reboot with systemctl failed: %s", err)
return false
}
default:
log.Warningf("updates: rebooting is not support on %s", runtime.GOOS)
return false
}
return true
}