igneum/packaging/windows/build-installer.ps1
2026-10-04 10:36:39 +00:00

188 lines
11 KiB
PowerShell

# Builds dist\Igneum-Miner-Setup-<version>.exe on a Windows PC (started by BUILD-INSTALLER.bat, or run directly).
# 1. Finds ISCC.exe (Inno Setup 6.3 or newer); installs Inno Setup 6 through winget when it is missing.
# 2. Finds the art (brand\icons in the repo layout, or an art\ folder next to this script).
# 3. Finds the payload: the extracted igneum-windows-app folder (igneum-app.exe, igneumd.exe, igneum-miner.exe, made by
# make-payload.sh on the Mac) next to this script, in the parent folder, or given with -Payload; otherwise the zip at
# -PayloadUrl is downloaded. "Igneum Miner.exe" (app\windows\BUILD-APP.bat) is shipped when it is in the payload.
# 4. Copies the payload into build\payload without logs, packs and per-PC worker builds.
# 5. Stamps the coin icon and the version block into igneum-app.exe, igneumd.exe and igneum-miner.exe with rcedit when
# the exes do not carry them yet (the Mac relink with packaging\windows\embed-resources.sh --relink makes this a no-op).
# 6. Compiles Igneum-Miner.iss and prints where the installer is.
#
# build-installer.ps1 [-Payload <folder>] [-PayloadUrl <zip url>] [-Version 0.3.0] [-NoRcedit] [-NoWinget]
param(
[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',
[switch]$NoRcedit,
[switch]$NoWinget
)
$ErrorActionPreference = 'Stop'
$ProgressPreference = 'SilentlyContinue'
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
$here = Split-Path -Parent $MyInvocation.MyCommand.Path
$build = Join-Path $here 'build'
$dist = Join-Path $here 'dist'
$rceditUrl = 'https://github.com/electron/rcedit/releases/download/v2.0.0/rcedit-x64.exe'
New-Item -ItemType Directory -Force -Path $build, $dist | Out-Null
function Say([string]$text) { Write-Host ('[build] ' + $text) }
function Find-Iscc {
$candidates = @()
foreach ($base in @(${env:ProgramFiles(x86)}, $env:ProgramFiles, (Join-Path $env:LOCALAPPDATA 'Programs'))) {
if ($base) { $candidates += (Join-Path $base 'Inno Setup 6\ISCC.exe') }
}
foreach ($key in @('HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1')) {
try {
$loc = (Get-ItemProperty -Path $key -ErrorAction Stop).InstallLocation
if ($loc) { $candidates += (Join-Path $loc 'ISCC.exe') }
} catch { }
}
foreach ($c in $candidates) { if ($c -and (Test-Path $c)) { return $c } }
return $null
}
# ---- 1. Inno Setup ----------------------------------------------------------------------------------------------------
$iscc = Find-Iscc
if (-not $iscc) {
if ($NoWinget) { throw 'Inno Setup 6 is not installed (and -NoWinget was given). Install it from https://jrsoftware.org/isdl.php and run again.' }
if (-not (Get-Command winget.exe -ErrorAction SilentlyContinue)) { throw 'Inno Setup 6 is not installed and winget is not available. Install Inno Setup 6 from https://jrsoftware.org/isdl.php and run again.' }
Say 'Inno Setup 6 is not installed; installing it through winget (a UAC prompt may appear)'
& winget.exe install --id JRSoftware.InnoSetup --exact --silent --accept-package-agreements --accept-source-agreements
$iscc = Find-Iscc
if (-not $iscc) { throw 'winget finished but ISCC.exe was not found. Install Inno Setup 6 from https://jrsoftware.org/isdl.php and run again.' }
}
# The version: ISCC.exe's own file version first; some installs (the GitHub runner image) ship an ISCC.exe without a
# version block (0.0.0.0), then the uninstall key's DisplayVersion or ISCmplr.dll next to it. Unknown = continue.
function Get-IsccVersion([string]$exe) {
$candidates = @((Get-Item $exe).VersionInfo.FileVersion, (Get-Item $exe).VersionInfo.ProductVersion)
foreach ($key in @('HKLM:\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1',
'HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1',
'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1')) {
try { $candidates += (Get-ItemProperty -Path $key -ErrorAction Stop).DisplayVersion } catch { }
}
$dll = Join-Path (Split-Path -Parent $exe) 'ISCmplr.dll'
if (Test-Path $dll) { $candidates += (Get-Item $dll).VersionInfo.FileVersion }
foreach ($c in $candidates) {
if (-not $c) { continue }
$clean = ($c -replace '[^0-9.].*$', '')
try { $v = [Version]$clean } catch { continue }
if ($v.Major -gt 0) { return $v }
}
return $null
}
$v = Get-IsccVersion $iscc
if ($v) {
Say "Inno Setup $v at $iscc"
if ($v -lt [Version]'6.3.0') { throw "Inno Setup $v is too old; 6.3 or newer is needed (winget upgrade --id JRSoftware.InnoSetup)." }
} else {
Say "Inno Setup at $iscc (version unknown: no version block, no uninstall key; continuing)"
}
# ---- 2. the art ------------------------------------------------------------------------------------------------------
$art = $null
foreach ($dir in @((Join-Path $here '..\..\brand\icons'), (Join-Path $here 'art'))) {
if (Test-Path (Join-Path $dir 'igneum.ico')) { $art = (Resolve-Path $dir).Path; break }
}
if (-not $art) { throw "No art folder: expected brand\icons two levels up (the repo layout) or an art\ folder next to this script with igneum.ico, inno-wizard-164x314.bmp and inno-wizard-small-55x58.bmp (made by brand\icons\make-icons.py)." }
foreach ($f in @('igneum.ico', 'inno-wizard-164x314.bmp', 'inno-wizard-small-55x58.bmp')) {
if (-not (Test-Path (Join-Path $art $f))) { throw "missing $f in $art" }
}
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')) {
if (-not (Test-Path (Join-Path $dir $f))) { return $false }
}
return $true
}
$source = $null
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'))) {
if (Test-PayloadDir $dir) { $source = (Resolve-Path $dir).Path; break }
}
}
if (-not $source) {
$zip = Join-Path $build 'igneum-windows.zip'
$unpacked = Join-Path $build 'payload-src'
Say "no igneum-windows folder next to this script; downloading $PayloadUrl"
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)." }
$source = $hit.DirectoryName
}
Say "payload source: $source"
# ---- 4. assemble build\payload ---------------------------------------------------------------------------------------
$payloadDir = Join-Path $build 'payload'
if (Test-Path $payloadDir) { Remove-Item -Recurse -Force $payloadDir }
New-Item -ItemType Directory -Force -Path $payloadDir | Out-Null
$skipNames = @('.DS_Store')
Get-ChildItem -Path $source -Recurse -Force | ForEach-Object {
$rel = $_.FullName.Substring($source.Length).TrimStart('\')
if ($rel -match '(^|\\)packs(\\|$)') { return }
if ($rel -match '\.(log|seeds)$') { return }
if ($rel -match '^proto-(cuda|opencl)\\[^\\]+\.exe$') { return } # GPU workers built per PC by the fallback path
if ($rel -match '^app\\windows\\(build|dist)(\\|$)') { return } # BUILD-APP.bat's intermediate files
if ($skipNames -contains $_.Name) { return }
$dest = Join-Path $payloadDir $rel
if ($_.PSIsContainer) { New-Item -ItemType Directory -Force -Path $dest | Out-Null }
else {
New-Item -ItemType Directory -Force -Path (Split-Path -Parent $dest) | Out-Null
Copy-Item -Path $_.FullName -Destination $dest -Force
}
}
$count = (Get-ChildItem -Path $payloadDir -Recurse -File | Measure-Object).Count
$bytes = (Get-ChildItem -Path $payloadDir -Recurse -File | Measure-Object -Property Length -Sum).Sum
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')) {
$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 ($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 }
else {
$rcedit = Join-Path $build 'rcedit-x64.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' } }
$rcArgs = @($exe, '--set-icon', (Join-Path $art 'igneum.ico'),
'--set-version-string', 'CompanyName', 'Igneum',
'--set-version-string', 'ProductName', 'Igneum Miner',
'--set-version-string', 'FileDescription', $desc,
'--set-version-string', 'OriginalFilename', $exeName,
'--set-version-string', 'LegalCopyright', 'Igneum. Nothing is bought or sold.',
'--set-file-version', $Version,
'--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"
}
# ---- 6. compile ------------------------------------------------------------------------------------------------------
$iss = Join-Path $here 'Igneum-Miner.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"
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))
Say 'The installer is unsigned until Igneum has a code-signing certificate: SmartScreen shows "Windows protected your PC" (More info > Run anyway).'