diff --git a/packaging/windows/Igneum-Wallet.iss b/packaging/windows/Igneum-Wallet.iss index b39b67006..014894340 100644 --- a/packaging/windows/Igneum-Wallet.iss +++ b/packaging/windows/Igneum-Wallet.iss @@ -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): \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 diff --git a/packaging/windows/build-installer.ps1 b/packaging/windows/build-installer.ps1 index e5a98f526..47b20049a 100644 --- a/packaging/windows/build-installer.ps1 +++ b/packaging/windows/build-installer.ps1 @@ -11,6 +11,7 @@ # # build-installer.ps1 [-Payload ] [-PayloadUrl ] [-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-.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)) diff --git a/packaging/windows/make-wallet-payload.sh b/packaging/windows/make-wallet-payload.sh new file mode 100755 index 000000000..f8174746b --- /dev/null +++ b/packaging/windows/make-wallet-payload.sh @@ -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" </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" diff --git a/packaging/windows/push-build-inputs.sh b/packaging/windows/push-build-inputs.sh index 39775bf73..987d324c2 100755 --- a/packaging/windows/push-build-inputs.sh +++ b/packaging/windows/push-build-inputs.sh @@ -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" diff --git a/packaging/windows/push-wallet-kit.sh b/packaging/windows/push-wallet-kit.sh new file mode 100755 index 000000000..08c4ee006 --- /dev/null +++ b/packaging/windows/push-wallet-kit.sh @@ -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//igneum-wallet-kit-.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//)" >&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//$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##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 diff --git a/packaging/windows/stop-igneum-wallet.ps1 b/packaging/windows/stop-igneum-wallet.ps1 new file mode 100644 index 000000000..d9cdf8bc4 --- /dev/null +++ b/packaging/windows/stop-igneum-wallet.ps1 @@ -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 : 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 } diff --git a/relay/playbooks/wallet-kit-pc2.ps1 b/relay/playbooks/wallet-kit-pc2.ps1 new file mode 100644 index 000000000..98ef429cb --- /dev/null +++ b/relay/playbooks/wallet-kit-pc2.ps1 @@ -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-.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'