mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 17:47:40 +00:00
orcad-remote-*, remote-install-*, remote-node-runtime-store-*, remote-versioned-install and remote-deploy-timing leave src/main/ssh. git mv plus import and path-literal rewrites; one e2e route regex learns the new folder. orcad-remote-deploy and orcad-remote-node-runtime-report stay for now because open PRs #29834 and #29829 edit them.
260 lines
13 KiB
YAML
260 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/managed-server/**'
|
|
- '!src/main/ssh/orcad-*.test.ts'
|
|
- '!src/main/managed-server/**/*.test.ts'
|
|
- 'src/main/managed-server/install/remote-install-transport.ts'
|
|
# orcad-entry runs this on every boot of the orcad the lane launches in the distro.
|
|
- 'src/main/orcad/orcad-wsl-*'
|
|
- '!src/main/orcad/orcad-wsl-*.test.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
|
|
- uses: ./.github/actions/build-mobile-web-bundle
|
|
- name: Build the template
|
|
run: |
|
|
pnpm build:orcad-prebuilds
|
|
pnpm build:orcad-prebuilds --require-slots linux-x64-glibc
|
|
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
|