mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 10:37:35 +00:00
* ci(wsl-orcad): skip the WSL MSI when the image already has 3.0.1 windows-2025 image 20261007.264.1 ships WSL 3.0.1.0, and reinstalling the pinned 3.0.1 MSI over it exits 1603, failing every run. Install only when the image is older, print the msiexec log on failure, and gate on >= 3.0.1 after. * fix(orcad): let a managed launch fall back when its default port is taken #29432 made any --port mandatory, but managed SSH/WSL launches pass the default 6768 as a preference and read the bound port from readiness. WSL2 distros share one VM loopback, so a second distro's orcad now failed with pinned_port_unavailable. Only an operator --port is required now.
255 lines
13 KiB
YAML
255 lines
13 KiB
YAML
name: WSL orcad E2E
|
|
|
|
# WSL via orcad (step C10) on real WSL2 distros: a GitHub-hosted windows-2025 runner imports a
|
|
# pinned Ubuntu 24.04 rootfs twice (systemd off and on) and runs
|
|
# src/main/wsl/wsl-orcad-real-distro-lane.test.ts against both: deploy and launch, the wsl.exe
|
|
# stdio bridge, runtime RPC, liveness recovery, holder survival and teardown. Not a required check.
|
|
# Hosted WSL2 was proven in #27783: WSL 2.7.14, import 13-18 s, cold boot 8-14 s.
|
|
on:
|
|
pull_request:
|
|
types: [opened, synchronize, reopened, ready_for_review, converted_to_draft]
|
|
paths:
|
|
- 'src/main/wsl/**'
|
|
- '!src/main/wsl/**/*.test.ts'
|
|
- 'src/main/wsl/wsl-orcad-real-distro-lane.test.ts'
|
|
# This lane executes only the real distro test above.
|
|
- 'src/main/ssh/orcad-*'
|
|
- '!src/main/ssh/orcad-*.test.ts'
|
|
- 'src/main/ssh/remote-install-transport.ts'
|
|
- '.github/workflows/wsl-orcad-e2e.yml'
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: wsl-orcad-e2e-${{ github.event.pull_request.number || github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
env:
|
|
# wsl.exe prints UTF-16LE to pipes unless told otherwise.
|
|
WSL_UTF8: '1'
|
|
ROOTFS_URL: https://releases.ubuntu.com/24.04.4/ubuntu-24.04.4-wsl-amd64.wsl
|
|
ROOTFS_SHA256: 9b2f7730dc68227dd04a9f3e5eab86ad85caf556b8606ad94f1f29ff5c4fd3f5
|
|
WSL_MSI_URL: https://github.com/microsoft/WSL/releases/download/3.0.1/wsl.3.0.1.0.x64.msi
|
|
WSL_MSI_SHA256: 28b1a0d013640a2ac95898ea705fa186e5b4ff767a1c1b49257161bc106599c6
|
|
PLAIN_DISTRO: orca-lane-plain
|
|
SYSTEMD_DISTRO: orca-lane-systemd
|
|
|
|
jobs:
|
|
# The distro is linux-x64-glibc; its node-pty slot only builds on Linux, and nothing else is needed.
|
|
template:
|
|
if: github.event_name != 'pull_request' || github.event.pull_request.draft != true
|
|
name: Build the linux-x64-glibc orcad template
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 20
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
with:
|
|
native-runtime: node
|
|
- name: Build the template
|
|
run: |
|
|
pnpm build:orcad-prebuilds
|
|
pnpm build:orcad-prebuilds --require-slots linux-x64-glibc
|
|
pnpm build:orcad
|
|
node config/scripts/build-orcad-template.mjs --targets linux-x64-glibc
|
|
- uses: actions/upload-artifact@v7
|
|
with:
|
|
name: wsl-orcad-template
|
|
path: out/orcad-template/
|
|
# The template carries dot-directories (`.claude-plugin`) its manifest checksums.
|
|
include-hidden-files: true
|
|
retention-days: 1
|
|
if-no-files-found: error
|
|
|
|
wsl2:
|
|
name: WSL2 distros (systemd off and on)
|
|
needs: template
|
|
runs-on: windows-2025
|
|
timeout-minutes: 45
|
|
env:
|
|
ORCA_BACKGROUND_LAUNCH: '1'
|
|
ORCA_WSL_ORCAD_LANE_RECEIPTS: ${{ github.workspace }}\test-results\wsl-orcad-lane
|
|
defaults:
|
|
run:
|
|
shell: pwsh
|
|
steps:
|
|
- uses: actions/checkout@v6
|
|
with:
|
|
persist-credentials: false
|
|
# WSL before 3.0.1 drops the VM-wide WSLInterop entry when a systemd distro stops
|
|
# (microsoft/WSL#13885). Images from 20261007 ship 3.0.1, where reinstalling the MSI fails
|
|
# with 1603, so install only when older; `wsl --update` gets rate-limited by the GitHub API.
|
|
- name: Check the installed WSL version
|
|
id: wsl-version
|
|
run: |
|
|
"ImageOS=$env:ImageOS ImageVersion=$env:ImageVersion"
|
|
$details = (wsl.exe --version) -join "`n"
|
|
$details
|
|
$match = [regex]::Match($details, '(?m)^WSL version:\s*(\d+(?:\.\d+){2,3})\s*$')
|
|
# Inbox WSL has no --version, so an unreadable version means the package is needed.
|
|
$installed = if ($LASTEXITCODE -eq 0 -and $match.Success) { [version]$match.Groups[1].Value } else { [version]'0.0.0' }
|
|
$needsInstall = $installed -lt [version]'3.0.1'
|
|
"Installed WSL $installed; minimum 3.0.1; install needed: $needsInstall"
|
|
"needs-install=$($needsInstall.ToString().ToLowerInvariant())" >> $env:GITHUB_OUTPUT
|
|
- name: Restore the pinned WSL package
|
|
id: wsl-msi-cache
|
|
if: steps.wsl-version.outputs.needs-install == 'true'
|
|
uses: actions/cache/restore@v5
|
|
with:
|
|
path: ${{ runner.temp }}/wsl-msi
|
|
key: wsl-orcad-msi-${{ env.WSL_MSI_SHA256 }}
|
|
- name: Install WSL 3.0.1
|
|
if: steps.wsl-version.outputs.needs-install == 'true'
|
|
timeout-minutes: 8
|
|
run: |
|
|
$dir = Join-Path $env:RUNNER_TEMP 'wsl-msi'
|
|
New-Item -ItemType Directory -Force $dir | Out-Null
|
|
$msi = Join-Path $dir 'wsl.msi'
|
|
if (-not (Test-Path $msi)) { & "$env:WINDIR\System32\curl.exe" --fail --location --silent --show-error --output $msi $env:WSL_MSI_URL }
|
|
if ((Get-FileHash $msi -Algorithm SHA256).Hash.ToLowerInvariant() -ne $env:WSL_MSI_SHA256) {
|
|
Remove-Item $msi; throw 'WSL package checksum mismatch'
|
|
}
|
|
# Kept beside the receipts so the always() upload carries it.
|
|
New-Item -ItemType Directory -Force $env:ORCA_WSL_ORCAD_LANE_RECEIPTS | Out-Null
|
|
$log = Join-Path $env:ORCA_WSL_ORCAD_LANE_RECEIPTS 'wsl-msi-install.log'
|
|
$install = Start-Process msiexec.exe -ArgumentList @('/i', $msi, '/qn', '/norestart', '/l*v', $log) -Wait -PassThru
|
|
if ($install.ExitCode -notin 0, 3010) {
|
|
if (Test-Path $log) {
|
|
$lines = @(Get-Content $log)
|
|
$failed = ($lines | Select-String -SimpleMatch 'Return value 3' | Select-Object -First 1).LineNumber
|
|
if ($failed) { '--- msiexec log before the failing action'; $lines[[math]::Max(0, $failed - 40)..($failed - 1)] }
|
|
'--- msiexec log tail'; $lines | Select-Object -Last 60
|
|
}
|
|
throw "WSL install failed: $($install.ExitCode)"
|
|
}
|
|
wsl.exe --shutdown
|
|
- name: Save the pinned WSL package
|
|
if: steps.wsl-version.outputs.needs-install == 'true' && steps.wsl-msi-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@v5
|
|
with:
|
|
path: ${{ runner.temp }}/wsl-msi
|
|
key: wsl-orcad-msi-${{ env.WSL_MSI_SHA256 }}
|
|
- name: Verify WSL is at least 3.0.1
|
|
run: |
|
|
$details = (wsl.exe --version) -join "`n"
|
|
if ($LASTEXITCODE -ne 0) { throw "Could not read the WSL version: $details" }
|
|
$details
|
|
$match = [regex]::Match($details, '(?m)^WSL version:\s*(\d+(?:\.\d+){2,3})\s*$')
|
|
if (-not $match.Success) { throw 'Could not parse the WSL version' }
|
|
if ([version]$match.Groups[1].Value -lt [version]'3.0.1') { throw "WSL $($match.Groups[1].Value) is older than 3.0.1" }
|
|
- name: Restore the pinned rootfs
|
|
id: rootfs-cache
|
|
uses: actions/cache/restore@v5
|
|
with:
|
|
path: ${{ runner.temp }}/wsl-rootfs
|
|
key: wsl-orcad-rootfs-${{ env.ROOTFS_SHA256 }}
|
|
# Runs beside the dependency install; the lane waits for it below.
|
|
- name: Import both distros as WSL2
|
|
id: import
|
|
background: true
|
|
timeout-minutes: 10
|
|
run: |
|
|
$cache = Join-Path $env:RUNNER_TEMP 'wsl-rootfs'
|
|
New-Item -ItemType Directory -Force $cache | Out-Null
|
|
$rootfs = Join-Path $cache 'ubuntu.wsl'
|
|
if (-not (Test-Path $rootfs)) { Invoke-WebRequest $env:ROOTFS_URL -OutFile $rootfs }
|
|
if ((Get-FileHash $rootfs -Algorithm SHA256).Hash.ToLowerInvariant() -ne $env:ROOTFS_SHA256) {
|
|
Remove-Item $rootfs; throw 'rootfs checksum mismatch'
|
|
}
|
|
foreach ($d in @(@{ Name = $env:PLAIN_DISTRO; Systemd = 'false' }, @{ Name = $env:SYSTEMD_DISTRO; Systemd = 'true' })) {
|
|
$sw = [Diagnostics.Stopwatch]::StartNew()
|
|
# No WSL1 fallback: the lane exists to prove WSL2.
|
|
wsl.exe --import $d.Name (Join-Path $env:RUNNER_TEMP $d.Name) $rootfs --version 2
|
|
if ($LASTEXITCODE -ne 0) { throw "WSL2 import of $($d.Name) failed: $LASTEXITCODE" }
|
|
"$($d.Name) import_ms=$($sw.ElapsedMilliseconds)"
|
|
if (-not ((wsl.exe --list --verbose) -match "$($d.Name)\s+\S+\s+2\s*$")) { throw "$($d.Name) is not a WSL2 distro" }
|
|
# A non-root default user, as on a real install; git for the lane's repo.
|
|
$setup = "set -e; id orca >/dev/null 2>&1 || useradd -m -s /bin/bash orca; " +
|
|
"printf '[boot]\nsystemd=$($d.Systemd)\n[user]\ndefault=orca\n' > /etc/wsl.conf; " +
|
|
"command -v git >/dev/null || (apt-get update -qq && apt-get install -y -qq git)"
|
|
wsl.exe -d $d.Name -u root --exec sh -c $setup
|
|
if ($LASTEXITCODE -ne 0) { throw "provisioning $($d.Name) failed: $LASTEXITCODE" }
|
|
wsl.exe --terminate $d.Name
|
|
$sw.Restart()
|
|
wsl.exe -d $d.Name --exec sh -c 'echo "user=$(id -un) pid1=$(ps -p 1 -o comm=) kernel=$(uname -r)"'
|
|
if ($LASTEXITCODE -ne 0) { throw "first boot of $($d.Name) failed: $LASTEXITCODE" }
|
|
"$($d.Name) boot_ms=$($sw.ElapsedMilliseconds)"
|
|
wsl.exe --terminate $d.Name
|
|
}
|
|
wsl.exe --list --verbose
|
|
- uses: ./.github/actions/install-node-dependencies
|
|
with:
|
|
native-runtime: node
|
|
- uses: actions/download-artifact@v8
|
|
with:
|
|
name: wsl-orcad-template
|
|
path: ${{ runner.temp }}/orcad-template
|
|
- wait: import
|
|
# Saved even when the lane fails; the import step verified its checksum.
|
|
- name: Save the pinned rootfs
|
|
if: steps.rootfs-cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@v5
|
|
with:
|
|
path: ${{ runner.temp }}/wsl-rootfs
|
|
key: wsl-orcad-rootfs-${{ env.ROOTFS_SHA256 }}
|
|
- name: 'Phase 1: deploy, RPC, kill orcad, terminate distro'
|
|
timeout-minutes: 20
|
|
env:
|
|
ORCA_RUN_WSL_ORCAD_LANE: '1'
|
|
ORCA_WSL_ORCAD_LANE_PHASE: launch
|
|
ORCA_ORCAD_TEMPLATE_PATH: ${{ runner.temp }}/orcad-template
|
|
run: |
|
|
New-Item -ItemType Directory -Force $env:ORCA_WSL_ORCAD_LANE_RECEIPTS | Out-Null
|
|
node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/wsl/wsl-orcad-real-distro-lane.test.ts --reporter=verbose 2>&1 | Tee-Object (Join-Path $env:ORCA_WSL_ORCAD_LANE_RECEIPTS 'launch.log')
|
|
if ($global:LASTEXITCODE -ne 0) { throw "launch phase failed: $global:LASTEXITCODE" }
|
|
# A new process: the holders must have kept both distros Running since phase 1 exited.
|
|
- name: 'Phase 2: holders survive, reconnect, teardown'
|
|
timeout-minutes: 10
|
|
env:
|
|
ORCA_RUN_WSL_ORCAD_LANE: '1'
|
|
ORCA_WSL_ORCAD_LANE_PHASE: survive
|
|
ORCA_ORCAD_TEMPLATE_PATH: ${{ runner.temp }}/orcad-template
|
|
run: |
|
|
node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/wsl/wsl-orcad-real-distro-lane.test.ts --reporter=verbose 2>&1 | Tee-Object (Join-Path $env:ORCA_WSL_ORCAD_LANE_RECEIPTS 'survive.log')
|
|
if ($global:LASTEXITCODE -ne 0) { throw "survive phase failed: $global:LASTEXITCODE" }
|
|
# Belt and braces for the runner: a missing VM-wide interop entry is restored, then fails.
|
|
- name: WSLInterop is intact
|
|
if: ${{ !cancelled() && steps.import.outcome == 'success' }}
|
|
timeout-minutes: 3
|
|
run: |
|
|
$state = wsl.exe -d $env:PLAIN_DISTRO -u root --exec sh -c 'if [ -e /proc/sys/fs/binfmt_misc/WSLInterop ]; then echo present; else echo :WSLInterop:M::MZ::/init:P > /proc/sys/fs/binfmt_misc/register && echo restored; fi'
|
|
"WSLInterop: $state"
|
|
wsl.exe --terminate $env:PLAIN_DISTRO
|
|
if ("$state".Trim() -ne 'present') { throw "WSLInterop was missing ($state)" }
|
|
- name: Collect orcad logs
|
|
if: failure()
|
|
timeout-minutes: 3
|
|
run: |
|
|
$out = $env:ORCA_WSL_ORCAD_LANE_RECEIPTS
|
|
New-Item -ItemType Directory -Force $out | Out-Null
|
|
foreach ($d in @($env:PLAIN_DISTRO, $env:SYSTEMD_DISTRO)) {
|
|
wsl.exe -d $d --exec sh -c 'for f in $(find "$HOME/.orca-remote" -name "*.log*" -type f 2>/dev/null); do echo "=== $f"; tail -n 400 "$f"; done' > (Join-Path $out "$d-orcad-logs.txt") 2>&1
|
|
}
|
|
wsl.exe --list --verbose
|
|
- name: Summary
|
|
if: always()
|
|
run: |
|
|
$dir = $env:ORCA_WSL_ORCAD_LANE_RECEIPTS
|
|
$lines = @("### WSL orcad lane — $env:ImageOS $env:ImageVersion", '')
|
|
foreach ($phase in 'launch', 'survive') {
|
|
$file = Join-Path $dir "$phase.json"
|
|
if (Test-Path $file) { $lines += "#### $phase", '```json', (Get-Content $file -Raw), '```' }
|
|
}
|
|
$lines | Add-Content $env:GITHUB_STEP_SUMMARY
|
|
- uses: actions/upload-artifact@v7
|
|
if: always()
|
|
with:
|
|
name: wsl-orcad-lane-receipts
|
|
path: test-results/wsl-orcad-lane/
|
|
retention-days: 7
|
|
if-no-files-found: ignore
|