Moving the mgmt port off 47990 (the fix for sharing a box with a Sunshine fork, whose web UI owns
that port) only ever worked for as long as mDNS did. The real port lived in the advert and nowhere
else: every client read it live and threw it away, so on a VPN, a routed subnet, or any
multicast-dead network the library silently fell back to a port nothing was listening on.
`KnownHost` gains `mgmt_port: Option<u16>` + `effective_mgmt_port()` + `learn_mgmt_port()`, exactly
the shape `mac` and `os` already use ("learned from the advert while online, persisted so it
survives the host going to sleep") — except this one is load-bearing rather than cosmetic, so
`upsert` states the preserve rule explicitly instead of relying on the does-not-mention-it accident
that `clipboard_sync` survives by, and `upsert_trusted` carries it across a re-key.
Wired through all four client families, each of which was wrong in its own way:
* CLI / Windows / Linux reached for `DEFAULT_MGMT_PORT` at the call site — the constant is the
FALLBACK, not the answer. Windows also needed the port on `Target`, which the library screen has
instead of a `KnownHost`.
* The session console read `advert.and_then(mgmt_port)` with NO saved fallback, two lines above an
`os` that gets the three-rung treatment right. It now matches, and learns on every tick.
* Linux's `mgmt_port_for` consulted live adverts only; it now falls back to the store.
* Android never carried the port at all — its native discovery record stopped at 8 fields. Added
`mgmt` as the 9th (the record's own documented "new fields append, never reorder" rule), then
through `DiscoveredHost` -> `KnownHost` -> `LibraryScreen`.
* Apple LOOKED done and was not: `StoredHost.mgmtPort` and `effectiveMgmtPort` have existed all
along, but nothing anywhere wrote the field and the `mgmt` TXT was never parsed — so it was
permanently nil and every Apple client resolved to 47990 regardless. That is worse than the
honest omissions above, because it reads as finished. Now parsed, carried on `DiscoveredHost`,
and written by `HostStore.updateMgmtPort` at the same site that learns MACs and the OS chain.
Also `PUNKTFUNK_NATIVE_PORT` in host.env, finishing the pair with PUNKTFUNK_MGMT_BIND: `--native-port`
was likewise CLI-only and died on a package upgrade. A bad value is a startup ERROR rather than the
silent fall back to 9777 that `PUNKTFUNK_DATA_PORT` still does — the failure that reads as "I moved
the port and the client still can't reach me". The client side of the native port already worked
(`KnownHost.port` is persisted, `--connect HOST:PORT` names it).
Adding the field broke three `KnownHost` literals in tests, which is the `Default` impl's stated
purpose working ("adding a field here can't silently produce records that lack it"). All three now
carry 47991 — deliberately NOT the default, so the assertions cannot pass vacuously against a
hardcode. New coverage: forward-compat decode of a store predating the field, the resolver
fallback, re-key carry-forward, and on Android the 9th-field parse plus 0/non-numeric/out-of-range
all reading as unknown.
What this does NOT fix: a host that moved its mgmt port and has NEVER been seen over mDNS. Nothing
tells the client where to look, and the honest fix is for the host to announce it in-band — the
`Welcome` message has an established "append a trailing field, older peer decodes to the default"
pattern for exactly this, at the cost of a C ABI accessor and a bump. Left for a separate change.
Verified: Linux (punktfunk-rust-ci/pf-lxcheck2, amd64) `cargo check --all-targets` clean for
pf-host-config, punktfunk-host, pf-client-core, punktfunk-cli, punktfunk-client-linux and
punktfunk-client-session — the last confirmed non-vacuous by planting a compile_error! and watching
the gate fail (cargo prints "Compiling", not "Checking", for bin-only packages, so the usual marker
grep lies about it). Android: :kit + :app compileDebugKotlin clean, ParseRecordTest 12/12 with both
new cases named in the XML. Apple: xcframework built, `swift build` complete, SharedFoundationTests
pass. cargo fmt --all --check clean. NOT verified: the Windows client (192.168.1.133 unreachable).
142 lines
10 KiB
Bash
142 lines
10 KiB
Bash
# punktfunk host configuration (~/.config/punktfunk/host.env) — consumed by punktfunk-host.service.
|
|
#
|
|
# YOU BARELY NEED THIS FILE. The host AUTO-DETECTS the live session per connect — which compositor
|
|
# is running (KWin / Mutter / sway / Hyprland / gamescope), its Wayland socket, session bus, and the
|
|
# matching input backend — and FOLLOWS the box when it switches between a desktop and Steam Gaming
|
|
# Mode, even mid-stream. EVERYTHING below is an optional override — the host runs with sane
|
|
# defaults if this file does not exist at all (its systemd unit no longer requires it).
|
|
#
|
|
# Two rules that save debugging sessions:
|
|
# * Keys are CASE-SENSITIVE. `punktfunk_gamescope_attach=1` sets nothing — use the exact
|
|
# uppercase names.
|
|
# * On a desktop you actually use, do NOT set PUNKTFUNK_COMPOSITOR / WAYLAND_DISPLAY /
|
|
# XDG_CURRENT_DESKTOP. Pinning the compositor DISABLES session-following (a switch to Game
|
|
# Mode mid-stream then kills the stream instead of being followed), and stale session vars
|
|
# point detection at dead sockets. Those knobs are for CI and dedicated appliances (below).
|
|
|
|
# GameStream/Moonlight compatibility — OPT-IN (the shipped unit runs the secure native-only
|
|
# host). Set =1 so stock Moonlight clients can pair and stream; TRUSTED LANs only — the compat
|
|
# planes carry plain-HTTP pairing + the legacy GCM-nonce path (security-review #5/#9), and need
|
|
# their extra firewall ports (TCP 47984/47989/48010, UDP 47998-48000/48002/48010; the packages
|
|
# ship a `punktfunk-gamestream` firewalld service / ufw profile for exactly this).
|
|
#PUNKTFUNK_GAMESTREAM=1
|
|
|
|
# Where the management API listens (default 0.0.0.0:47990). Two uses:
|
|
# * 127.0.0.1:47990 keeps it off the LAN — at the cost of paired clients browsing your library.
|
|
# * MOVING THE PORT is how you share a machine with Sunshine/Apollo/Vibeshine: 47990 is their web
|
|
# UI as well as our management API, and with PUNKTFUNK_GAMESTREAM off it is the ONLY port the
|
|
# two still share. Nothing else needs editing — clients learn the port from discovery and the
|
|
# web console reads it from ~/.config/punktfunk/mgmt-endpoint, which the host rewrites on start.
|
|
# Running two Moonlight-compatible hosts at once is still unsupported; see the troubleshooting
|
|
# page. On Windows also see PUNKTFUNK_NO_ISOLATE — the display topology is the second conflict.
|
|
#PUNKTFUNK_MGMT_BIND=0.0.0.0:47991
|
|
|
|
# The native punktfunk/1 (QUIC) control port clients connect on. Default 9777. Clients discover it
|
|
# over mDNS, and a host added by hand keeps whatever port it was added with, so moving this is safe
|
|
# on both sides. A typo here is a startup ERROR rather than a silent fall back to 9777.
|
|
#PUNKTFUNK_NATIVE_PORT=9778
|
|
|
|
# Video source (GameStream/Moonlight sessions only): `virtual` creates a per-client virtual
|
|
# output at the client's exact resolution+refresh (the flagship mode, and the default);
|
|
# `portal` captures an existing monitor.
|
|
#PUNKTFUNK_VIDEO_SOURCE=virtual
|
|
|
|
# GPU zero-copy capture (dmabuf → CUDA → NVENC / VAAPI / Vulkan) is ON by default and falls back to
|
|
# CPU automatically. No need to set it. Set to 0 only to force the CPU path.
|
|
# PUNKTFUNK_ZEROCOPY=0
|
|
|
|
# The name this host shows up under in Moonlight and the Punktfunk clients. Defaults to the
|
|
# machine's hostname; set it to give the box a friendly name without renaming the machine.
|
|
#PUNKTFUNK_HOST_NAME=Living Room
|
|
|
|
# --- Session anchors (rarely needed) -------------------------------------------------------
|
|
# As a `systemctl --user` service the host inherits the correct XDG_RUNTIME_DIR from systemd and
|
|
# derives the bus (`unix:path=$XDG_RUNTIME_DIR/bus`) itself. Set these ONLY when running the host
|
|
# outside a user service (ssh, cron) — and with YOUR uid (`id -u`), never a copy-pasted 1000: a
|
|
# wrong uid points the host at another user's (nonexistent) PipeWire/D-Bus, and every session
|
|
# fails with errors like "pw audio connect … Creation failed".
|
|
#XDG_RUNTIME_DIR=/run/user/<uid>
|
|
#DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/<uid>/bus
|
|
|
|
# --- Steam Gaming Mode (Linux boxes with gamescope session infra: Bazzite/SteamOS/Nobara) ---
|
|
# Game Mode is auto-handled; two models decide WHERE it runs when a client streams:
|
|
# * MANAGED (the default where session infra is detected) — the host relaunches the gaming
|
|
# session HEADLESS at the CLIENT's exact mode ("game mode on the virtual screen"); physical
|
|
# displays drop out of it, and the box is restored on a debounced idle after disconnect.
|
|
# * ATTACH — the BOX owns its session: Game Mode stays on the physical screen and the host
|
|
# captures/follows it, never tearing it down. Reconnects land wherever the box is.
|
|
#PUNKTFUNK_GAMESCOPE_ATTACH=1 # pick the ATTACH model
|
|
#PUNKTFUNK_GAMESCOPE_MANAGED=1 # force MANAGED even where infra detection wouldn't pick it
|
|
#PUNKTFUNK_GAMESCOPE_SESSION=steam # host owns a gamescope-session-plus session at the client mode
|
|
#PUNKTFUNK_GAMESCOPE_NODE=auto # raw attach: discover + capture a running gamescope's node
|
|
# # (do NOT combine with SESSION)
|
|
#PUNKTFUNK_GAMESCOPE_APP=vkcube # nested command for ad-hoc bare-gamescope sessions
|
|
#PUNKTFUNK_SESSION_WATCH=0 # disable mid-stream Desktop<->Game following (on by default
|
|
# # on gamescope-infra boxes)
|
|
|
|
# --- Force a backend (CI / tests / dedicated single-session appliances ONLY) ---------------
|
|
# PINS the backend: the host stops following the live session entirely — per connect AND
|
|
# mid-stream. Fine for a dedicated headless appliance (punktfunk-kde-session.service, a pure
|
|
# gamescope box) or a CI run; wrong for any box that switches sessions.
|
|
#PUNKTFUNK_COMPOSITOR=kwin # kwin | mutter | gamescope | wlroots | hyprland
|
|
#PUNKTFUNK_INPUT_BACKEND=kwin # wlr | kwin | libei | gamescope — anything else logs "unknown
|
|
# PUNKTFUNK_INPUT_BACKEND" and auto-detects instead:
|
|
# PUNKTFUNK_COMPOSITOR above wins (except mutter), else
|
|
# XDG_CURRENT_DESKTOP — KDE -> kwin, GNOME -> libei, else wlr
|
|
#WAYLAND_DISPLAY=wayland-kde # headless-KDE appliance socket; retargeted per connect otherwise
|
|
#XDG_CURRENT_DESKTOP=KDE
|
|
|
|
# Optional overrides (apps.json is the primary mechanism for per-app settings):
|
|
#PUNKTFUNK_FEC_PCT=20 # video FEC overhead percent
|
|
#PUNKTFUNK_PERF=1 # per-stage timing logs
|
|
#PUNKTFUNK_MDNS=0 # disable the mDNS adverts (native + GameStream) — for multicast-
|
|
# dead networks/containers; clients add the host by address instead
|
|
# Display-management policy (keep-alive · topology · conflict · identity · layout · max) is set in the
|
|
# web console (Host → Virtual displays) → ~/.config/punktfunk/display-settings.json, NOT here; a
|
|
# settings file supersedes the legacy PUNKTFUNK_MONITOR_LINGER_MS / _NO_ISOLATE / *_VIRTUAL_PRIMARY
|
|
# knobs. One transport knob has no console equivalent:
|
|
#PUNKTFUNK_IDLE_TIMEOUT_MS=8000 # disconnect-detection latency: how long before a DROPPED client is
|
|
# declared gone (a kept display then starts its linger, or frees).
|
|
# Lower (e.g. 3000) to reclaim kept displays sooner after an
|
|
# ungraceful drop; clamped ≥1s, keep-alive ping scales with it so a
|
|
# live session never false-disconnects. A deliberate quit is instant.
|
|
# Session recovery hook: fired (debounced, ≥60 s apart) when a client connects while NO graphical
|
|
# session is live for this uid — e.g. a compositor crash dropped the box to the GDM greeter, whose
|
|
# auto-login only runs once per boot, so the box would otherwise need a walk-up login or a reboot.
|
|
# Restarting the display manager re-runs auto-login and the client's retry lands in the recovered
|
|
# desktop. Runs detached via `sh -c` as the host's user, so it needs passwordless privilege for
|
|
# exactly this action (sudoers drop-in: `youruser ALL=(root) NOPASSWD: /usr/bin/systemctl restart
|
|
# gdm`, or a polkit rule + plain `systemctl restart display-manager`). Unset = disabled.
|
|
#PUNKTFUNK_RECOVER_SESSION_CMD=sudo -n systemctl restart gdm
|
|
|
|
# Full-chroma 4:4:4 (HEVC Range Extensions) — sharper text/desktop, no chroma loss. Honored only on
|
|
# the punktfunk/1 native path when the client advertises 4:4:4 AND the GPU supports it (probed; else
|
|
# the session stays 4:2:0). HEVC-only; independent of 10-bit. NVENC (NVIDIA) is the validated path;
|
|
# VAAPI/AMF/QSV decline (4:2:0). GameStream/Moonlight always stays 4:2:0.
|
|
#PUNKTFUNK_444=1
|
|
#PUNKTFUNK_10BIT=1 # HEVC Main10 / HDR (when the client advertises 10-bit)
|
|
|
|
# Frame limiter for the GAME — how fast the compositor lets it render. Unset (or 0) = no limit,
|
|
# the default. It does NOT cap the stream: the client still negotiates and receives its full rate,
|
|
# because the encode loop re-encodes the held frame when the compositor produced no new one (an
|
|
# almost-empty P-frame). So a 60-capped game on a 120 Hz session still sends 120 frames a second,
|
|
# and the GPU time the game gives up goes to capture and encode instead — and to heat and battery
|
|
# on a laptop or handheld.
|
|
# gamescope only, today: it takes this as --nested-refresh, the rate it clamps the game to. That is
|
|
# the nested output's rate, so everything gamescope composites moves at it. Other compositors have
|
|
# no equivalent lever and ignore it.
|
|
#PUNKTFUNK_MAX_FPS=60
|
|
|
|
# Run the virtual display at a MULTIPLE of the session's frame rate, without sending a single
|
|
# extra frame. A compositor paints on its own vblank, so a frame finished just after the capture
|
|
# sampled waits nearly a whole interval to be picked up — the jittery part of the latency budget.
|
|
# At 2 that worst case halves. Costs the compositor and GPU the extra composites, so it's opt-in;
|
|
# 1 (default) is off, 4 is the ceiling. If the backend won't give the multiplied rate it reports
|
|
# what it achieved and the stream paces to that, exactly as it would for any other refusal.
|
|
#PUNKTFUNK_VDISPLAY_HZ_MULT=2
|
|
#RUST_LOG=info
|
|
# Management API bearer token. The mgmt API is HTTPS + token-authenticated ALWAYS (even on
|
|
# loopback); if unset it is auto-generated + persisted to ~/.config/punktfunk/mgmt-token (which the
|
|
# bundled web console sources). Set here only to pin a specific token.
|
|
#PUNKTFUNK_MGMT_TOKEN=
|