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/meshbay-node.spec | 126 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) create mode 100644 packaging/win/meshbay-node.spec (limited to 'packaging/win/meshbay-node.spec') diff --git a/packaging/win/meshbay-node.spec b/packaging/win/meshbay-node.spec new file mode 100644 index 0000000..f1f994d --- /dev/null +++ b/packaging/win/meshbay-node.spec @@ -0,0 +1,126 @@ +# PyInstaller spec for the bundled Windows node daemon. +# +# Output: dist/meshbay-node/meshbay-node.exe (--onedir, relocatable) +# Consumed by: packaging/win/build-node-runtime.ps1, which moves the tree to +# packages/meshbay-client/node-runtime/ for electron-builder to carry as an +# extraResource. +# +# The dependency set (aiortc, av, aioquic, pydantic-core, uvicorn, watchdog, +# guessit, blake3, ...) has C/Rust extensions and does dynamic imports, so +# each awkward package is pulled in whole with collect_all rather than left to +# PyInstaller's static analysis. Add to COLLECT_ALL / HIDDEN / COPY_META when a +# frozen run raises ModuleNotFoundError — that is the expected way this list grows. + +from PyInstaller.utils.hooks import collect_all, copy_metadata + +# Packages taken in full: data files + shared libs + every submodule. +COLLECT_ALL = [ + "meshbay_node", + "meshbay_common", + "aiortc", + "aioice", + "aioquic", + "av", + "cryptography", + "pylibsrtp", + "pyee", + "google_crc32c", + "pydantic", + "pydantic_core", + "fastapi", + "starlette", + "uvicorn", + "anyio", + "sniffio", + "h11", + "websockets", + "httpx", + "httpcore", + "watchdog", + "guessit", + "rebulk", + "babelfish", + "mutagen", + "PIL", + "blake3", + "msgpack", + "zstandard", + "aiosqlite", + "jwt", + "dns", + "tzdata", +] + +# importlib.metadata lookups that survive freezing only if the dist-info is copied. +COPY_META = [ + "meshbay-node", + "meshbay-common", + "guessit", + "rebulk", + "babelfish", + "uvicorn", + "fastapi", +] + +# Imports PyInstaller's static pass misses even with collect_all. +HIDDEN = [ + "uvicorn.logging", + "uvicorn.loops.auto", + "uvicorn.loops.asyncio", + "uvicorn.protocols.http.auto", + "uvicorn.protocols.http.h11_impl", + "uvicorn.protocols.websockets.auto", + "uvicorn.protocols.websockets.websockets_impl", + "uvicorn.lifespan.on", +] + +datas, binaries, hiddenimports = [], [], list(HIDDEN) +for pkg in COLLECT_ALL: + d, b, h = collect_all(pkg) + datas += d + binaries += b + hiddenimports += h +for dist in COPY_META: + datas += copy_metadata(dist) + +a = Analysis( + ["node-entry.py"], + pathex=[], + binaries=binaries, + datas=datas, + hiddenimports=hiddenimports, + hookspath=[], + hooksconfig={}, + runtime_hooks=[], + excludes=["tkinter", "pytest", "ruff", "piexif", "IPython"], + noarchive=False, +) +pyz = PYZ(a.pure) + +exe = EXE( + pyz, + a.scripts, + [], + exclude_binaries=True, + name="meshbay-node", + debug=False, + bootloader_ignore_signals=False, + strip=False, + upx=False, + console=True, + disable_windowed_traceback=False, + argv_emulation=False, + target_arch=None, + codesign_identity=None, + entitlements_file=None, + icon="../../packages/meshbay-client/build/icon.ico", +) +coll = COLLECT( + exe, + a.binaries, + a.datas, + strip=False, + upx=False, + upx_exclude=[], + name="meshbay-node", +) -- cgit v1.2.3