diff --git a/Makefile b/Makefile
index bef4a146..df44f781 100644
--- a/Makefile
+++ b/Makefile
@@ -644,10 +644,12 @@ shfmt:
.PHONY: go-licenses
go-licenses:
- # the allow list corresponds to https://github.com/cncf/foundation/blob/e5db022a0009f4db52b89d9875640cf3137153fe/allowed-third-party-license-policy.md
+ # the allow list corresponds to https://github.com/cncf/foundation/blob/87e70a07d5a0cf06cd6f8208b8d52c16f035724a/policies-guidance/allowed-third-party-license-policy.md
# hashicorp/hcl/v2 is MPL-2.0; covered by the CNCF license exception for hashicorp/hcl
# see also https://github.com/cncf/foundation/issues/1242
- go-licenses check --include_tests --ignore github.com/hashicorp/hcl/v2 ./... --allowed_licenses=$$(cat ./hack/allowed-licenses.txt)
+ # segmentio/asm is MIT-0; approved by CNCF, but go-licenses has not support it yet.
+ # see also https://github.com/google/licenseclassifier/issues/75
+ go-licenses check --include_tests --ignore github.com/hashicorp/hcl/v2 --ignore github.com/segmentio/asm ./... --allowed_licenses=$$(cat ./hack/allowed-licenses.txt)
.PHONY: gosocialcheck
gosocialcheck:
diff --git a/cmd/limactl/main_hcs_windows.go b/cmd/limactl/main_hcs_windows.go
new file mode 100644
index 00000000..1b8b5a64
--- /dev/null
+++ b/cmd/limactl/main_hcs_windows.go
@@ -0,0 +1,9 @@
+//go:build !external_hcs
+
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package main
+
+// Import hcs driver to register it in the registry on windows.
+import _ "github.com/lima-vm/lima/v2/pkg/driver/hcs"
diff --git a/cmd/limactl/main_windows.go b/cmd/limactl/main_wsl2_windows.go
similarity index 100%
rename from cmd/limactl/main_windows.go
rename to cmd/limactl/main_wsl2_windows.go
diff --git a/go.mod b/go.mod
index 20c9ed84..2d2973c1 100644
--- a/go.mod
+++ b/go.mod
@@ -18,7 +18,8 @@ require (
al.essio.dev/pkg/shellescape v1.6.0
github.com/AlecAivazis/survey/v2 v2.3.7
github.com/Code-Hex/vz/v3 v3.7.1 // gomodjail:unconfined
- github.com/Microsoft/go-winio v0.6.2 // gomodjail:unconfined
+ github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 // gomodjail:unconfined
+ github.com/Microsoft/hcsshim v0.14.1
github.com/apparentlymart/go-cidr v1.1.1
github.com/balajiv113/fd v0.0.0-20230330094840-143eec500f3e
github.com/cheggaaa/pb/v3 v3.2.1 // gomodjail:unconfined
@@ -81,7 +82,11 @@ require (
github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect
github.com/buger/jsonparser v1.1.2 // indirect
+ github.com/containerd/cgroups/v3 v3.1.3 // indirect
+ github.com/containerd/errdefs v1.0.0 // indirect
+ github.com/containerd/errdefs/pkg v0.3.0 // indirect
github.com/containerd/log v0.1.0 // indirect
+ github.com/containerd/typeurl/v2 v2.3.0 // indirect
github.com/digitalocean/go-libvirt v0.0.0-20220804181439-8648fbde413e // indirect
github.com/dimchansky/utfbom v1.1.1 // indirect
github.com/djherbis/times v1.6.0 // indirect
@@ -91,6 +96,7 @@ require (
github.com/fsnotify/fsnotify v1.8.0 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/goccy/go-json v0.10.6 // indirect
+ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/btree v1.1.3 // indirect
github.com/google/gopacket v1.1.19 // indirect
github.com/google/jsonschema-go v0.4.3 // indirect
@@ -114,13 +120,14 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 // indirect
- github.com/segmentio/asm v1.1.3 // indirect
+ github.com/segmentio/asm v1.2.1 // indirect
github.com/segmentio/encoding v0.5.4 // indirect
// gomodjail:unconfined
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
github.com/yuin/gopher-lua v1.1.2 // indirect
github.com/zclconf/go-cty v1.19.0 // indirect
+ go.opencensus.io v0.24.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
go.yaml.in/yaml/v4 v4.0.0-rc.6 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa // indirect
diff --git a/go.sum b/go.sum
index ecb954cd..1cac1ade 100644
--- a/go.sum
+++ b/go.sum
@@ -1,13 +1,17 @@
al.essio.dev/pkg/shellescape v1.6.0 h1:NxFcEqzFSEVCGN2yq7Huv/9hyCEGVa/TncnOOBBeXHA=
al.essio.dev/pkg/shellescape v1.6.0/go.mod h1:6sIqp7X2P6mThCQ7twERpZTuigpr6KbZWtls1U8I890=
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
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.7.1 h1:EN1yNiyrbPq+dl388nne2NySo8I94EnPppvqypA65XM=
github.com/Code-Hex/vz/v3 v3.7.1/go.mod h1:1LsW0jqW0r0cQ+IeR4hHbjdqOtSidNCVMWhStMHGho8=
-github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
-github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
+github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29 h1:0kQAzHq8vLs7Pptv+7TxjdETLf/nIqJpIB4oC6Ba4vY=
+github.com/Microsoft/go-winio v0.6.3-0.20251027160822-ad3df93bed29/go.mod h1:ZWa7ssZJT30CCDGJ7fk/2SBTq9BIQrrVjrcss0UW2s0=
+github.com/Microsoft/hcsshim v0.14.1 h1:CMuB3fqQVfPdhyXhUqYdUmPUIOhJkmghCx3dJet8Cqs=
+github.com/Microsoft/hcsshim v0.14.1/go.mod h1:VnzvPLyWUhxiPVsJ31P6XadxCcTogTguBFDy/1GR/OM=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
github.com/VividCortex/ewma v1.2.0 h1:f58SaIzcDXrSy3kWaHNvuJgJ3Nmz59Zji6XoJR/q1ow=
@@ -39,18 +43,29 @@ github.com/bmatcuk/doublestar/v4 v4.7.1 h1:fdDeAqgT47acgwd9bd9HxJRDmc9UAmPpc+2m0
github.com/bmatcuk/doublestar/v4 v4.7.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/buger/jsonparser v1.1.2 h1:frqHqw7otoVbk5M8LlE/L7HTnIq2v9RX6EJ48i9AxJk=
github.com/buger/jsonparser v1.1.2/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cheggaaa/pb/v3 v3.2.1 h1:aprZbFRG+B7+ug76S8QZ6Y1PW168UHzOmbC3wa+aU6I=
github.com/cheggaaa/pb/v3 v3.2.1/go.mod h1:U9hSVxoKqJrZIE3PkFG1xXXNaK/Ilzg+EF/scpXzEdw=
github.com/cilium/ebpf v0.22.0 h1:v2ktp0roffpMOj2MMf3idtCQZOsAoC4BJbAJN+ke2bY=
github.com/cilium/ebpf v0.22.0/go.mod h1:CDzZbe2hC5JjlDC+CY3KFCzlYwN4gbxppYM+Z10bQt4=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/containerd/cgroups/v3 v3.1.3 h1:eUNflyMddm18+yrDmZPn3jI7C5hJ9ahABE5q6dyLYXQ=
+github.com/containerd/cgroups/v3 v3.1.3/go.mod h1:PKZ2AcWmSBsY/tJUVhtS/rluX0b1uq1GmPO1ElCmbOw=
github.com/containerd/continuity v0.5.0 h1:7a85HZpCSs+1Zps0Ee3DPSuAWY+0SJM1JNM51nlEVDg=
github.com/containerd/continuity v0.5.0/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE=
+github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
+github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
+github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
+github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
+github.com/containerd/typeurl/v2 v2.3.0 h1:HZHPhRWo5XMy3QGQoPrUzbW/2ckwjfweHmOwlkIrPAQ=
+github.com/containerd/typeurl/v2 v2.3.0/go.mod h1:Qk+PAdUYArVj41TnGi6rJ+48RF0PkcTc4i/taoBcK0w=
github.com/containers/gvisor-tap-vsock v0.8.9 h1:6b7pqxFcKJ0EycBt1V4zPo3FQtgLLgs50AYkbFIb9eU=
github.com/containers/gvisor-tap-vsock v0.8.9/go.mod h1:OfqLraPkar5xMQcGbl9czDDSM6/xelt0HJpyB3es6v0=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4=
@@ -84,6 +99,10 @@ github.com/elliotchance/orderedmap v1.8.0 h1:TrOREecvh3JbS+NCgwposXG5ZTFHtEsQiCG
github.com/elliotchance/orderedmap v1.8.0/go.mod h1:wsDwEaX5jEoyhbs7x93zk2H/qv0zwuhg4inXhDkYqys=
github.com/elliotwutingfeng/asciiset v0.0.0-20260129054604-cfde2086bc57 h1:x5yxNrq8XffV/OoNUeFPM6hxHVi5OTspSTBxr/9pemg=
github.com/elliotwutingfeng/asciiset v0.0.0-20260129054604-cfde2086bc57/go.mod h1:GLo/8fDswSAniFG+BFIaiSPcK610jyzgEhWYPQwuQdw=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w=
github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE=
github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0=
@@ -107,10 +126,30 @@ github.com/goccy/go-yaml v1.19.2 h1:PmFC1S6h8ljIz6gMRBopkjP1TVT7xuwrButHID66PoM=
github.com/goccy/go-yaml v1.19.2/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
+github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg=
github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF8=
@@ -119,6 +158,7 @@ github.com/google/jsonschema-go v0.4.3 h1:/DBOLZTfDow7pe2GmaJNhltueGTtDKICi8V8p+
github.com/google/jsonschema-go v0.4.3/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
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.21.0 h1:9FKApQkDpMKgBjwLFytBHUCgqnQgxaQnci0uiESfbzs=
@@ -218,6 +258,7 @@ github.com/pkg/sftp v1.13.11/go.mod h1:uNkH9roSXglNJqM+glJJi+TQXQUm0fXFWqCFmT8hs
github.com/pkg/xattr v0.4.12 h1:rRTkSyFNTRElv6pkA3zpjHpQ90p/OdHQC1GmGh1aTjM=
github.com/pkg/xattr v0.4.12/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/rjeczalik/notify v0.9.3 h1:6rJAzHTGKXGj76sbRgDiDcYj/HniypXmSJo1SWakZeY=
github.com/rjeczalik/notify v0.9.3/go.mod h1:gF3zSOrafR9DQEWSE8TjfI9NkooDxbyT4UgRGKZA0lc=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
@@ -228,8 +269,8 @@ github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDj
github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3 h1:1EYB5IzjZawrrnELUi78f9fPu57HuXjmddZPjrls/28=
github.com/santhosh-tekuri/jsonschema/v6 v6.0.3/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
-github.com/segmentio/asm v1.1.3 h1:WM03sfUOENvvKexOLp+pCqgb/WDjsi7EK8gIsICtzhc=
-github.com/segmentio/asm v1.1.3/go.mod h1:Ld3L4ZXGNcSLRg4JBsZ3//1+f/TjYl0Mzen/DQy1EJg=
+github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
+github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0=
github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0=
github.com/sethvargo/go-password v0.4.0 h1:eSidVKQw5C7CmTDAtH3RipBTSjdU1ZRxQaynD2GWLVU=
@@ -242,8 +283,13 @@ github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.12.0 h1:K6Mr6jO9JICuend/5xzTM03ydSV3vdNRYAdPSukj8uI=
github.com/stretchr/testify v1.12.0/go.mod h1:bOYBZb5qJ00vPzWfIqBUZPaxK8jWiXc6d3ErP4Ca9Gw=
github.com/u-root/uio v0.0.0-20240224005618-d2acac8f3701 h1:pyC9PaHYZFgEKFdlp3G8RaCKgVpHZnecvArXvPXcFkM=
@@ -260,6 +306,8 @@ github.com/zclconf/go-cty v1.19.0 h1:IV8WdqYZc2c5rLX9bEoLNXKojBAp0MZPBHMIrCoa/s4
github.com/zclconf/go-cty v1.19.0/go.mod h1:12W89jGn3JCOIQi7infWr9m80rOkb5RNYJqXMZcN4c8=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
+go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
+go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU=
@@ -278,12 +326,17 @@ go.yaml.in/yaml/v4 v4.0.0-rc.6 h1:1h7H1ohdUh93/FyE4YaDa1Zh64K6VVbjF4K6WUxMtH4=
go.yaml.in/yaml/v4 v4.0.0-rc.6/go.mod h1:aZqd9kCMsGL7AuUv/m/PvWLdg5sjJsZ4oHDEnfPPfY0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@@ -293,8 +346,13 @@ golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.40.0 h1:hUv+3cXcdRHz08UmSiOob7sadHig73uo5bkXxQ/tvUs=
golang.org/x/mod v0.40.0/go.mod h1:0/weTWkPWGBikyTWAX3dkjVztMmBA5hM0DH6BElSupE=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
@@ -305,8 +363,11 @@ golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ=
golang.org/x/net v0.58.0 h1:ynWG7rqYi4ccpTEuPZ2QGWHktVEM9DMCj9yzDE0Q7To=
golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs=
golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -316,10 +377,12 @@ golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180926160741-c2ed4eda69e7/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -357,6 +420,10 @@ golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
@@ -368,13 +435,33 @@ golang.org/x/tools v0.49.0 h1:3NI7VXzL9+1WZD52Dx2ttoPwD5DWrFGpl9mFZDlmisI=
golang.org/x/tools v0.49.0/go.mod h1:SJNXV9DBKT0UbdttsQjbfJlAE/q+y36++zo3uL3N0Oo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4=
gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa h1:mZHHdPZl0dbGHCflZgAq/Q468DWVFcU2whhB2KAo8fk=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.83.1 h1:HIO0+BEtBP6soyqvqC8sNUjZ7bTs+0hFQuFF+RAy++Y=
google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc=
google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
@@ -390,3 +477,5 @@ 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=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/hack/allowed-licenses.txt b/hack/allowed-licenses.txt
index 0d34de67..e6a780ba 100644
--- a/hack/allowed-licenses.txt
+++ b/hack/allowed-licenses.txt
@@ -1 +1 @@
-Apache-2.0,BSD-2-Clause,BSD-2-Clause-FreeBSD,BSD-3-Clause,MIT,ISC,Python-2.0,PostgreSQL,X11,Zlib
+Apache-2.0,BSD-2-Clause,BSD-2-Clause-FreeBSD,BSD-3-Clause,MIT,MIT-0,ISC,Python-2.0,PostgreSQL,X11,Zlib
diff --git a/pkg/cidata/cidata.go b/pkg/cidata/cidata.go
index 26c82eda..b1d33390 100644
--- a/pkg/cidata/cidata.go
+++ b/pkg/cidata/cidata.go
@@ -294,7 +294,7 @@ func templateArgs(ctx context.Context, bootScripts bool, instDir, name string, i
}
case firstUsernetIndex != -1 || *instConfig.VMType == limatype.VZ:
args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS)
- case *instConfig.HostResolver.Enabled:
+ case limayaml.HostResolverEnabled(instConfig):
args.UDPDNSLocalPort = udpDNSLocalPort
args.TCPDNSLocalPort = tcpDNSLocalPort
args.DNSAddresses = append(args.DNSAddresses, args.SlirpDNS)
diff --git a/pkg/driver/driver.go b/pkg/driver/driver.go
index b8fb2d0f..7f901965 100644
--- a/pkg/driver/driver.go
+++ b/pkg/driver/driver.go
@@ -119,7 +119,7 @@ type ConfiguredDriver struct {
// the instance directory and the driver's capability flags. It is returned by
// Driver.Info and, for external drivers, carried as JSON in InfoResponse.
type Info struct {
- // Name is the driver name, e.g. "qemu", "vz", "wsl2" or "krunkit".
+ // Name is the driver name, e.g. "qemu", "vz", "wsl2", "hcs" or "krunkit".
Name string `json:"name"`
// VsockPort is the vsock port used by the guest agent, or 0 when vsock is
// not used.
diff --git a/pkg/driver/hcs/errors_windows.go b/pkg/driver/hcs/errors_windows.go
new file mode 100644
index 00000000..19488b32
--- /dev/null
+++ b/pkg/driver/hcs/errors_windows.go
@@ -0,0 +1,8 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package hcs
+
+import "errors"
+
+var errUnimplemented = errors.New("unimplemented")
diff --git a/pkg/driver/hcs/hcs_api_windows.go b/pkg/driver/hcs/hcs_api_windows.go
new file mode 100644
index 00000000..5b2c9558
--- /dev/null
+++ b/pkg/driver/hcs/hcs_api_windows.go
@@ -0,0 +1,286 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package hcs
+
+import (
+ "context"
+ "fmt"
+ "syscall"
+ "time"
+ "unsafe"
+
+ "github.com/sirupsen/logrus"
+ "golang.org/x/sys/windows"
+)
+
+// ---------------------------------------------------------------------------
+// computecore.dll (HCS) bindings
+// ---------------------------------------------------------------------------
+
+var (
+ modcomputecore = windows.NewLazySystemDLL("computecore.dll")
+
+ procHcsCreateOperation = modcomputecore.NewProc("HcsCreateOperation")
+ procHcsCloseOperation = modcomputecore.NewProc("HcsCloseOperation")
+ procHcsWaitForOperationResult = modcomputecore.NewProc("HcsWaitForOperationResult")
+ procHcsCreateComputeSystem = modcomputecore.NewProc("HcsCreateComputeSystem")
+ procHcsStartComputeSystem = modcomputecore.NewProc("HcsStartComputeSystem")
+ procHcsTerminateComputeSystem = modcomputecore.NewProc("HcsTerminateComputeSystem")
+ procHcsCloseComputeSystem = modcomputecore.NewProc("HcsCloseComputeSystem")
+ procHcsGrantVMAccess = modcomputecore.NewProc("HcsGrantVmAccess")
+ procHcsOpenComputeSystem = modcomputecore.NewProc("HcsOpenComputeSystem")
+ procHcsWaitForComputeSystemExit = modcomputecore.NewProc("HcsWaitForComputeSystemExit")
+ procHcsGetComputeSystemProperties = modcomputecore.NewProc("HcsGetComputeSystemProperties")
+
+ modapiMsWinCoreComL110 = windows.NewLazySystemDLL("api-ms-win-core-com-l1-1-0.dll")
+ procCoTaskMemFree = modapiMsWinCoreComL110.NewProc("CoTaskMemFree")
+)
+
+type (
+ hcsOperation syscall.Handle
+ hcsSystem syscall.Handle
+)
+
+const (
+ infiniteTimeout = 0xFFFFFFFF
+ syscallWatcherTimeout = 4 * time.Minute
+)
+
+// hresultErr converts an HRESULT return value into a Go error, unwrapping
+// FACILITY_WIN32 the same way hcsshim's generated bindings do.
+func hresultErr(r0 uintptr) error {
+ if int32(r0) >= 0 {
+ return nil
+ }
+ if r0&0x1fff0000 == 0x00070000 {
+ r0 &= 0xffff
+ }
+ return syscall.Errno(r0)
+}
+
+// coString copies a CoTaskMem-allocated PWSTR into a Go string and frees it.
+func coString(p *uint16) string {
+ if p == nil {
+ return ""
+ }
+ s := windows.UTF16PtrToString(p)
+ windows.CoTaskMemFree(unsafe.Pointer(p))
+ return s
+}
+
+func hcsCreateOperation() (hcsOperation, error) {
+ r0, _, e1 := syscall.SyscallN(procHcsCreateOperation.Addr(), 0, 0)
+ if r0 == 0 {
+ return 0, fmt.Errorf("HcsCreateOperation: %w", e1)
+ }
+ return hcsOperation(r0), nil
+}
+
+func hcsCloseOperation(op hcsOperation) {
+ _, _, _ = syscall.SyscallN(procHcsCloseOperation.Addr(), uintptr(op))
+}
+
+// hcsWait drives one async HCS call to completion and returns the result
+// document (which carries structured error info on failure).
+func hcsWait(op hcsOperation, what string, callErr error) (string, error) {
+ if callErr != nil {
+ return "", fmt.Errorf("%s: %w", what, callErr)
+ }
+ var result *uint16
+ r0, _, _ := syscall.SyscallN(procHcsWaitForOperationResult.Addr(),
+ uintptr(op), uintptr(infiniteTimeout), uintptr(unsafe.Pointer(&result)))
+ doc := coString(result)
+ if err := hresultErr(r0); err != nil {
+ return doc, fmt.Errorf("%s: %w (result: %s)", what, err, doc)
+ }
+ return doc, nil
+}
+
+func hcsCreateComputeSystem(id, configuration string) (hcsSystem, error) {
+ op, err := hcsCreateOperation()
+ if err != nil {
+ return 0, err
+ }
+ defer hcsCloseOperation(op)
+
+ idP, err := syscall.UTF16PtrFromString(id)
+ if err != nil {
+ return 0, err
+ }
+ cfgP, err := syscall.UTF16PtrFromString(configuration)
+ if err != nil {
+ return 0, err
+ }
+ var system hcsSystem
+ r0, _, _ := syscall.SyscallN(procHcsCreateComputeSystem.Addr(),
+ uintptr(unsafe.Pointer(idP)), uintptr(unsafe.Pointer(cfgP)),
+ uintptr(op), 0, uintptr(unsafe.Pointer(&system)))
+ if _, err := hcsWait(op, "HcsCreateComputeSystem", hresultErr(r0)); err != nil {
+ return 0, err
+ }
+ return system, nil
+}
+
+func hcsStartComputeSystem(system hcsSystem) error {
+ op, err := hcsCreateOperation()
+ if err != nil {
+ return err
+ }
+ defer hcsCloseOperation(op)
+ r0, _, _ := syscall.SyscallN(procHcsStartComputeSystem.Addr(),
+ uintptr(system), uintptr(op), 0)
+ _, err = hcsWait(op, "HcsStartComputeSystem", hresultErr(r0))
+ return err
+}
+
+func hcsTerminateComputeSystem(system hcsSystem) error {
+ op, err := hcsCreateOperation()
+ if err != nil {
+ return err
+ }
+ defer hcsCloseOperation(op)
+ r0, _, _ := syscall.SyscallN(procHcsTerminateComputeSystem.Addr(),
+ uintptr(system), uintptr(op), 0)
+ _, err = hcsWait(op, "HcsTerminateComputeSystem", hresultErr(r0))
+ return err
+}
+
+func execute(ctx context.Context, timeout time.Duration, f func() error) error {
+ done := make(chan error, 1)
+ go func() { done <- f() }()
+
+ var watcher <-chan time.Time
+ if timeout > 0 {
+ t := time.NewTimer(timeout)
+ defer t.Stop()
+ watcher = t.C
+ }
+
+ for {
+ select {
+ case err := <-done:
+ return err
+ case <-watcher:
+ logrus.WithField("timeout", timeout).
+ Warn("HCS syscall exceeded timeout; still waiting to avoid use-after-free in computecore.dll")
+ watcher = nil
+ case <-ctx.Done():
+ logrus.WithError(ctx.Err()).
+ Warn("HCS syscall context canceled; still waiting to avoid use-after-free in computecore.dll")
+ ctx = context.WithoutCancel(ctx)
+ }
+ }
+}
+
+func hcsGetComputeSystemProperties(ctx context.Context, system hcsSystem, operation hcsOperation, propertyQuery string) (hr error) {
+ return execute(ctx, syscallWatcherTimeout, func() error {
+ return _hcsGetComputeSystemProperties(system, operation, propertyQuery)
+ })
+}
+
+func _hcsGetComputeSystemProperties(system hcsSystem, operation hcsOperation, propertyQuery string) (hr error) {
+ var _p0 *uint16
+ _p0, hr = syscall.UTF16PtrFromString(propertyQuery)
+ if hr != nil {
+ return hr
+ }
+
+ hr = procHcsGetComputeSystemProperties.Find()
+ if hr != nil {
+ return hr
+ }
+ r0, _, _ := syscall.SyscallN(procHcsGetComputeSystemProperties.Addr(), uintptr(system), uintptr(operation), uintptr(unsafe.Pointer(_p0)))
+ if int32(r0) < 0 {
+ if r0&0x1fff0000 == 0x00070000 {
+ r0 &= 0xffff
+ }
+ hr = syscall.Errno(r0)
+ }
+ return hr
+}
+
+func hcsOpenComputeSystem(ctx context.Context, id string, requestedAccess uint32) (computeSystem hcsSystem, hr error) {
+ hr = execute(ctx, syscallWatcherTimeout, func() error {
+ return _hcsOpenComputeSystem(id, requestedAccess, &computeSystem)
+ })
+ return computeSystem, hr
+}
+
+func _hcsOpenComputeSystem(id string, requestedAccess uint32, computeSystem *hcsSystem) (hr error) {
+ var _p0 *uint16
+ _p0, hr = syscall.UTF16PtrFromString(id)
+ if hr != nil {
+ return hr
+ }
+
+ hr = procHcsOpenComputeSystem.Find()
+ if hr != nil {
+ return hr
+ }
+ r0, _, _ := syscall.SyscallN(procHcsOpenComputeSystem.Addr(), uintptr(unsafe.Pointer(_p0)), uintptr(requestedAccess), uintptr(unsafe.Pointer(computeSystem)))
+ if int32(r0) < 0 {
+ if r0&0x1fff0000 == 0x00070000 {
+ r0 &= 0xffff
+ }
+ hr = syscall.Errno(r0)
+ }
+
+ return hr
+}
+
+func coTaskMemFree(buffer unsafe.Pointer) {
+ _, _, _ = syscall.SyscallN(procCoTaskMemFree.Addr(), uintptr(buffer))
+}
+
+func convertAndFreeCoTaskMemString(buffer *uint16) string {
+ str := syscall.UTF16ToString((*[1 << 29]uint16)(unsafe.Pointer(buffer))[:])
+ coTaskMemFree(unsafe.Pointer(buffer))
+ return str
+}
+
+func hcsWaitForComputeSystemExit(ctx context.Context, system hcsSystem, timeoutMs uint32) (result string, hr error) {
+ return result, execute(ctx, syscallWatcherTimeout, func() error {
+ var resultp *uint16
+ err := _hcsWaitForComputeSystemExit(system, timeoutMs, &resultp)
+ if resultp != nil {
+ result = convertAndFreeCoTaskMemString(resultp)
+ }
+ return err
+ })
+}
+
+func _hcsWaitForComputeSystemExit(system hcsSystem, timeoutMs uint32, result **uint16) (hr error) {
+ hr = procHcsWaitForComputeSystemExit.Find()
+ if hr != nil {
+ return hr
+ }
+ r0, _, _ := syscall.SyscallN(procHcsWaitForComputeSystemExit.Addr(), uintptr(system), uintptr(timeoutMs), uintptr(unsafe.Pointer(result)))
+ if int32(r0) < 0 {
+ if r0&0x1fff0000 == 0x00070000 {
+ r0 &= 0xffff
+ }
+ hr = syscall.Errno(r0)
+ }
+ return hr
+}
+
+func hcsCloseComputeSystem(system hcsSystem) {
+ _, _, _ = syscall.SyscallN(procHcsCloseComputeSystem.Addr(), uintptr(system))
+}
+
+// hcsGrantVMAccess ACLs a host file so the VM worker process may read it
+// (hcsshim does the equivalent before every kernel-direct boot).
+func hcsGrantVMAccess(vmID, path string) error {
+ idP, err := syscall.UTF16PtrFromString(vmID)
+ if err != nil {
+ return err
+ }
+ pathP, err := syscall.UTF16PtrFromString(path)
+ if err != nil {
+ return err
+ }
+ r0, _, _ := syscall.SyscallN(procHcsGrantVMAccess.Addr(),
+ uintptr(unsafe.Pointer(idP)), uintptr(unsafe.Pointer(pathP)))
+ return hresultErr(r0)
+}
diff --git a/pkg/driver/hcs/hcs_driver_windows.go b/pkg/driver/hcs/hcs_driver_windows.go
new file mode 100644
index 00000000..b8e3aba0
--- /dev/null
+++ b/pkg/driver/hcs/hcs_driver_windows.go
@@ -0,0 +1,416 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package hcs
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net"
+ "os"
+ "path/filepath"
+ "time"
+
+ "github.com/Microsoft/go-winio"
+ "github.com/Microsoft/hcsshim/hcn"
+ "github.com/docker/go-units"
+ "github.com/lima-vm/go-qcow2reader/image/vhdx"
+ "github.com/sirupsen/logrus"
+
+ "github.com/lima-vm/lima/v2/pkg/driver"
+ "github.com/lima-vm/lima/v2/pkg/limatype"
+ "github.com/lima-vm/lima/v2/pkg/limatype/filenames"
+ "github.com/lima-vm/lima/v2/pkg/reflectutil"
+)
+
+var knownYamlProperties = []string{
+ "AdditionalDisks",
+ "Arch",
+ "Audio",
+ "CACertificates",
+ "Containerd",
+ "CopyToHost",
+ "CPUs",
+ "CPUType",
+ "Disk",
+ "DNS",
+ "Env",
+ "Firmware",
+ "GuestInstallPrefix",
+ "HostResolver",
+ "Images",
+ "Memory",
+ "Message",
+ "MinimumLimaVersion",
+ "Mounts",
+ "MountType",
+ "MountTypesUnsupported",
+ "MountInotify",
+ "NestedVirtualization",
+ "Networks",
+ "OS",
+ "Param",
+ "Plain",
+ "PortForwards",
+ "Probes",
+ "PropagateProxyEnv",
+ "Provision",
+ "SSH",
+ "TimeZone",
+ "TPM",
+ "UpgradePackages",
+ "User",
+ "Video",
+ "VMType",
+ "VMOpts",
+}
+
+const Enabled = true
+
+type LimaHcsDriver struct {
+ Instance *limatype.Instance
+
+ SSHLocalPort int
+ vSockPort int
+ virtioPort string
+
+ system hcsSystem
+ serialListener net.Listener
+ sshAddress string
+}
+
+var _ driver.Driver = (*LimaHcsDriver)(nil)
+
+func New() *LimaHcsDriver {
+ return &LimaHcsDriver{}
+}
+
+func (l *LimaHcsDriver) Configure(ctx context.Context, inst *limatype.Instance) (*driver.ConfiguredDriver, error) {
+ cfg := inst.Config
+ if cfg.VMType == nil {
+ cfg.VMType = new(limatype.HCS)
+ }
+ if cfg.MountType == nil {
+ cfg.MountType = new(limatype.REVSSHFS)
+ }
+ if err := validateConfig(ctx, cfg); err != nil {
+ return nil, err
+ }
+
+ l.Instance = inst
+ l.SSHLocalPort = inst.SSHLocalPort
+
+ return &driver.ConfiguredDriver{
+ Driver: l,
+ }, nil
+}
+
+func (l *LimaHcsDriver) Validate(ctx context.Context) error {
+ return validateConfig(ctx, l.Instance.Config)
+}
+
+func validateConfig(_ context.Context, cfg *limatype.LimaYAML) error {
+ if cfg == nil {
+ return errors.New("configuration is nil")
+ }
+ // TODO: revise this list for HCS
+ if cfg.VMType != nil {
+ if unknown := reflectutil.UnknownNonEmptyFields(cfg, knownYamlProperties...); len(unknown) > 0 {
+ logrus.Warnf("Ignoring: vmType %s: %+v", *cfg.VMType, unknown)
+ }
+ }
+
+ if cfg.OS != nil && *cfg.OS == limatype.WINDOWS {
+ return errors.New("currently Windows guest OS is only supported on QEMU")
+ }
+
+ if !limatype.IsNativeArch(*cfg.Arch) {
+ return fmt.Errorf("unsupported arch: %#q", *cfg.Arch)
+ }
+
+ if cfg.TPM != nil && *cfg.TPM {
+ return errors.New("field `tpm` is not supported on HCS driver")
+ }
+
+ if cfg.VMType != nil {
+ if cfg.Mounts != nil {
+ for i, mount := range cfg.Mounts {
+ if unknown := reflectutil.UnknownNonEmptyFields(mount); len(unknown) > 0 {
+ logrus.Warnf("Ignoring: vmType %s: mounts[%d]: %+v", *cfg.VMType, i, unknown)
+ }
+ }
+ }
+
+ if cfg.Networks != nil {
+ for i, network := range cfg.Networks {
+ if unknown := reflectutil.UnknownNonEmptyFields(network); len(unknown) > 0 {
+ logrus.Warnf("Ignoring: vmType %s: networks[%d]: %+v", *cfg.VMType, i, unknown)
+ }
+ }
+ }
+
+ if cfg.Audio.Device != nil {
+ audioDevice := *cfg.Audio.Device
+ if audioDevice != "" {
+ logrus.Warnf("Ignoring: vmType %s: `audio.device`: %+v", *cfg.VMType, audioDevice)
+ }
+ }
+
+ if cfg.HostResolver.Enabled == nil {
+ cfg.HostResolver.Enabled = new(false)
+ }
+ if *cfg.HostResolver.Enabled {
+ return errors.New("the HCS driver does not support HostResolver.Enabled")
+ }
+ }
+
+ return nil
+}
+
+func (l *LimaHcsDriver) BootScripts(_ context.Context) (map[string][]byte, error) {
+ return nil, nil
+}
+
+func (l *LimaHcsDriver) InspectStatus(ctx context.Context, inst *limatype.Instance) string {
+ inst.SSHLocalPort = 22
+ state, err := getComputeSystemState(ctx, "lima-"+inst.Name)
+ if err != nil {
+ if errors.Is(err, errNotFound) {
+ inst.Status = limatype.StatusUnknown
+ return inst.Status
+ }
+ inst.Status = limatype.StatusBroken
+ inst.Errors = append(inst.Errors, fmt.Errorf("failed to inspect HCS sompute system: %w", err))
+ return inst.Status
+ }
+ if state == "Running" {
+ inst.Status = limatype.StatusRunning
+ e, err := hcn.GetEndpointByName(endpointName(inst.Name))
+ if err != nil {
+ logrus.Errorf("failed to get HCN endpoint: %s", err.Error())
+ inst.Status = limatype.StatusBroken
+ return inst.Status
+ }
+ ips := e.IpConfigurations
+ if len(ips) == 0 {
+ logrus.Errorf("the HCN endpoint for instance %#q does not have an IP address yet", inst.Name)
+ inst.Status = limatype.StatusBroken
+ return inst.Status
+ }
+ ip := ips[0]
+ l.sshAddress = ip.IpAddress
+ inst.SSHAddress = ip.IpAddress
+ } else {
+ inst.Status = limatype.StatusStopped
+ }
+ return inst.Status
+}
+
+func (l *LimaHcsDriver) Delete(_ context.Context) error {
+ if endpoint, err := hcn.GetEndpointByName(endpointName(l.Instance.Name)); err == nil {
+ if err := endpoint.Delete(); err != nil {
+ return fmt.Errorf("failed to delete the HCN endpoint: %w", err)
+ }
+ }
+ if network, err := hcn.GetNetworkByName(networkName(l.Instance.Name)); err == nil {
+ if err := network.Delete(); err != nil {
+ return fmt.Errorf("failed to delete the HCN network: %w", err)
+ }
+ }
+ return nil
+}
+
+func (l *LimaHcsDriver) Start(ctx context.Context) (chan error, error) {
+ inst := l.Instance
+ diskPath := filepath.Join(inst.Dir, filenames.Disk) + ".vhdx"
+ isoPath := filepath.Join(inst.Dir, filenames.CIDataISO)
+
+ network, err := ensureNetwork(networkName(inst.Name))
+ if err != nil {
+ return nil, err
+ }
+ endpoint, err := ensureEndpoint(network, endpointName(inst.Name), inst.Dir)
+ if err != nil {
+ return nil, err
+ }
+
+ pipeName := `\\.\pipe\lima-` + inst.Name + `-com1`
+ ln, err := winio.ListenPipe(pipeName, nil)
+ if err != nil {
+ return nil, fmt.Errorf("failed to listen on %#q: %w", pipeName, err)
+ }
+ l.serialListener = ln
+ go serveSerial(ln, filepath.Join(inst.Dir, filenames.SerialLog))
+
+ vmID := "lima-" + inst.Name
+ for _, p := range []string{diskPath, isoPath} {
+ if err := hcsGrantVMAccess(vmID, p); err != nil {
+ return nil, fmt.Errorf("failed to grant VM access to %#q: %w", p, err)
+ }
+ }
+
+ memBytes, err := units.RAMInBytes(*inst.Config.Memory)
+ if err != nil {
+ return nil, err
+ }
+ config, err := buildVMConfig(diskPath, isoPath, endpoint.Id, endpoint.MacAddress, pipeName, *inst.Config.CPUs, memBytes>>20)
+ if err != nil {
+ return nil, err
+ }
+ logrus.Debugf("HCS compute system config: %s", config)
+
+ system, err := createComputeSystem(ctx, "lima-"+inst.Name, config)
+ if err != nil {
+ return nil, err
+ }
+ l.system = system
+ if err := startComputeSystem(ctx, system); err != nil {
+ return nil, err
+ }
+
+ for i := range 50 {
+ endpoint, err = hcn.GetEndpointByName(endpointName(inst.Name))
+ if err != nil {
+ logrus.Warnf("failed in GetEndpointByName: %s", err.Error())
+ time.Sleep(2 * time.Second)
+ continue
+ }
+ if len(endpoint.IpConfigurations) == 0 {
+ if i == 49 {
+ return nil, fmt.Errorf("HNS did not assign an IP address to the endpoint %#q", endpoint.Name)
+ }
+ time.Sleep(2 * time.Second)
+ continue
+ }
+ l.sshAddress = endpoint.IpConfigurations[0].IpAddress
+ logrus.Infof("HCS: the guest is expected to get %s (MAC %s)", l.sshAddress, endpoint.MacAddress)
+ break
+ }
+
+ errCh := make(chan error, 1)
+ go func() {
+ result, waitErr := hcsWaitForComputeSystemExit(ctx, system, hcsTimeoutInfinite)
+ if waitErr != nil {
+ errCh <- fmt.Errorf("HCS compute system exited: %w (result=%s)", waitErr, result)
+ return
+ }
+ errCh <- nil
+ }()
+ return errCh, nil
+}
+
+func (l *LimaHcsDriver) RunGUI(_ context.Context) error {
+ return fmt.Errorf("RunGUI is not supported for the given driver '%s' and display '%s'", "hcs", *l.Instance.Config.Video.Display)
+}
+
+func (l *LimaHcsDriver) Stop(ctx context.Context) error {
+ if l.system == 0 {
+ return nil
+ }
+ if err := terminateComputeSystem(ctx, l.system); err != nil {
+ return err
+ }
+ if l.serialListener != nil {
+ _ = l.serialListener.Close()
+ }
+
+ if endpoint, err := hcn.GetEndpointByName(endpointName(l.Instance.Name)); err == nil {
+ if err = endpoint.Delete(); err != nil {
+ return fmt.Errorf("failed to delete the HCN endpoint: %w", err)
+ }
+ }
+
+ if network, err := hcn.GetNetworkByName(networkName(l.Instance.Name)); err == nil {
+ if err := network.Delete(); err != nil {
+ return fmt.Errorf("failed to delete the HCN network %w", err)
+ }
+ }
+
+ return nil
+}
+
+// GuestAgentConn returns the guest agent connection, or nil (if forwarded by ssh).
+// As of 08-01-2024, github.com/mdlayher/vsock does not natively support vsock on
+// Windows, so use the winio library to create the connection.
+func (l *LimaHcsDriver) GuestAgentConn(_ context.Context) (net.Conn, string, error) {
+ return nil, "", nil
+}
+
+func (l *LimaHcsDriver) Info(_ context.Context) driver.Info {
+ var info driver.Info
+ info.Name = "hcs"
+ if l.Instance != nil {
+ info.InstanceDir = l.Instance.Dir
+ }
+ info.VirtioPort = l.virtioPort
+ info.VsockPort = l.vSockPort
+
+ info.Features = driver.DriverFeatures{
+ DynamicSSHAddress: true,
+ StaticSSHPort: true,
+ SkipSocketForwarding: true,
+ NoCloudInit: false,
+ CanRunGUI: false,
+ SupportedImageFormats: []string{string(vhdx.Type)},
+ }
+ return info
+}
+
+func (l *LimaHcsDriver) SSHAddress(_ context.Context) (string, error) {
+ if l.sshAddress == "" {
+ return "", errors.New("the SSH address is not known yet")
+ }
+ return l.sshAddress, nil
+}
+
+func (l *LimaHcsDriver) Create(_ context.Context) error {
+ return nil
+}
+
+func (l *LimaHcsDriver) CreateDisk(ctx context.Context) error {
+ if err := mayConvertQcow2ToVHDX(ctx, l.Instance.Dir); err != nil {
+ return err
+ }
+ disk := filepath.Join(l.Instance.Dir, filenames.Disk)
+ link := disk + ".vhdx"
+ if _, err := os.Stat(link); err == nil {
+ return nil
+ } else if !errors.Is(err, os.ErrNotExist) {
+ return err
+ }
+ return os.Link(disk, link)
+}
+
+func (l *LimaHcsDriver) ChangeDisplayPassword(_ context.Context, _ string) error {
+ return nil
+}
+
+func (l *LimaHcsDriver) DisplayConnection(_ context.Context) (string, error) {
+ return "", nil
+}
+
+func (l *LimaHcsDriver) CreateSnapshot(_ context.Context, _ string) error {
+ return errUnimplemented
+}
+
+func (l *LimaHcsDriver) ApplySnapshot(_ context.Context, _ string) error {
+ return errUnimplemented
+}
+
+func (l *LimaHcsDriver) DeleteSnapshot(_ context.Context, _ string) error {
+ return errUnimplemented
+}
+
+func (l *LimaHcsDriver) ListSnapshots(_ context.Context) (string, error) {
+ return "", errUnimplemented
+}
+
+func (l *LimaHcsDriver) ForwardGuestAgent(_ context.Context) bool {
+ // If driver is not providing, use host agent
+ return l.vSockPort == 0 && l.virtioPort == ""
+}
+
+func (l *LimaHcsDriver) AdditionalSetupForSSH(_ context.Context) error {
+ return nil
+}
diff --git a/pkg/driver/hcs/hcs_driver_windows_test.go b/pkg/driver/hcs/hcs_driver_windows_test.go
new file mode 100644
index 00000000..f76f26c5
--- /dev/null
+++ b/pkg/driver/hcs/hcs_driver_windows_test.go
@@ -0,0 +1,244 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package hcs
+
+import (
+ "os"
+ "path/filepath"
+ "reflect"
+ "runtime"
+ "testing"
+
+ "github.com/lima-vm/go-qcow2reader/image/vhdx"
+ "gotest.tools/v3/assert"
+
+ "github.com/lima-vm/lima/v2/pkg/limatype"
+ "github.com/lima-vm/lima/v2/pkg/limatype/filenames"
+)
+
+func newTestInstance(t *testing.T, cfg *limatype.LimaYAML) *limatype.Instance {
+ t.Helper()
+ return &limatype.Instance{
+ Name: "hcs",
+ Dir: t.TempDir(),
+ SSHLocalPort: 60022,
+ Config: cfg,
+ }
+}
+
+func TestValidateConfig(t *testing.T) {
+ arch := limatype.NewArch(runtime.GOARCH)
+
+ tests := []struct {
+ name string
+ cfg *limatype.LimaYAML
+ expectedErr string
+ }{
+ {
+ name: "nil config is rejected",
+ cfg: nil,
+ expectedErr: "configuration is nil",
+ },
+ {
+ name: "minimal config is accepted",
+ cfg: &limatype.LimaYAML{
+ VMType: new(limatype.HCS),
+ Arch: &arch,
+ },
+ },
+ {
+ name: "Linux guest is accepted",
+ cfg: &limatype.LimaYAML{
+ VMType: new(limatype.HCS),
+ Arch: &arch,
+ OS: new(limatype.LINUX),
+ },
+ },
+ {
+ name: "Windows guest is rejected",
+ cfg: &limatype.LimaYAML{
+ VMType: new(limatype.HCS),
+ Arch: &arch,
+ OS: new(limatype.WINDOWS),
+ },
+ expectedErr: "currently Windows guest OS is only supported on QEMU",
+ },
+ {
+ name: "emulation of a non-native arch is rejected",
+ cfg: &limatype.LimaYAML{
+ VMType: new(limatype.HCS),
+ Arch: new(limatype.Arch("unknown")),
+ },
+ expectedErr: "unsupported arch",
+ },
+ {
+ name: "tpm is rejected",
+ cfg: &limatype.LimaYAML{
+ VMType: new(limatype.HCS),
+ Arch: &arch,
+ TPM: new(true),
+ },
+ expectedErr: "field `tpm` is not supported on HCS driver",
+ },
+ {
+ name: "tpm=false is accepted",
+ cfg: &limatype.LimaYAML{
+ VMType: new(limatype.HCS),
+ Arch: &arch,
+ TPM: new(false),
+ },
+ },
+ {
+ name: "unsupported fields are only warned about, not rejected",
+ cfg: &limatype.LimaYAML{
+ VMType: new(limatype.HCS),
+ Arch: &arch,
+ Audio: limatype.Audio{Device: new("none")},
+ Mounts: []limatype.Mount{{Location: "~"}},
+ },
+ },
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ err := validateConfig(t.Context(), tt.cfg)
+ if tt.expectedErr == "" {
+ assert.NilError(t, err)
+ } else {
+ assert.ErrorContains(t, err, tt.expectedErr)
+ }
+ })
+ }
+}
+
+func TestKnownYamlProperties(t *testing.T) {
+ yamlType := reflect.TypeFor[limatype.LimaYAML]()
+ for _, name := range knownYamlProperties {
+ t.Run(name, func(t *testing.T) {
+ _, ok := yamlType.FieldByName(name)
+ assert.Assert(t, ok, "limatype.LimaYAML has no field %#q", name)
+ })
+ }
+}
+
+func TestConfigure(t *testing.T) {
+ arch := limatype.NewArch(runtime.GOARCH)
+
+ t.Run("fills in the HCS defaults", func(t *testing.T) {
+ inst := newTestInstance(t, &limatype.LimaYAML{Arch: &arch})
+
+ l := New()
+ configured, err := l.Configure(t.Context(), inst)
+ assert.NilError(t, err)
+ assert.Assert(t, configured != nil)
+
+ assert.Equal(t, *inst.Config.VMType, limatype.HCS)
+ assert.Equal(t, *inst.Config.MountType, limatype.REVSSHFS)
+ assert.Equal(t, l.Instance, inst)
+ assert.Equal(t, l.SSHLocalPort, inst.SSHLocalPort)
+ })
+
+ t.Run("keeps the values that are already set", func(t *testing.T) {
+ inst := newTestInstance(t, &limatype.LimaYAML{
+ Arch: &arch,
+ VMType: new(limatype.HCS),
+ MountType: new(limatype.NINEP),
+ })
+
+ l := New()
+ _, err := l.Configure(t.Context(), inst)
+ assert.NilError(t, err)
+ assert.Equal(t, *inst.Config.MountType, limatype.NINEP)
+ })
+
+ t.Run("propagates a validation error", func(t *testing.T) {
+ inst := newTestInstance(t, &limatype.LimaYAML{
+ Arch: &arch,
+ TPM: new(true),
+ })
+
+ l := New()
+ configured, err := l.Configure(t.Context(), inst)
+ assert.ErrorContains(t, err, "field `tpm` is not supported")
+ assert.Assert(t, configured == nil)
+ })
+}
+
+func TestValidate(t *testing.T) {
+ arch := limatype.NewArch(runtime.GOARCH)
+
+ l := New()
+ l.Instance = newTestInstance(t, &limatype.LimaYAML{
+ Arch: &arch,
+ VMType: new(limatype.HCS),
+ })
+ assert.NilError(t, l.Validate(t.Context()))
+
+ l.Instance.Config.TPM = new(true)
+ assert.ErrorContains(t, l.Validate(t.Context()), "field `tpm` is not supported")
+}
+
+func TestInfo(t *testing.T) {
+ l := New()
+
+ info := l.Info(t.Context())
+ assert.Equal(t, info.Name, "hcs")
+ assert.Equal(t, info.InstanceDir, "")
+
+ assert.Equal(t, info.Features.NoCloudInit, false)
+ assert.Equal(t, info.Features.CanRunGUI, false)
+
+ assert.Equal(t, info.Features.DynamicSSHAddress, true)
+ assert.Equal(t, info.Features.StaticSSHPort, true)
+ assert.DeepEqual(t, info.Features.SupportedImageFormats, []string{string(vhdx.Type)})
+
+ l.Instance = newTestInstance(t, &limatype.LimaYAML{})
+ assert.Equal(t, l.Info(t.Context()).InstanceDir, l.Instance.Dir)
+}
+
+func TestSSHAddress(t *testing.T) {
+ l := New()
+
+ _, err := l.SSHAddress(t.Context())
+ assert.ErrorContains(t, err, "not known yet")
+
+ l.sshAddress = "192.168.10.5"
+ got, err := l.SSHAddress(t.Context())
+ assert.NilError(t, err)
+ assert.Equal(t, got, "192.168.10.5")
+}
+
+func TestCreateDisk(t *testing.T) {
+ l := New()
+ l.Instance = newTestInstance(t, &limatype.LimaYAML{})
+
+ diskPath := filepath.Join(l.Instance.Dir, filenames.Disk)
+ const content = "not a real disk"
+ assert.NilError(t, os.WriteFile(diskPath, []byte(content), 0o600))
+
+ assert.NilError(t, l.CreateDisk(t.Context()))
+
+ st, err := os.Stat(diskPath + ".vhdx")
+ assert.NilError(t, err)
+ assert.Equal(t, st.Size(), int64(len(content)))
+
+ assert.NilError(t, l.CreateDisk(t.Context()))
+}
+
+func TestGuestAgentIsForwardedByHostAgent(t *testing.T) {
+ l := New()
+ assert.Equal(t, l.ForwardGuestAgent(t.Context()), true)
+
+ conn, addr, err := l.GuestAgentConn(t.Context())
+ assert.NilError(t, err)
+ assert.Assert(t, conn == nil)
+ assert.Equal(t, addr, "")
+}
+
+func TestStopBeforeStart(t *testing.T) {
+ l := New()
+ l.Instance = newTestInstance(t, &limatype.LimaYAML{})
+
+ assert.NilError(t, l.Stop(t.Context()))
+}
diff --git a/pkg/driver/hcs/helper_windows.go b/pkg/driver/hcs/helper_windows.go
new file mode 100644
index 00000000..3b459ece
--- /dev/null
+++ b/pkg/driver/hcs/helper_windows.go
@@ -0,0 +1,325 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package hcs
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "syscall"
+
+ "github.com/Microsoft/hcsshim/hcn"
+ "github.com/lima-vm/go-qcow2reader/image/qcow2"
+ "github.com/lima-vm/go-qcow2reader/image/vhdx"
+ "github.com/sirupsen/logrus"
+
+ "github.com/lima-vm/lima/v2/pkg/imgutil/nativeimgutil"
+ "github.com/lima-vm/lima/v2/pkg/limatype/filenames"
+ "github.com/lima-vm/lima/v2/pkg/limayaml"
+)
+
+const (
+ // hcsTimeoutInfinite corresponds to Win32 INFINITE.
+ hcsTimeoutInfinite uint32 = 0xFFFFFFFF
+
+ // These flags are not exported by the hcn package.
+ netFlagEnableDNS hcn.NetworkFlags = 1
+ netFlagEnableDHCP hcn.NetworkFlags = 2
+ netFlagIsolateSwitch hcn.NetworkFlags = 32
+
+ networkAddressPrefix = "192.168.10.0/24"
+ networkGateway = "192.168.10.1"
+)
+
+type computeSystemProperties struct {
+ State string `json:"State"`
+}
+
+var errNotFound = errors.New("not found")
+
+// getComputeSystemState obtains compute system properties.
+func getComputeSystemState(ctx context.Context, id string) (string, error) {
+ system, err := hcsOpenComputeSystem(ctx, id, syscall.GENERIC_ALL)
+ if err != nil {
+ return "", errNotFound
+ }
+ defer hcsCloseComputeSystem(system)
+
+ op, err := hcsCreateOperation()
+ if err != nil {
+ return "", fmt.Errorf("failed to create an HCS operation: %w", err)
+ }
+ defer hcsCloseOperation(op)
+
+ if err := hcsGetComputeSystemProperties(ctx, system, op, "{}"); err != nil {
+ return "", fmt.Errorf("failed to get compute system properties: %w", err)
+ }
+
+ result, err := hcsWait(op, "hcsGetComputeSystemProperties", nil)
+ if err != nil {
+ return "", fmt.Errorf("failed to get compute system properties: %w (result=%s)", err, result)
+ }
+
+ var properties computeSystemProperties
+ if err := json.Unmarshal([]byte(result), &properties); err != nil {
+ return "", fmt.Errorf("failed to decode compute system properties: %w", err)
+ }
+
+ return properties.State, nil
+}
+
+// buildVMConfig returns the HCS compute system configuration document.
+func buildVMConfig(
+ diskPath, isoPath, endpointID, macAddress, serialPipe string,
+ cpus int, memoryMiB int64,
+) (string, error) {
+ config := map[string]any{
+ "SchemaVersion": map[string]any{
+ "Major": 2,
+ "Minor": 1,
+ },
+ "Owner": "Lima",
+ "ShouldTerminateOnLastHandleClosed": true,
+ "VirtualMachine": map[string]any{
+ "Chipset": map[string]any{
+ "Uefi": map[string]any{
+ "Console": "ComPort1",
+ "BootThis": map[string]any{
+ "DevicePath": "Primary disk",
+ "DiskNumber": 0,
+ "DeviceType": "ScsiDrive",
+ },
+ },
+ },
+ "ComputeTopology": map[string]any{
+ "Memory": map[string]any{
+ "Backing": "Virtual",
+ "SizeInMB": memoryMiB,
+ },
+ "Processor": map[string]any{
+ "Count": cpus,
+ },
+ },
+ "Devices": map[string]any{
+ "Scsi": map[string]any{
+ "Primary disk": map[string]any{
+ "Attachments": map[string]any{
+ "0": map[string]any{
+ "Type": "VirtualDisk",
+ "Path": diskPath,
+ },
+ },
+ },
+ "cidata": map[string]any{
+ "Attachments": map[string]any{
+ "0": map[string]any{
+ "Type": "Iso",
+ "Path": isoPath,
+ },
+ },
+ },
+ },
+ "ComPorts": map[string]any{
+ "0": map[string]any{
+ "NamedPipe": serialPipe,
+ },
+ },
+ "NetworkAdapters": map[string]any{
+ endpointID: map[string]any{
+ "EndpointId": endpointID,
+ "MacAddress": macAddress,
+ },
+ },
+ },
+ },
+ }
+
+ configBytes, err := json.Marshal(config)
+ if err != nil {
+ return "", err
+ }
+
+ return string(configBytes), nil
+}
+
+func ensureNetwork(name string) (*hcn.HostComputeNetwork, error) {
+ network, err := hcn.GetNetworkByName(name)
+ if err == nil {
+ return network, nil
+ }
+
+ if !hcn.IsNotFoundError(err) {
+ return nil, fmt.Errorf("failed to look up the HCN network %#q: %w", name, err)
+ }
+
+ network = &hcn.HostComputeNetwork{
+ Name: name,
+ SchemaVersion: hcn.V2SchemaVersion(),
+ Type: hcn.ICS,
+ Flags: netFlagEnableDNS | netFlagEnableDHCP | hcn.EnableNonPersistent | netFlagIsolateSwitch,
+ Ipams: []hcn.Ipam{{
+ Type: "Static",
+ Subnets: []hcn.Subnet{{
+ IpAddressPrefix: networkAddressPrefix,
+ Routes: []hcn.Route{{
+ NextHop: networkGateway,
+ DestinationPrefix: "0.0.0.0/0",
+ }},
+ }},
+ }},
+ }
+
+ return network.Create()
+}
+
+func ensureEndpoint(network *hcn.HostComputeNetwork, name, instDir string) (*hcn.HostComputeEndpoint, error) {
+ endpoint, err := hcn.GetEndpointByName(name)
+ if err == nil {
+ if endpoint.HostComputeNetwork != network.Id {
+ return nil, fmt.Errorf("endpoint %#q already exists on a different network", name)
+ }
+ return endpoint, nil
+ }
+ if !hcn.IsNotFoundError(err) {
+ return nil, err
+ }
+
+ endpoint = &hcn.HostComputeEndpoint{
+ Name: name,
+ SchemaVersion: hcn.V2SchemaVersion(),
+ HostComputeNetwork: network.Id,
+ MacAddress: hnsMACAddress(instDir),
+ }
+
+ return endpoint.Create()
+}
+
+func networkName(instName string) string {
+ return "lima-" + instName + "-net"
+}
+
+func endpointName(instName string) string {
+ return "lima-" + instName + "-ep"
+}
+
+// hnsMACAddress converts MAC address notation saved in limayaml struct to the one HNS expects.
+func hnsMACAddress(instDir string) string {
+ return strings.ToUpper(strings.ReplaceAll(limayaml.MACAddress(instDir), ":", "-"))
+}
+
+// createComputeSystem creates (but does not start) an HCS compute system.
+func createComputeSystem(_ context.Context, id, config string) (hcsSystem, error) {
+ system, err := hcsCreateComputeSystem(id, config)
+ if err != nil {
+ return 0, fmt.Errorf("create compute system error: %w", err)
+ }
+
+ return system, nil
+}
+
+func startComputeSystem(_ context.Context, system hcsSystem) error {
+ return hcsStartComputeSystem(system)
+}
+
+func terminateComputeSystem(_ context.Context, system hcsSystem) error {
+ return hcsTerminateComputeSystem(system)
+}
+
+// serveSerial accepts connections from the guest port.
+// It appends everything it reads to logPath and return when ln is closed.
+func serveSerial(ln net.Listener, logPath string) {
+ f, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o644)
+ if err != nil {
+ logrus.WithError(err).Errorf("failed to open the serial log %#q", logPath)
+ return
+ }
+ defer f.Close()
+
+ for {
+ conn, err := ln.Accept()
+ if err != nil {
+ if errors.Is(err, net.ErrClosed) {
+ return
+ }
+ logrus.WithError(err).Warn("failed to accept a serial connection")
+ return
+ }
+ logrus.Debugf("serial: the guest connected to %s", ln.Addr())
+ if _, err := io.Copy(f, conn); err != nil {
+ logrus.WithError(err).Debug("serial: copy finished with an error")
+ }
+ conn.Close()
+ }
+}
+
+// mayConvertQcow2ToVHDX checks `disk` and converts it to .vhdx if it is qcow2.
+func mayConvertQcow2ToVHDX(ctx context.Context, instDir string) error {
+ disk := filepath.Join(instDir, filenames.Disk)
+ _, err := os.Stat(disk)
+ if err == nil {
+ return nil
+ }
+ if !os.IsNotExist(err) {
+ return err
+ }
+
+ imagePath := filepath.Join(instDir, filenames.Image)
+ format, err := nativeimgutil.DetectFormat(imagePath)
+ if err != nil {
+ return err
+ }
+
+ switch format {
+ case string(vhdx.Type):
+ logrus.Debug("disk is already .vhdx")
+ if err = os.Rename(imagePath, disk); err != nil {
+ return fmt.Errorf("failed to rename %#q to %#q: %w", imagePath, disk, err)
+ }
+ if err := os.Chmod(disk, 0o644); err != nil {
+ return fmt.Errorf("failed to chmod %#q: %w", disk, err)
+ }
+ return nil
+ case qcow2.Type, "raw":
+ if err := execQemuImgConvert(ctx, imagePath, disk); err != nil {
+ return fmt.Errorf("failed to convert to vhdx: %w", err)
+ }
+ if err := execFsutil(ctx, disk); err != nil {
+ return fmt.Errorf("failed to set disk flag: %w", err)
+ }
+ return nil
+ default:
+ return fmt.Errorf("disk type %s is not supported", format)
+ }
+}
+
+func execQemuImgConvert(ctx context.Context, source, dist string) error {
+ var stdout, stderr bytes.Buffer
+ cmd := exec.CommandContext(ctx, "qemu-img", "convert", "-O", "vhdx", source, dist)
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("failed to run %v: stdout=%#q, stderr=%#q: %w",
+ cmd.Args, stdout.String(), stderr.String(), err)
+ }
+ return nil
+}
+
+func execFsutil(ctx context.Context, disk string) error {
+ var stdout, stderr bytes.Buffer
+ cmd := exec.CommandContext(ctx, "fsutil", "sparse", "setflag", disk, "0")
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ if err := cmd.Run(); err != nil {
+ return fmt.Errorf("failed to run %v: stdout=%#q, stderr=%#q: %w", cmd.Args, stdout.String(), stderr.String(), err)
+ }
+ return nil
+}
diff --git a/pkg/driver/hcs/helper_windows_test.go b/pkg/driver/hcs/helper_windows_test.go
new file mode 100644
index 00000000..3b402f2e
--- /dev/null
+++ b/pkg/driver/hcs/helper_windows_test.go
@@ -0,0 +1,173 @@
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package hcs
+
+import (
+ "encoding/json"
+ "net"
+ "os"
+ "path/filepath"
+ "regexp"
+ "sync"
+ "testing"
+
+ "gotest.tools/v3/assert"
+)
+
+const (
+ testDiskPath = `C:\Users\lima\.lima\hcs\disk.vhdx`
+ testISOPath = `C:\Users\lima\.lima\hcs\cidata.iso`
+ testEndpointID = "11111111-2222-3333-4444-555555555555"
+ testMACAddress = "52-55-55-AA-BB-CC"
+ testSerialPipe = `\\.\pipe\lima-hcs-com1`
+ testCPUs = 2
+ testMemoryMiB = 2048
+)
+
+func TestNetworkAndEndpointName(t *testing.T) {
+ assert.Equal(t, networkName("test"), "lima-test-net")
+ assert.Equal(t, endpointName("test"), "lima-test-ep")
+}
+
+func TestHNSMACAddress(t *testing.T) {
+ dir := t.TempDir()
+ got := hnsMACAddress(dir)
+
+ re := regexp.MustCompile(`^52-55-55(-[0-9A-F]{2}){3}$`)
+ assert.Assert(t, re.MatchString(got), "unexpected MAC address %#q", got)
+
+ assert.Equal(t, got, hnsMACAddress(dir), "MAC Address should be identical if input is the same.")
+
+ assert.Assert(t, got != hnsMACAddress(t.TempDir()))
+}
+
+func TestBuildVMConfig(t *testing.T) {
+ got, err := buildVMConfig(testDiskPath, testISOPath, testEndpointID, testMACAddress, testSerialPipe, testCPUs, testMemoryMiB)
+ assert.NilError(t, err)
+
+ const want = `{
+ "SchemaVersion": {"Major": 2, "Minor": 1},
+ "Owner": "Lima",
+ "ShouldTerminateOnLastHandleClosed": true,
+ "VirtualMachine": {
+ "Chipset": {
+ "Uefi": {
+ "Console": "ComPort1",
+ "BootThis": {"DevicePath": "Primary disk", "DiskNumber": 0, "DeviceType": "ScsiDrive"}
+ }
+ },
+ "ComputeTopology": {
+ "Memory": {"Backing": "Virtual", "SizeInMB": 2048},
+ "Processor": {"Count": 2}
+ },
+ "Devices": {
+ "Scsi": {
+ "Primary disk": {
+ "Attachments": {"0": {"Type": "VirtualDisk", "Path": "C:\\Users\\lima\\.lima\\hcs\\disk.vhdx"}}
+ },
+ "cidata": {
+ "Attachments": {"0": {"Type": "Iso", "Path": "C:\\Users\\lima\\.lima\\hcs\\cidata.iso"}}
+ }
+ },
+ "ComPorts": {"0": {"NamedPipe": "\\\\.\\pipe\\lima-hcs-com1"}},
+ "NetworkAdapters": {
+ "11111111-2222-3333-4444-555555555555": {
+ "EndpointId": "11111111-2222-3333-4444-555555555555",
+ "MacAddress": "52-55-55-AA-BB-CC"
+ }
+ }
+ }
+ }
+}`
+
+ var gotDoc, wantDoc any
+ assert.NilError(t, json.Unmarshal([]byte(got), &gotDoc))
+ assert.NilError(t, json.Unmarshal([]byte(want), &wantDoc))
+ assert.DeepEqual(t, gotDoc, wantDoc)
+}
+
+func TestBuildVMConfigSerialPortNumbering(t *testing.T) {
+ got, err := buildVMConfig(testDiskPath, testISOPath, testEndpointID, testMACAddress, testSerialPipe, testCPUs, testMemoryMiB)
+ assert.NilError(t, err)
+
+ var doc struct {
+ VirtualMachine struct {
+ Chipset struct {
+ Uefi struct {
+ Console string
+ }
+ }
+ Devices struct {
+ ComPorts map[string]struct {
+ NamedPipe string
+ }
+ }
+ }
+ }
+ assert.NilError(t, json.Unmarshal([]byte(got), &doc))
+
+ assert.Equal(t, doc.VirtualMachine.Chipset.Uefi.Console, "ComPort1")
+ assert.Equal(t, doc.VirtualMachine.Devices.ComPorts["0"].NamedPipe, testSerialPipe)
+}
+
+func TestServeSerial(t *testing.T) {
+ logPath := filepath.Join(t.TempDir(), "serial.log")
+ ln := newFakeListener()
+
+ done := make(chan struct{})
+ go func() {
+ serveSerial(ln, logPath)
+ close(done)
+ }()
+
+ lines := []string{"[ 0.000000] Linux version 6.11\n", "lima login: \n"}
+ for _, line := range lines {
+ guest, host := net.Pipe()
+ ln.conns <- host
+ _, err := guest.Write([]byte(line))
+ assert.NilError(t, err)
+ assert.NilError(t, guest.Close())
+ }
+
+ assert.NilError(t, ln.Close())
+ <-done
+
+ b, err := os.ReadFile(logPath)
+ assert.NilError(t, err)
+ assert.Equal(t, string(b), lines[0]+lines[1])
+}
+
+type fakeListener struct {
+ conns chan net.Conn
+ done chan struct{}
+ once sync.Once
+}
+
+func newFakeListener() *fakeListener {
+ return &fakeListener{
+ conns: make(chan net.Conn),
+ done: make(chan struct{}),
+ }
+}
+
+func (ln *fakeListener) Accept() (net.Conn, error) {
+ select {
+ case conn := <-ln.conns:
+ return conn, nil
+ case <-ln.done:
+ return nil, net.ErrClosed
+ }
+}
+
+func (ln *fakeListener) Close() error {
+ ln.once.Do(func() { close(ln.done) })
+ return nil
+}
+
+func (ln *fakeListener) Addr() net.Addr { return fakeAddr{} }
+
+type fakeAddr struct{}
+
+func (fakeAddr) Network() string { return "fake" }
+func (fakeAddr) String() string { return "fake" }
diff --git a/pkg/driver/hcs/register.go b/pkg/driver/hcs/register.go
new file mode 100644
index 00000000..ea9d104b
--- /dev/null
+++ b/pkg/driver/hcs/register.go
@@ -0,0 +1,16 @@
+//go:build windows && !external_hcs
+
+// SPDX-FileCopyrightText: Copyright The Lima Authors
+// SPDX-License-Identifier: Apache-2.0
+
+package hcs
+
+import (
+ "context"
+
+ "github.com/lima-vm/lima/v2/pkg/registry"
+)
+
+func init() {
+ registry.Register(context.Background(), New())
+}
diff --git a/pkg/hostagent/hostagent.go b/pkg/hostagent/hostagent.go
index 67db7775..2c59e0c8 100644
--- a/pkg/hostagent/hostagent.go
+++ b/pkg/hostagent/hostagent.go
@@ -155,7 +155,7 @@ func New(ctx context.Context, instName string, stdout io.Writer, signalCh chan o
}
var udpDNSLocalPort, tcpDNSLocalPort int
- if *inst.Config.HostResolver.Enabled {
+ if limayaml.HostResolverEnabled(inst.Config) {
udpDNSLocalPort, err = freeport.UDP()
if err != nil {
return nil, err
@@ -408,7 +408,7 @@ func (a *HostAgent) Run(ctx context.Context) error {
}()
adjustNofileRlimit()
- if limayaml.FirstUsernetIndex(a.instConfig) == -1 && *a.instConfig.HostResolver.Enabled {
+ if limayaml.FirstUsernetIndex(a.instConfig) == -1 && limayaml.HostResolverEnabled(a.instConfig) {
hosts := a.instConfig.HostResolver.Hosts
if hosts == nil {
hosts = make(map[string]string)
diff --git a/pkg/instance/start.go b/pkg/instance/start.go
index b35c6b00..41caa5be 100644
--- a/pkg/instance/start.go
+++ b/pkg/instance/start.go
@@ -17,6 +17,7 @@ import (
"github.com/docker/go-units"
"github.com/lima-vm/go-qcow2reader"
+ "github.com/lima-vm/go-qcow2reader/image/vhdx"
"github.com/sirupsen/logrus"
"github.com/lima-vm/lima/v2/pkg/autostart"
@@ -537,6 +538,11 @@ func prepareDisk(ctx context.Context, inst *limatype.Instance) error {
diskSize := img.Size()
+ if img.Type() == vhdx.Type {
+ logrus.Warn("Skip resizing the instance because go-qcow2reader cannot read vhdx type image")
+ return nil
+ }
+
if inst.Disk == diskSize {
return nil
}
diff --git a/pkg/limatype/lima_yaml.go b/pkg/limatype/lima_yaml.go
index fa6b3831..ca1fae32 100644
--- a/pkg/limatype/lima_yaml.go
+++ b/pkg/limatype/lima_yaml.go
@@ -99,13 +99,14 @@ const (
QEMU VMType = "qemu"
VZ VMType = "vz"
WSL2 VMType = "wsl2"
+ HCS VMType = "hcs"
)
var (
OSTypes = []OS{LINUX, DARWIN, FREEBSD, WINDOWS}
ArchTypes = []Arch{X8664, AARCH64, ARMV7L, PPC64LE, RISCV64, S390X}
MountTypes = []MountType{REVSSHFS, NINEP, VIRTIOFS, WSLMount}
- VMTypes = []VMType{QEMU, VZ, WSL2}
+ VMTypes = []VMType{QEMU, VZ, WSL2, HCS}
)
type User struct {
diff --git a/pkg/limayaml/defaults.go b/pkg/limayaml/defaults.go
index e2134809..99e22fc5 100644
--- a/pkg/limayaml/defaults.go
+++ b/pkg/limayaml/defaults.go
@@ -78,6 +78,14 @@ func FirstUsernetIndex(l *limatype.LimaYAML) int {
return slices.IndexFunc(l.Networks, func(network limatype.Network) bool { return networks.IsUsernet(network.Lima) })
}
+// HostResolverEnabled returns whether the host resolver is enabled.
+// The default value depends on the driver and is filled in the driver-specific FillDefault().
+// A nil value is treated as true to keep HostResolver.Enabled true for drivers which do not fill the field.
+
+func HostResolverEnabled(l *limatype.LimaYAML) bool {
+ return l.HostResolver.Enabled == nil || *l.HostResolver.Enabled
+}
+
func MACAddress(uniqueID string) string {
sha := sha256.Sum256([]byte(osutil.MachineID() + uniqueID))
// "5" is the magic number in the Lima ecosystem.
@@ -616,9 +624,7 @@ func FillDefault(ctx context.Context, y, d, o *limatype.LimaYAML, filePath strin
if o.HostResolver.Enabled != nil {
y.HostResolver.Enabled = o.HostResolver.Enabled
}
- if y.HostResolver.Enabled == nil {
- y.HostResolver.Enabled = new(true)
- }
+ // y.HostResolver.Enabled default value depends on the driver; filled in driver-specific FillDefault()
if y.HostResolver.IPv6 == nil {
y.HostResolver.IPv6 = d.HostResolver.IPv6
diff --git a/pkg/limayaml/defaults_test.go b/pkg/limayaml/defaults_test.go
index 6b76ac26..fafd2139 100644
--- a/pkg/limayaml/defaults_test.go
+++ b/pkg/limayaml/defaults_test.go
@@ -140,7 +140,7 @@ func TestFillDefault(t *testing.T) {
Display: new("none"),
},
HostResolver: limatype.HostResolver{
- Enabled: new(true),
+ Enabled: nil,
IPv6: new(false),
},
PropagateProxyEnv: new(true),
@@ -539,7 +539,8 @@ func TestFillDefault(t *testing.T) {
expect.HostResolver.Hosts["default"] = dExpected.HostResolver.Hosts["default"]
- // dExpected.DNS will be ignored, and not appended to y.DNS
+ // The user-provided default is applied when the config doesn't specify it.
+ expect.HostResolver.Enabled = dExpected.HostResolver.Enabled
// "TWO" does not exist in filledDefaults.Env, so is set from dExpected.Env
expect.Env["TWO"] = dExpected.Env["TWO"]
diff --git a/pkg/limayaml/validate.go b/pkg/limayaml/validate.go
index 886c86a8..97cdaa33 100644
--- a/pkg/limayaml/validate.go
+++ b/pkg/limayaml/validate.go
@@ -401,8 +401,14 @@ func Validate(y *limatype.LimaYAML, warn bool) error {
}
}
- if y.HostResolver.Enabled != nil && *y.HostResolver.Enabled && len(y.DNS) > 0 {
- errs = errors.Join(errs, errors.New("field `dns` must be empty when field `HostResolver.Enabled` is true"))
+ if y.VMType != nil && *y.VMType == limatype.HCS {
+ if y.HostResolver.Enabled != nil && *y.HostResolver.Enabled && len(y.DNS) > 0 {
+ errs = errors.Join(errs, errors.New("field `dns` must be empty when field `HostResolver.Enabled` is true"))
+ }
+ } else {
+ if HostResolverEnabled(y) && len(y.DNS) > 0 {
+ errs = errors.Join(errs, errors.New("field `dns` must be empty when field `HostResolver.Enabled` is true"))
+ }
}
err := validateNetwork(y)
diff --git a/templates/README.md b/templates/README.md
index a5c92130..fe64ab1d 100644
--- a/templates/README.md
+++ b/templates/README.md
@@ -122,6 +122,7 @@ See .
- [`experimental/vnc`](./experimental/vnc.yaml): use vnc display and xorg server
- [`experimental/alsa`](./experimental/alsa.yaml): use alsa and default audio device
+- [`experimental/hcs`](./experimental/hcs.yaml): use [HCS driver](https://lima-vm.io/docs/config/vmtype/hcs/) on Windows hosts
### Lost+found
diff --git a/templates/default.yaml b/templates/default.yaml
index 56833a90..0dad1996 100644
--- a/templates/default.yaml
+++ b/templates/default.yaml
@@ -614,7 +614,7 @@ propagateProxyEnv: null
# can only work when using a vmnet network interface and the host has working
# IPv6 configured as well.
hostResolver:
- # 🟢 Builtin default: true
+ # 🟢 Builtin default: true (except for hcs driver; it should be always false with hcs driver)
enabled: null
# 🟢 Builtin default: false
ipv6: null
diff --git a/templates/experimental/hcs.yaml b/templates/experimental/hcs.yaml
new file mode 100644
index 00000000..49f54085
--- /dev/null
+++ b/templates/experimental/hcs.yaml
@@ -0,0 +1,13 @@
+# This template only works on Windows versions with Hyper-V platform enabled.
+vmType: hcs
+
+images:
+# The following image is qcow2; the hcs driver converts it into VHDX on the first start using `qemu-img`.
+- location: "https://cloud-images.ubuntu.com/releases/resolute/release-20260720/ubuntu-26.04-server-cloudimg-amd64.img"
+ arch: "x86_64"
+ digest: "sha256:117816726abbdefc5ef3e38902e81a76f1c76c3610e709999d0885f9d5d9b477"
+
+plain: true
+
+dns:
+- 8.8.8.8
diff --git a/website/content/en/docs/config/vmtype/hcs.md b/website/content/en/docs/config/vmtype/hcs.md
new file mode 100644
index 00000000..a998f917
--- /dev/null
+++ b/website/content/en/docs/config/vmtype/hcs.md
@@ -0,0 +1,66 @@
+---
+title: HCS
+weight: 5
+---
+
+> **Warning**
+> "hcs" mode is experimental
+
+| âš¡ Requirement | Lima >= 2.3 + (Windows >= Windows 11) |
+| ----------------- | -------------------------------------------------------- |
+
+"hcs" option makes use of native virtualization support provided by Windows' Host Compute System (HCS) API ([more info](https://learn.microsoft.com/en-us/virtualization/api/hcs/overview)).
+
+An example configuration:
+{{< tabpane text=true >}}
+{{% tab header="CLI" %}}
+```bash
+limactl start --vm-type=hcs --plain --dns 8.8.8.8
+```
+{{% /tab %}}
+{{% tab header="YAML" %}}
+```yaml
+# Example to run Ubuntu using vmType: hcs
+vmType: hcs
+images:
+- location: "https://cloud-images.ubuntu.com/releases/resolute/release-20260720/ubuntu-26.04-server-cloudimg-amd64.img"
+ arch: "x86_64"
+ digest: "sha256:117816726abbdefc5ef3e38902e81a76f1c76c3610e709999d0885f9d5d9b477"
+plain: true
+dns:
+- 8.8.8.8
+```
+{{% /tab %}}
+{{< /tabpane >}}
+
+### Caveats
+- We tested it works only on Windows 11 (x86_64).
+- Only plain mode is supported (no file mount, no dynamic port-forwarding).
+- `qemu-img` is required for the image conversion; see [Disk images](#disk-images).
+- The user must be a member of the Hyper-V Administrators group; see [Setup for HCS driver](#setup-for-hcs-driver)
+- `limactl` should be run from a shell opened with Administrator privileges.
+- Currently, only one instance can run using hcs at a time, beucause the HCN network subnet is fixed and cannot be shared by multiple instances.
+- A DNS server has to be specified explicitly.
+
+
+### Disk images
+- The `hcs` driver can only boot from a `.vhdx` disk image.
+- If the image is not a `.vhdx` (for example `.qcow2`), Lima automatically converts it to `.vhdx` by running `qemu-img convert -O vhdx`.
+- Therefore, if you would like to download the image or to pass a disk image which is not `.vhdx`, `qemu-img` is required.
+
+### Setup for HCS driver
+1. **Enable virtualization on Windows:**
+ Follow the instrunctions in [Microsoft guide](https://support.microsoft.com/en-us/windows/experience/enable-virtualization-on-windows) to enable virtualization on your Windows host.
+
+2. **Add yourself to `Hyper-V Administrators` group:**
+ Run the following command on powershell or cmd.exe with Run as administrator:
+ ```bash
+ net localgroup "Hyper-V Administrators" /add
+ ```
+
+ Then, restart the terminal or reboot the computer for the changes to take effect.
+
+ To verify that your user account has been added to `Hyper-V Administrators` group, run:
+ ```bash
+ net localgroup "Hyper-V Administrators"
+ ```
diff --git a/website/content/en/docs/dev/internals.md b/website/content/en/docs/dev/internals.md
index 1c1b6bce..fc293f44 100644
--- a/website/content/en/docs/dev/internals.md
+++ b/website/content/en/docs/dev/internals.md
@@ -47,6 +47,7 @@ disk:
- `image`: the downloaded VM image; renamed to `disk` or `iso` during setup
- `image.ipsw`: hardlink to `image`, created for running `VZMacOSInstaller` that requires the image file to have the `.ipsw` suffix
- `disk`: the VM disk (can be a symlink to legacy `diffdisk`)
+- `disk.vhdx`: hardlink to `disk` to pass a `.vhdx` path to HCS driver
- `iso`: optional CDROM image for ISO-based installations (can be a symlink to legacy `basedisk`)
- `basedisk`: legacy name for the downloaded image (pre-v2.1 instances; may remain as a qcow2 backing file)
- `diffdisk`: legacy name for `disk` (pre-v2.1 instances)
@@ -72,7 +73,7 @@ VZ:
- `vz-efi`: EFIVariable store file for a VM
Serial:
-- `serial.log`: default serial log (QEMU only), for debugging
+- `serial.log`: default serial log (QEMU and HCS), for debugging
- `serial.sock`: default serial socket (QEMU only), for debugging (Usage: `socat -,echo=0,icanon=0 unix-connect:serial.sock`)
- `serialp.log`: PCI serial log (QEMU (ARM) only), for debugging
- `serialp.sock`: PCI serial socket (QEMU (ARM) only), for debugging (Usage: `socat -,echo=0,icanon=0 unix-connect:serialp.sock`)
diff --git a/website/content/en/docs/releases/experimental.md b/website/content/en/docs/releases/experimental.md
index 0908b460..b0773529 100644
--- a/website/content/en/docs/releases/experimental.md
+++ b/website/content/en/docs/releases/experimental.md
@@ -16,6 +16,7 @@ The following features are experimental and subject to change:
- `user.passwordlessSudo` (See [Sudo](../config/sudo))
- `External drivers`: building and using drivers as separate executables (see [Virtual Machine Drivers](../dev/drivers))
- [`vmType: krunkit`](../config/vmtype/krunkit.md)
+- [`vmType:hcs`]((../config/vmtype/hcs.md))
- [`github` URL scheme](../templates/github.md): referencing templates on GitHub with `github:` URLs
- [macOS guests](../usage/guests/macos.md)
- [FreeBSD guests](../usage/guests/freebsd.md)