Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cmd/lima-driver-ac/main_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package main

import (
"github.com/lima-vm/lima/v2/pkg/driver/ac"
"github.com/lima-vm/lima/v2/pkg/driver/external/server"
)

// To be used as an external driver for Lima.
func main() {
server.Serve(ac.New())
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
# SPDX-License-Identifier: Apache-2.0

# This script replaces the cloud-init functionality of creating a user and setting its SSH keys
# when using a WSL2 VM.
[ "$LIMA_CIDATA_VMTYPE" = "wsl2" ] || exit 0
# when using a WSL2 or AC VM.
[ "$LIMA_CIDATA_VMTYPE" = "wsl2" ] || [ "$LIMA_CIDATA_VMTYPE" = "ac" ] || exit 0
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The provision scripts should not be aware of the VM driver names

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is currently necessary because drivers don't yet have the capability to provide their own boot scripts. We briefly touched on this on Slack in the #lima-gsoc channel1. @unsuman is aware.

Footnotes

  1. https://cloud-native.slack.com/archives/C08KKELMQ92/p1755330101367019?thread_ts=1755325617.281999&cid=C08KKELMQ92

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option is to introduce a common feature variable ("container" ?), that all three drivers can set.


# create user
# shellcheck disable=SC2153
Expand All @@ -22,4 +22,4 @@ chmod 600 "${LIMA_CIDATA_HOME}"/.ssh/authorized_keys
echo "${LIMA_CIDATA_USER} ALL=(ALL) NOPASSWD:ALL" | tee -a /etc/sudoers.d/99_lima_sudoers

# symlink CIDATA to the hardcoded path for requirement checks (TODO: make this not hardcoded)
ln -sfFn "${LIMA_CIDATA_MNT}" /mnt/lima-cidata
[ "$LIMA_CIDATA_MNT" = "/mnt/lima-cidata" ] || ln -sfFn "${LIMA_CIDATA_MNT}" /mnt/lima-cidata
2 changes: 1 addition & 1 deletion pkg/cidata/cidata.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ func GenerateISO9660(instDir, name string, instConfig *limayaml.LimaYAML, udpDNS
})
}

if args.VMType == limayaml.WSL2 {
if args.VMType == limayaml.WSL2 || args.VMType == limayaml.AC {
layout = append(layout, iso9660util.Entry{
Path: "ssh_authorized_keys",
Reader: strings.NewReader(strings.Join(args.SSHPubKeys, "\n")),
Expand Down
243 changes: 243 additions & 0 deletions pkg/driver/ac/ac_driver_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package ac

import (
"context"
"fmt"
"net"
"regexp"

"github.com/sirupsen/logrus"

"github.com/lima-vm/lima/v2/pkg/driver"
"github.com/lima-vm/lima/v2/pkg/limayaml"
"github.com/lima-vm/lima/v2/pkg/reflectutil"
"github.com/lima-vm/lima/v2/pkg/store"
)

var knownYamlProperties = []string{
"Arch",
"Containerd",
"CopyToHost",
"CPUType",
"Disk",
"DNS",
"Env",
"HostResolver",
"Images",
"Message",
"Mounts",
"MountType",
"Param",
"Plain",
"PortForwards",
"Probes",
"PropagateProxyEnv",
"Provision",
"SSH",
"VMType",
}

const Enabled = true

type LimaAcDriver struct {
Instance *store.Instance

SSHLocalPort int
vSockPort int
virtioPort string
}

var _ driver.Driver = (*LimaAcDriver)(nil)

func New() *LimaAcDriver {
return &LimaAcDriver{
vSockPort: 0,
virtioPort: "",
}
}

func (l *LimaAcDriver) Configure(inst *store.Instance) *driver.ConfiguredDriver {
l.Instance = inst
l.SSHLocalPort = inst.SSHLocalPort

return &driver.ConfiguredDriver{
Driver: l,
}
}

func (l *LimaAcDriver) Validate() error {
if *l.Instance.Config.MountType != limayaml.VIRTIOFS && *l.Instance.Config.MountType != limayaml.REVSSHFS {
return fmt.Errorf("field `mountType` must be %q or %q for AC driver, got %q", limayaml.VIRTIOFS, limayaml.REVSSHFS, *l.Instance.Config.MountType)
}
// TODO: revise this list for AC
if unknown := reflectutil.UnknownNonEmptyFields(l.Instance.Config, knownYamlProperties...); len(unknown) > 0 {
logrus.Warnf("Ignoring: vmType %s: %+v", *l.Instance.Config.VMType, unknown)
}

if !limayaml.IsNativeArch(*l.Instance.Config.Arch) {
return fmt.Errorf("unsupported arch: %q", *l.Instance.Config.Arch)
}

// TODO: real filetype checks
tarFileRegex := regexp.MustCompile(`.*tar\.*`)
for i, image := range l.Instance.Config.Images {
if unknown := reflectutil.UnknownNonEmptyFields(image, "File"); len(unknown) > 0 {
logrus.Warnf("Ignoring: vmType %s: images[%d]: %+v", *l.Instance.Config.VMType, i, unknown)
}
match := tarFileRegex.MatchString(image.Location)
if image.Arch == *l.Instance.Config.Arch && !match {
return fmt.Errorf("unsupported image type for vmType: %s, tarball root file system required: %q", *l.Instance.Config.VMType, image.Location)
}
}

for i, mount := range l.Instance.Config.Mounts {
if unknown := reflectutil.UnknownNonEmptyFields(mount); len(unknown) > 0 {
logrus.Warnf("Ignoring: vmType %s: mounts[%d]: %+v", *l.Instance.Config.VMType, i, unknown)
}
}

for i, network := range l.Instance.Config.Networks {
if unknown := reflectutil.UnknownNonEmptyFields(network); len(unknown) > 0 {
logrus.Warnf("Ignoring: vmType %s: networks[%d]: %+v", *l.Instance.Config.VMType, i, unknown)
}
}

audioDevice := *l.Instance.Config.Audio.Device
if audioDevice != "" {
logrus.Warnf("Ignoring: vmType %s: `audio.device`: %+v", *l.Instance.Config.VMType, audioDevice)
}

return nil
}

func (l *LimaAcDriver) Start(ctx context.Context) (chan error, error) {
logrus.Infof("Starting AC VM")
status, err := store.GetAcStatus(l.Instance.Name)
if err != nil {
return nil, err
}

distroName := "lima-" + l.Instance.Name

if status == store.StatusUninitialized {
if err := EnsureFs(ctx, l.Instance); err != nil {
return nil, err
}
if err := initVM(ctx, l.Instance.Dir, distroName); err != nil {
return nil, err
}
cpus := l.Instance.CPUs
memory := int(l.Instance.Memory >> 20) // MiB
if err := registerVM(ctx, distroName, cpus, memory); err != nil {
return nil, err
}
}

errCh := make(chan error)

if err := startVM(ctx, distroName); err != nil {
return nil, err
}

if err := provisionVM(
ctx,
l.Instance.Dir,
l.Instance.Name,
distroName,
errCh,
); err != nil {
return nil, err
}

return errCh, err
}

func (l *LimaAcDriver) canRunGUI() bool {
return false
}

func (l *LimaAcDriver) RunGUI() error {
return fmt.Errorf("RunGUI is not supported for the given driver '%s' and display '%s'", "ac", *l.Instance.Config.Video.Display)
}

func (l *LimaAcDriver) Stop(ctx context.Context) error {
logrus.Info("Shutting down AC VM")
distroName := "lima-" + l.Instance.Name
return stopVM(ctx, distroName)
}

func (l *LimaAcDriver) Unregister(ctx context.Context) error {
distroName := "lima-" + l.Instance.Name
status, err := store.GetAcStatus(l.Instance.Name)
if err != nil {
return err
}
switch status {
case store.StatusRunning, store.StatusStopped, store.StatusBroken, store.StatusInstalling:
return unregisterVM(ctx, distroName)
}

logrus.Info("VM not registered, skipping unregistration")
return nil
}

// GuestAgentConn returns the guest agent connection, or nil (if forwarded by ssh).
func (l *LimaAcDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) {
return nil, "", nil
}

func (l *LimaAcDriver) Info() driver.Info {
var info driver.Info
if l.Instance != nil {
info.InstanceDir = l.Instance.Dir
}
info.DriverName = "ac"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ac" isn't a descriptive name.
Can't we just call it "apple-container" ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are breaking @afbjorklund's pun about AC/DC (Apple Container/Debian Container). 🤣

Copy link
Member Author

@afbjorklund afbjorklund Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we will abandon those before merging, also they didn't rhyme with "WSL2" anyway (WC? 🚽)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We use "vz" for Virtualization.framework, so I thought we could use "ac" for Apple Container

Copy link
Member Author

@afbjorklund afbjorklund Aug 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using "cz" is a bit non-obvious, and there is no Containerization.framework - only a Swift library...

/usr/local/bin/container:
	/usr/lib/libc++.1.dylib (compatibility version 1.0.0, current version 2000.62.0)
	/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.12)
	/usr/lib/libbz2.1.0.dylib (compatibility version 1.0.0, current version 1.0.8)
	/usr/lib/liblzma.5.dylib (compatibility version 6.0.0, current version 6.3.0)
	/usr/lib/libarchive.2.dylib (compatibility version 9.0.0, current version 9.2.0)
	/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
	/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1356.0.0)
	/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 4037.0.0)
	/System/Library/Frameworks/CryptoKit.framework/Versions/A/CryptoKit (compatibility version 1.0.0, current version 1.0.0)
	/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation (compatibility version 300.0.0, current version 4037.0.0)
	/System/Library/Frameworks/Network.framework/Versions/A/Network (compatibility version 1.0.0, current version 5569.0.219)
	/System/Library/Frameworks/Security.framework/Versions/A/Security (compatibility version 1.0.0, current version 61901.0.56)
	/System/Library/Frameworks/Virtualization.framework/Versions/A/Virtualization (compatibility version 1.0.0, current version 259.0.0)
	/System/Library/Frameworks/vmnet.framework/Versions/A/vmnet (compatibility version 1.0.0, current version 1.0.0, weak)
	/usr/lib/libobjc.A.dylib (compatibility version 1.0.0, current version 228.0.0)
	/usr/lib/swift/libswiftCore.dylib (compatibility version 0.0.0, current version 0.0.0)
	/usr/lib/swift/libswiftCoreFoundation.dylib (compatibility version 1.0.0, current version 120.100.0, weak)
	/usr/lib/swift/libswiftCoreImage.dylib (compatibility version 1.0.0, current version 2.2.0, weak)
	/usr/lib/swift/libswiftDarwin.dylib (compatibility version 1.0.0, current version 347.0.12)
	/usr/lib/swift/libswiftDispatch.dylib (compatibility version 1.0.0, current version 56.0.0)
	/usr/lib/swift/libswiftIOKit.dylib (compatibility version 1.0.0, current version 1.0.0, weak)
	/usr/lib/swift/libswiftMetal.dylib (compatibility version 1.0.0, current version 370.57.0, weak)
	/usr/lib/swift/libswiftOSLog.dylib (compatibility version 1.0.0, current version 8.0.0, weak)
	/usr/lib/swift/libswiftObjectiveC.dylib (compatibility version 1.0.0, current version 948.0.0, weak)
	/usr/lib/swift/libswiftQuartzCore.dylib (compatibility version 1.0.0, current version 5.0.0, weak)
	/usr/lib/swift/libswiftSynchronization.dylib (compatibility version 1.0.0, current version 0.0.0)
	/usr/lib/swift/libswiftUniformTypeIdentifiers.dylib (compatibility version 1.0.0, current version 874.0.0, weak)
	/usr/lib/swift/libswiftXPC.dylib (compatibility version 1.0.0, current version 105.0.13)
	/usr/lib/swift/libswift_Builtin_float.dylib (compatibility version 1.0.0, current version 0.0.0, weak)
	/usr/lib/swift/libswift_Concurrency.dylib (compatibility version 1.0.0, current version 0.0.0)
	/usr/lib/swift/libswift_StringProcessing.dylib (compatibility version 1.0.0, current version 0.0.0)
	/usr/lib/swift/libswiftos.dylib (compatibility version 1.0.0, current version 1076.0.0)
	/usr/lib/swift/libswiftsimd.dylib (compatibility version 1.0.0, current version 23.0.0, weak)
	/usr/lib/swift/libswift_errno.dylib (compatibility version 1.0.0, current version 347.0.12)
	/usr/lib/swift/libswift_stdio.dylib (compatibility version 1.0.0, current version 347.0.12)
	/usr/lib/swift/libswift_signal.dylib (compatibility version 1.0.0, current version 347.0.12)

The use of "hvf" for Hypervisor.framework and "vz" for Virtualization.framework is confusing enough

info.CanRunGUI = l.canRunGUI()
info.VirtioPort = l.virtioPort
info.VsockPort = l.vSockPort
return info
}

func (l *LimaAcDriver) Initialize(_ context.Context) error {
return nil
}

func (l *LimaAcDriver) CreateDisk(_ context.Context) error {
return nil
}

func (l *LimaAcDriver) Register(_ context.Context) error {
return nil
}

func (l *LimaAcDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
return nil
}

func (l *LimaAcDriver) DisplayConnection(_ context.Context) (string, error) {
return "", nil
}

func (l *LimaAcDriver) CreateSnapshot(_ context.Context, _ string) error {
return errUnimplemented
}

func (l *LimaAcDriver) ApplySnapshot(_ context.Context, _ string) error {
return errUnimplemented
}

func (l *LimaAcDriver) DeleteSnapshot(_ context.Context, _ string) error {
return errUnimplemented
}

func (l *LimaAcDriver) ListSnapshots(_ context.Context) (string, error) {
return "", errUnimplemented
}

func (l *LimaAcDriver) ForwardGuestAgent() bool {
// If driver is not providing, use host agent
return l.vSockPort == 0 && l.virtioPort == ""
}
8 changes: 8 additions & 0 deletions pkg/driver/ac/errors_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package ac

import "errors"

var errUnimplemented = errors.New("unimplemented")
40 changes: 40 additions & 0 deletions pkg/driver/ac/fs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: Copyright The Lima Authors
// SPDX-License-Identifier: Apache-2.0

package ac

import (
"context"
"errors"
"os"
"path/filepath"

"github.com/sirupsen/logrus"

"github.com/lima-vm/lima/v2/pkg/fileutils"
"github.com/lima-vm/lima/v2/pkg/store"
"github.com/lima-vm/lima/v2/pkg/store/filenames"
)

// EnsureFs downloads the root fs.
func EnsureFs(ctx context.Context, inst *store.Instance) error {
baseDisk := filepath.Join(inst.Dir, filenames.BaseDisk)
if _, err := os.Stat(baseDisk); errors.Is(err, os.ErrNotExist) {
var ensuredBaseDisk bool
errs := make([]error, len(inst.Config.Images))
for i, f := range inst.Config.Images {
if _, err := fileutils.DownloadFile(ctx, baseDisk, f.File, true, "the image", *inst.Config.Arch); err != nil {
errs[i] = err
continue
}
ensuredBaseDisk = true
break
}
if !ensuredBaseDisk {
return fileutils.Errors(errs)
}
}
logrus.Info("Download succeeded")

return nil
}
8 changes: 8 additions & 0 deletions pkg/driver/ac/lima-init.TEMPLATE
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash
set -eu
export LOG_FILE=/var/log/lima-init.log
exec > >(tee $LOG_FILE) 2>&1
ln -sf rtc0 /dev/rtc
chmod 666 /dev/fuse
export LIMA_CIDATA_MNT="{{.CIDataPath}}"
exec "$LIMA_CIDATA_MNT/boot.sh"
Loading
Loading