Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/driver/krunkit/krunkit_driver_darwin_arm64.go
2639 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package krunkit
5
6
import (
7
"context"
8
"embed"
9
"errors"
10
"fmt"
11
"net"
12
"os"
13
"os/exec"
14
"path/filepath"
15
"strings"
16
"syscall"
17
"time"
18
19
"github.com/coreos/go-semver/semver"
20
"github.com/lima-vm/go-qcow2reader/image/raw"
21
"github.com/sirupsen/logrus"
22
23
"github.com/lima-vm/lima/v2/pkg/driver"
24
"github.com/lima-vm/lima/v2/pkg/driverutil"
25
"github.com/lima-vm/lima/v2/pkg/executil"
26
"github.com/lima-vm/lima/v2/pkg/limatype"
27
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
28
"github.com/lima-vm/lima/v2/pkg/limayaml"
29
"github.com/lima-vm/lima/v2/pkg/networks/usernet"
30
"github.com/lima-vm/lima/v2/pkg/osutil"
31
"github.com/lima-vm/lima/v2/pkg/ptr"
32
)
33
34
type LimaKrunkitDriver struct {
35
Instance *limatype.Instance
36
SSHLocalPort int
37
38
usernetClient *usernet.Client
39
stopUsernet context.CancelFunc
40
krunkitCmd *exec.Cmd
41
krunkitWaitCh chan error
42
}
43
44
var (
45
_ driver.Driver = (*LimaKrunkitDriver)(nil)
46
vmType limatype.VMType = "krunkit"
47
)
48
49
func New() *LimaKrunkitDriver {
50
return &LimaKrunkitDriver{}
51
}
52
53
func (l *LimaKrunkitDriver) Configure(inst *limatype.Instance) *driver.ConfiguredDriver {
54
l.Instance = inst
55
l.SSHLocalPort = inst.SSHLocalPort
56
57
return &driver.ConfiguredDriver{
58
Driver: l,
59
}
60
}
61
62
func (l *LimaKrunkitDriver) CreateDisk(ctx context.Context) error {
63
// Krunkit also supports qcow2 disks but raw is faster to create and use.
64
return driverutil.EnsureDisk(ctx, l.Instance.Dir, *l.Instance.Config.Disk, raw.Type)
65
}
66
67
func (l *LimaKrunkitDriver) Start(ctx context.Context) (chan error, error) {
68
if l.Instance.Config.SSH.OverVsock != nil && *l.Instance.Config.SSH.OverVsock {
69
logrus.Warn(".ssh.overVsock is not implemented yet for krunkit driver")
70
}
71
72
var err error
73
l.usernetClient, l.stopUsernet, err = startUsernet(ctx, l.Instance)
74
if err != nil {
75
return nil, fmt.Errorf("failed to start usernet: %w", err)
76
}
77
78
krunkitCmd, err := Cmdline(l.Instance)
79
if err != nil {
80
return nil, fmt.Errorf("failed to construct krunkit command line: %w", err)
81
}
82
// Detach krunkit process from parent Lima process
83
krunkitCmd.SysProcAttr = executil.BackgroundSysProcAttr
84
85
logPath := filepath.Join(l.Instance.Dir, "krunkit.log")
86
logfile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
87
if err != nil {
88
return nil, fmt.Errorf("failed to open krunkit logfile: %w", err)
89
}
90
krunkitCmd.Stderr = logfile
91
92
logrus.Infof("Starting krun VM (hint: to watch the progress, see %q)", logPath)
93
logrus.Infof("krunkitCmd.Args: %v", krunkitCmd.Args)
94
95
if err := krunkitCmd.Start(); err != nil {
96
logfile.Close()
97
return nil, errors.New("failed to start krunkitCmd")
98
}
99
100
pidPath := filepath.Join(l.Instance.Dir, filenames.PIDFile(*l.Instance.Config.VMType))
101
if err := os.WriteFile(pidPath, fmt.Appendf(nil, "%d\n", krunkitCmd.Process.Pid), 0o644); err != nil {
102
logrus.WithError(err).Warn("Failed to write PID file")
103
}
104
105
l.krunkitCmd = krunkitCmd
106
l.krunkitWaitCh = make(chan error, 1)
107
go func() {
108
defer func() {
109
logfile.Close()
110
os.RemoveAll(pidPath)
111
close(l.krunkitWaitCh)
112
}()
113
l.krunkitWaitCh <- krunkitCmd.Wait()
114
}()
115
116
err = l.usernetClient.ConfigureDriver(ctx, l.Instance, l.SSHLocalPort)
117
if err != nil {
118
l.krunkitWaitCh <- fmt.Errorf("failed to configure usernet: %w", err)
119
}
120
121
return l.krunkitWaitCh, nil
122
}
123
124
func (l *LimaKrunkitDriver) Stop(_ context.Context) error {
125
if l.krunkitCmd == nil {
126
return nil
127
}
128
129
if err := l.krunkitCmd.Process.Signal(syscall.SIGTERM); err != nil {
130
logrus.WithError(err).Warn("Failed to send interrupt signal")
131
}
132
133
go func() {
134
if l.usernetClient != nil {
135
_ = l.usernetClient.UnExposeSSH(l.Instance.SSHLocalPort)
136
}
137
if l.stopUsernet != nil {
138
l.stopUsernet()
139
}
140
}()
141
142
timeout := time.After(30 * time.Second)
143
select {
144
case <-l.krunkitWaitCh:
145
return nil
146
case <-timeout:
147
if err := l.krunkitCmd.Process.Kill(); err != nil {
148
return err
149
}
150
151
<-l.krunkitWaitCh
152
return nil
153
}
154
}
155
156
func (l *LimaKrunkitDriver) Validate(_ context.Context) error {
157
return validateConfig(l.Instance.Config)
158
}
159
160
func validateConfig(cfg *limatype.LimaYAML) error {
161
if cfg == nil {
162
return errors.New("configuration is nil")
163
}
164
macOSProductVersion, err := osutil.ProductVersion()
165
if err != nil {
166
return err
167
}
168
if macOSProductVersion.LessThan(*semver.New("13.0.0")) {
169
return errors.New("krunkit driver requires macOS 13 or higher to run")
170
}
171
if cfg.Arch != nil && !limayaml.IsNativeArch(*cfg.Arch) {
172
return fmt.Errorf("unsupported arch: %q (krunkit requires native arch)", *cfg.Arch)
173
}
174
if _, err := exec.LookPath(vmType); err != nil {
175
return errors.New("krunkit CLI not found in PATH. Install it via:\nbrew tap slp/krunkit\nbrew install krunkit")
176
}
177
178
if cfg.MountType != nil && (*cfg.MountType != limatype.VIRTIOFS && *cfg.MountType != limatype.REVSSHFS) {
179
return fmt.Errorf("field `mountType` must be %q or %q for krunkit driver, got %q", limatype.VIRTIOFS, limatype.REVSSHFS, *cfg.MountType)
180
}
181
182
return nil
183
}
184
185
func isFedoraConfigured(cfg *limatype.LimaYAML) bool {
186
for _, b := range cfg.Base {
187
if strings.Contains(strings.ToLower(b.URL), "fedora") {
188
return true
189
}
190
}
191
for _, img := range cfg.Images {
192
if strings.Contains(strings.ToLower(img.Location), "fedora") {
193
return true
194
}
195
}
196
return false
197
}
198
199
func (l *LimaKrunkitDriver) FillConfig(_ context.Context, cfg *limatype.LimaYAML, _ string) error {
200
if cfg.MountType == nil {
201
cfg.MountType = ptr.Of(limatype.VIRTIOFS)
202
} else {
203
*cfg.MountType = limatype.VIRTIOFS
204
}
205
206
if cfg.Arch == nil {
207
cfg.Arch = ptr.Of(limatype.AARCH64)
208
} else {
209
*cfg.Arch = limatype.AARCH64
210
}
211
212
cfg.VMType = ptr.Of(vmType)
213
214
return validateConfig(cfg)
215
}
216
217
//go:embed boot/*.sh
218
var bootFS embed.FS
219
220
func (l *LimaKrunkitDriver) BootScripts() (map[string][]byte, error) {
221
scripts := make(map[string][]byte)
222
223
entries, err := bootFS.ReadDir("boot")
224
if err == nil && !isFedoraConfigured(l.Instance.Config) {
225
for _, entry := range entries {
226
if entry.IsDir() {
227
continue
228
}
229
230
content, err := bootFS.ReadFile("boot/" + entry.Name())
231
if err != nil {
232
return nil, err
233
}
234
235
scripts[entry.Name()] = content
236
}
237
}
238
239
// Disabled by krunkit driver for Fedora to make boot time faster
240
if isFedoraConfigured(l.Instance.Config) {
241
scripts["00-reboot-if-required.sh"] = []byte(`#!/bin/sh
242
set -eu
243
exit 0
244
`)
245
}
246
247
return scripts, nil
248
}
249
250
func (l *LimaKrunkitDriver) Create(_ context.Context) error {
251
return nil
252
}
253
254
func (l *LimaKrunkitDriver) Info() driver.Info {
255
var info driver.Info
256
info.Name = vmType
257
if l.Instance != nil && l.Instance.Dir != "" {
258
info.InstanceDir = l.Instance.Dir
259
}
260
261
info.Features = driver.DriverFeatures{
262
DynamicSSHAddress: false,
263
SkipSocketForwarding: false,
264
CanRunGUI: false,
265
}
266
return info
267
}
268
269
func (l *LimaKrunkitDriver) SSHAddress(_ context.Context) (string, error) {
270
return "127.0.0.1", nil
271
}
272
273
func (l *LimaKrunkitDriver) ForwardGuestAgent() bool {
274
return true
275
}
276
277
func (l *LimaKrunkitDriver) Delete(_ context.Context) error {
278
return nil
279
}
280
281
func (l *LimaKrunkitDriver) InspectStatus(_ context.Context, _ *limatype.Instance) string {
282
return ""
283
}
284
285
func (l *LimaKrunkitDriver) RunGUI() error {
286
return nil
287
}
288
289
func (l *LimaKrunkitDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
290
return errUnimplemented
291
}
292
293
func (l *LimaKrunkitDriver) DisplayConnection(_ context.Context) (string, error) {
294
return "", errUnimplemented
295
}
296
297
func (l *LimaKrunkitDriver) CreateSnapshot(_ context.Context, _ string) error {
298
return errUnimplemented
299
}
300
301
func (l *LimaKrunkitDriver) ApplySnapshot(_ context.Context, _ string) error {
302
return errUnimplemented
303
}
304
305
func (l *LimaKrunkitDriver) DeleteSnapshot(_ context.Context, _ string) error {
306
return errUnimplemented
307
}
308
309
func (l *LimaKrunkitDriver) ListSnapshots(_ context.Context) (string, error) {
310
return "", errUnimplemented
311
}
312
313
func (l *LimaKrunkitDriver) Register(_ context.Context) error {
314
return nil
315
}
316
317
func (l *LimaKrunkitDriver) Unregister(_ context.Context) error {
318
return nil
319
}
320
321
func (l *LimaKrunkitDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) {
322
return nil, "unix", nil
323
}
324
325
func (l *LimaKrunkitDriver) AdditionalSetupForSSH(_ context.Context) error {
326
return nil
327
}
328
329