# Windows packaging (W4) A single per-user installer — `MeshBay-Setup-.exe` — that lays down the Electron **client** and the Python **node** daemon. No hub (server-only, stays Linux). `meshbay-common` rides along inside the node runtime. ## What the installer contains ``` %LOCALAPPDATA%\Programs\meshbay-client\ ├─ MeshBay.exe Electron client ├─ resources\ │ ├─ app.asar src/ + ui/ (the interface ships in the package) │ └─ node-runtime\ │ ├─ meshbay-node.exe frozen daemon (PyInstaller onedir) │ ├─ _internal\ … its Python + deps (aiortc, av, aioquic, …) │ └─ ffmpeg.exe, ffprobe.exe only if built with -FfmpegDir └─ Uninstall MeshBay.exe ``` Runtime data stays where the node already puts it: `%LOCALAPPDATA%\meshbay\` (`node.toml`, `keystore.enc`, `unlock.key`, `data\`). The installer never writes there and the uninstaller never deletes it — installers place files, not secrets. `build/installer.nsh` also adds `…\resources\node-runtime` to the **per-user** `Path` (`HKCU\Environment`) so `meshbay-node` works in a terminal, and takes it back out on uninstall. New shells only — a `WM_SETTINGCHANGE` broadcast nudges open ones. It uses stock `WordFunc.nsh` (the `EnVar` plugin is not in electron-builder's NSIS bundle). ## Build On a Windows machine with **Node ≥ 22**, **Python ≥ 3.12** (`py -3.12`) and **git**: ```powershell cd packages\meshbay-client npm run dist:win ``` That runs [`build-win.ps1`](build-win.ps1): | Step | | |---|---| | 1 | Node ≥ 22 check | | 2 | `npm ci` + download Electron's Chromium | | 3 | bump Electron to the latest release (Chromium CVE policy; `-NoElectronBump` to skip) | | 4 | `npm run sync-ui` — copy the interface from `meshbay-hub/.../static` | | 5 | [`build-node-runtime.ps1`](build-node-runtime.ps1) — PyInstaller freeze → `packages/meshbay-client/node-runtime/` | | 6 | `electron-builder --win nsis` → `packages/meshbay-client/dist/MeshBay-Setup-.exe` | ### ffmpeg Not bundled by default — the node resolves `ffmpeg`/`ffprobe` from `PATH` at startup, and video streaming needs them. To make the installer self-contained: ```powershell npm run dist:win -- -FfmpegDir "C:\path\to\ffmpeg\bin" # or: $env:MESHBAY_FFMPEG_DIR = "C:\path\to\ffmpeg\bin"; npm run dist:win ``` They are copied beside `meshbay-node.exe`, which is on the daemon's search path. ### Iterating ```powershell # rebuild only the installer, reuse an existing node-runtime\ powershell -File ..\..\packaging\win\build-win.ps1 -SkipNodeRuntime # build just the frozen daemon, into an existing venv, keep it for next time powershell -File ..\..\packaging\win\build-node-runtime.ps1 -Python C:\path\python.exe -KeepBuildVenv ``` ## Why PyInstaller and not the python-embed zip The frozen `meshbay-node.exe` is a genuine relocatable single binary. It is what the client spawns (`findNodeBinary` in `src/main.js`) and what the W3 autostart launcher points at (`meshbay_node.platform._node_exe`). The python.org embeddable zip would need pip to make a `meshbay-node.exe` wrapper, and that wrapper bakes in an **absolute** interpreter path — it stops working the moment the tree is installed somewhere other than where it was built. ## Dependency surface that needs watching `meshbay-node.spec` pulls the awkward packages in whole (`collect_all`) because they have C/Rust extensions or do dynamic imports: `aiortc`, `av` (bundles FFmpeg DLLs), `aioquic`, `pydantic_core`, `uvicorn`, `watchdog`, `guessit`, `blake3`, `zstandard`, `msgpack`. If a frozen run raises `ModuleNotFoundError`, add the package to `COLLECT_ALL` / `HIDDEN` / `COPY_META` in the spec — that is the expected way the list grows. ## Not done - **Authenticode signing** — Phase 13.9. Unsigned installer triggers SmartScreen. - **CI** — no Windows runner builds this yet (Phase 18.3). Build is manual. - **Delta updates / auto-update** — `electron-updater` is not wired.