diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-09-04 09:28:14 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-09-04 09:28:14 +0200 |
| commit | 3ce52774760b222d94d78bc0118e9da2662a809f (patch) | |
| tree | 52a16c7e05080869fbb17b83665b2fb2048b21e2 /packaging/win/build-win.ps1 | |
| parent | 220e6e701806213a576ce80fa655cd9cf4a51880 (diff) | |
| download | meshbay-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.ps1 | 117 |
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 +} |