Repository: lima-vm/lima

Issue:
`limactl network` CLI for manipulating `~/.lima/_config/networks.yaml`
There should be the CLI for manipulating `~/.lima/_config/networks.yaml`.

e.g.
```console
$ limactl network list
NAME		MODE		SUBNET
user-v2	user-v2	192.168.104.0/24
shared	shared	192.168.105.0/24
bridged	bridged	-
host		host		192.168.106.0/24

$ limactl network create foo
{
  "name": "foo",
  "mode": "user-v2",
  "gateway": "192.168.107.1",
  "netmask": "255.255.255.0",
  "labels": null
}

$ limactl network delete foo
```

Support for the labels must be consistent with:
- https://github.com/lima-vm/lima/issues/3240

Discussion:

Solution patch:
diff --git a/cmd/limactl/completion.go b/cmd/limactl/completion.go
index f69005b9..f68f463b 100644
--- a/cmd/limactl/completion.go
+++ b/cmd/limactl/completion.go
@@ -4,10 +4,14 @@
 package main
 
 import (
+	"maps"
+	"net"
+	"slices"
 	"strings"
 
 	"github.com/spf13/cobra"
 
+	"github.com/lima-vm/lima/pkg/networks"
 	"github.com/lima-vm/lima/pkg/store"
 	"github.com/lima-vm/lima/pkg/templatestore"
 )
@@ -51,3 +55,24 @@ func bashCompleteDiskNames(_ *cobra.Command) ([]string, cobra.ShellCompDirective
 	}
 	return disks, cobra.ShellCompDirectiveNoFileComp
 }
+
+func bashCompleteNetworkNames(_ *cobra.Command) ([]string, cobra.ShellCompDirective) {
+	config, err := networks.LoadConfig()
+	if err != nil {
+		return nil, cobra.ShellCompDirectiveDefault
+	}
+	networks := slices.Sorted(maps.Keys(config.Networks))
+	return networks, cobra.ShellCompDirectiveNoFileComp
+}
+
+func bashFlagCompleteNetworkInterfaceNames(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
+	intf, err := net.Interfaces()
+	if err != nil {
+		return nil, cobra.ShellCompDirectiveDefault
+	}
+	var intfNames []string
+	for _, f := range intf {
+		intfNames = append(intfNames, f.Name)
+	}
+	return intfNames, cobra.ShellCompDirectiveNoFileComp
+}
diff --git a/cmd/limactl/disk.go b/cmd/limactl/disk.go
index 7f19b069..f6b035b1 100644
--- a/cmd/limactl/disk.go
+++ b/cmd/limactl/disk.go
@@ -141,11 +141,11 @@ $ limactl disk list
 	return diskListCommand
 }
 
-func diskMatches(diskName string, disks []string) []string {
+func nameMatches(nameName string, names []string) []string {
 	matches := []string{}
-	for _, disk := range disks {
-		if disk == diskName {
-			matches = append(matches, disk)
+	for _, name := range names {
+		if name == nameName {
+			matches = append(matches, name)
 		}
 	}
 	return matches
@@ -165,7 +165,7 @@ func diskListAction(cmd *cobra.Command, args []string) error {
 	disks := []string{}
 	if len(args) > 0 {
 		for _, arg := range args {
-			matches := diskMatches(arg, allDisks)
+			matches := nameMatches(arg, allDisks)
 			if len(matches) > 0 {
 				disks = append(disks, matches...)
 			} else {
diff --git a/cmd/limactl/main.go b/cmd/limactl/main.go
index a006f0bb..acfa4107 100644
--- a/cmd/limactl/main.go
+++ b/cmd/limactl/main.go
@@ -189,6 +189,7 @@ func newApp() *cobra.Command {
 		newRestartCommand(),
 		newSudoersCommand(),
 		newStartAtLoginCommand(),
+		newNetworkCommand(),
 	)
 
 	return rootCmd
diff --git a/cmd/limactl/network.go b/cmd/limactl/network.go
new file mode 100644
index 00000000..7fd32ff2
--- /dev/null
+++ b/cmd/limactl/network.go
@@ -0,0 +1,271 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package main
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"maps"
+	"net"
+	"os"
+	"slices"
+	"strings"
+	"text/tabwriter"
+
+	"github.com/sirupsen/logrus"
+	"github.com/spf13/cobra"
+
+	"github.com/lima-vm/lima/pkg/networks"
+	"github.com/lima-vm/lima/pkg/yqutil"
+)
+
+const networkCreateExample = `  Create a network:
+  $ limactl network create foo --gateway 192.168.42.1/24
+
+  Connect VM instances to the newly created network:
+  $ limactl create --network lima:foo --name vm1
+  $ limactl create --network lima:foo --name vm2
+`
+
+func newNetworkCommand() *cobra.Command {
+	networkCommand := &cobra.Command{
+		Use:     "network",
+		Short:   "Lima network management",
+		Example: networkCreateExample,
+		GroupID: advancedCommand,
+	}
+	networkCommand.AddCommand(
+		newNetworkListCommand(),
+		newNetworkCreateCommand(),
+		newNetworkDeleteCommand(),
+	)
+	return networkCommand
+}
+
+func newNetworkListCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:               "list",
+		Short:             "List networks",
+		Aliases:           []string{"ls"},
+		Args:              WrapArgsError(cobra.ArbitraryArgs),
+		RunE:              networkListAction,
+		ValidArgsFunction: networkBashComplete,
+	}
+	flags := cmd.Flags()
+	flags.Bool("json", false, "JSONify output")
+	return cmd
+}
+
+func networkListAction(cmd *cobra.Command, args []string) error {
+	flags := cmd.Flags()
+	jsonFormat, err := flags.GetBool("json")
+	if err != nil {
+		return err
+	}
+
+	config, err := networks.LoadConfig()
+	if err != nil {
+		return err
+	}
+
+	allNetworks := slices.Sorted(maps.Keys(config.Networks))
+
+	networks := []string{}
+	if len(args) > 0 {
+		for _, arg := range args {
+			matches := nameMatches(arg, allNetworks)
+			if len(matches) > 0 {
+				networks = append(networks, matches...)
+			} else {
+				logrus.Warnf("No network matching %v found.", arg)
+			}
+		}
+	} else {
+		networks = allNetworks
+	}
+
+	if jsonFormat {
+		w := cmd.OutOrStdout()
+		for _, name := range networks {
+			nw, ok := config.Networks[name]
+			if !ok {
+				logrus.Errorf("network %q does not exist", nw)
+				continue
+			}
+			j, err := json.Marshal(nw)
+			if err != nil {
+				return err
+			}
+			fmt.Fprintln(w, string(j))
+		}
+		return nil
+	}
+
+	w := tabwriter.NewWriter(cmd.OutOrStdout(), 4, 8, 4, ' ', 0)
+	fmt.Fprintln(w, "NAME\tMODE\tGATEWAY\tINTERFACE")
+	for _, name := range networks {
+		nw, ok := config.Networks[name]
+		if !ok {
+			logrus.Errorf("network %q does not exist", nw)
+			continue
+		}
+		gwStr := "-"
+		if nw.Gateway != nil {
+			gw := net.IPNet{
+				IP:   nw.Gateway,
+				Mask: net.IPMask(nw.NetMask),
+			}
+			gwStr = gw.String()
+		}
+		intfStr := "-"
+		if nw.Interface != "" {
+			intfStr = nw.Interface
+		}
+		fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", name, nw.Mode, gwStr, intfStr)
+	}
+	return w.Flush()
+}
+
+func newNetworkCreateCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:     "create NETWORK",
+		Short:   "Create a Lima network",
+		Example: networkCreateExample,
+		Args:    WrapArgsError(cobra.ExactArgs(1)),
+		RunE:    networkCreateAction,
+	}
+	flags := cmd.Flags()
+	flags.String("mode", networks.ModeUserV2, "mode")
+	_ = cmd.RegisterFlagCompletionFunc("mode", func(*cobra.Command, []string, string) ([]string, cobra.ShellCompDirective) {
+		return networks.Modes, cobra.ShellCompDirectiveNoFileComp
+	})
+	flags.String("gateway", "", "gateway, e.g., \"192.168.42.1/24\"")
+	flags.String("interface", "", "interface for bridged mode")
+	_ = cmd.RegisterFlagCompletionFunc("interface", bashFlagCompleteNetworkInterfaceNames)
+	return cmd
+}
+
+func networkCreateAction(cmd *cobra.Command, args []string) error {
+	name := args[0]
+	// LoadConfig ensures existence of networks.yaml
+	config, err := networks.LoadConfig()
+	if err != nil {
+		return err
+	}
+	if _, ok := config.Networks[name]; ok {
+		return fmt.Errorf("network %q already exists", name)
+	}
+
+	flags := cmd.Flags()
+	mode, err := flags.GetString("mode")
+	if err != nil {
+		return err
+	}
+
+	gateway, err := flags.GetString("gateway")
+	if err != nil {
+		return err
+	}
+
+	intf, err := flags.GetString("interface")
+	if err != nil {
+		return err
+	}
+
+	switch mode {
+	case networks.ModeBridged:
+		if gateway != "" {
+			return fmt.Errorf("network mode %q does not support specifying gateway", mode)
+		}
+		if intf == "" {
+			return fmt.Errorf("network mode %q requires specifying interface", mode)
+		}
+	default:
+		if gateway == "" {
+			return fmt.Errorf("network mode %q requires specifying gateway", mode)
+		}
+		if intf != "" {
+			return fmt.Errorf("network mode %q does not support specifying interface", mode)
+		}
+	}
+
+	if !strings.Contains(gateway, "/") {
+		gateway += "/24"
+	}
+	gwIP, gwMask, err := net.ParseCIDR(gateway)
+	if err != nil {
+		return fmt.Errorf("failed to parse CIDR %q: %w", gateway, err)
+	}
+	if gwIP.IsUnspecified() || gwIP.IsLoopback() {
+		return fmt.Errorf("invalid IP address: %v", gwIP)
+	}
+	gwMaskStr := "255.255.255.0"
+	if gwMask != nil {
+		gwMaskStr = net.IP(gwMask.Mask).String()
+	}
+	// TODO: check IP range collision
+
+	yq := fmt.Sprintf(`.networks.%q = {"mode":%q,"gateway":%q,"netmask":%q,"interface":%q}`, name, mode, gwIP.String(), gwMaskStr, intf)
+
+	return networkApplyYQ(yq)
+}
+
+func networkApplyYQ(yq string) error {
+	filePath, err := networks.ConfigFile()
+	if err != nil {
+		return err
+	}
+	yContent, err := os.ReadFile(filePath)
+	if err != nil {
+		return err
+	}
+	yBytes, err := yqutil.EvaluateExpression(yq, yContent)
+	if err != nil {
+		return err
+	}
+	if err := os.WriteFile(filePath, yBytes, 0o644); err != nil {
+		return err
+	}
+	return nil
+}
+
+func newNetworkDeleteCommand() *cobra.Command {
+	cmd := &cobra.Command{
+		Use:               "delete NETWORK [NETWORK, ...]",
+		Short:             "Delete one or more Lima networks",
+		Aliases:           []string{"remove", "rm"},
+		Args:              WrapArgsError(cobra.MinimumNArgs(1)),
+		RunE:              networkDeleteAction,
+		ValidArgsFunction: networkBashComplete,
+	}
+	flags := cmd.Flags()
+	flags.BoolP("force", "f", false, "Force delete (currently always required)")
+	return cmd
+}
+
+func networkDeleteAction(cmd *cobra.Command, args []string) error {
+	flags := cmd.Flags()
+	force, err := flags.GetBool("force")
+	if err != nil {
+		return err
+	}
+	if !force {
+		return errors.New("`limactl network delete` currently always requires `--force`")
+		// Because the command currently does not check whether the network being removed is in use
+	}
+
+	var yq string
+	for i, name := range args {
+		yq += fmt.Sprintf("del(.networks.%q)", name)
+		if i < len(args)-1 {
+			yq += " | "
+		}
+	}
+	return networkApplyYQ(yq)
+}
+
+func networkBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
+	return bashCompleteNetworkNames(cmd)
+}
diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go
index 457265ae..082f8472 100644
--- a/pkg/driver/driver.go
+++ b/pkg/driver/driver.go
@@ -5,16 +5,10 @@ package driver
 
 import (
 	"context"
-	"errors"
 	"net"
-
-	"github.com/lima-vm/lima/pkg/store"
 )
 
 // Driver interface is used by hostagent for managing vm.
-//
-// This interface is extended by BaseDriver which provides default implementation.
-// All other driver definition must extend BaseDriver.
 type Driver interface {
 	// Validate returns error if the current driver isn't support for given config
 	Validate() error
@@ -71,84 +65,7 @@ type Driver interface {
 
 	// GuestAgentConn returns the guest agent connection, or nil (if forwarded by ssh).
 	GuestAgentConn(_ context.Context) (net.Conn, error)
-}
-
-type BaseDriver struct {
-	Instance *store.Instance
-
-	SSHLocalPort int
-	VSockPort    int
-	VirtioPort   string
-}
-
-var _ Driver = (*BaseDriver)(nil)
-
-func (d *BaseDriver) Validate() error {
-	return nil
-}
-
-func (d *BaseDriver) Initialize(_ context.Context) error {
-	return nil
-}
-
-func (d *BaseDriver) CreateDisk(_ context.Context) error {
-	return nil
-}
-
-func (d *BaseDriver) Start(_ context.Context) (chan error, error) {
-	return nil, nil
-}
-
-func (d *BaseDriver) CanRunGUI() bool {
-	return false
-}
-
-func (d *BaseDriver) RunGUI() error {
-	return nil
-}
-
-func (d *BaseDriver) Stop(_ context.Context) error {
-	return nil
-}
-
-func (d *BaseDriver) Register(_ context.Context) error {
-	return nil
-}
-
-func (d *BaseDriver) Unregister(_ context.Context) error {
-	return nil
-}
-
-func (d *BaseDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
-	return nil
-}
-
-func (d *BaseDriver) GetDisplayConnection(_ context.Context) (string, error) {
-	return "", nil
-}
-
-func (d *BaseDriver) CreateSnapshot(_ context.Context, _ string) error {
-	return errors.New("unimplemented")
-}
-
-func (d *BaseDriver) ApplySnapshot(_ context.Context, _ string) error {
-	return errors.New("unimplemented")
-}
-
-func (d *BaseDriver) DeleteSnapshot(_ context.Context, _ string) error {
-	return errors.New("unimplemented")
-}
-
-func (d *BaseDriver) ListSnapshots(_ context.Context) (string, error) {
-	return "", errors.New("unimplemented")
-}
-
-func (d *BaseDriver) ForwardGuestAgent() bool {
-	// if driver is not providing, use host agent
-	return d.VSockPort == 0 && d.VirtioPort == ""
-}
 
-func (d *BaseDriver) GuestAgentConn(_ context.Context) (net.Conn, error) {
-	// use the unix socket forwarded by host agent
-	return nil, nil
+	VSockPort() int
+	VirtioPort() string
 }
diff --git a/pkg/qemu/entitlementutil/entitlementutil.go b/pkg/driver/qemu/entitlementutil/entitlementutil.go
similarity index 100%
rename from pkg/qemu/entitlementutil/entitlementutil.go
rename to pkg/driver/qemu/entitlementutil/entitlementutil.go
diff --git a/pkg/qemu/qemu.go b/pkg/driver/qemu/qemu.go
similarity index 96%
rename from pkg/qemu/qemu.go
rename to pkg/driver/qemu/qemu.go
index 2fb08886..b1986d43 100644
--- a/pkg/qemu/qemu.go
+++ b/pkg/driver/qemu/qemu.go
@@ -28,12 +28,12 @@ import (
 	"github.com/sirupsen/logrus"
 
 	"github.com/lima-vm/lima/pkg/fileutils"
-	"github.com/lima-vm/lima/pkg/imgutil/qemuimgutil"
 	"github.com/lima-vm/lima/pkg/iso9660util"
 	"github.com/lima-vm/lima/pkg/limayaml"
 	"github.com/lima-vm/lima/pkg/networks"
 	"github.com/lima-vm/lima/pkg/networks/usernet"
 	"github.com/lima-vm/lima/pkg/osutil"
+	"github.com/lima-vm/lima/pkg/qemuimgutil"
 	"github.com/lima-vm/lima/pkg/store"
 	"github.com/lima-vm/lima/pkg/store/filenames"
 )
@@ -85,7 +85,7 @@ func minimumQemuVersion() (hardMin, softMin semver.Version) {
 }
 
 // EnsureDisk also ensures the kernel and the initrd.
-func EnsureDisk(ctx context.Context, cfg Config) error {
+func EnsureDisk(_ context.Context, cfg Config) error {
 	diffDisk := filepath.Join(cfg.InstanceDir, filenames.DiffDisk)
 	if _, err := os.Stat(diffDisk); err == nil || !errors.Is(err, os.ErrNotExist) {
 		// disk is already ensured
@@ -93,42 +93,7 @@ func EnsureDisk(ctx context.Context, cfg Config) error {
 	}
 
 	baseDisk := filepath.Join(cfg.InstanceDir, filenames.BaseDisk)
-	kernel := filepath.Join(cfg.InstanceDir, filenames.Kernel)
-	kernelCmdline := filepath.Join(cfg.InstanceDir, filenames.KernelCmdline)
-	initrd := filepath.Join(cfg.InstanceDir, filenames.Initrd)
-	if _, err := os.Stat(baseDisk); errors.Is(err, os.ErrNotExist) {
-		var ensuredBaseDisk bool
-		errs := make([]error, len(cfg.LimaYAML.Images))
-		for i, f := range cfg.LimaYAML.Images {
-			if _, err := fileutils.DownloadFile(ctx, baseDisk, f.File, true, "the image", *cfg.LimaYAML.Arch); err != nil {
-				errs[i] = err
-				continue
-			}
-			if f.Kernel != nil {
-				if _, err := fileutils.DownloadFile(ctx, kernel, f.Kernel.File, false, "the kernel", *cfg.LimaYAML.Arch); err != nil {
-					errs[i] = err
-					continue
-				}
-				if f.Kernel.Cmdline != "" {
-					if err := os.WriteFile(kernelCmdline, []byte(f.Kernel.Cmdline), 0o644); err != nil {
-						errs[i] = err
-						continue
-					}
-				}
-			}
-			if f.Initrd != nil {
-				if _, err := fileutils.DownloadFile(ctx, initrd, *f.Initrd, false, "the initrd", *cfg.LimaYAML.Arch); err != nil {
-					errs[i] = err
-					continue
-				}
-			}
-			ensuredBaseDisk = true
-			break
-		}
-		if !ensuredBaseDisk {
-			return fileutils.Errors(errs)
-		}
-	}
+
 	diskSize, _ := units.RAMInBytes(*cfg.LimaYAML.Disk)
 	if diskSize == 0 {
 		return nil
diff --git a/pkg/qemu/qemu_driver.go b/pkg/driver/qemu/qemu_driver.go
similarity index 91%
rename from pkg/qemu/qemu_driver.go
rename to pkg/driver/qemu/qemu_driver.go
index e28b2342..084021b8 100644
--- a/pkg/qemu/qemu_driver.go
+++ b/pkg/driver/qemu/qemu_driver.go
@@ -27,33 +27,42 @@ import (
 	"github.com/sirupsen/logrus"
 
 	"github.com/lima-vm/lima/pkg/driver"
+	"github.com/lima-vm/lima/pkg/driver/qemu/entitlementutil"
 	"github.com/lima-vm/lima/pkg/executil"
 	"github.com/lima-vm/lima/pkg/limayaml"
 	"github.com/lima-vm/lima/pkg/networks/usernet"
 	"github.com/lima-vm/lima/pkg/osutil"
-	"github.com/lima-vm/lima/pkg/qemu/entitlementutil"
 	"github.com/lima-vm/lima/pkg/store"
 	"github.com/lima-vm/lima/pkg/store/filenames"
 )
 
 type LimaQemuDriver struct {
-	*driver.BaseDriver
+	Instance     *store.Instance
+	SSHLocalPort int
+	vSockPort    int
+	virtioPort   string
+
 	qCmd    *exec.Cmd
 	qWaitCh chan error
 
 	vhostCmds []*exec.Cmd
 }
 
-func New(driver *driver.BaseDriver) *LimaQemuDriver {
-	driver.VSockPort = 0
-	driver.VirtioPort = filenames.VirtioPort
+var _ driver.Driver = (*LimaQemuDriver)(nil)
+
+func New(inst *store.Instance, sshLocalPort int) *LimaQemuDriver {
 	// virtserialport doesn't seem to work reliably: https://github.com/lima-vm/lima/issues/2064
 	// but on Windows default Unix socket forwarding is not available
+	var virtioPort string
+	virtioPort = filenames.VirtioPort
 	if runtime.GOOS != "windows" {
-		driver.VirtioPort = ""
+		virtioPort = ""
 	}
 	return &LimaQemuDriver{
-		BaseDriver: driver,
+		Instance:     inst,
+		vSockPort:    0,
+		virtioPort:   virtioPort,
+		SSHLocalPort: sshLocalPort,
 	}
 }
 
@@ -94,7 +103,7 @@ func (l *LimaQemuDriver) Start(ctx context.Context) (chan error, error) {
 		LimaYAML:     l.Instance.Config,
 		SSHLocalPort: l.SSHLocalPort,
 		SSHAddress:   l.Instance.SSHAddress,
-		VirtioGA:     l.VirtioPort != "",
+		VirtioGA:     l.virtioPort != "",
 	}
 	qExe, qArgs, err := Cmdline(ctx, qCfg)
 	if err != nil {
@@ -212,7 +221,7 @@ func (l *LimaQemuDriver) Start(ctx context.Context) (chan error, error) {
 	go func() {
 		if usernetIndex := limayaml.FirstUsernetIndex(l.Instance.Config); usernetIndex != -1 {
 			client := usernet.NewClientByName(l.Instance.Config.Networks[usernetIndex].Lima)
-			err := client.ConfigureDriver(ctx, l.BaseDriver)
+			err := client.ConfigureDriver(ctx, l.Instance, l.SSHLocalPort)
 			if err != nil {
 				l.qWaitCh <- err
 			}
@@ -260,11 +269,11 @@ func (l *LimaQemuDriver) checkBinarySignature() error {
 	}
 	// The codesign --xml option is only available on macOS Monterey and later
 	if !macOSProductVersion.LessThan(*semver.New("12.0.0")) {
-		qExe, _, err := Exe(l.BaseDriver.Instance.Arch)
+		qExe, _, err := Exe(l.Instance.Arch)
 		if err != nil {
-			return fmt.Errorf("failed to find the QEMU binary for the architecture %q: %w", l.BaseDriver.Instance.Arch, err)
+			return fmt.Errorf("failed to find the QEMU binary for the architecture %q: %w", l.Instance.Arch, err)
 		}
-		if accel := Accel(l.BaseDriver.Instance.Arch); accel == "hvf" {
+		if accel := Accel(l.Instance.Arch); accel == "hvf" {
 			entitlementutil.AskToSignIfNotSignedProperly(qExe)
 		}
 	}
@@ -498,3 +507,36 @@ func (a *qArgTemplateApplier) applyTemplate(qArg string) (string, error) {
 	}
 	return b.String(), nil
 }
+
+func (l *LimaQemuDriver) Initialize(_ context.Context) error {
+	return nil
+}
+
+func (l *LimaQemuDriver) CanRunGUI() bool {
+	return false
+}
+
+func (l *LimaQemuDriver) RunGUI() error {
+	return nil
+}
+
+func (l *LimaQemuDriver) Register(_ context.Context) error {
+	return nil
+}
+
+func (l *LimaQemuDriver) Unregister(_ context.Context) error {
+	return nil
+}
+
+func (l *LimaQemuDriver) ForwardGuestAgent() bool {
+	// if driver is not providing, use host agent
+	return l.vSockPort == 0 && l.virtioPort == ""
+}
+
+func (l *LimaQemuDriver) VSockPort() int {
+	return l.vSockPort
+}
+
+func (l *LimaQemuDriver) VirtioPort() string {
+	return l.virtioPort
+}
diff --git a/pkg/driver/vz/disk.go b/pkg/driver/vz/disk.go
new file mode 100644
index 00000000..f98cf229
--- /dev/null
+++ b/pkg/driver/vz/disk.go
@@ -0,0 +1,58 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package vz
+
+import (
+	"context"
+	"errors"
+	"fmt"
+	"os"
+	"path/filepath"
+
+	"github.com/docker/go-units"
+
+	"github.com/lima-vm/lima/pkg/imgutil/proxyimgutil"
+	"github.com/lima-vm/lima/pkg/iso9660util"
+	"github.com/lima-vm/lima/pkg/store"
+	"github.com/lima-vm/lima/pkg/store/filenames"
+)
+
+func EnsureDisk(_ context.Context, inst *store.Instance) error {
+	diffDisk := filepath.Join(inst.Dir, filenames.DiffDisk)
+	if _, err := os.Stat(diffDisk); err == nil || !errors.Is(err, os.ErrNotExist) {
+		// disk is already ensured
+		return err
+	}
+
+	diskUtil := proxyimgutil.NewDiskUtil()
+
+	baseDisk := filepath.Join(inst.Dir, filenames.BaseDisk)
+
+	diskSize, _ := units.RAMInBytes(*inst.Config.Disk)
+	if diskSize == 0 {
+		return nil
+	}
+	isBaseDiskISO, err := iso9660util.IsISO9660(baseDisk)
+	if err != nil {
+		return err
+	}
+	if isBaseDiskISO {
+		// Create an empty data volume (sparse)
+		diffDiskF, err := os.Create(diffDisk)
+		if err != nil {
+			return err
+		}
+
+		err = diskUtil.MakeSparse(diffDiskF, 0)
+		if err != nil {
+			diffDiskF.Close()
+			return fmt.Errorf("failed to create sparse diff disk %q: %w", diffDisk, err)
+		}
+		return diffDiskF.Close()
+	}
+	if err = diskUtil.ConvertToRaw(baseDisk, diffDisk, &diskSize, false); err != nil {
+		return fmt.Errorf("failed to convert %q to a raw disk %q: %w", baseDisk, diffDisk, err)
+	}
+	return err
+}
diff --git a/pkg/vz/errors_darwin.go b/pkg/driver/vz/errors_darwin.go
similarity index 100%
rename from pkg/vz/errors_darwin.go
rename to pkg/driver/vz/errors_darwin.go
diff --git a/pkg/vz/network_darwin.go b/pkg/driver/vz/network_darwin.go
similarity index 100%
rename from pkg/vz/network_darwin.go
rename to pkg/driver/vz/network_darwin.go
diff --git a/pkg/vz/rosetta_directory_share.go b/pkg/driver/vz/rosetta_directory_share.go
similarity index 100%
rename from pkg/vz/rosetta_directory_share.go
rename to pkg/driver/vz/rosetta_directory_share.go
diff --git a/pkg/vz/rosetta_directory_share_arm64.go b/pkg/driver/vz/rosetta_directory_share_arm64.go
similarity index 100%
rename from pkg/vz/rosetta_directory_share_arm64.go
rename to pkg/driver/vz/rosetta_directory_share_arm64.go
diff --git a/pkg/vz/vm_darwin.go b/pkg/driver/vz/vm_darwin.go
similarity index 80%
rename from pkg/vz/vm_darwin.go
rename to pkg/driver/vz/vm_darwin.go
index 9ea33a86..147af63f 100644
--- a/pkg/vz/vm_darwin.go
+++ b/pkg/driver/vz/vm_darwin.go
@@ -24,7 +24,6 @@ import (
 	"github.com/lima-vm/go-qcow2reader/image/raw"
 	"github.com/sirupsen/logrus"
 
-	"github.com/lima-vm/lima/pkg/driver"
 	"github.com/lima-vm/lima/pkg/imgutil/proxyimgutil"
 	"github.com/lima-vm/lima/pkg/iso9660util"
 	"github.com/lima-vm/lima/pkg/limayaml"
@@ -51,13 +50,13 @@ type virtualMachineWrapper struct {
 // Hold all *os.File created via socketpair() so that they won't get garbage collected. f.FD() gets invalid if f gets garbage collected.
 var vmNetworkFiles = make([]*os.File, 1)
 
-func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWrapper, chan error, error) {
-	usernetClient, err := startUsernet(ctx, driver)
+func startVM(ctx context.Context, inst *store.Instance, sshLocalPort int) (*virtualMachineWrapper, chan error, error) {
+	usernetClient, err := startUsernet(ctx, inst)
 	if err != nil {
 		return nil, nil, err
 	}
 
-	machine, err := createVM(driver)
+	machine, err := createVM(inst)
 	if err != nil {
 		return nil, nil, err
 	}
@@ -95,7 +94,7 @@ func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWra
 			case newState := <-machine.StateChangedNotify():
 				switch newState {
 				case vz.VirtualMachineStateRunning:
-					pidFile := filepath.Join(driver.Instance.Dir, filenames.PIDFile(*driver.Instance.Config.VMType))
+					pidFile := filepath.Join(inst.Dir, filenames.PIDFile(*inst.Config.VMType))
 					if _, err := os.Stat(pidFile); !errors.Is(err, os.ErrNotExist) {
 						logrus.Errorf("pidfile %q already exists", pidFile)
 						errCh <- err
@@ -107,7 +106,7 @@ func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWra
 					filesToRemove[pidFile] = struct{}{}
 					logrus.Info("[VZ] - vm state change: running")
 
-					err := usernetClient.ConfigureDriver(ctx, driver)
+					err := usernetClient.ConfigureDriver(ctx, inst, sshLocalPort)
 					if err != nil {
 						errCh <- err
 					}
@@ -116,7 +115,7 @@ func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWra
 					wrapper.mu.Lock()
 					wrapper.stopped = true
 					wrapper.mu.Unlock()
-					_ = usernetClient.UnExposeSSH(driver.SSHLocalPort)
+					_ = usernetClient.UnExposeSSH(inst.SSHLocalPort)
 					errCh <- errors.New("vz driver state stopped")
 				default:
 					logrus.Debugf("[VZ] - vm state change: %q", newState)
@@ -128,17 +127,17 @@ func startVM(ctx context.Context, driver *driver.BaseDriver) (*virtualMachineWra
 	return wrapper, errCh, err
 }
 
-func startUsernet(ctx context.Context, driver *driver.BaseDriver) (*usernet.Client, error) {
-	if firstUsernetIndex := limayaml.FirstUsernetIndex(driver.Instance.Config); firstUsernetIndex != -1 {
-		nwName := driver.Instance.Config.Networks[firstUsernetIndex].Lima
+func startUsernet(ctx context.Context, inst *store.Instance) (*usernet.Client, error) {
+	if firstUsernetIndex := limayaml.FirstUsernetIndex(inst.Config); firstUsernetIndex != -1 {
+		nwName := inst.Config.Networks[firstUsernetIndex].Lima
 		return usernet.NewClientByName(nwName), nil
 	}
 	// Start a in-process gvisor-tap-vsock
-	endpointSock, err := usernet.SockWithDirectory(driver.Instance.Dir, "", usernet.EndpointSock)
+	endpointSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.EndpointSock)
 	if err != nil {
 		return nil, err
 	}
-	vzSock, err := usernet.SockWithDirectory(driver.Instance.Dir, "", usernet.FDSock)
+	vzSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.FDSock)
 	if err != nil {
 		return nil, err
 	}
@@ -150,7 +149,7 @@ func startUsernet(ctx context.Context, driver *driver.BaseDriver) (*usernet.Clie
 		FdSocket: vzSock,
 		Async:    true,
 		DefaultLeases: map[string]string{
-			networks.SlirpIPAddress: limayaml.MACAddress(driver.Instance.Dir),
+			networks.SlirpIPAddress: limayaml.MACAddress(inst.Dir),
 		},
 		Subnet: networks.SlirpNetwork,
 	})
@@ -161,41 +160,41 @@ func startUsernet(ctx context.Context, driver *driver.BaseDriver) (*usernet.Clie
 	return usernet.NewClient(endpointSock, subnetIP), err
 }
 
-func createVM(driver *driver.BaseDriver) (*vz.VirtualMachine, error) {
-	vmConfig, err := createInitialConfig(driver)
+func createVM(inst *store.Instance) (*vz.VirtualMachine, error) {
+	vmConfig, err := createInitialConfig(inst)
 	if err != nil {
 		return nil, err
 	}
 
-	if err = attachPlatformConfig(driver, vmConfig); err != nil {
+	if err = attachPlatformConfig(inst, vmConfig); err != nil {
 		return nil, err
 	}
 
-	if err = attachSerialPort(driver, vmConfig); err != nil {
+	if err = attachSerialPort(inst, vmConfig); err != nil {
 		return nil, err
 	}
 
-	if err = attachNetwork(driver, vmConfig); err != nil {
+	if err = attachNetwork(inst, vmConfig); err != nil {
 		return nil, err
 	}
 
-	if err = attachDisks(driver, vmConfig); err != nil {
+	if err = attachDisks(inst, vmConfig); err != nil {
 		return nil, err
 	}
 
-	if err = attachDisplay(driver, vmConfig); err != nil {
+	if err = attachDisplay(inst, vmConfig); err != nil {
 		return nil, err
 	}
 
-	if err = attachFolderMounts(driver, vmConfig); err != nil {
+	if err = attachFolderMounts(inst, vmConfig); err != nil {
 		return nil, err
 	}
 
-	if err = attachAudio(driver, vmConfig); err != nil {
+	if err = attachAudio(inst, vmConfig); err != nil {
 		return nil, err
 	}
 
-	if err = attachOtherDevices(driver, vmConfig); err != nil {
+	if err = attachOtherDevices(inst, vmConfig); err != nil {
 		return nil, err
 	}
 
@@ -207,20 +206,20 @@ func createVM(driver *driver.BaseDriver) (*vz.VirtualMachine, error) {
 	return vz.NewVirtualMachine(vmConfig)
 }
 
-func createInitialConfig(driver *driver.BaseDriver) (*vz.VirtualMachineConfiguration, error) {
-	bootLoader, err := bootLoader(driver)
+func createInitialConfig(inst *store.Instance) (*vz.VirtualMachineConfiguration, error) {
+	bootLoader, err := bootLoader(inst)
 	if err != nil {
 		return nil, err
 	}
 
-	bytes, err := units.RAMInBytes(*driver.Instance.Config.Memory)
+	bytes, err := units.RAMInBytes(*inst.Config.Memory)
 	if err != nil {
 		return nil, err
 	}
 
 	vmConfig, err := vz.NewVirtualMachineConfiguration(
 		bootLoader,
-		uint(*driver.Instance.Config.CPUs),
+		uint(*inst.Config.CPUs),
 		uint64(bytes),
 	)
 	if err != nil {
@@ -229,8 +228,8 @@ func createInitialConfig(driver *driver.BaseDriver) (*vz.VirtualMachineConfigura
 	return vmConfig, nil
 }
 
-func attachPlatformConfig(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error {
-	machineIdentifier, err := getMachineIdentifier(driver)
+func attachPlatformConfig(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
+	machineIdentifier, err := getMachineIdentifier(inst)
 	if err != nil {
 		return err
 	}
@@ -241,7 +240,7 @@ func attachPlatformConfig(driver *driver.BaseDriver, vmConfig *vz.VirtualMachine
 	}
 
 	// nested virt
-	if *driver.Instance.Config.NestedVirtualization {
+	if *inst.Config.NestedVirtualization {
 		macOSProductVersion, err := osutil.ProductVersion()
 		if err != nil {
 			return fmt.Errorf("failed to get macOS product version: %w", err)
@@ -264,8 +263,8 @@ func attachPlatformConfig(driver *driver.BaseDriver, vmConfig *vz.VirtualMachine
 	return nil
 }
 
-func attachSerialPort(driver *driver.BaseDriver, config *vz.VirtualMachineConfiguration) error {
-	path := filepath.Join(driver.Instance.Dir, filenames.SerialVirtioLog)
+func attachSerialPort(inst *store.Instance, config *vz.VirtualMachineConfiguration) error {
+	path := filepath.Join(inst.Dir, filenames.SerialVirtioLog)
 	serialPortAttachment, err := vz.NewFileSerialPortAttachment(path, false)
 	if err != nil {
 		return err
@@ -302,14 +301,14 @@ func newVirtioNetworkDeviceConfiguration(attachment vz.NetworkDeviceAttachment,
 	return networkConfig, nil
 }
 
-func attachNetwork(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error {
+func attachNetwork(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
 	var configurations []*vz.VirtioNetworkDeviceConfiguration
 
-	// Configure default usernetwork with limayaml.MACAddress(driver.Instance.Dir) for eth0 interface
-	firstUsernetIndex := limayaml.FirstUsernetIndex(driver.Instance.Config)
+	// Configure default usernetwork with limayaml.MACAddress(inst.Dir) for eth0 interface
+	firstUsernetIndex := limayaml.FirstUsernetIndex(inst.Config)
 	if firstUsernetIndex == -1 {
 		// slirp network using gvisor netstack
-		vzSock, err := usernet.SockWithDirectory(driver.Instance.Dir, "", usernet.FDSock)
+		vzSock, err := usernet.SockWithDirectory(inst.Dir, "", usernet.FDSock)
 		if err != nil {
 			return err
 		}
@@ -317,13 +316,13 @@ func attachNetwork(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigu
 		if err != nil {
 			return err
 		}
-		networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(driver.Instance.Dir))
+		networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(inst.Dir))
 		if err != nil {
 			return err
 		}
 		configurations = append(configurations, networkConfig)
 	} else {
-		vzSock, err := usernet.Sock(driver.Instance.Config.Networks[firstUsernetIndex].Lima, usernet.FDSock)
+		vzSock, err := usernet.Sock(inst.Config.Networks[firstUsernetIndex].Lima, usernet.FDSock)
 		if err != nil {
 			return err
 		}
@@ -331,14 +330,14 @@ func attachNetwork(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigu
 		if err != nil {
 			return err
 		}
-		networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(driver.Instance.Dir))
+		networkConfig, err := newVirtioFileNetworkDeviceConfiguration(networkConn, limayaml.MACAddress(inst.Dir))
 		if err != nil {
 			return err
 		}
 		configurations = append(configurations, networkConfig)
 	}
 
-	for i, nw := range driver.Instance.Networks {
+	for i, nw := range inst.Networks {
 		if nw.VZNAT != nil && *nw.VZNAT {
 			attachment, err := vz.NewNATNetworkDeviceAttachment()
 			if err != nil {
@@ -434,10 +433,10 @@ func validateDiskFormat(diskPath string) error {
 	return nil
 }
 
-func attachDisks(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error {
-	baseDiskPath := filepath.Join(driver.Instance.Dir, filenames.BaseDisk)
-	diffDiskPath := filepath.Join(driver.Instance.Dir, filenames.DiffDisk)
-	ciDataPath := filepath.Join(driver.Instance.Dir, filenames.CIDataISO)
+func attachDisks(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
+	baseDiskPath := filepath.Join(inst.Dir, filenames.BaseDisk)
+	diffDiskPath := filepath.Join(inst.Dir, filenames.DiffDisk)
+	ciDataPath := filepath.Join(inst.Dir, filenames.CIDataISO)
 	isBaseDiskCDROM, err := iso9660util.IsISO9660(baseDiskPath)
 	if err != nil {
 		return err
@@ -473,7 +472,7 @@ func attachDisks(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigura
 
 	diskUtil := proxyimgutil.NewDiskUtil()
 
-	for _, d := range driver.Instance.Config.AdditionalDisks {
+	for _, d := range inst.Config.AdditionalDisks {
 		diskName := d.Name
 		disk, err := store.InspectDisk(diskName)
 		if err != nil {
@@ -484,7 +483,7 @@ func attachDisks(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigura
 			return fmt.Errorf("failed to run attach disk %q, in use by instance %q", diskName, disk.Instance)
 		}
 		logrus.Infof("Mounting disk %q on %q", diskName, disk.MountPoint)
-		err = disk.Lock(driver.Instance.Dir)
+		err = disk.Lock(inst.Dir)
 		if err != nil {
 			return fmt.Errorf("failed to run lock disk %q: %w", diskName, err)
 		}
@@ -523,8 +522,8 @@ func attachDisks(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigura
 	return nil
 }
 
-func attachDisplay(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error {
-	switch *driver.Instance.Config.Video.Display {
+func attachDisplay(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
+	switch *inst.Config.Video.Display {
 	case "vz", "default":
 		graphicsDeviceConfiguration, err := vz.NewVirtioGraphicsDeviceConfiguration()
 		if err != nil {
@@ -543,14 +542,14 @@ func attachDisplay(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigu
 	case "none":
 		return nil
 	default:
-		return fmt.Errorf("unexpected video display %q", *driver.Instance.Config.Video.Display)
+		return fmt.Errorf("unexpected video display %q", *inst.Config.Video.Display)
 	}
 }
 
-func attachFolderMounts(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error {
+func attachFolderMounts(inst *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
 	var mounts []vz.DirectorySharingDeviceConfiguration
-	if *driver.Instance.Config.MountType == limayaml.VIRTIOFS {
-		for i, mount := range driver.Instance.Config.Mounts {
+	if *inst.Config.MountType == limayaml.VIRTIOFS {
+		for i, mount := range inst.Config.Mounts {
 			if _, err := os.Stat(mount.Location); errors.Is(err, os.ErrNotExist) {
 				err := os.MkdirAll(mount.Location, 0o750)
 				if err != nil {
@@ -577,7 +576,7 @@ func attachFolderMounts(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineCo
 		}
 	}
 
-	if *driver.Instance.Config.Rosetta.Enabled {
+	if *inst.Config.Rosetta.Enabled {
 		logrus.Info("Setting up Rosetta share")
 		directorySharingDeviceConfig, err := createRosettaDirectoryShareConfiguration()
 		if err != nil {
@@ -593,8 +592,8 @@ func attachFolderMounts(driver *driver.BaseDriver, vmConfig *vz.VirtualMachineCo
 	return nil
 }
 
-func attachAudio(driver *driver.BaseDriver, config *vz.VirtualMachineConfiguration) error {
-	switch *driver.Instance.Config.Audio.Device {
+func attachAudio(inst *store.Instance, config *vz.VirtualMachineConfiguration) error {
+	switch *inst.Config.Audio.Device {
 	case "vz", "default":
 		outputStream, err := vz.NewVirtioSoundDeviceHostOutputStreamConfiguration()
 		if err != nil {
@@ -612,11 +611,11 @@ func attachAudio(driver *driver.BaseDriver, config *vz.VirtualMachineConfigurati
 	case "", "none":
 		return nil
 	default:
-		return fmt.Errorf("unexpected audio device %q", *driver.Instance.Config.Audio.Device)
+		return fmt.Errorf("unexpected audio device %q", *inst.Config.Audio.Device)
 	}
 }
 
-func attachOtherDevices(_ *driver.BaseDriver, vmConfig *vz.VirtualMachineConfiguration) error {
+func attachOtherDevices(_ *store.Instance, vmConfig *vz.VirtualMachineConfiguration) error {
 	entropyConfig, err := vz.NewVirtioEntropyDeviceConfiguration()
 	if err != nil {
 		return err
@@ -690,8 +689,8 @@ func attachOtherDevices(_ *driver.BaseDriver, vmConfig *vz.VirtualMachineConfigu
 	return nil
 }
 
-func getMachineIdentifier(driver *driver.BaseDriver) (*vz.GenericMachineIdentifier, error) {
-	identifier := filepath.Join(driver.Instance.Dir, filenames.VzIdentifier)
+func getMachineIdentifier(inst *store.Instance) (*vz.GenericMachineIdentifier, error) {
+	identifier := filepath.Join(inst.Dir, filenames.VzIdentifier)
 	if _, err := os.Stat(identifier); os.IsNotExist(err) {
 		machineIdentifier, err := vz.NewGenericMachineIdentifier()
 		if err != nil {
@@ -706,15 +705,15 @@ func getMachineIdentifier(driver *driver.BaseDriver) (*vz.GenericMachineIdentifi
 	return vz.NewGenericMachineIdentifierWithDataPath(identifier)
 }
 
-func bootLoader(driver *driver.BaseDriver) (vz.BootLoader, error) {
-	linuxBootLoader, err := linuxBootLoader(driver)
+func bootLoader(inst *store.Instance) (vz.BootLoader, error) {
+	linuxBootLoader, err := linuxBootLoader(inst)
 	if linuxBootLoader != nil {
 		return linuxBootLoader, nil
 	} else if !errors.Is(err, os.ErrNotExist) {
 		return nil, err
 	}
 
-	efiVariableStore, err := getEFI(driver)
+	efiVariableStore, err := getEFI(inst)
 	if err != nil {
 		return nil, err
 	}
@@ -722,10 +721,10 @@ func bootLoader(driver *driver.BaseDriver) (vz.BootLoader, error) {
 	return vz.NewEFIBootLoader(vz.WithEFIVariableStore(efiVariableStore))
 }
 
-func linuxBootLoader(driver *driver.BaseDriver) (*vz.LinuxBootLoader, error) {
-	kernel := filepath.Join(driver.Instance.Dir, filenames.Kernel)
-	kernelCmdline := filepath.Join(driver.Instance.Dir, filenames.KernelCmdline)
-	initrd := filepath.Join(driver.Instance.Dir, filenames.Initrd)
+func linuxBootLoader(inst *store.Instance) (*vz.LinuxBootLoader, error) {
+	kernel := filepath.Join(inst.Dir, filenames.Kernel)
+	kernelCmdline := filepath.Join(inst.Dir, filenames.KernelCmdline)
+	initrd := filepath.Join(inst.Dir, filenames.Initrd)
 	if _, err := os.Stat(kernel); err != nil {
 		if errors.Is(err, os.ErrNotExist) {
 			logrus.Debugf("Kernel file %q not found", kernel)
@@ -747,8 +746,8 @@ func linuxBootLoader(driver *driver.BaseDriver) (*vz.LinuxBootLoader, error) {
 	return vz.NewLinuxBootLoader(kernel, opt...)
 }
 
-func getEFI(driver *driver.BaseDriver) (*vz.EFIVariableStore, error) {
-	efi := filepath.Join(driver.Instance.Dir, filenames.VzEfi)
+func getEFI(inst *store.Instance) (*vz.EFIVariableStore, error) {
+	efi := filepath.Join(inst.Dir, filenames.VzEfi)
 	if _, err := os.Stat(efi); os.IsNotExist(err) {
 		return vz.NewEFIVariableStore(efi, vz.WithCreatingEFIVariableStore())
 	}
diff --git a/pkg/vz/vz_driver_darwin.go b/pkg/driver/vz/vz_driver_darwin.go
similarity index 79%
rename from pkg/vz/vz_driver_darwin.go
rename to pkg/driver/vz/vz_driver_darwin.go
index ee1b34b9..7242bf8b 100644
--- a/pkg/vz/vz_driver_darwin.go
+++ b/pkg/driver/vz/vz_driver_darwin.go
@@ -22,6 +22,7 @@ import (
 	"github.com/lima-vm/lima/pkg/limayaml"
 	"github.com/lima-vm/lima/pkg/osutil"
 	"github.com/lima-vm/lima/pkg/reflectutil"
+	"github.com/lima-vm/lima/pkg/store"
 )
 
 var knownYamlProperties = []string{
@@ -68,16 +69,23 @@ var knownYamlProperties = []string{
 const Enabled = true
 
 type LimaVzDriver struct {
-	*driver.BaseDriver
+	Instance *store.Instance
+
+	SSHLocalPort int
+	vSockPort    int
+	virtioPort   string
 
 	machine *virtualMachineWrapper
 }
 
-func New(driver *driver.BaseDriver) *LimaVzDriver {
-	driver.VSockPort = 2222
-	driver.VirtioPort = ""
+var _ driver.Driver = (*LimaVzDriver)(nil)
+
+func New(inst *store.Instance, sshLocalPort int) *LimaVzDriver {
 	return &LimaVzDriver{
-		BaseDriver: driver,
+		Instance:     inst,
+		vSockPort:    2222,
+		virtioPort:   "",
+		SSHLocalPort: sshLocalPort,
 	}
 }
 
@@ -167,17 +175,17 @@ func (l *LimaVzDriver) Validate() error {
 }
 
 func (l *LimaVzDriver) Initialize(_ context.Context) error {
-	_, err := getMachineIdentifier(l.BaseDriver)
+	_, err := getMachineIdentifier(l.Instance)
 	return err
 }
 
 func (l *LimaVzDriver) CreateDisk(ctx context.Context) error {
-	return EnsureDisk(ctx, l.BaseDriver)
+	return EnsureDisk(ctx, l.Instance)
 }
 
 func (l *LimaVzDriver) Start(ctx context.Context) (chan error, error) {
 	logrus.Infof("Starting VZ (hint: to watch the boot progress, see %q)", filepath.Join(l.Instance.Dir, "serial*.log"))
-	vm, errCh, err := startVM(ctx, l.BaseDriver)
+	vm, errCh, err := startVM(ctx, l.Instance, l.SSHLocalPort)
 	if err != nil {
 		if errors.Is(err, vz.ErrUnsupportedOSVersion) {
 			return nil, fmt.Errorf("vz driver requires macOS 13 or higher to run: %w", err)
@@ -237,10 +245,55 @@ func (l *LimaVzDriver) Stop(_ context.Context) error {
 
 func (l *LimaVzDriver) GuestAgentConn(_ context.Context) (net.Conn, error) {
 	for _, socket := range l.machine.SocketDevices() {
-		connect, err := socket.Connect(uint32(l.VSockPort))
+		connect, err := socket.Connect(uint32(l.vSockPort))
 		if err == nil && connect.SourcePort() != 0 {
 			return connect, nil
 		}
 	}
 	return nil, errors.New("unable to connect to guest agent via vsock port 2222")
 }
+
+func (l *LimaVzDriver) Register(_ context.Context) error {
+	return nil
+}
+
+func (l *LimaVzDriver) Unregister(_ context.Context) error {
+	return nil
+}
+
+func (l *LimaVzDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
+	return nil
+}
+
+func (l *LimaVzDriver) GetDisplayConnection(_ context.Context) (string, error) {
+	return "", nil
+}
+
+func (l *LimaVzDriver) CreateSnapshot(_ context.Context, _ string) error {
+	return errors.New("unimplemented")
+}
+
+func (l *LimaVzDriver) ApplySnapshot(_ context.Context, _ string) error {
+	return errors.New("unimplemented")
+}
+
+func (l *LimaVzDriver) DeleteSnapshot(_ context.Context, _ string) error {
+	return errors.New("unimplemented")
+}
+
+func (l *LimaVzDriver) ListSnapshots(_ context.Context) (string, error) {
+	return "", errors.New("unimplemented")
+}
+
+func (l *LimaVzDriver) ForwardGuestAgent() bool {
+	// If driver is not providing, use host agent
+	return l.vSockPort == 0 && l.virtioPort == ""
+}
+
+func (l *LimaVzDriver) VSockPort() int {
+	return l.vSockPort
+}
+
+func (l *LimaVzDriver) VirtioPort() string {
+	return l.virtioPort
+}
diff --git a/pkg/driver/vz/vz_driver_others.go b/pkg/driver/vz/vz_driver_others.go
new file mode 100644
index 00000000..589d0e75
--- /dev/null
+++ b/pkg/driver/vz/vz_driver_others.go
@@ -0,0 +1,109 @@
+//go:build !darwin || no_vz
+
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package vz
+
+import (
+	"context"
+	"errors"
+	"net"
+
+	"github.com/lima-vm/lima/pkg/driver"
+	"github.com/lima-vm/lima/pkg/store"
+)
+
+var ErrUnsupported = errors.New("vm driver 'vz' needs macOS 13 or later (Hint: try recompiling Lima if you are seeing this error on macOS 13)")
+
+const Enabled = false
+
+type LimaVzDriver struct {
+	Instance *store.Instance
+
+	SSHLocalPort int
+	vSockPort    int
+	virtioPort   string
+}
+
+var _ driver.Driver = (*LimaVzDriver)(nil)
+
+func New(_ *store.Instance, _ int) *LimaVzDriver {
+	return &LimaVzDriver{}
+}
+
+func (l *LimaVzDriver) Validate() error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) Initialize(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) CreateDisk(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) Start(_ context.Context) (chan error, error) {
+	return nil, ErrUnsupported
+}
+
+func (l *LimaVzDriver) Stop(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) CanRunGUI() bool {
+	return false
+}
+
+func (l *LimaVzDriver) RunGUI() error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) GetDisplayConnection(_ context.Context) (string, error) {
+	return "", ErrUnsupported
+}
+
+func (l *LimaVzDriver) CreateSnapshot(_ context.Context, _ string) error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) ApplySnapshot(_ context.Context, _ string) error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) DeleteSnapshot(_ context.Context, _ string) error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) ListSnapshots(_ context.Context) (string, error) {
+	return "", ErrUnsupported
+}
+
+func (l *LimaVzDriver) Register(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) Unregister(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaVzDriver) ForwardGuestAgent() bool {
+	return false
+}
+
+func (l *LimaVzDriver) GuestAgentConn(_ context.Context) (net.Conn, error) {
+	return nil, ErrUnsupported
+}
+
+func (l *LimaVzDriver) VSockPort() int {
+	return l.vSockPort
+}
+
+func (l *LimaVzDriver) VirtioPort() string {
+	return l.virtioPort
+}
diff --git a/pkg/wsl2/fs.go b/pkg/driver/wsl2/fs.go
similarity index 65%
rename from pkg/wsl2/fs.go
rename to pkg/driver/wsl2/fs.go
index 8a3060ac..53823a7f 100644
--- a/pkg/wsl2/fs.go
+++ b/pkg/driver/wsl2/fs.go
@@ -11,19 +11,19 @@ import (
 
 	"github.com/sirupsen/logrus"
 
-	"github.com/lima-vm/lima/pkg/driver"
 	"github.com/lima-vm/lima/pkg/fileutils"
+	"github.com/lima-vm/lima/pkg/store"
 	"github.com/lima-vm/lima/pkg/store/filenames"
 )
 
 // EnsureFs downloads the root fs.
-func EnsureFs(ctx context.Context, driver *driver.BaseDriver) error {
-	baseDisk := filepath.Join(driver.Instance.Dir, filenames.BaseDisk)
+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(driver.Instance.Config.Images))
-		for i, f := range driver.Instance.Config.Images {
-			if _, err := fileutils.DownloadFile(ctx, baseDisk, f.File, true, "the image", *driver.Instance.Config.Arch); err != nil {
+		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
 			}
diff --git a/pkg/wsl2/lima-init.TEMPLATE b/pkg/driver/wsl2/lima-init.TEMPLATE
similarity index 100%
rename from pkg/wsl2/lima-init.TEMPLATE
rename to pkg/driver/wsl2/lima-init.TEMPLATE
diff --git a/pkg/wsl2/vm_windows.go b/pkg/driver/wsl2/vm_windows.go
similarity index 100%
rename from pkg/wsl2/vm_windows.go
rename to pkg/driver/wsl2/vm_windows.go
diff --git a/pkg/driver/wsl2/wsl_driver_others.go b/pkg/driver/wsl2/wsl_driver_others.go
new file mode 100644
index 00000000..274761b1
--- /dev/null
+++ b/pkg/driver/wsl2/wsl_driver_others.go
@@ -0,0 +1,109 @@
+//go:build !windows || no_wsl
+
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package wsl2
+
+import (
+	"context"
+	"errors"
+	"net"
+
+	"github.com/lima-vm/lima/pkg/driver"
+	"github.com/lima-vm/lima/pkg/store"
+)
+
+var ErrUnsupported = errors.New("vm driver 'wsl2' requires Windows 10 build 19041 or later (Hint: try recompiling Lima if you are seeing this error on Windows 10+)")
+
+const Enabled = false
+
+type LimaWslDriver struct {
+	Instance *store.Instance
+
+	SSHLocalPort int
+	vSockPort    int
+	virtioPort   string
+}
+
+var _ driver.Driver = (*LimaWslDriver)(nil)
+
+func New(_ *store.Instance, _ int) *LimaWslDriver {
+	return &LimaWslDriver{}
+}
+
+func (l *LimaWslDriver) Validate() error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) Initialize(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) CreateDisk(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) Start(_ context.Context) (chan error, error) {
+	return nil, ErrUnsupported
+}
+
+func (l *LimaWslDriver) Stop(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) CanRunGUI() bool {
+	return false
+}
+
+func (l *LimaWslDriver) RunGUI() error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) GetDisplayConnection(_ context.Context) (string, error) {
+	return "", ErrUnsupported
+}
+
+func (l *LimaWslDriver) CreateSnapshot(_ context.Context, _ string) error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) ApplySnapshot(_ context.Context, _ string) error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) DeleteSnapshot(_ context.Context, _ string) error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) ListSnapshots(_ context.Context) (string, error) {
+	return "", ErrUnsupported
+}
+
+func (l *LimaWslDriver) Register(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) Unregister(_ context.Context) error {
+	return ErrUnsupported
+}
+
+func (l *LimaWslDriver) ForwardGuestAgent() bool {
+	return false
+}
+
+func (l *LimaWslDriver) GuestAgentConn(_ context.Context) (net.Conn, error) {
+	return nil, ErrUnsupported
+}
+
+func (l *LimaWslDriver) VSockPort() int {
+	return l.vSockPort
+}
+
+func (l *LimaWslDriver) VirtioPort() string {
+	return l.virtioPort
+}
diff --git a/pkg/wsl2/wsl_driver_windows.go b/pkg/driver/wsl2/wsl_driver_windows.go
similarity index 75%
rename from pkg/wsl2/wsl_driver_windows.go
rename to pkg/driver/wsl2/wsl_driver_windows.go
index 4ad7d472..cd9df59e 100644
--- a/pkg/wsl2/wsl_driver_windows.go
+++ b/pkg/driver/wsl2/wsl_driver_windows.go
@@ -5,6 +5,7 @@ package wsl2
 
 import (
 	"context"
+	"errors"
 	"fmt"
 	"net"
 	"regexp"
@@ -47,18 +48,26 @@ var knownYamlProperties = []string{
 const Enabled = true
 
 type LimaWslDriver struct {
-	*driver.BaseDriver
+	Instance *store.Instance
+
+	SSHLocalPort int
+	vSockPort    int
+	virtioPort   string
 }
 
-func New(driver *driver.BaseDriver) *LimaWslDriver {
+var _ driver.Driver = (*LimaWslDriver)(nil)
+
+func New(inst *store.Instance, sshLocalPort int) *LimaWslDriver {
 	port, err := freeport.VSock()
 	if err != nil {
 		logrus.WithError(err).Error("failed to get free VSock port")
 	}
-	driver.VSockPort = port
-	driver.VirtioPort = ""
+
 	return &LimaWslDriver{
-		BaseDriver: driver,
+		Instance:     inst,
+		vSockPort:    port,
+		virtioPort:   "",
+		SSHLocalPort: sshLocalPort,
 	}
 }
 
@@ -123,10 +132,10 @@ func (l *LimaWslDriver) Start(ctx context.Context) (chan error, error) {
 	distroName := "lima-" + l.Instance.Name
 
 	if status == store.StatusUninitialized {
-		if err := EnsureFs(ctx, l.BaseDriver); err != nil {
+		if err := EnsureFs(ctx, l.Instance); err != nil {
 			return nil, err
 		}
-		if err := initVM(ctx, l.BaseDriver.Instance.Dir, distroName); err != nil {
+		if err := initVM(ctx, l.Instance.Dir, distroName); err != nil {
 			return nil, err
 		}
 	}
@@ -139,8 +148,8 @@ func (l *LimaWslDriver) Start(ctx context.Context) (chan error, error) {
 
 	if err := provisionVM(
 		ctx,
-		l.BaseDriver.Instance.Dir,
-		l.BaseDriver.Instance.Name,
+		l.Instance.Dir,
+		l.Instance.Name,
 		distroName,
 		errCh,
 	); err != nil {
@@ -197,7 +206,56 @@ func (l *LimaWslDriver) GuestAgentConn(ctx context.Context) (net.Conn, error) {
 	}
 	sockAddr := &winio.HvsockAddr{
 		VMID:      VMIDGUID,
-		ServiceID: winio.VsockServiceID(uint32(l.VSockPort)),
+		ServiceID: winio.VsockServiceID(uint32(l.vSockPort)),
 	}
 	return winio.Dial(ctx, sockAddr)
 }
+
+func (l *LimaWslDriver) Initialize(_ context.Context) error {
+	return nil
+}
+
+func (l *LimaWslDriver) CreateDisk(_ context.Context) error {
+	return nil
+}
+
+func (l *LimaWslDriver) Register(_ context.Context) error {
+	return nil
+}
+
+func (l *LimaWslDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
+	return nil
+}
+
+func (l *LimaWslDriver) GetDisplayConnection(_ context.Context) (string, error) {
+	return "", nil
+}
+
+func (l *LimaWslDriver) CreateSnapshot(_ context.Context, _ string) error {
+	return errors.New("unimplemented")
+}
+
+func (l *LimaWslDriver) ApplySnapshot(_ context.Context, _ string) error {
+	return errors.New("unimplemented")
+}
+
+func (l *LimaWslDriver) DeleteSnapshot(_ context.Context, _ string) error {
+	return errors.New("unimplemented")
+}
+
+func (l *LimaWslDriver) ListSnapshots(_ context.Context) (string, error) {
+	return "", errors.New("unimplemented")
+}
+
+func (l *LimaWslDriver) ForwardGuestAgent() bool {
+	// If driver is not providing, use host agent
+	return l.vSockPort == 0 && l.virtioPort == ""
+}
+
+func (l *LimaWslDriver) VSockPort() int {
+	return l.vSockPort
+}
+
+func (l *LimaWslDriver) VirtioPort() string {
+	return l.virtioPort
+}
diff --git a/pkg/driverutil/driverutil.go b/pkg/driverutil/driverutil.go
index eb27833e..1a957211 100644
--- a/pkg/driverutil/driverutil.go
+++ b/pkg/driverutil/driverutil.go
@@ -4,9 +4,9 @@
 package driverutil
 
 import (
+	"github.com/lima-vm/lima/pkg/driver/vz"
+	"github.com/lima-vm/lima/pkg/driver/wsl2"
 	"github.com/lima-vm/lima/pkg/limayaml"
-	"github.com/lima-vm/lima/pkg/vz"
-	"github.com/lima-vm/lima/pkg/wsl2"
 )
 
 // Drivers returns the available drivers.
diff --git a/pkg/driverutil/instance.go b/pkg/driverutil/instance.go
index d7c443ff..0d68f66e 100644
--- a/pkg/driverutil/instance.go
+++ b/pkg/driverutil/instance.go
@@ -5,19 +5,20 @@ package driverutil
 
 import (
 	"github.com/lima-vm/lima/pkg/driver"
+	"github.com/lima-vm/lima/pkg/driver/qemu"
+	"github.com/lima-vm/lima/pkg/driver/vz"
+	"github.com/lima-vm/lima/pkg/driver/wsl2"
 	"github.com/lima-vm/lima/pkg/limayaml"
-	"github.com/lima-vm/lima/pkg/qemu"
-	"github.com/lima-vm/lima/pkg/vz"
-	"github.com/lima-vm/lima/pkg/wsl2"
+	"github.com/lima-vm/lima/pkg/store"
 )
 
-func CreateTargetDriverInstance(base *driver.BaseDriver) driver.Driver {
-	limaDriver := base.Instance.Config.VMType
+func CreateTargetDriverInstance(inst *store.Instance, sshLocalPort int) driver.Driver {
+	limaDriver := inst.Config.VMType
 	if *limaDriver == limayaml.VZ {
-		return vz.New(base)
+		return vz.New(inst, sshLocalPort)
 	}
 	if *limaDriver == limayaml.WSL2 {
-		return wsl2.New(base)
+		return wsl2.New(inst, sshLocalPort)
 	}
-	return qemu.New(base)
+	return qemu.New(inst, sshLocalPort)
 }
diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go
index 908c8c6a..7ae27aaa 100644
--- a/pkg/hostagent/hostagent.go
+++ b/pkg/hostagent/hostagent.go
@@ -131,13 +131,9 @@ func New(instName string, stdout io.Writer, signalCh chan os.Signal, opts ...Opt
 		}
 	}
 
-	baseDriver := driver.BaseDriver{
-		Instance:     inst,
-		SSHLocalPort: sshLocalPort,
-	}
-	limaDriver := driverutil.CreateTargetDriverInstance(&baseDriver)
-	vSockPort := baseDriver.VSockPort
-	virtioPort := baseDriver.VirtioPort
+	limaDriver := driverutil.CreateTargetDriverInstance(inst, sshLocalPort)
+	vSockPort := limaDriver.VSockPort()
+	virtioPort := limaDriver.VirtioPort()
 
 	if err := cidata.GenerateCloudConfig(inst.Dir, instName, inst.Config); err != nil {
 		return nil, err
diff --git a/pkg/imgutil/proxyimgutil/proxyimgutil.go b/pkg/imgutil/proxyimgutil/proxyimgutil.go
index 7ff88abc..0c56ca82 100644
--- a/pkg/imgutil/proxyimgutil/proxyimgutil.go
+++ b/pkg/imgutil/proxyimgutil/proxyimgutil.go
@@ -10,7 +10,7 @@ import (
 
 	"github.com/lima-vm/lima/pkg/imgutil"
 	"github.com/lima-vm/lima/pkg/imgutil/nativeimgutil"
-	"github.com/lima-vm/lima/pkg/imgutil/qemuimgutil"
+	"github.com/lima-vm/lima/pkg/qemuimgutil"
 )
 
 // ImageDiskManager is a proxy implementation of imgutil.ImageDiskManager that uses both QEMU and native image utilities.
diff --git a/pkg/instance/create.go b/pkg/instance/create.go
index b9088785..7b69ccc3 100644
--- a/pkg/instance/create.go
+++ b/pkg/instance/create.go
@@ -11,7 +11,6 @@ import (
 	"path/filepath"
 
 	"github.com/lima-vm/lima/pkg/cidata"
-	"github.com/lima-vm/lima/pkg/driver"
 	"github.com/lima-vm/lima/pkg/driverutil"
 	"github.com/lima-vm/lima/pkg/limayaml"
 	"github.com/lima-vm/lima/pkg/osutil"
@@ -76,9 +75,7 @@ func Create(ctx context.Context, instName string, instConfig []byte, saveBrokenY
 		return nil, err
 	}
 
-	limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{
-		Instance: inst,
-	})
+	limaDriver := driverutil.CreateTargetDriverInstance(inst, 0)
 
 	if err := limaDriver.Register(ctx); err != nil {
 		return nil, err
diff --git a/pkg/instance/delete.go b/pkg/instance/delete.go
index c4078d48..ca9a8ae3 100644
--- a/pkg/instance/delete.go
+++ b/pkg/instance/delete.go
@@ -9,7 +9,6 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/lima-vm/lima/pkg/driver"
 	"github.com/lima-vm/lima/pkg/driverutil"
 	"github.com/lima-vm/lima/pkg/store"
 )
@@ -37,9 +36,7 @@ func Delete(ctx context.Context, inst *store.Instance, force bool) error {
 }
 
 func unregister(ctx context.Context, inst *store.Instance) error {
-	limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{
-		Instance: inst,
-	})
+	limaDriver := driverutil.CreateTargetDriverInstance(inst, 0)
 
 	return limaDriver.Unregister(ctx)
 }
diff --git a/pkg/instance/start.go b/pkg/instance/start.go
index acd3741e..d0c344fc 100644
--- a/pkg/instance/start.go
+++ b/pkg/instance/start.go
@@ -90,9 +90,7 @@ func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) {
 			return nil, err
 		}
 	}
-	limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{
-		Instance: inst,
-	})
+	limaDriver := driverutil.CreateTargetDriverInstance(inst, 0)
 
 	if err := limaDriver.Validate(); err != nil {
 		return nil, err
@@ -107,6 +105,43 @@ func Prepare(ctx context.Context, inst *store.Instance) (*Prepared, error) {
 	_, err := os.Stat(baseDisk)
 	created := err == nil
 
+	kernel := filepath.Join(inst.Dir, filenames.Kernel)
+	kernelCmdline := filepath.Join(inst.Dir, filenames.KernelCmdline)
+	initrd := filepath.Join(inst.Dir, filenames.Initrd)
+	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
+			}
+			if f.Kernel != nil {
+				if _, err := fileutils.DownloadFile(ctx, kernel, f.Kernel.File, false, "the kernel", *inst.Config.Arch); err != nil {
+					errs[i] = err
+					continue
+				}
+				if f.Kernel.Cmdline != "" {
+					if err := os.WriteFile(kernelCmdline, []byte(f.Kernel.Cmdline), 0o644); err != nil {
+						errs[i] = err
+						continue
+					}
+				}
+			}
+			if f.Initrd != nil {
+				if _, err := fileutils.DownloadFile(ctx, initrd, *f.Initrd, false, "the initrd", *inst.Config.Arch); err != nil {
+					errs[i] = err
+					continue
+				}
+			}
+			ensuredBaseDisk = true
+			break
+		}
+		if !ensuredBaseDisk {
+			return nil, fileutils.Errors(errs)
+		}
+	}
+
 	if err := limaDriver.CreateDisk(ctx); err != nil {
 		return nil, err
 	}
diff --git a/pkg/limayaml/containerd.yaml b/pkg/limayaml/containerd.yaml
index 43163b53..b78207d1 100644
--- a/pkg/limayaml/containerd.yaml
+++ b/pkg/limayaml/containerd.yaml
@@ -1,9 +1,9 @@
 archives:
-- location: https://github.com/containerd/nerdctl/releases/download/v2.1.2/nerdctl-full-2.1.2-linux-amd64.tar.gz
+- location: https://github.com/containerd/nerdctl/releases/download/v2.1.3/nerdctl-full-2.1.3-linux-amd64.tar.gz
   arch: x86_64
-  digest: sha256:b3ab8564c8fa6feb89d09bee881211b700b047373c767bec38256d0d68f93074
-- location: https://github.com/containerd/nerdctl/releases/download/v2.1.2/nerdctl-full-2.1.2-linux-arm64.tar.gz
+  digest: sha256:e4750ba025c1a9b3d365285f800e683287ee782f0a878bc8c395f28d7bc194ba
+- location: https://github.com/containerd/nerdctl/releases/download/v2.1.3/nerdctl-full-2.1.3-linux-arm64.tar.gz
   arch: aarch64
-  digest: sha256:1b52f32b7d5bbf63005bceb6a3cacd237d2fa8f1d05bb590e8ce58731779b9ee
+  digest: sha256:544fa1e518155fcc01a117ea49819d12d96b4dacfb2b62922f9f7956dc9f6dc8
 # No arm-v7
 # No riscv64
diff --git a/pkg/networks/networks.go b/pkg/networks/networks.go
index 12a1555d..717627fb 100644
--- a/pkg/networks/networks.go
+++ b/pkg/networks/networks.go
@@ -6,15 +6,15 @@ package networks
 import "net"
 
 type Config struct {
-	Paths    Paths              `yaml:"paths"`
-	Group    string             `yaml:"group,omitempty"` // default: "everyone"
-	Networks map[string]Network `yaml:"networks"`
+	Paths    Paths              `yaml:"paths" json:"paths"`
+	Group    string             `yaml:"group,omitempty" json:"group,omitempty"` // default: "everyone"
+	Networks map[string]Network `yaml:"networks" json:"networks"`
 }
 
 type Paths struct {
-	SocketVMNet string `yaml:"socketVMNet"`
-	VarRun      string `yaml:"varRun"`
-	Sudoers     string `yaml:"sudoers,omitempty"`
+	SocketVMNet string `yaml:"socketVMNet" json:"socketVMNet"`
+	VarRun      string `yaml:"varRun" json:"varRun"`
+	Sudoers     string `yaml:"sudoers,omitempty" json:"sudoers,omitempty"`
 }
 
 const (
@@ -24,10 +24,17 @@ const (
 	ModeBridged = "bridged"
 )
 
+var Modes = []string{
+	ModeUserV2,
+	ModeHost,
+	ModeShared,
+	ModeBridged,
+}
+
 type Network struct {
-	Mode      string `yaml:"mode"`                // "host", "shared", or "bridged"
-	Interface string `yaml:"interface,omitempty"` // only used by "bridged" networks
-	Gateway   net.IP `yaml:"gateway,omitempty"`   // only used by "host" and "shared" networks
-	DHCPEnd   net.IP `yaml:"dhcpEnd,omitempty"`   // default: same as Gateway, last byte is 254
-	NetMask   net.IP `yaml:"netmask,omitempty"`   // default: 255.255.255.0
+	Mode      string `yaml:"mode" json:"mode"`                               // "user-v2", "host", "shared", or "bridged"
+	Interface string `yaml:"interface,omitempty" json:"interface,omitempty"` // only used by "bridged" networks
+	Gateway   net.IP `yaml:"gateway,omitempty" json:"gateway,omitempty"`     // only used by "user-v2", "host" and "shared" networks
+	DHCPEnd   net.IP `yaml:"dhcpEnd,omitempty" json:"dhcpEnd,omitempty"`     // default: same as Gateway, last byte is 254
+	NetMask   net.IP `yaml:"netmask,omitempty" json:"netmask,omitempty"`     // default: 255.255.255.0
 }
diff --git a/pkg/networks/usernet/client.go b/pkg/networks/usernet/client.go
index 2296f103..8bc478cb 100644
--- a/pkg/networks/usernet/client.go
+++ b/pkg/networks/usernet/client.go
@@ -17,10 +17,10 @@ import (
 	gvproxyclient "github.com/containers/gvisor-tap-vsock/pkg/client"
 	"github.com/containers/gvisor-tap-vsock/pkg/types"
 
-	"github.com/lima-vm/lima/pkg/driver"
 	"github.com/lima-vm/lima/pkg/httpclientutil"
 	"github.com/lima-vm/lima/pkg/limayaml"
 	"github.com/lima-vm/lima/pkg/networks/usernet/dnshosts"
+	"github.com/lima-vm/lima/pkg/store"
 )
 
 type Client struct {
@@ -32,18 +32,18 @@ type Client struct {
 	subnet   net.IP
 }
 
-func (c *Client) ConfigureDriver(ctx context.Context, driver *driver.BaseDriver) error {
-	macAddress := limayaml.MACAddress(driver.Instance.Dir)
+func (c *Client) ConfigureDriver(ctx context.Context, inst *store.Instance, sshLocalPort int) error {
+	macAddress := limayaml.MACAddress(inst.Dir)
 	ipAddress, err := c.ResolveIPAddress(ctx, macAddress)
 	if err != nil {
 		return err
 	}
-	err = c.ResolveAndForwardSSH(ipAddress, driver.SSHLocalPort)
+	err = c.ResolveAndForwardSSH(ipAddress, sshLocalPort)
 	if err != nil {
 		return err
 	}
-	hosts := driver.Instance.Config.HostResolver.Hosts
-	hosts[fmt.Sprintf("%s.internal", driver.Instance.Hostname)] = ipAddress
+	hosts := inst.Config.HostResolver.Hosts
+	hosts[fmt.Sprintf("%s.internal", inst.Hostname)] = ipAddress
 	err = c.AddDNSHosts(hosts)
 	return err
 }
diff --git a/pkg/imgutil/qemuimgutil/qemuimgutil.go b/pkg/qemuimgutil/qemuimgutil.go
similarity index 100%
rename from pkg/imgutil/qemuimgutil/qemuimgutil.go
rename to pkg/qemuimgutil/qemuimgutil.go
diff --git a/pkg/snapshot/snapshot.go b/pkg/snapshot/snapshot.go
index 520a41d9..9c48fc0c 100644
--- a/pkg/snapshot/snapshot.go
+++ b/pkg/snapshot/snapshot.go
@@ -6,35 +6,26 @@ package snapshot
 import (
 	"context"
 
-	"github.com/lima-vm/lima/pkg/driver"
 	"github.com/lima-vm/lima/pkg/driverutil"
 	"github.com/lima-vm/lima/pkg/store"
 )
 
 func Del(ctx context.Context, inst *store.Instance, tag string) error {
-	limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{
-		Instance: inst,
-	})
+	limaDriver := driverutil.CreateTargetDriverInstance(inst, 0)
 	return limaDriver.DeleteSnapshot(ctx, tag)
 }
 
 func Save(ctx context.Context, inst *store.Instance, tag string) error {
-	limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{
-		Instance: inst,
-	})
+	limaDriver := driverutil.CreateTargetDriverInstance(inst, 0)
 	return limaDriver.CreateSnapshot(ctx, tag)
 }
 
 func Load(ctx context.Context, inst *store.Instance, tag string) error {
-	limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{
-		Instance: inst,
-	})
+	limaDriver := driverutil.CreateTargetDriverInstance(inst, 0)
 	return limaDriver.ApplySnapshot(ctx, tag)
 }
 
 func List(ctx context.Context, inst *store.Instance) (string, error) {
-	limaDriver := driverutil.CreateTargetDriverInstance(&driver.BaseDriver{
-		Instance: inst,
-	})
+	limaDriver := driverutil.CreateTargetDriverInstance(inst, 0)
 	return limaDriver.ListSnapshots(ctx)
 }
diff --git a/pkg/vz/disk.go b/pkg/vz/disk.go
deleted file mode 100644
index 06194faf..00000000
--- a/pkg/vz/disk.go
+++ /dev/null
@@ -1,95 +0,0 @@
-// SPDX-FileCopyrightText: Copyright The Lima Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package vz
-
-import (
-	"context"
-	"errors"
-	"fmt"
-	"os"
-	"path/filepath"
-
-	"github.com/docker/go-units"
-
-	"github.com/lima-vm/lima/pkg/driver"
-	"github.com/lima-vm/lima/pkg/fileutils"
-	"github.com/lima-vm/lima/pkg/imgutil/proxyimgutil"
-	"github.com/lima-vm/lima/pkg/iso9660util"
-	"github.com/lima-vm/lima/pkg/store/filenames"
-)
-
-func EnsureDisk(ctx context.Context, driver *driver.BaseDriver) error {
-	diffDisk := filepath.Join(driver.Instance.Dir, filenames.DiffDisk)
-	if _, err := os.Stat(diffDisk); err == nil || !errors.Is(err, os.ErrNotExist) {
-		// disk is already ensured
-		return err
-	}
-
-	diskUtil := proxyimgutil.NewDiskUtil()
-
-	baseDisk := filepath.Join(driver.Instance.Dir, filenames.BaseDisk)
-	kernel := filepath.Join(driver.Instance.Dir, filenames.Kernel)
-	kernelCmdline := filepath.Join(driver.Instance.Dir, filenames.KernelCmdline)
-	initrd := filepath.Join(driver.Instance.Dir, filenames.Initrd)
-	if _, err := os.Stat(baseDisk); errors.Is(err, os.ErrNotExist) {
-		var ensuredBaseDisk bool
-		errs := make([]error, len(driver.Instance.Config.Images))
-		for i, f := range driver.Instance.Config.Images {
-			if _, err := fileutils.DownloadFile(ctx, baseDisk, f.File, true, "the image", *driver.Instance.Config.Arch); err != nil {
-				errs[i] = err
-				continue
-			}
-			if f.Kernel != nil {
-				// ensure decompress kernel because vz expects it to be decompressed
-				if _, err := fileutils.DownloadFile(ctx, kernel, f.Kernel.File, true, "the kernel", *driver.Instance.Config.Arch); err != nil {
-					errs[i] = err
-					continue
-				}
-				if f.Kernel.Cmdline != "" {
-					if err := os.WriteFile(kernelCmdline, []byte(f.Kernel.Cmdline), 0o644); err != nil {
-						errs[i] = err
-						continue
-					}
-				}
-			}
-			if f.Initrd != nil {
-				if _, err := fileutils.DownloadFile(ctx, initrd, *f.Initrd, false, "the initrd", *driver.Instance.Config.Arch); err != nil {
-					errs[i] = err
-					continue
-				}
-			}
-			ensuredBaseDisk = true
-			break
-		}
-		if !ensuredBaseDisk {
-			return fileutils.Errors(errs)
-		}
-	}
-	diskSize, _ := units.RAMInBytes(*driver.Instance.Config.Disk)
-	if diskSize == 0 {
-		return nil
-	}
-	isBaseDiskISO, err := iso9660util.IsISO9660(baseDisk)
-	if err != nil {
-		return err
-	}
-	if isBaseDiskISO {
-		// Create an empty data volume (sparse)
-		diffDiskF, err := os.Create(diffDisk)
-		if err != nil {
-			return err
-		}
-
-		err = diskUtil.MakeSparse(diffDiskF, 0)
-		if err != nil {
-			diffDiskF.Close()
-			return fmt.Errorf("failed to create sparse diff disk %q: %w", diffDisk, err)
-		}
-		return diffDiskF.Close()
-	}
-	if err = diskUtil.ConvertToRaw(baseDisk, diffDisk, &diskSize, false); err != nil {
-		return fmt.Errorf("failed to convert %q to a raw disk %q: %w", baseDisk, diffDisk, err)
-	}
-	return err
-}
diff --git a/pkg/vz/vz_driver_others.go b/pkg/vz/vz_driver_others.go
deleted file mode 100644
index 66265628..00000000
--- a/pkg/vz/vz_driver_others.go
+++ /dev/null
@@ -1,43 +0,0 @@
-//go:build !darwin || no_vz
-
-// SPDX-FileCopyrightText: Copyright The Lima Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package vz
-
-import (
-	"context"
-	"errors"
-
-	"github.com/lima-vm/lima/pkg/driver"
-)
-
-var ErrUnsupported = errors.New("vm driver 'vz' needs macOS 13 or later (Hint: try recompiling Lima if you are seeing this error on macOS 13)")
-
-const Enabled = false
-
-type LimaVzDriver struct {
-	*driver.BaseDriver
-}
-
-func New(driver *driver.BaseDriver) *LimaVzDriver {
-	return &LimaVzDriver{
-		BaseDriver: driver,
-	}
-}
-
-func (l *LimaVzDriver) Validate() error {
-	return ErrUnsupported
-}
-
-func (l *LimaVzDriver) CreateDisk(_ context.Context) error {
-	return ErrUnsupported
-}
-
-func (l *LimaVzDriver) Start(_ context.Context) (chan error, error) {
-	return nil, ErrUnsupported
-}
-
-func (l *LimaVzDriver) Stop(_ context.Context) error {
-	return ErrUnsupported
-}
diff --git a/pkg/wsl2/wsl_driver_others.go b/pkg/wsl2/wsl_driver_others.go
deleted file mode 100644
index 4ad86d02..00000000
--- a/pkg/wsl2/wsl_driver_others.go
+++ /dev/null
@@ -1,43 +0,0 @@
-//go:build !windows || no_wsl
-
-// SPDX-FileCopyrightText: Copyright The Lima Authors
-// SPDX-License-Identifier: Apache-2.0
-
-package wsl2
-
-import (
-	"context"
-	"errors"
-
-	"github.com/lima-vm/lima/pkg/driver"
-)
-
-var ErrUnsupported = errors.New("vm driver 'wsl2' requires Windows 10 build 19041 or later (Hint: try recompiling Lima if you are seeing this error on Windows 10+)")
-
-const Enabled = false
-
-type LimaWslDriver struct {
-	*driver.BaseDriver
-}
-
-func New(driver *driver.BaseDriver) *LimaWslDriver {
-	return &LimaWslDriver{
-		BaseDriver: driver,
-	}
-}
-
-func (l *LimaWslDriver) Validate() error {
-	return ErrUnsupported
-}
-
-func (l *LimaWslDriver) CreateDisk(_ context.Context) error {
-	return ErrUnsupported
-}
-
-func (l *LimaWslDriver) Start(_ context.Context) (chan error, error) {
-	return nil, ErrUnsupported
-}
-
-func (l *LimaWslDriver) Stop(_ context.Context) error {
-	return ErrUnsupported
-}



Test patch:
diff --git a/pkg/qemu/qemu_test.go b/pkg/driver/qemu/qemu_test.go
similarity index 100%
rename from pkg/qemu/qemu_test.go
rename to pkg/driver/qemu/qemu_test.go
diff --git a/pkg/vz/network_darwin_test.go b/pkg/driver/vz/network_darwin_test.go
similarity index 100%
rename from pkg/vz/network_darwin_test.go
rename to pkg/driver/vz/network_darwin_test.go
diff --git a/pkg/imgutil/qemuimgutil/qemuimgutil_test.go b/pkg/qemuimgutil/qemuimgutil_test.go
similarity index 100%
rename from pkg/imgutil/qemuimgutil/qemuimgutil_test.go
rename to pkg/qemuimgutil/qemuimgutil_test.go