-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathconfig.go
324 lines (279 loc) · 10.4 KB
/
config.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
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
// Package config allows tools such as the gokr-packer or breakglass reading
// gokrazy instance configuration (with fallback to the older host-specific
// configuration) for data such as the HTTP password, or package-specific
// command line flags.
package config
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"time"
"github.com/gokrazy/internal/instanceflag"
)
// InternalCompatibilityFlags keep older gokr-packer behavior or user interface
// working, but should never be set by users in config.json manually.
type InternalCompatibilityFlags struct {
// These have become 'gok overwrite' flags:
Overwrite string `json:",omitempty"` // -overwrite
OverwriteBoot string `json:",omitempty"` // -overwrite_boot
OverwriteMBR string `json:",omitempty"` // -overwrite_mbr
OverwriteRoot string `json:",omitempty"` // -overwrite_root
Sudo string `json:",omitempty"` // -sudo
TargetStorageBytes int `json:",omitempty"` // -target_storage_bytes
// These have become 'gok update' flags:
Update string `json:",omitempty"` // -update
Insecure bool `json:",omitempty"` // -insecure
Testboot bool `json:",omitempty"` // -testboot
// These will likely not be carried over from gokr-packer to gok because of
// a lack of usage.
InitPkg string `json:",omitempty"` // -init_pkg
OverwriteInit string `json:",omitempty"` // -overwrite_init
}
type UpdateStruct struct {
// Hostname (in UpdateStruct) overrides Struct.Hostname, but only for
// deploying the update via HTTP, not in the generated image.
Hostname string `json:",omitempty"`
// UseTLS can be one of:
//
// - empty (""), meaning use TLS if certificates exist
// - "off", disabling TLS even if certificates exist
// - "self-signed", creating TLS certificates if needed
UseTLS string `json:",omitempty"` // -tls
// NoPassword, if true, prevents any password from being
// included in the image. Without a password, the only access
// will be over the serial console and the web interface will
// only accept connections from localhost.
//
// If the appliance image wishes for the gokrazy web interface
// and API to be accessible, it is then the responsible of a
// package in the image to make it available, supplying
// authentication, authorization, and proxying to localhost as
// desired. For example, Tailscale might be used.
NoPassword bool `json:",omitempty"`
HTTPPort string `json:",omitempty"` // -http_port
HTTPSPort string `json:",omitempty"` // -https_port
HTTPPassword string `json:",omitempty"` // http-password.txt
CertPEM string `json:",omitempty"` // cert.pem
KeyPEM string `json:",omitempty"` // key.pem
}
func (u *UpdateStruct) WithFallbackToHostSpecific(host string) (*UpdateStruct, error) {
if u == nil {
u = &UpdateStruct{}
}
result := UpdateStruct{
Hostname: u.Hostname,
NoPassword: u.NoPassword,
}
if u.HTTPPort != "" {
result.HTTPPort = u.HTTPPort
} else {
port, err := HostnameSpecific(host).ReadFile("http-port.txt")
if err != nil && !os.IsNotExist(err) {
return nil, err
}
result.HTTPPort = port
}
if u.HTTPSPort != "" {
result.HTTPSPort = u.HTTPSPort
} else {
// There was no extra file for the HTTPS port (http-port.txt was used).
result.HTTPSPort = result.HTTPPort
}
if !u.NoPassword {
if u.HTTPPassword != "" {
result.HTTPPassword = u.HTTPPassword
} else {
pw, err := HostnameSpecific(host).ReadFile("http-password.txt")
if err != nil && !os.IsNotExist(err) {
return nil, err
}
result.HTTPPassword = pw
}
}
// Intentionally no fallback for CertPEM and KeyPEM at this stage: their
// fallback happens conditionally if UseTLS != off, in
// tlsflag.CertificatePathsFor().
return &result, nil
}
type PackageConfig struct {
// GoBuildFlags will be passed to “go build” as extra arguments.
//
// To pass build tags, do not use -tags=mycustomtag; instead set the
// GoBuildTags field to not overwrite the gokrazy default build tags.
GoBuildFlags []string `json:",omitempty"`
// GoBuildTags will be added to the list of gokrazy default build tags.
GoBuildTags []string `json:",omitempty"`
// Environment contains key=value pairs, like in Go’s os.Environ().
Environment []string `json:",omitempty"`
// CommandLineFlags will be set when starting the program.
CommandLineFlags []string `json:",omitempty"`
// DontStart makes the gokrazy init not start this program
// automatically. Users can still start it manually via the web interface,
// or interactively via breakglass.
DontStart bool `json:",omitempty"`
// WaitForClock makes the gokrazy init wait for clock synchronization before
// starting the program. This is useful when modifying the program source to
// call gokrazy.WaitForClock() is inconvenient.
WaitForClock bool `json:",omitempty"`
// ExtraFilePaths maps from root file system destination path to a relative
// or absolute path on the host on which the packer is running.
//
// Lookup order:
// 1. <path>_<target_goarch>.tar
// 2. <path>.tar
// 3. <path> (directory)
ExtraFilePaths map[string]string `json:",omitempty"`
// ExtraFileContents maps from root file system destination path to the
// plain text contents of the file.
ExtraFileContents map[string]string `json:",omitempty"`
}
// MountDevice instructs gokrazy to mount the specified source on the specified
// target using the specified file system type and the specified options.
//
// If Target contains a directory in /mnt (not a subdirectory), the packer will
// create it on the (read-only) root file system.
type MountDevice struct {
Source string // e.g. /dev/sdx or PARTUUID=abcdef
Type string // e.g. ext4
Target string // e.g. /mnt/usb, will be created by the packer
Options string // a subset of mount(8)-like options
}
// Fields where we need to distinguish between not being set (= use the default)
// and being set to an empty value (= disable), such as FirmwarePackage, are
// pointers. Fields that are required (Hostname) or where the empty value is not
// valid (InternalCompatibilityFlags.Sudo) don’t need to be pointers. If needed,
// we can switch fields from non-pointer to pointer, as the JSON on-disk
// representation does not change, and the config package is in
// github.com/gokrazy/internal.
type Struct struct {
Hostname string // -hostname
DeviceType string `json:",omitempty"` // -device_type
Update *UpdateStruct `json:",omitempty"`
Packages []string // flag.Args()
// If PackageConfig is specified, all package config is taken from the
// config struct, no longer from the file system, except for extrafiles/.
PackageConfig map[string]PackageConfig `json:",omitempty"`
SerialConsole string `json:",omitempty"`
GokrazyPackages *[]string `json:",omitempty"` // -gokrazy_pkgs
KernelPackage *string `json:",omitempty"` // -kernel_package
FirmwarePackage *string `json:",omitempty"` // -firmware_package
EEPROMPackage *string `json:",omitempty"` // -eeprom_package
// extra lines to append to the config.txt file on the boot partition which
// is read by the Raspberry Pi bootloader:
// https://www.raspberrypi.com/documentation/computers/config_txt.html
BootloaderExtraLines []string `json:",omitempty"`
MountDevices []MountDevice `json:",omitempty"`
// Do not set these manually in config.json, these fields only exist so that
// the entire old gokr-packer flag surface keeps working.
InternalCompatibilityFlags *InternalCompatibilityFlags `json:",omitempty"`
Meta struct {
Instance string
Path string
LastModified time.Time
} `json:"-"` // omit from JSON
}
// NewStruct returns a config.Struct that was not loaded from a file, but
// instead created empty, with only the hostname field set.
//
// This is handy for best-effort compatibility for older setups (before instance
// config was introduced). Aside from compatibility, ReadFromFile() should be
// used instead of NewStruct().
func NewStruct(hostname string) *Struct {
return &Struct{
Hostname: hostname,
Update: &UpdateStruct{},
InternalCompatibilityFlags: &InternalCompatibilityFlags{},
}
}
func (s *Struct) SerialConsoleOrDefault() string {
if s.SerialConsole == "" {
return "serial0,115200"
}
return s.SerialConsole
}
func (s *Struct) GokrazyPackagesOrDefault() []string {
if s.GokrazyPackages == nil {
return []string{
"github.com/gokrazy/gokrazy/cmd/dhcp",
"github.com/gokrazy/gokrazy/cmd/ntp",
"github.com/gokrazy/gokrazy/cmd/randomd",
"github.com/gokrazy/gokrazy/cmd/heartbeat",
}
}
return *s.GokrazyPackages
}
func (s *Struct) KernelPackageOrDefault() string {
if s.KernelPackage == nil {
// KernelPackage unspecified, fall back to the default.
return "github.com/gokrazy/kernel.rpi"
}
return *s.KernelPackage
}
func (s *Struct) FirmwarePackageOrDefault() string {
if s.FirmwarePackage == nil {
// FirmwarePackage unspecified, fall back to the default.
return "github.com/gokrazy/firmware"
}
return *s.FirmwarePackage
}
func (s *Struct) EEPROMPackageOrDefault() string {
if s.EEPROMPackage == nil {
// EEPROMPackage unspecified, fall back to the default.
return "github.com/gokrazy/rpi-eeprom"
}
return *s.EEPROMPackage
}
// FormatForFile pretty-prints the config struct as JSON, ready for storing it
// in the config.json file.
func (s *Struct) FormatForFile() ([]byte, error) {
b, err := json.MarshalIndent(s, "", " ")
if err != nil {
return nil, err
}
b = append(b, '\n')
return b, nil
}
func (i *InternalCompatibilityFlags) SudoOrDefault() string {
if i.Sudo == "" {
return "auto"
}
return i.Sudo
}
func InstancePath() string {
return filepath.Join(instanceflag.ParentDir(), instanceflag.Instance())
}
func InstanceConfigPath() string {
return filepath.Join(InstancePath(), "config.json")
}
func ReadFromFile() (*Struct, error) {
configJSON := InstanceConfigPath()
f, err := os.Open(configJSON)
if err != nil {
return nil, err
}
defer f.Close()
st, err := f.Stat()
if err != nil {
return nil, err
}
b, err := io.ReadAll(f)
if err != nil {
return nil, err
}
var cfg Struct
if err := json.Unmarshal(b, &cfg); err != nil {
return nil, fmt.Errorf("decoding %s: %v", configJSON, err)
}
if cfg.Update == nil {
cfg.Update = &UpdateStruct{}
}
if cfg.InternalCompatibilityFlags == nil {
cfg.InternalCompatibilityFlags = &InternalCompatibilityFlags{}
}
cfg.Meta.Instance = instanceflag.Instance()
cfg.Meta.Path = configJSON
cfg.Meta.LastModified = st.ModTime()
return &cfg, nil
}