Repository: lima-vm/lima
Pull request #1735 (2023-08-12 09:44:27)

Add OS to lima.yaml for future guests
Avoid hardcoding "linux" everywhere, use OS instead (like Arch).

~~TODO: Add proper cidata template parameter for LIMA_CIDATA_HOME~~

There are some other hardcoded Linux or Systemd specific things too:

`/bin/bash`  or `/run`, those are not addressed here and are left as is.

----

Doing the OS refactoring in a separate PR, makes it easier to add OS

* #1509


Patch:
diff --git a/cmd/limactl/hostagent.go b/cmd/limactl/hostagent.go
index 009525dc..78e6b615 100644
--- a/cmd/limactl/hostagent.go
+++ b/cmd/limactl/hostagent.go
@@ -29,7 +29,7 @@ func newHostagentCommand() *cobra.Command {
 	hostagentCommand.Flags().StringP("pidfile", "p", "", "write pid to file")
 	hostagentCommand.Flags().String("socket", "", "hostagent socket")
 	hostagentCommand.Flags().Bool("run-gui", false, "run gui synchronously within hostagent")
-	hostagentCommand.Flags().String("nerdctl-archive", "", "local file path (not URL) of nerdctl-full-VERSION-linux-GOARCH.tar.gz")
+	hostagentCommand.Flags().String("nerdctl-archive", "", "local file path (not URL) of nerdctl-full-VERSION-GOOS-GOARCH.tar.gz")
 	return hostagentCommand
 }
 
diff --git a/docs/internal.md b/docs/internal.md
index c785c08e..c0c65589 100644
--- a/docs/internal.md
+++ b/docs/internal.md
@@ -133,7 +133,7 @@ The directory contains the following files:
 - `network-config`: [Cloud-init Networking Config Version 2](https://cloudinit.readthedocs.io/en/latest/topics/network-config-format-v2.html)
 - `lima.env`: The `LIMA_CIDATA_*` environment variables (see below) available during `boot.sh` processing
 - `lima-guestagent`: Lima guest agent binary
-- `nerdctl-full.tgz`: [`nerdctl-full-<VERSION>-linux-<ARCH>.tar.gz`](https://github.com/containerd/nerdctl/releases)
+- `nerdctl-full.tgz`: [`nerdctl-full-<VERSION>-<OS>-<ARCH>.tar.gz`](https://github.com/containerd/nerdctl/releases)
 - `boot.sh`: Boot script
 - `boot/*`: Boot script modules
 - `util/*`: Utility command scripts, executed in the boot script modules
diff --git a/examples/default.yaml b/examples/default.yaml
index e8dfbb94..140888dd 100644
--- a/examples/default.yaml
+++ b/examples/default.yaml
@@ -11,6 +11,10 @@
 # 🟢 Builtin default: "qemu"
 vmType: null
 
+# OS: "Linux".
+# 🟢 Builtin default: "Linux"
+os: null
+
 # Arch: "default", "x86_64", "aarch64".
 # 🟢 Builtin default: "default" (corresponds to the host architecture)
 arch: null
diff --git a/go.mod b/go.mod
index f70beda4..17881bab 100644
--- a/go.mod
+++ b/go.mod
@@ -22,8 +22,9 @@ require (
 	github.com/goccy/go-yaml v1.11.0
 	github.com/google/go-cmp v0.5.9
 	github.com/gorilla/mux v1.8.0
+	github.com/hashicorp/go-multierror v1.1.1
 	github.com/lima-vm/go-qcow2reader v0.1.1
-	github.com/lima-vm/sshocker v0.3.3
+	github.com/lima-vm/sshocker v0.3.2
 	github.com/lithammer/dedent v1.1.0
 	github.com/mattn/go-isatty v0.0.19
 	github.com/mattn/go-shellwords v1.0.12
@@ -74,6 +75,7 @@ require (
 	github.com/google/gnostic v0.5.7-v3refs // indirect
 	github.com/google/gofuzz v1.2.0 // indirect
 	github.com/google/gopacket v1.1.19 // indirect
+	github.com/hashicorp/errwrap v1.1.0 // indirect
 	github.com/imdario/mergo v0.3.13 // indirect
 	github.com/inconshreveable/mousetrap v1.1.0 // indirect
 	github.com/insomniacslk/dhcp v0.0.0-20220504074936-1ca156eafb9f // indirect
diff --git a/go.sum b/go.sum
index 0375f5aa..b638d134 100644
--- a/go.sum
+++ b/go.sum
@@ -137,6 +137,11 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
 github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
+github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
+github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
@@ -174,8 +179,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
 github.com/lima-vm/go-qcow2reader v0.1.1 h1:UegxTAU4usoSzKLDqhbaNmmV4Ec4qhQO11QYeLLy7Jc=
 github.com/lima-vm/go-qcow2reader v0.1.1/go.mod h1:e3p29BzLT8hNh4jbLezdFAHU4eBijf0bm5GilStCRKE=
-github.com/lima-vm/sshocker v0.3.3 h1:ijdrW5Hf9+wkyUgE+wklKJjYeCbI0Q3Ipj2Yx+l0Mi0=
-github.com/lima-vm/sshocker v0.3.3/go.mod h1:UJ5lcP8XdVdSKxpRn9dvwhpJxwM2L2FQ0lCVkT4Azzg=
+github.com/lima-vm/sshocker v0.3.2 h1:o0WqVzcpt6mzVCuqtS3N3O8kwTx6X4SLr4h7YaRISuE=
+github.com/lima-vm/sshocker v0.3.2/go.mod h1:9SWN6wob210VM6oJkkzvWQOlHSp/rQLB+0fSEc92zig=
 github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2 h1:DZMFueDbfz6PNc1GwDRA8+6lBx1TB9UnxDQliCqR73Y=
 github.com/linuxkit/virtsock v0.0.0-20220523201153-1a23e78aa7a2/go.mod h1:SWzULI85WerrFt3u+nIm5F9l7EvxZTKQvd0InF3nmgM=
 github.com/lithammer/dedent v1.1.0 h1:VNzHMVCBNG1j0fh3OrsFRkVUwStdDArbgBWoPAffktY=
diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go
index 82dc2578..c5237058 100644
--- a/pkg/cidata/cidata.go
+++ b/pkg/cidata/cidata.go
@@ -303,7 +303,7 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
 		}
 	}
 
-	guestAgentBinary, err := GuestAgentBinary(*y.Arch)
+	guestAgentBinary, err := GuestAgentBinary(*y.OS, *y.Arch)
 	if err != nil {
 		return err
 	}
@@ -329,7 +329,10 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
 	return iso9660util.Write(filepath.Join(instDir, filenames.CIDataISO), "cidata", layout)
 }
 
-func GuestAgentBinary(arch string) (io.ReadCloser, error) {
+func GuestAgentBinary(ostype limayaml.OS, arch limayaml.Arch) (io.ReadCloser, error) {
+	if ostype == "" {
+		return nil, errors.New("os must be set")
+	}
 	if arch == "" {
 		return nil, errors.New("arch must be set")
 	}
@@ -337,7 +340,7 @@ func GuestAgentBinary(arch string) (io.ReadCloser, error) {
 	if err != nil {
 		return nil, err
 	}
-	gaPath := filepath.Join(dir, "lima-guestagent.Linux-"+arch)
+	gaPath := filepath.Join(dir, "lima-guestagent."+ostype+"-"+arch)
 	return os.Open(gaPath)
 }
 
diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go
index 76e3b8b6..e17b280b 100644
--- a/pkg/hostagent/hostagent.go
+++ b/pkg/hostagent/hostagent.go
@@ -20,6 +20,7 @@ import (
 	"github.com/lima-vm/lima/pkg/driverutil"
 	"github.com/lima-vm/lima/pkg/networks"
 
+	"github.com/hashicorp/go-multierror"
 	"github.com/lima-vm/lima/pkg/cidata"
 	guestagentapi "github.com/lima-vm/lima/pkg/guestagent/api"
 	guestagentclient "github.com/lima-vm/lima/pkg/guestagent/api/client"
@@ -401,7 +402,7 @@ func (a *HostAgent) startHostAgentRoutines(ctx context.Context) error {
 	})
 	var mErr error
 	if err := a.waitForRequirements("essential", a.essentialRequirements()); err != nil {
-		mErr = errors.Join(mErr, err)
+		mErr = multierror.Append(mErr, err)
 	}
 	if *a.y.SSH.ForwardAgent {
 		faScript := `#!/bin/bash
@@ -413,19 +414,19 @@ sudo chown -R "${USER}" /run/host-services`
 		stdout, stderr, err := ssh.ExecuteScript("127.0.0.1", a.sshLocalPort, a.sshConfig, faScript, faDesc)
 		logrus.Debugf("stdout=%q, stderr=%q, err=%v", stdout, stderr, err)
 		if err != nil {
-			mErr = errors.Join(mErr, fmt.Errorf("stdout=%q, stderr=%q: %w", stdout, stderr, err))
+			mErr = multierror.Append(mErr, fmt.Errorf("stdout=%q, stderr=%q: %w", stdout, stderr, err))
 		}
 	}
 	if *a.y.MountType == limayaml.REVSSHFS {
 		mounts, err := a.setupMounts()
 		if err != nil {
-			mErr = errors.Join(mErr, err)
+			mErr = multierror.Append(mErr, err)
 		}
 		a.onClose = append(a.onClose, func() error {
 			var unmountMErr error
 			for _, m := range mounts {
 				if unmountErr := m.close(); unmountErr != nil {
-					unmountMErr = errors.Join(unmountMErr, unmountErr)
+					unmountMErr = multierror.Append(unmountMErr, unmountErr)
 				}
 			}
 			return unmountMErr
@@ -437,12 +438,12 @@ sudo chown -R "${USER}" /run/host-services`
 			for _, d := range a.y.AdditionalDisks {
 				disk, inspectErr := store.InspectDisk(d.Name)
 				if inspectErr != nil {
-					unlockMErr = errors.Join(unlockMErr, inspectErr)
+					unlockMErr = multierror.Append(unlockMErr, inspectErr)
 					continue
 				}
 				logrus.Infof("Unmounting disk %q", disk.Name)
 				if unlockErr := disk.Unlock(); unlockErr != nil {
-					unlockMErr = errors.Join(unlockMErr, unlockErr)
+					unlockMErr = multierror.Append(unlockMErr, unlockErr)
 				}
 			}
 			return unlockMErr
@@ -450,15 +451,15 @@ sudo chown -R "${USER}" /run/host-services`
 	}
 	go a.watchGuestAgentEvents(ctx)
 	if err := a.waitForRequirements("optional", a.optionalRequirements()); err != nil {
-		mErr = errors.Join(mErr, err)
+		mErr = multierror.Append(mErr, err)
 	}
 	if err := a.waitForRequirements("final", a.finalRequirements()); err != nil {
-		mErr = errors.Join(mErr, err)
+		mErr = multierror.Append(mErr, err)
 	}
 	// Copy all config files _after_ the requirements are done
 	for _, rule := range a.y.CopyToHost {
 		if err := copyToHost(ctx, a.sshConfig, a.sshLocalPort, rule.HostFile, rule.GuestFile); err != nil {
-			mErr = errors.Join(mErr, err)
+			mErr = multierror.Append(mErr, err)
 		}
 	}
 	return mErr
@@ -470,7 +471,7 @@ func (a *HostAgent) close() error {
 	for i := len(a.onClose) - 1; i >= 0; i-- {
 		f := a.onClose[i]
 		if err := f(); err != nil {
-			mErr = errors.Join(mErr, err)
+			mErr = multierror.Append(mErr, err)
 		}
 	}
 	return mErr
@@ -499,12 +500,12 @@ func (a *HostAgent) watchGuestAgentEvents(ctx context.Context) {
 				local := hostAddress(rule, guestagentapi.IPPort{})
 				// using ctx.Background() because ctx has already been cancelled
 				if err := forwardSSH(context.Background(), a.sshConfig, a.sshLocalPort, local, rule.GuestSocket, verbCancel, rule.Reverse); err != nil {
-					mErr = errors.Join(mErr, err)
+					mErr = multierror.Append(mErr, err)
 				}
 			}
 		}
 		if err := forwardSSH(context.Background(), a.sshConfig, a.sshLocalPort, localUnix, remoteUnix, verbCancel, false); err != nil {
-			mErr = errors.Join(mErr, err)
+			mErr = multierror.Append(mErr, err)
 		}
 		return mErr
 	})
diff --git a/pkg/hostagent/mount.go b/pkg/hostagent/mount.go
index 2a3690ec..712965d8 100644
--- a/pkg/hostagent/mount.go
+++ b/pkg/hostagent/mount.go
@@ -1,10 +1,11 @@
 package hostagent
 
 import (
-	"errors"
 	"fmt"
 	"os"
 
+	"github.com/hashicorp/go-multierror"
+
 	"github.com/lima-vm/lima/pkg/limayaml"
 	"github.com/lima-vm/lima/pkg/localpathutil"
 	"github.com/lima-vm/sshocker/pkg/reversesshfs"
@@ -23,7 +24,7 @@ func (a *HostAgent) setupMounts() ([]*mount, error) {
 	for _, f := range a.y.Mounts {
 		m, err := a.setupMount(f)
 		if err != nil {
-			mErr = errors.Join(mErr, err)
+			mErr = multierror.Append(mErr, err)
 			continue
 		}
 		res = append(res, m)
diff --git a/pkg/hostagent/requirements.go b/pkg/hostagent/requirements.go
index 0e92049f..cf0ff354 100644
--- a/pkg/hostagent/requirements.go
+++ b/pkg/hostagent/requirements.go
@@ -1,10 +1,10 @@
 package hostagent
 
 import (
-	"errors"
 	"fmt"
 	"time"
 
+	"github.com/hashicorp/go-multierror"
 	"github.com/lima-vm/lima/pkg/limayaml"
 	"github.com/lima-vm/sshocker/pkg/ssh"
 	"github.com/sirupsen/logrus"
@@ -28,10 +28,10 @@ func (a *HostAgent) waitForRequirements(label string, requirements []requirement
 			}
 			if req.fatal {
 				logrus.Infof("No further %s requirements will be checked", label)
-				return errors.Join(mErr, fmt.Errorf("failed to satisfy the %s requirement %d of %d %q: %s; skipping further checks: %w", label, i+1, len(requirements), req.description, req.debugHint, err))
+				return multierror.Append(mErr, fmt.Errorf("failed to satisfy the %s requirement %d of %d %q: %s; skipping further checks: %w", label, i+1, len(requirements), req.description, req.debugHint, err))
 			}
 			if j == retries-1 {
-				mErr = errors.Join(mErr, fmt.Errorf("failed to satisfy the %s requirement %d of %d %q: %s: %w", label, i+1, len(requirements), req.description, req.debugHint, err))
+				mErr = multierror.Append(mErr, fmt.Errorf("failed to satisfy the %s requirement %d of %d %q: %s: %w", label, i+1, len(requirements), req.description, req.debugHint, err))
 				break retryLoop
 			}
 			time.Sleep(10 * time.Second)
diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go
index c9960401..910fe21b 100644
--- a/pkg/limayaml/defaults.go
+++ b/pkg/limayaml/defaults.go
@@ -39,17 +39,17 @@ const (
 
 func defaultContainerdArchives() []File {
 	const nerdctlVersion = "1.5.0"
-	location := func(goarch string) string {
-		return "https://github.com/containerd/nerdctl/releases/download/v" + nerdctlVersion + "/nerdctl-full-" + nerdctlVersion + "-linux-" + goarch + ".tar.gz"
+	location := func(goos string, goarch string) string {
+		return "https://github.com/containerd/nerdctl/releases/download/v" + nerdctlVersion + "/nerdctl-full-" + nerdctlVersion + "-" + goos + "-" + goarch + ".tar.gz"
 	}
 	return []File{
 		{
-			Location: location("amd64"),
+			Location: location("linux", "amd64"),
 			Arch:     X8664,
 			Digest:   "sha256:3f8c494e3c6a265fe2a3c41ef9d6bc859eeeb22095b3353d3558d8120833a23a",
 		},
 		{
-			Location: location("arm64"),
+			Location: location("linux", "arm64"),
 			Arch:     AARCH64,
 			Digest:   "sha256:32a2537e0a80e1493b5934ca56c3e237466606a1b720aef23b9c0a7fc3303bdb",
 		},
@@ -134,6 +134,13 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
 		y.VMType = o.VMType
 	}
 	y.VMType = pointer.String(ResolveVMType(y.VMType))
+	if y.OS == nil {
+		y.OS = d.OS
+	}
+	if o.OS != nil {
+		y.OS = o.OS
+	}
+	y.OS = pointer.String(ResolveOS(y.OS))
 	if y.Arch == nil {
 		y.Arch = d.Arch
 	}
@@ -768,6 +775,16 @@ func FillCopyToHostDefaults(rule *CopyToHost, instDir string) {
 	}
 }
 
+func NewOS(osname string) OS {
+	switch osname {
+	case "linux":
+		return LINUX
+	default:
+		logrus.Warnf("Unknown os: %s", osname)
+		return osname
+	}
+}
+
 func goarm() int {
 	if runtime.GOOS != "linux" {
 		return 0
@@ -824,6 +841,13 @@ func ResolveVMType(s *string) VMType {
 	return NewVMType(*s)
 }
 
+func ResolveOS(s *string) OS {
+	if s == nil || *s == "" || *s == "default" {
+		return NewOS("linux")
+	}
+	return *s
+}
+
 func ResolveArch(s *string) Arch {
 	if s == nil || *s == "" || *s == "default" {
 		return NewArch(runtime.GOARCH)
diff --git a/pkg/limayaml/limayaml.go b/pkg/limayaml/limayaml.go
index 1bd545fc..2f530a23 100644
--- a/pkg/limayaml/limayaml.go
+++ b/pkg/limayaml/limayaml.go
@@ -8,6 +8,7 @@ import (
 
 type LimaYAML struct {
 	VMType             *VMType         `yaml:"vmType,omitempty" json:"vmType,omitempty"`
+	OS                 *OS             `yaml:"os,omitempty" json:"os,omitempty"`
 	Arch               *Arch           `yaml:"arch,omitempty" json:"arch,omitempty"`
 	Images             []Image         `yaml:"images" json:"images"` // REQUIRED
 	CPUType            map[Arch]string `yaml:"cpuType,omitempty" json:"cpuType,omitempty"`
@@ -39,11 +40,14 @@ type LimaYAML struct {
 	Rosetta           Rosetta        `yaml:"rosetta,omitempty" json:"rosetta,omitempty"`
 }
 
+type OS = string
 type Arch = string
 type MountType = string
 type VMType = string
 
 const (
+	LINUX OS = "Linux"
+
 	X8664   Arch = "x86_64"
 	AARCH64 Arch = "aarch64"
 	ARMV7L  Arch = "armv7l"
diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go
index 32ccd5b2..47a93520 100644
--- a/pkg/limayaml/validate.go
+++ b/pkg/limayaml/validate.go
@@ -42,6 +42,11 @@ func validateFileObject(f File, fieldName string) error {
 }
 
 func Validate(y LimaYAML, warn bool) error {
+	switch *y.OS {
+	case LINUX:
+	default:
+		return fmt.Errorf("field `os` must be %q; got %q", LINUX, *y.OS)
+	}
 	switch *y.Arch {
 	case X8664, AARCH64, ARMV7L, RISCV64:
 	default:
diff --git a/pkg/qemu/qemu_driver.go b/pkg/qemu/qemu_driver.go
index 6bcc299c..02052595 100644
--- a/pkg/qemu/qemu_driver.go
+++ b/pkg/qemu/qemu_driver.go
@@ -20,6 +20,7 @@ import (
 
 	"github.com/digitalocean/go-qemu/qmp"
 	"github.com/digitalocean/go-qemu/qmp/raw"
+	"github.com/hashicorp/go-multierror"
 	"github.com/lima-vm/lima/pkg/driver"
 	"github.com/lima-vm/lima/pkg/limayaml"
 	"github.com/lima-vm/lima/pkg/networks/usernet"
@@ -285,7 +286,7 @@ func (l *LimaQemuDriver) killVhosts() error {
 	var mErr error
 	for i, vhost := range l.vhostCmds {
 		if err := vhost.Process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) {
-			mErr = errors.Join(mErr, fmt.Errorf("Failed to kill virtiofsd instance #%d: %w", i, err))
+			mErr = multierror.Append(mErr, fmt.Errorf("Failed to kill virtiofsd instance #%d: %w", i, err))
 		}
 	}
 
@@ -323,7 +324,7 @@ func (l *LimaQemuDriver) shutdownQEMU(ctx context.Context, timeout time.Duration
 	case qWaitErr := <-qWaitCh:
 		logrus.WithError(qWaitErr).Info("QEMU has exited")
 		l.removeVNCFiles()
-		return errors.Join(qWaitErr, l.killVhosts())
+		return multierror.Append(qWaitErr, l.killVhosts()).ErrorOrNil()
 	case <-deadline:
 	}
 	logrus.Warnf("QEMU did not exit in %v, forcibly killing QEMU", timeout)
@@ -344,7 +345,7 @@ func (l *LimaQemuDriver) killQEMU(_ context.Context, _ time.Duration, qCmd *exec
 	qemuPIDPath := filepath.Join(l.Instance.Dir, filenames.PIDFile(*l.Yaml.VMType))
 	_ = os.RemoveAll(qemuPIDPath)
 	l.removeVNCFiles()
-	return errors.Join(qWaitErr, l.killVhosts())
+	return multierror.Append(qWaitErr, l.killVhosts()).ErrorOrNil()
 }
 
 func newUsernetClient(nwName string) *usernet.Client {
diff --git a/pkg/start/start.go b/pkg/start/start.go
index d500cb04..acecfde3 100644
--- a/pkg/start/start.go
+++ b/pkg/start/start.go
@@ -28,7 +28,7 @@ import (
 // to be running before timing out.
 const DefaultWatchHostAgentEventsTimeout = 10 * time.Minute
 
-// ensureNerdctlArchiveCache prefetches the nerdctl-full-VERSION-linux-GOARCH.tar.gz archive
+// ensureNerdctlArchiveCache prefetches the nerdctl-full-VERSION-GOOS-GOARCH.tar.gz archive
 // into the cache before launching the hostagent process, so that we can show the progress in tty.
 // https://github.com/lima-vm/lima/issues/326
 func ensureNerdctlArchiveCache(y *limayaml.LimaYAML) (string, error) {
diff --git a/pkg/usrlocalsharelima/usrlocalsharelima.go b/pkg/usrlocalsharelima/usrlocalsharelima.go
index df508712..60792903 100644
--- a/pkg/usrlocalsharelima/usrlocalsharelima.go
+++ b/pkg/usrlocalsharelima/usrlocalsharelima.go
@@ -27,6 +27,7 @@ func Dir() (string, error) {
 		}
 	}
 
+	ostype := limayaml.NewOS("linux")
 	arch := limayaml.NewArch(runtime.GOARCH)
 	if arch == "" {
 		return "", fmt.Errorf("failed to get arch for %q", runtime.GOARCH)
@@ -40,12 +41,12 @@ func Dir() (string, error) {
 		// - self:  /Applications/Lima.app/Contents/MacOS/limactl
 		// - agent: /Applications/Lima.app/Contents/MacOS/lima-guestagent.Linux-x86_64
 		// - dir:   /Applications/Lima.app/Contents/MacOS
-		filepath.Join(selfDir, "lima-guestagent.Linux-"+arch),
+		filepath.Join(selfDir, "lima-guestagent."+ostype+"-"+arch),
 		// candidate 1:
 		// - self:  /usr/local/bin/limactl
 		// - agent: /usr/local/share/lima/lima-guestagent.Linux-x86_64
 		// - dir:   /usr/local/share/lima
-		filepath.Join(selfDirDir, "share/lima/lima-guestagent.Linux-"+arch),
+		filepath.Join(selfDirDir, "share/lima/lima-guestagent."+ostype+"-"+arch),
 		// TODO: support custom path
 	}
 	for _, gaCandidate := range gaCandidates {
@@ -56,6 +57,6 @@ func Dir() (string, error) {
 		}
 	}
 
-	return "", fmt.Errorf("failed to find \"lima-guestagent.Linux-%s\" binary for %q, attempted %v",
-		arch, self, gaCandidates)
+	return "", fmt.Errorf("failed to find \"lima-guestagent.%s-%s\" binary for %q, attempted %v",
+		ostype, arch, self, gaCandidates)
 }


Test patch:
diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go
index 01672b14..a9e37366 100644
--- a/pkg/limayaml/defaults_test.go
+++ b/pkg/limayaml/defaults_test.go
@@ -61,6 +61,7 @@ func TestFillDefault(t *testing.T) {
 	// Builtin default values
 	builtin := LimaYAML{
 		VMType: pointer.String("qemu"),
+		OS:     pointer.String(LINUX),
 		Arch:   pointer.String(arch),
 		CPUType: map[Arch]string{
 			AARCH64: "cortex-a72",
@@ -266,6 +267,7 @@ func TestFillDefault(t *testing.T) {
 	// Choose values that are different from the "builtin" defaults
 	d = LimaYAML{
 		VMType: pointer.String("vz"),
+		OS:     pointer.String("unknown"),
 		Arch:   pointer.String("unknown"),
 		CPUType: map[Arch]string{
 			AARCH64: "arm64",
@@ -443,6 +445,7 @@ func TestFillDefault(t *testing.T) {
 
 	o = LimaYAML{
 		VMType: pointer.String("qemu"),
+		OS:     pointer.String(LINUX),
 		Arch:   pointer.String(arch),
 		CPUType: map[Arch]string{
 			AARCH64: "uber-arm",