# Build the three Windows installer artifacts with PyInstaller: # # dist/installer/GTIFiscalAgent-Setup-.exe (onefile, --uac-admin) <- main download artifact # dist/installer/GTIFiscalAgentService/ (onedir) <- embedded in Setup, extracted to Program Files # dist/installer/Uninstall.exe (onefile, --uac-admin) <- embedded in Setup, ARP uninstaller # dist/installer/GTIFiscalAgent-Setup-.exe.sha256 # # Reproducibility = automated, repeatable, deterministic inputs + immutable # PUBLISHED release (byte-identical hashes are NOT required). # # Requires (fails fast if missing): python with PyInstaller, pywin32, fdb, # httpx; icacls for the ACL on credentials.bin. [CmdletBinding()] param( [string]$Python = "python", [switch]$SkipSetup = $false, [switch]$SkipService = $false, [switch]$SkipUninstall = $false ) $ErrorActionPreference = "Stop" $Root = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path $Version = (Get-Content (Join-Path $Root "release\VERSION") -Raw).Trim() $Out = Join-Path $Root "dist\installer" $Work = Join-Path $Root "build\installer" $SpecDir = Join-Path $Root "build\installer" function Invoke-Step([string]$Title, [scriptblock]$Body) { Write-Host "== $Title ==" -ForegroundColor Cyan & $Body if ($LASTEXITCODE -ne 0) { throw "$Title failed with exit code $LASTEXITCODE" } } Write-Host "Building GTI Fiscal Agent installer v$Version" -ForegroundColor Green $py = (Get-Command $Python).Source & $py -c "import PyInstaller, fdb, httpx, win32serviceutil; print('deps ok, pyinstaller', PyInstaller.__version__)" if ($LASTEXITCODE -ne 0) { throw "build environment is missing PyInstaller/fdb/httpx/pywin32" } if (-not (Get-Command "icacls" -ErrorAction SilentlyContinue)) { throw "missing required tool: icacls" } $Paths = @("--paths", (Join-Path $Root "agent\src"), "--paths", (Join-Path $Root "agent\scripts"), "--paths", (Join-Path $Root "agent\service"), "--paths", (Join-Path $Root "central")) $Common = @("--noconfirm", "--clean", "--distpath", $Out, "--workpath", $Work, "--specpath", $SpecDir, "--log-level", "WARN") # win32crypt is imported lazily inside credential_store (function body), so the # AST scan misses it; the agent service modules also need the pywin32 service libs. $Hidden = @("--hidden-import=win32timezone", "--hidden-import=win32crypt", "--hidden-import=win32serviceutil", "--hidden-import=win32service", "--hidden-import=win32event", "--hidden-import=servicemanager") # None of the reachable runtime modules import these (verified by tracing # __import__ while loading run_agent_service's full chain); they only add # ~100MB+ of dead weight via hook-gathered submodules. Exclude them. $Excludes = @("--exclude-module=numpy", "--exclude-module=PIL", "--exclude-module=yaml", "--exclude-module=scipy", "--exclude-module=pandas", "--exclude-module=matplotlib", "--exclude-module=openpyxl", "--exclude-module=jinja2") if (-not $SkipService) { Invoke-Step "Build service host (onedir)" -Body { & $py -m PyInstaller @Common @Paths @Hidden @Excludes "--onedir" ` "--name" "GTIFiscalAgentService" (Join-Path $Root "installer\service_main.py") } } if (-not $SkipUninstall) { Invoke-Step "Build Uninstall.exe (onefile, uac-admin)" -Body { & $py -m PyInstaller @Common @Paths @Hidden @Excludes "--onefile" "--uac-admin" "--name" "Uninstall" ` (Join-Path $Root "installer\uninstall_main.py") } } if (-not $SkipSetup) { $ServiceData = (Join-Path $Out "GTIFiscalAgentService") + ";" + "GTIFiscalAgentService" $UninstallData = (Join-Path $Out "Uninstall.exe") + ";" + "." $ExecutorData = (Join-Path $Root "scripts\firebird_write_executor.py") + ";" + "scripts" Invoke-Step "Build Setup.exe (onefile, uac-admin)" -Body { & $py -m PyInstaller @Common @Paths @Hidden @Excludes "--onefile" "--uac-admin" "--name" "GTIFiscalAgent-Setup" ` "--add-data" $ServiceData "--add-data" $UninstallData "--add-data" $ExecutorData ` (Join-Path $Root "installer\setup_main.py") } } $BuiltSetup = Join-Path $Out "GTIFiscalAgent-Setup.exe" $FinalSetup = Join-Path $Out "GTIFiscalAgent-Setup-$Version.exe" if (Test-Path $BuiltSetup) { Move-Item -Force $BuiltSetup $FinalSetup $Digest = (Get-FileHash -Algorithm SHA256 -Path $FinalSetup).Hash.ToLowerInvariant() Set-Content -Path "$FinalSetup.sha256" -Value "$Digest $([IO.Path]::GetFileName($FinalSetup))`n" -NoNewline -Encoding Ascii Write-Host "Installer: $FinalSetup" -ForegroundColor Green Write-Host "sha256: $Digest" -ForegroundColor Green } else { Write-Host "Setup artifact not built (skipped); service host + Uninstall.exe still available under $Out" -ForegroundColor Yellow } Get-ChildItem $Out -Recurse | Select-Object FullName, Length | Format-Table -AutoSize Write-Host "Build complete." -ForegroundColor Green