mirror of
https://github.com/lima-vm/lima.git
synced 2026-10-28 11:44:35 +08:00
Merge pull request #5342 from jandubois/ci-plain-windows-jobs
CI: factor Windows setup into composite actions; add plain-Windows jobs
This commit is contained in:
13 files changed
+568
-66
No files matched your search
@@ -0,0 +1,27 @@
|
||||
name: 'windows msys2 prep'
|
||||
description: >
|
||||
Adds C:\msys64\usr\bin to PATH for the rest of the job, unit tests and
|
||||
make included, and installs the MSYS2 packages the Windows integration
|
||||
jobs need.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Prepend MSYS2 to PATH and install test dependencies
|
||||
shell: pwsh
|
||||
# Writes only the static literal C:\msys64\usr\bin; no PR-controlled data
|
||||
# reaches the env file.
|
||||
run: | # zizmor: ignore[github-env]
|
||||
$ErrorActionPreference = 'Stop'
|
||||
Add-Content -Path $env:GITHUB_PATH -Value 'C:\msys64\usr\bin'
|
||||
# rsync: without it the copy test skips its rsync backend, so the Windows
|
||||
# path conversion that backend performs goes unexercised.
|
||||
# openssh: keeps a Cygwin ssh ahead of %SystemRoot%\System32\OpenSSH, so
|
||||
# these jobs cover the Cygwin toolchain and the plain jobs the native one.
|
||||
& 'C:\msys64\usr\bin\pacman.exe' -Sy --noconfirm diffutils jq openssh rsync socat w3m
|
||||
if ($LASTEXITCODE -ne 0) { throw "pacman failed: $LASTEXITCODE" }
|
||||
# Lima's ssh selection skips an ssh.exe without scp.exe and ssh-keygen.exe
|
||||
# beside it, and reverse-sshfs takes the sftp-server from the same install.
|
||||
foreach ($exe in 'bin\ssh.exe', 'bin\scp.exe', 'bin\ssh-keygen.exe', 'lib\ssh\sftp-server.exe') {
|
||||
$exePath = Join-Path 'C:\msys64\usr' $exe
|
||||
if (-not (Test-Path $exePath)) { throw "missing: $exePath" }
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
name: 'windows plain build'
|
||||
description: >
|
||||
Builds limactl.exe and the Linux/amd64 guest agent with `go build`, without
|
||||
make. limactl needs the guest agent to start an instance, and
|
||||
`go build ./cmd/limactl` does not produce it.
|
||||
|
||||
Without make's -X ldflag the version stays "<unknown>". Lima treats an
|
||||
unparsable version as newer than any other, so default.yaml's
|
||||
minimumLimaVersion accepts these builds.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Build limactl and Linux/amd64 guest agent
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
go build -o _output\bin\limactl.exe .\cmd\limactl
|
||||
if ($LASTEXITCODE -ne 0) { throw "limactl build failed: $LASTEXITCODE" }
|
||||
New-Item -ItemType Directory -Force -Path _output\share\lima | Out-Null
|
||||
$env:CGO_ENABLED = '0'
|
||||
$env:GOOS = 'linux'
|
||||
$env:GOARCH = 'amd64'
|
||||
go build -o _output\share\lima\lima-guestagent.Linux-x86_64 .\cmd\lima-guestagent
|
||||
if ($LASTEXITCODE -ne 0) { throw "lima-guestagent build failed: $LASTEXITCODE" }
|
||||
@@ -0,0 +1,172 @@
|
||||
name: 'windows plain host'
|
||||
description: >
|
||||
Uninstalls MSYS2 and Git for Windows, then verifies they are gone and the
|
||||
native OpenSSH client is present, so a runner-image change in either
|
||||
direction fails the job.
|
||||
|
||||
Destructive and irreversible: it also rewrites the Machine and User PATH in
|
||||
the registry, so run it only on an ephemeral runner.
|
||||
|
||||
Run AFTER steps that need git CLI (checkout, windows_plain_templates) and
|
||||
BEFORE the build / smoke test steps.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Uninstall Git for Windows
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$uninstaller = 'C:\Program Files\Git\unins000.exe'
|
||||
if (Test-Path $uninstaller) {
|
||||
Write-Host "Running Git for Windows Inno Setup uninstaller..."
|
||||
& $uninstaller /VERYSILENT /NORESTART /SUPPRESSMSGBOXES
|
||||
# A copy of unins000 in %TEMP% (_unins.tmp, or _iu*.tmp in older Inno
|
||||
# Setup) does the uninstall. unins000 waits for it, but the copy
|
||||
# outlives unins000 to delete unins000.exe and the Git directory.
|
||||
$deadline = (Get-Date).AddSeconds(120)
|
||||
while ((Get-Date) -lt $deadline -and
|
||||
(Get-Process -Name 'unins000', '_unins*', '_iu*' -ErrorAction SilentlyContinue)) {
|
||||
Start-Sleep -Seconds 2
|
||||
}
|
||||
} else {
|
||||
Write-Host "No Git for Windows uninstaller at $uninstaller; skipping."
|
||||
}
|
||||
foreach ($d in 'C:\Program Files\Git', 'C:\Program Files (x86)\Git') {
|
||||
if (Test-Path $d) {
|
||||
Write-Host "Removing leftover $d"
|
||||
Remove-Item $d -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
# pwsh does not wait for the GUI uninstaller or record its exit status,
|
||||
# so the verify step below is the gate.
|
||||
exit 0
|
||||
|
||||
- name: Uninstall MSYS2 and standalone mingw
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# MSYS2 and the mingw dirs on this image are extracted trees with no
|
||||
# uninstaller.
|
||||
$procs = Get-Process | Where-Object {
|
||||
try { $_.Modules.ModuleName -contains 'msys-2.0.dll' } catch { $false }
|
||||
}
|
||||
if ($procs) {
|
||||
Write-Host "Stopping $($procs.Count) process(es) still mapping msys-2.0.dll"
|
||||
$procs | Stop-Process -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 1
|
||||
}
|
||||
foreach ($d in 'C:\msys64', 'C:\tools\msys64', 'C:\msys32',
|
||||
'C:\mingw64', 'C:\mingw32') {
|
||||
if (Test-Path $d) {
|
||||
Write-Host "Removing $d"
|
||||
Remove-Item $d -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
- name: Scrub forbidden entries from Machine + User PATH
|
||||
shell: pwsh
|
||||
# Publishes the runner's own Machine + User PATH minus a static deny-list;
|
||||
# no PR-controlled data reaches the env file.
|
||||
run: | # zizmor: ignore[github-env]
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# The uninstall leaves stale entries in the Machine and User PATH.
|
||||
# Registry changes do not reach the running runner, so also publish the
|
||||
# result via $GITHUB_ENV. $GITHUB_PATH entries from earlier steps are
|
||||
# still prepended on top of it, out of the scrub's reach.
|
||||
$forbidden = '(?i)(msys|mingw|cygwin|\\Git\\(cmd|bin|usr))'
|
||||
foreach ($scope in @('Machine','User')) {
|
||||
$orig = [Environment]::GetEnvironmentVariable('Path', $scope)
|
||||
if (-not $orig) { continue }
|
||||
$cleaned = ($orig -split ';' | Where-Object { $_ -and ($_ -notmatch $forbidden) }) -join ';'
|
||||
if ($cleaned -ne $orig) {
|
||||
Write-Host "Scrubbing $scope PATH"
|
||||
[Environment]::SetEnvironmentVariable('Path', $cleaned, $scope)
|
||||
}
|
||||
}
|
||||
$machine = [Environment]::GetEnvironmentVariable('Path','Machine')
|
||||
$user = [Environment]::GetEnvironmentVariable('Path','User')
|
||||
$combined = (@($machine, $user) | Where-Object { $_ }) -join ';'
|
||||
Add-Content -Path $env:GITHUB_ENV -Value "PATH=$combined"
|
||||
|
||||
- name: Verify host is vanilla Windows (no MSYS2 / Git for Windows / Cygwin)
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# Check the inherited PATH, which is what later steps see. Rebuilding it
|
||||
# from the registry would only repeat the scrub step's own read.
|
||||
$fail = @()
|
||||
|
||||
# --- Layer 1: filesystem ---
|
||||
$fsForbidden = @(
|
||||
'C:\msys64', 'C:\tools\msys64', 'C:\msys32',
|
||||
'C:\Program Files\Git', 'C:\Program Files (x86)\Git',
|
||||
'C:\mingw64', 'C:\mingw32',
|
||||
'C:\cygwin', 'C:\cygwin64', 'C:\tools\cygwin'
|
||||
)
|
||||
foreach ($d in $fsForbidden) {
|
||||
if (Test-Path $d) { $fail += "filesystem: $d still exists" }
|
||||
}
|
||||
|
||||
# --- Layer 2: PATH (process + machine + user) ---
|
||||
$pathRegex = '(?i)(msys|mingw|cygwin|\\Git\\(cmd|bin|usr))'
|
||||
foreach ($scope in @('Process','Machine','User')) {
|
||||
$p = [Environment]::GetEnvironmentVariable('Path', $scope)
|
||||
if (-not $p) { continue }
|
||||
foreach ($entry in $p -split ';') {
|
||||
if ($entry -and ($entry -match $pathRegex)) {
|
||||
$fail += ("PATH ({0}): {1}" -f $scope, $entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Layer 3: smoking-gun binaries ---
|
||||
# WSL installs %SystemRoot%\System32\bash.exe; allow that bash only.
|
||||
$forbiddenCmds = @('cygpath','pacman','mintty','git','git-bash','git-cmd')
|
||||
foreach ($cmd in $forbiddenCmds) {
|
||||
$g = Get-Command $cmd -ErrorAction SilentlyContinue
|
||||
if ($g) { $fail += ("binary: {0} -> {1}" -f $cmd, $g.Source) }
|
||||
}
|
||||
$allowedBash = Join-Path $env:SystemRoot 'System32\bash.exe'
|
||||
$bash = Get-Command bash -ErrorAction SilentlyContinue
|
||||
if ($bash -and ($bash.Source -ne $allowedBash)) {
|
||||
$fail += "binary: bash -> $($bash.Source) (only $allowedBash is allowed)"
|
||||
}
|
||||
$sh = Get-Command sh -ErrorAction SilentlyContinue
|
||||
if ($sh) { $fail += "binary: sh -> $($sh.Source)" }
|
||||
|
||||
# --- Layer 4: registry uninstall keys ---
|
||||
$uninstallRoots = @(
|
||||
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall',
|
||||
'HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall',
|
||||
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall'
|
||||
)
|
||||
foreach ($root in $uninstallRoots) {
|
||||
if (-not (Test-Path $root)) { continue }
|
||||
$keys = Get-ChildItem $root -ErrorAction SilentlyContinue
|
||||
foreach ($k in $keys) {
|
||||
$props = Get-ItemProperty $k.PSPath -ErrorAction SilentlyContinue
|
||||
if ($props -and $props.DisplayName -and
|
||||
($props.DisplayName -match '(?i)(^git$|msys|git for windows|cygwin|mingw)')) {
|
||||
$fail += ("registry: {0} -> DisplayName='{1}'" -f $k.PSPath, $props.DisplayName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Layer 5: the toolchain these jobs do need ---
|
||||
# Check up front. A missing ssh.exe fails later with an opaque exec
|
||||
# error, a missing scp.exe or ssh-keygen.exe makes Lima fall back to a
|
||||
# bare ssh lookup, and a missing sftp-server.exe drops reverse-sshfs to
|
||||
# the builtin driver.
|
||||
$opensshDir = Join-Path $env:SystemRoot 'System32\OpenSSH'
|
||||
foreach ($exe in 'ssh.exe', 'scp.exe', 'ssh-keygen.exe', 'sftp-server.exe') {
|
||||
$exePath = Join-Path $opensshDir $exe
|
||||
if (-not (Test-Path $exePath)) { $fail += "missing: $exePath" }
|
||||
}
|
||||
|
||||
if ($fail.Count -gt 0) {
|
||||
Write-Host "Plain-Windows verification FAILED:" -ForegroundColor Red
|
||||
$fail | ForEach-Object { Write-Host " $_" }
|
||||
Write-Error "Runner is not in the expected plain-Windows state; see the list above."
|
||||
exit 1
|
||||
}
|
||||
Write-Host "Plain-Windows verification PASSED."
|
||||
@@ -0,0 +1,78 @@
|
||||
name: 'windows plain smoke test'
|
||||
description: >
|
||||
Runs Lima's create / start / shell / copy / stop / delete cycle against a
|
||||
given template on a plain Windows host, and dumps the instance logs on
|
||||
failure.
|
||||
inputs:
|
||||
template:
|
||||
description: Path to the Lima template (e.g. .\templates\experimental\wsl2.yaml).
|
||||
required: true
|
||||
instance-name:
|
||||
description: Name to use for the test instance.
|
||||
required: false
|
||||
default: plain
|
||||
vm-type:
|
||||
description: >
|
||||
Lima vmType to force via `--vm-type` (e.g. "qemu" or "wsl2"). When empty,
|
||||
the template or Lima's default decides.
|
||||
required: false
|
||||
default: ''
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Smoke test (create / start / shell / copy / stop / delete)
|
||||
shell: pwsh
|
||||
env:
|
||||
LIMA_HOME: ${{ runner.temp }}\lima
|
||||
INSTANCE: ${{ inputs.instance-name }}
|
||||
TEMPLATE: ${{ inputs.template }}
|
||||
VM_TYPE: ${{ inputs.vm-type }}
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# $ErrorActionPreference does not stop on a native command's exit code.
|
||||
function Invoke-Limactl {
|
||||
& .\_output\bin\limactl.exe --debug @args
|
||||
if ($LASTEXITCODE -ne 0) { throw "limactl $($args -join ' ') exited $LASTEXITCODE" }
|
||||
}
|
||||
if (Test-Path $env:LIMA_HOME) { Remove-Item $env:LIMA_HOME -Recurse -Force }
|
||||
New-Item -ItemType Directory -Path $env:LIMA_HOME | Out-Null
|
||||
$createArgs = @('--tty=false', "--name=$env:INSTANCE")
|
||||
if ($env:VM_TYPE) { $createArgs += "--vm-type=$env:VM_TYPE" }
|
||||
$createArgs += $env:TEMPLATE
|
||||
Invoke-Limactl create @createArgs
|
||||
Invoke-Limactl start $env:INSTANCE
|
||||
Invoke-Limactl shell --tty=false $env:INSTANCE -- uname -srm
|
||||
'roundtrip' | Out-File -Encoding ascii "$env:LIMA_HOME\rt.txt"
|
||||
Invoke-Limactl copy "$env:LIMA_HOME\rt.txt" "${env:INSTANCE}:/tmp/rt.txt"
|
||||
Invoke-Limactl shell --tty=false $env:INSTANCE -- cat /tmp/rt.txt
|
||||
# Cover the guest -> host direction too.
|
||||
Invoke-Limactl copy "${env:INSTANCE}:/tmp/rt.txt" "$env:LIMA_HOME\rt-back.txt"
|
||||
# -Raw yields $null for an empty file, and $null.Trim() would throw
|
||||
# before the mismatch message a zero-byte copy is meant to produce.
|
||||
$back = "$(Get-Content "$env:LIMA_HOME\rt-back.txt" -Raw)".Trim()
|
||||
if ($back -ne 'roundtrip') {
|
||||
throw "round-trip mismatch: expected 'roundtrip', got '$back'"
|
||||
}
|
||||
Invoke-Limactl stop $env:INSTANCE
|
||||
Invoke-Limactl delete --force $env:INSTANCE
|
||||
|
||||
- name: Dump Lima logs on failure
|
||||
if: failure()
|
||||
shell: pwsh
|
||||
env:
|
||||
LIMA_HOME: ${{ runner.temp }}\lima
|
||||
INSTANCE: ${{ inputs.instance-name }}
|
||||
run: |
|
||||
$instDir = Join-Path $env:LIMA_HOME $env:INSTANCE
|
||||
if (-not (Test-Path $instDir)) {
|
||||
Write-Host "No instance directory at $instDir, nothing to dump."
|
||||
exit 0
|
||||
}
|
||||
Get-ChildItem $instDir | Format-Table Name, Length, LastWriteTime
|
||||
foreach ($name in 'ha.stdout.log','ha.stderr.log','serial.log','lima.yaml','ssh.config') {
|
||||
$p = Join-Path $instDir $name
|
||||
if (Test-Path $p) {
|
||||
Write-Host ("===== {0} =====" -f $p)
|
||||
Get-Content $p
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
name: 'windows plain templates'
|
||||
description: >
|
||||
Resolves Lima's template symlinks and copies templates/ to
|
||||
_output/share/lima/templates/, replicating `make`'s TEMPLATES target without
|
||||
make.
|
||||
|
||||
A checkout writes the git symlinks under templates/ as NTFS symlinks or as
|
||||
plaintext stubs, depending on core.symlinks, so the targets are read from
|
||||
git's object store instead.
|
||||
|
||||
This action MUST run before windows_plain_host (which uninstalls Git for
|
||||
Windows), because it shells out to `git ls-tree` and `git cat-file`.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install templates (replicating make's TEMPLATES target)
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (-not (Get-Command git -ErrorAction SilentlyContinue)) {
|
||||
throw "git not found; this action must run before windows_plain_host"
|
||||
}
|
||||
$srcRoot = 'templates'
|
||||
$dstRoot = '_output\share\lima\templates'
|
||||
if (Test-Path $dstRoot) { Remove-Item $dstRoot -Recurse -Force }
|
||||
New-Item -ItemType Directory -Force -Path $dstRoot | Out-Null
|
||||
Copy-Item -Path "$srcRoot\*" -Destination $dstRoot -Recurse -Force
|
||||
|
||||
$linkTargets = @{}
|
||||
# Check ls-tree's exit code before the loop, whose `git cat-file` calls
|
||||
# overwrite $LASTEXITCODE.
|
||||
$lsTreeOutput = git ls-tree -r HEAD $srcRoot
|
||||
if ($LASTEXITCODE -ne 0) { throw "git ls-tree failed: $LASTEXITCODE" }
|
||||
foreach ($line in $lsTreeOutput) {
|
||||
if ($line -notmatch '^120000\s') { continue }
|
||||
$parts = $line -split "`t", 2
|
||||
$sha = ($parts[0] -split '\s+')[2]
|
||||
$posixPath = $parts[1]
|
||||
# A failed cat-file writes only to stderr. Keep the value a string,
|
||||
# since $null.Trim() would throw ahead of the check below.
|
||||
$target = "$(git cat-file blob $sha)".Trim()
|
||||
if ($LASTEXITCODE -ne 0) { throw "git cat-file $sha failed: $LASTEXITCODE" }
|
||||
$linkTargets[$posixPath] = $target
|
||||
}
|
||||
|
||||
foreach ($posixPath in $linkTargets.Keys) {
|
||||
$resolved = $posixPath
|
||||
$depth = 0
|
||||
while ($linkTargets.ContainsKey($resolved)) {
|
||||
if ($depth++ -gt 16) { throw "Symlink chain too deep starting from $posixPath" }
|
||||
$target = $linkTargets[$resolved]
|
||||
$slash = $resolved.LastIndexOf('/')
|
||||
$resolved = if ($slash -ge 0) { "$($resolved.Substring(0, $slash))/$target" } else { $target }
|
||||
$segs = New-Object System.Collections.ArrayList
|
||||
foreach ($s in ($resolved -split '/')) {
|
||||
if ($s -eq '' -or $s -eq '.') { continue }
|
||||
if ($s -eq '..') {
|
||||
if ($segs.Count -gt 0) { $segs.RemoveAt($segs.Count - 1) | Out-Null }
|
||||
continue
|
||||
}
|
||||
$segs.Add($s) | Out-Null
|
||||
}
|
||||
$resolved = $segs -join '/'
|
||||
}
|
||||
# Defensive: the '..' handling above silently underflows. Guard against
|
||||
# a future symlink edit whose chain resolves outside templates/.
|
||||
if (-not $resolved.StartsWith("$srcRoot/")) {
|
||||
throw "Symlink $posixPath resolved to $resolved, which escapes $srcRoot/"
|
||||
}
|
||||
$srcFile = $resolved -replace '/', '\'
|
||||
$dstFile = Join-Path '_output\share\lima' ($posixPath -replace '/', '\')
|
||||
Write-Host "Resolving symlink: $posixPath -> $resolved"
|
||||
Copy-Item -Force -Path $srcFile -Destination $dstFile
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
name: 'windows qemu install'
|
||||
description: Installs QEMU on the Windows runner via winget.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Install QEMU
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
winget install --silent --accept-source-agreements --accept-package-agreements --disable-interactivity SoftwareFreedomConservancy.QEMU
|
||||
if ($LASTEXITCODE -ne 0) { throw "winget install QEMU failed: $LASTEXITCODE" }
|
||||
@@ -0,0 +1,55 @@
|
||||
name: 'windows wsl2 setup'
|
||||
description: >
|
||||
Enables WSL2 and imports a dummy distro, because `wsl --list --verbose`
|
||||
(called from Lima) fails when no distro is installed (lima-vm/lima#1826).
|
||||
WSL2 2.5.7.0 and later reject a distro tarball without /bin/sh and /etc.
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Enable WSL2
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# $ErrorActionPreference does not stop on a native command's exit code.
|
||||
wsl --set-default-version 2
|
||||
if ($LASTEXITCODE -ne 0) { throw "wsl --set-default-version failed: $LASTEXITCODE" }
|
||||
wsl --shutdown
|
||||
if ($LASTEXITCODE -ne 0) { throw "wsl --shutdown failed: $LASTEXITCODE" }
|
||||
# --update and --list --online reach a Microsoft update service that
|
||||
# returns transient 403s, so these four calls are informational.
|
||||
wsl --update
|
||||
wsl --status
|
||||
wsl --version
|
||||
wsl --list --online
|
||||
# pwsh would otherwise exit with --list --online's status.
|
||||
exit 0
|
||||
- name: Install dummy WSL2 distro
|
||||
shell: pwsh
|
||||
run: |
|
||||
$ErrorActionPreference = 'Stop'
|
||||
# Keep the scratch tarball and the distro disk under $RUNNER_TEMP so no
|
||||
# untracked files land in the checked-out workspace.
|
||||
$scratch = Join-Path $env:RUNNER_TEMP 'wsl2-dummy'
|
||||
New-Item -ItemType Directory -Force -Path "$scratch\bin","$scratch\etc" | Out-Null
|
||||
Set-Content -Path "$scratch\bin\sh" -Value ''
|
||||
$tarball = Join-Path $env:RUNNER_TEMP 'wsl2-dummy.tar'
|
||||
tar -cf $tarball --format ustar -C $scratch .
|
||||
if ($LASTEXITCODE -ne 0) { throw "tar -cf $tarball failed: $LASTEXITCODE" }
|
||||
# Unchecked, because a fresh runner has nothing to unregister. A second
|
||||
# run on a developer machine needs the unregister before the import.
|
||||
wsl --unregister dummy
|
||||
$distroDir = Join-Path $env:RUNNER_TEMP 'wsl2-dummy-distro'
|
||||
New-Item -ItemType Directory -Force -Path $distroDir | Out-Null
|
||||
# A composite action step cannot set timeout-minutes, so bound the
|
||||
# import here. -ArgumentList joins its elements unquoted, so quote the
|
||||
# two paths.
|
||||
$p = Start-Process -FilePath wsl.exe -PassThru -NoNewWindow `
|
||||
-ArgumentList '--import', 'dummy', "`"$distroDir`"", "`"$tarball`""
|
||||
if (-not $p.WaitForExit(60000)) {
|
||||
$p.Kill()
|
||||
throw "wsl --import dummy timed out after 60s"
|
||||
}
|
||||
if ($p.ExitCode -ne 0) { throw "wsl --import dummy failed: $($p.ExitCode)" }
|
||||
wsl --list --verbose
|
||||
# Informational, like the listings in the step above.
|
||||
exit 0
|
||||
+74
-43
@@ -147,36 +147,11 @@ jobs:
|
||||
- name: Uninstall
|
||||
run: sudo make uninstall
|
||||
|
||||
windows:
|
||||
windows-wsl2:
|
||||
name: "Windows tests (WSL2)"
|
||||
runs-on: windows-2025
|
||||
timeout-minutes: 45
|
||||
steps:
|
||||
- name: Enable WSL2
|
||||
run: |
|
||||
wsl --set-default-version 2
|
||||
wsl --shutdown
|
||||
wsl --update
|
||||
wsl --status
|
||||
wsl --version
|
||||
wsl --list --online
|
||||
- name: Install WSL2 distro
|
||||
timeout-minutes: 1
|
||||
run: |
|
||||
# FIXME: At least one distro has to be installed here,
|
||||
# otherwise `wsl --list --verbose` (called from Lima) fails:
|
||||
# https://github.com/lima-vm/lima/pull/1826#issuecomment-1729993334
|
||||
# The distro image itself is not consumed by Lima.
|
||||
# Starting with WSL2 version 2.5.7.0 the distro will be rejected
|
||||
# if it doesn't contain /bin/sh and /etc.
|
||||
# ------------------------------------------------------------------
|
||||
mkdir dummy
|
||||
mkdir dummy\bin
|
||||
mkdir dummy\etc
|
||||
echo "" >dummy\bin\sh
|
||||
tar -cf dummy.tar --format ustar -C dummy .
|
||||
wsl --import dummy $env:TEMP dummy.tar
|
||||
wsl --list --verbose
|
||||
- name: Set gitconfig
|
||||
run: |
|
||||
git config --global core.autocrlf false
|
||||
@@ -190,27 +165,26 @@ jobs:
|
||||
with:
|
||||
go-version: stable
|
||||
cache: false
|
||||
- uses: $/.github/actions/windows_wsl2_setup
|
||||
- uses: $/.github/actions/windows_msys2_prep
|
||||
- name: Unit tests
|
||||
run: go test -v ./...
|
||||
- name: Make
|
||||
run: make
|
||||
- name: Integration tests (WSL2, Windows host)
|
||||
run: |
|
||||
$env:PATH = "$pwd\_output\bin;" + 'C:\msys64\usr\bin;' + $env:PATH
|
||||
pacman -Sy --noconfirm diffutils jq socat w3m
|
||||
$env:MSYS2_ENV_CONV_EXCL = 'HOME_HOST;HOME_GUEST;_LIMA_WINDOWS_EXTRA_PATH'
|
||||
$env:PATH = "$pwd\_output\bin;" + $env:PATH
|
||||
$env:MSYS2_ENV_CONV_EXCL = 'HOME_HOST;HOME_GUEST'
|
||||
$env:HOME_HOST = $(cygpath.exe "$env:USERPROFILE")
|
||||
$env:HOME_GUEST = "/mnt$env:HOME_HOST"
|
||||
$env:LIMACTL_CREATE_ARGS = '--vm-type=wsl2 --mount-type=wsl2 --containerd=system'
|
||||
$env:_LIMA_WINDOWS_EXTRA_PATH = 'C:\Program Files\Git\usr\bin'
|
||||
bash.exe -c "./hack/test-templates.sh templates/experimental/wsl2.yaml"
|
||||
- name: Run BATS port-forwarding test (WSL2, Windows host)
|
||||
run: |
|
||||
$env:PATH = "$pwd\_output\bin;" + 'C:\msys64\usr\bin;' + $env:PATH
|
||||
$env:MSYS2_ENV_CONV_EXCL = '_LIMA_WINDOWS_EXTRA_PATH;LIMA_BATS_PORT_FORWARDING_BASE_TEMPLATE'
|
||||
$env:PATH = "$pwd\_output\bin;" + $env:PATH
|
||||
$env:MSYS2_ENV_CONV_EXCL = 'LIMA_BATS_PORT_FORWARDING_BASE_TEMPLATE'
|
||||
$env:LIMACTL_CREATE_ARGS = '--vm-type=wsl2 --mount-type=wsl2 --containerd=system'
|
||||
$env:LIMA_BATS_PORT_FORWARDING_BASE_TEMPLATE = 'template:experimental/wsl2'
|
||||
$env:_LIMA_WINDOWS_EXTRA_PATH = 'C:\Program Files\Git\usr\bin'
|
||||
bash.exe -c "./hack/bats/lib/bats-core/bin/bats --timing ./hack/bats/tests/port-forwarding.bats"
|
||||
|
||||
windows-qemu:
|
||||
@@ -231,29 +205,86 @@ jobs:
|
||||
with:
|
||||
go-version: stable
|
||||
cache: false
|
||||
- uses: $/.github/actions/windows_qemu_install
|
||||
- uses: $/.github/actions/windows_msys2_prep
|
||||
- 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 diffutils jq socat w3m
|
||||
$env:MSYS2_ENV_CONV_EXCL = 'HOME_HOST;HOME_GUEST;_LIMA_WINDOWS_EXTRA_PATH'
|
||||
$env:PATH = "$pwd\_output\bin;" + 'C:\Program Files\QEMU;' + $env:PATH
|
||||
$env:MSYS2_ENV_CONV_EXCL = 'HOME_HOST;HOME_GUEST'
|
||||
$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"
|
||||
- name: Run BATS port-forwarding test (QEMU, Windows host)
|
||||
run: |
|
||||
$env:PATH = "$pwd\_output\bin;" + 'C:\msys64\usr\bin;' + 'C:\Program Files\QEMU;' + $env:PATH
|
||||
$env:MSYS2_ENV_CONV_EXCL = '_LIMA_WINDOWS_EXTRA_PATH'
|
||||
$env:PATH = "$pwd\_output\bin;" + 'C:\Program Files\QEMU;' + $env:PATH
|
||||
$env:LIMACTL_CREATE_ARGS = '--vm-type=qemu'
|
||||
$env:_LIMA_WINDOWS_EXTRA_PATH = 'C:\Program Files\Git\usr\bin'
|
||||
bash.exe -c "./hack/bats/lib/bats-core/bin/bats --timing ./hack/bats/tests/port-forwarding.bats"
|
||||
|
||||
windows-plain-wsl2:
|
||||
# Smoke test of Lima's WSL2 driver on a host with only what a default
|
||||
# Windows 10/11 install ships (native OpenSSH, wsl.exe, tar). If a step
|
||||
# ever needs MSYS2 or Git for Windows back to pass, Lima has gained a new
|
||||
# external-binary dependency, and that needs review first.
|
||||
name: "Windows tests (WSL2, plain Windows host)"
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: stable
|
||||
cache: false
|
||||
- uses: $/.github/actions/windows_wsl2_setup
|
||||
# No windows_plain_templates: wsl2.yaml has no `base:`.
|
||||
- uses: $/.github/actions/windows_plain_host
|
||||
- uses: $/.github/actions/windows_plain_build
|
||||
- uses: $/.github/actions/windows_plain_smoke_test
|
||||
with:
|
||||
template: .\templates\experimental\wsl2.yaml
|
||||
|
||||
windows-plain-qemu:
|
||||
# Like windows-plain-wsl2, for the QEMU driver. The reverse-sshfs mount
|
||||
# starts through the native sftp-server, but sshocker does not fail when
|
||||
# the mount check fails, so the mount itself is not covered.
|
||||
name: "Windows tests (QEMU, plain Windows host)"
|
||||
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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
||||
with:
|
||||
go-version: stable
|
||||
cache: false
|
||||
- uses: $/.github/actions/windows_qemu_install
|
||||
- uses: $/.github/actions/windows_plain_templates
|
||||
- uses: $/.github/actions/windows_plain_host
|
||||
# No QEMU on PATH here: limactl appends the default QEMU install dir to
|
||||
# its own PATH, and this job tests that.
|
||||
- name: Check that QEMU is not on PATH
|
||||
run: |
|
||||
$qemu = Get-Command qemu-system-x86_64 -ErrorAction SilentlyContinue
|
||||
if ($qemu) { throw "QEMU is on PATH: $($qemu.Source)" }
|
||||
- uses: $/.github/actions/windows_plain_build
|
||||
- uses: $/.github/actions/windows_plain_smoke_test
|
||||
with:
|
||||
template: .\templates\default.yaml
|
||||
vm-type: qemu
|
||||
|
||||
qemu:
|
||||
name: "Integration tests (QEMU, macOS host)"
|
||||
runs-on: macos-15-large # Intel
|
||||
|
||||
@@ -40,14 +40,6 @@ func main() {
|
||||
|
||||
yq.MaybeRunYQ()
|
||||
if runtime.GOOS == "windows" {
|
||||
extras, hasExtra := os.LookupEnv("_LIMA_WINDOWS_EXTRA_PATH")
|
||||
if hasExtra && strings.TrimSpace(extras) != "" {
|
||||
p := os.Getenv("PATH")
|
||||
err := os.Setenv("PATH", strings.TrimSpace(extras)+string(filepath.ListSeparator)+p)
|
||||
if err != nil {
|
||||
logrus.Warning("Can't add extras to PATH, relying entirely on system PATH")
|
||||
}
|
||||
}
|
||||
p := os.Getenv("PATH")
|
||||
newPath := envutil.AppendDirsToPath(p, envutil.PlatformCommonToolDirs)
|
||||
if newPath != p {
|
||||
|
||||
@@ -128,7 +128,20 @@ func TestRsyncCommandPaths(t *testing.T) {
|
||||
// The paths are everything after the "--" separator.
|
||||
sep := slices.Index(cmd.Args, "--")
|
||||
assert.Assert(t, sep != -1, "rsync args must contain the %#q separator: %v", "--", cmd.Args)
|
||||
assert.DeepEqual(t, cmd.Args[sep+1:], tt.expected)
|
||||
// Command converts each local path for the rsync it runs. Convert
|
||||
// without the trailing slash so a conversion that drops it still
|
||||
// fails here.
|
||||
expected := make([]string, len(tt.expected))
|
||||
for i, p := range tt.expected {
|
||||
trimmed := strings.TrimSuffix(p, "/")
|
||||
expected[i], err = tool.pathForRsync(t.Context(), trimmed)
|
||||
assert.NilError(t, err)
|
||||
expected[i] += strings.TrimPrefix(p, trimmed)
|
||||
}
|
||||
assert.DeepEqual(t, cmd.Args[sep+1:], expected)
|
||||
for _, arg := range cmd.Args[sep+1:] {
|
||||
assert.Assert(t, !looksRemoteToRsync(arg), "rsync reads %q as host:path", arg)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,40 @@ func TestPickCompleteSSHOnWindows(t *testing.T) {
|
||||
t.Setenv("PATH", mingit)
|
||||
assert.Equal(t, pickCompleteSSHOnWindows(), nativeSSH)
|
||||
})
|
||||
|
||||
t.Run("incomplete native install disqualifies it too", func(t *testing.T) {
|
||||
fakeRoot := resolvedTempDir(t)
|
||||
nativeDir := filepath.Join(fakeRoot, "System32", "OpenSSH")
|
||||
assert.NilError(t, os.MkdirAll(nativeDir, 0o755))
|
||||
assert.NilError(t, os.WriteFile(filepath.Join(nativeDir, "ssh.exe"), nil, 0o644))
|
||||
t.Setenv("SystemRoot", fakeRoot)
|
||||
t.Setenv("PATH", "")
|
||||
|
||||
assert.Equal(t, pickCompleteSSHOnWindows(), "",
|
||||
"an ssh.exe without its companions never qualifies, wherever it lives")
|
||||
})
|
||||
}
|
||||
|
||||
// TestNewSSHExeFallsBackToPATH: when no directory holds a complete install,
|
||||
// NewSSHExe takes whatever `ssh` PATH resolves.
|
||||
func TestNewSSHExeFallsBackToPATH(t *testing.T) {
|
||||
if runtime.GOOS != "windows" {
|
||||
t.Skip("Windows-only PATH walk")
|
||||
}
|
||||
|
||||
partial := resolvedTempDir(t)
|
||||
sshExe := filepath.Join(partial, "ssh.exe")
|
||||
assert.NilError(t, os.WriteFile(sshExe, nil, 0o644))
|
||||
|
||||
t.Setenv("SystemRoot", resolvedTempDir(t))
|
||||
t.Setenv("PATH", partial)
|
||||
t.Setenv(EnvShellSSH, "")
|
||||
|
||||
assert.Equal(t, pickCompleteSSHOnWindows(), "", "the partial install must not qualify")
|
||||
|
||||
got, err := NewSSHExe()
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, got.Exe, sshExe)
|
||||
}
|
||||
|
||||
// TestCygpathForSSH: an ssh.exe next to cygpath.exe is Cygwin-based and
|
||||
|
||||
@@ -147,20 +147,6 @@ This page documents the environment variables used in Lima.
|
||||
export LIMA_USERNET_RESOLVE_IP_ADDRESS_TIMEOUT=5
|
||||
```
|
||||
|
||||
### `_LIMA_WINDOWS_EXTRA_PATH`
|
||||
|
||||
- **Description**: Additional directories which will be added to PATH by `limactl.exe` process to search for tools.
|
||||
It is useful, when there is a need to prevent collisions between binaries available in active shell and ones
|
||||
used by `limactl.exe` - injecting them only for the running process w/o altering PATH observed by user shell.
|
||||
Is is Windows specific and does nothing for other platforms.
|
||||
- **Default**: unset
|
||||
- **Usage**:
|
||||
```bat
|
||||
set _LIMA_WINDOWS_EXTRA_PATH=C:\Program Files\Git\usr\bin
|
||||
```
|
||||
- **Note**: It is an experimental setting and has no guarantees being ever promoted to stable. It may be removed
|
||||
or changed at any stage of project development.
|
||||
|
||||
### `QEMU_SYSTEM_AARCH64`
|
||||
|
||||
- **Description**: Path to the `qemu-system-aarch64` binary.
|
||||
|
||||
@@ -3,6 +3,11 @@ title: Breaking changes
|
||||
weight: 20
|
||||
---
|
||||
|
||||
## v2.3.0
|
||||
- The experimental `_LIMA_WINDOWS_EXTRA_PATH` environment variable was removed. It prepended directories to
|
||||
`limactl.exe`'s own PATH on Windows hosts, leaving the calling shell's PATH untouched. Put them on `PATH`
|
||||
instead.
|
||||
|
||||
## v2.2.0
|
||||
- The default [`socket_vmnet` group](../config/network/vmnet.md) in `networks.yaml` was changed from `everyone` to
|
||||
`admin`. A non-admin user must set `group` to a group they belong to (e.g. `staff`).
|
||||
|
||||
Reference in new issue
Block a user