feat(packaging): nix web console + plugin/script runner everywhere; fix tray zbus/tokio crash
ci / web (push) Successful in 58s
apple / swift (push) Successful in 1m24s
ci / docs-site (push) Successful in 59s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 17s
decky / build-publish (push) Successful in 25s
windows-host / package (push) Successful in 19m46s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 17s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 17s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 18s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 17s
android / android (push) Successful in 16m12s
arch / build-publish (push) Successful in 12m14s
ci / bench (push) Successful in 5m27s
apple / screenshots (push) Successful in 6m32s
ci / rust (push) Successful in 19m26s
deb / build-publish (push) Successful in 12m58s
docker / deploy-docs (push) Successful in 24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 24m51s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 24m51s

Ship the management web console and the plugin/script runner across the packaging
matrix, and fix a Linux tray startup crash.

- nix: add punktfunk-web (Nitro SSR on bun) and punktfunk-scripting (Effect runner),
  with services.punktfunk.{web,scripting} modules. Both bun node_modules come from
  fixed-output derivations pinned to real deps hashes; patchShebangs the web
  node_modules so orval/paraglide/vite run in the sandbox. Validated on x86_64-linux
  (nix build .#punktfunk-{web,scripting,tray,host} all green).

- punktfunk-scripting now ships for deb/rpm/arch/nix/windows, mirroring the web-console
  mechanics (vendored bun on deb/rpm/arch, pkgs.bun on nix; one self-contained
  `bun build --target=bun` bundle). Opt-in: host Recommends it, but the systemd --user
  unit ships un-started (Windows registers its scheduled task disabled).

- fix(tray): build punktfunk-tray in its OWN cargo invocation on nix/rpm/arch. Cargo
  unifies features across one `cargo build`, so co-building the tray with the host pulls
  the host's ashpd->zbus/tokio onto the tray's shared zbus and it panics at startup
  ("there is no reactor running, must be called from the context of a Tokio 1.x
  runtime"). Isolated, the tray's zbus stays on async-io. The .deb already did this;
  runtime-verified on nix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-17 21:15:49 +02:00
parent fb92dcd1c2
commit e7a8cf2bba
17 changed files with 912 additions and 43 deletions
+34 -11
View File
@@ -27,7 +27,8 @@ 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]$BunExe = $env:BUN_EXE, # portable bun.exe runtime for the console
[string]$ScriptingBundle = $env:SCRIPTING_BUNDLE, # built runner-cli.js -> bundle the plugin/script runner
[string]$BunExe = $env:BUN_EXE, # portable bun.exe runtime for the console + runner
[string]$VbCableDir = $env:VBCABLE_DIR, # official base VB-CABLE package -> bundle the virtual mic
[switch]$NoDriver, # build without the bundled pf-vdisplay driver
[switch]$NoSign # skip signing (local debug)
@@ -257,28 +258,50 @@ 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 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). 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)) {
# --- stage the bun runtime + the two bun payloads (web console, plugin/script runner) --------------
# Both the web console and the runner run on bun. Stage everything ISCC reads into $OutDir (the
# non-WOW64-redirected C:\t area, same reason as the .iss/host.env staging above). bun is staged ONCE
# and shared: the two payloads pass their own defines and the .iss keys WithWeb / WithScripting on
# (their dir + BunExe). Each payload is omitted when its inputs are unset (e.g. a local debug pack).
$haveBun = $BunExe -and (Test-Path $BunExe)
$wantWeb = $WebDir -and (Test-Path $WebDir) -and $haveBun
$wantScripting = $ScriptingBundle -and (Test-Path $ScriptingBundle) -and $haveBun
if ($wantWeb -or $wantScripting) {
$bunStage = Join-Path $OutDir 'bun.exe'
Copy-Item -LiteralPath $BunExe -Destination $bunStage -Force
$defines += "/DBunExe=$bunStage"
}
# The web console: the self-contained .output tree (Nitro noExternals - deps bundled + tree-shaken,
# no node_modules), run by the PunktfunkWeb scheduled task, auto-wired to the host's loopback mgmt API.
if ($wantWeb) {
$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
$bunStage = Join-Path $OutDir 'bun.exe'
Copy-Item -LiteralPath $BunExe -Destination $bunStage -Force
$webRun = Join-Path $OutDir 'web-run.cmd'
Copy-Item (Join-Path $repoRoot 'scripts\windows\web-run.cmd') -Destination $webRun -Force
# The console is provisioned by `punktfunk-host.exe web setup` (not a staged web-setup.ps1).
$defines += "/DWebDir=$webStage"
$defines += "/DBunExe=$bunStage"
$defines += "/DWebRunCmd=$webRun"
Write-Host "bundling the web console from $WebDir (+ bun $BunExe)"
}
else { Write-Host "no -WebDir/-BunExe -> installer built WITHOUT the web console" }
# The plugin/script runner: one self-contained bundle (effect + the SDK inlined). Its scheduled task
# is registered DISABLED (opt-in) by the installer. Built by CI (SCRIPTING_BUNDLE) alongside the web
# console; omitted when -ScriptingBundle/-BunExe are unset.
if ($wantScripting) {
$scrStage = Join-Path $OutDir 'scripting'
if (Test-Path $scrStage) { Remove-Item $scrStage -Recurse -Force }
New-Item -ItemType Directory -Force -Path $scrStage | Out-Null
$scrBundle = Join-Path $scrStage 'runner-cli.js'
Copy-Item -LiteralPath $ScriptingBundle -Destination $scrBundle -Force
$scrRun = Join-Path $scrStage 'scripting-run.cmd'
Copy-Item (Join-Path $repoRoot 'scripts\windows\scripting-run.cmd') -Destination $scrRun -Force
$defines += "/DScriptingBundle=$scrBundle"
$defines += "/DScriptingRunCmd=$scrRun"
Write-Host "bundling the plugin/script runner from $ScriptingBundle (+ bun $BunExe)"
}
else { Write-Host "no -ScriptingBundle/-BunExe -> installer built WITHOUT the plugin/script runner" }
# --- build + stage the HDR Vulkan layer (pf-vkhdr-layer) --------------------------------------
# A tiny always-on Vulkan implicit layer (cdylib) that advertises HDR10/scRGB surface formats on the
+44 -4
View File
@@ -40,6 +40,11 @@
#ifndef WebRunCmd
#define WebRunCmd "..\..\scripts\windows\web-run.cmd"
#endif
; The plugin/script runner launcher (the action the opt-in PunktfunkScripting task runs) - staged
; next to the .iss by pack-host-installer.ps1 (absolute path passed in).
#ifndef ScriptingRunCmd
#define ScriptingRunCmd "..\..\scripts\windows\scripting-run.cmd"
#endif
; StageDir (the staged pf-vdisplay payload + nefconc.exe + install-pf-vdisplay.ps1) is optional.
#ifdef StageDir
#define WithDriver
@@ -66,6 +71,13 @@
#define WithWeb
#endif
#endif
; ScriptingBundle (the built runner-cli.js) + BunExe are passed together by pack-host-installer.ps1
; to bundle the plugin/script runner. Both required -> WithScripting.
#ifdef ScriptingBundle
#ifdef BunExe
#define WithScripting
#endif
#endif
; VkLayerDir (the staged pf-vkhdr-layer: pf_vkhdr_layer.dll + .json) is optional - present when the
; HDR Vulkan layer was built. It lets Vulkan games (Doom: The Dark Ages, etc.) enable HDR over the
; virtual display (the ICD won't advertise HDR there; the layer injects the surface formats, self-
@@ -179,15 +191,26 @@ Source: "{#LicensesDir}\*"; DestDir: "{app}\licenses"; Flags: ignoreversion
; dynamically (replaceable DLLs) - FFmpeg is used under the LGPL v2.1+; see {app}\licenses.
Source: "{#FfmpegBin}\*.dll"; DestDir: "{app}"; Flags: ignoreversion
#endif
; The portable bun runtime -> {app}\bun\bun.exe. Shared by the web console AND the plugin/script
; runner (both run on bun), so stage it once when EITHER is bundled.
#if defined(WithWeb) || defined(WithScripting)
Source: "{#BunExe}"; DestDir: "{app}\bun"; DestName: "bun.exe"; Flags: ignoreversion
#endif
#ifdef WithWeb
; 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. (`punktfunk-host.exe web setup`
; provisions the console at install time - no staged provisioner script.)
; bundled in, no node_modules) -> {app}\web\.output, and the launcher the PunktfunkWeb task runs ->
; {app}\web\web-run.cmd. (`punktfunk-host.exe web setup` provisions the console at install time - no
; staged provisioner script.)
Source: "{#WebDir}\*"; DestDir: "{app}\web\.output"; Flags: ignoreversion recursesubdirs createallsubdirs
Source: "{#BunExe}"; DestDir: "{app}\bun"; DestName: "bun.exe"; Flags: ignoreversion
Source: "{#WebRunCmd}"; DestDir: "{app}\web"; DestName: "web-run.cmd"; Flags: ignoreversion
#endif
#ifdef WithScripting
; The plugin/script runner: one self-contained bundle (effect + the SDK inlined) -> {app}\scripting\
; runner-cli.js, and the launcher the (opt-in) PunktfunkScripting task runs -> {app}\scripting\
; scripting-run.cmd. Runs on the shared bun above.
Source: "{#ScriptingBundle}"; DestDir: "{app}\scripting"; DestName: "runner-cli.js"; Flags: ignoreversion
Source: "{#ScriptingRunCmd}"; DestDir: "{app}\scripting"; DestName: "scripting-run.cmd"; Flags: ignoreversion
#endif
#ifdef WithDriver
; The driver payload + nefconc.exe + install-pf-vdisplay.ps1, extracted to {tmp} and removed after install.
Source: "{#StageDir}\*"; DestDir: "{tmp}\pfvdisplay"; Flags: deleteafterinstall recursesubdirs createallsubdirs; Tasks: installdriver
@@ -255,6 +278,16 @@ Filename: "{app}\punktfunk-host.exe"; Parameters: "service start"; WorkingDir: "
Filename: "{app}\punktfunk-host.exe"; Parameters: "web setup {code:WebSetupParams}{code:PublicFwParam}"; WorkingDir: "{app}"; \
StatusMsg: "Setting up the punktfunk web console..."; Flags: runhidden waituntilterminated
#endif
#ifdef WithScripting
; Register the plugin/script runner's scheduled task (boot, SYSTEM, restart-on-failure) but leave it
; DISABLED - the runner is OPT-IN (inert until you add scripts/plugins). Enable it when ready:
; Enable-ScheduledTask -TaskName PunktfunkScripting
; Best-effort (-ErrorAction SilentlyContinue): a task hiccup never fails the whole install. No braces
; in the command, so no Inno {{ }} escaping needed.
Filename: "powershell.exe"; \
Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""$a=New-ScheduledTaskAction -Execute '{app}\scripting\scripting-run.cmd'; $t=New-ScheduledTaskTrigger -AtStartup; $p=New-ScheduledTaskPrincipal -UserId 'SYSTEM' -LogonType ServiceAccount -RunLevel Highest; $s=New-ScheduledTaskSettingsSet -RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries; Register-ScheduledTask -TaskName PunktfunkScripting -Action $a -Trigger $t -Principal $p -Settings $s -Force -ErrorAction SilentlyContinue | Out-Null; Disable-ScheduledTask -TaskName PunktfunkScripting -ErrorAction SilentlyContinue | Out-Null"""; \
StatusMsg: "Registering the punktfunk script runner (disabled; opt-in)..."; Flags: runhidden waituntilterminated
#endif
; Launch the status tray as the SIGNED-IN user (not the elevated install user) right away, so the
; icon appears without waiting for the next sign-in.
Filename: "{app}\punktfunk-tray.exe"; Flags: runasoriginaluser nowait skipifsilent; Tasks: trayicon
@@ -283,6 +316,13 @@ Filename: "powershell.exe"; \
Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""Stop-ScheduledTask -TaskName PunktfunkWeb -ErrorAction SilentlyContinue; Get-NetTCPConnection -LocalPort 47992,3000 -State Listen -ErrorAction SilentlyContinue | ForEach-Object {{ Stop-Process -Id $_.OwningProcess -Force -ErrorAction SilentlyContinue }; Unregister-ScheduledTask -TaskName PunktfunkWeb -Confirm:$false -ErrorAction SilentlyContinue; Get-NetFirewallRule -DisplayName 'punktfunk web console (*' -ErrorAction SilentlyContinue | Remove-NetFirewallRule"""; \
Flags: runhidden waituntilterminated; RunOnceId: "PunktfunkWebCleanup"
#endif
#ifdef WithScripting
; Stop + remove the PunktfunkScripting task (leaves %ProgramData%\punktfunk config + the operator's
; scripts/plugins, like the rest of the uninstall does). Unconditional cleanup of the task name.
Filename: "powershell.exe"; \
Parameters: "-NoProfile -ExecutionPolicy Bypass -Command ""Stop-ScheduledTask -TaskName PunktfunkScripting -ErrorAction SilentlyContinue; Unregister-ScheduledTask -TaskName PunktfunkScripting -Confirm:$false -ErrorAction SilentlyContinue"""; \
Flags: runhidden waituntilterminated; RunOnceId: "PunktfunkScriptingCleanup"
#endif
[Code]
{ True if another Moonlight-compatible streaming host is installed - by its SCM service key or its