ci: parallelize GitHub releases and fix WSL path resolution

This commit is contained in:
lda committed 2026-10-13 03:51:55 +07:00 Verified
1 parent f6f419277c
commit eb55554806
6 files changed
+288 -59

No files matched your search

+131 -48
View File
@@ -8,20 +8,64 @@ on:
workflow_dispatch:
permissions:
contents: write
contents: read
env:
SQLX_OFFLINE: "true"
RELEASE_VERSION: ${{ github.ref_name }}
jobs:
build:
runs-on: ubuntu-latest
router:
name: Router (ARMv7 musl)
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@v4
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: actions/cache@v4
with:
targets: x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu
path: |
~/.cargo/registry
~/.cargo/git
target
key: release-router-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: release-router-${{ runner.os }}-${{ runner.arch }}-
- name: Cache cross executable
id: cross
uses: actions/cache@v4
with:
path: ~/.cargo/bin/cross
key: cross-${{ runner.os }}-${{ runner.arch }}-0.2.5
- name: Install cross
if: steps.cross.outputs.cache-hit != 'true'
run: cargo install cross --version 0.2.5 --locked
- name: Build router binaries
run: cross build --release --locked --target armv7-unknown-linux-musleabihf -p wakey -p wakey-agent
- name: Package rootfs tarball
shell: pwsh
run: ./scripts/package_rootfs.ps1 -Version $env:RELEASE_VERSION -Target armv7-unknown-linux-musleabihf
- name: Upload rootfs
uses: actions/upload-artifact@v4
with:
name: wakey-rootfs
path: dist/wakey-rootfs-*armv7-unknown-linux-musleabihf.tgz
if-no-files-found: error
compression-level: 0
ui:
name: UI
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
@@ -33,8 +77,36 @@ jobs:
cache: pnpm
cache-dependency-path: ui/pnpm-lock.yaml
- name: Install cross
run: cargo install cross --locked
- name: Build UI dist
working-directory: ui
run: |
pnpm install --frozen-lockfile
pnpm build
- name: Upload UI
uses: actions/upload-artifact@v4
with:
name: wakey-ui
path: ui/dist/
if-no-files-found: error
cc:
name: CC (${{ matrix.target }})
strategy:
fail-fast: false
matrix:
include:
- target: x86_64-unknown-linux-gnu
runner: ubuntu-24.04
- target: aarch64-unknown-linux-gnu
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- name: Cache cargo
uses: actions/cache@v4
@@ -43,57 +115,67 @@ jobs:
~/.cargo/registry
~/.cargo/git
target
key: linux-cargo-${{ hashFiles('**/Cargo.lock') }}
key: release-cc-${{ matrix.target }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: release-cc-${{ matrix.target }}-
- name: Build (armv7-musl with cross)
run: |
cross build --release --target armv7-unknown-linux-musleabihf -p wakey -p wakey-agent
# Native GNU builds use the runner's glibc. Gitea remains the separate
# older-glibc release path for deployment hosts that need it.
- name: Build control-plane natively
run: cargo build --release --locked --target ${{ matrix.target }} -p wakey-control-plane
- name: Build UI dist
shell: bash
run: |
set -euo pipefail
cd ui
pnpm install --frozen-lockfile
pnpm build
- name: Build control-plane (linux-gnu)
shell: bash
run: |
set -euo pipefail
# x86_64 builds natively; aarch64 needs cross
echo "Building control-plane x86_64-unknown-linux-gnu (native)"
cargo build --release --target x86_64-unknown-linux-gnu -p wakey-control-plane
echo "Building control-plane aarch64-unknown-linux-gnu (cross)"
cross build --release --target aarch64-unknown-linux-gnu -p wakey-control-plane
- name: Package rootfs tarball
shell: pwsh
run: |
./scripts/package_rootfs.ps1 -Version "${{ github.ref_name }}" -Target armv7-unknown-linux-musleabihf
- name: Package control-plane bundles
shell: bash
run: |
set -euo pipefail
chmod +x scripts/update_wakey_cc.sh scripts/package_wakey_cc_bundle.sh
for target in x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do
./scripts/package_wakey_cc_bundle.sh --version "${{ github.ref_name }}" --target "$target"
done
- name: Upload artifact
- name: Upload control-plane binary
uses: actions/upload-artifact@v4
with:
name: wakey-cc-bin-${{ matrix.target }}
path: target/${{ matrix.target }}/release/wakey-control-plane
if-no-files-found: error
release:
name: Package and publish
needs: [router, ui, cc]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@v4
- name: Download rootfs
uses: actions/download-artifact@v4
with:
name: wakey-rootfs
path: dist/wakey-rootfs-*armv7-unknown-linux-musleabihf.tgz
path: dist
- name: Download UI
uses: actions/download-artifact@v4
with:
name: wakey-ui
path: ui/dist
- name: Download x86-64 CC
uses: actions/download-artifact@v4
with:
name: wakey-cc-bin-x86_64-unknown-linux-gnu
path: target/x86_64-unknown-linux-gnu/release
- name: Download ARM64 CC
uses: actions/download-artifact@v4
with:
name: wakey-cc-bin-aarch64-unknown-linux-gnu
path: target/aarch64-unknown-linux-gnu/release
- name: Package control-plane bundles
run: |
for target in x86_64-unknown-linux-gnu aarch64-unknown-linux-gnu; do
sh ./scripts/package_wakey_cc_bundle.sh --version "$RELEASE_VERSION" --target "$target"
done
- name: Upload control-plane bundles
uses: actions/upload-artifact@v4
with:
name: wakey-cc-bundles
path: dist/wakey-cc-*unknown-linux-gnu.tgz
if-no-files-found: error
compression-level: 0
- name: Publish Release to GitHub
if: startsWith(github.ref, 'refs/tags/')
@@ -101,6 +183,7 @@ jobs:
with:
token: ${{ secrets.GITHUB_TOKEN }}
tag_name: ${{ github.ref_name }}
fail_on_unmatched_files: true
files: |
dist/wakey-rootfs-*armv7-unknown-linux-musleabihf.tgz
dist/wakey-cc-*unknown-linux-gnu.tgz
+8
View File
@@ -144,6 +144,14 @@ Implementation notes:
## CI (GitHub)
- `.github/workflows/release.yml` builds router binaries, UI, x86-64 CC and
ARM64 CC in parallel. CC uses matching Ubuntu 24.04 runners and native Cargo;
Cross is used only for the ARMv7-musl router binaries. A final job downloads
their artifacts, packages CC bundles and publishes after every build succeeds.
- GitHub's native GNU binaries inherit its runner's glibc requirements. Use the
Gitea bundles for older deployment hosts, checking that its build actually
used Zig's configured baseline. Gitea retains one job for `--once` runners.
- Defined in `.github/workflows/ci.yml` on pushes/PRs to `main` or `master`:
- **`rust` job:** `cargo fmt --check`, `clippy -D warnings`, `cargo test --workspace` on `ubuntu-latest`.
- **`ui` job:** under `ui/`, `pnpm install --frozen-lockfile`, then `pnpm run typecheck`, `format:check`, and `build` (mirrors what you need before packaging the control-plane bundle). GitHub sets `CI=true` for the runner; pnpm uses lockfile `ui/pnpm-lock.yaml` for cache.
+6
View File
@@ -67,6 +67,12 @@ From Windows PowerShell, using the WSL wrapper:
Path control (binary and config are independent):
When called from PowerShell inside WSL, absolute POSIX paths pass through
unchanged. Windows paths are converted using the selected distro's `wslpath`.
Relative runner home, binary and config paths resolve from the current PowerShell
directory. Conversion also supports paths that have not been created yet.
The helper, runner home and config paths must be accessible in that distro.
```powershell
./scripts/act_runner_wsl.ps1 -Action status `
-RunnerBin /home/lda/gitea-runner/act_runner `
+8 -10
View File
@@ -2,7 +2,7 @@
param(
[ValidateSet("update", "register", "start", "stop", "status")]
[string]$Action = "start",
[string]$Distro = $(if ($env:WAKEY_WSL_DISTRO) { $env:WAKEY_WSL_DISTRO } else { "FedoraLinux-43" }),
[string]$Distro = $( $env:WAKEY_WSL_DISTRO ?? "FedoraLinux-43" ),
[string]$RunnerHome,
[string]$RunnerBin = "/home/lda/gitea-runner/act_runner",
[string]$ConfigPath,
@@ -22,7 +22,7 @@ if (-not (Test-Path $scriptWin)) {
throw "Missing Fedora runner helper: $scriptWin"
}
$scriptLinux = (& wsl.exe -d $Distro -e wslpath -a "$scriptWin").Trim()
$scriptLinux = ConvertTo-WslPath -WindowsPath $scriptWin -Distro $Distro
if (-not $scriptLinux) {
throw "Failed to resolve WSL path for $scriptWin"
}
@@ -30,21 +30,19 @@ if (-not $scriptLinux) {
if (-not $RunnerHome) {
$repoRootWin = Split-Path -Parent $PSScriptRoot
$defaultRunnerHomeWin = Join-Path $repoRootWin "ar_data/wsl_runner"
$RunnerHome = (& wsl.exe -d $Distro -e wslpath -a "$defaultRunnerHomeWin").Trim()
if (-not $RunnerHome) {
throw "Failed to resolve WSL runner home for $defaultRunnerHomeWin"
}
$RunnerHome = $defaultRunnerHomeWin
}
if (-not $ConfigPath) {
$repoRootWin = Split-Path -Parent $PSScriptRoot
$defaultConfigWin = Join-Path $repoRootWin "ar_data/wsl_runner/config.yaml"
$ConfigPath = (& wsl.exe -d $Distro -e wslpath -a "$defaultConfigWin").Trim()
if (-not $ConfigPath) {
throw "Failed to resolve WSL config path for $defaultConfigWin"
}
$ConfigPath = $defaultConfigWin
}
$RunnerHome = ConvertTo-WslPath -Path $RunnerHome -Distro $Distro
$RunnerBin = ConvertTo-WslPath -Path $RunnerBin -Distro $Distro
$ConfigPath = ConvertTo-WslPath -Path $ConfigPath -Distro $Distro
$parts = @("bash", (Quote-ShArg $scriptLinux), (Quote-ShArg $Action), "--runner-home", (Quote-ShArg $RunnerHome))
$parts += @("--runner-bin", (Quote-ShArg $RunnerBin), "--config-path", (Quote-ShArg $ConfigPath))
if ($ServerUrl) { $parts += @("--server-url", (Quote-ShArg $ServerUrl)) }
+38 -1
View File
@@ -27,8 +27,15 @@ function Test-IsWsl {
return $false
}
# With -Distro, resolve local relative paths from the PowerShell working
# directory and convert Windows paths with the distro's mount configuration.
# Without it, keep the existing mapping used by SCP callers, including remote destinations.
function ConvertTo-WslPath {
param([string]$WindowsPath)
param(
[Alias('Path')]
[string]$WindowsPath,
[string]$Distro
)
if ([string]::IsNullOrWhiteSpace($WindowsPath)) {
return $WindowsPath
@@ -39,6 +46,36 @@ function ConvertTo-WslPath {
return $WindowsPath
}
# wslpath expects a Windows input here. Use the selected distro's mount
# configuration when requested, while keeping existing POSIX paths intact.
if ($Distro) {
# Absolute Windows paths can be supplied even from Linux PowerShell.
# Relative inputs use the filesystem provider without requiring a file
# to exist, which is important for a new runner home/config.
if ($WindowsPath -notmatch '^[A-Za-z]:[\\/]|^\\\\') {
$provider = $null
$drive = $null
$WindowsPath = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath(
$WindowsPath, [ref]$provider, [ref]$drive)
if ($provider.Name -ne 'FileSystem') {
throw "WSL paths must use the filesystem provider"
}
if ($WindowsPath.StartsWith('/')) {
return $WindowsPath
}
}
$output = & wsl.exe -d $Distro -e wslpath -a "$WindowsPath"
$exitCode = $LASTEXITCODE
if ($exitCode -ne 0) {
throw "wslpath failed for '$WindowsPath' in '$Distro' (exit $exitCode)"
}
$converted = ($output -join "`n").Trim()
if (-not $converted) {
throw "wslpath returned no path for '$WindowsPath' in '$Distro'"
}
return $converted
}
# Handle Windows path (e.g., C:\path\to\file -> /mnt/c/path/to/file)
if ($WindowsPath -match '^([A-Z]):(.*)$') {
$drive = $matches[1].ToLower()
+97
View File
@@ -0,0 +1,97 @@
#!/usr/bin/env pwsh
# Run with pwsh -NoProfile -File scripts/tests/wsl_paths.ps1.
$ErrorActionPreference = 'Stop'
. "$PSScriptRoot/../lib.ps1"
# Intercept WSL so these tests never start or register a real runner.
$global:WakeyWslTest = @{
Calls = [System.Collections.Generic.List[object]]::new()
ExitCode = 0
# Fake conversion response, chosen to exercise a path containing spaces.
Output = '/mnt/c/runner home/config.yaml'
}
function global:wsl.exe {
$global:WakeyWslTest.Calls.Add(@($args))
$global:LASTEXITCODE = $global:WakeyWslTest.ExitCode
if ($args[3] -eq 'wslpath') { $global:WakeyWslTest.Output }
}
foreach ($path in @('/home/lda/repo/scripts/helper.sh', '/mnt/c/runner home/config.yaml')) {
$result = ConvertTo-WslPath -WindowsPath $path -Distro TestDistro
if ($result -cne $path) { throw "POSIX path changed: $result" }
}
if ($global:WakeyWslTest.Calls.Count -ne 0) { throw 'POSIX paths invoked wslpath' }
foreach ($path in @('C:\runner home\config.yaml', 'C:/runner home/config.yaml', '\\server\share\runner home\config.yaml')) {
$global:WakeyWslTest.Calls.Clear()
$result = ConvertTo-WslPath -WindowsPath $path -Distro TestDistro
if ($result -cne '/mnt/c/runner home/config.yaml') { throw 'Mock Windows conversion failed' }
$call = $global:WakeyWslTest.Calls[0]
if (($call[0..4] -join ' ') -cne '-d TestDistro -e wslpath -a' -or $call[5] -cne $path) {
throw 'Distro or Windows path was not passed intact'
}
}
if ((ConvertTo-WslPath 'D:\runner\config.yaml') -cne '/mnt/d/runner/config.yaml') {
throw 'Existing drive conversion changed'
}
foreach ($path in @('root@router:/root/.bin/', 'relative/file')) {
if ((ConvertTo-WslPath $path) -cne $path) { throw 'Existing SCP path handling changed' }
}
# Resolve missing relative paths from the PS location, including dot segments.
$temp = Join-Path ([System.IO.Path]::GetTempPath()) "wakey-wsl-paths-$([guid]::NewGuid())"
New-Item -ItemType Directory -Path $temp | Out-Null
Push-Location $temp
try {
foreach ($relative in @('new folder/config.yaml', '.\new folder\config.yaml', 'nested/../new folder/config.yaml')) {
$expected = Join-Path $temp 'new folder/config.yaml'
$global:WakeyWslTest.Calls.Clear()
$result = ConvertTo-WslPath -Path $relative -Distro TestDistro
if ($IsWindows) {
if ($global:WakeyWslTest.Calls[0][5] -cne $expected) { throw 'Relative Windows path was not resolved before conversion' }
} elseif ($result -cne $expected -or $global:WakeyWslTest.Calls.Count -ne 0) {
throw "Relative POSIX path was not resolved: $result"
}
}
} finally {
Pop-Location
Remove-Item -Recurse -Force $temp
}
foreach ($case in @(
@{ ExitCode = 1; Output = '' },
@{ ExitCode = 1; Output = '/mnt/c/bad' },
@{ ExitCode = 0; Output = '' }
)) {
$global:WakeyWslTest.ExitCode = $case.ExitCode
$global:WakeyWslTest.Output = $case.Output
$caught = $false
try { ConvertTo-WslPath -WindowsPath 'C:\bad' -Distro TestDistro | Out-Null }
catch { $caught = $_.Exception.Message -like 'wslpath *' }
if (-not $caught) { throw 'Failed/empty wslpath result was accepted' }
}
if (-not $IsWindows) {
$global:WakeyWslTest.Calls.Clear()
$global:WakeyWslTest.ExitCode = 0
& "$PSScriptRoot/../act_runner_wsl.ps1" -Action status -Distro TestDistro
if ($global:WakeyWslTest.Calls.Count -ne 1 -or $global:WakeyWslTest.Calls[0][3] -ne 'bash') {
throw 'POSIX wrapper paths should invoke only the runner command'
}
$command = $global:WakeyWslTest.Calls[0][5]
if ($command -notlike '*act_runner_fedora.sh*' -or $command -notlike '*ar_data/wsl_runner/config.yaml*') {
throw 'Wrapper did not resolve default paths'
}
$global:WakeyWslTest.Calls.Clear()
$global:WakeyWslTest.Output = '/mnt/c/runner home/config.yaml'
& "$PSScriptRoot/../act_runner_wsl.ps1" -Action status -Distro TestDistro `
-RunnerHome 'new runner home' -RunnerBin 'bin/act_runner' -ConfigPath 'C:\runner home\config.yaml'
if ($global:WakeyWslTest.Calls.Count -ne 2 -or $global:WakeyWslTest.Calls[0][3] -ne 'wslpath') {
throw 'Explicit Windows config should be converted once before running bash'
}
$command = $global:WakeyWslTest.Calls[1][5]
foreach ($path in @((Join-Path (Get-Location).Path 'new runner home'), (Join-Path (Get-Location).Path 'bin/act_runner'), '/mnt/c/runner home/config.yaml')) {
if (-not $command.Contains((Quote-ShArg $path))) { throw "Explicit path missing from runner command: $path" }
}
}
Write-Host 'WSL path tests passed'