From 627188b4b72dac8588dc96abf226aed9b0cc3674 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Tue, 16 Jun 2026 06:59:40 +0000 Subject: [PATCH] =?UTF-8?q?ci(windows):=20setup-windows-runner.ps1=20?= =?UTF-8?q?=E2=80=94=20Gitea=20Actions=20host=20runner=20provisioner?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows analogue of scripts/ci/setup-macos-runner.sh: downloads act_runner (gitea-runner) in host mode, bumps Node 20 via nvm4w (actions/checkout@v4), registers against git.unom.io with labels windows-amd64:host, and installs a SYSTEM scheduled task that keeps the daemon alive across reboots. The daemon's env wrapper hard-codes this box's MSVC/WinUI toolchain (cargo/rustup, NASM, CMake, LLVM, FFmpeg, the ASCII CARGO_HOME SDL3's PCH needs) so the Windows workflow inherits a working toolchain. Idempotent; token (from org unom -> Settings -> Actions -> Runners) not persisted. Co-Authored-By: Claude Opus 4.8 --- scripts/ci/setup-windows-runner.ps1 | 107 ++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 scripts/ci/setup-windows-runner.ps1 diff --git a/scripts/ci/setup-windows-runner.ps1 b/scripts/ci/setup-windows-runner.ps1 new file mode 100644 index 0000000..c15b08e --- /dev/null +++ b/scripts/ci/setup-windows-runner.ps1 @@ -0,0 +1,107 @@ +# Provision this Windows box as the Gitea Actions runner for the Windows client CI/packaging. +# The Windows analogue of scripts/ci/setup-macos-runner.sh. Idempotent — safe to re-run. Run +# ELEVATED (admin) on the box, e.g. over SSH: +# +# ssh "" 'powershell -NoProfile -ExecutionPolicy Bypass -File C:\path\setup-windows-runner.ps1 -Token ' +# +# Installs: the act_runner (gitea-runner) binary in **host mode** (jobs run directly on Windows, +# no containers — MSVC/WinUI builds need the host toolchain), Node 20 via the box's nvm4w (JS +# actions like actions/checkout run via node on PATH), and a SYSTEM scheduled task that keeps the +# daemon alive across reboots with nobody logged in. Registration happens once (.runner file); the +# token is NOT persisted. +# +# Get the token from Gitea: org **unom** -> Settings -> Actions -> Runners -> "Create new runner". +# +# Env/param knobs: -Instance (default https://git.unom.io), -Token (GITEA_RUNNER_TOKEN; required +# for first registration), -RunnerName (default COMPUTERNAME), -Labels (default windows-amd64:host +# — match the Windows job's runs-on), -Version (act_runner, default 1.0.8). +# +# The daemon's env wrapper hard-codes this box's MSVC build paths (cargo/rustup, NASM, CMake, LLVM, +# FFmpeg, the ASCII CARGO_HOME that SDL3's PCH needs) so the Windows workflow inherits a working +# toolchain without re-deriving dev-box specifics. Per-checkout vars (CARGO_WORKSPACE_DIR for the +# windows-reactor build.rs) are set by the workflow, not here. +param( + [string]$Instance = $(if ($env:GITEA_INSTANCE) { $env:GITEA_INSTANCE } else { "https://git.unom.io" }), + [string]$Version = $(if ($env:ACT_RUNNER_VERSION) { $env:ACT_RUNNER_VERSION } else { "1.0.8" }), + [string]$RunnerName = $(if ($env:RUNNER_NAME) { $env:RUNNER_NAME } else { $env:COMPUTERNAME }), + [string]$Labels = $(if ($env:RUNNER_LABELS) { $env:RUNNER_LABELS } else { "windows-amd64:host" }), + [string]$Token = $env:GITEA_RUNNER_TOKEN +) +$ErrorActionPreference = "Stop" +$RunnerHome = "C:\Users\Public\act-runner" +$Exe = "$RunnerHome\act_runner.exe" +New-Item -ItemType Directory -Force -Path $RunnerHome | Out-Null + +# --- act_runner binary (gitea-runner; CLI surface unchanged from act_runner) --- +$need = $true +if (Test-Path $Exe) { try { $need = -not ((& $Exe --version 2>$null) -match [regex]::Escape($Version)) } catch { } } +if ($need) { + $url = "https://dl.gitea.com/gitea-runner/$Version/gitea-runner-$Version-windows-amd64.exe" + Write-Host "==> downloading act_runner $Version" + Invoke-WebRequest -Uri $url -OutFile "$Exe.tmp" -UseBasicParsing + Move-Item -Force "$Exe.tmp" $Exe +} +& $Exe --version + +# --- Node 20 (actions/checkout@v4 demands node20) via the box's nvm4w --- +if (Get-Command nvm -ErrorAction SilentlyContinue) { + if (-not ((node --version 2>$null) -match "^v20")) { + nvm install 20.18.0 | Out-Null + nvm use 20.18.0 | Out-Null + } +} +Write-Host "node $(node --version)" + +# --- config + host-mode labels (empty the docker defaults so .runner's labels rule) --- +Push-Location $RunnerHome +if (-not (Test-Path config.yaml)) { & $Exe generate-config | Set-Content -Encoding ASCII config.yaml } +(Get-Content config.yaml) | + Where-Object { $_ -notmatch "docker.gitea.com/runner-images" } | + ForEach-Object { $_ -replace '^(\s*)labels:\s*$', '${1}labels: []' } | + Set-Content -Encoding ASCII config.yaml +Pop-Location + +# --- one-time registration --- +if (-not (Test-Path "$RunnerHome\.runner")) { + if (-not $Token) { + Write-Warning "Not registered yet. Re-run with -Token ." + Write-Host " (Gitea: org unom -> Settings -> Actions -> Runners -> Create new runner)" + exit 1 + } + & $Exe register --no-interactive --instance $Instance --token $Token --name $RunnerName --labels $Labels +} + +# --- daemon env wrapper (the box's MSVC/WinUI/FFmpeg toolchain) --- +$wrapper = "$RunnerHome\run-runner.ps1" +@' +$env:CARGO_HOME = "C:\Users\Public\.cargo" +$env:RUSTUP_HOME = "C:\Users\Enrico Bühler\.rustup" +$env:CMAKE_POLICY_VERSION_MINIMUM = "3.5" +$env:LIBCLANG_PATH = "C:\Program Files\LLVM\bin" +$env:FFMPEG_DIR = "C:\Users\Public\ffmpeg" +$env:PATH = "C:\Users\Public\.cargo\bin;C:\nvm4w\nodejs;C:\Program Files\NASM;C:\Program Files\CMake\bin;C:\Program Files\LLVM\bin;C:\Users\Public\ffmpeg\bin;" + $env:PATH +Set-Location "C:\Users\Public\act-runner" +& "C:\Users\Public\act-runner\act_runner.exe" daemon --config config.yaml +'@ | Set-Content -Encoding UTF8 $wrapper + +# --- SYSTEM scheduled task: keep the daemon alive across reboots, no login needed --- +$taskName = "gitea-act-runner" +if (schtasks /Query /TN $taskName 2>$null) { + schtasks /End /TN $taskName 2>$null | Out-Null + schtasks /Delete /TN $taskName /F | Out-Null +} +$action = New-ScheduledTaskAction -Execute "powershell.exe" ` + -Argument "-NoProfile -ExecutionPolicy Bypass -File `"$wrapper`"" +$trigger = New-ScheduledTaskTrigger -AtStartup +$principal = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries ` + -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -ExecutionTimeLimit ([TimeSpan]::Zero) +Register-ScheduledTask -TaskName $taskName -Action $action -Trigger $trigger ` + -Principal $principal -Settings $settings -Force | Out-Null +Start-ScheduledTask -TaskName $taskName +Start-Sleep -Seconds 4 + +Write-Host "==> runner '$RunnerName' labels=$Labels instance=$Instance" +$p = Get-Process act_runner -ErrorAction SilentlyContinue +if ($p) { Write-Host "daemon running (pid $($p.Id), session $($p.SessionId))" } +else { Write-Warning "daemon not running yet — check $RunnerHome\runner task / logs" }