Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/cidata/cidata.go
2614 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package cidata
5
6
import (
7
"compress/gzip"
8
"context"
9
"errors"
10
"fmt"
11
"io"
12
"maps"
13
"net"
14
"net/url"
15
"os"
16
"path"
17
"path/filepath"
18
"slices"
19
"strconv"
20
"strings"
21
"time"
22
"unicode"
23
24
"github.com/docker/go-units"
25
"github.com/sirupsen/logrus"
26
27
"github.com/lima-vm/lima/v2/pkg/debugutil"
28
"github.com/lima-vm/lima/v2/pkg/driver"
29
"github.com/lima-vm/lima/v2/pkg/instance/hostname"
30
"github.com/lima-vm/lima/v2/pkg/iso9660util"
31
"github.com/lima-vm/lima/v2/pkg/limatype"
32
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
33
"github.com/lima-vm/lima/v2/pkg/limayaml"
34
"github.com/lima-vm/lima/v2/pkg/localpathutil"
35
"github.com/lima-vm/lima/v2/pkg/networks"
36
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
37
"github.com/lima-vm/lima/v2/pkg/osutil"
38
"github.com/lima-vm/lima/v2/pkg/sshutil"
39
)
40
41
var netLookupIP = func(host string) []net.IP {
42
ips, err := net.LookupIP(host)
43
if err != nil {
44
logrus.Debugf("net.LookupIP %s: %s", host, err)
45
return nil
46
}
47
48
return ips
49
}
50
51
func setupEnv(instConfigEnv map[string]string, propagateProxyEnv bool, slirpGateway string) (map[string]string, error) {
52
// Start with the proxy variables from the system settings.
53
env, err := osutil.ProxySettings()
54
if err != nil {
55
return env, err
56
}
57
// env.* settings from lima.yaml override system settings without giving a warning
58
maps.Copy(env, instConfigEnv)
59
// Current process environment setting override both system settings and env.*
60
lowerVars := []string{"ftp_proxy", "http_proxy", "https_proxy", "no_proxy"}
61
upperVars := make([]string, len(lowerVars))
62
for i, name := range lowerVars {
63
upperVars[i] = strings.ToUpper(name)
64
}
65
if propagateProxyEnv {
66
for _, name := range append(lowerVars, upperVars...) {
67
if value, ok := os.LookupEnv(name); ok {
68
if _, ok := env[name]; ok && value != env[name] {
69
logrus.Infof("Overriding %q value %q with %q from limactl process environment",
70
name, env[name], value)
71
}
72
env[name] = value
73
}
74
}
75
}
76
// Replace IP that IsLoopback in proxy settings with the gateway address
77
// Delete settings with empty values, so the user can choose to ignore system settings.
78
for _, name := range append(lowerVars, upperVars...) {
79
value, ok := env[name]
80
if ok && value == "" {
81
delete(env, name)
82
} else if ok && !strings.EqualFold(name, "no_proxy") {
83
u, err := url.Parse(value)
84
if err != nil {
85
logrus.Warnf("Ignoring invalid proxy %q=%v: %s", name, value, err)
86
continue
87
}
88
89
for _, ip := range netLookupIP(u.Hostname()) {
90
if ip.IsLoopback() {
91
newHost := slirpGateway
92
if u.Port() != "" {
93
newHost = net.JoinHostPort(newHost, u.Port())
94
}
95
u.Host = newHost
96
value = u.String()
97
}
98
}
99
if value != env[name] {
100
logrus.Infof("Replacing %q value %q with %q", name, env[name], value)
101
env[name] = value
102
}
103
}
104
}
105
// Make sure uppercase variants have the same value as lowercase ones.
106
// If both are set, the lowercase variant value takes precedence.
107
for _, lowerName := range lowerVars {
108
upperName := strings.ToUpper(lowerName)
109
if _, ok := env[lowerName]; ok {
110
if _, ok := env[upperName]; ok && env[lowerName] != env[upperName] {
111
logrus.Warnf("Changing %q value from %q to %q to match %q",
112
upperName, env[upperName], env[lowerName], lowerName)
113
}
114
env[upperName] = env[lowerName]
115
} else if _, ok := env[upperName]; ok {
116
env[lowerName] = env[upperName]
117
}
118
}
119
return env, nil
120
}
121
122
func templateArgs(ctx context.Context, bootScripts bool, instDir, name string, instConfig *limatype.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort, vsockPort int, virtioPort string, noCloudInit, rosettaEnabled, rosettaBinFmt bool) (*TemplateArgs, error) {
123
if err := limayaml.Validate(instConfig, false); err != nil {
124
return nil, err
125
}
126
archive := "nerdctl-full.tgz"
127
args := TemplateArgs{
128
Debug: debugutil.Debug,
129
BootScripts: bootScripts,
130
Name: name,
131
Hostname: hostname.FromInstName(name), // TODO: support customization
132
User: *instConfig.User.Name,
133
Comment: removeControlChars(*instConfig.User.Comment),
134
Home: *instConfig.User.Home,
135
Shell: *instConfig.User.Shell,
136
UID: *instConfig.User.UID,
137
GuestInstallPrefix: *instConfig.GuestInstallPrefix,
138
UpgradePackages: *instConfig.UpgradePackages,
139
Containerd: Containerd{System: *instConfig.Containerd.System, User: *instConfig.Containerd.User, Archive: archive},
140
SlirpNICName: networks.SlirpNICName,
141
142
VMType: *instConfig.VMType,
143
VSockPort: vsockPort,
144
VirtioPort: virtioPort,
145
RosettaEnabled: rosettaEnabled,
146
RosettaBinFmt: rosettaBinFmt,
147
Plain: *instConfig.Plain,
148
TimeZone: *instConfig.TimeZone,
149
NoCloudInit: noCloudInit,
150
Param: instConfig.Param,
151
}
152
153
firstUsernetIndex := limayaml.FirstUsernetIndex(instConfig)
154
var subnet net.IP
155
var err error
156
157
if firstUsernetIndex != -1 {
158
usernetName := instConfig.Networks[firstUsernetIndex].Lima
159
subnet, err = usernet.Subnet(usernetName)
160
if err != nil {
161
return nil, err
162
}
163
args.SlirpGateway = usernet.GatewayIP(subnet)
164
args.SlirpDNS = usernet.GatewayIP(subnet)
165
} else {
166
subnet, _, err = net.ParseCIDR(networks.SlirpNetwork)
167
if err != nil {
168
return nil, err
169
}
170
args.SlirpGateway = usernet.GatewayIP(subnet)
171
if *instConfig.VMType == limatype.VZ {
172
args.SlirpDNS = usernet.GatewayIP(subnet)
173
} else {
174
args.SlirpDNS = usernet.DNSIP(subnet)
175
}
176
args.SlirpIPAddress = networks.SlirpIPAddress
177
}
178
179
// change instance id on every boot so network config will be processed again
180
args.IID = fmt.Sprintf("iid-%d", time.Now().Unix())
181
182
pubKeys, err := sshutil.DefaultPubKeys(ctx, *instConfig.SSH.LoadDotSSHPubKeys)
183
if err != nil {
184
return nil, err
185
}
186
if len(pubKeys) == 0 {
187
return nil, errors.New("no SSH key was found, run `ssh-keygen`")
188
}
189
for _, f := range pubKeys {
190
args.SSHPubKeys = append(args.SSHPubKeys, f.Content)
191
}
192
193
var fstype string
194
switch *instConfig.MountType {
195
case limatype.REVSSHFS:
196
fstype = "sshfs"
197
case limatype.NINEP:
198
fstype = "9p"
199
case limatype.VIRTIOFS:
200
fstype = "virtiofs"
201
}
202
hostHome, err := localpathutil.Expand("~")
203
if err != nil {
204
return nil, err
205
}
206
for _, f := range instConfig.Mounts {
207
tag := limayaml.MountTag(f.Location, *f.MountPoint)
208
options := "defaults"
209
switch fstype {
210
case "9p", "virtiofs":
211
options = "ro"
212
if *f.Writable {
213
options = "rw"
214
}
215
if fstype == "9p" {
216
options += ",trans=virtio"
217
options += fmt.Sprintf(",version=%s", *f.NineP.ProtocolVersion)
218
msize, err := units.RAMInBytes(*f.NineP.Msize)
219
if err != nil {
220
return nil, fmt.Errorf("failed to parse msize for %q: %w", f.Location, err)
221
}
222
options += fmt.Sprintf(",msize=%d", msize)
223
options += fmt.Sprintf(",cache=%s", *f.NineP.Cache)
224
}
225
// don't fail the boot, if virtfs is not available
226
options += ",nofail"
227
}
228
args.Mounts = append(args.Mounts, Mount{Tag: tag, MountPoint: *f.MountPoint, Type: fstype, Options: options})
229
if f.Location == hostHome {
230
args.HostHomeMountPoint = *f.MountPoint
231
}
232
}
233
234
switch *instConfig.MountType {
235
case limatype.REVSSHFS:
236
args.MountType = "reverse-sshfs"
237
case limatype.NINEP:
238
args.MountType = "9p"
239
case limatype.VIRTIOFS:
240
args.MountType = "virtiofs"
241
}
242
243
for i, d := range instConfig.AdditionalDisks {
244
format := true
245
if d.Format != nil {
246
format = *d.Format
247
}
248
fstype := ""
249
if d.FSType != nil {
250
fstype = *d.FSType
251
}
252
args.Disks = append(args.Disks, Disk{
253
Name: d.Name,
254
Device: diskDeviceNameFromOrder(i),
255
Format: format,
256
FSType: fstype,
257
FSArgs: d.FSArgs,
258
})
259
}
260
261
args.Networks = append(args.Networks, Network{MACAddress: limayaml.MACAddress(instDir), Interface: networks.SlirpNICName, Metric: 200})
262
for i, nw := range instConfig.Networks {
263
if i == firstUsernetIndex {
264
continue
265
}
266
args.Networks = append(args.Networks, Network{MACAddress: nw.MACAddress, Interface: nw.Interface, Metric: *nw.Metric})
267
}
268
269
args.Env, err = setupEnv(instConfig.Env, *instConfig.PropagateProxyEnv, args.SlirpGateway)
270
if err != nil {
271
return nil, err
272
}
273
274
switch {
275
case len(instConfig.DNS) > 0:
276
for _, addr := range instConfig.DNS {
277
args.DNSAddresses = append(args.DNSAddresses, addr.String())
278
}
279
case firstUsernetIndex != -1 || *instConfig.VMType == limatype.VZ:
280
args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS)
281
case *instConfig.HostResolver.Enabled:
282
args.UDPDNSLocalPort = udpDNSLocalPort
283
args.TCPDNSLocalPort = tcpDNSLocalPort
284
args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS)
285
default:
286
args.DNSAddresses, err = osutil.DNSAddresses()
287
if err != nil {
288
return nil, err
289
}
290
}
291
292
args.CACerts.RemoveDefaults = instConfig.CACertificates.RemoveDefaults
293
294
for _, path := range instConfig.CACertificates.Files {
295
expanded, err := localpathutil.Expand(path)
296
if err != nil {
297
return nil, err
298
}
299
300
content, err := os.ReadFile(expanded)
301
if err != nil {
302
return nil, err
303
}
304
305
cert := getCert(string(content))
306
args.CACerts.Trusted = append(args.CACerts.Trusted, cert)
307
}
308
309
for _, content := range instConfig.CACertificates.Certs {
310
cert := getCert(content)
311
args.CACerts.Trusted = append(args.CACerts.Trusted, cert)
312
}
313
314
// Remove empty caCerts (default values) from configuration yaml
315
if !*args.CACerts.RemoveDefaults && len(args.CACerts.Trusted) == 0 {
316
args.CACerts.RemoveDefaults = nil
317
args.CACerts.Trusted = nil
318
}
319
320
args.BootCmds = getBootCmds(instConfig.Provision)
321
322
for i, f := range instConfig.Provision {
323
if f.Mode == limatype.ProvisionModeDependency && *f.SkipDefaultDependencyResolution {
324
args.SkipDefaultDependencyResolution = true
325
}
326
if f.Mode == limatype.ProvisionModeData {
327
args.DataFiles = append(args.DataFiles, DataFile{
328
FileName: fmt.Sprintf("%08d", i),
329
Overwrite: strconv.FormatBool(*f.Overwrite),
330
Owner: *f.Owner,
331
Path: *f.Path,
332
Permissions: *f.Permissions,
333
})
334
}
335
if f.Mode == limatype.ProvisionModeYQ {
336
args.YQProvisions = append(args.YQProvisions, YQProvision{
337
FileName: fmt.Sprintf("%08d", i),
338
Format: *f.Format,
339
Owner: *f.Owner,
340
Path: *f.Path,
341
Permissions: *f.Permissions,
342
})
343
}
344
}
345
346
return &args, nil
347
}
348
349
func GenerateCloudConfig(ctx context.Context, instDir, name string, instConfig *limatype.LimaYAML) error {
350
args, err := templateArgs(ctx, false, instDir, name, instConfig, 0, 0, 0, "", false, false, false)
351
if err != nil {
352
return err
353
}
354
// mounts are not included here
355
args.Mounts = nil
356
// resolv_conf is not included here
357
args.DNSAddresses = nil
358
359
if err := ValidateTemplateArgs(args); err != nil {
360
return err
361
}
362
363
config, err := ExecuteTemplateCloudConfig(args)
364
if err != nil {
365
return err
366
}
367
368
os.RemoveAll(filepath.Join(instDir, filenames.CloudConfig)) // delete existing
369
return os.WriteFile(filepath.Join(instDir, filenames.CloudConfig), config, 0o444)
370
}
371
372
func GenerateISO9660(ctx context.Context, drv driver.Driver, instDir, name string, instConfig *limatype.LimaYAML, udpDNSLocalPort, tcpDNSLocalPort int, guestAgentBinary, nerdctlArchive string, vsockPort int, virtioPort string, noCloudInit, rosettaEnabled, rosettaBinFmt bool) error {
373
args, err := templateArgs(ctx, true, instDir, name, instConfig, udpDNSLocalPort, tcpDNSLocalPort, vsockPort, virtioPort, noCloudInit, rosettaEnabled, rosettaBinFmt)
374
if err != nil {
375
return err
376
}
377
378
if err := ValidateTemplateArgs(args); err != nil {
379
return err
380
}
381
382
layout, err := ExecuteTemplateCIDataISO(args)
383
if err != nil {
384
return err
385
}
386
387
driverScripts, err := drv.BootScripts()
388
if err != nil {
389
return fmt.Errorf("failed to get boot scripts: %w", err)
390
}
391
392
for filename, content := range driverScripts {
393
layout = append(layout, iso9660util.Entry{
394
Path: fmt.Sprintf("boot/%s", filename),
395
Reader: strings.NewReader(string(content)),
396
})
397
}
398
399
for i, f := range instConfig.Provision {
400
switch f.Mode {
401
case limatype.ProvisionModeSystem, limatype.ProvisionModeUser, limatype.ProvisionModeDependency:
402
layout = append(layout, iso9660util.Entry{
403
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
404
Reader: strings.NewReader(*f.Script),
405
})
406
case limatype.ProvisionModeData:
407
layout = append(layout, iso9660util.Entry{
408
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
409
Reader: strings.NewReader(*f.Content),
410
})
411
case limatype.ProvisionModeYQ:
412
layout = append(layout, iso9660util.Entry{
413
Path: fmt.Sprintf("provision.%s/%08d", f.Mode, i),
414
Reader: strings.NewReader(*f.Expression),
415
})
416
case limatype.ProvisionModeBoot:
417
continue
418
case limatype.ProvisionModeAnsible:
419
continue
420
default:
421
return fmt.Errorf("unknown provision mode %q", f.Mode)
422
}
423
}
424
425
if guestAgentBinary != "" {
426
var guestAgent io.ReadCloser
427
if strings.HasSuffix(guestAgentBinary, ".gz") {
428
logrus.Debugf("Decompressing %s", guestAgentBinary)
429
guestAgentGz, err := os.Open(guestAgentBinary)
430
if err != nil {
431
return err
432
}
433
defer guestAgentGz.Close()
434
guestAgent, err = gzip.NewReader(guestAgentGz)
435
if err != nil {
436
return err
437
}
438
} else {
439
guestAgent, err = os.Open(guestAgentBinary)
440
if err != nil {
441
return err
442
}
443
}
444
445
defer guestAgent.Close()
446
layout = append(layout, iso9660util.Entry{
447
Path: "lima-guestagent",
448
Reader: guestAgent,
449
})
450
}
451
452
if nerdctlArchive != "" {
453
nftgz := args.Containerd.Archive
454
nftgzR, err := os.Open(nerdctlArchive)
455
if err != nil {
456
return err
457
}
458
defer nftgzR.Close()
459
layout = append(layout, iso9660util.Entry{
460
// ISO9660 requires len(Path) <= 30
461
Path: nftgz,
462
Reader: nftgzR,
463
})
464
}
465
466
if noCloudInit {
467
layout = append(layout, iso9660util.Entry{
468
Path: "ssh_authorized_keys",
469
Reader: strings.NewReader(strings.Join(args.SSHPubKeys, "\n")),
470
})
471
return writeCIDataDir(filepath.Join(instDir, filenames.CIDataISODir), layout)
472
}
473
474
return iso9660util.Write(filepath.Join(instDir, filenames.CIDataISO), "cidata", layout)
475
}
476
477
func removeControlChars(s string) string {
478
out := make([]rune, 0, len(s))
479
for _, r := range s {
480
if unicode.IsPrint(r) {
481
out = append(out, r)
482
}
483
}
484
return string(out)
485
}
486
487
func getCert(content string) Cert {
488
lines := []string{}
489
for line := range strings.SplitSeq(content, "\n") {
490
if line == "" {
491
continue
492
}
493
lines = append(lines, strings.TrimSpace(line))
494
}
495
// return lines
496
return Cert{Lines: lines}
497
}
498
499
func getBootCmds(p []limatype.Provision) []BootCmds {
500
var bootCmds []BootCmds
501
for _, f := range p {
502
if f.Mode == limatype.ProvisionModeBoot {
503
bootCmds = append(bootCmds, BootCmds{Lines: strings.Split(*f.Script, "\n")})
504
}
505
}
506
return bootCmds
507
}
508
509
func diskDeviceNameFromOrder(order int) string {
510
return fmt.Sprintf("vd%c", int('b')+order)
511
}
512
513
func writeCIDataDir(rootPath string, layout []iso9660util.Entry) error {
514
slices.SortFunc(layout, func(a, b iso9660util.Entry) int {
515
return strings.Compare(strings.ToLower(a.Path), strings.ToLower(b.Path))
516
})
517
518
if err := os.RemoveAll(rootPath); err != nil {
519
return err
520
}
521
522
for _, e := range layout {
523
if dir := path.Dir(e.Path); dir != "" && dir != "/" {
524
if err := os.MkdirAll(filepath.Join(rootPath, dir), 0o700); err != nil {
525
return err
526
}
527
}
528
f, err := os.OpenFile(filepath.Join(rootPath, e.Path), os.O_CREATE|os.O_RDWR, 0o700)
529
if err != nil {
530
return err
531
}
532
if _, err := io.Copy(f, e.Reader); err != nil {
533
_ = f.Close()
534
return err
535
}
536
_ = f.Close()
537
}
538
539
return nil
540
}
541
542