From 3ce52774760b222d94d78bc0118e9da2662a809f Mon Sep 17 00:00:00 2001 From: Christophe Besson Date: Fri, 4 Sep 2026 09:28:14 +0200 Subject: feat: Windows installer (W4) — one per-user NSIS package, client + node MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `npm run dist:win` produces MeshBay-Setup-.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 --- packaging/win/bump-electron.mjs | 43 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 packaging/win/bump-electron.mjs (limited to 'packaging/win/bump-electron.mjs') diff --git a/packaging/win/bump-electron.mjs b/packaging/win/bump-electron.mjs new file mode 100644 index 0000000..78189bf --- /dev/null +++ b/packaging/win/bump-electron.mjs @@ -0,0 +1,43 @@ +// Bump the pinned Electron to the latest release, in package.json and +// package-lock.json, and keep the `allowScripts` key matching. +// +// Chromium CVEs are fixed in Electron releases; a client built against an old +// one ships those holes. This is the same policy packaging/build/build-client.sh +// applies on Linux -- factored out to a file so build-win.ps1 does not have to +// carry a PowerShell here-string (whose terminator rules make it fragile). +// +// Run from packages/meshbay-client. Writes both manifests and exits 0 whether +// or not a bump happened; exits non-zero only on an actual error. Prints the +// new version to stdout when it changed, nothing when it did not. + +import { readFileSync, writeFileSync } from 'node:fs'; +import { execFileSync } from 'node:child_process'; + +const pinned = JSON.parse(readFileSync('package-lock.json', 'utf8')) + .packages['node_modules/electron'].version; + +let latest; +try { + latest = execFileSync('npm', ['view', 'electron', 'version'], { encoding: 'utf8' }).trim(); +} catch { + process.stderr.write('npm registry unreachable -- keeping Electron ' + pinned + '\n'); + process.exit(0); +} + +if (latest === pinned) process.exit(0); + +execFileSync('npm', ['install', '--save-dev', '--ignore-scripts', `electron@${latest}`], + { stdio: 'inherit' }); + +const pkg = JSON.parse(readFileSync('package.json', 'utf8')); +if (pkg.allowScripts) { + for (const k of Object.keys(pkg.allowScripts)) { + if (k.startsWith('electron@')) { + delete pkg.allowScripts[k]; + pkg.allowScripts[`electron@${latest}`] = true; + } + } + writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n'); +} + +process.stdout.write(latest + '\n'); -- cgit v1.2.3