Repository: lima-vm/lima

Issue:
Provide standard values "default" and "none" for most options
### Description

It would be nice to be able to use simple defaults like "default".

```yaml
audio:
  # EXPERIMENTAL
  # QEMU audiodev, e.g., "none", "coreaudio", "pa", "alsa", "oss".
  # Choosing "none" will mute the audio output, and not play any sound.
  # Choosing "default" will pick the suitable of coreudio, pa, dsound.
  # 🟢 Builtin default: ""
  device: default

video:
  # QEMU display, e.g., "none", "cocoa", "sdl", "gtk", "vnc", "default".
  # Choosing "none" will hide the video output, and not show any window.
  # Choosing "default" will pick the first available of: gtk, sdl, cocoa.
  # 🟢 Builtin default: "none"
  display: default
```



Instead of having to remember gtk and pa, or vz, or whatever...

* https://github.com/lima-vm/lima/pull/990
* https://github.com/lima-vm/lima/pull/2456

Note: Vz will not support the QEMU device/driver names.
But it should still honor the "none" and "default" strings.

Discussion:
@afbjorklund: Other PRs used the same ("default") also for images and mounts:

```yaml
images:
- default

mounts:
- default
```

But those are arrays, so "none" is usually just written out as `[]`.

One _could_ have the string (`"none"`) expand to the empty list?@afbjorklund: New syntax:

```yaml
images: []
mounts: []

base:
- template://_images/ubuntu
- template://_default/mounts
```

Solution patch:
diff --git a/.codespellrc b/.codespellrc
index 141e7610..c3f69b58 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -6,7 +6,7 @@
 skip = .git,*.pb.desc,*/node_modules/*,*/public/js/*,*/public/scss/*
 
 # Comma separated list of words to be ignored. Words must be lowercased.
-ignore-words-list = ans,distroname,testof,hda,ststr
+ignore-words-list = ans,distroname,testof,hda,ststr,archtypes
 
 # Check file names as well.
 check-filenames = true
diff --git a/.gitignore b/.gitignore
index 344e50b3..086c11c8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 _output/
 _artifacts/
 lima.REJECTED.yaml
+schema-limayaml.json
 .config
diff --git a/Makefile b/Makefile
index 4ba40c18..8f819545 100644
--- a/Makefile
+++ b/Makefile
@@ -421,6 +421,14 @@ ifeq ($(native_compiling),true)
 		--output _output --prefix $(PREFIX)
 endif
 
+################################################################################
+schema-limayaml.json: _output/bin/limactl$(exe)
+	$< generate-jsonschema >$@
+
+.PHONY: check-jsonschema
+check-jsonschema: schema-limayaml.json
+	check-jsonschema --schemafile $< examples/default.yaml
+
 ################################################################################
 .PHONY: diagrams
 diagrams: docs/lima-sequence-diagram.png
diff --git a/cmd/limactl/genschema.go b/cmd/limactl/genschema.go
new file mode 100644
index 00000000..687fb5e2
--- /dev/null
+++ b/cmd/limactl/genschema.go
@@ -0,0 +1,59 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+
+	"github.com/invopop/jsonschema"
+	"github.com/lima-vm/lima/pkg/limayaml"
+	"github.com/spf13/cobra"
+	orderedmap "github.com/wk8/go-ordered-map/v2"
+)
+
+func newGenSchemaCommand() *cobra.Command {
+	genschemaCommand := &cobra.Command{
+		Use:    "generate-jsonschema",
+		Short:  "Generate json-schema document",
+		Args:   WrapArgsError(cobra.NoArgs),
+		RunE:   genschemaAction,
+		Hidden: true,
+	}
+	return genschemaCommand
+}
+
+func toAny(args []string) []any {
+	result := []any{nil}
+	for _, arg := range args {
+		result = append(result, arg)
+	}
+	return result
+}
+
+func getProp(props *orderedmap.OrderedMap[string, *jsonschema.Schema], key string) *jsonschema.Schema {
+	value, ok := props.Get(key)
+	if !ok {
+		return nil
+	}
+	return value
+}
+
+func genschemaAction(cmd *cobra.Command, _ []string) error {
+	schema := jsonschema.Reflect(&limayaml.LimaYAML{})
+	// allow Disk to be either string (name) or object (struct)
+	schema.Definitions["Disk"].Type = "" // was: "object"
+	schema.Definitions["Disk"].OneOf = []*jsonschema.Schema{
+		{Type: "string"},
+		{Type: "object"},
+	}
+	properties := schema.Definitions["LimaYAML"].Properties
+	getProp(properties, "os").Enum = toAny(limayaml.OSTypes)
+	getProp(properties, "arch").Enum = toAny(limayaml.ArchTypes)
+	getProp(properties, "mountType").Enum = toAny(limayaml.MountTypes)
+	getProp(properties, "vmType").Enum = toAny(limayaml.VMTypes)
+	j, err := json.MarshalIndent(schema, "", "    ")
+	if err != nil {
+		return err
+	}
+	_, err = fmt.Fprintln(cmd.OutOrStdout(), string(j))
+	return err
+}
diff --git a/cmd/limactl/main.go b/cmd/limactl/main.go
index d069fbe8..24967b12 100644
--- a/cmd/limactl/main.go
+++ b/cmd/limactl/main.go
@@ -150,6 +150,7 @@ func newApp() *cobra.Command {
 		newDiskCommand(),
 		newUsernetCommand(),
 		newGenDocCommand(),
+		newGenSchemaCommand(),
 		newSnapshotCommand(),
 		newProtectCommand(),
 		newUnprotectCommand(),
diff --git a/examples/default.yaml b/examples/default.yaml
index 3d7d35df..802ab5f9 100644
--- a/examples/default.yaml
+++ b/examples/default.yaml
@@ -32,7 +32,7 @@ os: null
 arch: null
 
 # OpenStack-compatible disk image.
-# 🟢 Builtin default: null (must be specified)
+# 🟢 Builtin default: none (must be specified)
 # 🔵 This file: Ubuntu images
 images:
 # Try to use release-yyyyMMdd image if available. Note that release-yyyyMMdd will be removed after several months.
@@ -74,7 +74,7 @@ disk: null
 # Expose host directories to the guest, the mount point might be accessible from all UIDs in the guest
 # "location" can use these template variables: {{.Home}}, {{.Dir}}, {{.Name}}, {{.UID}}, {{.User}}, and {{.Param.Key}}.
 # "mountPoint" can use these template variables: {{.Home}}, {{.Name}}, {{.UID}}, {{.User}}, and {{.Param.Key}}
-# 🟢 Builtin default: null (Mount nothing)
+# 🟢 Builtin default: [] (Mount nothing)
 # 🔵 This file: Mount the home as read-only, /tmp/lima as writable
 mounts:
 - location: "~"
@@ -123,8 +123,9 @@ mounts:
 
 # List of mount types not supported by the kernel of this distro.
 # Also used to resolve the default mount type when not explicitly specified.
-# 🟢 Builtin default: null
-mountTypesUnsupported: null
+# 🟢 Builtin default: []
+mountTypesUnsupported:
+# - "9p"
 
 # Mount type for above mounts, such as "reverse-sshfs" (from sshocker), "9p" (QEMU’s virtio-9p-pci, aka virtfs),
 # or "virtiofs" (experimental on Linux; needs `vmType: vz` on macOS).
@@ -139,7 +140,7 @@ mountInotify: null
 # instance, labeled by name. (e.g. if the disk is named "data", it will be labeled
 # "lima-data" inside the instance). The disk will be mounted inside the instance at
 # `/mnt/lima-${VOLUME}`.
-# 🟢 Builtin default: null
+# 🟢 Builtin default: []
 additionalDisks:
 # disks should either be a list of disk name strings, for example:
 # - "data"
@@ -222,7 +223,7 @@ containerd:
 # Provisioning scripts need to be idempotent because they might be called
 # multiple times, e.g. when the host VM is being restarted.
 # The scripts can use the following template variables: {{.Home}}, {{.UID}}, {{.User}}, and {{.Param.Key}}
-# 🟢 Builtin default: null
+# 🟢 Builtin default: []
 # provision:
 # # `system` is executed with root privileges
 # - mode: system
@@ -265,7 +266,7 @@ containerd:
 # Probe scripts to check readiness.
 # The scripts run in user mode. They must start with a '#!' line.
 # The scripts can use the following template variables: {{.Home}}, {{.UID}}, {{.User}}, and {{.Param.Key}}
-# 🟢 Builtin default: null
+# 🟢 Builtin default: []
 # probes:
 # # Only `readiness` probes are supported right now.
 # - mode: readiness
@@ -352,7 +353,7 @@ video:
 
 # The instance can get routable IP addresses from the vmnet framework using
 # https://github.com/lima-vm/socket_vmnet.
-# 🟢 Builtin default: null
+# 🟢 Builtin default: []
 networks:
 # Lima can manage daemons for networks defined in $LIMA_HOME/_config/networks.yaml
 # automatically. The socket_vmnet binary must be installed into
@@ -435,7 +436,7 @@ networks:
 # The same template variables as for listing instances can be used, for example {{.Dir}}.
 # You can view the complete list of variables using `limactl list --list-fields` command.
 # It also includes {{.HostOS}} and {{.HostArch}} vars, for the runtime GOOS and GOARCH.
-# 🟢 Builtin default: null
+# 🟢 Builtin default: ""
 # message: |
 #   This will be shown to the user.
 
@@ -444,7 +445,7 @@ networks:
 # to /etc/environment.
 # If you set any of "ftp_proxy", "http_proxy", "https_proxy", or "no_proxy", then
 # Lima will automatically set an uppercase variant to the same value as well.
-# 🟢 Builtin default: null
+# 🟢 Builtin default: {}
 # env:
 #   KEY: value
 
@@ -480,7 +481,7 @@ hostResolver:
   # Static names can be defined here as an alternative to adding them to the hosts /etc/hosts.
   # Values can be either other hostnames, or IP addresses. The host.lima.internal name is
   # predefined to specify the gateway address to the host.
-  # 🟢 Builtin default: null
+  # 🟢 Builtin default: {}
   hosts:
   #   guest.name: 127.1.1.1
   #   host.name: host.lima.internal
@@ -492,7 +493,7 @@ hostResolver:
 # that has an IPv4 address, to the list. In case this still doesn't work (e.g. VPN
 # setups), the servers can be specified here explicitly. If nameservers are specified
 # here, then the configuration from network preferences will be ignored.
-# 🟢 Builtin default: null
+# 🟢 Builtin default: []
 # dns:
 # - 1.1.1.1
 # - 1.0.0.1
diff --git a/go.mod b/go.mod
index b543014d..3ba0094d 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ go 1.22.0
 require (
 	al.essio.dev/pkg/shellescape v1.5.1
 	github.com/AlecAivazis/survey/v2 v2.3.7
-	github.com/Code-Hex/vz/v3 v3.1.0
+	github.com/Code-Hex/vz/v3 v3.2.0
 	github.com/Microsoft/go-winio v0.6.2
 	github.com/apparentlymart/go-cidr v1.1.0
 	github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e
@@ -24,6 +24,7 @@ require (
 	github.com/goccy/go-yaml v1.12.0
 	github.com/google/go-cmp v0.6.0
 	github.com/google/yamlfmt v0.13.0
+	github.com/invopop/jsonschema v0.12.0
 	github.com/lima-vm/go-qcow2reader v0.2.0
 	github.com/lima-vm/sshocker v0.3.4
 	github.com/mattn/go-isatty v0.0.20
@@ -39,6 +40,7 @@ require (
 	github.com/sirupsen/logrus v1.9.4-0.20230606125235-dd1b4c2e81af
 	github.com/spf13/cobra v1.8.1
 	github.com/spf13/pflag v1.0.5
+	github.com/wk8/go-ordered-map/v2 v2.1.8
 	golang.org/x/net v0.30.0
 	golang.org/x/sync v0.8.0
 	golang.org/x/sys v0.26.0
@@ -57,8 +59,10 @@ require (
 	github.com/VividCortex/ewma v1.2.0 // indirect
 	github.com/a8m/envsubst v1.4.2 // indirect
 	github.com/alecthomas/participle/v2 v2.1.1 // indirect
+	github.com/bahlo/generic-list-go v0.2.0 // indirect
 	github.com/bmatcuk/doublestar/v4 v4.6.0 // indirect
 	github.com/braydonk/yaml v0.7.0 // indirect
+	github.com/buger/jsonparser v1.1.1 // indirect
 	github.com/containerd/errdefs v0.3.0 // indirect
 	github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
 	github.com/digitalocean/go-libvirt v0.0.0-20220804181439-8648fbde413e // indirect
@@ -139,7 +143,3 @@ require (
 // We can't just `require` github.com/inetaf/tcpproxy, as gvisor-tap-vsock
 // still imports inet.af/tcpproxy: https://github.com/containers/gvisor-tap-vsock/pull/399
 replace inet.af/tcpproxy => github.com/inetaf/tcpproxy v0.0.0-20240214030015-3ce58045626c
-
-// Nested virtualization support is yet to be merged into VZ https://github.com/Code-Hex/vz/pull/159.
-// We use our (temporary) fork to add the feature.
-replace github.com/Code-Hex/vz/v3 => github.com/lima-vm/vz/v3 v3.0.0-20241008080607-2a22b5e278ee
diff --git a/go.sum b/go.sum
index e73d47ec..cfb7aad1 100644
--- a/go.sum
+++ b/go.sum
@@ -4,6 +4,8 @@ github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkk
 github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
 github.com/Code-Hex/go-infinity-channel v1.0.0 h1:M8BWlfDOxq9or9yvF9+YkceoTkDI1pFAqvnP87Zh0Nw=
 github.com/Code-Hex/go-infinity-channel v1.0.0/go.mod h1:5yUVg/Fqao9dAjcpzoQ33WwfdMWmISOrQloDRn3bsvY=
+github.com/Code-Hex/vz/v3 v3.2.0 h1:1wwlfNaa8bXQql1e0oB9C8DiVi2uOKHZSZRiBo9lvQI=
+github.com/Code-Hex/vz/v3 v3.2.0/go.mod h1:WqWQuBbT4SbjO4C4GHG9m9HO8j5jecAmMh4eyVSEbEg=
 github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
 github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
 github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
@@ -21,12 +23,16 @@ github.com/alecthomas/repr v0.4.0/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW5
 github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU=
 github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc=
 github.com/armon/go-proxyproto v0.0.0-20210323213023-7e956b284f0a/go.mod h1:QmP9hvJ91BbJmGVGSbutW19IC0Q9phDCLGaomwTJbgU=
+github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
+github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
 github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e h1:IdMhFPEfTZQU971tIHx3UhY4l+yCeynprnINrDTSrOc=
 github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e/go.mod h1:aXGMJsd3XrnUFTuyf/pTGg5jG6CY8JMZ5juywvShjgQ=
 github.com/bmatcuk/doublestar/v4 v4.6.0 h1:HTuxyug8GyFbRkrffIpzNCSK4luc0TY3wzXvzIZhEXc=
 github.com/bmatcuk/doublestar/v4 v4.6.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
 github.com/braydonk/yaml v0.7.0 h1:ySkqO7r0MGoCNhiRJqE0Xe9yhINMyvOAB3nFjgyJn2k=
 github.com/braydonk/yaml v0.7.0/go.mod h1:hcm3h581tudlirk8XEUPDBAimBPbmnL0Y45hCRl47N4=
+github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
+github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
 github.com/cheggaaa/pb/v3 v3.1.5 h1:QuuUzeM2WsAqG2gMqtzaWithDJv0i+i6UlnwSCI4QLk=
 github.com/cheggaaa/pb/v3 v3.1.5/go.mod h1:CrxkeghYTXi1lQBEI7jSn+3svI3cuc19haAj6jM60XI=
 github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ=
@@ -140,6 +146,8 @@ github.com/inetaf/tcpproxy v0.0.0-20240214030015-3ce58045626c h1:gYfYE403/nlrGNY
 github.com/inetaf/tcpproxy v0.0.0-20240214030015-3ce58045626c/go.mod h1:Di7LXRyUcnvAcLicFhtM9/MlZl/TNgRSDHORM2c6CMI=
 github.com/insomniacslk/dhcp v0.0.0-20240710054256-ddd8a41251c9 h1:LZJWucZz7ztCqY6Jsu7N9g124iJ2kt/O62j3+UchZFg=
 github.com/insomniacslk/dhcp v0.0.0-20240710054256-ddd8a41251c9/go.mod h1:KclMyHxX06VrVr0DJmeFSUb1ankt7xTfoOA35pCkoic=
+github.com/invopop/jsonschema v0.12.0 h1:6ovsNSuvn9wEQVOyc72aycBMVQFKz7cPdMJn10CvzRI=
+github.com/invopop/jsonschema v0.12.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
 github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
 github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -169,8 +177,6 @@ github.com/lima-vm/go-qcow2reader v0.2.0 h1:PvhKCCAEfJa1b4wTDhlmey6VKQiQKJd+UXKy
 github.com/lima-vm/go-qcow2reader v0.2.0/go.mod h1:e3p29BzLT8hNh4jbLezdFAHU4eBijf0bm5GilStCRKE=
 github.com/lima-vm/sshocker v0.3.4 h1:5rn6vMkfqwZSZiBW+Udo505OIRhPB4xbLUDdEnFgWwI=
 github.com/lima-vm/sshocker v0.3.4/go.mod h1:QT4c7XNmeQTv79h5/8EgiS7U51B9BLenlXV7idCY0tE=
-github.com/lima-vm/vz/v3 v3.0.0-20241008080607-2a22b5e278ee h1:USiLYd9WbmtU1mPM0egUMrz9QVpMkblMyKioC2EsWCA=
-github.com/lima-vm/vz/v3 v3.0.0-20241008080607-2a22b5e278ee/go.mod h1:WqWQuBbT4SbjO4C4GHG9m9HO8j5jecAmMh4eyVSEbEg=
 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/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
@@ -272,6 +278,8 @@ github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8
 github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701/go.mod h1:P3a5rG4X7tI17Nn3aOIAYr5HbIMukwXG0urG0WuL8OA=
 github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
 github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
+github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
+github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
 github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
 github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
 github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go
index 722c1a6c..a35fccf7 100644
--- a/pkg/cidata/cidata.go
+++ b/pkg/cidata/cidata.go
@@ -202,7 +202,7 @@ func templateArgs(bootScripts bool, instDir, name string, instConfig *limayaml.L
 		if err != nil {
 			return nil, err
 		}
-		mountPoint, err := localpathutil.Expand(f.MountPoint)
+		mountPoint, err := localpathutil.Expand(*f.MountPoint)
 		if err != nil {
 			return nil, err
 		}
diff --git a/pkg/hostagent/mount.go b/pkg/hostagent/mount.go
index fda4fff2..646dc5d4 100644
--- a/pkg/hostagent/mount.go
+++ b/pkg/hostagent/mount.go
@@ -37,7 +37,7 @@ func (a *HostAgent) setupMount(m limayaml.Mount) (*mount, error) {
 		return nil, err
 	}
 
-	mountPoint, err := localpathutil.Expand(m.MountPoint)
+	mountPoint, err := localpathutil.Expand(*m.MountPoint)
 	if err != nil {
 		return nil, err
 	}
diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go
index 32817cee..40ff3671 100644
--- a/pkg/limayaml/defaults.go
+++ b/pkg/limayaml/defaults.go
@@ -619,10 +619,12 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
 		} else {
 			logrus.WithError(err).Warnf("Couldn't process mount location %q as a template", mount.Location)
 		}
-		if out, err := executeGuestTemplate(mount.MountPoint, instDir, y.Param); err == nil {
-			mount.MountPoint = out.String()
-		} else {
-			logrus.WithError(err).Warnf("Couldn't process mount point %q as a template", mount.MountPoint)
+		if mount.MountPoint != nil {
+			if out, err := executeGuestTemplate(*mount.MountPoint, instDir, y.Param); err == nil {
+				mount.MountPoint = ptr.Of(out.String())
+			} else {
+				logrus.WithError(err).Warnf("Couldn't process mount point %q as a template", *mount.MountPoint)
+			}
 		}
 		if i, ok := location[mount.Location]; ok {
 			if mount.SSHFS.Cache != nil {
@@ -652,7 +654,7 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
 			if mount.Writable != nil {
 				mounts[i].Writable = mount.Writable
 			}
-			if mount.MountPoint != "" {
+			if mount.MountPoint != nil {
 				mounts[i].MountPoint = mount.MountPoint
 			}
 		} else {
@@ -695,8 +697,8 @@ func FillDefault(y, d, o *LimaYAML, filePath string) {
 				mounts[i].NineP.Cache = ptr.Of(Default9pCacheForRO)
 			}
 		}
-		if mount.MountPoint == "" {
-			mounts[i].MountPoint = mount.Location
+		if mount.MountPoint == nil {
+			mounts[i].MountPoint = ptr.Of(mount.Location)
 		}
 	}
 
diff --git a/pkg/limayaml/limayaml.go b/pkg/limayaml/limayaml.go
index 469cb7a3..dc27723e 100644
--- a/pkg/limayaml/limayaml.go
+++ b/pkg/limayaml/limayaml.go
@@ -7,46 +7,46 @@ import (
 )
 
 type LimaYAML struct {
-	MinimumLimaVersion    *string       `yaml:"minimumLimaVersion,omitempty" json:"minimumLimaVersion,omitempty"`
-	VMType                *VMType       `yaml:"vmType,omitempty" json:"vmType,omitempty"`
+	MinimumLimaVersion    *string       `yaml:"minimumLimaVersion,omitempty" json:"minimumLimaVersion,omitempty" jsonschema:"nullable"`
+	VMType                *VMType       `yaml:"vmType,omitempty" json:"vmType,omitempty" jsonschema:"nullable"`
 	VMOpts                VMOpts        `yaml:"vmOpts,omitempty" json:"vmOpts,omitempty"`
-	OS                    *OS           `yaml:"os,omitempty" json:"os,omitempty"`
-	Arch                  *Arch         `yaml:"arch,omitempty" json:"arch,omitempty"`
+	OS                    *OS           `yaml:"os,omitempty" json:"os,omitempty" jsonschema:"nullable"`
+	Arch                  *Arch         `yaml:"arch,omitempty" json:"arch,omitempty" jsonschema:"nullable"`
 	Images                []Image       `yaml:"images" json:"images"` // REQUIRED
-	CPUType               CPUType       `yaml:"cpuType,omitempty" json:"cpuType,omitempty"`
-	CPUs                  *int          `yaml:"cpus,omitempty" json:"cpus,omitempty"`
-	Memory                *string       `yaml:"memory,omitempty" json:"memory,omitempty"` // go-units.RAMInBytes
-	Disk                  *string       `yaml:"disk,omitempty" json:"disk,omitempty"`     // go-units.RAMInBytes
-	AdditionalDisks       []Disk        `yaml:"additionalDisks,omitempty" json:"additionalDisks,omitempty"`
+	CPUType               CPUType       `yaml:"cpuType,omitempty" json:"cpuType,omitempty" jsonschema:"nullable"`
+	CPUs                  *int          `yaml:"cpus,omitempty" json:"cpus,omitempty" jsonschema:"nullable"`
+	Memory                *string       `yaml:"memory,omitempty" json:"memory,omitempty" jsonschema:"nullable"` // go-units.RAMInBytes
+	Disk                  *string       `yaml:"disk,omitempty" json:"disk,omitempty" jsonschema:"nullable"`     // go-units.RAMInBytes
+	AdditionalDisks       []Disk        `yaml:"additionalDisks,omitempty" json:"additionalDisks,omitempty" jsonschema:"nullable"`
 	Mounts                []Mount       `yaml:"mounts,omitempty" json:"mounts,omitempty"`
-	MountTypesUnsupported []string      `yaml:"mountTypesUnsupported,omitempty" json:"mountTypesUnsupported,omitempty"`
-	MountType             *MountType    `yaml:"mountType,omitempty" json:"mountType,omitempty"`
-	MountInotify          *bool         `yaml:"mountInotify,omitempty" json:"mountInotify,omitempty"`
+	MountTypesUnsupported []string      `yaml:"mountTypesUnsupported,omitempty" json:"mountTypesUnsupported,omitempty" jsonschema:"nullable"`
+	MountType             *MountType    `yaml:"mountType,omitempty" json:"mountType,omitempty" jsonschema:"nullable"`
+	MountInotify          *bool         `yaml:"mountInotify,omitempty" json:"mountInotify,omitempty" jsonschema:"nullable"`
 	SSH                   SSH           `yaml:"ssh,omitempty" json:"ssh,omitempty"` // REQUIRED (FIXME)
 	Firmware              Firmware      `yaml:"firmware,omitempty" json:"firmware,omitempty"`
 	Audio                 Audio         `yaml:"audio,omitempty" json:"audio,omitempty"`
 	Video                 Video         `yaml:"video,omitempty" json:"video,omitempty"`
 	Provision             []Provision   `yaml:"provision,omitempty" json:"provision,omitempty"`
-	UpgradePackages       *bool         `yaml:"upgradePackages,omitempty" json:"upgradePackages,omitempty"`
+	UpgradePackages       *bool         `yaml:"upgradePackages,omitempty" json:"upgradePackages,omitempty" jsonschema:"nullable"`
 	Containerd            Containerd    `yaml:"containerd,omitempty" json:"containerd,omitempty"`
-	GuestInstallPrefix    *string       `yaml:"guestInstallPrefix,omitempty" json:"guestInstallPrefix,omitempty"`
+	GuestInstallPrefix    *string       `yaml:"guestInstallPrefix,omitempty" json:"guestInstallPrefix,omitempty" jsonschema:"nullable"`
 	Probes                []Probe       `yaml:"probes,omitempty" json:"probes,omitempty"`
 	PortForwards          []PortForward `yaml:"portForwards,omitempty" json:"portForwards,omitempty"`
 	CopyToHost            []CopyToHost  `yaml:"copyToHost,omitempty" json:"copyToHost,omitempty"`
 	Message               string        `yaml:"message,omitempty" json:"message,omitempty"`
-	Networks              []Network     `yaml:"networks,omitempty" json:"networks,omitempty"`
+	Networks              []Network     `yaml:"networks,omitempty" json:"networks,omitempty" jsonschema:"nullable"`
 	// `network` was deprecated in Lima v0.7.0, removed in Lima v0.14.0. Use `networks` instead.
 	Env          map[string]string `yaml:"env,omitempty" json:"env,omitempty"`
 	Param        map[string]string `yaml:"param,omitempty" json:"param,omitempty"`
 	DNS          []net.IP          `yaml:"dns,omitempty" json:"dns,omitempty"`
 	HostResolver HostResolver      `yaml:"hostResolver,omitempty" json:"hostResolver,omitempty"`
 	// `useHostResolver` was deprecated in Lima v0.8.1, removed in Lima v0.14.0. Use `hostResolver.enabled` instead.
-	PropagateProxyEnv    *bool          `yaml:"propagateProxyEnv,omitempty" json:"propagateProxyEnv,omitempty"`
+	PropagateProxyEnv    *bool          `yaml:"propagateProxyEnv,omitempty" json:"propagateProxyEnv,omitempty" jsonschema:"nullable"`
 	CACertificates       CACertificates `yaml:"caCerts,omitempty" json:"caCerts,omitempty"`
 	Rosetta              Rosetta        `yaml:"rosetta,omitempty" json:"rosetta,omitempty"`
-	Plain                *bool          `yaml:"plain,omitempty" json:"plain,omitempty"`
-	TimeZone             *string        `yaml:"timezone,omitempty" json:"timezone,omitempty"`
-	NestedVirtualization *bool          `yaml:"nestedVirtualization,omitempty" json:"nestedVirtualization,omitempty"`
+	Plain                *bool          `yaml:"plain,omitempty" json:"plain,omitempty" jsonschema:"nullable"`
+	TimeZone             *string        `yaml:"timezone,omitempty" json:"timezone,omitempty" jsonschema:"nullable"`
+	NestedVirtualization *bool          `yaml:"nestedVirtualization,omitempty" json:"nestedVirtualization,omitempty" jsonschema:"nullable"`
 }
 
 type (
@@ -76,17 +76,24 @@ const (
 	WSL2 VMType = "wsl2"
 )
 
+var (
+	OSTypes    = []OS{LINUX}
+	ArchTypes  = []Arch{X8664, AARCH64, ARMV7L, RISCV64}
+	MountTypes = []MountType{REVSSHFS, NINEP, VIRTIOFS, WSLMount}
+	VMTypes    = []VMType{QEMU, VZ, WSL2}
+)
+
 type VMOpts struct {
 	QEMU QEMUOpts `yaml:"qemu,omitempty" json:"qemu,omitempty"`
 }
 
 type QEMUOpts struct {
-	MinimumVersion *string `yaml:"minimumVersion,omitempty" json:"minimumVersion,omitempty"`
+	MinimumVersion *string `yaml:"minimumVersion,omitempty" json:"minimumVersion,omitempty" jsonschema:"nullable"`
 }
 
 type Rosetta struct {
-	Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty"`
-	BinFmt  *bool `yaml:"binfmt,omitempty" json:"binfmt,omitempty"`
+	Enabled *bool `yaml:"enabled,omitempty" json:"enabled,omitempty" jsonschema:"nullable"`
+	BinFmt  *bool `yaml:"binfmt,omitempty" json:"binfmt,omitempty" jsonschema:"nullable"`
 }
 
 type File struct {
@@ -120,8 +127,8 @@ type Disk struct {
 
 type Mount struct {
 	Location   string   `yaml:"location" json:"location"` // REQUIRED
-	MountPoint string   `yaml:"mountPoint,omitempty" json:"mountPoint,omitempty"`
-	Writable   *bool    `yaml:"writable,omitempty" json:"writable,omitempty"`
+	MountPoint *string  `yaml:"mountPoint,omitempty" json:"mountPoint,omitempty" jsonschema:"nullable"`
+	Writable   *bool    `yaml:"writable,omitempty" json:"writable,omitempty" jsonschema:"nullable"`
 	SSHFS      SSHFS    `yaml:"sshfs,omitempty" json:"sshfs,omitempty"`
 	NineP      NineP    `yaml:"9p,omitempty" json:"9p,omitempty"`
 	Virtiofs   Virtiofs `yaml:"virtiofs,omitempty" json:"virtiofs,omitempty"`
@@ -135,16 +142,16 @@ const (
 )
 
 type SSHFS struct {
-	Cache          *bool       `yaml:"cache,omitempty" json:"cache,omitempty"`
-	FollowSymlinks *bool       `yaml:"followSymlinks,omitempty" json:"followSymlinks,omitempty"`
-	SFTPDriver     *SFTPDriver `yaml:"sftpDriver,omitempty" json:"sftpDriver,omitempty"`
+	Cache          *bool       `yaml:"cache,omitempty" json:"cache,omitempty" jsonschema:"nullable"`
+	FollowSymlinks *bool       `yaml:"followSymlinks,omitempty" json:"followSymlinks,omitempty" jsonschema:"nullable"`
+	SFTPDriver     *SFTPDriver `yaml:"sftpDriver,omitempty" json:"sftpDriver,omitempty" jsonschema:"nullable"`
 }
 
 type NineP struct {
-	SecurityModel   *string `yaml:"securityModel,omitempty" json:"securityModel,omitempty"`
-	ProtocolVersion *string `yaml:"protocolVersion,omitempty" json:"protocolVersion,omitempty"`
-	Msize           *string `yaml:"msize,omitempty" json:"msize,omitempty"`
-	Cache           *string `yaml:"cache,omitempty" json:"cache,omitempty"`
+	SecurityModel   *string `yaml:"securityModel,omitempty" json:"securityModel,omitempty" jsonschema:"nullable"`
+	ProtocolVersion *string `yaml:"protocolVersion,omitempty" json:"protocolVersion,omitempty" jsonschema:"nullable"`
+	Msize           *string `yaml:"msize,omitempty" json:"msize,omitempty" jsonschema:"nullable"`
+	Cache           *string `yaml:"cache,omitempty" json:"cache,omitempty" jsonschema:"nullable"`
 }
 
 type Virtiofs struct {
@@ -152,19 +159,19 @@ type Virtiofs struct {
 }
 
 type SSH struct {
-	LocalPort *int `yaml:"localPort,omitempty" json:"localPort,omitempty"`
+	LocalPort *int `yaml:"localPort,omitempty" json:"localPort,omitempty" jsonschema:"nullable"`
 
 	// LoadDotSSHPubKeys loads ~/.ssh/*.pub in addition to $LIMA_HOME/_config/user.pub .
-	LoadDotSSHPubKeys *bool `yaml:"loadDotSSHPubKeys,omitempty" json:"loadDotSSHPubKeys,omitempty"` // default: false
-	ForwardAgent      *bool `yaml:"forwardAgent,omitempty" json:"forwardAgent,omitempty"`           // default: false
-	ForwardX11        *bool `yaml:"forwardX11,omitempty" json:"forwardX11,omitempty"`               // default: false
-	ForwardX11Trusted *bool `yaml:"forwardX11Trusted,omitempty" json:"forwardX11Trusted,omitempty"` // default: false
+	LoadDotSSHPubKeys *bool `yaml:"loadDotSSHPubKeys,omitempty" json:"loadDotSSHPubKeys,omitempty" jsonschema:"nullable"` // default: false
+	ForwardAgent      *bool `yaml:"forwardAgent,omitempty" json:"forwardAgent,omitempty" jsonschema:"nullable"`           // default: false
+	ForwardX11        *bool `yaml:"forwardX11,omitempty" json:"forwardX11,omitempty" jsonschema:"nullable"`               // default: false
+	ForwardX11Trusted *bool `yaml:"forwardX11Trusted,omitempty" json:"forwardX11Trusted,omitempty" jsonschema:"nullable"` // default: false
 }
 
 type Firmware struct {
 	// LegacyBIOS disables UEFI if set.
 	// LegacyBIOS is ignored for aarch64.
-	LegacyBIOS *bool `yaml:"legacyBIOS,omitempty" json:"legacyBIOS,omitempty"`
+	LegacyBIOS *bool `yaml:"legacyBIOS,omitempty" json:"legacyBIOS,omitempty" jsonschema:"nullable"`
 
 	// Images specify UEFI images (edk2-aarch64-code.fd.gz).
 	// Defaults to built-in UEFI.
@@ -173,17 +180,17 @@ type Firmware struct {
 
 type Audio struct {
 	// Device is a QEMU audiodev string
-	Device *string `yaml:"device,omitempty" json:"device,omitempty"`
+	Device *string `yaml:"device,omitempty" json:"device,omitempty" jsonschema:"nullable"`
 }
 
 type VNCOptions struct {
-	Display *string `yaml:"display,omitempty" json:"display,omitempty"`
+	Display *string `yaml:"display,omitempty" json:"display,omitempty" jsonschema:"nullable"`
 }
 
 type Video struct {
 	// Display is a QEMU display string
-	Display *string    `yaml:"display,omitempty" json:"display,omitempty"`
-	VNC     VNCOptions `yaml:"vnc" json:"vnc"`
+	Display *string    `yaml:"display,omitempty" json:"display,omitempty" jsonschema:"nullable"`
+	VNC     VNCOptions `yaml:"vnc,omitempty" json:"vnc,omitempty"`
 }
 
 type ProvisionMode = string
@@ -197,16 +204,16 @@ const (
 )
 
 type Provision struct {
-	Mode                            ProvisionMode `yaml:"mode" json:"mode"` // default: "system"
+	Mode                            ProvisionMode `yaml:"mode,omitempty" json:"mode,omitempty" jsonschema:"default=system"`
 	SkipDefaultDependencyResolution *bool         `yaml:"skipDefaultDependencyResolution,omitempty" json:"skipDefaultDependencyResolution,omitempty"`
 	Script                          string        `yaml:"script" json:"script"`
 	Playbook                        string        `yaml:"playbook,omitempty" json:"playbook,omitempty"`
 }
 
 type Containerd struct {
-	System   *bool  `yaml:"system,omitempty" json:"system,omitempty"`     // default: false
-	User     *bool  `yaml:"user,omitempty" json:"user,omitempty"`         // default: true
-	Archives []File `yaml:"archives,omitempty" json:"archives,omitempty"` // default: see defaultContainerdArchives
+	System   *bool  `yaml:"system,omitempty" json:"system,omitempty" jsonschema:"nullable"` // default: false
+	User     *bool  `yaml:"user,omitempty" json:"user,omitempty" jsonschema:"nullable"`     // default: true
+	Archives []File `yaml:"archives,omitempty" json:"archives,omitempty"`                   // default: see defaultContainerdArchives
 }
 
 type ProbeMode = string
@@ -216,10 +223,10 @@ const (
 )
 
 type Probe struct {
-	Mode        ProbeMode // default: "readiness"
-	Description string
-	Script      string
-	Hint        string
+	Mode        ProbeMode `yaml:"mode,omitempty" json:"mode,omitempty" jsonschema:"default=readiness"`
+	Description string    `yaml:"description,omitempty" json:"description,omitempty"`
+	Script      string    `yaml:"script,omitempty" json:"script,omitempty"`
+	Hint        string    `yaml:"hint,omitempty" json:"hint,omitempty"`
 }
 
 type Proto = string
@@ -264,13 +271,13 @@ type Network struct {
 }
 
 type HostResolver struct {
-	Enabled *bool             `yaml:"enabled,omitempty" json:"enabled,omitempty"`
-	IPv6    *bool             `yaml:"ipv6,omitempty" json:"ipv6,omitempty"`
-	Hosts   map[string]string `yaml:"hosts,omitempty" json:"hosts,omitempty"`
+	Enabled *bool             `yaml:"enabled,omitempty" json:"enabled,omitempty" jsonschema:"nullable"`
+	IPv6    *bool             `yaml:"ipv6,omitempty" json:"ipv6,omitempty" jsonschema:"nullable"`
+	Hosts   map[string]string `yaml:"hosts,omitempty" json:"hosts,omitempty" jsonschema:"nullable"`
 }
 
 type CACertificates struct {
-	RemoveDefaults *bool    `yaml:"removeDefaults,omitempty" json:"removeDefaults,omitempty"` // default: false
-	Files          []string `yaml:"files,omitempty" json:"files,omitempty"`
-	Certs          []string `yaml:"certs,omitempty" json:"certs,omitempty"`
+	RemoveDefaults *bool    `yaml:"removeDefaults,omitempty" json:"removeDefaults,omitempty" jsonschema:"nullable"` // default: false
+	Files          []string `yaml:"files,omitempty" json:"files,omitempty" jsonschema:"nullable"`
+	Certs          []string `yaml:"certs,omitempty" json:"certs,omitempty" jsonschema:"nullable"`
 }
diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go
index 123f9b85..6c1a5d6d 100644
--- a/pkg/limayaml/validate.go
+++ b/pkg/limayaml/validate.go
@@ -487,7 +487,7 @@ func ValidateParamIsUsed(y *LimaYAML) error {
 				keyIsUsed = true
 				break
 			}
-			if re.MatchString(p.MountPoint) {
+			if p.MountPoint != nil && re.MatchString(*p.MountPoint) {
 				keyIsUsed = true
 				break
 			}



Test patch:
diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go
index f65d4594..848dc803 100644
--- a/pkg/limayaml/defaults_test.go
+++ b/pkg/limayaml/defaults_test.go
@@ -132,7 +132,7 @@ func TestFillDefault(t *testing.T) {
 		},
 		Mounts: []Mount{
 			{Location: "/tmp"},
-			{Location: "{{.Dir}}/{{.Param.ONE}}", MountPoint: "/mnt/{{.Param.ONE}}"},
+			{Location: "{{.Dir}}/{{.Param.ONE}}", MountPoint: ptr.Of("/mnt/{{.Param.ONE}}")},
 		},
 		MountType: ptr.Of(NINEP),
 		Provision: []Provision{
@@ -205,7 +205,7 @@ func TestFillDefault(t *testing.T) {
 	}
 
 	expect.Mounts = slices.Clone(y.Mounts)
-	expect.Mounts[0].MountPoint = expect.Mounts[0].Location
+	expect.Mounts[0].MountPoint = ptr.Of(expect.Mounts[0].Location)
 	expect.Mounts[0].Writable = ptr.Of(false)
 	expect.Mounts[0].SSHFS.Cache = ptr.Of(true)
 	expect.Mounts[0].SSHFS.FollowSymlinks = ptr.Of(false)
@@ -217,7 +217,7 @@ func TestFillDefault(t *testing.T) {
 	expect.Mounts[0].Virtiofs.QueueSize = nil
 	// Only missing Mounts field is Writable, and the default value is also the null value: false
 	expect.Mounts[1].Location = fmt.Sprintf("%s/%s", instDir, y.Param["ONE"])
-	expect.Mounts[1].MountPoint = fmt.Sprintf("/mnt/%s", y.Param["ONE"])
+	expect.Mounts[1].MountPoint = ptr.Of(fmt.Sprintf("/mnt/%s", y.Param["ONE"]))
 	expect.Mounts[1].Writable = ptr.Of(false)
 	expect.Mounts[1].SSHFS.Cache = ptr.Of(true)
 	expect.Mounts[1].SSHFS.FollowSymlinks = ptr.Of(false)
@@ -431,7 +431,7 @@ func TestFillDefault(t *testing.T) {
 	expect.Containerd.Archives = slices.Clone(d.Containerd.Archives)
 	expect.Containerd.Archives[0].Arch = *d.Arch
 	expect.Mounts = slices.Clone(d.Mounts)
-	expect.Mounts[0].MountPoint = expect.Mounts[0].Location
+	expect.Mounts[0].MountPoint = ptr.Of(expect.Mounts[0].Location)
 	expect.Mounts[0].SSHFS.Cache = ptr.Of(true)
 	expect.Mounts[0].SSHFS.FollowSymlinks = ptr.Of(false)
 	expect.Mounts[0].SSHFS.SFTPDriver = ptr.Of("")