Igneum Wallet: the Windows build chain (8 October 2026, main's order after 0.1.6: a Windows arm by the wallet's next OTA, PC 2 by job when it is back). The pieces, the miner's chain step for step with the wallet's names: packaging/windows/push-build-inputs.sh --wallet packs app/igneum-wallet and app/igneum-common into the build-inputs zip with vendor/igneum-node as a symlink to node/ (the wallet links the node's rpc crates by that path; unzip restores it inside the distro) and adds the build unit {app/igneum-wallet, igneum-wallet, windows} with its tests, so the PC's build job makes igneum-wallet.exe on the gnu target as it makes igneum-app.exe; packaging/windows/push-wallet-kit.sh publishes the kit zip (the host sources and BUILD-WALLET-APP.bat, build-installer.ps1, Igneum-Wallet.iss, stop-igneum-wallet.ps1, the icons, the wallet's packaged igneum-wallet.json from the token file) to the downloads host; relay/playbooks/wallet-kit-pc2.ps1 is the run job on PC 2: the host through BUILD-WALLET-APP.bat (MSVC, WebView2 SDK), the payload from the build job's engine and the node pin, the installer through build-installer.ps1 -Product wallet (Inno, rcedit), the smoke run with the host's version block, and, over a running older wallet, the installer end to end with the installed version read back (rule 14), every output dropped on the relay with its sha256; packaging/windows/build-installer.ps1 takes -Product miner|wallet (folder, engine, stop script, exes, .iss, setup name, the version-block stamping by product); packaging/windows/Igneum-Wallet.iss is rebuilt on release-0.3.25's detach-safe miner shape (install-close-23 and install-detach-25: the payload's own stop script with -Install, the install marker under igneum\wallet, the one-shot detached task under the app's job runner, the version-named file refusal, the stale-marker clear) with the wallet's names; packaging/windows/stop-igneum-wallet.ps1 is the miner's 0.3.23 stop script for the wallet (the host first by path, the engine through its API, the unlock wait with the STILL LOCKED line); make-wallet-payload.sh is the Mac-side payload maker for a hand build. release-0.3.25's installer-stop-check passes on the wallet pair (every rule holds); the check itself stays on the release line, whose miner installer carries the shape master's does not yet. Signing: as the miner's, deferred until the certificates exist (release-0.3.20 section 2.4). Untested on a PC until PC 2 returns: the first run is the known-failed run by design.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
igneum-labs 2026-10-08 11:59:16 +00:00
parent 8c775d56ca
commit 89e180defc
7 changed files with 518 additions and 13 deletions

View file

@ -1,3 +1,4 @@
; Igneum Wallet installer for Windows (8 October 2026: the miner's detach-safe shape of release-0.3.25, install-close-23 and install-detach-25, with the wallet's names: the payload's own stop script with -Install, the install marker, the one-shot detached task under the app's job runner, the version-named file refusal)
; Igneum Wallet installer for Windows, Inno Setup 6.3 or newer. A copy of Igneum-Miner.iss with the names, the AppId and
; the payload changed: the wallet engine (igneum-wallet.exe), "Igneum Wallet.exe" (the window host when
; BUILD-WALLET-APP.bat made it), igneumd.exe (started only when the miner's node is not running). Untested on a PC at
@ -74,11 +75,194 @@ Name: "{autodesktop}\Igneum Wallet"; Filename: "{app}\igneum-wallet.exe"; Parame
[Run]
Filename: "{app}\igneum-wallet.exe"; Parameters: "--launch"; Description: "Open Igneum Wallet now"; Flags: postinstall nowait skipifsilent runasoriginaluser
[UninstallRun]
Filename: "powershell.exe"; Parameters: "-NoProfile -ExecutionPolicy Bypass -File ""{app}\stop-igneum-wallet.ps1"""; Flags: runhidden waituntilterminated; RunOnceId: "StopIgneumWallet"
[UninstallDelete]
; the WebView2 cache is not in the install log; remove it with the folder. The vault in %LOCALAPPDATA%\igneum\wallet stays.
; The WebView2 cache is not in the install log; remove it with the folder. The vault in %LOCALAPPDATA%\igneum\wallet stays.
Type: filesandordirs; Name: "{app}"
[Code]
// /IGNOTA=1: the app's own updater (before 0.3.22) started this install; relaunch the app when the files are in.
// /IGNOTA=2: the 0.3.22 helper started it and relaunches the app itself (src/ota.rs WIN_HELPER): no relaunch here.
function OtaRelaunch: Boolean;
begin
Result := ExpandConstant('{param:IGNOTA|0}') = '1';
end;
// The 0.3.2-and-earlier install in Program Files (administrator), when this per-user install lands on top of it.
function OldAdminInstallDir: String;
begin
Result := ExpandConstant('{commonpf}\Igneum Wallet');
if not FileExists(Result + '\igneum-wallet.exe') then
Result := '';
end;
// Stops a running copy before the files are replaced (an upgrade over a running miner): the old copy's own
// stop-igneum-wallet.ps1 when one is installed (Program Files or here), else ours from the payload. Then, when someone is at
// the keyboard, offers to remove the Program Files copy (its uninstaller needs one administrator prompt; the data in
// %LOCALAPPDATA%\igneum is the same for both, nothing to copy). A silent (over-the-air) install never asks.
// The install marker (0.3.23): <LOCALAPPDATA>\igneum\app\install-running.flag while this installer works; the engine's
// update helper and the window host never relaunch the app while it is there (the wallet's updater and wallet-host.cpp read the same marker).
function MarkerPath: String;
begin
Result := ExpandConstant('{localappdata}\igneum\wallet\install-running.flag');
end;
procedure SetMarker;
begin
ForceDirectories(ExtractFileDir(MarkerPath));
SaveStringToFile(MarkerPath, GetDateTimeString('yyyy/mm/dd hh:nn:ss', '-', ':') + ' ' + '{#AppVersion}', False);
end;
procedure ClearMarker;
begin
DeleteFile(MarkerPath);
end;
// An installer started by the app's own job runner is a CHILD of the engine; the stop step then quits the engine, and
// the engine ends the job's whole process tree on its way out (src/jobrun.rs kill_tree: taskkill /T), the installer with
// it, between PrepareToInstall and the copy (PC 2, 0.3.24 take 2, 8 October 2026, 09:24Z: the host gone, no file copied,
// install-running.flag left behind, no ssDone and no DeinitializeSetup). So under a job (IGNEUM_JOB_ID set) this installer
// copies itself to the app's updates folder and runs that copy DETACHED through a one-shot scheduled task (a child of
// the Task Scheduler, outside the job's tree), with the same switches plus /IGDETACHED=1 and its own log, then exits
// without touching the app; the detached run installs and removes the task at its end. The job's proof is the --version
// wait and the detached log, not this process's exit code (1: "Setup failed to initialize").
const
DetachTask = 'Igneum Wallet install';
function DetachedRun: Boolean;
begin
Result := ExpandConstant('{param:IGDETACHED|0}') = '1';
end;
function UnderAppJob: Boolean;
begin
Result := (GetEnv('IGNEUM_JOB_ID') <> '') and (not DetachedRun);
end;
function DetachLogPath: String;
begin
Result := ExpandConstant('{localappdata}\igneum\wallet\updates\install-{#AppVersion}-detached.log');
end;
// Copies this installer beside the app's data and starts the copy through a one-shot task. True when the task started.
function RelaunchDetached: Boolean;
var
Dir, Copy, Args, Cmd: String;
ResultCode: Integer;
begin
Result := False;
Dir := ExpandConstant('{localappdata}\igneum\wallet\updates');
ForceDirectories(Dir);
Copy := Dir + '\Igneum-Wallet-Setup-{#AppVersion}.exe';
if not FileCopy(ExpandConstant('{srcexe}'), Copy, False) then
begin
Log('detach: could not copy ' + ExpandConstant('{srcexe}') + ' to ' + Copy);
exit;
end;
Args := '/VERYSILENT /NORESTART /SUPPRESSMSGBOXES /IGNOTA=' + ExpandConstant('{param:IGNOTA|0}') + ' /IGDETACHED=1 /LOG="' + DetachLogPath + '"';
Cmd := '/Create /F /SC ONCE /ST 23:59 /RL LIMITED /TN "' + DetachTask + '" /TR "\"' + Copy + '\" ' + Args + '"';
if not Exec('schtasks.exe', Cmd, '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or (ResultCode <> 0) then
begin
Log('detach: schtasks /Create returned ' + IntToStr(ResultCode));
exit;
end;
if not Exec('schtasks.exe', '/Run /TN "' + DetachTask + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) or (ResultCode <> 0) then
begin
Log('detach: schtasks /Run returned ' + IntToStr(ResultCode));
Exec('schtasks.exe', '/Delete /F /TN "' + DetachTask + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
exit;
end;
Log('detach: this installer runs under the app''s job ' + GetEnv('IGNEUM_JOB_ID') + '; the copy at ' + Copy + ' installs detached, log ' + DetachLogPath);
Result := True;
end;
procedure RemoveDetachTask;
var
ResultCode: Integer;
begin
if DetachedRun then
Exec('schtasks.exe', '/Delete /F /TN "' + DetachTask + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
procedure CurStepChanged(CurStep: TSetupStep);
begin
if CurStep = ssDone then
begin
ClearMarker;
RemoveDetachTask;
end;
end;
// Two refusals before anything runs (0.3.23 take 1, PC 2, 22:07 BST): the installer's file name must carry ITS version
// (a 0.3.21 kit once shipped a 0.3.22.0 host; a job that passes -Version reads this name), and a marker an earlier
// installer left behind (older than 15 min: it died) is cleared with a line, so nothing holds on it.
function InitializeSetup: Boolean;
var
Name: String;
begin
Result := True;
if UnderAppJob then
begin
// the detached copy does the install; this process ends here, before any marker or stop step, so the job's tree
// teardown finds nothing of the install to kill
Result := not RelaunchDetached;
if not Result then exit;
Log('detach: the detached start failed; installing in place under the job (the stop step will end this process with the engine)');
end;
Name := ExtractFileName(ExpandConstant('{srcexe}'));
if (Pos('Setup-', Name) > 0) and (Pos('Setup-{#AppVersion}', Name) = 0) then
begin
Log('REFUSED: this installer is {#AppVersion} but its file is named ' + Name + ' (the name must carry the version it installs)');
if not WizardSilent then
MsgBox('This installer is version {#AppVersion} but its file is named ' + Name + '. Download the installer again.', mbError, MB_OK);
Result := False;
exit;
end;
if FileExists(MarkerPath) then
begin
Log('a stale install marker was found at ' + MarkerPath + ' (an earlier installer did not finish); replaced by this run');
ClearMarker;
end;
end;
procedure DeinitializeSetup;
begin
ClearMarker;
end;
function PrepareToInstall(var NeedsRestart: Boolean): String;
var
StopScript, OldDir: String;
ResultCode: Integer;
begin
Result := '';
SetMarker;
OldDir := OldAdminInstallDir;
// Always THIS installer's stop step, never the installed one: PC 2, 0.3.23 take 1 (7 October 2026, 22:07 BST) ran the
// installed 0.3.21 script, which only asked the engine to quit; the window host lived on, Inno could not replace it,
// and the host started the old engine again 45 s later. The payload's script ends the host first, by its path under
// the install dir it is given, then the engine, then waits for every file to unlock (tools/ci/installer-stop-check.sh).
ExtractTemporaryFile('stop-igneum-wallet.ps1');
StopScript := ExpandConstant('{tmp}\stop-igneum-wallet.ps1');
Log('stop step: ' + StopScript + ' -Install "' + ExpandConstant('{app}') + '"');
if Exec('powershell.exe', '-NoProfile -ExecutionPolicy Bypass -File "' + StopScript + '" -Install "' + ExpandConstant('{app}') + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode) then
Log('stop step: exit ' + IntToStr(ResultCode) + ' (0 = every file of ours is free; 3 = a file stayed locked, CloseApplications is the fallback)')
else
Log('stop step: powershell did not start (' + SysErrorMessage(ResultCode) + '); CloseApplications is the fallback');
if (OldDir <> '') and FileExists(OldDir + '\stop-igneum-wallet.ps1') then
Exec('powershell.exe', '-NoProfile -ExecutionPolicy Bypass -File "' + StopScript + '" -Install "' + OldDir + '"', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
if (OldDir <> '') and (not WizardSilent) and FileExists(OldDir + '\unins000.exe') then
begin
if MsgBox('Igneum Wallet now installs in your user folder, so updates need no administrator prompt.' + #13#10#13#10 +
'An older copy is still in ' + OldDir + '. Remove it now? (one administrator prompt; your chain data, address and settings stay)',
mbConfirmation, MB_YESNO) = IDYES then
ShellExec('runas', OldDir + '\unins000.exe', '/VERYSILENT /SUPPRESSMSGBOXES /NORESTART', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);
end;
end;
procedure CurPageChanged(CurPageID: Integer);
begin
if CurPageID = wpFinished then

View file

@ -11,6 +11,7 @@
#
# build-installer.ps1 [-Payload <folder>] [-PayloadUrl <zip url>] [-Version 0.3.0] [-NoRcedit] [-NoWinget]
param(
[ValidateSet('miner', 'wallet')][string]$Product = 'miner',
[string]$Payload = '',
[string]$PayloadUrl = $(if ($env:IGNEUM_PAYLOAD_URL) { $env:IGNEUM_PAYLOAD_URL } else { 'https://dl.igneum.network/igneum-windows-app.zip' }),
[string]$Version = '0.3.0',
@ -18,6 +19,11 @@ param(
[switch]$NoWinget
)
$ErrorActionPreference = 'Stop'
# the wallet's arm (8 October 2026): the same steps with the wallet's names (payload folder igneum-windows-wallet, engine
# igneum-wallet.exe, the node, stop-igneum-wallet.ps1, Igneum-Wallet.iss, Igneum-Wallet-Setup-<v>.exe); -Product miner is the default
$P = if ($Product -eq 'wallet') { @{ Folder = 'igneum-windows-wallet'; Zip = 'igneum-windows-wallet.zip'; Engine = 'igneum-wallet.exe'; Stop = 'stop-igneum-wallet.ps1'; Exes = @('igneum-wallet.exe', 'igneumd.exe'); Iss = 'Igneum-Wallet.iss'; Setup = 'Igneum-Wallet-Setup'; Name = 'Igneum Wallet'; Required = @('igneum-wallet.exe', 'stop-igneum-wallet.ps1', 'igneumd.exe') } }
else { @{ Folder = 'igneum-windows-app'; Zip = 'igneum-windows-app.zip'; Engine = 'igneum-app.exe'; Stop = 'stop-igneum.ps1'; Exes = @('igneum-app.exe', 'igneumd.exe', 'igneum-miner.exe'); Iss = 'Igneum-Miner.iss'; Setup = 'Igneum-Miner-Setup'; Name = 'Igneum Miner'; Required = @('igneum-app.exe', 'stop-igneum.ps1', 'igneumd.exe', 'igneum-miner.exe') } }
if ($Product -eq 'wallet' -and -not $env:IGNEUM_PAYLOAD_URL) { $PayloadUrl = 'https://dl.igneum.network/igneum-windows-wallet.zip' }
$ProgressPreference = 'SilentlyContinue'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
@ -96,7 +102,7 @@ Say "art: $art"
# ---- 3. the payload --------------------------------------------------------------------------------------------------
function Test-PayloadDir([string]$dir) {
if (-not $dir -or -not (Test-Path $dir)) { return $false }
foreach ($f in @('igneum-app.exe', 'stop-igneum.ps1', 'igneumd.exe', 'igneum-miner.exe')) {
foreach ($f in $P.Required) {
if (-not (Test-Path (Join-Path $dir $f))) { return $false }
}
return $true
@ -106,7 +112,7 @@ if ($Payload) {
if (-not (Test-PayloadDir $Payload)) { throw "-Payload $Payload does not hold START-IGNEUM.bat, the .ps1 files and both exes." }
$source = (Resolve-Path $Payload).Path
} else {
foreach ($dir in @((Join-Path $here 'igneum-windows-app'), (Join-Path $here '..\igneum-windows-app'), (Join-Path $here 'payload'), (Join-Path $here '..\..\igneum-windows-app'))) {
foreach ($dir in @((Join-Path $here $P.Folder), (Join-Path $here ('..\' + $P.Folder)), (Join-Path $here 'payload'), (Join-Path $here ('..\..\' + $P.Folder)))) {
if (Test-PayloadDir $dir) { $source = (Resolve-Path $dir).Path; break }
}
}
@ -117,8 +123,8 @@ if (-not $source) {
Invoke-WebRequest -Uri $PayloadUrl -OutFile $zip -UseBasicParsing
if (Test-Path $unpacked) { Remove-Item -Recurse -Force $unpacked }
Expand-Archive -Path $zip -DestinationPath $unpacked -Force
$hit = Get-ChildItem -Path $unpacked -Recurse -Filter 'igneum-app.exe' | Select-Object -First 1
if (-not $hit -or -not (Test-PayloadDir $hit.DirectoryName)) { throw "the zip from $PayloadUrl holds no complete payload (igneum-app.exe, igneumd.exe, igneum-miner.exe, stop-igneum.ps1)." }
$hit = Get-ChildItem -Path $unpacked -Recurse -Filter $P.Engine | Select-Object -First 1
if (-not $hit -or -not (Test-PayloadDir $hit.DirectoryName)) { throw "the zip from $PayloadUrl holds no complete payload ($($P.Required -join ', '))." }
$source = $hit.DirectoryName
}
Say "payload source: $source"
@ -148,11 +154,11 @@ Say ("payload: {0} files, {1:N1} MB" -f $count, ($bytes / 1MB))
# ---- 5. icon and version block in the exes ---------------------------------------------------------------------------
$rcedit = $null
foreach ($exeName in @('igneum-app.exe', 'igneumd.exe', 'igneum-miner.exe')) {
foreach ($exeName in $P.Exes) {
$exe = Join-Path $payloadDir $exeName
if (-not (Test-Path $exe)) { continue }
$info = (Get-Item $exe).VersionInfo
if ($info.ProductName -eq 'Igneum Miner') { Say "$exeName already carries the Igneum Miner version block (relinked on the Mac)"; continue }
if ($info.ProductName -eq $P.Name) { Say "$exeName already carries the $($P.Name) version block (relinked on the Mac)"; continue }
if ($NoRcedit) { Say "$exeName has no version block; -NoRcedit given, so Explorer shows it without the coin icon"; continue }
if (-not $rcedit) {
if ($env:RCEDIT -and (Test-Path $env:RCEDIT)) { $rcedit = $env:RCEDIT }
@ -161,10 +167,10 @@ foreach ($exeName in @('igneum-app.exe', 'igneumd.exe', 'igneum-miner.exe')) {
if (-not (Test-Path $rcedit)) { Say "downloading rcedit from $rceditUrl"; Invoke-WebRequest -Uri $rceditUrl -OutFile $rcedit -UseBasicParsing }
}
}
$desc = switch ($exeName) { 'igneumd.exe' { 'Igneum Miner node' } 'igneum-app.exe' { 'Igneum Miner engine' } default { 'Igneum Miner' } }
$desc = switch ($exeName) { 'igneumd.exe' { $P.Name + ' node' } 'igneum-app.exe' { 'Igneum Miner engine' } 'igneum-wallet.exe' { 'Igneum Wallet engine' } default { $P.Name } }
$rcArgs = @($exe, '--set-icon', (Join-Path $art 'igneum.ico'),
'--set-version-string', 'CompanyName', 'Igneum',
'--set-version-string', 'ProductName', 'Igneum Miner',
'--set-version-string', 'ProductName', $P.Name,
'--set-version-string', 'FileDescription', $desc,
'--set-version-string', 'OriginalFilename', $exeName,
'--set-version-string', 'LegalCopyright', 'Igneum. Nothing is bought or sold.',
@ -172,16 +178,16 @@ foreach ($exeName in @('igneum-app.exe', 'igneumd.exe', 'igneum-miner.exe')) {
'--set-product-version', $Version)
& $rcedit @rcArgs
if ($LASTEXITCODE -ne 0) { throw "rcedit failed on $exeName (exit $LASTEXITCODE)" }
Say "$exeName stamped: coin icon, Igneum Miner $Version"
Say "$exeName stamped: coin icon, $($P.Name) $Version"
}
# ---- 6. compile ------------------------------------------------------------------------------------------------------
$iss = Join-Path $here 'Igneum-Miner.iss'
$iss = Join-Path $here $P.Iss
$isccArgs = @('/Qp', "/DPayload=$payloadDir", "/DArtDir=$art", "/DAppVersion=$Version", "/O$dist", $iss)
Say "compiling $iss"
& $iscc @isccArgs
if ($LASTEXITCODE -ne 0) { throw "ISCC failed (exit $LASTEXITCODE)" }
$out = Join-Path $dist "Igneum-Miner-Setup-$Version.exe"
$out = Join-Path $dist ($P.Setup + "-$Version.exe")
if (-not (Test-Path $out)) { throw "ISCC finished but $out is missing" }
$size = (Get-Item $out).Length
Say ("done: {0} ({1:N1} MB)" -f $out, ($size / 1MB))

View file

@ -0,0 +1,67 @@
#!/usr/bin/env bash
# Assembles the Windows payload for Igneum Wallet (8 October 2026, the wallet's Windows arm: the miner's make-payload.sh
# step for step, with the wallet's pieces) and zips it: packaging/windows/igneum-windows-wallet/ and the zip at $1
# (default ~/Desktop/igneum-windows-wallet.zip). On the PC: extract the zip next to packaging\windows, run
# app\windows\BUILD-WALLET-APP.bat (the WebView2 window host), then packaging\windows\build-installer.ps1 -Product wallet.
#
# What goes in:
# igneum-wallet.exe the engine (app/igneum-wallet), built on the MSVC target on a PC or by a job
# (IGNEUM_WALLET_EXE names it; the default is the PC job's cargo output)
# igneumd.exe the node the wallet starts when the miner's is not on the machine (the same pin as the
# miner's payload: IGNEUM_WIN_RELEASE)
# lib*.dll the mingw runtime DLLs when the node exe needs them, as the miner's payload ships them
# igneum-wallet.json the update manifest URL, the public RPC, the add-network page, the consensus override
# (packaging/mac/packaged-config.sh write_wallet_config; the token stays out of the repo)
# stop-igneum-wallet.ps1 what the installer runs before an upgrade and on uninstall
# app\windows\ wallet-host.cpp, wallet-host.rc, wallet-version.h, biometric.h, BUILD-WALLET-APP.bat
# Igneum Wallet.exe the window host, when app/windows/dist/ holds one (BUILD-WALLET-APP.bat ran before this)
# Nothing of the miner's rides here: no GPU workers, no prover, no WSL2.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' "$ROOT/app/igneum-wallet/Cargo.toml" | head -1)"
OUT="${1:-$HOME/Desktop/igneum-windows-wallet.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_WALLET_EXE:-$ROOT/app/igneum-wallet/target/x86_64-pc-windows-msvc/release/igneum-wallet.exe}"
MINGW=/opt/homebrew/opt/mingw-w64/toolchain-x86_64/x86_64-w64-mingw32
STAGE="$HERE/igneum-windows-wallet"
. "$ROOT/packaging/mac/packaged-config.sh"
[ -f "$ENGINE" ] || { echo "no $ENGINE: build the wallet engine for Windows first (cd app/igneum-wallet && cargo build --release, on a PC or by job; IGNEUM_WALLET_EXE names another)" >&2; exit 1; }
[ -f "$REL/igneumd.exe" ] || { echo "no $REL/igneumd.exe; the node's Windows build is missing (IGNEUM_WIN_RELEASE)" >&2; exit 1; }
rm -rf "$STAGE"
mkdir -p "$STAGE/app/windows/art"
cp "$ENGINE" "$STAGE/igneum-wallet.exe"
cp "$REL/igneumd.exe" "$STAGE/igneumd.exe"
for dll in lib/libstdc++-6.dll lib/libgcc_s_seh-1.dll bin/libwinpthread-1.dll; do
name="$(basename "$dll")"
if [ -f "$REL/$name" ]; then cp "$REL/$name" "$STAGE/"
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 (the node exe is linked -static, so it may not need it)"; fi
done
[ -x "$HERE/check-runtime-dlls.sh" ] && "$HERE/check-runtime-dlls.sh" "$STAGE" || true
cp "$ROOT/app/windows/wallet-host.cpp" "$ROOT/app/windows/wallet-host.rc" "$ROOT/app/windows/wallet-version.h" "$ROOT/app/windows/biometric.h" "$ROOT/app/windows/BUILD-WALLET-APP.bat" "$STAGE/app/windows/"
cp "$ROOT/brand/icons/igneum.ico" "$STAGE/app/windows/art/"
if [ -f "$ROOT/app/windows/dist/Igneum Wallet.exe" ]; then cp "$ROOT/app/windows/dist/Igneum Wallet.exe" "$STAGE/"; echo "window host: Igneum Wallet.exe from app/windows/dist"; fi
cp "$HERE/stop-igneum-wallet.ps1" "$STAGE/stop-igneum-wallet.ps1"
write_wallet_config "$STAGE/igneum-wallet.json"
cat > "$STAGE/README.txt" <<TXT
Igneum Wallet $VERSION for Windows (payload). Test network; nothing is bought or sold.
Nobody from Igneum will ever ask for your words or your key.
This folder is what the installer packs. To build the installer on a PC:
1. app\\windows\\BUILD-WALLET-APP.bat builds "Igneum Wallet.exe" (the window; needs Visual Studio)
2. packaging\\windows\\build-installer.ps1 -Product wallet builds Igneum-Wallet-Setup-$VERSION.exe (Inno Setup via winget)
To run without the installer: double-click igneum-wallet.exe (the wallet opens in your browser).
TXT
for f in "$STAGE/README.txt" "$STAGE/stop-igneum-wallet.ps1" "$STAGE/app/windows/BUILD-WALLET-APP.bat"; do
[ -f "$f" ] && perl -pi -e 's/\r?\n/\r\n/' "$f"
done
rm -f "$OUT"
if command -v zip >/dev/null 2>&1; then (cd "$HERE" && zip -qr "$OUT" "igneum-windows-wallet" -x '*.DS_Store')
elif command -v 7z >/dev/null 2>&1; then (cd "$HERE" && 7z a -tzip -bso0 -bsp0 "$OUT" "igneum-windows-wallet" '-xr!.DS_Store')
else echo "neither zip nor 7z is on PATH" >&2; exit 1; fi
echo "staged $STAGE"
ls -la "$OUT"

View file

@ -11,6 +11,9 @@
# every job its own name since 5 October 2026 night: with one shared name a job
# published while another agent's pack landed pinned THAT agent's sources, three
# times in one evening; zips older than two days are pruned here)
# --wallet packs app/igneum-wallet too and adds its Windows build unit (igneum-wallet, the wallet's engine; 8 October
# 2026, the wallet's Windows arm): the wallet's Cargo.toml reaches the node at ../../vendor/igneum-node, so the
# zip carries vendor/igneum-node as a symlink to node/ (unzip restores it inside the distro); needs the node
# --no-node packs and builds the app engine only (a UI or engine change with no node change: miner-ui-2, 5 October
# 2026); the manifest's node block says "none" and the builds list has no node entry.
#
@ -46,6 +49,7 @@ while [ $# -gt 0 ]; do
--app-tests) APP_TESTS="$2"; shift 2 ;;
--no-app) WITH_APP=0; shift ;;
--no-node) WITH_NODE=0; shift ;;
--wallet) WITH_WALLET=1; shift ;;
--no-deploy) DEPLOY=0; shift ;;
--out) OUT="$2"; shift 2 ;;
--name) NAME="$2"; shift 2 ;;
@ -101,6 +105,13 @@ fi
if [ "$WITH_APP" = 1 ]; then
echo "packing app/igneum-app ($APP_VERSION) and brand/icons"
rsync -a --exclude 'target' --exclude '.DS_Store' "$ROOT/app/igneum-app/" "$STAGE/app/igneum-app/"
if [ "${WITH_WALLET:-0}" = 1 ]; then
[ "${WITH_NODE:-1}" = 1 ] || { echo "--wallet needs the node in the zip (the wallet links its rpc crates)" >&2; exit 1; }
echo "packing app/igneum-wallet and app/igneum-common (the wallet's engine), vendor/igneum-node -> node"
rsync -a --exclude 'target' --exclude '.DS_Store' "$ROOT/app/igneum-wallet/" "$STAGE/app/igneum-wallet/"
rsync -a --exclude 'target' --exclude '.DS_Store' "$ROOT/app/igneum-common/" "$STAGE/app/igneum-common/"
mkdir -p "$STAGE/vendor" && ln -sfn ../node "$STAGE/vendor/igneum-node"
fi
rsync -a --exclude '.DS_Store' "$ROOT/brand/icons/" "$STAGE/brand/icons/"
cp "$ROOT/brand/igneum-coin-1024.png" "$STAGE/brand/" # app/igneum-app/src/server.rs embeds ../../../brand/igneum-coin-1024.png
fi
@ -119,6 +130,10 @@ if node_tests.strip() and with_node == "1":
tests.append({"dir": "node", "packages": node_tests.split()})
if with_app == "1":
builds.append({"dir": "app/igneum-app", "packages": ["igneum-app"], "bins": ["igneum-app"], "targets": ["linux", "windows"], "optional_on": ["linux"]})
import os
if os.environ.get("WITH_WALLET") == "1":
builds.append({"dir": "app/igneum-wallet", "packages": ["igneum-wallet"], "bins": ["igneum-wallet"], "targets": ["windows"]})
tests.append({"dir": "app/igneum-wallet", "packages": ["igneum-wallet"]})
if app_tests.strip():
tests.append({"dir": "app/igneum-app", "packages": app_tests.split()})
m = {
@ -137,7 +152,7 @@ PY
# taken as unchanged. 5 October 2026: the 0.3.6 job compiled the new daemon against the cached 0.3.5 consensus crate.
find "$TMP" -type f -exec touch {} +
rm -f "$OUT"
(cd "$TMP" && zip -qr "$OUT" "igneum-build-inputs" "igneum-pow" -x '*.DS_Store')
(cd "$TMP" && zip -qry "$OUT" "igneum-build-inputs" "igneum-pow" -x '*.DS_Store')
SUM="$(shasum -a 256 "$OUT" | awk '{print $1}')"
SIZE="$(stat -f %z "$OUT")"
printf '%s\n' "$SUM" > "${OUT%.zip}.sha256"

View file

@ -0,0 +1,46 @@
#!/usr/bin/env bash
# Publishes the Igneum Wallet's Windows kit (8 October 2026, the wallet's Windows arm): the pieces the PC 2 run job
# relay/playbooks/wallet-kit-pc2.ps1 needs that are not binaries: app/windows (the host sources and BUILD-WALLET-APP.bat),
# packaging/windows (build-installer.ps1, Igneum-Wallet.iss, stop-igneum-wallet.ps1, LICENSE.txt), brand/icons, and the
# wallet's packaged configuration igneum-wallet.json (the manifest URL from the token file, the public RPC, the
# consensus override; packaging/mac/packaged-config.sh write_wallet_config; the token never in the repo). The engine and
# the node are NOT in this zip: the build job makes the engine (push-build-inputs.sh --wallet) and the node is the pin.
#
# packaging/windows/push-wallet-kit.sh [--no-deploy]
#
# The zip goes to the downloads folder (dl/<token>/igneum-wallet-kit-<version>.zip) with its sha256; --no-deploy leaves the
# deploy to the main session (the same Vercel deploy as push-inputs.sh). The playbook's IGNEUM_WALLET_KIT_URL is the printed URL.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
ROOT="$(cd "$HERE/../.." && pwd)"
DEPLOY=1; [ "${1:-}" = "--no-deploy" ] && DEPLOY=0
VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' "$ROOT/app/igneum-wallet/Cargo.toml" | head -1)"
TOKEN_FILE="$HOME/.config/igneum/dl-token"
[ -f "$TOKEN_FILE" ] || { echo "no $TOKEN_FILE" >&2; exit 1; }
TOKEN="$(tr -d '[:space:]' < "$TOKEN_FILE")"
DLSITE="${IGNEUM_DLSITE:-}"
[ -n "$DLSITE" ] || { [ -f "$HOME/.config/igneum/dlsite-dir" ] && DLSITE="$(tr -d '[:space:]' < "$HOME/.config/igneum/dlsite-dir")"; } || true
[ -n "$DLSITE" ] && [ -d "$DLSITE/dl/$TOKEN" ] || { echo "no downloads folder: set IGNEUM_DLSITE or ~/.config/igneum/dlsite-dir (it must hold dl/<token>/)" >&2; exit 1; }
. "$ROOT/packaging/mac/packaged-config.sh"
TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
STAGE="$TMP/igneum-wallet-kit"
mkdir -p "$STAGE/app/windows" "$STAGE/packaging/windows" "$STAGE/brand/icons"
for f in wallet-host.cpp wallet-host.rc wallet-version.h biometric.h BUILD-WALLET-APP.bat; do cp "$ROOT/app/windows/$f" "$STAGE/app/windows/"; done
for f in build-installer.ps1 Igneum-Wallet.iss stop-igneum-wallet.ps1; do cp "$ROOT/packaging/windows/$f" "$STAGE/packaging/windows/"; done
[ -f "$ROOT/packaging/windows/LICENSE.txt" ] && cp "$ROOT/packaging/windows/LICENSE.txt" "$STAGE/packaging/windows/" || cp "$ROOT/LICENSE" "$STAGE/packaging/windows/LICENSE.txt" 2>/dev/null || true
cp "$ROOT"/brand/icons/igneum.ico "$ROOT"/brand/icons/inno-wizard-*.bmp "$STAGE/brand/icons/" 2>/dev/null || cp "$ROOT/brand/icons/igneum.ico" "$STAGE/brand/icons/"
write_wallet_config "$STAGE/packaging/windows/igneum-wallet.json"
for f in "$STAGE"/app/windows/*.bat "$STAGE"/packaging/windows/*.ps1 "$STAGE"/packaging/windows/*.iss; do [ -f "$f" ] && perl -pi -e 's/\r?\n/\r\n/' "$f"; done
NAME="igneum-wallet-kit-$VERSION.zip"
OUT="$DLSITE/dl/$TOKEN/$NAME"
rm -f "$OUT"
(cd "$TMP" && zip -qr "$OUT" "igneum-wallet-kit" -x '*.DS_Store')
shasum -a 256 "$OUT" | awk '{print $1}' > "$OUT.sha256"
echo "kit: $OUT ($(stat -f %z "$OUT") bytes, sha256 $(cat "$OUT.sha256" | cut -c1-16)...)"
echo "IGNEUM_WALLET_KIT_URL=https://dl.igneum.network/dl/<token>/$NAME"
if [ "$DEPLOY" = 1 ]; then
(cd "$DLSITE" && npx --yes vercel@latest --global-config "$HOME/.config/igneum/vercel" deploy --prod --yes 2>&1 | sed "s#$TOKEN#<token>#g"; exit "${PIPESTATUS[0]}") || { echo "the deploy failed" >&2; exit 1; }
curl -fsSL -o /dev/null -w "live: %{http_code} %{size_download} bytes\n" "https://dl.igneum.network/dl/$TOKEN/$NAME" || echo "not reachable yet (the edge serves the previous deployment for some seconds)"
else
echo "not deployed: cd $DLSITE && npx --yes vercel@latest --global-config ~/.config/igneum/vercel deploy --prod --yes"
fi

View file

@ -0,0 +1,58 @@
# Stops a running Igneum Wallet on this PC. Run by the installer before an upgrade and on uninstall; also the Start Menu
# "Stop Igneum Wallet" entry. 0.3.23 (7 October 2026, PC 2 at 20:54 BST: an installer quit the engine, the window host's
# restart ladder started the old engine again 10 s later, the host stayed locked and no file was replaced): the window
# host goes FIRST, by its path under the install folder, so nothing can restart the engine; then the engine is asked to
# quit through its local API (the node after it), waited for, and whatever of ours is left is ended by path.
# -Install <dir>: the install folder whose processes to end. The installer passes its {app} (this script then runs from
# the installer's temp folder, so its own location is not the install dir); the Start Menu entry and uninstall run it
# from the install folder itself and give nothing.
param([string]$Install = '')
$ErrorActionPreference = 'Continue'
$install = if ($Install) { $Install.TrimEnd('\') } else { Split-Path -Parent $MyInvocation.MyCommand.Path }
Write-Host "stopping Igneum Wallet under $install"
function Ours { @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.ExecutablePath -and $_.ExecutablePath.StartsWith($install, [StringComparison]::OrdinalIgnoreCase) }) }
foreach ($h in (Ours | Where-Object { $_.Name -eq 'Igneum Wallet.exe' })) {
Write-Host "ending the window host (pid $($h.ProcessId)) first, so it cannot start the engine again"
Stop-Process -Id $h.ProcessId -Force -ErrorAction SilentlyContinue
}
$urlFile = Join-Path $env:LOCALAPPDATA 'igneum\wallet\wallet.url'
$asked = $false
if (Test-Path $urlFile) {
$url = (Get-Content $urlFile -Raw).Trim()
if ($url) {
try {
Invoke-WebRequest -Uri ($url + 'api/quit') -Method POST -Body '{}' -ContentType 'application/json' -UseBasicParsing -TimeoutSec 5 | Out-Null
$asked = $true
Write-Host 'asked the engine to stop (the node after it)'
} catch { }
}
}
if ($asked) {
$deadline = (Get-Date).AddSeconds(50)
while ((Get-Date) -lt $deadline) {
$alive = @(Ours | Where-Object { $_.Name -eq 'igneum-wallet.exe' -or $_.Name -eq 'igneumd.exe' })
if ($alive.Count -eq 0) { break }
Start-Sleep -Milliseconds 500
}
}
foreach ($p in (Ours)) {
Write-Host "ending $($p.Name) (pid $($p.ProcessId))"
Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue
}
# the files must be free before Inno copies: each exe opened for write, up to 30 s; one clear line for a file that stays locked
# (the installer's /CLOSEAPPLICATIONS is the fallback for that one)
$deadline = (Get-Date).AddSeconds(30)
$locked = @()
do {
$locked = @()
foreach ($n in @('Igneum Wallet.exe', 'igneum-wallet.exe', 'igneumd.exe', 'igneum-miner.exe', 'igneum-worker-cuda.exe', 'igneum-worker-opencl.exe')) {
$f = Join-Path $install $n
if (-not (Test-Path $f)) { continue }
try { $fs = [IO.File]::Open($f, [IO.FileMode]::Open, [IO.FileAccess]::ReadWrite, [IO.FileShare]::None); $fs.Close() } catch { $locked += $n }
}
if ($locked.Count -eq 0) { break }
Start-Sleep -Milliseconds 500
} while ((Get-Date) -lt $deadline)
if ($locked.Count) { Write-Host ("STILL LOCKED after 30 s: " + ($locked -join ', ') + " (the installer's close-applications step is the fallback; a locked file is why an install leaves the old version in place)") } else { Write-Host 'every file of ours is free' }
Write-Host 'Igneum Wallet is stopped.'
if ($locked.Count) { exit 3 }

View file

@ -0,0 +1,129 @@
# The Igneum Wallet's Windows kit on PC 2 (8 October 2026, the wallet's Windows arm; the miner's chain step for step):
# a run job that builds "Igneum Wallet.exe" (the WebView2 host, MSVC, app\windows\BUILD-WALLET-APP.bat), assembles the
# payload from the build job's engine (igneum-wallet.exe from the inputs zip's Windows build) and the node pin, builds
# Igneum-Wallet-Setup-<v>.exe with Inno Setup (packaging\windows\build-installer.ps1 -Product wallet), runs the smoke
# (igneum-wallet.exe --version, the host's version block), and, when an older wallet is installed and running here,
# runs the installer over it end to end (rule 14: a text test is a lint; the installer is tested by running it over a
# running older app) and reads the installed version back. Every file goes back as a drop on the relay (the installer,
# its sha256, the host's sha256, the log). Every line that matters starts with RESULT. The miners keep running: this is
# CPU work under low priority; nothing of the miner app is touched.
#
# Inputs (job params or the environment): IGNEUM_WALLET_KIT_URL the zip with the repo's packaging\windows,
# app\windows, brand\icons, packaging\mac\packaged-config.sh outputs (the wallet-kit zip packaging/windows/push-wallet-kit.sh
# writes); IGNEUM_WALLET_EXE_URL the engine built by the build job (a .zst from its outputs, or a plain .exe);
# IGNEUM_NODE_EXE_URL igneumd.exe at the pin; IGNEUM_WALLET_VERSION the version the kit carries.
$ErrorActionPreference = 'Continue'
function Say($t) { Write-Output "RESULT $t" }
function Stage($t) { Write-Output ("STAGE " + $t + " " + (Get-Date).ToUniversalTime().ToString('HH:mm:ssZ')) }
$job = $env:IGNEUM_JOB_DIR; if (-not $job) { $job = Join-Path $env:TEMP ('wallet-kit-' + (Get-Date -Format 'yyyyMMdd-HHmmss')) }
New-Item -ItemType Directory -Force -Path $job | Out-Null
$kitUrl = $env:IGNEUM_WALLET_KIT_URL; $exeUrl = $env:IGNEUM_WALLET_EXE_URL; $nodeUrl = $env:IGNEUM_NODE_EXE_URL; $ver = $env:IGNEUM_WALLET_VERSION
if (-not $kitUrl -or -not $exeUrl -or -not $nodeUrl -or -not $ver) { Say 'error=params reason=IGNEUM_WALLET_KIT_URL,_IGNEUM_WALLET_EXE_URL,_IGNEUM_NODE_EXE_URL_and_IGNEUM_WALLET_VERSION_are_needed'; exit 2 }
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
function Fetch($url, $to) { try { Invoke-WebRequest -Uri $url -OutFile $to -UseBasicParsing -TimeoutSec 600; return (Test-Path $to) } catch { Say ("fetch failed " + $url + ": " + $_.Exception.Message); return $false } }
function Sha($p) { (Get-FileHash -LiteralPath $p -Algorithm SHA256).Hash.ToLower() }
function Drop($path, $title) {
if (-not (Test-Path -LiteralPath $path)) { Say ("drop skipped, missing " + $path); return }
$bytes = [IO.File]::ReadAllBytes($path)
if ($bytes.Length -gt 60MB) { Say ("drop skipped, " + (Split-Path -Leaf $path) + " is " + $bytes.Length + " bytes (over the relay's cap); its sha256 is in the log"); return }
try {
$hdr = @{ 'x-igneum-key' = $env:IGNEUM_INTAKE_KEY; 'x-file-name' = (Split-Path -Leaf $path); 'x-title' = $title; 'x-from' = 'pc2' }
$r = Invoke-RestMethod -Uri 'https://relay.igneum.network/api/relay?fn=drop' -Method Post -Headers $hdr -Body $bytes -ContentType 'application/octet-stream' -TimeoutSec 600
Say ("dropped " + (Split-Path -Leaf $path) + " " + $bytes.Length + " bytes sha256=" + (Sha $path))
} catch { Say ("drop failed for " + (Split-Path -Leaf $path) + ": " + $_.Exception.Message) }
}
# ---- 1. the kit: the repo's packaging and host sources ----
Stage 'fetch'
$kitZip = Join-Path $job 'wallet-kit.zip'
if (-not (Fetch $kitUrl $kitZip)) { Say 'error=kit_fetch'; exit 2 }
$kit = Join-Path $job 'kit'
Expand-Archive -LiteralPath $kitZip -DestinationPath $kit -Force
$root = Get-ChildItem -Path $kit -Directory | Select-Object -First 1
if (-not $root) { Say 'error=kit_empty'; exit 2 }
$root = $root.FullName
foreach ($need in @('app\windows\BUILD-WALLET-APP.bat', 'app\windows\wallet-host.cpp', 'packaging\windows\build-installer.ps1', 'packaging\windows\Igneum-Wallet.iss', 'packaging\windows\stop-igneum-wallet.ps1', 'brand\icons\igneum.ico', 'packaging\windows\igneum-wallet.json')) {
if (-not (Test-Path (Join-Path $root $need))) { Say ("error=kit_incomplete missing=" + $need); exit 2 }
}
Say ("kit " + (Sha $kitZip) + " unpacked at " + $root)
# the engine and the node
$payload = Join-Path $root 'packaging\windows\igneum-windows-wallet'
New-Item -ItemType Directory -Force -Path $payload | Out-Null
$exeDl = Join-Path $job 'engine.dl'
if (-not (Fetch $exeUrl $exeDl)) { Say 'error=engine_fetch'; exit 2 }
if ($exeUrl -match '\.zst$') {
$zstd = Get-Command zstd -ErrorAction SilentlyContinue
if (-not $zstd) { Say 'error=no_zstd reason=the_engine_is_a_.zst_and_zstd.exe_is_not_on_PATH'; exit 2 }
& $zstd.Source -d -q -f $exeDl -o (Join-Path $payload 'igneum-wallet.exe')
} else { Copy-Item -LiteralPath $exeDl -Destination (Join-Path $payload 'igneum-wallet.exe') -Force }
$nodeDl = Join-Path $job 'igneumd.dl'
if (-not (Fetch $nodeUrl $nodeDl)) { Say 'error=node_fetch'; exit 2 }
if ($nodeUrl -match '\.zst$') { & (Get-Command zstd).Source -d -q -f $nodeDl -o (Join-Path $payload 'igneumd.exe') } else { Copy-Item -LiteralPath $nodeDl -Destination (Join-Path $payload 'igneumd.exe') -Force }
$engVer = & (Join-Path $payload 'igneum-wallet.exe') --version 2>&1
Say ("engine " + $engVer + " sha256=" + (Sha (Join-Path $payload 'igneum-wallet.exe')))
if ("$engVer" -notmatch ('igneum-wallet ' + [regex]::Escape($ver))) { Say ("error=engine_version expected=" + $ver + " got=" + $engVer); exit 2 }
Say ("node " + ((& (Join-Path $payload 'igneumd.exe') --version 2>&1) -join ' ') + " sha256=" + (Sha (Join-Path $payload 'igneumd.exe')))
Copy-Item -LiteralPath (Join-Path $root 'packaging\windows\stop-igneum-wallet.ps1') -Destination $payload -Force
Copy-Item -LiteralPath (Join-Path $root 'packaging\windows\igneum-wallet.json') -Destination $payload -Force
New-Item -ItemType Directory -Force -Path (Join-Path $payload 'app\windows\art') | Out-Null
foreach ($f in @('wallet-host.cpp', 'wallet-host.rc', 'wallet-version.h', 'biometric.h', 'BUILD-WALLET-APP.bat')) { Copy-Item -LiteralPath (Join-Path $root ('app\windows\' + $f)) -Destination (Join-Path $payload 'app\windows') -Force }
Copy-Item -LiteralPath (Join-Path $root 'brand\icons\igneum.ico') -Destination (Join-Path $payload 'app\windows\art') -Force
# ---- 2. the window host (MSVC, WebView2 SDK from NuGet) ----
Stage 'host'
Push-Location (Join-Path $root 'app\windows')
$hostLog = Join-Path $job 'host-build.log'
cmd /c 'BUILD-WALLET-APP.bat < nul' > $hostLog 2>&1
Pop-Location
$hostExe = Join-Path $root 'app\windows\dist\Igneum Wallet.exe'
if (-not (Test-Path -LiteralPath $hostExe)) { Say 'error=host_build see=host-build.log'; Drop $hostLog 'wallet-kit host-build.log'; exit 2 }
Copy-Item -LiteralPath $hostExe -Destination $payload -Force
$hostInfo = (Get-Item -LiteralPath $hostExe).VersionInfo
Say ("host built: " + $hostInfo.ProductName + " " + $hostInfo.ProductVersion + " sha256=" + (Sha $hostExe))
if ($hostInfo.ProductName -ne 'Igneum Wallet' -or $hostInfo.ProductVersion -notlike ($ver + '*')) { Say ("error=host_version_block got=" + $hostInfo.ProductName + " " + $hostInfo.ProductVersion); exit 2 }
# ---- 3. the installer ----
Stage 'installer'
$instLog = Join-Path $job 'installer-build.log'
Push-Location (Join-Path $root 'packaging\windows')
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File '.\build-installer.ps1' -Product wallet -Payload $payload -Version $ver *> $instLog
Pop-Location
$setup = Join-Path $root ('packaging\windows\dist\Igneum-Wallet-Setup-' + $ver + '.exe')
if (-not (Test-Path -LiteralPath $setup)) { Say 'error=installer_build see=installer-build.log'; Drop $instLog 'wallet-kit installer-build.log'; exit 2 }
Say ("installer built: " + (Split-Path -Leaf $setup) + " " + (Get-Item -LiteralPath $setup).Length + " bytes sha256=" + (Sha $setup))
# ---- 4. the smoke run, and the installer over a running older wallet when one is here ----
Stage 'smoke'
$old = Join-Path $env:LOCALAPPDATA 'Programs\Igneum Wallet\igneum-wallet.exe'
if (Test-Path -LiteralPath $old) {
$oldVer = & $old --version 2>&1
Say ("installed before: " + $oldVer)
$running = @(Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | Where-Object { $_.ExecutablePath -and $_.ExecutablePath.StartsWith((Split-Path -Parent $old), [StringComparison]::OrdinalIgnoreCase) })
Say ("wallet processes running before the install: " + $running.Count)
$log = Join-Path $job 'install.log'
$p = Start-Process -FilePath $setup -ArgumentList @('/VERYSILENT', '/NORESTART', '/SUPPRESSMSGBOXES', ('/LOG="' + $log + '"')) -PassThru -Wait
Say ("installer exit " + $p.ExitCode + " (1 is the detached hand-off under a job; the detached log says the rest)")
$deadline = (Get-Date).AddMinutes(6); $newVer = ''
while ((Get-Date) -lt $deadline) { Start-Sleep -Seconds 10; try { $newVer = (& $old --version 2>&1) -join ' ' } catch { $newVer = '' }; if ($newVer -match [regex]::Escape($ver)) { break } }
Say ("installed after: " + $newVer)
if ($newVer -notmatch [regex]::Escape($ver)) { Say 'error=install_did_not_land' }
$detached = Join-Path $env:LOCALAPPDATA ('igneum\wallet\updates\install-' + $ver + '-detached.log')
if (Test-Path -LiteralPath $detached) { Drop $detached 'wallet-kit detached install log' }
if (Test-Path -LiteralPath $log) { Drop $log 'wallet-kit install log' }
} else {
Say 'no installed wallet here: the installer is built and smoke-run only (install it once by hand or by a run job to test an upgrade next time)'
$tmpv = & (Join-Path $payload 'igneum-wallet.exe') --version 2>&1
Say ("smoke: " + $tmpv)
}
# ---- 5. outputs ----
Stage 'upload'
$sum = Join-Path $job ('Igneum-Wallet-Setup-' + $ver + '.exe.sha256')
((Sha $setup) + ' ' + (Split-Path -Leaf $setup)) | Set-Content -LiteralPath $sum -Encoding ascii
((Sha $hostExe) + ' Igneum Wallet.exe') | Set-Content -LiteralPath (Join-Path $job 'wallet-host.sha256') -Encoding ascii
Drop $setup ('wallet-kit ' + (Split-Path -Leaf $setup))
Drop $sum 'wallet-kit installer sha256'
Drop (Join-Path $job 'wallet-host.sha256') 'wallet-kit host sha256'
Drop $hostLog 'wallet-kit host-build.log'
Drop $instLog 'wallet-kit installer-build.log'
Say 'done'