<# .SYNOPSIS Freeze the MeshBay node daemon into a relocatable Windows binary. .DESCRIPTION Produces packages/meshbay-client/node-runtime/meshbay-node.exe (+ its onedir payload) with PyInstaller. electron-builder then carries that tree into the NSIS installer as an extraResource (see package.json build.win). The frozen binary is what the desktop client spawns and what the W3 autostart launcher points at, so it must be a genuine single .exe -- which is why this uses PyInstaller and not the python-embed zip (whose pip console-script wrapper bakes in an absolute interpreter path and does not survive being installed somewhere else). ffmpeg/ffprobe are NOT bundled by default. Pass -FfmpegDir (or set MESHBAY_FFMPEG_DIR) to copy them in beside the daemon; otherwise the node resolves them from PATH at startup (meshbay_node.platform.check_media_tools) and streaming needs ffmpeg installed separately. .PARAMETER Python Interpreter used to build. Must be 3.12+ and able to install the packages. Default: a throwaway venv this script creates under build/_node-build-venv. .PARAMETER FfmpegDir Directory containing ffmpeg.exe and ffprobe.exe to bundle. Optional. .PARAMETER KeepBuildVenv Do not delete the throwaway build venv on success (faster re-runs). #> [CmdletBinding()] param( [string]$Python = "", [string]$FfmpegDir = $env:MESHBAY_FFMPEG_DIR, [switch]$KeepBuildVenv ) $ErrorActionPreference = "Stop" Set-StrictMode -Version Latest $WinDir = $PSScriptRoot $Repo = (Resolve-Path (Join-Path $WinDir "..\..")).Path $Client = Join-Path $Repo "packages\meshbay-client" $OutDir = Join-Path $Client "node-runtime" $BuildVenv = Join-Path $Client "build\_node-build-venv" $SpecFile = Join-Path $WinDir "meshbay-node.spec" function Step($msg) { Write-Host "==> $msg" -ForegroundColor Cyan } # --- 1. interpreter -------------------------------------------------------- $createdVenv = $false if (-not $Python) { Step "creating build venv ($BuildVenv)" if (Get-Command py -ErrorAction SilentlyContinue) { & py -3.12 -m venv --clear $BuildVenv } else { & python -m venv --clear $BuildVenv } if ($LASTEXITCODE -ne 0) { throw "venv creation failed" } $Python = Join-Path $BuildVenv "Scripts\python.exe" $createdVenv = $true } if (-not (Test-Path $Python)) { throw "Python not found: $Python" } $verOut = (& $Python -c "import sys; print(sys.version_info[0]); print(sys.version_info[1])") $verMajor = [int]$verOut[0] $verMinor = [int]$verOut[1] if ($verMajor -lt 3 -or ($verMajor -eq 3 -and $verMinor -lt 12)) { throw "need Python 3.12 or newer, got $verMajor.$verMinor" } Step "python $verMajor.$verMinor ($Python)" # --- 2. dependencies ----------------------------------------------------- Step "installing meshbay-common, meshbay-node and PyInstaller" & $Python -m pip install --upgrade pip --quiet & $Python -m pip install --quiet ` (Join-Path $Repo "packages\meshbay-common") ` (Join-Path $Repo "packages\meshbay-node") ` "pyinstaller>=6.10" ` "tzdata" # tzdata: Windows ships no IANA zone database, so zoneinfo (pulled in # transitively) has nothing to read without it. PyInstaller warns # 'Hidden import "tzdata" not found' when it is absent. if ($LASTEXITCODE -ne 0) { throw "pip install failed" } # --- 3. freeze -------------------------------------------------------- if (Test-Path $OutDir) { Remove-Item -Recurse -Force $OutDir } $pyiWork = Join-Path $Client "build\_pyinstaller" $pyiDist = Join-Path $Client "build\_pyinstaller-dist" Step "running PyInstaller (this takes a few minutes)" Push-Location $WinDir try { & $Python -m PyInstaller --noconfirm --clean ` --workpath $pyiWork --distpath $pyiDist ` $SpecFile if ($LASTEXITCODE -ne 0) { throw "PyInstaller failed" } } finally { Pop-Location } $frozen = Join-Path $pyiDist "meshbay-node" if (-not (Test-Path (Join-Path $frozen "meshbay-node.exe"))) { throw "PyInstaller did not produce meshbay-node.exe at $frozen" } # --- 4. optional ffmpeg ---------------------------------------------- if ($FfmpegDir) { foreach ($tool in @("ffmpeg.exe", "ffprobe.exe")) { $src = Join-Path $FfmpegDir $tool if (-not (Test-Path $src)) { throw "$tool not found in $FfmpegDir" } Copy-Item $src (Join-Path $frozen $tool) Step "bundled $tool" } } else { Write-Host " ffmpeg not bundled -- the node will look for it on PATH" -ForegroundColor Yellow } # --- 5. publish ---------------------------------------------------- Move-Item $frozen $OutDir Remove-Item -Recurse -Force $pyiWork, $pyiDist -ErrorAction SilentlyContinue if ($createdVenv -and -not $KeepBuildVenv) { Remove-Item -Recurse -Force $BuildVenv -ErrorAction SilentlyContinue } # --- 6. smoke test ----------------------------------------------- # Capture, do NOT pipe to Select-Object -First: that stops the native process # mid-write and reports a spurious non-zero exit. Step "smoke test: meshbay-node --help" $exe = Join-Path $OutDir "meshbay-node.exe" $help = & $exe --help 2>&1 if ($LASTEXITCODE -ne 0) { throw "frozen meshbay-node --help exited $LASTEXITCODE" } if ($help -notmatch "meshbay-node") { throw "frozen --help output looks wrong" } $mb = (Get-ChildItem $OutDir -Recurse | Measure-Object Length -Sum).Sum / 1MB Write-Host "" Write-Host ("OK node-runtime ready at {0} ({1:N0} MB)" -f $OutDir, $mb) -ForegroundColor Green