Repository: lima-vm/lima

Issue:
[v1.1 RC] `/opt/homebrew/opt/lima/bin/limactl`, `/home/linuxbrew/.linuxbrew/bin/limactl` can't detect non-native guest agents
https://github.com/AkihiroSuda/homebrew-core/commit/535744dfa73bfe5e61d7e9a6b693c6368f4e2d2c


`/opt/homebrew/bin/limactl` can detect all the guest agents:
```console
$ /opt/homebrew/bin/limactl info | jq .guestAgents
{
  "aarch64": {
    "location": "/opt/homebrew/share/lima/lima-guestagent.Linux-aarch64.gz"
  },
  "armv7l": {
    "location": "/opt/homebrew/share/lima/lima-guestagent.Linux-armv7l.gz"
  },
  "ppc64le": {
    "location": "/opt/homebrew/share/lima/lima-guestagent.Linux-ppc64le.gz"
  },
  "riscv64": {
    "location": "/opt/homebrew/share/lima/lima-guestagent.Linux-riscv64.gz"
  },
  "s390x": {
    "location": "/opt/homebrew/share/lima/lima-guestagent.Linux-s390x.gz"
  },
  "x86_64": {
    "location": "/opt/homebrew/share/lima/lima-guestagent.Linux-x86_64.gz"
  }
}
```

```console
$ ls -l /opt/homebrew/share/lima/
total 0
lrwxr-xr-x   1 suda  admin    72  5 21 05:30 lima-guestagent.Linux-aarch64.gz@ -> ../../Cellar/lima/1.1.0-rc.1/share/lima/lima-guestagent.Linux-aarch64.gz
lrwxr-xr-x   1 suda  admin    94  5 21 05:30 lima-guestagent.Linux-armv7l.gz@ -> ../../Cellar/lima-additional-guestagents/1.1.0-rc.1/share/lima/lima-guestagent.Linux-armv7l.gz
lrwxr-xr-x   1 suda  admin    95  5 21 05:30 lima-guestagent.Linux-ppc64le.gz@ -> ../../Cellar/lima-additional-guestagents/1.1.0-rc.1/share/lima/lima-guestagent.Linux-ppc64le.gz
lrwxr-xr-x   1 suda  admin    95  5 21 05:30 lima-guestagent.Linux-riscv64.gz@ -> ../../Cellar/lima-additional-guestagents/1.1.0-rc.1/share/lima/lima-guestagent.Linux-riscv64.gz
lrwxr-xr-x   1 suda  admin    93  5 21 05:30 lima-guestagent.Linux-s390x.gz@ -> ../../Cellar/lima-additional-guestagents/1.1.0-rc.1/share/lima/lima-guestagent.Linux-s390x.gz
lrwxr-xr-x   1 suda  admin    94  5 21 05:30 lima-guestagent.Linux-x86_64.gz@ -> ../../Cellar/lima-additional-guestagents/1.1.0-rc.1/share/lima/lima-guestagent.Linux-x86_64.gz
drwxr-xr-x  50 suda  admin  1600  5 21 05:30 templates/
```

However, `/opt/homebrew/opt/lima/bin/limactl` can't detect non-native guest agents
```console
$ /opt/homebrew/opt/lima/bin/limactl info | jq .guestAgents
WARN[0000] Failed to resolve the guest agent binary for "x86_64"  error="guest agent binary could not be found for Linux-x86_64 (Hint: try installing `lima-additional-guestagents` package)"
WARN[0000] Failed to resolve the guest agent binary for "armv7l"  error="guest agent binary could not be found for Linux-armv7l (Hint: try installing `lima-additional-guestagents` package)"
WARN[0000] Failed to resolve the guest agent binary for "ppc64le"  error="guest agent binary could not be found for Linux-ppc64le (Hint: try installing `lima-additional-guestagents` package)"
WARN[0000] Failed to resolve the guest agent binary for "riscv64"  error="guest agent binary could not be found for Linux-riscv64 (Hint: try installing `lima-additional-guestagents` package)"
WARN[0000] Failed to resolve the guest agent binary for "s390x"  error="guest agent binary could not be found for Linux-s390x (Hint: try installing `lima-additional-guestagents` package)"
{
  "aarch64": {
    "location": "/opt/homebrew/opt/lima/share/lima/lima-guestagent.Linux-aarch64.gz"
  }
}
```

```console
$ ls -l /opt/homebrew/opt/lima/share/lima/
total 24648
-rw-r--r--   1 suda  admin  12616560  5 21 05:30 lima-guestagent.Linux-aarch64.gz
drwxr-xr-x  50 suda  admin      1600  5 21 05:30 templates/
```

Hints from issue comments:
@AkihiroSuda: Practically this might not be a problem for users, but this is required to pass this test
https://github.com/AkihiroSuda/homebrew-core/commit/535744dfa73bfe5e61d7e9a6b693c6368f4e2d2c

```ruby
info = JSON.parse shell_output("#{Formula["lima"].bin}/limactl info")
assert_includes info["guestAgents"], "riscv64"
```@jandubois: > However, `/opt/homebrew/opt/lima/bin/limactl` can't detect non-native guest agents

That is expected though because the additional guestagents are installed in `/opt/homebrew/opt/lima-additional-guestagents/share/lima/` and not in `/opt/homebrew/opt/lima/share/lima/`. That's what I first thought was the issue in https://github.com/lima-vm/lima/issues/3540#issuecomment-2880889319.

So it is kind of by definition that when you use `/opt/homebrew/opt/lima/bin/limactl` that you will not see any `<PREFIX>/share/lima` files from other packages, but only those included in the `lima` formula.

I think the test needs to be changed, or maybe it should be dropped?@jandubois: > info = JSON.parse shell_output("#{Formula["lima"].bin}/limactl info")

Is there a reason not to use the top level prefix here?

```ruby
info = JSON.parse shell_output("#{HOMEBREW_PREFIX}/bin/limactl info")
```@AkihiroSuda: > info = JSON.parse shell_output("#{HOMEBREW_PREFIX}/bin/limactl info")


Let's just amend the test with this and call it a day.

We can revisit this later if something doesn't work well.

@AkihiroSuda: The tests are passing on macOS, failing on Linux

- https://github.com/Homebrew/homebrew-core/pull/224206#issuecomment-2896811998


```console
$ /home/linuxbrew/.linuxbrew/bin/limactl info | jq .guestAgents
WARN[0000] Failed to resolve the guest agent binary for "x86_64"  error="guest agent binary could not be found for Linux-x86_64 (Hint: try installing `lima-additional-guestagents` package)"
WARN[0000] Failed to resolve the guest agent binary for "armv7l"  error="guest agent binary could not be found for Linux-armv7l (Hint: try installing `lima-additional-guestagents` package)"
WARN[0000] Failed to resolve the guest agent binary for "ppc64le"  error="guest agent binary could not be found for Linux-ppc64le (Hint: try installing `lima-additional-guestagents` package)"
WARN[0000] Failed to resolve the guest agent binary for "riscv64"  error="guest agent binary could not be found for Linux-riscv64 (Hint: try installing `lima-additional-guestagents` package)"
WARN[0000] Failed to resolve the guest agent binary for "s390x"  error="guest agent binary could not be found for Linux-s390x (Hint: try installing `lima-additional-guestagents` package)"
{
  "aarch64": {
    "location": "/home/linuxbrew/.linuxbrew/Cellar/lima/1.1.0/share/lima/lima-guestagent.Linux-aarch64.gz"
  }
}

$ ls -l /home/linuxbrew/.linuxbrew/bin/limactl 
lrwxrwxrwx 1 suda suda 32 May 21 16:07 /home/linuxbrew/.linuxbrew/bin/limactl -> ../Cellar/lima/1.1.0/bin/limactl


$ ls -l /home/linuxbrew/.linuxbrew/share/lima/
total 32
drwxrwxr-x 3 suda suda 4096 May 21 16:08 lima
lrwxrwxrwx 1 suda suda   67 May 21 16:08 lima-guestagent.Linux-aarch64.gz -> ../../Cellar/lima/1.1.0/share/lima/lima-guestagent.Linux-aarch64.gz
lrwxrwxrwx 1 suda suda   89 May 21 16:08 lima-guestagent.Linux-armv7l.gz -> ../../Cellar/lima-additional-guestagents/1.1.0/share/lima/lima-guestagent.Linux-armv7l.gz
lrwxrwxrwx 1 suda suda   90 May 21 16:08 lima-guestagent.Linux-ppc64le.gz -> ../../Cellar/lima-additional-guestagents/1.1.0/share/lima/lima-guestagent.Linux-ppc64le.gz
lrwxrwxrwx 1 suda suda   90 May 21 16:08 lima-guestagent.Linux-riscv64.gz -> ../../Cellar/lima-additional-guestagents/1.1.0/share/lima/lima-guestagent.Linux-riscv64.gz
lrwxrwxrwx 1 suda suda   88 May 21 16:08 lima-guestagent.Linux-s390x.gz -> ../../Cellar/lima-additional-guestagents/1.1.0/share/lima/lima-guestagent.Linux-s390x.gz
lrwxrwxrwx 1 suda suda   89 May 21 16:08 lima-guestagent.Linux-x86_64.gz -> ../../Cellar/lima-additional-guestagents/1.1.0/share/lima/lima-guestagent.Linux-x86_64.gz
drwxrwxr-x 5 suda suda 4096 May 21 16:08 templates

$ ls -l /home/linuxbrew/.linuxbrew/share/lima/lima
total 8
lrwxrwxrwx 1 suda suda   75 May 21 16:08 lima-guestagent.Linux-aarch64.gz -> ../../../Cellar/lima/1.1.0/share/lima/lima/lima-guestagent.Linux-aarch64.gz
drwxrwxr-x 5 suda suda 4096 May 21 16:08 templates
```
@AkihiroSuda: Probably `lima-additional-guestagents` should have `bin/lima-additional-guestagents-path` script that shows `$(dirname $0)/../share/lima`?

The main `limactl` binary should call that script to obtain the additional guestagent path

Solution patch:
diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml
index 6cd1ef29..15d1155f 100644
--- a/.github/workflows/codeql.yaml
+++ b/.github/workflows/codeql.yaml
@@ -32,7 +32,7 @@ jobs:
       uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
 
     - name: Initialize CodeQL
-      uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e  # v3.28.19
+      uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858  # v3.29.0
       with:
         languages: ${{ matrix.language }}
         build-mode: ${{ matrix.build-mode }}
@@ -48,6 +48,6 @@ jobs:
         exit 1
 
     - name: Perform CodeQL Analysis
-      uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e  # v3.28.19
+      uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858  # v3.29.0
       with:
         category: "/language:${{matrix.language}}"
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f571f7cb..0cf4435d 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -109,7 +109,7 @@ jobs:
         - - -
         Release manager: [ADD YOUR NAME HERE] (@[ADD YOUR GITHUB ID HERE])
         EOF
-    - uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd  # v2.3.0
+    - uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be  # v2.4.0
       if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
       with:
         subject-path: _artifacts/*
diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml
index 06056b9f..49f7a95e 100644
--- a/.github/workflows/scorecard.yml
+++ b/.github/workflows/scorecard.yml
@@ -60,6 +60,6 @@ jobs:
     # Upload the results to GitHub's code scanning dashboard (optional).
     # Commenting out will disable upload of results to your repo's Code Scanning dashboard
     - name: "Upload to code-scanning"
-      uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e  # v3.28.19
+      uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858  # v3.29.0
       with:
         sarif_file: results.sarif
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index febac6a2..c3670fd3 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -151,8 +151,8 @@ jobs:
       run: sudo make uninstall
 
   windows:
-    name: "Windows tests"
-    runs-on: windows-2022-8-cores
+    name: "Windows tests (WSL2)"
+    runs-on: windows-2025-8-cores
     timeout-minutes: 30
     steps:
     - name: Enable WSL2
@@ -205,6 +205,40 @@ jobs:
         $env:_LIMA_WINDOWS_EXTRA_PATH = 'C:\Program Files\Git\usr\bin'
         bash.exe -c "./hack/test-templates.sh templates/experimental/wsl2.yaml"
 
+  windows-qemu:
+    name: "Windows tests (QEMU)"
+    runs-on: windows-2025
+    timeout-minutes: 30
+    steps:
+    - name: Set gitconfig
+      run: |
+        git config --global core.autocrlf false
+        git config --global core.eol lf
+    - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683  # v4.2.2
+      with:
+        # To avoid "can't parse builtin Lima version" errors
+        fetch-depth: 0
+    - uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5  # v5.5.0
+      with:
+        go-version: 1.24.x
+    - name: Unit tests
+      run: go test -v ./...
+    - name: Make
+      run: make
+    - name: Install QEMU
+      run: |
+        winget install --silent --accept-source-agreements --accept-package-agreements --disable-interactivity SoftwareFreedomConservancy.QEMU
+    - name: Integration tests (QEMU, Windows host)
+      run: |
+        $env:PATH = "$pwd\_output\bin;" + 'C:\msys64\usr\bin;' + 'C:\Program Files\QEMU;' + $env:PATH
+        pacman -Sy --noconfirm openbsd-netcat diffutils
+        $env:MSYS2_ENV_CONV_EXCL = 'HOME_HOST;HOME_GUEST;_LIMA_WINDOWS_EXTRA_PATH'
+        $env:HOME_HOST = $(cygpath.exe "$env:USERPROFILE")
+        $env:HOME_GUEST = "$env:HOME_HOST"
+        $env:LIMACTL_CREATE_ARGS = '--vm-type=qemu'
+        $env:_LIMA_WINDOWS_EXTRA_PATH = 'C:\Program Files\Git\usr\bin'
+        bash.exe -c "./hack/test-templates.sh templates/default.yaml"
+
   qemu:
     name: "Integration tests (QEMU, macOS host)"
     runs-on: macos-15-large  # Intel
diff --git a/README.md b/README.md
index 81b3cc56..a264a197 100644
--- a/README.md
+++ b/README.md
@@ -9,6 +9,7 @@
 
 # Lima: Linux Machines
 
+[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/lima-vm/lima)
 [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/6505/badge)](https://www.bestpractices.dev/projects/6505)
 [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/lima-vm/lima/badge)](https://scorecard.dev/viewer/?uri=github.com/lima-vm/lima)
 
@@ -73,7 +74,7 @@ GUI:
   - Login: <https://cloud-native.slack.com/>
 
 ### Code of Conduct
-Lima follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
+Lima follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
 
 - - -
 **We are a [Cloud Native Computing Foundation](https://cncf.io/) sandbox project.**
diff --git a/cmd/limactl/start.go b/cmd/limactl/start.go
index ad053484..3f90f66e 100644
--- a/cmd/limactl/start.go
+++ b/cmd/limactl/start.go
@@ -338,7 +338,7 @@ func chooseNextCreatorState(tmpl *limatmpl.Template, yq string) (*limatmpl.Templ
 			}
 			return tmpl, nil
 		case 2: // "Choose another template..."
-			templates, err := templatestore.Templates()
+			templates, err := filterHiddenTemplates()
 			if err != nil {
 				return tmpl, err
 			}
@@ -379,22 +379,34 @@ func createStartActionCommon(cmd *cobra.Command, _ []string) (exit bool, err err
 	if listTemplates, err := cmd.Flags().GetBool("list-templates"); err != nil {
 		return true, err
 	} else if listTemplates {
-		templates, err := templatestore.Templates()
+		templates, err := filterHiddenTemplates()
 		if err != nil {
 			return true, err
 		}
 		w := cmd.OutOrStdout()
 		for _, f := range templates {
-			// Don't show internal base templates like `_default/*` and `_images/*`.
-			if !strings.HasPrefix(f.Name, "_") {
-				_, _ = fmt.Fprintln(w, f.Name)
-			}
+			_, _ = fmt.Fprintln(w, f.Name)
 		}
 		return true, nil
 	}
 	return false, nil
 }
 
+func filterHiddenTemplates() ([]templatestore.Template, error) {
+	templates, err := templatestore.Templates()
+	if err != nil {
+		return nil, err
+	}
+	var filtered []templatestore.Template
+	for _, f := range templates {
+		// Don't show internal base templates like `_default/*` and `_images/*`.
+		if !strings.HasPrefix(f.Name, "_") {
+			filtered = append(filtered, f)
+		}
+	}
+	return filtered, nil
+}
+
 func createAction(cmd *cobra.Command, args []string) error {
 	if exit, err := createStartActionCommon(cmd, args); err != nil {
 		return err
diff --git a/go.mod b/go.mod
index 8134023f..14a53959 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,7 @@ require (
 	github.com/foxcpp/go-mockdns v1.1.0
 	github.com/goccy/go-yaml v1.18.0
 	github.com/google/go-cmp v0.7.0
-	github.com/google/yamlfmt v0.17.0
+	github.com/google/yamlfmt v0.17.1
 	github.com/invopop/jsonschema v0.13.0
 	github.com/lima-vm/go-qcow2reader v0.6.0
 	github.com/lima-vm/sshocker v0.3.8 // gomodjail:unconfined
@@ -49,9 +49,9 @@ require (
 	google.golang.org/protobuf v1.36.6 // gomodjail:unconfined
 	gopkg.in/op/go-logging.v1 v1.0.0-20160211212156-b2cb9fa56473
 	gotest.tools/v3 v3.5.2
-	k8s.io/api v0.32.5
-	k8s.io/apimachinery v0.32.5
-	k8s.io/client-go v0.32.5
+	k8s.io/api v0.32.6
+	k8s.io/apimachinery v0.32.6
+	k8s.io/client-go v0.32.6
 )
 
 require (
diff --git a/go.sum b/go.sum
index 152136a8..fb72ed4d 100644
--- a/go.sum
+++ b/go.sum
@@ -123,8 +123,8 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaU
 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
 github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
-github.com/google/yamlfmt v0.17.0 h1:/tdp01rIlvLz3LgJ2NtMLnqgAadZm33P7GcPU680b+w=
-github.com/google/yamlfmt v0.17.0/go.mod h1:gs0UEklJOYkUJ+OOCG0hg9n+DzucKDPlJElTUasVNK8=
+github.com/google/yamlfmt v0.17.1 h1:NgKi21+MUVntxhPhIsstylN6QNCCfDv3wKNKwlm/3Hs=
+github.com/google/yamlfmt v0.17.1/go.mod h1:gs0UEklJOYkUJ+OOCG0hg9n+DzucKDPlJElTUasVNK8=
 github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=
 github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg=
 github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
@@ -430,12 +430,12 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
 gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
 gvisor.dev/gvisor v0.0.0-20240916094835-a174eb65023f h1:O2w2DymsOlM/nv2pLNWCMCYOldgBBMkD7H0/prN5W2k=
 gvisor.dev/gvisor v0.0.0-20240916094835-a174eb65023f/go.mod h1:sxc3Uvk/vHcd3tj7/DHVBoR5wvWT/MmRq2pj7HRJnwU=
-k8s.io/api v0.32.5 h1:uqjjsYo1kTJr5NIcoIaP9F+TgXgADH7nKQx91FDAhtk=
-k8s.io/api v0.32.5/go.mod h1:bXXFU3fGCZ/eFMZvfHZC69PeGbXEL4zzjuPVzOxHF64=
-k8s.io/apimachinery v0.32.5 h1:6We3aJ6crC0ap8EhsEXcgX3LpI6SEjubpiOMXLROwPM=
-k8s.io/apimachinery v0.32.5/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
-k8s.io/client-go v0.32.5 h1:huFmQMzgWu0z4kbWsuZci+Gt4Fo72I4CcrvhToZ/Qp0=
-k8s.io/client-go v0.32.5/go.mod h1:Qchw6f9WIVrur7DKojAHpRgGLcANT0RLIvF39Jz58xA=
+k8s.io/api v0.32.6 h1:UiBAMRzTP24Tz9UT1uhhmAv1auGTT9PT/npywSk9JrU=
+k8s.io/api v0.32.6/go.mod h1:+iFCyQN34v2rsL53iQEN9lYE03mFdgPvgSXvATIDteg=
+k8s.io/apimachinery v0.32.6 h1:odtEUjg7OT3132sBFsFn4Arj4Gd+BplYekmLQP8L3ak=
+k8s.io/apimachinery v0.32.6/go.mod h1:GpHVgxoKlTxClKcteaeuF1Ul/lDVb74KpZcxcmLDElE=
+k8s.io/client-go v0.32.6 h1:Q+O+Sd9LKKFnsGZNVX2q1RDILYRpQZX+ea2RoIgjKlM=
+k8s.io/client-go v0.32.6/go.mod h1:yqL9XJ2cTXy3WdJwdeyob3O6xiLwWrh9DP7SeszniW0=
 k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
 k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
 k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f h1:GA7//TjRY9yWGy1poLzYYJJ4JRdzg3+O6e8I+e+8T5Y=
diff --git a/hack/test-templates.sh b/hack/test-templates.sh
index cd228a26..9bba8ba3 100755
--- a/hack/test-templates.sh
+++ b/hack/test-templates.sh
@@ -60,6 +60,11 @@ declare -A CHECKS=(
 )
 
 case "$NAME" in
+"default")
+	# CI failure:
+	# "[hostagent] failed to confirm whether /c/Users/runneradmin [remote] is successfully mounted"
+	[ "${OS_HOST}" = "Msys" ] && CHECKS["mount-home"]=
+	;;
 "alpine"*)
 	WARNING "Alpine does not support systemd"
 	CHECKS["systemd"]=
diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go
index 750383e2..8298882c 100644
--- a/pkg/limayaml/validate.go
+++ b/pkg/limayaml/validate.go
@@ -219,7 +219,7 @@ func Validate(y *LimaYAML, warn bool) error {
 		case ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot, ProvisionModeData, ProvisionModeDependency, ProvisionModeAnsible:
 		default:
 			return fmt.Errorf("field `provision[%d].mode` must one of %q, %q, %q, %q, %q, or %q",
-				i, ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot, ProvisionModeDependency, ProvisionModeAnsible, ProvisionModeAnsible)
+				i, ProvisionModeSystem, ProvisionModeUser, ProvisionModeBoot, ProvisionModeData, ProvisionModeDependency, ProvisionModeAnsible)
 		}
 		if p.Mode != ProvisionModeDependency && p.SkipDefaultDependencyResolution != nil {
 			return fmt.Errorf("field `provision[%d].mode` cannot set skipDefaultDependencyResolution, only valid on scripts of type %q",
diff --git a/pkg/qemu/qemu.go b/pkg/qemu/qemu.go
index ca775b2d..0ba01b61 100644
--- a/pkg/qemu/qemu.go
+++ b/pkg/qemu/qemu.go
@@ -1146,6 +1146,9 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
 
 	switch arch {
 	case limayaml.X8664:
+		// Archlinux package "edk2-ovmf"
+		// @see: https://archlinux.org/packages/extra/any/edk2-ovmf/files
+		candidates = append(candidates, "/usr/share/edk2/x64/OVMF_CODE.4m.fd")
 		// Debian package "ovmf"
 		candidates = append(candidates, "/usr/share/OVMF/OVMF_CODE.fd")
 		candidates = append(candidates, "/usr/share/OVMF/OVMF_CODE_4M.fd")
@@ -1153,15 +1156,19 @@ func getFirmware(qemuExe string, arch limayaml.Arch) (string, error) {
 		candidates = append(candidates, "/usr/share/edk2/ovmf/OVMF_CODE.fd")
 		// openSUSE package "qemu-ovmf-x86_64"
 		candidates = append(candidates, "/usr/share/qemu/ovmf-x86_64.bin")
-		// Archlinux package "edk2-ovmf"
-		candidates = append(candidates, "/usr/share/edk2-ovmf/x64/OVMF_CODE.fd")
 	case limayaml.AARCH64:
+		// Archlinux package "edk2-aarch64"
+		// @see: https://archlinux.org/packages/extra/any/edk2-aarch64/files
+		candidates = append(candidates, "/usr/share/edk2/aarch64/QEMU_CODE.fd")
 		// Debian package "qemu-efi-aarch64"
 		// Fedora package "edk2-aarch64"
 		candidates = append(candidates, "/usr/share/AAVMF/AAVMF_CODE.fd")
 		// Debian package "qemu-efi-aarch64" (unpadded, backwards compatibility)
 		candidates = append(candidates, "/usr/share/qemu-efi-aarch64/QEMU_EFI.fd")
 	case limayaml.ARMV7L:
+		// Archlinux package "edk2-arm"
+		// @see: https://archlinux.org/packages/extra/any/edk2-arm/files
+		candidates = append(candidates, "/usr/share/edk2/arm/QEMU_CODE.fd")
 		// Debian package "qemu-efi-arm"
 		// Fedora package "edk2-arm"
 		candidates = append(candidates, "/usr/share/AAVMF/AAVMF32_CODE.fd")
diff --git a/pkg/sshutil/sshutil.go b/pkg/sshutil/sshutil.go
index 81e81e37..fa5370a4 100644
--- a/pkg/sshutil/sshutil.go
+++ b/pkg/sshutil/sshutil.go
@@ -154,13 +154,22 @@ func DefaultPubKeys(loadDotSSH bool) ([]PubKey, error) {
 	return res, nil
 }
 
+type openSSHInfo struct {
+	// Version is set to the version of OpenSSH, or semver.New("0.0.0") if the version cannot be determined.
+	Version semver.Version
+
+	// Some distributions omit this feature by default, for example, Alpine, NixOS.
+	GSSAPISupported bool
+}
+
 var sshInfo struct {
 	sync.Once
 	// aesAccelerated is set to true when AES acceleration is available.
 	// Available on almost all modern Intel/AMD processors.
 	aesAccelerated bool
-	// openSSHVersion is set to the version of OpenSSH, or semver.New("0.0.0") if the version cannot be determined.
-	openSSHVersion semver.Version
+
+	// OpenSSH executable information for the version and supported options.
+	openSSH openSSHInfo
 }
 
 // CommonOpts returns ssh option key-value pairs like {"IdentityFile=/path/to/id_foo"}.
@@ -226,7 +235,6 @@ func CommonOpts(sshPath string, useDotSSH bool) ([]string, error) {
 		"StrictHostKeyChecking=no",
 		"UserKnownHostsFile=/dev/null",
 		"NoHostAuthenticationForLocalhost=yes",
-		"GSSAPIAuthentication=no",
 		"PreferredAuthentications=publickey",
 		"Compression=no",
 		"BatchMode=yes",
@@ -235,11 +243,15 @@ func CommonOpts(sshPath string, useDotSSH bool) ([]string, error) {
 
 	sshInfo.Do(func() {
 		sshInfo.aesAccelerated = detectAESAcceleration()
-		sshInfo.openSSHVersion = DetectOpenSSHVersion(sshPath)
+		sshInfo.openSSH = detectOpenSSHInfo(sshPath)
 	})
 
+	if sshInfo.openSSH.GSSAPISupported {
+		opts = append(opts, "GSSAPIAuthentication=no")
+	}
+
 	// Only OpenSSH version 8.1 and later support adding ciphers to the front of the default set
-	if !sshInfo.openSSHVersion.LessThan(*semver.New("8.1.0")) {
+	if !sshInfo.openSSH.Version.LessThan(*semver.New("8.1.0")) {
 		// By default, `ssh` choose chacha20-poly1305@openssh.com, even when AES accelerator is available.
 		// (OpenSSH_8.1p1, macOS 11.6, MacBookPro 2020, Core i7-1068NG7)
 		//
@@ -321,7 +333,7 @@ func SSHArgsFromOpts(opts []string) []string {
 }
 
 func ParseOpenSSHVersion(version []byte) *semver.Version {
-	regex := regexp.MustCompile(`^OpenSSH_(\d+\.\d+)(?:p(\d+))?\b`)
+	regex := regexp.MustCompile(`(?m)^OpenSSH_(\d+\.\d+)(?:p(\d+))?\b`)
 	matches := regex.FindSubmatch(version)
 	if len(matches) == 3 {
 		if len(matches[2]) == 0 {
@@ -332,6 +344,10 @@ func ParseOpenSSHVersion(version []byte) *semver.Version {
 	return &semver.Version{}
 }
 
+func parseOpenSSHGSSAPISupported(version string) bool {
+	return !strings.Contains(version, `Unsupported option "gssapiauthentication"`)
+}
+
 // sshExecutable beyond path also records size and mtime, in the case of ssh upgrades.
 type sshExecutable struct {
 	Path    string
@@ -340,14 +356,14 @@ type sshExecutable struct {
 }
 
 var (
-	// sshVersions caches the parsed version of each ssh executable, if it is needed again.
-	sshVersions   = map[sshExecutable]*semver.Version{}
-	sshVersionsRW sync.RWMutex
+	// openSSHInfos caches the parsed version and supported options of each ssh executable, if it is needed again.
+	openSSHInfos   = map[sshExecutable]*openSSHInfo{}
+	openSSHInfosRW sync.RWMutex
 )
 
-func DetectOpenSSHVersion(ssh string) semver.Version {
+func detectOpenSSHInfo(ssh string) openSSHInfo {
 	var (
-		v      semver.Version
+		info   openSSHInfo
 		exe    sshExecutable
 		stderr bytes.Buffer
 	)
@@ -357,25 +373,33 @@ func DetectOpenSSHVersion(ssh string) semver.Version {
 	} else {
 		st, _ := os.Stat(path)
 		exe = sshExecutable{Path: path, Size: st.Size(), ModTime: st.ModTime()}
-		sshVersionsRW.RLock()
-		ver := sshVersions[exe]
-		sshVersionsRW.RUnlock()
-		if ver != nil {
-			return *ver
+		openSSHInfosRW.RLock()
+		info := openSSHInfos[exe]
+		openSSHInfosRW.RUnlock()
+		if info != nil {
+			return *info
 		}
 	}
-	cmd := exec.Command(path, "-V")
+	// -V should be last
+	cmd := exec.Command(path, "-o", "GSSAPIAuthentication=no", "-V")
 	cmd.Stderr = &stderr
 	if err := cmd.Run(); err != nil {
 		logrus.Warnf("failed to run %v: stderr=%q", cmd.Args, stderr.String())
 	} else {
-		v = *ParseOpenSSHVersion(stderr.Bytes())
-		logrus.Debugf("OpenSSH version %s detected", v)
-		sshVersionsRW.Lock()
-		sshVersions[exe] = &v
-		sshVersionsRW.Unlock()
+		info = openSSHInfo{
+			Version:         *ParseOpenSSHVersion(stderr.Bytes()),
+			GSSAPISupported: parseOpenSSHGSSAPISupported(stderr.String()),
+		}
+		logrus.Debugf("OpenSSH version %s detected, is GSSAPI supported: %t", info.Version, info.GSSAPISupported)
+		openSSHInfosRW.Lock()
+		openSSHInfos[exe] = &info
+		openSSHInfosRW.Unlock()
 	}
-	return v
+	return info
+}
+
+func DetectOpenSSHVersion(ssh string) semver.Version {
+	return detectOpenSSHInfo(ssh).Version
 }
 
 // detectValidPublicKey returns whether content represent a public key.
diff --git a/pkg/usrlocalsharelima/usrlocalsharelima.go b/pkg/usrlocalsharelima/usrlocalsharelima.go
index 51fd4a19..0c8e99c7 100644
--- a/pkg/usrlocalsharelima/usrlocalsharelima.go
+++ b/pkg/usrlocalsharelima/usrlocalsharelima.go
@@ -23,32 +23,42 @@ import (
 // It will also append the file extension on Windows, if necessary.
 // This function is different from os.Executable(), which will use /proc/self/exe on Linux
 // and therefore will resolve any symlink used to locate the executable. This function will
-// return the symlink instead because we want to locate ../share/lima relative to the location
-// of the symlink, and not the actual executable. This is important when using Homebrew.
-//
-// If os.Args[0] is invalid, this function still falls back on os.Executable().
+// return the symlink instead because we want to be able to locate ../share/lima relative
+// to the location of the symlink, and not the actual executable. This is important when
+// using Homebrew.
 var executableViaArgs0 = sync.OnceValues(func() (string, error) {
 	if os.Args[0] == "" {
-		logrus.Warn("os.Args[0] has not been set")
-	} else {
-		executable, err := exec.LookPath(os.Args[0])
-		if err == nil {
-			// LookPath() will add the `.exe` file extension on Windows, but will not return an
-			// absolute path if the argument contained any of `:/\` (or just `/` on Unix).
-			return filepath.Abs(executable)
-		}
-		logrus.Warnf("os.Args[0] is invalid: %v", err)
+		return "", errors.New("os.Args[0] has not been set")
+	}
+	executable, err := exec.LookPath(os.Args[0])
+	if err == nil {
+		executable, err = filepath.Abs(executable)
+	}
+	if err != nil {
+		return "", fmt.Errorf("os.Args[0] is invalid: %w", err)
 	}
-	return os.Executable()
+
+	return executable, nil
 })
 
 // Dir returns the location of the <PREFIX>/lima/share directory, relative to the location
 // of the current executable. It checks for multiple possible filesystem layouts and returns
 // the first candidate that contains the native guest agent binary.
 func Dir() (string, error) {
-	self, err := executableViaArgs0()
+	selfPaths := []string{}
+
+	selfViaArgs0, err := executableViaArgs0()
 	if err != nil {
-		return "", err
+		logrus.WithError(err).Warn("failed to find executable from os.Args[0]")
+	} else {
+		selfPaths = append(selfPaths, selfViaArgs0)
+	}
+
+	selfViaOS, err := os.Executable()
+	if err != nil {
+		logrus.WithError(err).Warn("failed to find os.Executable()")
+	} else if len(selfPaths) == 0 || selfViaOS != selfPaths[0] {
+		selfPaths = append(selfPaths, selfViaOS)
 	}
 
 	ostype := limayaml.NewOS("linux")
@@ -57,31 +67,35 @@ func Dir() (string, error) {
 		return "", fmt.Errorf("failed to get arch for %q", runtime.GOARCH)
 	}
 
-	// self:  /usr/local/bin/limactl
-	selfDir := filepath.Dir(self)
-	selfDirDir := filepath.Dir(selfDir)
-	gaCandidates := []string{
-		// candidate 0:
-		// - self:  /Applications/Lima.app/Contents/MacOS/limactl
-		// - agent: /Applications/Lima.app/Contents/MacOS/lima-guestagent.Linux-x86_64
-		// - dir:   /Applications/Lima.app/Contents/MacOS
-		filepath.Join(selfDir, "lima-guestagent."+ostype+"-"+arch),
-		// candidate 1:
-		// - self:  /usr/local/bin/limactl
-		// - agent: /usr/local/share/lima/lima-guestagent.Linux-x86_64
-		// - dir:   /usr/local/share/lima
-		filepath.Join(selfDirDir, "share/lima/lima-guestagent."+ostype+"-"+arch),
-		// TODO: support custom path
-	}
-	if debugutil.Debug {
-		// candidate 2: launched by `~/go/bin/dlv dap`
-		// - self: ${workspaceFolder}/cmd/limactl/__debug_bin_XXXXXX
-		// - agent: ${workspaceFolder}/_output/share/lima/lima-guestagent.Linux-x86_64
-		// - dir:  ${workspaceFolder}/_output/share/lima
-		candidateForDebugBuild := filepath.Join(filepath.Dir(selfDirDir), "_output/share/lima/lima-guestagent."+ostype+"-"+arch)
-		gaCandidates = append(gaCandidates, candidateForDebugBuild)
-		logrus.Infof("debug mode detected, adding more guest agent candidates: %v", candidateForDebugBuild)
+	gaCandidates := []string{}
+	for _, self := range selfPaths {
+		// self:  /usr/local/bin/limactl
+		selfDir := filepath.Dir(self)
+		selfDirDir := filepath.Dir(selfDir)
+		gaCandidates = append(gaCandidates,
+			// candidate 0:
+			// - self:  /Applications/Lima.app/Contents/MacOS/limactl
+			// - agent: /Applications/Lima.app/Contents/MacOS/lima-guestagent.Linux-x86_64
+			// - dir:   /Applications/Lima.app/Contents/MacOS
+			filepath.Join(selfDir, "lima-guestagent."+ostype+"-"+arch),
+			// candidate 1:
+			// - self:  /usr/local/bin/limactl
+			// - agent: /usr/local/share/lima/lima-guestagent.Linux-x86_64
+			// - dir:   /usr/local/share/lima
+			filepath.Join(selfDirDir, "share/lima/lima-guestagent."+ostype+"-"+arch),
+			// TODO: support custom path
+		)
+		if debugutil.Debug {
+			// candidate 2: launched by `~/go/bin/dlv dap`
+			// - self: ${workspaceFolder}/cmd/limactl/__debug_bin_XXXXXX
+			// - agent: ${workspaceFolder}/_output/share/lima/lima-guestagent.Linux-x86_64
+			// - dir:  ${workspaceFolder}/_output/share/lima
+			candidateForDebugBuild := filepath.Join(filepath.Dir(selfDirDir), "_output/share/lima/lima-guestagent."+ostype+"-"+arch)
+			gaCandidates = append(gaCandidates, candidateForDebugBuild)
+			logrus.Infof("debug mode detected, adding more guest agent candidates: %v", candidateForDebugBuild)
+		}
 	}
+
 	for _, gaCandidate := range gaCandidates {
 		if _, err := os.Stat(gaCandidate); err == nil {
 			return filepath.Dir(gaCandidate), nil
@@ -95,8 +109,8 @@ func Dir() (string, error) {
 		}
 	}
 
-	return "", fmt.Errorf("failed to find \"lima-guestagent.%s-%s\" binary for %q, attempted %v",
-		ostype, arch, self, gaCandidates)
+	return "", fmt.Errorf("failed to find \"lima-guestagent.%s-%s\" binary for %v, attempted %v",
+		ostype, arch, selfPaths, gaCandidates)
 }
 
 // GuestAgentBinary returns the absolute path of the guest agent binary, possibly with ".gz" suffix.
diff --git a/website/content/en/docs/community/contributing.md b/website/content/en/docs/community/contributing.md
index c2504ddf..641c597a 100644
--- a/website/content/en/docs/community/contributing.md
+++ b/website/content/en/docs/community/contributing.md
@@ -9,13 +9,13 @@ Every commit must be signed off with the `Signed-off-by: REAL NAME <email@exampl
 
 Use the `git commit -s` command to add the Signed-off-by line.
 
-See also <https://github.com/cncf/foundation/blob/main/dco-guidelines.md>.
+See also <https://github.com/cncf/foundation/blob/main/policies-guidance/dco-guidelines.md>.
 
 ## Licensing
 
 Lima is licensed under the terms of [Apache License, Version 2.0](https://github.com/lima-vm/lima/blob/master/LICENSE).
 
-See also <https://github.com/cncf/foundation/blob/main/allowed-third-party-license-policy.md> for third-party dependencies.
+See also <https://github.com/cncf/foundation/blob/main/policies-guidance/allowed-third-party-license-policy.md> for third-party dependencies.
 
 ## Sending pull requests
 
diff --git a/website/content/en/docs/community/governance.md b/website/content/en/docs/community/governance.md
index d88cab74..490bf805 100644
--- a/website/content/en/docs/community/governance.md
+++ b/website/content/en/docs/community/governance.md
@@ -6,7 +6,7 @@ weight: 10
 <!-- The governance model is similar to https://github.com/containerd/project/blob/main/GOVERNANCE.md but simplified -->
 
 ## Code of Conduct
-Lima follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
+Lima follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/main/code-of-conduct.md).
 
 ## Maintainership
 Lima is governed by Maintainers who are elected from active contributors.
diff --git a/website/content/en/docs/config/multi-arch.md b/website/content/en/docs/config/multi-arch.md
index aa279c0d..ee77f072 100644
--- a/website/content/en/docs/config/multi-arch.md
+++ b/website/content/en/docs/config/multi-arch.md
@@ -58,7 +58,7 @@ $ lima nerdctl build --platform=amd64,arm64 -t example.com/foo:latest .
 $ lima nerdctl push --all-platforms example.com/foo:latest
 ```
 
-See also https://github.com/containerd/nerdctl/blob/master/docs/multi-platform.md
+See also https://github.com/containerd/nerdctl/blob/main/docs/multi-platform.md
 
 ## [Fast mode 2 (Rosetta): Intel containers on ARM VM on ARM Host](#fast-mode-2)
 
diff --git a/website/content/en/docs/installation/_index.md b/website/content/en/docs/installation/_index.md
index 25190610..40834393 100644
--- a/website/content/en/docs/installation/_index.md
+++ b/website/content/en/docs/installation/_index.md
@@ -36,7 +36,7 @@ Port: <https://ports.macports.org/port/lima/>
 nix-env -i lima
 ```
 
-Nix file: <https://github.com/NixOS/nixpkgs/blob/master/pkgs/applications/virtualization/lima/default.nix>
+Nix file: <https://github.com/NixOS/nixpkgs/blob/master/pkgs/by-name/li/lima/package.nix>
 {{% /tab %}}
 
 {{% tab header="Binary" %}}



Test patch:
diff --git a/pkg/limayaml/validate_test.go b/pkg/limayaml/validate_test.go
index ed223382..d23db802 100644
--- a/pkg/limayaml/validate_test.go
+++ b/pkg/limayaml/validate_test.go
@@ -40,6 +40,37 @@ func TestValidateProbes(t *testing.T) {
 	assert.Error(t, err, "field `probe[0].file.digest` support is not yet implemented")
 }
 
+func TestValidateProvisionMode(t *testing.T) {
+	images := `images: [{location: /}]`
+	provisionBoot := `provision: [{mode: boot, script: "touch /tmp/param-$PARAM_BOOT"}]`
+	y, err := Load([]byte(provisionBoot+"\n"+images), "lima.yaml")
+	assert.NilError(t, err)
+
+	err = Validate(y, false)
+	assert.NilError(t, err)
+
+	provisionUser := `provision: [{mode: user, script: "touch /tmp/param-$PARAM_USER"}]`
+	y, err = Load([]byte(provisionUser+"\n"+images), "lima.yaml")
+	assert.NilError(t, err)
+
+	err = Validate(y, false)
+	assert.NilError(t, err)
+
+	provisionDependency := `provision: [{mode: ansible, script: "touch /tmp/param-$PARAM_DEPENDENCY"}]`
+	y, err = Load([]byte(provisionDependency+"\n"+images), "lima.yaml")
+	assert.NilError(t, err)
+
+	err = Validate(y, false)
+	assert.NilError(t, err)
+
+	provisionInvalid := `provision: [{mode: invalid}]`
+	y, err = Load([]byte(provisionInvalid+"\n"+images), "lima.yaml")
+	assert.NilError(t, err)
+
+	err = Validate(y, false)
+	assert.Error(t, err, "field `provision[0].mode` must one of \"system\", \"user\", \"boot\", \"data\", \"dependency\", or \"ansible\"")
+}
+
 func TestValidateProvisionData(t *testing.T) {
 	images := `images: [{location: /}]`
 	validData := `provision: [{mode: data, path: /tmp, content: hello}]`
diff --git a/pkg/sshutil/sshutil_test.go b/pkg/sshutil/sshutil_test.go
index 972c3037..4b8b81cc 100644
--- a/pkg/sshutil/sshutil_test.go
+++ b/pkg/sshutil/sshutil_test.go
@@ -29,6 +29,16 @@ func TestParseOpenSSHVersion(t *testing.T) {
 
 	// OpenBSD 5.8
 	assert.Check(t, ParseOpenSSHVersion([]byte("OpenSSH_7.0, LibreSSL")).Equal(*semver.New("7.0.0")))
+
+	// NixOS 25.05
+	assert.Check(t, ParseOpenSSHVersion([]byte(`command-line line 0: Unsupported option "gssapiauthentication"
+OpenSSH_10.0p2, OpenSSL 3.4.1 11 Feb 2025`)).Equal(*semver.New("10.0.2")))
+}
+
+func TestParseOpenSSHGSSAPISupported(t *testing.T) {
+	assert.Check(t, parseOpenSSHGSSAPISupported("OpenSSH_8.4p1 Ubuntu"))
+	assert.Check(t, !parseOpenSSHGSSAPISupported(`command-line line 0: Unsupported option "gssapiauthentication"
+OpenSSH_10.0p2, OpenSSL 3.4.1 11 Feb 2025`))
 }
 
 func Test_detectValidPublicKey(t *testing.T) {