summaryrefslogtreecommitdiffstats
path: root/packaging/win/build-win.ps1
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-04 09:28:14 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-04 09:28:14 +0200
commit3ce52774760b222d94d78bc0118e9da2662a809f (patch)
tree52a16c7e05080869fbb17b83665b2fb2048b21e2 /packaging/win/build-win.ps1
parent220e6e701806213a576ce80fa655cd9cf4a51880 (diff)
downloadmeshbay-3ce52774760b222d94d78bc0118e9da2662a809f.tar.gz
feat: Windows installer (W4) — one per-user NSIS package, client + node
`npm run dist:win` produces MeshBay-Setup-<version>.exe: the Electron client and, beside it under resources/node-runtime/, the frozen meshbay-node daemon (meshbay-common inside it). No hub. Per-user, no elevation — matches the W3 constraint that a logon-triggered scheduled task needs admin. electron-builder / package.json build.win nsis, build/icon.ico, extraResources -> node-runtime/ build.nsis oneClick:false perMachine:false allowElevation:false allowToChangeInstallationDirectory:true dist:win -> packaging/win/build-win.ps1 (mirrors dist -> build-client.sh) packaging/win/ meshbay-node.spec + node-entry.py PyInstaller freeze of meshbay_node.daemon:main. The awkward deps (aiortc, av, aioquic, pydantic_core, uvicorn, watchdog, guessit, blake3, tzdata) are pulled in whole with collect_all — that list is expected to grow when a frozen run raises ModuleNotFoundError. build-node-runtime.ps1 throwaway venv -> pip install -> PyInstaller -> packages/meshbay-client/node-runtime/ (gitignored) build-win.ps1 Node>=22 check, npm ci, Electron bump, sync-ui, node runtime, electron-builder --win nsis bump-electron.mjs the Chromium-CVE "build against latest Electron" policy, out of the PS script (5.1 here-string terminator rules) README.md PyInstaller, not the python-embed zip: the frozen meshbay-node.exe is a genuine relocatable single binary, which is what src/main.js:findNodeBinary spawns (process.resourcesPath/node-runtime/meshbay-node.exe when packaged) and what the W3 autostart launcher points at. The embeddable zip needs pip to make that wrapper and the wrapper bakes in an absolute interpreter path. build/installer.nsh: on uninstall, taskkill meshbay-node.exe and delete the W3 Startup .vbs (it would point wscript at a deleted binary every sign-in). %LOCALAPPDATA%\meshbay\ — node.toml, keystore.enc — is never touched. ffmpeg is not bundled by default (node finds it on PATH); build-win.ps1 -FfmpegDir copies ffmpeg.exe/ffprobe.exe in for a self-contained installer. Verified on the Windows guest: PyInstaller freeze builds first try (node-runtime 147 MB), frozen `meshbay-node status` talks to the live daemon's loopback API; electron-builder --win nsis produces MeshBay-Setup-0.1.0.exe (155 MB), oneClick/perMachine flags applied, node-runtime bundled at the path findNodeBinary expects. test_packaging_win.py (14) pins the config invariants and the NSIS <-> platform.py autostart seam. Node suite 798 pass / 34 skip. Open: Authenticode signing (13.9 — unsigned => SmartScreen), Windows CI (18.3), electron-updater. First clean-machine install + DPAPI + autostart round-trip is a manual check. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Diffstat (limited to 'packaging/win/build-win.ps1')
-rw-r--r--packaging/win/build-win.ps1117
1 files changed, 117 insertions, 0 deletions
diff --git a/packaging/win/build-win.ps1 b/packaging/win/build-win.ps1
new file mode 100644
index 0000000..4784071
--- /dev/null
+++ b/packaging/win/build-win.ps1
@@ -0,0 +1,117 @@
+<#
+.SYNOPSIS
+ Build the Windows installer: Electron client + bundled Python node.
+
+.DESCRIPTION
+ The Windows counterpart of packaging/build/build-client.sh. Produces a
+ single per-user NSIS installer (MeshBay-Setup-<version>.exe) that lays down
+ the Electron app and, beside it under resources/node-runtime/, the frozen
+ meshbay-node daemon. No hub -- a desktop machine installs client + node
+ (+ common, which is inside the node runtime).
+
+ Steps:
+ 1. Node >= 22 check
+ 2. npm ci (+ approve Electron's install script, download Chromium)
+ 3. bump Electron to the latest release (Chromium CVE policy -- see
+ build-client.sh; skip with -NoElectronBump)
+ 4. npm run sync-ui (copy the interface from the hub package)
+ 5. build-node-runtime.ps1 (PyInstaller freeze of the daemon)
+ 6. electron-builder --win nsis
+
+.PARAMETER FfmpegDir
+ Passed through to build-node-runtime.ps1 -- directory with ffmpeg.exe /
+ ffprobe.exe to bundle. Optional; without it the node uses PATH.
+
+.PARAMETER NoElectronBump
+ Keep the pinned Electron instead of upgrading to the latest release.
+
+.PARAMETER SkipNodeRuntime
+ Reuse an existing packages/meshbay-client/node-runtime/ (faster iteration
+ on the electron-builder side).
+#>
+[CmdletBinding()]
+param(
+ [string]$FfmpegDir = $env:MESHBAY_FFMPEG_DIR,
+ [switch]$NoElectronBump,
+ [switch]$SkipNodeRuntime
+)
+
+$ErrorActionPreference = "Stop"
+Set-StrictMode -Version Latest
+
+$WinDir = $PSScriptRoot
+$Repo = (Resolve-Path (Join-Path $WinDir "..\..")).Path
+$Client = Join-Path $Repo "packages\meshbay-client"
+
+function Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan }
+
+# --- 1. Node -------------------------------------------------------------
+if (-not (Get-Command node -ErrorAction SilentlyContinue)) {
+ throw "Node.js not found. Install Node 22 or newer from nodejs.org."
+}
+$nodeMajor = [int](& node -e "process.stdout.write(String(process.versions.node.split('.')[0]))")
+if ($nodeMajor -lt 22) { throw "Node $nodeMajor is too old -- need 22 or newer for Electron" }
+Step "Node $(& node --version)"
+
+Push-Location $Client
+try {
+ # --- 2. deps ------------------------------------------------------
+ Step "npm ci"
+ & npm ci --ignore-scripts
+ if ($LASTEXITCODE -ne 0) { throw "npm ci failed" }
+
+ # --- 3. Electron: build against the latest release --------------
+ # Writes package.json + package-lock.json, so the build leaves the repo
+ # dirty on purpose -- commit the new pin.
+ if (-not $NoElectronBump) {
+ Step "checking for a newer Electron"
+ $bumped = & node (Join-Path $WinDir "bump-electron.mjs")
+ if ($LASTEXITCODE -ne 0) { throw "electron bump failed" }
+ if ($bumped) {
+ Write-Host " Electron -> $bumped (package.json + lock updated, commit them)" -ForegroundColor Yellow
+ } else {
+ Write-Host " Electron is already current"
+ }
+ }
+
+ Step "downloading Electron's Chromium"
+ & npm approve-scripts electron 2>$null
+ & node node_modules/electron/install.js
+
+ # --- 4. UI -----------------------------------------------------
+ Step "npm run sync-ui"
+ & npm run sync-ui
+ if ($LASTEXITCODE -ne 0) { throw "sync-ui failed" }
+
+ # --- 5. node runtime ---------------------------------------
+ $rtExe = Join-Path $Client "node-runtime\meshbay-node.exe"
+ if ($SkipNodeRuntime -and (Test-Path $rtExe)) {
+ Step "reusing existing node-runtime/"
+ } else {
+ Step "building the bundled node (PyInstaller)"
+ $rtArgs = @{}
+ if ($FfmpegDir) { $rtArgs["FfmpegDir"] = $FfmpegDir }
+ & (Join-Path $WinDir "build-node-runtime.ps1") @rtArgs
+ if ($LASTEXITCODE -ne 0) { throw "build-node-runtime.ps1 failed" }
+ }
+
+ # --- 6. installer ---------------------------------------
+ Step "electron-builder --win nsis"
+ & npx electron-builder --win nsis
+ if ($LASTEXITCODE -ne 0) { throw "electron-builder failed" }
+}
+finally {
+ Pop-Location
+}
+
+$setup = Get-ChildItem (Join-Path $Client "dist") -Filter "*Setup*.exe" -ErrorAction SilentlyContinue |
+ Sort-Object LastWriteTime | Select-Object -Last 1
+Write-Host ""
+if ($setup) {
+ Write-Host "OK installer: $($setup.FullName)" -ForegroundColor Green
+ Write-Host (" ({0:N0} MB)" -f ($setup.Length / 1MB))
+}
+else {
+ Write-Host "!! no *Setup*.exe found in $Client\dist" -ForegroundColor Red
+ exit 1
+}