# Windows one-click app, built on GitHub's Windows runners so no PC is needed (4 October 2026). # # parse: every .ps1 under the Windows folders through the Windows PowerShell 5.1 parser (powershell.exe, the PowerShell # on the PCs; 5.1 rejects "$name: text" and that class broke two launchers on 4 October), PSScriptAnalyzer as # warnings, and a parenthesis check of every .bat/.cmd (the bare ")" class of 3 October). Required: build # needs it. # build: the engine (app/igneum-app, cargo on the MSVC target, so one fewer input), the window host exactly as # app\windows\BUILD-APP.bat does it (MSVC, WebView2 SDK from NuGet, static loader, host.rc with the coin icon), # the payload with packaging/windows/make-payload.sh in Git Bash, the installer with build-installer.ps1 # (Inno Setup, rcedit), a smoke run of both exes (--version, --help), the launcher's DRY_RUN, then the installer, # the payload zip and the host as artifacts (90 days). # # Inputs that are not in git (igneumd.exe, igneum-miner.exe from the node fork; the prebuilt GPU workers with NVIDIA's # NVRTC DLLs) come from payload-inputs.zip on the downloads host, published by packaging/windows/push-inputs.sh on the # Mac; the DL_TOKEN repository secret is the path token (gh secret set DL_TOKEN < ~/.config/igneum/dl-token). # The Mac side of the loop is packaging/windows/fetch-ci-artifacts.sh (gh run download into the downloads folder). name: windows-ci on: push: branches: [master] paths: - 'app/**' - 'packaging/windows/**' - 'packaging/mac/packaged-config.sh' - 'proto-cuda/windows-app/**' - 'proto-cuda/windows-miner/**' - 'proto-cuda/windows-node/**' - 'proto-cuda/nvrtc/**' - 'proto-cuda/build.bat' - 'proto-opencl/**' - 'proving/windows-wsl2/**' - 'relay/clients/**' - 'brand/icons/**' - 'tools/ci/windows/**' - '.github/workflows/windows.yml' workflow_dispatch: concurrency: group: windows-${{ github.ref }} cancel-in-progress: true jobs: parse: name: PowerShell 5.1 parse, PSScriptAnalyzer, batch parentheses runs-on: windows-latest timeout-minutes: 15 steps: - uses: actions/checkout@v4 - name: Windows PowerShell 5.1 parse of every .ps1 (with the negative self-test) shell: powershell run: | $PSVersionTable.PSVersion.ToString() & powershell.exe -NoProfile -ExecutionPolicy Bypass -File tools\ci\windows\check-ps51.ps1 if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: parentheses in every .bat and .cmd (with the negative self-test) shell: powershell run: | & powershell.exe -NoProfile -ExecutionPolicy Bypass -File tools\ci\windows\check-bat.ps1 if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } - name: PSScriptAnalyzer (warnings only, never fails the job) shell: powershell continue-on-error: true run: | try { if (-not (Get-Module -ListAvailable PSScriptAnalyzer)) { Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force -Scope CurrentUser | Out-Null Set-PSRepository -Name PSGallery -InstallationPolicy Trusted Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser -AllowClobber } Import-Module PSScriptAnalyzer $folders = @('proto-cuda/windows-app', 'proto-cuda/windows-miner', 'proto-cuda/windows-node', 'proving/windows-wsl2', 'relay/clients', 'packaging/windows', 'tools/ci/windows') $total = 0 foreach ($f in $folders) { $results = Invoke-ScriptAnalyzer -Path $f -Recurse -Severity Warning, Error -ExcludeRule PSAvoidUsingWriteHost, PSUseShouldProcessForStateChangingFunctions, PSUseSingularNouns, PSAvoidUsingPositionalParameters foreach ($r in $results) { $total += 1 $file = ($r.ScriptPath -replace '\\', '/') Write-Host ("::warning file={0},line={1}::{2}: {3}" -f $file, $r.Line, $r.RuleName, $r.Message) } } Write-Host "PSScriptAnalyzer: $total warnings (informational)" } catch { Write-Host "::warning::PSScriptAnalyzer could not run: $_" } build: name: engine, window host, payload, installer, smoke run needs: parse runs-on: windows-latest timeout-minutes: 60 steps: - uses: actions/checkout@v4 - name: versions shell: bash run: | set -euo pipefail v="$(sed -n 's/^version = "\(.*\)"/\1/p' app/igneum-app/Cargo.toml | head -1)" echo "APP_VERSION=$v" >> "$GITHUB_ENV" echo "app version $v" rustc --version; cargo --version git --version; bash --version | head -1; perl --version | sed -n 2p; 7z 2>/dev/null | head -2 | tail -1 || true - name: engine (app/igneum-app, cargo build --release on the MSVC target) shell: bash working-directory: app/igneum-app run: | set -euo pipefail cargo build --release --locked ls -la target/release/igneum-app.exe - name: payload inputs (payload-inputs.zip from the downloads host, sha256 checked) shell: bash env: DL_TOKEN: ${{ secrets.DL_TOKEN }} run: | set -euo pipefail if [ -z "${DL_TOKEN:-}" ]; then echo "::error::the DL_TOKEN repository secret is not set. On the Mac: tr -d '[:space:]' < ~/.config/igneum/dl-token | gh secret set DL_TOKEN --repo igneum-network/igneum" exit 1 fi base="https://dl.igneum.network/dl/$DL_TOKEN" mkdir -p build/inputs "$HOME/.config/igneum" printf '%s' "$DL_TOKEN" > "$HOME/.config/igneum/dl-token" # make-payload.sh reads it for the update manifest URL curl -fsSL --retry 3 -o build/payload-inputs.json "$base/payload-inputs.json" curl -fsSL --retry 3 -o build/payload-inputs.sha256 "$base/payload-inputs.sha256" curl -fsSL --retry 3 -o build/payload-inputs.zip "$base/payload-inputs.zip" echo "$(tr -d '[:space:]' < build/payload-inputs.sha256) build/payload-inputs.zip" | sha256sum -c - 7z x -y -bso0 -bsp0 -obuild/inputs-unpacked build/payload-inputs.zip mv build/inputs-unpacked/payload-inputs/* build/inputs/ echo "inputs manifest:"; cat build/payload-inputs.json echo "inputs:"; ls -la build/inputs for f in igneumd.exe igneum-miner.exe; do [ -f "build/inputs/$f" ] || { echo "::error::payload-inputs.zip has no $f"; exit 1; }; done - name: window host (app\windows\BUILD-APP.bat, exactly as on the PC) shell: cmd working-directory: app\windows run: call BUILD-APP.bat < nul - name: payload (packaging/windows/make-payload.sh, as on the Mac, in Git Bash) shell: bash run: | set -euo pipefail export IGNEUM_WIN_RELEASE="$PWD/build/inputs" export IGNEUM_WORKERS_DIR="$PWD/build/inputs" export IGNEUM_APP_EXE="$PWD/app/igneum-app/target/release/igneum-app.exe" packaging/windows/make-payload.sh "$PWD/packaging/windows/dist/igneum-windows-app.zip" test -f "packaging/windows/igneum-windows-app/Igneum Miner.exe" || { echo "::error::the window host did not land in the payload"; exit 1; } - name: installer (packaging/windows/build-installer.ps1 in Windows PowerShell 5.1, Inno Setup, rcedit) shell: powershell working-directory: packaging\windows run: | $iscc = "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe" if (-not (Test-Path $iscc)) { choco install innosetup -y --no-progress | Out-Null } & powershell.exe -NoProfile -ExecutionPolicy Bypass -File .\build-installer.ps1 -NoWinget -Version $env:APP_VERSION if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } Get-ChildItem dist | Format-Table Name, Length - name: smoke run (igneum-app.exe --version, Igneum Miner.exe --version and --help) shell: powershell run: | $payload = Resolve-Path 'packaging\windows\igneum-windows-app' function Run-Capture([string]$exe, [string]$flag) { $out = Join-Path $env:RUNNER_TEMP ('smoke-' + [IO.Path]::GetRandomFileName() + '.txt') $p = Start-Process -FilePath $exe -ArgumentList $flag -Wait -NoNewWindow -PassThru -RedirectStandardOutput $out $text = if (Test-Path $out) { (Get-Content $out -Raw) } else { '' } Write-Host ("{0} {1} -> exit {2}: {3}" -f (Split-Path -Leaf $exe), $flag, $p.ExitCode, $text.Trim()) if ($p.ExitCode -ne 0) { throw "$exe $flag exited $($p.ExitCode)" } return $text } $v = $env:APP_VERSION $a = Run-Capture (Join-Path $payload 'igneum-app.exe') '--version' if ($a -notmatch "igneum-app $([regex]::Escape($v))") { throw "igneum-app --version did not print 'igneum-app $v'" } $b = Run-Capture (Join-Path $payload 'Igneum Miner.exe') '--version' if ($b -notmatch "Igneum Miner $([regex]::Escape($v))") { throw "Igneum Miner.exe --version did not print 'Igneum Miner $v' (version.h and Cargo.toml differ?)" } $c = Run-Capture (Join-Path $payload 'Igneum Miner.exe') '--help' if ($c -notmatch 'Usage') { throw 'Igneum Miner.exe --help did not print the usage line' } $info = (Get-Item (Join-Path $payload 'Igneum Miner.exe')).VersionInfo Write-Host ("host version block: {0} {1} {2}" -f $info.ProductName, $info.ProductVersion, $info.FileDescription) if ($info.ProductName -ne 'Igneum Miner') { throw 'the host exe carries no Igneum Miner version block (host.rc)' } - name: launcher dry run (proto-cuda/windows-app, DRY_RUN=1, Windows PowerShell 5.1 via the .ps1 and the .bat) shell: powershell run: | $stage = Join-Path $env:RUNNER_TEMP 'launcher' New-Item -ItemType Directory -Force -Path $stage | Out-Null Copy-Item -Path 'proto-cuda\windows-app\*' -Destination $stage -Recurse -Force foreach ($f in @('igneumd.exe', 'igneum-miner.exe', 'igneum-worker-cuda.exe', 'igneum-worker-opencl.exe')) { if (Test-Path "build\inputs\$f") { Copy-Item "build\inputs\$f" $stage } } Get-ChildItem 'build\inputs' -Filter 'nvrtc*.dll' | Copy-Item -Destination $stage $env:DRY_RUN = '1' & powershell.exe -NoProfile -ExecutionPolicy Bypass -File (Join-Path $stage 'start-igneum.ps1') if ($LASTEXITCODE -ne 0) { throw "start-igneum.ps1 DRY_RUN=1 exited $LASTEXITCODE" } $bat = cmd /c "cd /d `"$stage`" && START-IGNEUM.bat < nul 2>&1" | Out-String Write-Host $bat if ($bat -notmatch 'dry run done') { throw 'START-IGNEUM.bat with DRY_RUN=1 did not reach the end of the plan' } - name: sizes shell: bash run: | set -euo pipefail { echo "## Windows build $APP_VERSION" echo echo "| file | bytes |" echo "|---|---:|" for f in packaging/windows/dist/Igneum-Miner-Setup-*.exe packaging/windows/dist/igneum-windows-app.zip "app/windows/dist/Igneum Miner.exe" app/igneum-app/target/release/igneum-app.exe; do printf '| %s | %s |\n' "$(basename "$f")" "$(stat -c %s "$f")" done echo echo "inputs: $(tr -d '\n' < build/payload-inputs.json | head -c 400)" } | tee -a "$GITHUB_STEP_SUMMARY" - uses: actions/upload-artifact@v4 with: name: igneum-windows-installer path: packaging/windows/dist/Igneum-Miner-Setup-*.exe retention-days: 90 if-no-files-found: error - uses: actions/upload-artifact@v4 with: name: igneum-windows-payload path: packaging/windows/dist/igneum-windows-app.zip retention-days: 90 if-no-files-found: error - uses: actions/upload-artifact@v4 with: name: igneum-windows-host path: app/windows/dist/Igneum Miner.exe retention-days: 90 if-no-files-found: error