Windows CI: the one-click app built on GitHub runners, no PC needed
windows.yml: parse job (every .ps1 through the Windows PowerShell 5.1 parser with a negative fixture, PSScriptAnalyzer as warnings, parenthesis check of every .bat/.cmd with a negative fixture), build job (engine on the MSVC target, window host through BUILD-APP.bat as it is, payload through make-payload.sh in Git Bash, installer through build-installer.ps1, smoke run of both exes, launcher DRY_RUN, three artifacts for 90 days). push-inputs.sh publishes payload-inputs.zip (node, miner, workers, NVRTC DLLs) to the downloads host from the Mac; fetch-ci-artifacts.sh pulls the green run's installer and payload back into the downloads folder. Host: --version and --help, version.h shared with host.rc. Launcher: DRY_RUN=1 prints the plan and starts nothing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
5097c0468a
commit
7717c4fdac
13 changed files with 679 additions and 14 deletions
233
.github/workflows/windows.yml
vendored
Normal file
233
.github/workflows/windows.yml
vendored
Normal file
|
|
@ -0,0 +1,233 @@
|
|||
# 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
|
||||
|
|
@ -8,8 +8,12 @@
|
|||
// Untested on a real PC at the time of writing (written on a Mac): BUILD-APP.bat is the first run.
|
||||
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#ifndef UNICODE
|
||||
#define UNICODE
|
||||
#endif
|
||||
#ifndef _UNICODE
|
||||
#define _UNICODE
|
||||
#endif
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
#include <wrl.h>
|
||||
|
|
@ -18,6 +22,7 @@
|
|||
#include <thread>
|
||||
#include <mutex>
|
||||
#include "WebView2.h"
|
||||
#include "version.h"
|
||||
|
||||
using namespace Microsoft::WRL;
|
||||
|
||||
|
|
@ -341,7 +346,40 @@ static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp) {
|
|||
return DefWindowProcW(hwnd, msg, wp, lp);
|
||||
}
|
||||
|
||||
// --version and --help print one line and exit, for the CI smoke run and support scripts. A windows-subsystem exe has
|
||||
// no console of its own: the text goes to the inherited stdout when there is one (a pipe or a file), else to the
|
||||
// parent's console.
|
||||
static bool handleCliFlags() {
|
||||
int argc = 0;
|
||||
LPWSTR* argv = CommandLineToArgvW(GetCommandLineW(), &argc);
|
||||
if (!argv) return false;
|
||||
std::wstring text;
|
||||
for (int i = 1; i < argc; i++) {
|
||||
std::wstring a = argv[i];
|
||||
if (a == L"--version" || a == L"-V") text = L"Igneum Miner " IGNEUM_HOST_VERSION_STR L" (window host)\r\n";
|
||||
else if (a == L"--help" || a == L"-h" || a == L"/?")
|
||||
text = L"Igneum Miner " IGNEUM_HOST_VERSION_STR L" (window host)\r\n"
|
||||
L"Usage: \"Igneum Miner.exe\" [--version | --help]\r\n"
|
||||
L"Without flags it starts igneum-app.exe --wrapper next to it and shows the dashboard in a WebView2 window.\r\n";
|
||||
}
|
||||
LocalFree(argv);
|
||||
if (text.empty()) return false;
|
||||
HANDLE out = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (out == nullptr || out == INVALID_HANDLE_VALUE) {
|
||||
if (AttachConsole(ATTACH_PARENT_PROCESS)) out = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
}
|
||||
if (out && out != INVALID_HANDLE_VALUE) {
|
||||
int n = WideCharToMultiByte(CP_UTF8, 0, text.c_str(), (int)text.size(), nullptr, 0, nullptr, nullptr);
|
||||
std::string utf8(n, 0);
|
||||
WideCharToMultiByte(CP_UTF8, 0, text.c_str(), (int)text.size(), &utf8[0], n, nullptr, nullptr);
|
||||
DWORD written = 0;
|
||||
WriteFile(out, utf8.data(), (DWORD)utf8.size(), &written, nullptr);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
int WINAPI wWinMain(HINSTANCE hInst, HINSTANCE, PWSTR, int) {
|
||||
if (handleCliFlags()) return 0;
|
||||
HANDLE once = CreateMutexW(nullptr, TRUE, L"Local\\IgneumMinerWindow");
|
||||
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
HWND other = FindWindowW(L"IgneumMinerWindow", nullptr);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
// Resources for Igneum Miner.exe (the window host): the coin icon and the version block.
|
||||
// Compiled by BUILD-APP.bat with rc.exe; the icon path is relative to brand\icons (passed with /i).
|
||||
// Compiled by BUILD-APP.bat with rc.exe; the icon path is relative to brand\icons (passed with /i). The version comes
|
||||
// from version.h, shared with host.cpp.
|
||||
#include <winver.h>
|
||||
#include "version.h"
|
||||
|
||||
1 ICON "igneum.ico"
|
||||
|
||||
1 VERSIONINFO
|
||||
FILEVERSION 0,3,0,0
|
||||
PRODUCTVERSION 0,3,0,0
|
||||
FILEVERSION IGNEUM_HOST_VERSION_RC
|
||||
PRODUCTVERSION IGNEUM_HOST_VERSION_RC
|
||||
FILEFLAGSMASK 0x3fL
|
||||
FILEFLAGS 0x0L
|
||||
FILEOS VOS_NT_WINDOWS32
|
||||
|
|
@ -19,12 +21,12 @@ BEGIN
|
|||
BEGIN
|
||||
VALUE "CompanyName", "Igneum"
|
||||
VALUE "FileDescription", "Igneum Miner"
|
||||
VALUE "FileVersion", "0.3.0"
|
||||
VALUE "FileVersion", IGNEUM_HOST_VERSION_STR
|
||||
VALUE "InternalName", "Igneum Miner"
|
||||
VALUE "LegalCopyright", "Igneum. Nothing is bought or sold."
|
||||
VALUE "OriginalFilename", "Igneum Miner.exe"
|
||||
VALUE "ProductName", "Igneum Miner"
|
||||
VALUE "ProductVersion", "0.3.0"
|
||||
VALUE "ProductVersion", IGNEUM_HOST_VERSION_STR
|
||||
END
|
||||
END
|
||||
BLOCK "VarFileInfo"
|
||||
|
|
|
|||
8
app/windows/version.h
Normal file
8
app/windows/version.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
// The version of "Igneum Miner.exe" (the window host). One place for host.rc (the version block Explorer shows) and
|
||||
// host.cpp (--version). Keep it equal to app/igneum-app/Cargo.toml and the AppVersion default in
|
||||
// packaging/windows/Igneum-Miner.iss when the app version moves. Include guards, not #pragma once: rc.exe reads it too.
|
||||
#ifndef IGNEUM_HOST_VERSION_H
|
||||
#define IGNEUM_HOST_VERSION_H
|
||||
#define IGNEUM_HOST_VERSION_STR "0.3.0"
|
||||
#define IGNEUM_HOST_VERSION_RC 0,3,0,0
|
||||
#endif
|
||||
|
|
@ -1,9 +1,70 @@
|
|||
# Igneum Miner for Windows (packaging/windows)
|
||||
|
||||
[](https://github.com/igneum-network/igneum/actions/workflows/windows.yml)
|
||||
|
||||
Inno Setup installer around the app payload. Version 0.3.0 (4 October 2026): the app (engine + window host) instead of
|
||||
the console launchers of 0.1.0.
|
||||
|
||||
## On the Mac
|
||||
## Built on GitHub, not on a PC (4 October 2026)
|
||||
|
||||
`.github/workflows/windows.yml` builds the whole Windows app on a hosted Windows runner on every push to master that
|
||||
touches `app/`, `packaging/windows/`, `proto-cuda/windows-*`, `proto-cuda/nvrtc/`, `proto-opencl/`,
|
||||
`proving/windows-wsl2/` or `relay/clients/` (and on `gh workflow run windows.yml`). Nobody runs `BUILD-APP.bat` or
|
||||
`BUILD-INSTALLER.bat` on a PC any more; both files stay for a hand build and the workflow runs them as they are.
|
||||
|
||||
Two jobs:
|
||||
|
||||
1. `parse` (required): every `.ps1` under the Windows folders through the Windows PowerShell 5.1 parser
|
||||
(`tools/ci/windows/check-ps51.ps1`, run with `powershell.exe`, the PowerShell on the PCs; it refuses to pass unless
|
||||
the fixture `tools/ci/windows/fixtures/bad-drive-ref.ps1.txt`, a `"$x: y"` drive-qualified reference, FAILS, so a
|
||||
green run proves the check bites), PSScriptAnalyzer as warnings, and a parenthesis check of every `.bat` and `.cmd`
|
||||
(`check-bat.ps1`: caret escapes, quotes and `for /f ('...')` strings ignored, depth never below zero, zero at the
|
||||
end; its fixture `bad-bare-paren.bat.txt` must be flagged). Both scripts run on a PC too:
|
||||
`powershell -NoProfile -ExecutionPolicy Bypass -File tools\ci\windows\check-ps51.ps1`.
|
||||
2. `build`: the engine with `cargo build --release` on the MSVC target (so the Mac cross-build is no longer an input),
|
||||
the window host with `app\windows\BUILD-APP.bat` as it is (MSVC from the runner's Visual Studio, WebView2 SDK from
|
||||
NuGet, `host.rc` with the coin icon; `version.h` carries the host version), the payload with `make-payload.sh` in
|
||||
Git Bash, the installer with `build-installer.ps1` (Inno Setup from the runner image or chocolatey, rcedit), then a
|
||||
smoke run (`igneum-app.exe --version`, `Igneum Miner.exe --version` and `--help`, the version block of the host),
|
||||
the launcher `proto-cuda/windows-app/start-igneum.ps1` with `DRY_RUN=1` (prints the node command and the GPU plan,
|
||||
starts nothing) through the `.ps1` and through `START-IGNEUM.bat`, and three artifacts kept 90 days:
|
||||
`igneum-windows-installer` (`Igneum-Miner-Setup-<version>.exe`), `igneum-windows-payload`
|
||||
(`igneum-windows-app.zip`), `igneum-windows-host` (`Igneum Miner.exe`).
|
||||
|
||||
### The inputs the runner cannot build
|
||||
|
||||
`igneumd.exe` and `igneum-miner.exe` come from the node fork in `vendor/` (not in git, 20 to 55 minutes to build) and
|
||||
the prebuilt GPU workers need NVIDIA's NVRTC DLLs (90 MB, not in git). They travel as `payload-inputs.zip` on the
|
||||
downloads host:
|
||||
|
||||
packaging/windows/push-inputs.sh # on the Mac, after each node or worker cross-build
|
||||
|
||||
collects `igneumd.exe`, `igneum-miner.exe`, the three mingw DLLs, `igneum-worker-cuda.exe`, `nvrtc*.dll`, the licence
|
||||
texts, `igneum-worker-opencl.exe` and an `inputs.json` (sha256 and bytes of each, the commits, the date), zips them
|
||||
into `dl/<token>/payload-inputs.zip` with `payload-inputs.sha256` and `payload-inputs.json` next to it in the
|
||||
downloads folder (`~/.config/igneum/dlsite-dir` names it; `IGNEUM_DLSITE` overrides), and deploys the folder with the
|
||||
Vercel CLI (`--no-deploy` to skip). The workflow downloads the three with the `DL_TOKEN` repository secret and checks
|
||||
the sha256. The secret was set once from the Mac and never printed:
|
||||
|
||||
tr -d '[:space:]' < ~/.config/igneum/dl-token | gh secret set DL_TOKEN --repo igneum-network/igneum
|
||||
|
||||
### The outputs, back to the downloads host
|
||||
|
||||
The runner cannot deploy the downloads project (it is deployed by CLI from a folder outside the repo with the Igneum
|
||||
Vercel login), so the Mac pulls the artifacts:
|
||||
|
||||
packaging/windows/fetch-ci-artifacts.sh [--deploy] [run-id]
|
||||
|
||||
downloads the installer and the payload zip from the latest green run on master (`gh run download`, as igneum-labs),
|
||||
copies them into `dl/<token>/` next to the Mac-built packages, writes `igneum-windows-ci.json` (run URL, time), and
|
||||
prints the deploy command, or deploys with `--deploy`.
|
||||
|
||||
### What still needs a human
|
||||
|
||||
A code-signing certificate. Until Igneum has one, the installer and the exes are unsigned and SmartScreen shows
|
||||
"Windows protected your PC" (More info, Run anyway). Everything else in this folder runs without a PC.
|
||||
|
||||
## On the Mac (hand build, kept for reference)
|
||||
|
||||
packaging/windows/make-payload.sh [out.zip]
|
||||
|
||||
|
|
@ -17,7 +78,7 @@ worker sources for the fallback build (`proto-cuda\`, `proto-opencl\`), `igneum-
|
|||
token from `~/.config/igneum/dl-token`, log intake, live page), `stop-igneum.ps1`, and `app\windows\` (the window host
|
||||
sources and `BUILD-APP.bat`, since WebView2 cannot be linked from the Mac).
|
||||
|
||||
## On the PC
|
||||
## On the PC (hand build, kept for reference; CI does all of this)
|
||||
|
||||
1. Extract the zip next to `packaging\windows` (or anywhere: `build-installer.ps1 -Payload <folder>`; without a folder it
|
||||
downloads `dl.igneum.network/igneum-windows-app.zip`).
|
||||
|
|
|
|||
52
packaging/windows/fetch-ci-artifacts.sh
Executable file
52
packaging/windows/fetch-ci-artifacts.sh
Executable file
|
|
@ -0,0 +1,52 @@
|
|||
#!/usr/bin/env bash
|
||||
# Pulls the Windows installer and payload from the latest green run of .github/workflows/windows.yml on master and
|
||||
# copies them into the downloads folder (dl/<token>/), where the other packages live. Run on the Mac:
|
||||
#
|
||||
# packaging/windows/fetch-ci-artifacts.sh [--deploy] [run-id]
|
||||
#
|
||||
# Without --deploy it prints the deploy command for the main session to run; with --deploy it deploys the folder with
|
||||
# the Vercel CLI itself. A run id (gh run list) picks a specific run instead of the latest green one.
|
||||
# Reads ~/.config/igneum/dl-token, ~/.config/igneum/dlsite-dir (IGNEUM_DLSITE overrides) and the gh login, which must
|
||||
# be igneum-labs (gh auth switch --user igneum-labs).
|
||||
set -euo pipefail
|
||||
REPO="igneum-network/igneum"
|
||||
DEPLOY=0
|
||||
RUN_ID=""
|
||||
for a in "$@"; do
|
||||
case "$a" in --deploy) DEPLOY=1 ;; *) RUN_ID="$a" ;; esac
|
||||
done
|
||||
TOKEN_FILE="$HOME/.config/igneum/dl-token"
|
||||
DLSITE="${IGNEUM_DLSITE:-}"
|
||||
[ -n "$DLSITE" ] || { [ -f "$HOME/.config/igneum/dlsite-dir" ] && DLSITE="$(tr -d '[:space:]' < "$HOME/.config/igneum/dlsite-dir")"; } || true
|
||||
[ -f "$TOKEN_FILE" ] || { echo "no $TOKEN_FILE" >&2; exit 1; }
|
||||
TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
|
||||
DEST="$DLSITE/dl/$TOKEN"
|
||||
[ -n "$DLSITE" ] && [ -d "$DEST" ] || { echo "no downloads folder: set IGNEUM_DLSITE or ~/.config/igneum/dlsite-dir (must hold dl/<token>/)" >&2; exit 1; }
|
||||
gh auth status 2>&1 | grep -q 'Active account: true' || { echo "gh is not logged in" >&2; exit 1; }
|
||||
gh auth status 2>&1 | grep -B1 'Active account: true' | grep -q 'igneum-labs' || { echo "gh active account is not igneum-labs: run gh auth switch --user igneum-labs" >&2; exit 1; }
|
||||
|
||||
if [ -z "$RUN_ID" ]; then
|
||||
RUN_ID="$(gh run list --repo "$REPO" --workflow windows.yml --branch master --status success --limit 1 --json databaseId --jq '.[0].databaseId')"
|
||||
[ -n "$RUN_ID" ] && [ "$RUN_ID" != "null" ] || { echo "no green windows.yml run on master yet" >&2; exit 1; }
|
||||
fi
|
||||
gh run view "$RUN_ID" --repo "$REPO" --json headSha,displayTitle,updatedAt,url,conclusion --jq '"run \(.url)\n\(.displayTitle)\n\(.headSha[0:12]) \(.updatedAt) \(.conclusion)"'
|
||||
|
||||
TMP="$(mktemp -d)"
|
||||
gh run download "$RUN_ID" --repo "$REPO" --name igneum-windows-installer --name igneum-windows-payload --dir "$TMP"
|
||||
SETUP="$(find "$TMP" -name 'Igneum-Miner-Setup-*.exe' | head -1)"
|
||||
PAYLOAD="$(find "$TMP" -name 'igneum-windows-app.zip' | head -1)"
|
||||
[ -n "$SETUP" ] && [ -n "$PAYLOAD" ] || { echo "the run has no installer or payload artifact" >&2; ls -R "$TMP"; exit 1; }
|
||||
cp "$SETUP" "$DEST/"
|
||||
cp "$PAYLOAD" "$DEST/igneum-windows-app.zip"
|
||||
cat > "$DEST/igneum-windows-ci.json" <<JSON
|
||||
{ "run": "https://github.com/$REPO/actions/runs/$RUN_ID", "installer": "$(basename "$SETUP")", "payload": "igneum-windows-app.zip", "fetched_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)" }
|
||||
JSON
|
||||
rm -rf "$TMP"
|
||||
echo "copied into $DEST:"
|
||||
ls -la "$DEST/$(basename "$SETUP")" "$DEST/igneum-windows-app.zip"
|
||||
if [ "$DEPLOY" = 1 ]; then
|
||||
(cd "$DLSITE" && npx vercel@latest --global-config "$HOME/.config/igneum/vercel" deploy --prod --yes 2>&1 | grep -v "$TOKEN" || true)
|
||||
echo "live: https://dl.igneum.network/dl/<token>/$(basename "$SETUP")"
|
||||
else
|
||||
echo "deploy: cd $DLSITE && npx vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes"
|
||||
fi
|
||||
|
|
@ -14,12 +14,18 @@
|
|||
# proto-cuda\, proto-opencl\ the worker sources and build.bat for that fallback
|
||||
# igneum-app.json the update manifest URL (token from ~/.config/igneum/dl-token, never in the repo), the log intake
|
||||
# stop-igneum.ps1 what the installer runs before an upgrade and on uninstall
|
||||
# app\windows\ host.cpp, host.rc, BUILD-APP.bat (the window host is built on the PC)
|
||||
# app\windows\ host.cpp, host.rc, version.h, BUILD-APP.bat (the window host is built on the PC or by CI)
|
||||
# Igneum Miner.exe the window host, when app/windows/dist/ holds one (BUILD-APP.bat ran before this script)
|
||||
# On GitHub (.github/workflows/windows.yml) the same script runs in Git Bash with IGNEUM_WIN_RELEASE and
|
||||
# IGNEUM_WORKERS_DIR pointing at the unpacked payload-inputs.zip (push-inputs.sh) and IGNEUM_APP_EXE at the engine
|
||||
# built on the runner; the mingw DLLs are taken from the release folder there, zip falls back to 7z.
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(cd "$HERE/../.." && pwd)"
|
||||
VERSION="0.3.0"
|
||||
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' "$ROOT/app/igneum-app/Cargo.toml" | head -1)"
|
||||
OUT="${1:-$HOME/Desktop/igneum-windows-app.zip}"
|
||||
case "$OUT" in /*) ;; *) OUT="$PWD/$OUT" ;; esac
|
||||
mkdir -p "$(dirname "$OUT")"
|
||||
REL="${IGNEUM_WIN_RELEASE:-$ROOT/vendor/igneum-node/target-integration/x86_64-pc-windows-gnu/release}"
|
||||
ENGINE="${IGNEUM_APP_EXE:-$ROOT/app/igneum-app/target/x86_64-pc-windows-gnu/release/igneum-app.exe}"
|
||||
MINGW=/opt/homebrew/opt/mingw-w64/toolchain-x86_64/x86_64-w64-mingw32
|
||||
|
|
@ -37,7 +43,8 @@ cp "$REL/igneumd.exe" "$STAGE/igneumd.exe"
|
|||
cp "$REL/igneum-miner.exe" "$STAGE/igneum-miner.exe"
|
||||
for dll in lib/libstdc++-6.dll lib/libgcc_s_seh-1.dll bin/libwinpthread-1.dll; do
|
||||
name="$(basename "$dll")"
|
||||
if [ -f "$MINGW/$dll" ]; then cp "$MINGW/$dll" "$STAGE/"; x86_64-w64-mingw32-strip "$STAGE/$name" 2>/dev/null || true
|
||||
if [ -f "$REL/$name" ]; then cp "$REL/$name" "$STAGE/" # push-inputs.sh ships them next to the exes (CI)
|
||||
elif [ -f "$MINGW/$dll" ]; then cp "$MINGW/$dll" "$STAGE/"; x86_64-w64-mingw32-strip "$STAGE/$name" 2>/dev/null || true
|
||||
else echo "note: $name not found in the mingw toolchain (the node exe is linked -static, so it may not need it)"; fi
|
||||
done
|
||||
|
||||
|
|
@ -63,9 +70,10 @@ else echo "note: no prebuilt igneum-worker-cuda.exe / igneum-worker-opencl.exe f
|
|||
# the worker sources for the fallback build
|
||||
cp "$ROOT/proto-cuda/host.cu" "$ROOT/proto-cuda/build.bat" "$ROOT/proto-cuda/README.md" "$STAGE/proto-cuda/"
|
||||
cp "$ROOT/proto-opencl/host.c" "$ROOT/proto-opencl/build.bat" "$ROOT/proto-opencl/README.md" "$STAGE/proto-opencl/"
|
||||
# the window host sources, built on the PC
|
||||
cp "$ROOT/app/windows/host.cpp" "$ROOT/app/windows/host.rc" "$ROOT/app/windows/BUILD-APP.bat" "$STAGE/app/windows/"
|
||||
# the window host sources, built on the PC or by CI; the built host when BUILD-APP.bat already ran here
|
||||
cp "$ROOT/app/windows/host.cpp" "$ROOT/app/windows/host.rc" "$ROOT/app/windows/version.h" "$ROOT/app/windows/BUILD-APP.bat" "$STAGE/app/windows/"
|
||||
mkdir -p "$STAGE/app/windows/art" && cp "$ROOT/brand/icons/igneum.ico" "$STAGE/app/windows/art/"
|
||||
if [ -f "$ROOT/app/windows/dist/Igneum Miner.exe" ]; then cp "$ROOT/app/windows/dist/Igneum Miner.exe" "$STAGE/"; echo "window host: Igneum Miner.exe from app/windows/dist"; fi
|
||||
cp "$HERE/stop-igneum.ps1" "$STAGE/stop-igneum.ps1"
|
||||
|
||||
# the packaged configuration: the download token stays out of the repo
|
||||
|
|
@ -96,7 +104,10 @@ for f in "$STAGE"/*.bat "$STAGE/README.txt" "$STAGE/stop-igneum.ps1" "$STAGE/app
|
|||
[ -f "$f" ] && perl -pi -e 's/\r?\n/\r\n/' "$f"
|
||||
done
|
||||
rm -f "$OUT"
|
||||
(cd "$HERE" && zip -qr "$OUT" "igneum-windows-app" -x '*.DS_Store')
|
||||
if command -v zip >/dev/null 2>&1; then (cd "$HERE" && zip -qr "$OUT" "igneum-windows-app" -x '*.DS_Store')
|
||||
elif command -v 7z >/dev/null 2>&1; then (cd "$HERE" && 7z a -tzip -bso0 -bsp0 "$OUT" "igneum-windows-app" '-xr!.DS_Store')
|
||||
else echo "neither zip nor 7z is on PATH" >&2; exit 1; fi
|
||||
echo "staged $STAGE"
|
||||
ls -la "$OUT"
|
||||
unzip -l "$OUT" | tail -n +4 | awk '{print $1, $4}' | sed '/^ *$/d' | head -40
|
||||
if command -v unzip >/dev/null 2>&1; then unzip -l "$OUT" | tail -n +4 | awk '{print $1, $4}' | sed '/^ *$/d' | head -40
|
||||
else (cd "$STAGE" && find . -type f | sort | head -40); fi
|
||||
|
|
|
|||
95
packaging/windows/push-inputs.sh
Executable file
95
packaging/windows/push-inputs.sh
Executable file
|
|
@ -0,0 +1,95 @@
|
|||
#!/usr/bin/env bash
|
||||
# Publishes payload-inputs.zip: the pieces of the Windows app that the GitHub build (.github/workflows/windows.yml)
|
||||
# cannot make on a hosted runner, because they come from the node fork (vendor/, not in git) or from NVIDIA's
|
||||
# redistributables (large, not in git). Run it on the Mac after every node or worker cross-build:
|
||||
#
|
||||
# packaging/windows/push-inputs.sh [--no-deploy]
|
||||
#
|
||||
# What goes in (flat): igneumd.exe, igneum-miner.exe (vendor/igneum-node/target-integration/x86_64-pc-windows-gnu/
|
||||
# release, IGNEUM_WIN_RELEASE overrides), the three mingw runtime DLLs, igneum-worker-cuda.exe with nvrtc64_*_0.dll,
|
||||
# nvrtc-builtins64_*.dll and the licence texts (proto-cuda/nvrtc, fetch-redist.sh + build-windows.sh),
|
||||
# igneum-worker-opencl.exe (proto-opencl), and inputs.json (sha256 and size of each file, the commits, the date).
|
||||
# The zip, its .sha256 and the .json land in the downloads folder (dl/<token>/) and the folder is deployed with the
|
||||
# Vercel CLI, exactly as the other packages are. The workflow fetches them with the DL_TOKEN repository secret and
|
||||
# refuses a zip whose sha256 does not match.
|
||||
#
|
||||
# Where things are read from (never in the repo): the token in ~/.config/igneum/dl-token, the downloads folder in
|
||||
# ~/.config/igneum/dlsite-dir (one line, the path of the dlsite directory; IGNEUM_DLSITE overrides), the Vercel login
|
||||
# in ~/.config/igneum/vercel.
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
ROOT="$(cd "$HERE/../.." && pwd)"
|
||||
REL="${IGNEUM_WIN_RELEASE:-$ROOT/vendor/igneum-node/target-integration/x86_64-pc-windows-gnu/release}"
|
||||
MINGW=/opt/homebrew/opt/mingw-w64/toolchain-x86_64/x86_64-w64-mingw32
|
||||
NVRTC_DIR="$ROOT/proto-cuda/nvrtc"
|
||||
CL_WORKER="$ROOT/proto-opencl/igneum-worker-opencl.exe"
|
||||
TOKEN_FILE="$HOME/.config/igneum/dl-token"
|
||||
DLSITE="${IGNEUM_DLSITE:-}"
|
||||
[ -n "$DLSITE" ] || { [ -f "$HOME/.config/igneum/dlsite-dir" ] && DLSITE="$(tr -d '[:space:]' < "$HOME/.config/igneum/dlsite-dir")"; } || true
|
||||
DEPLOY=1
|
||||
[ "${1:-}" = "--no-deploy" ] && DEPLOY=0
|
||||
|
||||
[ -f "$TOKEN_FILE" ] || { echo "no $TOKEN_FILE (the downloads token)" >&2; exit 1; }
|
||||
TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
|
||||
[ -n "$DLSITE" ] && [ -d "$DLSITE/dl/$TOKEN" ] || { echo "no downloads folder: set IGNEUM_DLSITE or write the dlsite path to ~/.config/igneum/dlsite-dir (it must hold dl/<token>/)" >&2; exit 1; }
|
||||
[ -f "$REL/igneumd.exe" ] || { echo "no $REL/igneumd.exe; cross-compile the node first (proto-cuda/windows-node/cross-build.sh)" >&2; exit 1; }
|
||||
[ -f "$REL/igneum-miner.exe" ] || { echo "no $REL/igneum-miner.exe; cross-compile the miner first" >&2; exit 1; }
|
||||
|
||||
STAGE="$(mktemp -d)/payload-inputs"
|
||||
mkdir -p "$STAGE"
|
||||
cp "$REL/igneumd.exe" "$REL/igneum-miner.exe" "$STAGE/"
|
||||
for dll in lib/libstdc++-6.dll lib/libgcc_s_seh-1.dll bin/libwinpthread-1.dll; do
|
||||
name="$(basename "$dll")"
|
||||
if [ -f "$MINGW/$dll" ]; then cp "$MINGW/$dll" "$STAGE/"; x86_64-w64-mingw32-strip "$STAGE/$name" 2>/dev/null || true
|
||||
else echo "note: $name not in the mingw toolchain (the node exe is linked -static, so it may not need it)"; fi
|
||||
done
|
||||
if [ -f "$NVRTC_DIR/igneum-worker-cuda.exe" ]; then
|
||||
cp "$NVRTC_DIR/igneum-worker-cuda.exe" "$STAGE/"
|
||||
for f in "$NVRTC_DIR"/redist/bin/nvrtc*.dll "$NVRTC_DIR"/redist/LICENSE-*.txt "$NVRTC_DIR/THIRD-PARTY.md"; do [ -f "$f" ] && cp "$f" "$STAGE/"; done
|
||||
ls "$STAGE"/nvrtc64_*_0.dll >/dev/null 2>&1 || echo "warning: igneum-worker-cuda.exe without nvrtc64_*_0.dll (run $NVRTC_DIR/fetch-redist.sh)"
|
||||
else echo "warning: no $NVRTC_DIR/igneum-worker-cuda.exe (run $NVRTC_DIR/build-windows.sh); the app will build the CUDA worker on the PC"; fi
|
||||
[ -f "$CL_WORKER" ] && cp "$CL_WORKER" "$STAGE/" || echo "warning: no $CL_WORKER"
|
||||
|
||||
# the manifest: what is in the zip, from where, when
|
||||
NODE_COMMIT="$(git -C "$ROOT/vendor/igneum-node-v4" rev-parse --short HEAD 2>/dev/null || git -C "$ROOT/vendor/igneum-node" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||
REPO_COMMIT="$(git -C "$ROOT" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||
{
|
||||
echo '{'
|
||||
echo " \"built_at\": \"$(date -u +%Y-%m-%dT%H:%M:%SZ)\","
|
||||
echo " \"node_source_commit\": \"$NODE_COMMIT\","
|
||||
echo " \"repo_commit\": \"$REPO_COMMIT\","
|
||||
echo ' "files": {'
|
||||
first=1
|
||||
for f in "$STAGE"/*; do
|
||||
name="$(basename "$f")"
|
||||
sum="$(shasum -a 256 "$f" | cut -d' ' -f1)"
|
||||
bytes="$(stat -f %z "$f")"
|
||||
[ $first = 1 ] || echo ','
|
||||
first=0
|
||||
printf ' "%s": { "sha256": "%s", "bytes": %s }' "$name" "$sum" "$bytes"
|
||||
done
|
||||
echo
|
||||
echo ' }'
|
||||
echo '}'
|
||||
} > "$STAGE/inputs.json"
|
||||
|
||||
DEST="$DLSITE/dl/$TOKEN"
|
||||
OUT="$DEST/payload-inputs.zip"
|
||||
rm -f "$OUT"
|
||||
(cd "$(dirname "$STAGE")" && zip -qr "$OUT" "payload-inputs" -x '*.DS_Store')
|
||||
shasum -a 256 "$OUT" | awk '{print $1}' > "$DEST/payload-inputs.sha256"
|
||||
cp "$STAGE/inputs.json" "$DEST/payload-inputs.json"
|
||||
echo "payload-inputs.zip: $(stat -f %z "$OUT") bytes, sha256 $(cat "$DEST/payload-inputs.sha256")"
|
||||
cat "$DEST/payload-inputs.json"
|
||||
rm -rf "$(dirname "$STAGE")"
|
||||
|
||||
if [ "$DEPLOY" = 1 ]; then
|
||||
echo "deploying $DLSITE"
|
||||
(cd "$DLSITE" && npx vercel@latest --global-config "$HOME/.config/igneum/vercel" deploy --prod --yes 2>&1 | grep -v "$TOKEN" || true)
|
||||
code="$(curl -s -o /dev/null -w '%{http_code}' "https://dl.igneum.network/dl/$TOKEN/payload-inputs.json")"
|
||||
echo "https://dl.igneum.network/dl/<token>/payload-inputs.json -> HTTP $code"
|
||||
[ "$code" = 200 ] || { echo "the manifest is not reachable yet; check the deploy output" >&2; exit 1; }
|
||||
else
|
||||
echo "not deployed (--no-deploy): cd $DLSITE && npx vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes"
|
||||
fi
|
||||
echo "Next: the GitHub build picks it up on the next push to master (or: gh workflow run windows.yml --repo igneum-network/igneum)."
|
||||
|
|
@ -23,6 +23,17 @@ Set-NodeTarget $rpcHost $rpcPort # the miners always dial the node on this PC
|
|||
Log "igneum launcher $packageVersion ($chainVersion) on $machine (run $stamp): node $rpcListen, p2p $p2pListen, peers $($peerNodes -join ' '), appdir $appDir, unsynced mining $(if ($unsyncedMining) { 'on' } else { 'off' }), identities $minersPerVendor per vendor"
|
||||
Log "If Windows Firewall asks about igneumd.exe: tick Private networks and click Allow access (the Mac node dials this PC on port $p2pPort). If you clicked Cancel once, run ALLOW-FIREWALL.bat."
|
||||
|
||||
# DRY_RUN=1 (CI, .github/workflows/windows.yml; or a quick check on a PC): print the plan and start nothing.
|
||||
if ($env:DRY_RUN -eq '1') {
|
||||
Log 'DRY_RUN=1: printing the plan, starting nothing'
|
||||
Log "node command: `"$nodeExe`" $((Get-NodeArgs) -join ' ')"
|
||||
Log "node binary present: $(Test-Path $nodeExe); miner binary present: $(Test-Path $miner)"
|
||||
$plan = Find-Hardware
|
||||
Log "miners to start: $(if ($plan.Count) { $plan -join ', ' } else { 'none' }) ($minersPerVendor identities per vendor, payout $(if ($payoutEvm) { $payoutEvm } else { 'derived from the PC name' }))"
|
||||
Log 'dry run done'
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ---- 1. the node ----------------------------------------------------------------------------------------------------------
|
||||
|
||||
$script:node.on = $true
|
||||
|
|
|
|||
69
tools/ci/windows/check-bat.ps1
Normal file
69
tools/ci/windows/check-bat.ps1
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
# Static check of every .bat and .cmd under the given folders for the parenthesis bug class seen on 3 October 2026:
|
||||
# a bare ")" that closes nothing, or an "if (...) (" block left open. cmd.exe has no dry-parse mode, so this mirrors
|
||||
# what its parser does with parentheses: caret escapes (^( and ^)) are not parentheses, nothing inside double quotes
|
||||
# or a for /f ('...') command string counts, rem and :: lines are comments. The depth is tracked across lines; it must
|
||||
# never go below zero and must be zero at the end of the file. Prints file:line:message and exits 1 on any finding.
|
||||
#
|
||||
# A built-in negative test checks fixtures\bad-bare-paren.bat.txt first and refuses to pass unless it is flagged.
|
||||
#
|
||||
# powershell -NoProfile -ExecutionPolicy Bypass -File tools\ci\windows\check-bat.ps1 [-Root <repo>]
|
||||
param(
|
||||
[string]$Root = '',
|
||||
[string[]]$Folders = @('proto-cuda/windows-app', 'proto-cuda/windows-miner', 'proto-cuda/windows-node',
|
||||
'proving/windows-wsl2', 'relay/clients', 'packaging/windows', 'app/windows',
|
||||
'proto-cuda', 'proto-opencl')
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (-not $Root) { $Root = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path }
|
||||
|
||||
function Check-Batch([string]$path) {
|
||||
$findings = @()
|
||||
$depth = 0
|
||||
$n = 0
|
||||
foreach ($raw in [IO.File]::ReadAllLines($path)) {
|
||||
$n += 1
|
||||
$line = $raw.TrimEnd("`r")
|
||||
if ($line -match '^\s*(@?rem\b|::)') { continue }
|
||||
$s = $line -replace '\^.', '' # caret escapes: ^( ^) ^> ^< ^& ^|
|
||||
$s = $s -replace '"[^"]*"', '' # double-quoted strings
|
||||
$s = $s -replace "'[^']*'", '' # for /f ('command') strings
|
||||
foreach ($ch in $s.ToCharArray()) {
|
||||
if ($ch -eq '(') { $depth += 1 }
|
||||
elseif ($ch -eq ')') {
|
||||
$depth -= 1
|
||||
if ($depth -lt 0) { $findings += @{ line = $n; msg = "')' closes nothing (bare parenthesis)" }; $depth = 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($depth -gt 0) { $findings += @{ line = $n; msg = "$depth unclosed '(' at the end of the file" } }
|
||||
return ,$findings
|
||||
}
|
||||
|
||||
# ---- 1. the negative test ----
|
||||
$fixture = Join-Path $PSScriptRoot 'fixtures\bad-bare-paren.bat.txt'
|
||||
$fx = Check-Batch $fixture
|
||||
if ($fx.Count -eq 0) { Write-Host "::error::self-test failed: $fixture was not flagged"; exit 2 }
|
||||
Write-Host ("self-test: fixture bad-bare-paren.bat.txt is flagged as expected at line {0}: {1}" -f $fx[0].line, $fx[0].msg)
|
||||
|
||||
# ---- 2. every .bat and .cmd ----
|
||||
$files = @()
|
||||
foreach ($f in $Folders) {
|
||||
$dir = Join-Path $Root $f
|
||||
if (-not (Test-Path $dir)) { Write-Host "note: no folder $f"; continue }
|
||||
$files += Get-ChildItem -Path $dir -Recurse -Include '*.bat', '*.cmd' -File | Where-Object { $_.FullName -notmatch '\\igneum-windows-app\\|\\build\\|\\dist\\|\\packs\\|\\emu\\|\\nvrtc\\redist\\' }
|
||||
}
|
||||
$files = $files | Sort-Object FullName -Unique
|
||||
$bad = 0
|
||||
foreach ($file in $files) {
|
||||
$rel = $file.FullName.Substring($Root.Length).TrimStart('\', '/')
|
||||
$found = Check-Batch $file.FullName
|
||||
if ($found.Count -eq 0) { Write-Host "ok $rel"; continue }
|
||||
foreach ($x in $found) {
|
||||
$bad += 1
|
||||
Write-Host "FAIL ${rel}:$($x.line): $($x.msg)"
|
||||
Write-Host "::error file=$($rel -replace '\\', '/'),line=$($x.line)::$($x.msg)"
|
||||
}
|
||||
}
|
||||
Write-Host ("{0} batch files checked, {1} findings" -f $files.Count, $bad)
|
||||
if ($bad -gt 0) { exit 1 }
|
||||
exit 0
|
||||
68
tools/ci/windows/check-ps51.ps1
Normal file
68
tools/ci/windows/check-ps51.ps1
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# Parses every .ps1 under the given folders with the PowerShell parser of the PowerShell that runs this script and
|
||||
# fails on any parse error, printed as file:line:message. Run it under Windows PowerShell 5.1 (powershell.exe), the
|
||||
# PowerShell on the project lead's PCs: 5.1 rejects "$name: text" (a drive-qualified variable reference) where newer parsers may
|
||||
# not, and that class caused the two launcher failures of 4 October 2026 (commits 82275e6 and 018f739).
|
||||
#
|
||||
# A built-in negative test parses fixtures\bad-drive-ref.ps1.txt first and refuses to pass unless that file FAILS with
|
||||
# the 5.1 message, so a green run proves the check bites.
|
||||
#
|
||||
# powershell -NoProfile -ExecutionPolicy Bypass -File tools\ci\windows\check-ps51.ps1 [-Root <repo>] [-AnyVersion]
|
||||
param(
|
||||
[string]$Root = '',
|
||||
[string[]]$Folders = @('proto-cuda/windows-app', 'proto-cuda/windows-miner', 'proto-cuda/windows-node',
|
||||
'proving/windows-wsl2', 'relay/clients', 'packaging/windows', 'app/windows', 'tools/ci/windows'),
|
||||
[switch]$AnyVersion
|
||||
)
|
||||
$ErrorActionPreference = 'Stop'
|
||||
if (-not $Root) { $Root = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path }
|
||||
$v = $PSVersionTable.PSVersion
|
||||
Write-Host "PowerShell $v ($($PSVersionTable.PSEdition))"
|
||||
if ($v.Major -ne 5 -and -not $AnyVersion) {
|
||||
Write-Host "::error::this check must run under Windows PowerShell 5.1 (powershell.exe), not PowerShell $v; pass -AnyVersion to override"
|
||||
exit 2
|
||||
}
|
||||
|
||||
function Parse-File([string]$path) {
|
||||
$tokens = $null
|
||||
$errors = $null
|
||||
[void][System.Management.Automation.Language.Parser]::ParseFile($path, [ref]$tokens, [ref]$errors)
|
||||
return ,@($errors)
|
||||
}
|
||||
|
||||
# ---- 1. the negative test: the fixture with "$x: y" must fail to parse ----
|
||||
$fixture = Join-Path $PSScriptRoot 'fixtures\bad-drive-ref.ps1.txt'
|
||||
$fixtureErrors = Parse-File $fixture
|
||||
if ($fixtureErrors.Count -eq 0) {
|
||||
Write-Host "::error::self-test failed: $fixture parsed without errors, so this parser does not catch the 5.1 drive-qualified variable bug"
|
||||
exit 2
|
||||
}
|
||||
$msg = $fixtureErrors[0].Message
|
||||
Write-Host ("self-test: fixture bad-drive-ref.ps1.txt fails as expected at line {0}: {1}" -f $fixtureErrors[0].Extent.StartLineNumber, $msg)
|
||||
if ($msg -notmatch 'Variable reference is not valid') {
|
||||
Write-Host "::error::self-test: the fixture failed, but not with the 'Variable reference is not valid' message (got: $msg)"
|
||||
exit 2
|
||||
}
|
||||
|
||||
# ---- 2. every .ps1 under the folders ----
|
||||
$files = @()
|
||||
foreach ($f in $Folders) {
|
||||
$dir = Join-Path $Root $f
|
||||
if (-not (Test-Path $dir)) { Write-Host "note: no folder $f"; continue }
|
||||
$files += Get-ChildItem -Path $dir -Recurse -Filter '*.ps1' -File | Where-Object { $_.FullName -notmatch '\\igneum-windows-app\\|\\build\\|\\dist\\' }
|
||||
}
|
||||
$bad = 0
|
||||
foreach ($file in ($files | Sort-Object FullName)) {
|
||||
$rel = $file.FullName.Substring($Root.Length).TrimStart('\', '/')
|
||||
$errs = Parse-File $file.FullName
|
||||
if ($errs.Count -eq 0) { Write-Host "ok $rel"; continue }
|
||||
foreach ($e in $errs) {
|
||||
$bad += 1
|
||||
$line = $e.Extent.StartLineNumber
|
||||
$col = $e.Extent.StartColumnNumber
|
||||
Write-Host "FAIL ${rel}:${line}:${col}: $($e.Message)"
|
||||
Write-Host "::error file=$($rel -replace '\\', '/'),line=$line,col=${col}::$($e.Message)"
|
||||
}
|
||||
}
|
||||
Write-Host ("{0} files parsed, {1} parse errors" -f $files.Count, $bad)
|
||||
if ($bad -gt 0) { exit 1 }
|
||||
exit 0
|
||||
10
tools/ci/windows/fixtures/bad-bare-paren.bat.txt
Normal file
10
tools/ci/windows/fixtures/bad-bare-paren.bat.txt
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
@echo off
|
||||
rem Negative fixture for check-bat.ps1 (named .txt so nothing runs it). The bare ")" on line 9 closes nothing:
|
||||
rem cmd.exe reports ") was unexpected at this time." and stops. Seen on 3 October 2026 in a launcher bat.
|
||||
if not exist "%~dp0igneumd.exe" (
|
||||
echo igneumd.exe is missing ^(extract the whole zip^).
|
||||
exit /b 1
|
||||
)
|
||||
echo ready
|
||||
)
|
||||
echo never reached
|
||||
7
tools/ci/windows/fixtures/bad-drive-ref.ps1.txt
Normal file
7
tools/ci/windows/fixtures/bad-drive-ref.ps1.txt
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Negative fixture for check-ps51.ps1 (named .txt so no PowerShell glob picks it up). Windows PowerShell 5.1 must
|
||||
# refuse this file: "$machine: text" is read as a drive-qualified variable reference, the bug class of commits
|
||||
# 82275e6 and 018f739 on 4 October 2026. The fix in real code is "${machine}: text".
|
||||
$machine = $env:COMPUTERNAME
|
||||
$vendorName = 'nvidia'
|
||||
$label = "igneum-evm-payout-v0:$machine:$vendorName"
|
||||
Write-Host "$label: derived"
|
||||
Loading…
Reference in a new issue