-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmain.go
143 lines (115 loc) · 4.09 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
package main
import (
"context"
"flag"
"fmt"
"log"
"math/rand"
"net/url"
"os"
"os/signal"
"syscall"
"time"
"github.com/gokrazy/gokapi/gusapi"
"github.com/gokrazy/gokrazy"
)
type opts struct {
gusServer string
checkFrequency string
destinationDir string
skipWaiting bool
}
const (
serverAPIPath = "/api/v1"
)
func main() {
ctx, cancel := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer cancel()
log.Print("gokrazy's selfupdate service starting up..")
var o opts
flag.StringVar(&o.gusServer, "gus_server", "", "the HTTP/S endpoint of the GUS (gokrazy Update System) server (required)")
flag.StringVar(&o.checkFrequency, "check_frequency", "1h", "the time frequency for checks to the update service. default: 1h")
flag.StringVar(&o.destinationDir, "destination_dir", "/tmp/selfupdate", "the destination directory for the fetched update file. default: /tmp/selfupdate")
flag.BoolVar(&o.skipWaiting, "skip_waiting", false, "skips the time frequency check and jitter waits, and immediately performs an update check. default: false")
flag.Parse()
if err := logic(ctx, o); err != nil {
log.Fatal(err)
}
}
func logic(ctx context.Context, o opts) error {
if o.gusServer == "" {
return fmt.Errorf("flag --gus_server must be provided")
}
frequency, err := time.ParseDuration(o.checkFrequency)
if err != nil {
return fmt.Errorf("failed to parse check_frequency duration: %w", err)
}
machineID := gokrazy.MachineID()
_, sbomHash, err := gokrazy.ReadSBOM()
if err != nil {
return fmt.Errorf("could not read SBOM from disk: %s", err.Error())
}
httpPassword, err := readConfigFile("gokr-pw.txt")
if err != nil {
return fmt.Errorf("could read neither /perm/gokr-pw.txt, nor /etc/gokr-pw.txt, nor /gokr-pw.txt: %s", err.Error())
}
httpPort, err := readConfigFile("http-port.txt")
if err != nil {
return fmt.Errorf("could read neither /perm/http-port.txt, nor /etc/http-port.txt, nor /http-port.txt: %s", err.Error())
}
gusBasePath, err := url.JoinPath(o.gusServer, serverAPIPath)
if err != nil {
return fmt.Errorf("error joining gus server url: %w", err)
}
gusCfg := gusapi.NewConfiguration()
gusCfg.BasePath = gusBasePath
gusCli := gusapi.NewAPIClient(gusCfg)
if o.skipWaiting {
log.Print("skipping waiting, performing an immediate updateProcess")
if err := updateProcess(ctx, gusCli, machineID, o.gusServer, sbomHash, o.destinationDir, httpPassword, httpPort); err != nil {
// If the updateProcess fails we exit with an error
// so that gokrazy supervisor will restart the process.
return fmt.Errorf("error performing updateProcess: %v", err)
}
// If the updateProcess doesn't error
// we happily return to terminate the process.
return nil
}
log.Print("entering update checking loop")
ticker := time.NewTicker(frequency)
for {
select {
case <-ctx.Done():
log.Print("stopping update checking")
return nil
case <-ticker.C:
jitter := time.Duration(rand.Int63n(250)) * time.Second
time.Sleep(jitter)
if err := updateProcess(ctx, gusCli, machineID, o.gusServer, sbomHash, o.destinationDir, httpPassword, httpPort); err != nil {
log.Printf("error performing updateProcess: %v", err)
continue
}
}
}
}
func updateProcess(ctx context.Context, gusCli *gusapi.APIClient, machineID, gusServer, sbomHash, destinationDir, httpPassword, httpPort string) error {
response, err := checkForUpdates(ctx, gusCli, machineID)
if err != nil {
return fmt.Errorf("unable to check for updates: %w", err)
}
// Check if we should update by comparing the update response SBOMHash with
// the current installation SBOMHash.
if !shouldUpdate(response, sbomHash) {
return nil
}
// The SBOMHash differs, start the selfupdate procedure.
if err := selfupdate(ctx, gusCli, gusServer, machineID, destinationDir, response, httpPassword, httpPort); err != nil {
return fmt.Errorf("unable to perform the selfupdate procedure: %w", err)
}
// The update is now correctly written to the disk partitions
// and the reboot is in progress
// sleep until the context chan is closed, then exit cleanly.
<-ctx.Done()
os.Exit(0)
return nil
}