Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/limayaml/defaults.go
2601 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package limayaml
5
6
import (
7
"bytes"
8
"context"
9
"crypto/sha256"
10
_ "embed"
11
"errors"
12
"fmt"
13
"maps"
14
"net"
15
"os"
16
"os/user"
17
"path/filepath"
18
"runtime"
19
"slices"
20
"strconv"
21
"strings"
22
"text/template"
23
24
"github.com/docker/go-units"
25
"github.com/goccy/go-yaml"
26
"github.com/pbnjay/memory"
27
"github.com/sirupsen/logrus"
28
29
"github.com/lima-vm/lima/v2/pkg/instance/hostname"
30
"github.com/lima-vm/lima/v2/pkg/ioutilx"
31
"github.com/lima-vm/lima/v2/pkg/limatype"
32
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
33
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
34
"github.com/lima-vm/lima/v2/pkg/localpathutil"
35
. "github.com/lima-vm/lima/v2/pkg/must"
36
"github.com/lima-vm/lima/v2/pkg/networks"
37
"github.com/lima-vm/lima/v2/pkg/osutil"
38
"github.com/lima-vm/lima/v2/pkg/ptr"
39
"github.com/lima-vm/lima/v2/pkg/version"
40
)
41
42
const (
43
// Default9pSecurityModel is "none" for supporting symlinks
44
// https://gitlab.com/qemu-project/qemu/-/issues/173
45
Default9pSecurityModel string = "none"
46
Default9pProtocolVersion string = "9p2000.L"
47
Default9pMsize string = "128KiB"
48
Default9pCacheForRO string = "fscache"
49
Default9pCacheForRW string = "mmap"
50
51
DefaultVirtiofsQueueSize int = 1024
52
)
53
54
var (
55
IPv4loopback1 = net.IPv4(127, 0, 0, 1)
56
57
userHomeDir = Must(os.UserHomeDir())
58
currentUser = Must(user.Current())
59
)
60
61
//go:embed containerd.yaml
62
var defaultContainerdYAML []byte
63
64
type ContainerdYAML struct {
65
Archives []limatype.File
66
}
67
68
func defaultContainerdArchives() []limatype.File {
69
var containerd ContainerdYAML
70
err := yaml.UnmarshalWithOptions(defaultContainerdYAML, &containerd, yaml.Strict())
71
if err != nil {
72
panic(fmt.Errorf("failed to unmarshal as YAML: %w", err))
73
}
74
return containerd.Archives
75
}
76
77
// FirstUsernetIndex gets the index of first usernet network under l.Network[]. Returns -1 if no usernet network found.
78
func FirstUsernetIndex(l *limatype.LimaYAML) int {
79
return slices.IndexFunc(l.Networks, func(network limatype.Network) bool { return networks.IsUsernet(network.Lima) })
80
}
81
82
func MACAddress(uniqueID string) string {
83
sha := sha256.Sum256([]byte(osutil.MachineID() + uniqueID))
84
// "5" is the magic number in the Lima ecosystem.
85
// (Visit https://en.wiktionary.org/wiki/lima and Command-F "five")
86
//
87
// But the second hex number is changed to 2 to satisfy the convention for
88
// local MAC addresses (https://en.wikipedia.org/wiki/MAC_address#Ranges_of_group_and_locally_administered_addresses)
89
//
90
// See also https://gitlab.com/wireshark/wireshark/-/blob/release-4.0/manuf to confirm the uniqueness of this prefix.
91
hw := append(net.HardwareAddr{0x52, 0x55, 0x55}, sha[0:3]...)
92
return hw.String()
93
}
94
95
// MountTag generates a stable mount tag from location and mountPoint.
96
// Both paths are hashed to handle the same location mounted to multiple mount points.
97
func MountTag(location, mountPoint string) string {
98
sha := sha256.Sum256([]byte(location + "\x00" + mountPoint))
99
return fmt.Sprintf("lima-%x", sha[0:8])
100
}
101
102
func defaultCPUs() int {
103
const x = 4
104
if hostCPUs := runtime.NumCPU(); hostCPUs < x {
105
return hostCPUs
106
}
107
return x
108
}
109
110
func defaultMemory() uint64 {
111
const x uint64 = 4 * 1024 * 1024 * 1024
112
if halfOfHostMemory := memory.TotalMemory() / 2; halfOfHostMemory < x {
113
return halfOfHostMemory
114
}
115
return x
116
}
117
118
func defaultMemoryAsString() string {
119
return units.BytesSize(float64(defaultMemory()))
120
}
121
122
func defaultDiskSizeAsString() string {
123
// currently just hardcoded
124
return "100GiB"
125
}
126
127
func defaultGuestInstallPrefix() string {
128
return "/usr/local"
129
}
130
131
// FillDefault updates undefined fields in y with defaults from d (or built-in default), and overwrites with values from o.
132
// Both d and o may be empty.
133
//
134
// Maps (`Env`) are being merged: first populated from d, overwritten by y, and again overwritten by o.
135
// Slices (e.g. `Mounts`, `Provision`) are appended, starting with o, followed by y, and finally d. This
136
// makes sure o takes priority over y over d, in cases it matters (e.g. `PortForwards`, where the first
137
// matching rule terminates the search).
138
//
139
// Exceptions:
140
// - Mounts are appended in d, y, o order, but "merged" when the Location matches a previous entry;
141
// the highest priority Writable setting wins.
142
// - Networks are appended in d, y, o order
143
// - DNS are picked from the highest priority where DNS is not empty.
144
// - CACertificates Files and Certs are uniquely appended in d, y, o order
145
func FillDefault(ctx context.Context, y, d, o *limatype.LimaYAML, filePath string, warn bool) {
146
instDir := filepath.Dir(filePath)
147
148
existingLimaVersion := ExistingLimaVersion(instDir)
149
150
if y.User.Name == nil {
151
y.User.Name = d.User.Name
152
}
153
if y.User.Comment == nil {
154
y.User.Comment = d.User.Comment
155
}
156
if y.User.Home == nil {
157
y.User.Home = d.User.Home
158
}
159
if y.User.Shell == nil {
160
y.User.Shell = d.User.Shell
161
}
162
if y.User.UID == nil {
163
y.User.UID = d.User.UID
164
}
165
if o.User.Name != nil {
166
y.User.Name = o.User.Name
167
}
168
if o.User.Comment != nil {
169
y.User.Comment = o.User.Comment
170
}
171
if o.User.Home != nil {
172
y.User.Home = o.User.Home
173
}
174
if o.User.Shell != nil {
175
y.User.Shell = o.User.Shell
176
}
177
if o.User.UID != nil {
178
y.User.UID = o.User.UID
179
}
180
if y.User.Name == nil {
181
y.User.Name = ptr.Of(osutil.LimaUser(ctx, existingLimaVersion, warn).Username)
182
warn = false
183
}
184
if y.User.Comment == nil {
185
y.User.Comment = ptr.Of(osutil.LimaUser(ctx, existingLimaVersion, warn).Name)
186
warn = false
187
}
188
if y.User.Home == nil {
189
y.User.Home = ptr.Of(osutil.LimaUser(ctx, existingLimaVersion, warn).HomeDir)
190
warn = false
191
}
192
if y.User.Shell == nil {
193
y.User.Shell = ptr.Of("/bin/bash")
194
}
195
if y.User.UID == nil {
196
uidString := osutil.LimaUser(ctx, existingLimaVersion, warn).Uid
197
if uid, err := strconv.ParseUint(uidString, 10, 32); err == nil {
198
y.User.UID = ptr.Of(uint32(uid))
199
} else {
200
// This should never happen; LimaUser() makes sure that .Uid is numeric
201
logrus.WithError(err).Warnf("Can't parse `user.uid` %q", uidString)
202
y.User.UID = ptr.Of(uint32(1000))
203
}
204
// warn = false
205
}
206
if out, err := executeGuestTemplate(*y.User.Home, instDir, y.User, y.Param); err == nil {
207
y.User.Home = ptr.Of(out.String())
208
} else {
209
logrus.WithError(err).Warnf("Couldn't process `user.home` value %q as a template", *y.User.Home)
210
}
211
212
if y.VMType == nil {
213
y.VMType = d.VMType
214
}
215
if o.VMType != nil {
216
y.VMType = o.VMType
217
}
218
219
if y.OS == nil {
220
y.OS = d.OS
221
}
222
if o.OS != nil {
223
y.OS = o.OS
224
}
225
y.OS = ptr.Of(ResolveOS(y.OS))
226
if y.Arch == nil {
227
y.Arch = d.Arch
228
}
229
if o.Arch != nil {
230
y.Arch = o.Arch
231
}
232
y.Arch = ptr.Of(ResolveArch(y.Arch))
233
234
y.Images = slices.Concat(o.Images, y.Images, d.Images)
235
for i := range y.Images {
236
img := &y.Images[i]
237
if img.Arch == "" {
238
img.Arch = *y.Arch
239
}
240
if img.Kernel != nil && img.Kernel.Arch == "" {
241
img.Kernel.Arch = img.Arch
242
}
243
if img.Initrd != nil && img.Initrd.Arch == "" {
244
img.Initrd.Arch = img.Arch
245
}
246
}
247
248
if y.CPUs == nil {
249
y.CPUs = d.CPUs
250
}
251
if o.CPUs != nil {
252
y.CPUs = o.CPUs
253
}
254
if y.CPUs == nil || *y.CPUs == 0 {
255
y.CPUs = ptr.Of(defaultCPUs())
256
}
257
258
if y.Memory == nil {
259
y.Memory = d.Memory
260
}
261
if o.Memory != nil {
262
y.Memory = o.Memory
263
}
264
if y.Memory == nil || *y.Memory == "" {
265
y.Memory = ptr.Of(defaultMemoryAsString())
266
}
267
268
if y.Disk == nil {
269
y.Disk = d.Disk
270
}
271
if o.Disk != nil {
272
y.Disk = o.Disk
273
}
274
if y.Disk == nil || *y.Disk == "" {
275
y.Disk = ptr.Of(defaultDiskSizeAsString())
276
}
277
278
y.AdditionalDisks = slices.Concat(o.AdditionalDisks, y.AdditionalDisks, d.AdditionalDisks)
279
280
if y.Audio.Device == nil {
281
y.Audio.Device = d.Audio.Device
282
}
283
if o.Audio.Device != nil {
284
y.Audio.Device = o.Audio.Device
285
}
286
if y.Audio.Device == nil {
287
y.Audio.Device = ptr.Of("")
288
}
289
290
if y.Video.Display == nil {
291
y.Video.Display = d.Video.Display
292
}
293
if o.Video.Display != nil {
294
y.Video.Display = o.Video.Display
295
}
296
if y.Video.Display == nil || *y.Video.Display == "" {
297
y.Video.Display = ptr.Of("none")
298
}
299
300
if y.Video.VNC.Display == nil {
301
y.Video.VNC.Display = d.Video.VNC.Display
302
}
303
if o.Video.VNC.Display != nil {
304
y.Video.VNC.Display = o.Video.VNC.Display
305
}
306
307
if y.Firmware.LegacyBIOS == nil {
308
y.Firmware.LegacyBIOS = d.Firmware.LegacyBIOS
309
}
310
if o.Firmware.LegacyBIOS != nil {
311
y.Firmware.LegacyBIOS = o.Firmware.LegacyBIOS
312
}
313
if y.Firmware.LegacyBIOS == nil {
314
y.Firmware.LegacyBIOS = ptr.Of(false)
315
}
316
317
y.Firmware.Images = slices.Concat(o.Firmware.Images, y.Firmware.Images, d.Firmware.Images)
318
for i := range y.Firmware.Images {
319
f := &y.Firmware.Images[i]
320
if f.Arch == "" {
321
f.Arch = *y.Arch
322
}
323
}
324
325
if y.TimeZone == nil {
326
y.TimeZone = d.TimeZone
327
}
328
if o.TimeZone != nil {
329
y.TimeZone = o.TimeZone
330
}
331
if y.TimeZone == nil {
332
y.TimeZone = ptr.Of(hostTimeZone())
333
}
334
335
if y.SSH.LocalPort == nil {
336
y.SSH.LocalPort = d.SSH.LocalPort
337
}
338
if o.SSH.LocalPort != nil {
339
y.SSH.LocalPort = o.SSH.LocalPort
340
}
341
if y.SSH.LocalPort == nil {
342
// y.SSH.LocalPort value is not filled here (filled by the hostagent)
343
y.SSH.LocalPort = ptr.Of(0)
344
}
345
if y.SSH.LoadDotSSHPubKeys == nil {
346
y.SSH.LoadDotSSHPubKeys = d.SSH.LoadDotSSHPubKeys
347
}
348
if o.SSH.LoadDotSSHPubKeys != nil {
349
y.SSH.LoadDotSSHPubKeys = o.SSH.LoadDotSSHPubKeys
350
}
351
if y.SSH.LoadDotSSHPubKeys == nil {
352
y.SSH.LoadDotSSHPubKeys = ptr.Of(false) // was true before Lima v1.0
353
}
354
355
if y.SSH.ForwardAgent == nil {
356
y.SSH.ForwardAgent = d.SSH.ForwardAgent
357
}
358
if o.SSH.ForwardAgent != nil {
359
y.SSH.ForwardAgent = o.SSH.ForwardAgent
360
}
361
if y.SSH.ForwardAgent == nil {
362
y.SSH.ForwardAgent = ptr.Of(false)
363
}
364
365
if y.SSH.ForwardX11 == nil {
366
y.SSH.ForwardX11 = d.SSH.ForwardX11
367
}
368
if o.SSH.ForwardX11 != nil {
369
y.SSH.ForwardX11 = o.SSH.ForwardX11
370
}
371
if y.SSH.ForwardX11 == nil {
372
y.SSH.ForwardX11 = ptr.Of(false)
373
}
374
375
if y.SSH.ForwardX11Trusted == nil {
376
y.SSH.ForwardX11Trusted = d.SSH.ForwardX11Trusted
377
}
378
if o.SSH.ForwardX11Trusted != nil {
379
y.SSH.ForwardX11Trusted = o.SSH.ForwardX11Trusted
380
}
381
if y.SSH.ForwardX11Trusted == nil {
382
y.SSH.ForwardX11Trusted = ptr.Of(false)
383
}
384
385
if y.SSH.OverVsock == nil {
386
y.SSH.OverVsock = d.SSH.OverVsock
387
}
388
if o.SSH.OverVsock != nil {
389
y.SSH.OverVsock = o.SSH.OverVsock
390
}
391
// y.SSH.OverVsock default value depends on the driver; filled in driver-specific FillDefault()
392
393
// The deprecated environment variable LIMA_SSH_OVER_VSOCK takes precedence over .ssh.overVsock
394
if envVar := os.Getenv("LIMA_SSH_OVER_VSOCK"); envVar != "" {
395
logrus.Warn("The environment variable LIMA_SSH_OVER_VSOCK is deprecated in favor of the YAML field .ssh.overVsock")
396
b, err := strconv.ParseBool(envVar)
397
if err != nil {
398
logrus.WithError(err).Warnf("invalid LIMA_SSH_OVER_VSOCK value %q", envVar)
399
} else {
400
logrus.Debugf("Overriding ssh.overVsock from %v to %v via LIMA_SSH_OVER_VSOCK", y.SSH.OverVsock, &b)
401
y.SSH.OverVsock = ptr.Of(b)
402
}
403
}
404
405
hosts := make(map[string]string)
406
// Values can be either names or IP addresses. Name values are canonicalized in the hostResolver.
407
maps.Copy(hosts, d.HostResolver.Hosts)
408
maps.Copy(hosts, y.HostResolver.Hosts)
409
maps.Copy(hosts, o.HostResolver.Hosts)
410
y.HostResolver.Hosts = hosts
411
412
y.Provision = slices.Concat(o.Provision, y.Provision, d.Provision)
413
for i := range y.Provision {
414
provision := &y.Provision[i]
415
if provision.Mode == "" {
416
provision.Mode = limatype.ProvisionModeSystem
417
}
418
if provision.Mode == limatype.ProvisionModeDependency && provision.SkipDefaultDependencyResolution == nil {
419
provision.SkipDefaultDependencyResolution = ptr.Of(false)
420
}
421
if provision.Mode == limatype.ProvisionModeData {
422
if provision.Content == nil {
423
provision.Content = ptr.Of("")
424
} else {
425
if out, err := executeGuestTemplate(*provision.Content, instDir, y.User, y.Param); err == nil {
426
provision.Content = ptr.Of(out.String())
427
} else {
428
logrus.WithError(err).Warnf("Couldn't process data content %q as a template", *provision.Content)
429
}
430
}
431
if provision.Overwrite == nil {
432
provision.Overwrite = ptr.Of(true)
433
}
434
}
435
if provision.Mode == limatype.ProvisionModeYQ {
436
if provision.Expression != nil {
437
if out, err := executeGuestTemplate(*provision.Expression, instDir, y.User, y.Param); err == nil {
438
provision.Expression = ptr.Of(out.String())
439
} else {
440
logrus.WithError(err).Warnf("Couldn't process expression %q as a template", *provision.Expression)
441
}
442
}
443
if provision.Format == nil {
444
provision.Format = ptr.Of("auto")
445
}
446
}
447
if provision.Mode == limatype.ProvisionModeData || provision.Mode == limatype.ProvisionModeYQ {
448
if provision.Owner == nil {
449
provision.Owner = ptr.Of("root:root")
450
} else {
451
if out, err := executeGuestTemplate(*provision.Owner, instDir, y.User, y.Param); err == nil {
452
provision.Owner = ptr.Of(out.String())
453
} else {
454
logrus.WithError(err).Warnf("Couldn't process owner %q as a template", *provision.Owner)
455
}
456
}
457
// Path is required; validation will throw an error when it is nil
458
if provision.Path != nil {
459
if out, err := executeGuestTemplate(*provision.Path, instDir, y.User, y.Param); err == nil {
460
provision.Path = ptr.Of(out.String())
461
} else {
462
logrus.WithError(err).Warnf("Couldn't process path %q as a template", *provision.Path)
463
}
464
}
465
if provision.Permissions == nil {
466
provision.Permissions = ptr.Of("644")
467
}
468
}
469
if provision.Script == nil {
470
provision.Script = ptr.Of("")
471
}
472
if *provision.Script != "" {
473
if out, err := executeGuestTemplate(*provision.Script, instDir, y.User, y.Param); err == nil {
474
*provision.Script = out.String()
475
} else {
476
logrus.WithError(err).Warnf("Couldn't process provisioning script %q as a template", *provision.Script)
477
}
478
}
479
}
480
481
if y.GuestInstallPrefix == nil {
482
y.GuestInstallPrefix = d.GuestInstallPrefix
483
}
484
if o.GuestInstallPrefix != nil {
485
y.GuestInstallPrefix = o.GuestInstallPrefix
486
}
487
if y.GuestInstallPrefix == nil {
488
y.GuestInstallPrefix = ptr.Of(defaultGuestInstallPrefix())
489
}
490
491
if y.UpgradePackages == nil {
492
y.UpgradePackages = d.UpgradePackages
493
}
494
if o.UpgradePackages != nil {
495
y.UpgradePackages = o.UpgradePackages
496
}
497
if y.UpgradePackages == nil {
498
y.UpgradePackages = ptr.Of(false)
499
}
500
501
if y.Containerd.System == nil {
502
y.Containerd.System = d.Containerd.System
503
}
504
if o.Containerd.System != nil {
505
y.Containerd.System = o.Containerd.System
506
}
507
if y.Containerd.System == nil {
508
y.Containerd.System = ptr.Of(false)
509
}
510
if y.Containerd.User == nil {
511
y.Containerd.User = d.Containerd.User
512
}
513
if o.Containerd.User != nil {
514
y.Containerd.User = o.Containerd.User
515
}
516
if y.Containerd.User == nil {
517
switch *y.Arch {
518
case limatype.X8664, limatype.AARCH64:
519
y.Containerd.User = ptr.Of(true)
520
default:
521
y.Containerd.User = ptr.Of(false)
522
}
523
}
524
525
y.Containerd.Archives = slices.Concat(o.Containerd.Archives, y.Containerd.Archives, d.Containerd.Archives)
526
if len(y.Containerd.Archives) == 0 {
527
y.Containerd.Archives = defaultContainerdArchives()
528
}
529
for i := range y.Containerd.Archives {
530
f := &y.Containerd.Archives[i]
531
if f.Arch == "" {
532
f.Arch = *y.Arch
533
}
534
}
535
536
y.Probes = slices.Concat(o.Probes, y.Probes, d.Probes)
537
for i := range y.Probes {
538
probe := &y.Probes[i]
539
if probe.Mode == "" {
540
probe.Mode = limatype.ProbeModeReadiness
541
}
542
if probe.Description == "" {
543
probe.Description = fmt.Sprintf("user probe %d/%d", i+1, len(y.Probes))
544
}
545
if probe.Script == nil {
546
probe.Script = ptr.Of("")
547
}
548
if out, err := executeGuestTemplate(*probe.Script, instDir, y.User, y.Param); err == nil {
549
probe.Script = ptr.Of(out.String())
550
} else {
551
logrus.WithError(err).Warnf("Couldn't process probing script %q as a template", *probe.Script)
552
}
553
}
554
555
y.PortForwards = slices.Concat(o.PortForwards, y.PortForwards, d.PortForwards)
556
for i := range y.PortForwards {
557
FillPortForwardDefaults(&y.PortForwards[i], instDir, y.User, y.Param)
558
// After defaults processing the singular HostPort and GuestPort values should not be used again.
559
}
560
561
y.CopyToHost = slices.Concat(o.CopyToHost, y.CopyToHost, d.CopyToHost)
562
for i := range y.CopyToHost {
563
FillCopyToHostDefaults(&y.CopyToHost[i], instDir, y.User, y.Param)
564
}
565
566
if y.HostResolver.Enabled == nil {
567
y.HostResolver.Enabled = d.HostResolver.Enabled
568
}
569
if o.HostResolver.Enabled != nil {
570
y.HostResolver.Enabled = o.HostResolver.Enabled
571
}
572
if y.HostResolver.Enabled == nil {
573
y.HostResolver.Enabled = ptr.Of(true)
574
}
575
576
if y.HostResolver.IPv6 == nil {
577
y.HostResolver.IPv6 = d.HostResolver.IPv6
578
}
579
if o.HostResolver.IPv6 != nil {
580
y.HostResolver.IPv6 = o.HostResolver.IPv6
581
}
582
if y.HostResolver.IPv6 == nil {
583
y.HostResolver.IPv6 = ptr.Of(false)
584
}
585
586
if y.PropagateProxyEnv == nil {
587
y.PropagateProxyEnv = d.PropagateProxyEnv
588
}
589
if o.PropagateProxyEnv != nil {
590
y.PropagateProxyEnv = o.PropagateProxyEnv
591
}
592
if y.PropagateProxyEnv == nil {
593
y.PropagateProxyEnv = ptr.Of(true)
594
}
595
596
networks := make([]limatype.Network, 0, len(d.Networks)+len(y.Networks)+len(o.Networks))
597
iface := make(map[string]int)
598
for _, nw := range slices.Concat(d.Networks, y.Networks, o.Networks) {
599
if i, ok := iface[nw.Interface]; ok {
600
if nw.Socket != "" {
601
networks[i].Socket = nw.Socket
602
networks[i].Lima = ""
603
}
604
if nw.Lima != "" {
605
if nw.Socket != "" {
606
// We can't return an error, so just log it, and prefer `lima` over `socket`
607
logrus.Errorf("Network %q has both socket=%q and lima=%q fields; ignoring socket",
608
nw.Interface, nw.Socket, nw.Lima)
609
}
610
networks[i].Lima = nw.Lima
611
networks[i].Socket = ""
612
}
613
if nw.MACAddress != "" {
614
networks[i].MACAddress = nw.MACAddress
615
}
616
if nw.Metric != nil {
617
networks[i].Metric = nw.Metric
618
}
619
} else {
620
// unnamed network definitions are not combined/overwritten
621
if nw.Interface != "" {
622
iface[nw.Interface] = len(networks)
623
}
624
networks = append(networks, nw)
625
}
626
}
627
y.Networks = networks
628
for i := range y.Networks {
629
nw := &y.Networks[i]
630
if nw.MACAddress == "" {
631
// every interface in every limayaml file must get its own unique MAC address
632
nw.MACAddress = MACAddress(fmt.Sprintf("%s#%d", filePath, i))
633
}
634
if nw.Interface == "" {
635
nw.Interface = "lima" + strconv.Itoa(i)
636
}
637
if nw.Metric == nil {
638
nw.Metric = ptr.Of(uint32(100))
639
}
640
}
641
642
y.MountTypesUnsupported = slices.Concat(o.MountTypesUnsupported, y.MountTypesUnsupported, d.MountTypesUnsupported)
643
644
// MountType has to be resolved before resolving Mounts
645
if y.MountType == nil {
646
y.MountType = d.MountType
647
}
648
if o.MountType != nil {
649
y.MountType = o.MountType
650
}
651
652
if y.MountInotify == nil {
653
y.MountInotify = d.MountInotify
654
}
655
if o.MountInotify != nil {
656
y.MountInotify = o.MountInotify
657
}
658
if y.MountInotify == nil {
659
y.MountInotify = ptr.Of(false)
660
}
661
662
// Combine all mounts; highest priority entry determines writable status.
663
// Only works for exact matches; does not normalize case or resolve symlinks.
664
mounts := make([]limatype.Mount, 0, len(d.Mounts)+len(y.Mounts)+len(o.Mounts))
665
mountPoint := make(map[string]int)
666
for _, mount := range slices.Concat(d.Mounts, y.Mounts, o.Mounts) {
667
if out, err := executeHostTemplate(mount.Location, instDir, y.Param); err == nil {
668
mount.Location = filepath.Clean(out.String())
669
} else {
670
logrus.WithError(err).Warnf("Couldn't process mount location %q as a template", mount.Location)
671
}
672
// Expand a path that begins with `~`. Relative paths are not modified, and rejected by Validate() later.
673
if localpathutil.IsTildePath(mount.Location) {
674
if location, err := localpathutil.Expand(mount.Location); err == nil {
675
mount.Location = location
676
} else {
677
logrus.WithError(err).Warnf("Couldn't expand location %q", mount.Location)
678
}
679
}
680
if mount.MountPoint == nil {
681
mountLocation := mount.Location
682
if runtime.GOOS == "windows" {
683
var err error
684
mountLocation, err = ioutilx.WindowsSubsystemPath(ctx, mountLocation)
685
if err != nil {
686
logrus.WithError(err).Warnf("Couldn't convert location %q into mount target", mount.Location)
687
}
688
}
689
mount.MountPoint = ptr.Of(mountLocation)
690
} else {
691
if out, err := executeGuestTemplate(*mount.MountPoint, instDir, y.User, y.Param); err == nil {
692
mount.MountPoint = ptr.Of(out.String())
693
} else {
694
logrus.WithError(err).Warnf("Couldn't process mount point %q as a template", *mount.MountPoint)
695
}
696
}
697
if i, ok := mountPoint[*mount.MountPoint]; ok {
698
if mount.SSHFS.Cache != nil {
699
mounts[i].SSHFS.Cache = mount.SSHFS.Cache
700
}
701
if mount.SSHFS.FollowSymlinks != nil {
702
mounts[i].SSHFS.FollowSymlinks = mount.SSHFS.FollowSymlinks
703
}
704
if mount.SSHFS.SFTPDriver != nil {
705
mounts[i].SSHFS.SFTPDriver = mount.SSHFS.SFTPDriver
706
}
707
if mount.NineP.SecurityModel != nil {
708
mounts[i].NineP.SecurityModel = mount.NineP.SecurityModel
709
}
710
if mount.NineP.ProtocolVersion != nil {
711
mounts[i].NineP.ProtocolVersion = mount.NineP.ProtocolVersion
712
}
713
if mount.NineP.Msize != nil {
714
mounts[i].NineP.Msize = mount.NineP.Msize
715
}
716
if mount.NineP.Cache != nil {
717
mounts[i].NineP.Cache = mount.NineP.Cache
718
}
719
if mount.Virtiofs.QueueSize != nil {
720
mounts[i].Virtiofs.QueueSize = mount.Virtiofs.QueueSize
721
}
722
if mount.Writable != nil {
723
mounts[i].Writable = mount.Writable
724
}
725
if mount.MountPoint != nil {
726
mounts[i].MountPoint = mount.MountPoint
727
}
728
} else {
729
mountPoint[*mount.MountPoint] = len(mounts)
730
mounts = append(mounts, mount)
731
}
732
}
733
y.Mounts = mounts
734
735
for i := range y.Mounts {
736
mount := &y.Mounts[i]
737
if mount.SSHFS.Cache == nil {
738
mount.SSHFS.Cache = ptr.Of(true)
739
}
740
if mount.SSHFS.FollowSymlinks == nil {
741
mount.SSHFS.FollowSymlinks = ptr.Of(false)
742
}
743
if mount.SSHFS.SFTPDriver == nil {
744
mount.SSHFS.SFTPDriver = ptr.Of("")
745
}
746
if mount.NineP.SecurityModel == nil {
747
mounts[i].NineP.SecurityModel = ptr.Of(Default9pSecurityModel)
748
}
749
if mount.NineP.ProtocolVersion == nil {
750
mounts[i].NineP.ProtocolVersion = ptr.Of(Default9pProtocolVersion)
751
}
752
if mount.NineP.Msize == nil {
753
mounts[i].NineP.Msize = ptr.Of(Default9pMsize)
754
}
755
if mount.Writable == nil {
756
mount.Writable = ptr.Of(false)
757
}
758
if mount.NineP.Cache == nil {
759
if *mount.Writable {
760
mounts[i].NineP.Cache = ptr.Of(Default9pCacheForRW)
761
} else {
762
mounts[i].NineP.Cache = ptr.Of(Default9pCacheForRO)
763
}
764
}
765
}
766
767
// Note: DNS lists are not combined; highest priority setting is picked
768
if len(y.DNS) == 0 {
769
y.DNS = d.DNS
770
}
771
if len(o.DNS) > 0 {
772
y.DNS = o.DNS
773
}
774
775
env := make(map[string]string)
776
maps.Copy(env, d.Env)
777
maps.Copy(env, y.Env)
778
maps.Copy(env, o.Env)
779
y.Env = env
780
781
param := make(map[string]string)
782
maps.Copy(param, d.Param)
783
maps.Copy(param, y.Param)
784
maps.Copy(param, o.Param)
785
y.Param = param
786
787
vmOpts := make(limatype.VMOpts)
788
maps.Copy(vmOpts, d.VMOpts)
789
maps.Copy(vmOpts, y.VMOpts)
790
maps.Copy(vmOpts, o.VMOpts)
791
y.VMOpts = vmOpts
792
793
if y.CACertificates.RemoveDefaults == nil {
794
y.CACertificates.RemoveDefaults = d.CACertificates.RemoveDefaults
795
}
796
if o.CACertificates.RemoveDefaults != nil {
797
y.CACertificates.RemoveDefaults = o.CACertificates.RemoveDefaults
798
}
799
if y.CACertificates.RemoveDefaults == nil {
800
y.CACertificates.RemoveDefaults = ptr.Of(false)
801
}
802
803
y.CACertificates.Files = unique(slices.Concat(d.CACertificates.Files, y.CACertificates.Files, o.CACertificates.Files))
804
y.CACertificates.Certs = unique(slices.Concat(d.CACertificates.Certs, y.CACertificates.Certs, o.CACertificates.Certs))
805
806
if y.NestedVirtualization == nil {
807
y.NestedVirtualization = d.NestedVirtualization
808
}
809
if o.NestedVirtualization != nil {
810
y.NestedVirtualization = o.NestedVirtualization
811
}
812
if y.NestedVirtualization == nil {
813
y.NestedVirtualization = ptr.Of(false)
814
}
815
816
if y.Plain == nil {
817
y.Plain = d.Plain
818
}
819
if o.Plain != nil {
820
y.Plain = o.Plain
821
}
822
if y.Plain == nil {
823
y.Plain = ptr.Of(false)
824
}
825
826
fixUpForPlainMode(y)
827
}
828
829
// ExistingLimaVersion returns empty if the instance was created with Lima prior to v0.20.
830
func ExistingLimaVersion(instDir string) string {
831
if !IsExistingInstanceDir(instDir) {
832
return version.Version
833
}
834
835
limaVersionFile := filepath.Join(instDir, filenames.LimaVersion)
836
if b, err := os.ReadFile(limaVersionFile); err == nil {
837
return strings.TrimSpace(string(b))
838
} else if !errors.Is(err, os.ErrNotExist) {
839
logrus.WithError(err).Warnf("Failed to read %q", limaVersionFile)
840
}
841
842
return version.Version
843
}
844
845
func fixUpForPlainMode(y *limatype.LimaYAML) {
846
if !*y.Plain {
847
return
848
}
849
deleteNonStaticPortForwards(&y.PortForwards)
850
y.Mounts = nil
851
y.Containerd.System = ptr.Of(false)
852
y.Containerd.User = ptr.Of(false)
853
y.TimeZone = ptr.Of("")
854
}
855
856
// deleteNonStaticPortForwards removes all non-static port forwarding rules in case of Plain mode.
857
func deleteNonStaticPortForwards(portForwards *[]limatype.PortForward) {
858
staticPortForwards := make([]limatype.PortForward, 0, len(*portForwards))
859
for _, rule := range *portForwards {
860
if rule.Static {
861
staticPortForwards = append(staticPortForwards, rule)
862
}
863
}
864
*portForwards = staticPortForwards
865
}
866
867
func executeGuestTemplate(format, instDir string, user limatype.User, param map[string]string) (bytes.Buffer, error) {
868
tmpl, err := template.New("").Parse(format)
869
if err == nil {
870
name := filepath.Base(instDir)
871
data := map[string]any{
872
"Name": name,
873
"Hostname": hostname.FromInstName(name), // TODO: support customization
874
"UID": *user.UID,
875
"User": *user.Name,
876
"Home": *user.Home,
877
"Param": param,
878
}
879
var out bytes.Buffer
880
err = tmpl.Execute(&out, data)
881
if err == nil {
882
return out, nil
883
}
884
}
885
return bytes.Buffer{}, err
886
}
887
888
func executeHostTemplate(format, instDir string, param map[string]string) (bytes.Buffer, error) {
889
tmpl, err := template.New("").Parse(format)
890
if err == nil {
891
limaHome, _ := dirnames.LimaDir()
892
globalTempDir := "/tmp"
893
if runtime.GOOS == "windows" {
894
// On Windows the global temp directory "%SystemRoot%\Temp" (normally "C:\Windows\Temp")
895
// is not writable by regular users.
896
globalTempDir = os.TempDir()
897
}
898
data := map[string]any{
899
"Dir": instDir,
900
"Name": filepath.Base(instDir),
901
// TODO: add hostname fields for the host and the guest
902
"UID": currentUser.Uid,
903
"User": currentUser.Username,
904
"Home": userHomeDir,
905
"Param": param,
906
"GlobalTempDir": globalTempDir,
907
"TempDir": os.TempDir(),
908
909
"Instance": filepath.Base(instDir), // DEPRECATED, use `{{.Name}}`
910
"LimaHome": limaHome, // DEPRECATED, use `{{.Dir}}` instead of `{{.LimaHome}}/{{.Instance}}`
911
}
912
var out bytes.Buffer
913
err = tmpl.Execute(&out, data)
914
if err == nil {
915
return out, nil
916
}
917
}
918
return bytes.Buffer{}, err
919
}
920
921
func FillPortForwardDefaults(rule *limatype.PortForward, instDir string, user limatype.User, param map[string]string) {
922
if rule.Proto == "" {
923
rule.Proto = limatype.ProtoAny
924
}
925
if rule.GuestIP == nil {
926
if rule.GuestIPMustBeZero != nil && *rule.GuestIPMustBeZero {
927
rule.GuestIP = net.IPv4zero
928
} else {
929
rule.GuestIP = IPv4loopback1
930
}
931
}
932
if rule.GuestIPMustBeZero == nil {
933
rule.GuestIPMustBeZero = ptr.Of(rule.GuestIP.Equal(net.IPv4zero))
934
}
935
if rule.HostIP == nil {
936
rule.HostIP = IPv4loopback1
937
}
938
if rule.GuestPortRange[0] == 0 && rule.GuestPortRange[1] == 0 {
939
if rule.GuestPort == 0 {
940
rule.GuestPortRange[0] = 1
941
rule.GuestPortRange[1] = 65535
942
} else {
943
rule.GuestPortRange[0] = rule.GuestPort
944
rule.GuestPortRange[1] = rule.GuestPort
945
}
946
}
947
if rule.GuestSocket != "" {
948
if out, err := executeGuestTemplate(rule.GuestSocket, instDir, user, param); err == nil {
949
rule.GuestSocket = out.String()
950
} else {
951
logrus.WithError(err).Warnf("Couldn't process guestSocket %q as a template", rule.GuestSocket)
952
}
953
}
954
if rule.HostSocket != "" {
955
if out, err := executeHostTemplate(rule.HostSocket, instDir, param); err == nil {
956
rule.HostSocket = out.String()
957
} else {
958
logrus.WithError(err).Warnf("Couldn't process hostSocket %q as a template", rule.HostSocket)
959
}
960
if !filepath.IsAbs(rule.HostSocket) {
961
rule.HostSocket = filepath.Join(instDir, filenames.SocketDir, rule.HostSocket)
962
}
963
} else if rule.HostPortRange[0] == 0 && rule.HostPortRange[1] == 0 {
964
if rule.HostPort == 0 {
965
rule.HostPortRange = rule.GuestPortRange
966
} else {
967
rule.HostPortRange[0] = rule.HostPort
968
rule.HostPortRange[1] = rule.HostPort
969
}
970
}
971
}
972
973
func FillCopyToHostDefaults(rule *limatype.CopyToHost, instDir string, user limatype.User, param map[string]string) {
974
if rule.GuestFile != "" {
975
if out, err := executeGuestTemplate(rule.GuestFile, instDir, user, param); err == nil {
976
rule.GuestFile = out.String()
977
} else {
978
logrus.WithError(err).Warnf("Couldn't process guest %q as a template", rule.GuestFile)
979
}
980
}
981
if rule.HostFile != "" {
982
if out, err := executeHostTemplate(rule.HostFile, instDir, param); err == nil {
983
rule.HostFile = out.String()
984
} else {
985
logrus.WithError(err).Warnf("Couldn't process host %q as a template", rule.HostFile)
986
}
987
}
988
}
989
990
func IsExistingInstanceDir(dir string) bool {
991
// existence of "lima.yaml" does not signify existence of the instance,
992
// because the file is created during the initialization of the instance.
993
for _, f := range []string{
994
filenames.HostAgentStdoutLog, filenames.HostAgentStderrLog,
995
filenames.VzIdentifier, filenames.BaseDisk, filenames.DiffDisk, filenames.CIDataISO,
996
} {
997
file := filepath.Join(dir, f)
998
if _, err := os.Lstat(file); !errors.Is(err, os.ErrNotExist) {
999
return true
1000
}
1001
}
1002
return false
1003
}
1004
1005
func ResolveOS(s *string) limatype.OS {
1006
if s == nil || *s == "" || *s == "default" {
1007
return limatype.NewOS("linux")
1008
}
1009
return *s
1010
}
1011
1012
func ResolveArch(s *string) limatype.Arch {
1013
if s == nil || *s == "" || *s == "default" {
1014
return limatype.NewArch(runtime.GOARCH)
1015
}
1016
return *s
1017
}
1018
1019
func IsNativeArch(arch limatype.Arch) bool {
1020
nativeX8664 := arch == limatype.X8664 && runtime.GOARCH == "amd64"
1021
nativeAARCH64 := arch == limatype.AARCH64 && runtime.GOARCH == "arm64"
1022
nativeARMV7L := arch == limatype.ARMV7L && runtime.GOARCH == "arm" && limatype.Goarm() == 7
1023
nativePPC64LE := arch == limatype.PPC64LE && runtime.GOARCH == "ppc64le"
1024
nativeRISCV64 := arch == limatype.RISCV64 && runtime.GOARCH == "riscv64"
1025
nativeS390X := arch == limatype.S390X && runtime.GOARCH == "s390x"
1026
return nativeX8664 || nativeAARCH64 || nativeARMV7L || nativePPC64LE || nativeRISCV64 || nativeS390X
1027
}
1028
1029
func unique(s []string) []string {
1030
keys := make(map[string]bool)
1031
list := []string{}
1032
for _, entry := range s {
1033
if _, found := keys[entry]; !found {
1034
keys[entry] = true
1035
list = append(list, entry)
1036
}
1037
}
1038
return list
1039
}
1040
1041