Files
punktfunk/web/.env.example
enricobuehler bb78117504 feat(host): moving the management port off 47990 now survives, and the console follows
47990 is the management API's port and also Sunshine's (and Apollo's, and Vibeshine's) web UI
port. With the GameStream planes off it is the ONLY port the two still share, so moving it is the
whole of what "run both on one box" needs — except moving it was barely possible:

* `--mgmt-bind` was the sole route, and it lives in a unit file / service registration that a
  package upgrade rewrites. There was no `host.env` key, so the change did not survive.
* The literal 47990 appeared in SIX places — mgmt::DEFAULT_PORT, the Windows service's console
  launch, scripts/punktfunk-web.service, the NixOS module, web/web-run.cmd, and the console's own
  default. Nothing downstream could learn a different port, so moving the listener silently left
  the console proxying to a port nothing was listening on.

Now there is one source of truth. `PUNKTFUNK_MGMT_BIND` joins `host.env` (the `--gamestream` /
PUNKTFUNK_GAMESTREAM shape: either source works, the CLI flag wins), and `serve` publishes the port
it ACTUALLY bound to ~/.config/punktfunk/mgmt-endpoint, in the same KEY=VALUE form mgmt-token
already uses so it is sourceable as a systemd EnvironmentFile and readable by the Windows service's
existing read_env_file_value. Every consumer derives from that; the 47990 literals survive only as
the fallback that keeps an OLD host working with a NEW console.

The two unit files drop their hardcoded `Environment=PUNKTFUNK_MGMT_URL=` rather than layering a
default beneath the file: whether Environment= or EnvironmentFile= wins is a directive-ordering
question, and the hand-written unit and the Nix-generated one do not order the same way. No
default, no precedence puzzle — the server's own built-in fallback covers a host that never wrote
the file.

Two robustness details worth naming, because both fail in the same direction:
* mgmt-endpoint is written write-then-rename. A torn read would set PUNKTFUNK_MGMT_URL to EMPTY,
  which is worse than a missing file — a built-in default only rescues an *unset* variable.
* mgmtUrl() now treats blank as unset, which `??` alone does not.

The publish happens in parse_serve next to the token persistence, so both files appear together;
the console's unit gates on mgmt-token, and its Restart=always picks up a lost race anyway.

What this does NOT change: a lost 47990 bind is still fatal to the whole host (the bind sits in
tokio::try_join! with the native plane), and running two Moonlight-compatible hosts at once is
still unsupported — on Windows the exclusive display topology is a second, independent conflict.
Both are documented rather than altered.

Verified on Linux in punktfunk-rust-ci (amd64): cargo check --all-targets clean for punktfunk-host
and pf-host-config with the "Checking punktfunk-host" marker confirmed present (a first run exited
0 having compiled nothing — the warm shared target dir judged it fresh), 40/40 mgmt tests pass
including the new one pinning the published line against both parsers that consume it. Console:
tsc --noEmit clean, bun test server/ 9/9. cargo fmt --all --check clean.
2026-08-14 19:44:19 +02:00

66 lines
3.9 KiB
Bash

# punktfunk web — management console (Nitro server on bun) configuration.
# Copy to `.env` (gitignored) or set these in the environment of `bun .output/server/index.mjs`.
# NOTE: on a packaged install (the punktfunk-web .deb) you edit NOTHING — the systemd --user units
# auto-wire these from the host's ~/.config/punktfunk/{mgmt-token,web-password,cert.pem,key.pem}.
# See web.env.example.
# REQUIRED in production: the shared login password for the console. The built Nitro
# server fails CLOSED (503 on every request) if this is unset, so a LAN-exposed server
# never admits anyone by accident.
PUNKTFUNK_UI_PASSWORD=change-me
# Management API the console proxies to. It serves HTTPS (the host's own identity cert) and
# requires auth (mTLS or the bearer below). Keep this loopback — the login-gated web server is
# the only path to it.
#
# ON A PACKAGED INSTALL YOU DO NOT SET THIS. The host writes the port it actually bound to
# ~/.config/punktfunk/mgmt-endpoint in this same KEY=VALUE form, and the shipped units source that
# file — so a host moved off 47990 (PUNKTFUNK_MGMT_BIND, e.g. to coexist with a Sunshine fork whose
# web UI owns that port) carries the console with it. This line is for dev, where you run the two
# halves by hand. Setting it explicitly always wins over the file.
PUNKTFUNK_MGMT_URL=https://127.0.0.1:47990
# REQUIRED: bearer token for the management API, injected server-side by the /api proxy (never
# sent to the browser). Must match the host's `--mgmt-token` / PUNKTFUNK_MGMT_TOKEN — otherwise
# the proxy gets 401.
PUNKTFUNK_MGMT_TOKEN=
# NOTE: NODE_TLS_REJECT_UNAUTHORIZED is intentionally NOT set. The host's self-signed loopback cert
# is accepted only for the /api proxy's loopback hop — scoped inside the proxy code (Bun per-request
# TLS: server/routes/api/[...].ts), so it can never silently unverify some other outbound TLS. A
# NON-loopback PUNKTFUNK_MGMT_URL is verified normally (present a valid chain).
# OPTIONAL: explicit cookie-sealing secret (>= 32 chars). If unset, the key is derived from the
# high-entropy PUNKTFUNK_MGMT_TOKEN (so a captured cookie is NOT an offline password oracle); only if
# no token is configured (dev/local) does it fall back to deriving from PUNKTFUNK_UI_PASSWORD.
# Rotating the mgmt token invalidates existing sessions.
# PUNKTFUNK_UI_SECRET=
# TLS: serve the console over HTTPS (HTTP/1.1 over TLS) using the HOST's own identity cert (the cert
# native clients already pin). Point these at the host's PEM files; BOTH set ⇒ HTTPS. Unset ⇒ plain
# HTTP (local dev only). (No HTTP/2 or HTTP/3: Bun.serve has no HTTP/2 server, and a browser won't
# speak HTTP/3/QUIC against this self-signed, no-SAN host cert.)
PUNKTFUNK_UI_TLS_CERT=/home/you/.config/punktfunk/cert.pem
PUNKTFUNK_UI_TLS_KEY=/home/you/.config/punktfunk/key.pem
# REQUIRED when serving over TLS: mark the session cookie Secure (browsers drop a Secure cookie over
# plain http://, so it is OFF by default; turn it ON whenever PUNKTFUNK_UI_TLS_* is set).
PUNKTFUNK_UI_SECURE=1
# The Bun server binds these (standard Nitro env):
# PORT=47992
# HOST=0.0.0.0
# The port plugin UIs are served on — their OWN ORIGIN, not the console's. Defaults to PORT + 1.
#
# This is a security boundary, not a layout choice. A plugin's interface is third-party code; served
# on the console's origin it ran as first-party script with the operator's session and could drive
# the whole admin API (security-review 2026-08-05 H-3). Same host, same certificate, different port
# means a different ORIGIN to the browser (so the same-origin policy separates them) while staying
# the same SITE (so the SameSite=Lax session cookie still reaches it and plugin pages keep working).
#
# The console refuses to serve plugin UIs on its own origin, so if this port cannot be bound, plugin
# UIs are DISABLED rather than silently moved back — the console says so on the plugin page.
# Open it in the firewall alongside PORT if you reach the console from other devices.
# PUNKTFUNK_UI_PLUGIN_PORT=47993