From de232ec2f71286fc51ccd2b03915b4744f56a8ee Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Mon, 22 Jun 2026 21:18:31 +0200 Subject: [PATCH] =?UTF-8?q?fix(web):=20bundle=20deps=20into=20the=20server?= =?UTF-8?q?=20(noExternals)=20=E2=80=94=20kill=20the=2047k-file=20install?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Windows installer ballooned to 154 MB and installed forever because the node-server bundle externalized the WHOLE @unom/ui dependency tree (payload, lexical, date-fns, prismjs…) to .output/server/node_modules — 47,567 files / 730 MB copied into Program Files. Set Nitro `noExternals: true` so every dependency is bundled + tree-shaken into the server output: .output drops to ~75 files / 10 MB, and the bare external imports (srvx, seroval…) bun couldn't resolve at runtime are gone — so the console runs on bun (no node, no node_modules), which is the issue we previously worked around with node. Windows installer now ships bun.exe + the ~75-file .output (was node.exe + a node_modules forest) and runs `bun .output\server\index.mjs`: - windows-host.yml: fetch a pinned portable bun (build tool AND shipped runtime); drop the node fetch + the .output/server install; smoke-boot under the bundled bun. - pack-host-installer.ps1 / punktfunk-host.iss: -NodeExe -> -BunExe; stage {app}\bun\bun.exe. - web-run.cmd / build-web.ps1: run/restart on bun; docs updated. Net win everywhere: the Linux .deb shrinks (node still runs the self-contained output), and the docker web image — which already ran `bun run .output/server/index.mjs` with only .output copied — is fixed (the externals had no node_modules to resolve at runtime). Validated locally: noExternals build = 75 files / 10 MB; node AND bun both serve /login (200) + static assets (200) + gate /api (401). (A true single binary via `bun build --compile` is blocked for now: Nitro serves public assets from an import.meta-relative path `--compile` doesn't embed (/$bunfs/public); the 75-file payload is the clean result.) Co-Authored-By: Claude Opus 4.8 (1M context) --- .gitea/workflows/windows-host.yml | 61 +++++++++-------------- docs-site/content/docs/windows-host.md | 2 +- packaging/windows/README.md | 15 +++--- packaging/windows/pack-host-installer.ps1 | 24 ++++----- packaging/windows/punktfunk-host.iss | 14 +++--- scripts/windows/README.md | 8 +-- scripts/windows/build-web.ps1 | 16 ++---- scripts/windows/web-run.cmd | 12 ++--- scripts/windows/web-setup.ps1 | 4 +- web/vite.config.ts | 8 +++ 10 files changed, 77 insertions(+), 87 deletions(-) diff --git a/.gitea/workflows/windows-host.yml b/.gitea/workflows/windows-host.yml index c7741b6..e3ffa19 100644 --- a/.gitea/workflows/windows-host.yml +++ b/.gitea/workflows/windows-host.yml @@ -1,7 +1,7 @@ # Build the punktfunk Windows HOST as a signed Inno Setup installer and publish it to Gitea's generic # package registry, so a Windows GPU box can install the streaming host (SYSTEM service + bundled # SudoVDA virtual-display driver + the web management console, run by a scheduled task on a bundled -# Node) from one signed setup.exe. Runs on the self-hosted Windows runner +# bun) from one signed setup.exe. Runs on the self-hosted Windows runner # (host mode; scripts/ci/setup-windows-runner.ps1) — same MSVC/Windows-SDK/LLVM env as windows.yml. # # Why an installer and not MSIX (like the client): the host installs a LocalSystem SCM service that @@ -104,46 +104,36 @@ jobs: choco install innosetup -y --no-progress } - - name: Fetch portable Node runtime (bundled to run the console) + - name: Fetch portable bun runtime (build tool + bundled to run the console) shell: pwsh run: | - # The installer ships a self-contained node.exe so the web console runs with no system-Node - # prerequisite. Pinned LTS (>= 20, matching the punktfunk-web .deb's `nodejs (>= 20)`); the - # smoke test below validates the .output bundle runs under exactly this node before shipping. - $ver = 'v22.11.0' - $url = "https://nodejs.org/dist/$ver/node-$ver-win-x64.zip" + # ONE pinned bun, used both to BUILD the console and shipped in the installer to RUN it. The + # .output is self-contained (Nitro noExternals — deps bundled + tree-shaken, no node_modules), + # so the installer ships just bun + a ~75-file .output instead of node + a node_modules forest. + $ver = 'bun-v1.3.14' + $url = "https://github.com/oven-sh/bun/releases/download/$ver/bun-windows-x64.zip" New-Item -ItemType Directory -Force -Path C:\t | Out-Null - $zip = 'C:\t\node.zip'; $dst = 'C:\t\nodedist' + $zip = 'C:\t\bun.zip'; $dst = 'C:\t\bundist' Invoke-WebRequest -Uri $url -OutFile $zip if (Test-Path $dst) { Remove-Item $dst -Recurse -Force } Expand-Archive -Path $zip -DestinationPath $dst -Force - $node = (Get-ChildItem -Path $dst -Recurse -Filter node.exe | Select-Object -First 1).FullName - if (-not $node) { throw "node.exe not found in $url" } - "NODE_EXE=$node" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - & $node --version + $bun = (Get-ChildItem -Path $dst -Recurse -Filter bun.exe | Select-Object -First 1).FullName + if (-not $bun) { throw "bun.exe not found in $url" } + "BUN_EXE=$bun" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 + & $bun --version - - name: Build + smoke-boot web console (node-server preset) + - name: Build + smoke-boot web console (bun) shell: pwsh env: - # PAT with read access to the unom org packages — the @unom npm registry needs auth. + # PAT with read access to the unom org packages — the @unom npm registry needs auth to BUILD. REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }} - # Same shape as deb.yml: bun builds the Nitro node-server bundle, node runs it; the installer - # bundles web\.output (handed over via WEB_OUTPUT_DIR) + the portable node above. The runner - # runs as SYSTEM (no dev-user PATH/npmrc), so bootstrap bun if absent and supply the private - # @unom registry token via the SYSTEM home .npmrc — kept OUT of the shipped bundle. + # The bun fetched above builds the Nitro server AND runs it. noExternals (vite.config) makes the + # output self-contained, so there's no .output/server install — the installer ships bun + the + # ~75-file .output. The runner is SYSTEM with no ~/.npmrc, so supply the private @unom token in + # the SYSTEM home .npmrc to BUILD (kept OUT of the shipped bundle — web\.npmrc has only the + # registry mapping, and nothing copies it into .output). run: | - $bun = (Get-Command bun -ErrorAction SilentlyContinue).Source - if (-not $bun) { foreach ($p in @("$env:USERPROFILE\.bun\bin\bun.exe", 'C:\Users\Public\bun\bin\bun.exe')) { if (Test-Path $p) { $bun = $p; break } } } - if (-not $bun) { - Write-Output "bun not found - installing via bun.sh" - Invoke-RestMethod https://bun.sh/install.ps1 | Invoke-Expression - $bun = Join-Path $env:USERPROFILE '.bun\bin\bun.exe' - } - if (-not (Test-Path $bun)) { throw "bun unavailable (install failed?): $bun" } - & $bun --version - # @unom is a private Gitea npm registry. The committed web\.npmrc has only the registry - # mapping; put the mapping + auth token in the SYSTEM home .npmrc so the token never lands in - # the shipped bundle (.output\server\.npmrc stays the clean mapping-only copy). + $bun = $env:BUN_EXE if ($env:REGISTRY_TOKEN) { $rc = Join-Path $env:USERPROFILE '.npmrc' Add-Content -Path $rc -Value '@unom:registry=https://git.unom.io/api/packages/unom/npm/' @@ -155,19 +145,16 @@ jobs: if (Select-String -Path .output\server\index.mjs -Pattern 'Bun\.serve' -Quiet) { throw "web build is a bun bundle (Bun.serve) - need the node-server preset" } - # Externalized @unom SSR deps must be installed inside .output\server (registry mapping via .npmrc). - Copy-Item .npmrc .output\server\.npmrc -Force - Push-Location .output\server; & $bun install; if ($LASTEXITCODE) { throw ".output/server dep install failed ($LASTEXITCODE)" }; Pop-Location Pop-Location - # Gate the installer on a real node boot serving /login (the runtime the installer ships). + # Gate the installer on a real boot under the BUNDLED bun (the runtime it ships), serving /login. $env:PORT = '3009'; $env:HOST = '127.0.0.1'; $env:PUNKTFUNK_UI_PASSWORD = 'ci' $server = (Resolve-Path 'web\.output\server\index.mjs').Path - $p = Start-Process -FilePath $env:NODE_EXE -ArgumentList $server -PassThru -WindowStyle Hidden + $p = Start-Process -FilePath $bun -ArgumentList $server -PassThru -WindowStyle Hidden Start-Sleep -Seconds 4 try { $code = (Invoke-WebRequest -Uri 'http://127.0.0.1:3009/login' -UseBasicParsing -TimeoutSec 10).StatusCode } catch { $code = 0 } Stop-Process -Id $p.Id -Force -ErrorAction SilentlyContinue - Write-Output "web console smoke: /login -> $code" - if ($code -ne 200) { throw "web console failed to boot under node" } + Write-Output "web console smoke (bun): /login -> $code" + if ($code -ne 200) { throw "web console failed to boot under bun" } "WEB_OUTPUT_DIR=$((Resolve-Path 'web\.output').Path)" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - name: Pack + sign installer diff --git a/docs-site/content/docs/windows-host.md b/docs-site/content/docs/windows-host.md index 1d271f1..5795201 100644 --- a/docs-site/content/docs/windows-host.md +++ b/docs-site/content/docs/windows-host.md @@ -33,7 +33,7 @@ and the CLI `punktfunk-host service install` path) are in [`packaging/windows`](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/windows/README.md). The installer also sets up the **web management console** (status, paired devices, the PIN pairing -flow): it bundles the console plus its own Node runtime and runs it as the **`PunktfunkWeb`** service +flow): it bundles the console plus its own bun runtime and runs it as the **`PunktfunkWeb`** service on **`http://:3000`**, starting at boot. During setup you choose the console **login password** (pre-filled with a secure random default and shown again on the final page); change it later in `%ProgramData%\punktfunk\web-password`. Open the console from any browser on the LAN and log diff --git a/packaging/windows/README.md b/packaging/windows/README.md index f537ef2..8a5123b 100644 --- a/packaging/windows/README.md +++ b/packaging/windows/README.md @@ -33,11 +33,12 @@ exe into `C:\Program Files\punktfunk\` and calls that subcommand, elevated. display without it). - Runs `punktfunk-host service install` (idempotent; writes a default `host.env` only if absent, so user config survives upgrades) and, by the *Start service now* task, `service start`. -- **Web management console** (bundled when packed with `-WebDir`/`-NodeExe`, which the CI always is): - lays down the built `.output` server + a portable Node, prompts for a console login password - (pre-filled with a secure random default, shown again on the final page; kept on upgrade), then - `web-setup.ps1` writes the ACL'd `%ProgramData%\punktfunk\web-password`, registers the - **`PunktfunkWeb`** scheduled task (boot, SYSTEM, restart-on-failure → `web-run.cmd` → `node` on +- **Web management console** (bundled when packed with `-WebDir`/`-BunExe`, which the CI always is): + lays down the built **self-contained** `.output` server (Nitro `noExternals` — deps bundled + + tree-shaken, ~75 files, no `node_modules`) + a portable **bun**, prompts for a console login + password (pre-filled with a secure random default, shown again on the final page; kept on upgrade), + then `web-setup.ps1` writes the ACL'd `%ProgramData%\punktfunk\web-password`, registers the + **`PunktfunkWeb`** scheduled task (boot, SYSTEM, restart-on-failure → `web-run.cmd` → `bun` on `:3000`), opens TCP 3000, and starts it. It proxies the host's loopback mgmt API with the host's own `%ProgramData%\punktfunk\mgmt-token`. - **Upgrade:** stops a running `PunktfunkHost` service and waits for `STOPPED` before replacing files @@ -63,10 +64,10 @@ read it from `%ProgramData%\punktfunk\web-password`. | File | Role | |------|------| | `punktfunk-host.iss` | Inno Setup script (the installer definition). | -| `pack-host-installer.ps1` | Orchestrator: cert + sign, stage the driver + FFmpeg + **web console** (`.output` + node) bundles, run ISCC, sign setup.exe, emit registry paths. | +| `pack-host-installer.ps1` | Orchestrator: cert + sign, stage the driver + FFmpeg + **web console** (`.output` + bun) bundles, run ISCC, sign setup.exe, emit registry paths. | | `stage-sudovda.ps1` | Stage the **vendored** SudoVDA driver + fetch/verify the **pinned** nefcon release into the bundle. | | `install-sudovda.ps1` | Runs at install time (elevated): trust cert → gated device-node create → `pnputil` install. | -| `../../scripts/windows/web-run.cmd` | The `PunktfunkWeb` task action: loads the mgmt token + login password env, runs the bundled `node` on the Nitro server (`:3000`). | +| `../../scripts/windows/web-run.cmd` | The `PunktfunkWeb` task action: loads the mgmt token + login password env, runs the bundled `bun` on the Nitro server (`:3000`). | | `../../scripts/windows/web-setup.ps1` | Install-time (elevated): write the ACL'd console password, register the `PunktfunkWeb` task + firewall rule, start it. | | `sudovda/` | **Vendored** prebuilt SudoVDA driver: `SudoVDA.inf` / `sudovda.cat` / `SudoVDA.dll` / `sudovda.cer`. | | `nvenc/nvenc.def`, `nvenc/gen-nvenc-importlib.ps1` | Synthesise `nvencodeapi.lib` for the `--features nvenc` link (llvm-dlltool / lib.exe). | diff --git a/packaging/windows/pack-host-installer.ps1 b/packaging/windows/pack-host-installer.ps1 index d06b087..0d34703 100644 --- a/packaging/windows/pack-host-installer.ps1 +++ b/packaging/windows/pack-host-installer.ps1 @@ -27,7 +27,7 @@ param( [string]$PfxPassword = $env:MSIX_CERT_PASSWORD, [string]$FfmpegDir = $env:FFMPEG_DIR, # bundle its bin\*.dll (amf-qsv build) [string]$WebDir = $env:WEB_OUTPUT_DIR, # built web .output tree -> bundle the mgmt console - [string]$NodeExe = $env:NODE_EXE, # portable node.exe (>= 20) runtime for the console + [string]$BunExe = $env:BUN_EXE, # portable bun.exe runtime for the console [switch]$NoDriver, # build without the bundled SudoVDA driver [switch]$NoSign # skip signing (local debug) ) @@ -184,30 +184,30 @@ if ($ffmpegBinSrc -and (Test-Path $ffmpegBinSrc)) { } else { Write-Host "no FFMPEG_DIR\bin -> installer built WITHOUT FFmpeg DLLs (nvenc/software-only host)" } -# --- stage the web management console (the built .output tree + a portable node + the launcher) --- -# The console runs as the PunktfunkWeb scheduled task (`node {app}\web\.output\server\index.mjs`), +# --- stage the web management console (the self-contained .output tree + a portable bun + launcher) - +# The console runs as the PunktfunkWeb scheduled task (`bun {app}\web\.output\server\index.mjs`), # auto-wired to the host's loopback mgmt API. Stage everything ISCC reads into $OutDir (the -# non-WOW64-redirected C:\t area, same reason as the .iss/host.env staging above). Built upstream -# (windows-host.yml mirrors deb.yml: bun build -> node-server preset + the .output/server deps); -# omitted when -WebDir/-NodeExe are unset (host-only installer, e.g. a local debug pack). -if ($WebDir -and (Test-Path $WebDir) -and $NodeExe -and (Test-Path $NodeExe)) { +# non-WOW64-redirected C:\t area, same reason as the .iss/host.env staging above). The .output is +# self-contained (Nitro noExternals — deps bundled + tree-shaken, no node_modules), so bun runs it +# directly; omitted when -WebDir/-BunExe are unset (host-only installer, e.g. a local debug pack). +if ($WebDir -and (Test-Path $WebDir) -and $BunExe -and (Test-Path $BunExe)) { $webStage = Join-Path $OutDir 'web' if (Test-Path $webStage) { Remove-Item $webStage -Recurse -Force } New-Item -ItemType Directory -Force -Path $webStage | Out-Null Copy-Item (Join-Path $WebDir '*') -Destination $webStage -Recurse -Force - $nodeStage = Join-Path $OutDir 'node.exe' - Copy-Item -LiteralPath $NodeExe -Destination $nodeStage -Force + $bunStage = Join-Path $OutDir 'bun.exe' + Copy-Item -LiteralPath $BunExe -Destination $bunStage -Force $webRun = Join-Path $OutDir 'web-run.cmd' $webSetup = Join-Path $OutDir 'web-setup.ps1' Copy-Item (Join-Path $repoRoot 'scripts\windows\web-run.cmd') -Destination $webRun -Force Copy-Item (Join-Path $repoRoot 'scripts\windows\web-setup.ps1') -Destination $webSetup -Force $defines += "/DWebDir=$webStage" - $defines += "/DNodeExe=$nodeStage" + $defines += "/DBunExe=$bunStage" $defines += "/DWebRunCmd=$webRun" $defines += "/DWebSetup=$webSetup" - Write-Host "bundling the web console from $WebDir (+ node $NodeExe)" + Write-Host "bundling the web console from $WebDir (+ bun $BunExe)" } -else { Write-Host "no -WebDir/-NodeExe -> installer built WITHOUT the web console" } +else { Write-Host "no -WebDir/-BunExe -> installer built WITHOUT the web console" } # --- build the installer (from the non-redirected copy under C:\t) ----------------------------- Write-Host "==> ISCC $($defines -join ' ') $issLocal" diff --git a/packaging/windows/punktfunk-host.iss b/packaging/windows/punktfunk-host.iss index fdaec6d..227b230 100644 --- a/packaging/windows/punktfunk-host.iss +++ b/packaging/windows/punktfunk-host.iss @@ -49,10 +49,10 @@ #ifdef FfmpegBin #define WithFfmpeg #endif -; WebDir (the built web .output tree) + NodeExe (a portable node.exe) are passed together by +; WebDir (the built web .output tree) + BunExe (a portable bun.exe) are passed together by ; pack-host-installer.ps1 to bundle the management console. Both required → WithWeb. #ifdef WebDir - #ifdef NodeExe + #ifdef BunExe #define WithWeb #endif #endif @@ -102,12 +102,12 @@ Source: "{#Readme}"; DestDir: "{app}"; DestName: "README.txt"; Flags: ignorevers Source: "{#FfmpegBin}\*.dll"; DestDir: "{app}"; Flags: ignoreversion #endif #ifdef WithWeb -; The web management console: the built Nitro/Node SSR bundle (.output = server + public assets) → -; {app}\web\.output, a portable Node runtime → {app}\node\node.exe, and the launcher the -; PunktfunkWeb task runs → {app}\web\web-run.cmd. web-setup.ps1 (the provisioner) goes to {tmp} and -; is removed after install. +; The web management console: the self-contained Nitro SSR bundle (.output = server + public; deps +; bundled in, no node_modules) → {app}\web\.output, a portable bun runtime → {app}\bun\bun.exe, and +; the launcher the PunktfunkWeb task runs → {app}\web\web-run.cmd. web-setup.ps1 (the provisioner) +; goes to {tmp} and is removed after install. Source: "{#WebDir}\*"; DestDir: "{app}\web\.output"; Flags: ignoreversion recursesubdirs createallsubdirs -Source: "{#NodeExe}"; DestDir: "{app}\node"; DestName: "node.exe"; Flags: ignoreversion +Source: "{#BunExe}"; DestDir: "{app}\bun"; DestName: "bun.exe"; Flags: ignoreversion Source: "{#WebRunCmd}"; DestDir: "{app}\web"; DestName: "web-run.cmd"; Flags: ignoreversion Source: "{#WebSetup}"; DestDir: "{tmp}"; DestName: "web-setup.ps1"; Flags: deleteafterinstall #endif diff --git a/scripts/windows/README.md b/scripts/windows/README.md index 0e5e381..c75eab8 100644 --- a/scripts/windows/README.md +++ b/scripts/windows/README.md @@ -35,10 +35,10 @@ won't start. The service is down only for the build duration. ## Web management console On an **installed** host (the `setup.exe`) the console is set up automatically — no manual steps. -The installer bundles the built `.output` server + a portable Node and runs -`scripts\windows\web-setup.ps1`, which registers the **`PunktfunkWeb`** scheduled task (at boot, as -SYSTEM, restart-on-failure) running `{app}\web\web-run.cmd` → `node …\.output\server\index.mjs` on -`:3000`, opens inbound TCP 3000, and writes the login password to +The installer bundles the built (self-contained, no-`node_modules`) `.output` server + a portable +bun and runs `scripts\windows\web-setup.ps1`, which registers the **`PunktfunkWeb`** scheduled task +(at boot, as SYSTEM, restart-on-failure) running `{app}\web\web-run.cmd` → +`bun …\.output\server\index.mjs` on `:3000`, opens inbound TCP 3000, and writes the login password to `%ProgramData%\punktfunk\web-password` (ACL'd to Administrators + SYSTEM). The mgmt bearer token it proxies with is the host's own `%ProgramData%\punktfunk\mgmt-token`. Browse `http://:3000` and log in with the password the installer shows on its final page. To change it, edit diff --git a/scripts/windows/build-web.ps1 b/scripts/windows/build-web.ps1 index 4e54e7b..70ebba5 100644 --- a/scripts/windows/build-web.ps1 +++ b/scripts/windows/build-web.ps1 @@ -3,9 +3,9 @@ powershell -ExecutionPolicy Bypass -File scripts\windows\build-web.ps1 - bun = build tool, node = runtime (the Nitro bundle externalizes srvx/@unom for SSR, which - bun fails to resolve at runtime). The PunktfunkWeb scheduled task runs web\web-run.cmd -> - node .output\server\index.mjs on :3000. + bun is both the build tool AND the runtime: vite.config's Nitro noExternals bundles every dep + into the self-contained .output (no node_modules, nothing for bun to fail to resolve), so the + PunktfunkWeb task runs web\web-run.cmd -> bun .output\server\index.mjs on :3000. #> $ErrorActionPreference = 'Stop' $repo = Split-Path (Split-Path $PSScriptRoot) @@ -19,17 +19,11 @@ Write-Host "bun install + build ..." & $bun install & $bun run build if ($LASTEXITCODE -ne 0) { throw "web build failed (exit $LASTEXITCODE)" } - -# The Nitro server bundle externalizes its runtime deps - install them in .output/server, -# with the @unom registry .npmrc present (else @unom/* 404s on npmjs). -Write-Host "installing externalized server deps ..." -Copy-Item "$web\.npmrc" "$web\.output\server\.npmrc" -Force -Set-Location "$web\.output\server" -& $bun install +# No .output/server install: noExternals means the output has no externalized deps to resolve. Write-Host "restarting $task ..." & schtasks /end /tn $task 2>$null | Out-Null -Get-CimInstance Win32_Process -Filter "Name='node.exe'" -ErrorAction SilentlyContinue | +Get-CimInstance Win32_Process -Filter "Name='bun.exe'" -ErrorAction SilentlyContinue | Where-Object { $_.CommandLine -match 'index\.mjs' } | ForEach-Object { Stop-Process -Id $_.ProcessId -Force -ErrorAction SilentlyContinue } Start-Sleep 2 diff --git a/scripts/windows/web-run.cmd b/scripts/windows/web-run.cmd index f6fc262..a9e4cbd 100644 --- a/scripts/windows/web-run.cmd +++ b/scripts/windows/web-run.cmd @@ -2,10 +2,10 @@ rem punktfunk web console launcher — the action the PunktfunkWeb scheduled task runs at boot. rem rem Lays out next to the installed payload: {app}\web\web-run.cmd, {app}\web\.output\... and -rem {app}\node\node.exe (so %~dp0 = {app}\web\). Auto-wires the console the same way the Linux +rem {app}\bun\bun.exe (so %~dp0 = {app}\web\). Auto-wires the console the same way the Linux rem systemd unit does: it sources the host's mgmt bearer token + the console login password from rem %ProgramData%\punktfunk\, points the /api proxy at the host's loopback HTTPS mgmt API, and runs -rem the Nitro/Node server on :3000. No env editing on a packaged install. +rem the (self-contained, no-node_modules) Nitro server on :3000 with the bundled bun. No env editing. setlocal EnableExtensions set "PFDATA=%ProgramData%\punktfunk" @@ -31,10 +31,10 @@ set "HOST=0.0.0.0" set "PUNKTFUNK_MGMT_URL=https://127.0.0.1:47990" set "NODE_TLS_REJECT_UNAUTHORIZED=0" -set "NODE=%~dp0..\node\node.exe" +set "BUN=%~dp0..\bun\bun.exe" set "SERVER=%~dp0.output\server\index.mjs" -if not exist "%NODE%" ( - echo [punktfunk-web] bundled node runtime missing at "%NODE%". +if not exist "%BUN%" ( + echo [punktfunk-web] bundled bun runtime missing at "%BUN%". exit /b 1 ) -"%NODE%" "%SERVER%" +"%BUN%" "%SERVER%" diff --git a/scripts/windows/web-setup.ps1 b/scripts/windows/web-setup.ps1 index 74416f7..9e9772e 100644 --- a/scripts/windows/web-setup.ps1 +++ b/scripts/windows/web-setup.ps1 @@ -1,6 +1,6 @@ <# Provision the punktfunk web console after the host installer has laid down its payload - ({app}\web\.output, {app}\node\node.exe, {app}\web\web-run.cmd). Invoked elevated from the + ({app}\web\.output, {app}\bun\bun.exe, {app}\web\web-run.cmd). Invoked elevated from the installer's [Run] section; idempotent (safe to re-run on upgrade). 1. Sets the console login password file %ProgramData%\punktfunk\web-password @@ -71,7 +71,7 @@ $settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoi -StartWhenAvailable -RestartInterval (New-TimeSpan -Minutes 1) -RestartCount 10 ` -ExecutionTimeLimit (New-TimeSpan -Seconds 0) Register-ScheduledTask -TaskName $TaskName -Action $action -Trigger $trigger -Principal $principal ` - -Settings $settings -Description 'punktfunk web management console (Nitro/Node SSR on :3000)' ` + -Settings $settings -Description 'punktfunk web management console (Nitro SSR on bun, :3000)' ` -Force | Out-Null Write-Host "registered scheduled task $TaskName -> $cmd" diff --git a/web/vite.config.ts b/web/vite.config.ts index 41975d8..c608d4a 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -46,6 +46,14 @@ export default defineConfig({ // .deb depend on apt-native `nodejs (>= 20)` instead of vendoring bun. CI still BUILDS with // bun; only the runtime target changes. (dev `vite dev` is unaffected.) preset: 'node-server', + // BUNDLE every dependency into the server output (no externalized node_modules). Three wins: + // (1) the .output tree drops from ~47k files / 730 MB (the whole untree-shaken @unom/ui dep + // tree — payload, lexical, date-fns…) to a handful of tree-shaken chunks; (2) it makes the + // output a self-contained graph `bun build --compile` can fold into ONE native binary (the + // Windows installer ships that instead of node + a node_modules forest); (3) it removes the + // bare external imports (`srvx`, `seroval`…) bun couldn't resolve at runtime — the reason we + // used to need node. node still runs the same self-contained output for the Linux .deb. + noExternals: true, compatibilityDate: '2026-06-10', // Scan server/{middleware,routes} for the auth gate + the /api proxy. scanDirs: [serverDir],