feat(host): GameStream/Moonlight compat is now opt-in (--gamestream) — secure native-only by default
apple / swift (push) Successful in 55s
windows-host / package (push) Successful in 2m31s
android / android (push) Successful in 4m40s
ci / rust (push) Successful in 4m43s
ci / web (push) Successful in 30s
ci / docs-site (push) Successful in 34s
deb / build-publish (push) Successful in 2m9s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 14s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 21s
ci / bench (push) Successful in 4m44s
docker / deploy-docs (push) Successful in 19s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m6s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m19s
apple / swift (push) Successful in 55s
windows-host / package (push) Successful in 2m31s
android / android (push) Successful in 4m40s
ci / rust (push) Successful in 4m43s
ci / web (push) Successful in 30s
ci / docs-site (push) Successful in 34s
deb / build-publish (push) Successful in 2m9s
decky / build-publish (push) Successful in 11s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 5s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 14s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 4s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 4s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 21s
ci / bench (push) Successful in 4m44s
docker / deploy-docs (push) Successful in 19s
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Successful in 8m6s
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Successful in 8m19s
Follows the security audit (#5/#9): the GameStream-compat plane carries inherent on-path weaknesses that can't be fixed on the wire without breaking stock Moonlight — its pairing runs over plain HTTP (#9, MITM-able during the pairing window) and its legacy control encryption can reuse GCM nonces (#5, a passive eavesdropper can recover/forge input). The native punktfunk/1 plane (SPAKE2 PIN pairing + per-direction AEAD nonces) has neither. So flip the default to secure-by-default: - `serve` → native punktfunk/1 plane + management API ONLY (no GameStream surface). - `serve --gamestream` → ALSO the GameStream/Moonlight-compat planes (nvhttp pairing, RTSP, ENet control, _nvstream mDNS). Opt-in, logged with a trusted-LAN caveat. `--moonlight` is an alias. - The native plane is now ALWAYS on in `serve` (`--native` is a kept-for-compat no-op); the unified GameStream+native host is `serve --gamestream`. `gamestream::serve` gates the GameStream spawns (nvhttp/rtsp/control/mdns) on the flag; the native plane + mgmt + native-pairing handle always run. To avoid silently regressing validated Moonlight deployments, the explicit deployment configs PRESERVE Moonlight via `--gamestream` (each documents dropping it for a secure native-only host): the Linux systemd unit, the Steam Deck installer, and the Windows service default (DEFAULT_HOST_CMD). The bare `serve` default (new/manual use) is secure. Docs swept to match (host-cli, moonlight, quickstart, install, packaging READMEs, CLAUDE.md, README, …): Moonlight setup now instructs `--gamestream`; native/console refs use bare `serve`. OpenAPI regenerated (a stale "run `serve --native`" string). fmt + clippy clean; 94 host tests green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,7 +47,7 @@ Low-latency desktop/game streaming stack, Linux-first, with a shared Rust protoc
|
||||
(no re-TOFU shortcut). Clients present persistent identities via QUIC client auth, the host stores
|
||||
paired fingerprints (`punktfunk1-paired.json`) and gates sessions with `--require-pairing` (the
|
||||
default; `--allow-tofu`/`--open` accept unpaired clients).
|
||||
**LAN auto-discovery**: both `serve --native` and `punktfunk1-host` advertise the native service over
|
||||
**LAN auto-discovery**: both `serve` and `punktfunk1-host` advertise the native service over
|
||||
mDNS (`_punktfunk._udp`, `crate::discovery`) with TXT `proto`/`fp`(cert fingerprint to
|
||||
pin)/`pair`(required|optional)/`id`; `punktfunk-probe --discover` lists hosts, Apple clients
|
||||
browse the same service via NWBrowser (validated cross-LAN 2026-06-12).
|
||||
@@ -111,7 +111,7 @@ Low-latency desktop/game streaming stack, Linux-first, with a shared Rust protoc
|
||||
slice threads) → `GtkGraphicsOffload`-wrapped picture, PipeWire playback (mic-player
|
||||
jitter ring inverted), SDL3 gamepad capture + rumble/lightbar feedback, keyboard via
|
||||
exact inverse of the host VK table, absolute mouse + 120-unit scroll. Validated live
|
||||
against `serve --native` on this box: 1080p60, steady 60 fps, capture→decoded p50
|
||||
against `serve` on this box: 1080p60, steady 60 fps, capture→decoded p50
|
||||
≈6.4 ms (debug build). `--connect host[:port]` for scripting. **Swift-parity batch +
|
||||
stage 1.5 (2026-06-12 evening)**: capture state machine (click-to-capture,
|
||||
Ctrl+Alt+Shift+Q / focus-loss release, held-state flush), app-lifetime SDL gamepad
|
||||
@@ -176,8 +176,9 @@ Low-latency desktop/game streaming stack, Linux-first, with a shared Rust protoc
|
||||
2. **Sub-frame pipelining**: overlap encode and transmit within a frame. Requires a direct
|
||||
NVENC SDK wrapper (libavcodec only emits whole AUs) — the next big latency lever (~2–4 ms
|
||||
at high res).
|
||||
3. **punktfunk/1 protocol growth.** **Done:** unified host (`serve --native` runs GameStream + the
|
||||
punktfunk/1 QUIC host in one process) with native pairing driven over the mgmt API /
|
||||
3. **punktfunk/1 protocol growth.** **Done:** unified host (`serve --gamestream` runs GameStream + the
|
||||
punktfunk/1 QUIC host in one process; bare `serve` is the secure native-only default — GameStream is
|
||||
opt-in, trusted-LAN only, security-review #5/#9) with native pairing driven over the mgmt API /
|
||||
web console (`mod native_pairing`: arm-on-demand → display PIN, paired-device list).
|
||||
**Done:** PIN pairing is the default, host-gated — the host requires pairing and advertises
|
||||
`pair=required` unless opted out with `--allow-tofu`/`--open` (then `pair=optional`, accepts
|
||||
@@ -280,9 +281,9 @@ scanout → KWin `--drm` impossible; everything renders offscreen via `renderD12
|
||||
# launcher menu is EMPTY (no apps, no System Settings).
|
||||
bash scripts/headless/run-headless-kde.sh 1920x1080
|
||||
|
||||
# host (shell 2):
|
||||
# host (shell 2): bare `serve` is native-only (secure default); add --gamestream for Moonlight compat.
|
||||
WAYLAND_DISPLAY=wayland-kde XDG_CURRENT_DESKTOP=KDE PUNKTFUNK_VIDEO_SOURCE=virtual \
|
||||
PUNKTFUNK_ZEROCOPY=1 cargo run -rp punktfunk-host -- serve
|
||||
PUNKTFUNK_ZEROCOPY=1 cargo run -rp punktfunk-host -- serve --gamestream
|
||||
|
||||
# punktfunk/1 native loopback test (no Moonlight needed; same env as serve, listener persists
|
||||
# across sessions — bound it with --max-sessions):
|
||||
|
||||
@@ -49,8 +49,10 @@ gamescope, Mutter, and Sway/wlroots backends), encoded with GPU **zero-copy** (d
|
||||
NVENC) up to 5120×1440@240. The native **`punktfunk/1`** protocol adds a QUIC control plane and a
|
||||
GF(2¹⁶) Leopard-FEC + AES-GCM data plane (p50 ~0.8 ms capture→reassembled at 720p120), with
|
||||
mid-stream mode renegotiation and a wall-clock skew handshake so latency stays valid across machines.
|
||||
Both protocols run from **one process** (`punktfunk-host serve --native`) and are managed through a
|
||||
REST API and web console. Builds against FFmpeg 7 or 8.
|
||||
Both run from **one process**: bare `punktfunk-host serve` is the **secure native-only default**
|
||||
(`punktfunk/1` + the management API/web console), and `serve --gamestream` additionally enables the
|
||||
GameStream/Moonlight-compat planes (opt-in, trusted-LAN only — GameStream has inherent on-path
|
||||
weaknesses). The host is managed through a REST API and web console. Builds against FFmpeg 7 or 8.
|
||||
|
||||
Full milestone status: **[docs.punktfunk.unom.io/docs/status](https://docs.punktfunk.unom.io/docs/status)** ·
|
||||
roadmap: **[/docs/roadmap](https://docs.punktfunk.unom.io/docs/roadmap)**.
|
||||
@@ -69,7 +71,8 @@ Windows host (NVIDIA-only) also ships as a signed installer.
|
||||
| **Windows** (NVIDIA, x64) | signed `setup.exe` from the package registry | [Windows Host](https://docs.punktfunk.unom.io/docs/windows-host) |
|
||||
|
||||
`punktfunk-host` is the streaming host; `punktfunk-web` is the browser console (pairing + status).
|
||||
After install, run `punktfunk-host serve --native` inside your desktop session, then pair from the web
|
||||
After install, run `punktfunk-host serve` inside your desktop session (the secure native default;
|
||||
add `--gamestream` on a trusted LAN if you also want stock Moonlight clients), then pair from the web
|
||||
console. Full instructions: **[docs.punktfunk.unom.io/docs/install](https://docs.punktfunk.unom.io/docs/install)**.
|
||||
|
||||
## Connect a client
|
||||
|
||||
@@ -154,56 +154,72 @@ impl AppState {
|
||||
/// QUIC server on `cfg.port` in the same process, sharing one [`crate::native_pairing`] handle with
|
||||
/// the management API so the web console can arm pairing and show the PIN. `None` = GameStream only
|
||||
/// (the mgmt API's native endpoints report `enabled: false`).
|
||||
/// Run the host. The **native punktfunk/1 plane + management API always run** (the secure default —
|
||||
/// SPAKE2 pairing, per-direction AEAD nonces); `gamestream` additionally brings up the
|
||||
/// GameStream/Moonlight-compat planes (nvhttp pairing, RTSP, ENet control, `_nvstream` mDNS), which
|
||||
/// carry inherent on-path weaknesses (plain-HTTP pairing + legacy GCM nonce reuse, security-review
|
||||
/// #5/#9) — so it is **opt-in** (`serve --gamestream`) and gated on a trusted LAN.
|
||||
pub fn serve(
|
||||
mgmt: crate::mgmt::Options,
|
||||
native: Option<crate::punktfunk1::NativeServe>,
|
||||
native: crate::punktfunk1::NativeServe,
|
||||
gamestream: bool,
|
||||
) -> Result<()> {
|
||||
let host = Host::detect()?;
|
||||
let identity = cert::ServerIdentity::load_or_create().context("host certificate")?;
|
||||
let state = Arc::new(AppState::new(host, identity));
|
||||
// The shared native-pairing handle exists only when we run the native host; it links the QUIC
|
||||
// ceremony and the management API.
|
||||
let np = match &native {
|
||||
Some(_) => Some(Arc::new(
|
||||
crate::native_pairing::NativePairing::load_with(None, None, false)
|
||||
.context("native pairing store")?,
|
||||
)),
|
||||
None => None,
|
||||
};
|
||||
// The native plane always runs, so the shared native-pairing handle (linking the QUIC ceremony
|
||||
// and the management API) always exists.
|
||||
let np = Arc::new(
|
||||
crate::native_pairing::NativePairing::load_with(None, None, false)
|
||||
.context("native pairing store")?,
|
||||
);
|
||||
tracing::info!(
|
||||
hostname = %state.host.hostname,
|
||||
uniqueid = %state.host.uniqueid,
|
||||
ip = %state.host.local_ip,
|
||||
native = native.is_some(),
|
||||
require_pairing = native.as_ref().map(|n| n.require_pairing),
|
||||
"punktfunk host (GameStream P1.1: serverinfo + pairing + mDNS)"
|
||||
native_port = native.port,
|
||||
require_pairing = native.require_pairing,
|
||||
gamestream,
|
||||
"punktfunk host"
|
||||
);
|
||||
if gamestream {
|
||||
tracing::warn!(
|
||||
"GameStream/Moonlight compat ENABLED (--gamestream): its pairing runs over plain HTTP and \
|
||||
its legacy control encryption can reuse GCM nonces (security-review #5/#9) — an on-path \
|
||||
LAN attacker could MITM pairing or recover input. Enable only on a TRUSTED network; prefer \
|
||||
the native punktfunk/1 plane + clients for untrusted/WAN use."
|
||||
);
|
||||
}
|
||||
let rt = tokio::runtime::Runtime::new().context("build tokio runtime")?;
|
||||
rt.block_on(async move {
|
||||
// rustls needs a process-wide crypto provider before any TLS config is built.
|
||||
let _ = rustls::crypto::aws_lc_rs::default_provider().install_default();
|
||||
let _advert = mdns::advertise(&state.host).context("mDNS advertise")?;
|
||||
rtsp::spawn(state.clone()).context("start RTSP server")?;
|
||||
control::spawn(state.clone()).context("start ENet control server")?;
|
||||
match (native, np) {
|
||||
(Some(cfg), Some(np)) => {
|
||||
tracing::info!(
|
||||
port = cfg.port,
|
||||
require_pairing = cfg.require_pairing,
|
||||
"unified host: also serving native punktfunk/1 (QUIC)"
|
||||
);
|
||||
tokio::try_join!(
|
||||
nvhttp::run(state.clone()),
|
||||
crate::mgmt::run(state.clone(), mgmt, Some(np.clone())),
|
||||
crate::punktfunk1::serve(crate::punktfunk1::native_serve_opts(&cfg), np),
|
||||
)?;
|
||||
}
|
||||
_ => {
|
||||
tokio::try_join!(
|
||||
nvhttp::run(state.clone()),
|
||||
crate::mgmt::run(state, mgmt, None)
|
||||
)?;
|
||||
}
|
||||
let native_opts = crate::punktfunk1::native_serve_opts(&native);
|
||||
if gamestream {
|
||||
// Unified host: GameStream compat planes + native + mgmt.
|
||||
let _advert = mdns::advertise(&state.host).context("mDNS advertise")?;
|
||||
rtsp::spawn(state.clone()).context("start RTSP server")?;
|
||||
control::spawn(state.clone()).context("start ENet control server")?;
|
||||
tracing::info!(
|
||||
port = native.port,
|
||||
"unified host: GameStream/Moonlight compat + native punktfunk/1 (QUIC)"
|
||||
);
|
||||
tokio::try_join!(
|
||||
nvhttp::run(state.clone()),
|
||||
crate::mgmt::run(state.clone(), mgmt, Some(np.clone())),
|
||||
crate::punktfunk1::serve(native_opts, np),
|
||||
)?;
|
||||
} else {
|
||||
// Secure default: native punktfunk/1 + management API only (no GameStream surface).
|
||||
tracing::info!(
|
||||
port = native.port,
|
||||
"secure host: native punktfunk/1 (QUIC) + management API \
|
||||
(GameStream OFF — pass --gamestream for stock-Moonlight compat)"
|
||||
);
|
||||
tokio::try_join!(
|
||||
crate::mgmt::run(state.clone(), mgmt, Some(np.clone())),
|
||||
crate::punktfunk1::serve(native_opts, np),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
//! `#[cfg(target_os = "linux")]`; the crate compiles everywhere so the workspace builds
|
||||
//! on non-Linux dev machines — it just can't run the pipeline there.
|
||||
//!
|
||||
//! Subcommands: `serve` runs the GameStream-compatible host + management REST API (and, with
|
||||
//! `--native`, the native punktfunk/1 host in-process); `punktfunk1-host` runs the native
|
||||
//! punktfunk/1 host standalone; `spike` is a capture→encode→file pipeline dev tool that also
|
||||
//! round-trips the encoded AUs through a `punktfunk_core` loopback.
|
||||
//! Subcommands: `serve` runs the native punktfunk/1 host + management REST API by default, and —
|
||||
//! with `--gamestream` — the GameStream/Moonlight-compat planes too (opt-in, trusted-LAN only);
|
||||
//! `punktfunk1-host` runs the native punktfunk/1 host standalone; `spike` is a capture→encode→file
|
||||
//! pipeline dev tool that also round-trips the encoded AUs through a `punktfunk_core` loopback.
|
||||
|
||||
// Scaffold: trait methods and config paths are defined ahead of their backends.
|
||||
#![allow(dead_code)]
|
||||
@@ -103,11 +103,11 @@ fn real_main() -> Result<()> {
|
||||
crate::capture::dxgi::install_gpu_pref_hook();
|
||||
|
||||
match args.first().map(String::as_str) {
|
||||
// GameStream host control plane (P1.1: mDNS + serverinfo) + management API, and (with
|
||||
// --native) the native punktfunk/1 host in the same process — the unified host.
|
||||
// The host: the native punktfunk/1 plane + management API by default (secure), and — with
|
||||
// --gamestream — the GameStream/Moonlight-compat planes too (opt-in; #5/#9 trusted-LAN caveat).
|
||||
Some("serve") => {
|
||||
let (mgmt_opts, native) = parse_serve(&args[1..])?;
|
||||
gamestream::serve(mgmt_opts, native)
|
||||
let (mgmt_opts, native, gamestream) = parse_serve(&args[1..])?;
|
||||
gamestream::serve(mgmt_opts, native, gamestream)
|
||||
}
|
||||
// Print the management API's OpenAPI document (for client codegen).
|
||||
Some("openapi") => {
|
||||
@@ -332,14 +332,16 @@ fn input_test() -> Result<()> {
|
||||
bail!("input-test requires Linux")
|
||||
}
|
||||
|
||||
/// `serve` options: the management API (GameStream ports are protocol-fixed) + whether to also run
|
||||
/// the native punktfunk/1 host in-process (`--native`, the unified host). Returns the mgmt options
|
||||
/// and the native host config (`None` = GameStream only). Native pairing is **required by default**
|
||||
/// `serve` options. The **native punktfunk/1 plane + management API are the secure default and always
|
||||
/// run**; `--gamestream` additionally enables the GameStream/Moonlight-compat planes (opt-in — they
|
||||
/// carry the inherent on-path #5/#9 weaknesses, so only on a trusted LAN). Returns the mgmt options,
|
||||
/// the native host config, and whether GameStream is enabled. Native pairing is **required by default**
|
||||
/// (an open host any LAN device can stream from is insecure); `--open` turns it off.
|
||||
fn parse_serve(args: &[String]) -> Result<(mgmt::Options, Option<punktfunk1::NativeServe>)> {
|
||||
fn parse_serve(args: &[String]) -> Result<(mgmt::Options, punktfunk1::NativeServe, bool)> {
|
||||
let mut opts = mgmt::Options::default();
|
||||
let mut native_port: Option<u16> = None;
|
||||
let mut native_port: u16 = 9777; // the native plane always runs now
|
||||
let mut open = false;
|
||||
let mut gamestream = false;
|
||||
let mut i = 0;
|
||||
while i < args.len() {
|
||||
let arg = args[i].as_str();
|
||||
@@ -365,16 +367,17 @@ fn parse_serve(args: &[String]) -> Result<(mgmt::Options, Option<punktfunk1::Nat
|
||||
}
|
||||
opts.token = Some(token);
|
||||
}
|
||||
// Also run the native punktfunk/1 (QUIC) host in this process — the unified host.
|
||||
// Pairing is then armed on demand from the management API / web console.
|
||||
"--native" => native_port = Some(native_port.unwrap_or(9777)),
|
||||
// The native plane is now the DEFAULT (always runs in `serve`); `--native` is kept as an
|
||||
// accepted no-op for back-compat / explicitness.
|
||||
"--native" => {}
|
||||
"--native-port" => {
|
||||
native_port = Some(
|
||||
next()?
|
||||
.parse()
|
||||
.map_err(|_| anyhow::anyhow!("bad --native-port (want a port number)"))?,
|
||||
)
|
||||
native_port = next()?
|
||||
.parse()
|
||||
.map_err(|_| anyhow::anyhow!("bad --native-port (want a port number)"))?
|
||||
}
|
||||
// Opt into the GameStream/Moonlight-compat planes (off by default — they carry the
|
||||
// inherent on-path #5/#9 weaknesses; only for a trusted LAN).
|
||||
"--gamestream" | "--moonlight" => gamestream = true,
|
||||
// Disable mandatory native pairing — any device can connect (trusted single-user
|
||||
// setups only). The default REQUIRES pairing.
|
||||
"--open" => open = true,
|
||||
@@ -393,11 +396,11 @@ fn parse_serve(args: &[String]) -> Result<(mgmt::Options, Option<punktfunk1::Nat
|
||||
if opts.token.is_none() {
|
||||
opts.token = Some(crate::mgmt_token::load_or_generate()?);
|
||||
}
|
||||
let native = native_port.map(|port| punktfunk1::NativeServe {
|
||||
port,
|
||||
let native = punktfunk1::NativeServe {
|
||||
port: native_port,
|
||||
require_pairing: !open,
|
||||
});
|
||||
Ok((opts, native))
|
||||
};
|
||||
Ok((opts, native, gamestream))
|
||||
}
|
||||
|
||||
fn parse_spike(args: &[String]) -> Result<Options> {
|
||||
@@ -502,8 +505,8 @@ fn print_usage() {
|
||||
"punktfunk-host — Linux streaming host
|
||||
|
||||
USAGE:
|
||||
punktfunk-host serve [OPTIONS] GameStream host control plane (mDNS + serverinfo …)
|
||||
+ the management REST API
|
||||
punktfunk-host serve [OPTIONS] native punktfunk/1 host + management REST API
|
||||
(secure default; add --gamestream for Moonlight compat)
|
||||
punktfunk-host openapi print the management API's OpenAPI document (codegen)
|
||||
punktfunk-host punktfunk1-host [OPTIONS] native punktfunk/1 host (QUIC control + UDP data plane)
|
||||
punktfunk-host probe-compositor exit 0 iff the compositor is up + ready (bringup gate)
|
||||
@@ -513,9 +516,12 @@ SERVE OPTIONS:
|
||||
--mgmt-bind <IP:PORT> management API address (default: 127.0.0.1:47990)
|
||||
--mgmt-token <TOKEN> bearer token for the management API (or PUNKTFUNK_MGMT_TOKEN);
|
||||
required when --mgmt-bind is not loopback
|
||||
--native also run the native punktfunk/1 (QUIC) host in this process —
|
||||
the unified host; pairing is armed from the management API/console
|
||||
--native-port <PORT> native QUIC port (default 9777; implies --native)
|
||||
--gamestream (--moonlight) ALSO run the GameStream/Moonlight-compat planes (nvhttp pairing,
|
||||
RTSP, ENet control, _nvstream mDNS). OFF by default — they carry
|
||||
inherent on-path weaknesses (plain-HTTP pairing + legacy GCM nonce
|
||||
reuse, security-review #5/#9); enable only on a TRUSTED LAN
|
||||
--native no-op (the native punktfunk/1 plane always runs in `serve` now)
|
||||
--native-port <PORT> native QUIC port (default 9777)
|
||||
--open disable mandatory native pairing (default: pairing REQUIRED —
|
||||
an open host any LAN device can stream from is insecure)
|
||||
|
||||
@@ -550,7 +556,7 @@ NOTES:
|
||||
(see docs/linux-setup.md). 'synthetic' needs no capture session and always runs.
|
||||
Encoded AUs are written to a playable file AND (unless --no-loopback) fed through a
|
||||
punktfunk_core host→client loopback that reassembles and byte-verifies each one.
|
||||
Both 'serve --native' and 'punktfunk1-host' advertise the native service over mDNS
|
||||
Both 'serve' and 'punktfunk1-host' advertise the native service over mDNS
|
||||
(_punktfunk._udp) for client auto-discovery — 'punktfunk-probe --discover' lists them."
|
||||
);
|
||||
#[cfg(target_os = "windows")]
|
||||
|
||||
@@ -850,7 +850,7 @@ async fn get_native_pairing(State(st): State<Arc<MgmtState>>) -> Json<NativePair
|
||||
request_body = ArmNativePairing,
|
||||
responses(
|
||||
(status = OK, description = "Pairing armed; the response carries the PIN to display", body = NativePairStatus),
|
||||
(status = SERVICE_UNAVAILABLE, description = "Native host not enabled (run `serve --native`)", body = ApiError),
|
||||
(status = SERVICE_UNAVAILABLE, description = "Native host not available in this process", body = ApiError),
|
||||
(status = UNAUTHORIZED, description = "Missing or invalid bearer token", body = ApiError),
|
||||
)
|
||||
)]
|
||||
@@ -861,7 +861,7 @@ async fn arm_native_pairing(
|
||||
let Some(np) = &st.native else {
|
||||
return api_error(
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
"native host not enabled (run `serve --native`)",
|
||||
"native host not available in this process",
|
||||
);
|
||||
};
|
||||
let ttl = req.ttl_secs.unwrap_or(120).clamp(15, 600);
|
||||
|
||||
@@ -58,9 +58,11 @@ const SERVICE_DESCRIPTION: &str =
|
||||
"Low-latency desktop/game streaming host. Launches the punktfunk host into the active session.";
|
||||
|
||||
/// The host subcommand the service launches, overridable via `PUNKTFUNK_HOST_CMD` in host.env.
|
||||
/// `serve --native` runs the GameStream (Moonlight) host + the native punktfunk/1 QUIC host in one
|
||||
/// process — the unified host an end user wants.
|
||||
const DEFAULT_HOST_CMD: &str = "serve --native";
|
||||
/// `serve --gamestream` runs the native punktfunk/1 QUIC host (always on) PLUS the GameStream
|
||||
/// (Moonlight) compat planes — the unified host a Windows end user typically wants (Moonlight is the
|
||||
/// common Windows client). Drop `--gamestream` for a secure native-only host (no plain-HTTP pairing /
|
||||
/// legacy GCM nonce reuse — security-review #5/#9; native clients only).
|
||||
const DEFAULT_HOST_CMD: &str = "serve --gamestream";
|
||||
|
||||
/// Event handles shared between the SCM control handler (which signals them) and the supervision loop
|
||||
/// (which waits on them). Stored as raw `isize` so the `'static + Send` handler can reach them without
|
||||
@@ -619,8 +621,9 @@ fn ensure_default_host_env() -> Result<()> {
|
||||
PUNKTFUNK_SECURE_DDA=1\n\
|
||||
RUST_LOG=info\n\
|
||||
\n\
|
||||
# The host subcommand the service launches (default: serve --native).\n\
|
||||
# PUNKTFUNK_HOST_CMD=serve --native\n\
|
||||
# The host subcommand the service launches (default: serve --gamestream = native + Moonlight\n\
|
||||
# compat). Use `serve` for a SECURE native-only host (no GameStream #5/#9 surface).\n\
|
||||
# PUNKTFUNK_HOST_CMD=serve --gamestream\n\
|
||||
\n\
|
||||
# Force a specific NVENC render GPU by name substring (multi-GPU boxes only):\n\
|
||||
# PUNKTFUNK_RENDER_ADAPTER=4090\n";
|
||||
|
||||
@@ -43,12 +43,12 @@ The client requests a bitrate; the host encodes to it. To find a good value for
|
||||
|
||||
## Multiple devices at once
|
||||
|
||||
Today the native `punktfunk/1` host (`serve --native`) streams **one session at a time** — additional
|
||||
Today the native `punktfunk/1` host (`serve`) streams **one session at a time** — additional
|
||||
clients wait in the accept queue until the active session ends. Each session gets its own virtual
|
||||
display at the client's exact resolution; concurrent native sessions are on the roadmap.
|
||||
|
||||
(`punktfunk1-host`, the standalone test host, has a `--max-concurrent N` knob, default 4, bounded by your
|
||||
GPU's encoder — see the [Host CLI](/docs/host-cli) reference — but `serve --native` does **not** take
|
||||
GPU's encoder — see the [Host CLI](/docs/host-cli) reference — but `serve` does **not** take
|
||||
that flag.)
|
||||
|
||||
## Codec and FEC
|
||||
|
||||
@@ -116,7 +116,7 @@ mDNS. It requires **PIN pairing** by default (secure on a LAN); pair once from y
|
||||
From any [client](/docs/clients) — `punktfunk-client --discover` finds the host on the LAN. On
|
||||
first connect, complete the PIN pairing — **arm it from the host's web console / mgmt API**, which
|
||||
makes the host display a 4-digit PIN to type into the client. (Pairing is required by default; pass
|
||||
`serve --native --open` only if you deliberately want to disable the requirement.) See
|
||||
`serve --open` only if you deliberately want to disable the requirement.) See
|
||||
[Clients](/docs/clients) and [Running as a Service](/docs/running-as-a-service).
|
||||
|
||||
## Appendix — build from source
|
||||
|
||||
@@ -6,18 +6,30 @@ description: The punktfunk-host commands and the flags you'll actually use.
|
||||
The host is one binary, `punktfunk-host`. Most of the time you'll run a single command; the rest reads
|
||||
its settings from [`host.env`](/docs/configuration).
|
||||
|
||||
## `serve --native`
|
||||
## `serve`
|
||||
|
||||
The normal way to run a host. Starts the unified host: the GameStream server (for Moonlight) **and**
|
||||
the native `punktfunk/1` server, plus the management API/web console — all in one process.
|
||||
The normal way to run a host. By default `serve` starts the **secure native host**: the native
|
||||
`punktfunk/1` server (QUIC, SPAKE2 PIN pairing, per-direction AEAD) plus the management API/web
|
||||
console — all in one process. The native plane is **always on**; there is no flag to turn it off.
|
||||
|
||||
```sh
|
||||
punktfunk-host serve --native
|
||||
punktfunk-host serve
|
||||
```
|
||||
|
||||
Add `--gamestream` (alias `--moonlight`) to **also** run the GameStream/Moonlight-compatible planes
|
||||
(nvhttp pairing, RTSP, ENet control, `_nvstream` mDNS) — required for stock [Moonlight](/docs/moonlight)
|
||||
clients. This is **opt-in** because GameStream carries inherent on-path weaknesses (pairing over plain
|
||||
HTTP; its legacy control encryption can reuse GCM nonces — security-review #5/#9), so enable it **only
|
||||
on a trusted LAN**. The native plane is immune to those issues.
|
||||
|
||||
```sh
|
||||
punktfunk-host serve --gamestream
|
||||
```
|
||||
|
||||
| Flag | Meaning |
|
||||
|---|---|
|
||||
| `--native` | Also run the native `punktfunk/1` server (recommended; enables the native clients and discovery). |
|
||||
| `--gamestream` / `--moonlight` | Also run the GameStream/Moonlight-compat planes (for stock Moonlight clients). Opt-in, trusted-LAN only — see above. |
|
||||
| `--native` | No-op. The native `punktfunk/1` server always runs in `serve`; kept only for backward compatibility. |
|
||||
| `--native-port <PORT>` | Native QUIC port (default `9777`). |
|
||||
| `--open` | Don't require pairing — serve any device on the network. Off by default; only for trusted single-user setups. |
|
||||
| `--mgmt-bind <IP:PORT>` | Management API address (default loopback `127.0.0.1:47990`). |
|
||||
@@ -29,7 +41,7 @@ The management API is **always HTTPS with bearer-token auth**. If you don't pass
|
||||
is auto-generated and persisted to `~/.config/punktfunk/mgmt-token`; `--mgmt-token` only overrides it. A
|
||||
token is **required** when you bind the API off loopback with `--mgmt-bind`.
|
||||
|
||||
By default the host **requires pairing** — see [Pairing & Trust](/docs/pairing). On `serve --native` you
|
||||
By default the host **requires pairing** — see [Pairing & Trust](/docs/pairing). On `serve` you
|
||||
**arm pairing from the web console** (or mgmt API); the host then displays a 4-digit PIN. Pass `--open` to
|
||||
turn off the mandatory-pairing default and serve any device on the network (trusted single-user setups
|
||||
only). The pairing flags below are `punktfunk1-host`-only and do **not** apply to `serve`.
|
||||
@@ -54,10 +66,10 @@ punktfunk-host punktfunk1-host --source virtual
|
||||
| `--require-pairing` | Only serve paired devices (implies `--allow-pairing`). |
|
||||
|
||||
`--max-concurrent`, `--allow-pairing`, and `--require-pairing` are **`punktfunk1-host`-only** — `serve` does not
|
||||
accept them. On `serve --native` you arm pairing from the web console instead, and concurrency is not
|
||||
accept them. On `serve` you arm pairing from the web console instead, and concurrency is not
|
||||
yet capped from the command line.
|
||||
|
||||
Both `serve --native` and `punktfunk1-host` advertise the host on the network so clients can discover it. List
|
||||
Both `serve` and `punktfunk1-host` advertise the host on the network so clients can discover it. List
|
||||
hosts from another machine with `punktfunk-probe --discover`.
|
||||
|
||||
## Environment
|
||||
|
||||
@@ -15,7 +15,7 @@ On **Windows** (NVIDIA), the host ships as a signed installer instead — see [W
|
||||
| **Ubuntu / Debian** | apt | `sudo apt install punktfunk-host` | [Ubuntu — GNOME](/docs/ubuntu-gnome) · [Ubuntu — KDE](/docs/ubuntu-kde) · [packaging/debian](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/debian/README.md) |
|
||||
| **Fedora / Bazzite** | rpm-ostree | `rpm-ostree install punktfunk punktfunk-web` | [Fedora — KDE](/docs/fedora-kde) · [Bazzite](/docs/bazzite) · [packaging/rpm](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/rpm/README.md) |
|
||||
| **Arch** | PKGBUILD | `makepkg -si` | [packaging/arch](https://git.unom.io/unom/punktfunk/src/branch/main/packaging/arch/README.md) |
|
||||
| **Steam Deck (host)** | on-device script | `bash scripts/steamdeck/install.sh` | [Steam Deck (Host)](/docs/steam-deck-host) |
|
||||
| **SteamOS (host)** | on-device script | `bash scripts/steamdeck/install.sh` | [SteamOS (Host)](/docs/steamos-host) |
|
||||
|
||||
Each registry is public — no auth, you just trust the repo's signing key. Adding the repo is a
|
||||
one-time step covered in the linked guide; after that, normal `apt upgrade` / `rpm-ostree upgrade`
|
||||
@@ -62,9 +62,12 @@ You need an NVIDIA GPU + driver (the host is NVENC-only on Windows). More detail
|
||||
2. Start the host inside your desktop session:
|
||||
|
||||
```sh
|
||||
punktfunk-host serve --native
|
||||
punktfunk-host serve
|
||||
```
|
||||
|
||||
Bare `serve` is the secure native-only default (native `punktfunk/1` + the web console). On a
|
||||
trusted LAN, add `--gamestream` to also serve stock [Moonlight](/docs/moonlight) clients.
|
||||
|
||||
3. Enable the web console and read its login password, then open `http://<host-ip>:3000`:
|
||||
|
||||
```sh
|
||||
|
||||
@@ -11,10 +11,20 @@ a browser, a smart TV, or any device without a native client.
|
||||
> discovery/pairing — including **Windows** and **Android** (phone and Android TV). See
|
||||
> [Clients](/docs/clients) before reaching for Moonlight.
|
||||
|
||||
## 1. Make sure the host is running
|
||||
## 1. Make sure the host is running with GameStream enabled
|
||||
|
||||
On the host machine, `serve --native` (or your [service](/docs/running-as-a-service)) should be up.
|
||||
The host advertises itself on the network, so Moonlight usually finds it on its own.
|
||||
Moonlight needs the GameStream planes, which are **opt-in**. Run the host with `--gamestream`:
|
||||
|
||||
```sh
|
||||
punktfunk-host serve --gamestream
|
||||
```
|
||||
|
||||
(Bare `serve` is the secure native-only default and stock Moonlight clients can't connect to it; the
|
||||
native plane is always on, and `--gamestream` adds the Moonlight-compat surface.) GameStream pairs over
|
||||
plain HTTP and its legacy control encryption is weaker than the native plane's, so only enable it on a
|
||||
**trusted LAN**. If you run the host as a [service](/docs/running-as-a-service), make sure its
|
||||
`ExecStart` includes `--gamestream`. The host advertises itself on the network, so Moonlight usually
|
||||
finds it on its own.
|
||||
|
||||
## 2. Add the host in Moonlight
|
||||
|
||||
|
||||
@@ -40,13 +40,13 @@ PIN ceremony before it can stream. It's the right path for the *first* device (b
|
||||
admitted anything) or when you're at the client and the console isn't handy.
|
||||
|
||||
Pairing has to be **armed** on the host before a client can pair (so a random device can't pair
|
||||
itself). On the production host (`serve --native`), this is done from the **web console**: open the
|
||||
itself). On the production host (`serve`), this is done from the **web console**: open the
|
||||
host's management console, click to arm pairing, and the host displays a 4-digit PIN along with the
|
||||
list of paired devices. This works on a headless host over the network — there is no command-line flag
|
||||
to arm pairing on `serve`.
|
||||
|
||||
(The standalone headless test host, `punktfunk1-host`, takes `--allow-pairing`/`--require-pairing` on its
|
||||
command line instead; the production `serve --native` host arms pairing from the console.)
|
||||
command line instead; the production `serve` host arms pairing from the console.)
|
||||
|
||||
Then, on the client:
|
||||
|
||||
@@ -61,7 +61,7 @@ the right setting on a shared network: a device has to complete the PIN ceremony
|
||||
connect.
|
||||
|
||||
If you're on a fully trusted single-user network and want to skip pairing, run the host open with
|
||||
`serve --native --open` (or `punktfunk1-host --allow-tofu` for the standalone host) — it then advertises
|
||||
`serve --open` (or `punktfunk1-host --allow-tofu` for the standalone host) — it then advertises
|
||||
`pair=optional` and accepts unpaired clients. Requiring pairing is strongly recommended.
|
||||
|
||||
## Trust-on-first-use (host opt-in)
|
||||
|
||||
@@ -22,9 +22,11 @@ Each one covers the NVIDIA driver, the dependencies, and how to build and run th
|
||||
From a terminal **inside your desktop session** (so the host can reach your compositor):
|
||||
|
||||
```sh
|
||||
punktfunk-host serve --native
|
||||
punktfunk-host serve
|
||||
```
|
||||
|
||||
This is the secure native-only default — the native `punktfunk/1` plane plus the web console. To also
|
||||
serve stock Moonlight clients, add `--gamestream` (trusted-LAN only; see [Moonlight](/docs/moonlight)).
|
||||
The host starts listening and prints its identity fingerprint. It advertises itself on your local
|
||||
network, so clients can find it by name. Leave it running. (To start it automatically at boot, see
|
||||
[Running as a Service](/docs/running-as-a-service).)
|
||||
|
||||
@@ -29,9 +29,10 @@ see [Status & Progress](/docs/status).
|
||||
|
||||
## ✅ Shipped
|
||||
|
||||
- **The host, two ways.** A GameStream host any [Moonlight](/docs/moonlight) client can use, and the
|
||||
lower-latency native [`punktfunk/1`](/docs/how-it-works) protocol (QUIC control + UDP data with
|
||||
GF(2¹⁶) Leopard FEC + AES-GCM). Both run from one process.
|
||||
- **The host, two ways.** The lower-latency native [`punktfunk/1`](/docs/how-it-works) protocol (QUIC
|
||||
control + UDP data with GF(2¹⁶) Leopard FEC + AES-GCM) — the secure default — and, opt-in via
|
||||
`serve --gamestream`, a GameStream host any [Moonlight](/docs/moonlight) client can use. Both run
|
||||
from one process.
|
||||
- **Native-resolution virtual displays** on Linux across KWin, GNOME/Mutter, gamescope, and
|
||||
Sway/wlroots, with a fully zero-copy GPU path to NVENC (stable 240 fps at 5120×1440).
|
||||
- **A native Windows host** (NVIDIA, x64) — a signed installer with secure-desktop capture and a
|
||||
|
||||
@@ -3,9 +3,14 @@ title: Running as a Service
|
||||
description: Start the host at boot — for a desktop you log into, or a fully headless always-on machine.
|
||||
---
|
||||
|
||||
Running `serve --native` in a terminal is fine for trying punktfunk out. To make a machine an
|
||||
Running `serve` in a terminal is fine for trying punktfunk out. To make a machine an
|
||||
always-available host, run it as a service. There are two cases.
|
||||
|
||||
> The bundled unit `scripts/punktfunk-host.service` runs `serve --gamestream`, so it serves both the
|
||||
> native `punktfunk/1` plane and stock [Moonlight](/docs/moonlight) clients. For a **secure native-only
|
||||
> host** (no GameStream — its pairing runs over plain HTTP and its legacy encryption is weaker;
|
||||
> security-review #5/#9), drop `--gamestream` from the unit's `ExecStart` and use bare `serve`.
|
||||
|
||||
## A. A desktop you log into
|
||||
|
||||
If you sit at the machine (or it auto-logs-in to a desktop), run the host as a **systemd user
|
||||
|
||||
@@ -70,7 +70,7 @@ Then log out and back in. On other distros this is `sudo usermod -aG input $USER
|
||||
manually.
|
||||
- Prefer a **wired** connection or 5 GHz Wi-Fi between host and client.
|
||||
- Streaming to **many devices at once** shares the GPU encoder. The production host
|
||||
(`serve --native`) handles one native session at a time, with extra clients queued; heavy load is
|
||||
(`serve`) handles one native session at a time, with extra clients queued; heavy load is
|
||||
usually bitrate-bound, so lower the bitrate first.
|
||||
|
||||
## Still stuck?
|
||||
|
||||
@@ -148,5 +148,8 @@ The host binary lands at `target/release/punktfunk-host`. Write `~/.config/punkt
|
||||
step 3, then run it inside your GNOME session:
|
||||
|
||||
```sh
|
||||
cargo run --release -p punktfunk-host -- serve --native
|
||||
cargo run --release -p punktfunk-host -- serve --gamestream
|
||||
```
|
||||
|
||||
(The native plane is always on; `--gamestream` adds the Moonlight-compat surface this guide's
|
||||
GameStream ports refer to — trusted LAN only. Drop it for a secure native-only host.)
|
||||
|
||||
@@ -104,5 +104,8 @@ cargo build --release -p punktfunk-host
|
||||
Write `~/.config/punktfunk/host.env` as in step 3, then run it inside your Plasma session:
|
||||
|
||||
```sh
|
||||
cargo run --release -p punktfunk-host -- serve --native
|
||||
cargo run --release -p punktfunk-host -- serve --gamestream
|
||||
```
|
||||
|
||||
(The native plane is always on; `--gamestream` adds the Moonlight-compat surface this guide's
|
||||
GameStream ports refer to — trusted LAN only. Drop it for a secure native-only host.)
|
||||
|
||||
@@ -621,7 +621,7 @@
|
||||
}
|
||||
},
|
||||
"503": {
|
||||
"description": "Native host not enabled (run `serve --native`)",
|
||||
"description": "Native host not available in this process",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
|
||||
@@ -45,7 +45,7 @@ settings, in-app SPAKE2 PIN pairing) + the video on a **`SwapChainPanel`**, all
|
||||
|
||||
## What we're building
|
||||
|
||||
A native Windows client that connects to a punktfunk/1 host (`serve --native` / `punktfunk1-host`), decodes
|
||||
A native Windows client that connects to a punktfunk/1 host (`serve` / `punktfunk1-host`), decodes
|
||||
HEVC, presents it low-latency, plays Opus audio, and captures local mouse/keyboard/gamepad to send
|
||||
back — i.e. the Windows analogue of the **GTK4 Linux client** (`clients/linux`),
|
||||
which is the architectural template. The Windows client is close to a 1:1 port of the Linux client
|
||||
@@ -149,7 +149,7 @@ Windows client should mirror it:
|
||||
`punktfunk-core { path, features=["quic"] }`, `windows`, the Reactor crate, `ffmpeg-next`, `opus`,
|
||||
`sdl3`, `mdns-sd`, `anyhow`, `tracing`. Mirror `clients/linux/Cargo.toml`.
|
||||
3. **Connect + control plane.** Port `session.rs` + `trust.rs`; validate headless against the 4090
|
||||
box (`punktfunk1-host`/`serve --native`) — handshake, PIN/TOFU, plane counters — before any UI/decode.
|
||||
box (`punktfunk1-host`/`serve`) — handshake, PIN/TOFU, plane counters — before any UI/decode.
|
||||
4. **Decode + present.** FFmpeg D3D11VA → `SwapChainPanel`. SDR (8-bit BGRA) first, then **P010 +
|
||||
HDR colorspace** (see the HDR section).
|
||||
5. **Audio.** WASAPI render + Opus decode (port `audio.rs`).
|
||||
|
||||
@@ -15,7 +15,7 @@ plan + dev box + SudoVDA protocol + no-GPU strategy added 2026-06-14 (12-agent r
|
||||
## Status (2026-06-15) — full pipeline live-validated on an RTX 4090
|
||||
|
||||
Every OS-touching backend is implemented behind the existing traits and **builds clean on
|
||||
`x86_64-pc-windows-msvc`** (and Linux unaffected). `serve --native` / `punktfunk1-host` **run on Windows**
|
||||
`x86_64-pc-windows-msvc`** (and Linux unaffected). `serve` / `punktfunk1-host` **run on Windows**
|
||||
(identity in `%APPDATA%`, QUIC bound, mDNS advertising, accepting sessions). The **full native
|
||||
pipeline is validated live on a real RTX 4090** (Windows 11): SudoVDA virtual display → DXGI
|
||||
Desktop Duplication (D3D11 zero-copy) → **NVENC HEVC** → punktfunk/1 → Rust reference client, at
|
||||
@@ -146,7 +146,7 @@ rustc 1.96 clippy is stricter than the Linux CI image on shared code, e.g. `need
|
||||
3. `cargo build -p punktfunk-host --features nvenc` (needs NASM + CMake for aws-lc-rs; libclang for
|
||||
any ffmpeg-using client). Default build (no feature) uses the openh264 software encoder.
|
||||
4. Run in the **interactive session** (not a Session-0 service / not over SSH — SendInput + DXGI
|
||||
Desktop Duplication need a desktop): `serve --native` or `punktfunk1-host --source virtual`. Set
|
||||
Desktop Duplication need a desktop): `serve` or `punktfunk1-host --source virtual`. Set
|
||||
`PUNKTFUNK_ENCODER=nvenc` to select NVENC (the DXGI capturer switches to zero-copy D3D11 output to
|
||||
match). The SudoVDA monitor activates once a real GPU drives WDDM, so capture + NVENC then work.
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ PUNKTFUNK_ENCODER=nvenc
|
||||
PUNKTFUNK_VIDEO_SOURCE=virtual
|
||||
PUNKTFUNK_SECURE_DDA=1
|
||||
RUST_LOG=info
|
||||
# PUNKTFUNK_HOST_CMD=serve --native # the host subcommand the service launches (default)
|
||||
# PUNKTFUNK_HOST_CMD=serve --gamestream # the host subcommand the service launches (default: native + Moonlight)
|
||||
```
|
||||
|
||||
The service loads these into its environment and carries `PUNKTFUNK_*` + `RUST_LOG` to the host child
|
||||
|
||||
@@ -102,11 +102,12 @@ so it's a much lighter sysext than the host.
|
||||
|
||||
If the host box runs a firewall, open the ports it listens on. The **native `punktfunk/1`** plane:
|
||||
|
||||
- **QUIC control plane: UDP 9777** (`serve --native --native-port N` to change).
|
||||
- **QUIC control plane: UDP 9777** (`serve --native-port N` to change).
|
||||
- **Data plane: an *ephemeral* UDP port** — negotiated per session, so there is no fixed port to
|
||||
open. For a restrictive firewall you'd need to allow a UDP range (the repo does not pin one).
|
||||
|
||||
And the **GameStream / Moonlight** ports (fixed):
|
||||
And the **GameStream / Moonlight** ports (fixed) — only needed if you run the host with
|
||||
`serve --gamestream` (opt-in, trusted LAN only); bare `serve` is native-only and doesn't open these:
|
||||
|
||||
| Port | Proto | Purpose |
|
||||
|---|---|---|
|
||||
|
||||
+20
-12
@@ -233,19 +233,23 @@ systemctl --user status punktfunk-host
|
||||
journalctl --user -u punktfunk-host -f
|
||||
```
|
||||
|
||||
> **What `serve` actually starts.** The unit's `ExecStart` runs `punktfunk-host serve`, which is the
|
||||
> **GameStream / Moonlight-compatible** host (mDNS discovery, pairing, RTSP, the fixed GameStream
|
||||
> ports, **plus the management REST API on 47990**). The native `punktfunk/1` (QUIC) host is a
|
||||
> *separate* subcommand — `punktfunk-host punktfunk1-host` — and is **not** what the bundled systemd unit
|
||||
> launches. So out of the box on Bazzite you get the **Moonlight-compatible** host.
|
||||
> (Source: `crates/punktfunk-host/src/main.rs` — `serve` → `gamestream::serve`; `punktfunk1-host` is its own
|
||||
> path.)
|
||||
> **What `serve` actually starts.** The bundled unit's `ExecStart` runs `punktfunk-host serve
|
||||
> --gamestream`, so out of the box you get the **unified host**: the native `punktfunk/1` (QUIC) plane
|
||||
> — always on in `serve` — **plus** the GameStream/Moonlight-compat planes (mDNS discovery, pairing,
|
||||
> RTSP, the fixed GameStream ports) and the management REST API on 47990. The `--gamestream` flag is
|
||||
> what adds the Moonlight surface; GameStream pairs over plain HTTP and its legacy encryption is weaker
|
||||
> than the native plane's (security-review #5/#9), so it's **opt-in and trusted-LAN only**. For a
|
||||
> **secure native-only host**, drop `--gamestream` from the unit's `ExecStart` (bare `serve`) — native
|
||||
> clients still work; only stock Moonlight stops.
|
||||
> (Source: `crates/punktfunk-host/src/main.rs` — `serve` runs the native plane + mgmt; `--gamestream`
|
||||
> adds `gamestream::serve`.)
|
||||
|
||||
> **Unit caveat:** `scripts/punktfunk-host.service` declares only `After=pipewire.service` and (in
|
||||
> the upstream/dev layout) assumes the binary at `%h/punktfunk/target/release/punktfunk-host`. The
|
||||
> **RPM-installed** binary lives at `/usr/bin/punktfunk-host`. If `systemctl --user cat
|
||||
> punktfunk-host` shows `ExecStart` pointing at a missing path in your home dir, drop an override
|
||||
> (`systemctl --user edit punktfunk-host`) setting `ExecStart=/usr/bin/punktfunk-host serve`.
|
||||
> (`systemctl --user edit punktfunk-host`) setting `ExecStart=/usr/bin/punktfunk-host serve
|
||||
> --gamestream` (or bare `serve` for a secure native-only host).
|
||||
|
||||
---
|
||||
|
||||
@@ -256,7 +260,9 @@ journalctl --user -u punktfunk-host -f
|
||||
> the GameStream-host port-map (`docs/gamestream-host-plan.md`). Treat the `firewall-cmd` lines as recommended-but-verified,
|
||||
> not a checked-in script.
|
||||
|
||||
**GameStream / Moonlight ports** (fixed; Moonlight derives them from the HTTP base):
|
||||
**GameStream / Moonlight ports** (fixed; Moonlight derives them from the HTTP base). These only apply
|
||||
when the host runs `serve --gamestream` (the bundled unit's default); on a bare-`serve` native-only
|
||||
host you don't open them:
|
||||
|
||||
| Port | Proto | Purpose |
|
||||
|---|---|---|
|
||||
@@ -382,7 +388,8 @@ desktop viewer.
|
||||
|
||||
- **Service `ExecStart` points at a missing path in `$HOME`.** The dev unit references
|
||||
`%h/punktfunk/target/release/...`. The RPM binary is `/usr/bin/punktfunk-host`. Override
|
||||
`ExecStart=/usr/bin/punktfunk-host serve` if needed (section 5).
|
||||
`ExecStart=/usr/bin/punktfunk-host serve --gamestream` (or bare `serve` for native-only) if needed
|
||||
(section 5).
|
||||
|
||||
- **Moonlight can't see the host.** Ensure UDP 5353 (mDNS) and the GameStream ports are open
|
||||
(section 6) and client + host are on the same L2 LAN segment.
|
||||
@@ -413,6 +420,7 @@ matching your Bazzite Fedora base (`rpm -E %fedora`).
|
||||
|
||||
1. The COPR is **operator-run / not assumed published** — both install paths depend on it.
|
||||
2. There is **no firewall script/doc in the repo** — the ports above are derived from the code.
|
||||
3. The bundled systemd unit runs the **GameStream/Moonlight** `serve` host, **not** the native
|
||||
`punktfunk/1` QUIC host (`punktfunk1-host` is separate and unmanaged by the unit).
|
||||
3. The bundled systemd unit runs `serve --gamestream` — the native `punktfunk/1` QUIC plane (always
|
||||
on) **plus** the GameStream/Moonlight planes. Drop `--gamestream` for a secure native-only host;
|
||||
`punktfunk1-host` is a separate standalone native host, unmanaged by the unit.
|
||||
4. The mgmt port (47990) is **loopback-only by default** — don't open it.
|
||||
|
||||
@@ -52,11 +52,12 @@ journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
||||
|
||||
Open the ports the host listens on. The **native `punktfunk/1`** plane:
|
||||
|
||||
- **QUIC control plane: UDP 9777** (`serve --native --native-port N` to change).
|
||||
- **QUIC control plane: UDP 9777** (`serve --native-port N` to change).
|
||||
- **Data plane: an *ephemeral* UDP port** — negotiated per session, so there is no fixed port to
|
||||
open. For a restrictive firewall you'd need to allow a UDP range (the repo does not pin one).
|
||||
|
||||
And the **GameStream / Moonlight** ports (fixed):
|
||||
And the **GameStream / Moonlight** ports (fixed) — only needed if you run the host with
|
||||
`serve --gamestream` (opt-in, trusted LAN only); bare `serve` is native-only and doesn't open these:
|
||||
|
||||
| Port | Proto | Purpose |
|
||||
|---|---|---|
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
# punktfunk streaming host — systemd USER unit (`serve --native` = GameStream + punktfunk/1).
|
||||
# punktfunk streaming host — systemd USER unit (`serve --gamestream` = native punktfunk/1 + the
|
||||
# GameStream/Moonlight-compat planes). For a SECURE native-only host (no plain-HTTP pairing / legacy
|
||||
# GCM nonce reuse — security-review #5/#9; native clients only), drop `--gamestream` from ExecStart.
|
||||
#
|
||||
# Install (against an already-running compositor session):
|
||||
# mkdir -p ~/.config/systemd/user && cp scripts/punktfunk-host.service ~/.config/systemd/user/
|
||||
@@ -29,7 +31,7 @@ PartOf=punktfunk-kde-session.service
|
||||
|
||||
[Service]
|
||||
EnvironmentFile=%h/.config/punktfunk/host.env
|
||||
ExecStart=%h/punktfunk/target/release/punktfunk-host serve --native
|
||||
ExecStart=%h/punktfunk/target/release/punktfunk-host serve --gamestream
|
||||
Restart=on-failure
|
||||
RestartSec=2
|
||||
|
||||
|
||||
@@ -51,9 +51,10 @@ default `pf2`), `PUNKTFUNK_MGMT_PORT` (47990), `PUNKTFUNK_WEB_PORT` (3000).
|
||||
- **Config:** `~/.config/punktfunk/host.env` (encoder/compositor) and `web.env` (generated web login
|
||||
password + session secret). Trust material (`cert.pem`, `mgmt-token`, `punktfunk1-paired.json`) lives
|
||||
here too and persists across updates.
|
||||
- **Services:** `~/.config/systemd/user/punktfunk-host.service` (runs `serve --native --mgmt-bind
|
||||
0.0.0.0:47990`, `+ --open` if chosen) and `punktfunk-web.service`. Linger is enabled so they run
|
||||
without a login session.
|
||||
- **Services:** `~/.config/systemd/user/punktfunk-host.service` (runs `serve --gamestream --mgmt-bind
|
||||
0.0.0.0:47990`, `+ --open` if chosen — `--gamestream` adds the Moonlight-compat planes so the Deck's
|
||||
Game Mode also streams to stock Moonlight; the native `punktfunk/1` plane is always on) and
|
||||
`punktfunk-web.service`. Linger is enabled so they run without a login session.
|
||||
- **System tuning (sudo):** `/etc/sysctl.d/99-punktfunk-net.conf` (32 MB UDP buffers — the #1
|
||||
high-bitrate lever), `/etc/udev/rules.d/60-punktfunk.rules`, and `$USER` in the `input` group.
|
||||
|
||||
|
||||
@@ -170,7 +170,9 @@ fi
|
||||
# --- 5. systemd user services ---------------------------------------------
|
||||
log "Installing systemd user services"
|
||||
mkdir -p "$UNITS"
|
||||
SERVE_ARGS="serve --native --mgmt-bind 0.0.0.0:$MGMT_PORT"
|
||||
# --gamestream keeps the Moonlight-compat planes (the Deck commonly streams to Moonlight too); drop
|
||||
# it for a secure native-only host (no #5/#9 surface — native clients only).
|
||||
SERVE_ARGS="serve --gamestream --mgmt-bind 0.0.0.0:$MGMT_PORT"
|
||||
[ "$OPEN" = 1 ] && SERVE_ARGS="$SERVE_ARGS --open"
|
||||
cat > "$UNITS/punktfunk-host.service" <<EOF
|
||||
# Generated by scripts/steamdeck/install.sh — punktfunk Steam Deck host (native binary).
|
||||
|
||||
@@ -23,9 +23,10 @@ PUNKTFUNK_SECURE_DDA=1
|
||||
# Log level (info | debug | trace). Logs land in %ProgramData%\punktfunk\logs\.
|
||||
RUST_LOG=info
|
||||
|
||||
# The host subcommand the service launches. Default: `serve --native` (GameStream/Moonlight + the
|
||||
# native punktfunk/1 QUIC host in one process). Uncomment to override.
|
||||
#PUNKTFUNK_HOST_CMD=serve --native
|
||||
# The host subcommand the service launches. Default: `serve --gamestream` (native punktfunk/1 host
|
||||
# ALWAYS on + the GameStream/Moonlight-compat planes). Use `serve` for a SECURE native-only host
|
||||
# (no plain-HTTP pairing / legacy GCM nonce reuse — security-review #5/#9). Uncomment to override.
|
||||
#PUNKTFUNK_HOST_CMD=serve --gamestream
|
||||
|
||||
# Multi-GPU boxes only: force the NVENC/Desktop-Duplication GPU by Description substring. Leave
|
||||
# unset on single-GPU machines (the default auto-picks the discrete adapter).
|
||||
|
||||
+2
-1
@@ -23,7 +23,8 @@ bun run dev # http://localhost:3000
|
||||
Start a host with the management API up:
|
||||
|
||||
```sh
|
||||
# from the repo root — `serve` brings up the GameStream control plane + the mgmt API:
|
||||
# from the repo root — `serve` brings up the native punktfunk/1 plane + the mgmt API (the console
|
||||
# only needs the mgmt API; add --gamestream too if you also want the Moonlight surface):
|
||||
WAYLAND_DISPLAY=wayland-kde XDG_CURRENT_DESKTOP=KDE \
|
||||
cargo run -rp punktfunk-host -- serve
|
||||
# loopback :47990, no token (a token is mandatory for non-loopback binds).
|
||||
|
||||
Reference in New Issue
Block a user