One home per fact: IA rules, docs drift gates in CI, and the READMEs stop restating the docs #337
@@ -175,6 +175,19 @@ jobs:
|
||||
- name: Test (unit + loopback + proptest + C ABI harness)
|
||||
run: cargo test --workspace --locked
|
||||
|
||||
# The deep half of the docs-drift gates (the `docs-drift` job checks the docs-site copy
|
||||
# and the textual rest): the committed spec must match what the binary actually serves.
|
||||
# Build already compiled punktfunk-host with default features, so this re-links at worst.
|
||||
# Byte diff on purpose — the generator is deterministic, and if that ever stops being
|
||||
# true it deserves to surface here.
|
||||
- name: OpenAPI spec drift gate
|
||||
run: |
|
||||
cargo run -p punktfunk-host --locked -- openapi > /tmp/openapi.regen.json
|
||||
diff -u api/openapi.json /tmp/openapi.regen.json >/dev/null || {
|
||||
echo "::error::api/openapi.json is stale — regenerate: cargo run -p punktfunk-host -- openapi > api/openapi.json && cp api/openapi.json docs-site/public/openapi.json"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# The GPU encode backends are OFF by default, so every step above compiles ~none of them:
|
||||
# `nvenc` gates enc/linux/nvenc_cuda.rs (+ nvenc_core/nvenc_status) and `vulkan-encode` gates
|
||||
# enc/linux/vulkan_video.rs (+ the vendored vk_av1_encode/vk_valve_rgb bindings) — ~8,150
|
||||
@@ -390,3 +403,28 @@ jobs:
|
||||
# schema stability across bun2nix releases). Fix with: scripts/ci/check-bun-nix.sh --fix
|
||||
- name: bun.nix drift gate
|
||||
run: sh scripts/ci/check-bun-nix.sh
|
||||
|
||||
# Docs drift gates — pure git-grep textual checks, no cargo, no bun install (the deep half,
|
||||
# regenerating the OpenAPI spec from the built host, rides in the `rust` job above). Same
|
||||
# reasoning as bun-nix for being UNFILTERED: docs drift arrives through commits that look
|
||||
# unrelated to docs — a renamed env var, a removed subcommand, a moved page.
|
||||
docs-drift:
|
||||
runs-on: ubuntu-24.04
|
||||
container:
|
||||
image: oven/bun:1
|
||||
timeout-minutes: 15
|
||||
steps:
|
||||
# oven/bun ships neither git nor a real node, and the slim base has no CA bundle —
|
||||
# actions/checkout needs all three (see the web job).
|
||||
- name: Install git + node + CA certs
|
||||
run: apt-get update && apt-get install -y --no-install-recommends ca-certificates git nodejs
|
||||
- uses: actions/checkout@v4
|
||||
# OpenAPI snapshot in sync, PUNKTFUNK_* vars in docs still exist, undocumented-var
|
||||
# ratchet (baseline: scripts/ci/docs-undocumented-env-baseline.txt), host-cli.md commands
|
||||
# still exist, data/platforms.json parses.
|
||||
- name: Docs drift gates
|
||||
run: sh scripts/ci/check-docs-drift.sh
|
||||
# Internal links only: /docs/* page links in docs-site content, relative file links in
|
||||
# the repo's markdown. External URLs and #anchors are deliberately not checked.
|
||||
- name: Docs link check
|
||||
run: sh scripts/ci/check-docs-links.sh
|
||||
|
||||
+28
-3
@@ -83,15 +83,40 @@ Two more gates that only apply to some changes:
|
||||
instead of waiting for the CI job that compiles it.
|
||||
|
||||
Generated artifacts are checked in. `include/punktfunk_core.h` (cbindgen) is regenerated by the build
|
||||
and CI fails if the committed copy drifts. `api/openapi.json` is **not** gated — nothing in CI
|
||||
regenerates or diffs it, so regenerate and commit it yourself whenever you touch the management API,
|
||||
and copy the snapshot the docs site serves:
|
||||
and CI fails if the committed copy drifts. `api/openapi.json` is gated the same way: the `rust` job
|
||||
regenerates the spec and diffs it against the committed file, and the `docs-drift` job checks that
|
||||
`docs-site/public/openapi.json` — the snapshot the docs site serves — is a byte-for-byte copy of it.
|
||||
Touch the management API and CI stays red until you regenerate and re-copy:
|
||||
|
||||
```sh
|
||||
cargo run -p punktfunk-host -- openapi > api/openapi.json
|
||||
cp api/openapi.json docs-site/public/openapi.json
|
||||
```
|
||||
|
||||
## Where facts live (docs vs READMEs vs website)
|
||||
|
||||
Every user-facing fact has exactly one canonical home; everything else links to it. Duplicated
|
||||
walkthroughs are how the docs drifted before — don't add new ones.
|
||||
|
||||
| Surface | Owns | Never contains |
|
||||
|---|---|---|
|
||||
| [docs-site](https://docs.punktfunk.unom.io) (`docs-site/content/`) | All user-facing facts: install, config, features, troubleshooting | Design rationale |
|
||||
| READMEs (root, `packaging/*`, `scripts/*`) | Dev/packager rationale and pointers into the docs | User walkthroughs duplicated from docs-site |
|
||||
| [punktfunk.unom.io](https://punktfunk.unom.io) (separate repo) | Marketing, downloads, blog | Instructions — it deep-links the docs instead |
|
||||
| punktfunk-planning (private) | Design rationale, RFCs, plans | Anything user-facing |
|
||||
|
||||
Docs pages are written for one of two audiences, not both at once: the **get-started track**
|
||||
(quickstart, install, pairing — short, one task per page, happy path only) assumes no Linux
|
||||
expertise; the **reference track** (configuration, CLI, API, per-compositor pages) is allowed to be
|
||||
dense. When a change touches a user-facing fact, update the docs-site page that owns it in the same
|
||||
PR.
|
||||
|
||||
CI enforces the cheap half of this (`scripts/ci/check-docs-drift.sh` and `check-docs-links.sh`):
|
||||
the OpenAPI snapshot must match `api/openapi.json`, every `PUNKTFUNK_*` variable the docs mention
|
||||
must still exist in the tree, the counts of undocumented `PUNKTFUNK_*` variables and undocumented
|
||||
`punktfunk-host` subcommands may never grow (document the new knob, or consciously raise the
|
||||
baseline in the script), and internal docs links must resolve.
|
||||
|
||||
Match the surrounding code's comment density and naming. Commit messages end with the
|
||||
`Co-Authored-By` trailer (see `git log`).
|
||||
|
||||
|
||||
@@ -109,36 +109,11 @@ installer (all-vendor: NVIDIA, AMD, Intel).
|
||||
|
||||
`punktfunk-host` is the streaming host; `punktfunk-web` is the browser console (pairing + status).
|
||||
|
||||
**Linux:** every package ships systemd **user** units, so you don't launch the host by hand. The
|
||||
host unit won't start until `~/.config/punktfunk/host.env` exists, so copy the template your package
|
||||
installed first:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.config/punktfunk
|
||||
# /usr/share/punktfunk/ on Fedora/Arch/Bazzite, /usr/share/punktfunk-host/ on Debian/Ubuntu
|
||||
# (on Bazzite take host.env.bazzite instead)
|
||||
cp /usr/share/punktfunk/host.env.example ~/.config/punktfunk/host.env
|
||||
|
||||
systemctl --user enable --now punktfunk-host # the streaming host
|
||||
systemctl --user enable --now punktfunk-web # the web console (Arch: install punktfunk-web first)
|
||||
```
|
||||
|
||||
The shipped host unit runs `serve --gamestream` — the native `punktfunk/1` plane **plus** the
|
||||
GameStream/Moonlight-compat planes, which belong on a trusted LAN only; for a native-only host drop
|
||||
the flag with a `systemctl --user edit punktfunk-host` drop-in (which needs an empty `ExecStart=`
|
||||
line before the replacement — the install guide has the snippet). Then open
|
||||
`https://<host-ip>:47992` and pair.
|
||||
|
||||
How the virtual display and input are wired up depends on your desktop — see
|
||||
[KDE](https://docs.punktfunk.unom.io/docs/kde) · [GNOME](https://docs.punktfunk.unom.io/docs/gnome) ·
|
||||
The per-platform guide walks you through the rest — first run, the web console, pairing, and the
|
||||
desktop-specific wiring ([KDE](https://docs.punktfunk.unom.io/docs/kde) ·
|
||||
[GNOME](https://docs.punktfunk.unom.io/docs/gnome) ·
|
||||
[Steam / gamescope](https://docs.punktfunk.unom.io/docs/gamescope) ·
|
||||
[Sway](https://docs.punktfunk.unom.io/docs/sway).
|
||||
|
||||
**Windows:** the installer registers and starts the host as a `LocalSystem` service, so there is
|
||||
nothing to run by hand — open the web console and pair. Use
|
||||
`punktfunk-host service start|stop|restart|status` if you need to control it. Upgrades happen in
|
||||
place — the console's **Updates** card, `winget upgrade unom.PunktfunkHost`, or the newer
|
||||
`setup.exe` over the old install; uninstall from Add/Remove Programs.
|
||||
[Sway](https://docs.punktfunk.unom.io/docs/sway)).
|
||||
|
||||
Full instructions: **[docs.punktfunk.unom.io/docs/install](https://docs.punktfunk.unom.io/docs/install)**.
|
||||
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
{
|
||||
"$comment": "Single source for the install/port facts that used to drift across four surfaces (docs-and-onboarding-overhaul WP1). Consumers: docs-site install pages (WP2), the website download page (WP3), the guided install script (WP4). A port number, repo URL or install command lives HERE and nowhere else — pages quote it, they don't restate it. Validated by scripts/ci/check-docs-drift.sh (parse only; ponytail: cross-check against code literals when a consumer exists).",
|
||||
|
||||
"ports": {
|
||||
"mgmt": {
|
||||
"port": 47990,
|
||||
"proto": "tcp",
|
||||
"what": "management REST API + web console endpoint",
|
||||
"env": "PUNKTFUNK_MGMT_BIND",
|
||||
"conflict": "Sunshine / Apollo / Vibeshine serve their web UI on 47990 too — the one port still shared with them once GameStream compat is off. Move it via PUNKTFUNK_MGMT_BIND; clients relearn it from discovery."
|
||||
},
|
||||
"native": {
|
||||
"port": 9777,
|
||||
"proto": "udp",
|
||||
"what": "punktfunk/1 QUIC control port",
|
||||
"env": "PUNKTFUNK_NATIVE_PORT"
|
||||
},
|
||||
"data": {
|
||||
"port": null,
|
||||
"proto": "udp",
|
||||
"what": "per-session video data plane — random port + hole-punch by default, pinnable",
|
||||
"env": "PUNKTFUNK_DATA_PORT"
|
||||
},
|
||||
"mdns": {
|
||||
"port": 5353,
|
||||
"proto": "udp",
|
||||
"what": "mDNS discovery"
|
||||
},
|
||||
"gamestream": {
|
||||
"tcp": [47984, 47989, 48010],
|
||||
"udp": [47998, 47999, 48000],
|
||||
"what": "GameStream/Moonlight-compat planes (opt-in, `serve --gamestream`)",
|
||||
"conflict": "Sunshine / Apollo / Vibeshine bind these same fixed ports and advertise the same mDNS name — run only one GameStream host at a time, or keep punktfunk native-only."
|
||||
}
|
||||
},
|
||||
|
||||
"conflicts": {
|
||||
"hosts": ["Sunshine", "Apollo", "Vibeshine"],
|
||||
"detect": "punktfunk-host detect-conflicts",
|
||||
"detectExit": "1 only when a conflicting host runs or will start on its own; dormant leftovers print but exit 0",
|
||||
"docs": "/docs/troubleshooting#another-streaming-host-sunshine-apollo--is-installed"
|
||||
},
|
||||
|
||||
"platforms": [
|
||||
{
|
||||
"id": "debian",
|
||||
"name": "Debian 13 / Ubuntu 24.04+",
|
||||
"installs": "host",
|
||||
"packageManager": "apt",
|
||||
"docs": "/docs/debian",
|
||||
"install": [
|
||||
"echo \"deb [signed-by=/etc/apt/keyrings/punktfunk.asc] https://git.unom.io/api/packages/unom/debian stable main\" | sudo tee /etc/apt/sources.list.d/punktfunk.list",
|
||||
"sudo apt update",
|
||||
"sudo apt install punktfunk-host"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "arch",
|
||||
"name": "Arch Linux / CachyOS",
|
||||
"installs": "host",
|
||||
"packageManager": "pacman",
|
||||
"docs": "/docs/arch",
|
||||
"install": [
|
||||
"curl -fsS https://git.unom.io/api/packages/unom/arch/repository.key | sudo pacman-key --add -",
|
||||
"sudo pacman-key --lsign-key E0CA04465C99C936E0B0C6510A317015A34DDD69",
|
||||
"printf '\\n[punktfunk]\\nServer = https://git.unom.io/api/packages/unom/arch/$repo/$arch\\n' | sudo tee -a /etc/pacman.conf >/dev/null",
|
||||
"sudo pacman -Sy punktfunk-host"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fedora",
|
||||
"name": "Fedora 43+",
|
||||
"installs": "host",
|
||||
"packageManager": "dnf",
|
||||
"docs": "/docs/fedora",
|
||||
"repo": "https://git.unom.io/api/packages/unom/rpm/fedora-44",
|
||||
"install": [
|
||||
"sudo dnf install punktfunk",
|
||||
"sudo usermod -aG input \"$USER\""
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "bazzite",
|
||||
"name": "Bazzite / Fedora Atomic",
|
||||
"installs": "host",
|
||||
"packageManager": "sysext",
|
||||
"docs": "/docs/bazzite",
|
||||
"install": [
|
||||
"curl -fsSLO https://git.unom.io/unom/punktfunk/raw/branch/main/packaging/bazzite/punktfunk-sysext.sh",
|
||||
"sudo bash punktfunk-sysext.sh install"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "nixos",
|
||||
"name": "NixOS",
|
||||
"installs": "host",
|
||||
"packageManager": "nix",
|
||||
"docs": "/docs/install",
|
||||
"install": [
|
||||
"# flake input: inputs.punktfunk.url = \"git+https://git.unom.io/unom/punktfunk\";",
|
||||
"# then: imports = [ punktfunk.nixosModules.default ]; services.punktfunk.host.enable = true;"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "windows",
|
||||
"name": "Windows 10/11",
|
||||
"installs": "host",
|
||||
"packageManager": "winget",
|
||||
"docs": "/docs/windows-host",
|
||||
"install": [
|
||||
"winget install unom.PunktfunkHost --interactive"
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "linux-client",
|
||||
"name": "Linux client (any distro)",
|
||||
"installs": "client",
|
||||
"packageManager": "flatpak",
|
||||
"docs": "/docs/install-client",
|
||||
"install": [
|
||||
"flatpak install --user https://flatpak.unom.io/io.unom.Punktfunk.flatpakref"
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
+10
-11
@@ -4,7 +4,12 @@ The Punktfunk documentation site: [Fumadocs](https://fumadocs.dev) on
|
||||
[TanStack Start](https://tanstack.com/start) (Vite + Nitro/bun preset).
|
||||
|
||||
Content lives in [`content/docs/`](content/docs) as `.md`/`.mdx`. This site is the source of truth
|
||||
for the **user-facing** guides; design rationale lives in the internal punktfunk-planning repo.
|
||||
for the **user-facing** guides; design rationale lives in the internal punktfunk-planning repo, and
|
||||
READMEs and the marketing site link here instead of restating anything — see "Where facts live" in
|
||||
[CONTRIBUTING.md](../CONTRIBUTING.md). Pages serve one of two audiences, not both at once: the
|
||||
**get-started track** (quickstart, install, pairing) assumes no Linux expertise — short pages, one
|
||||
task each, happy path only; the **reference track** (configuration, CLI, API, per-compositor
|
||||
pages) is allowed to be dense.
|
||||
|
||||
## API reference
|
||||
|
||||
@@ -19,16 +24,10 @@ cargo run -p punktfunk-host -- openapi > api/openapi.json
|
||||
cp api/openapi.json docs-site/public/openapi.json
|
||||
```
|
||||
|
||||
Nothing in CI diffs the two, so the snapshot goes stale silently — that manual `cp` is the only
|
||||
thing keeping them in sync. Before publishing docs, check that they match:
|
||||
|
||||
```bash
|
||||
diff <(jq -S . api/openapi.json) <(jq -S . docs-site/public/openapi.json)
|
||||
```
|
||||
|
||||
That should print nothing. Right now it doesn't: the committed snapshot predates the
|
||||
`/api/v1/update/check`, `/api/v1/update/apply` and `/api/v1/update/status` endpoints, so the
|
||||
published `/api` reference is missing the host self-update surface — re-copy it.
|
||||
CI keeps the pair honest: the `docs-drift` job fails unless the snapshot is a byte-for-byte copy
|
||||
of `api/openapi.json`, and the `rust` job regenerates the spec and diffs it against the committed
|
||||
one — so a management-API change can't publish stale API docs any more, it fails CI until you run
|
||||
the two commands above.
|
||||
|
||||
## Develop
|
||||
|
||||
|
||||
+3
-2
@@ -9,8 +9,9 @@ present; the NVIDIA-specific notes below apply to the NVENC path.
|
||||
> [`debian/README.md`](debian/README.md) (`apt update && apt upgrade` for new builds).
|
||||
|
||||
> 👉 **End-to-end Bazzite setup walkthrough** (install → udev/group → `host.env` → service →
|
||||
> firewall → verify → troubleshooting): [`bazzite/README.md`](bazzite/README.md). This file is the
|
||||
> higher-level packaging rationale.
|
||||
> firewall → verify): [docs.punktfunk.unom.io/docs/bazzite](https://docs.punktfunk.unom.io/docs/bazzite).
|
||||
> [`bazzite/README.md`](bazzite/README.md) keeps the install-path/packaging view (sysext vs bootc
|
||||
> vs layering); this file is the higher-level packaging rationale.
|
||||
|
||||
```
|
||||
packaging/
|
||||
|
||||
+10
-22
@@ -35,29 +35,17 @@ them). Enable exactly one.
|
||||
The registry **signs the repo database and every package**, so first import its key into
|
||||
pacman's keyring (a one-time step — after this, packages install signature-verified):
|
||||
|
||||
```sh
|
||||
# 1. Trust the registry signing key.
|
||||
curl -fsS https://git.unom.io/api/packages/unom/arch/repository.key \
|
||||
| sudo pacman-key --add -
|
||||
sudo pacman-key --lsign-key E0CA04465C99C936E0B0C6510A317015A34DDD69
|
||||
The user-facing walkthrough — key import, repo add, `pacman -Syu` install — lives on the
|
||||
[Arch docs page](https://docs.punktfunk.unom.io/docs/arch), stated once so it can't drift (see
|
||||
"Where facts live" in [`CONTRIBUTING.md`](../../CONTRIBUTING.md)). Packager notes: no `SigLevel`
|
||||
line is needed (pacman's default `Required DatabaseOptional` verifies the signed packages against
|
||||
the imported key), the repo-add uses `printf` rather than a heredoc so it works in fish (CachyOS's
|
||||
default shell has no `<<EOF`), and Arch is rolling — packages are built against current Arch
|
||||
sonames, so the box itself must stay updated.
|
||||
|
||||
# 2. Add the repo (pick ONE channel — punktfunk for releases, punktfunk-canary for main builds).
|
||||
# printf, not a heredoc, so this works in fish too (CachyOS's default shell has no `<<EOF`).
|
||||
printf '\n[punktfunk]\nServer = https://git.unom.io/api/packages/unom/arch/$repo/$arch\n' \
|
||||
| sudo tee -a /etc/pacman.conf >/dev/null
|
||||
|
||||
# 3. Sync + install.
|
||||
sudo pacman -Sy punktfunk-host # gaming rig
|
||||
sudo pacman -Sy punktfunk-client # the native GTK4 Linux client
|
||||
sudo pacman -Sy punktfunk-web # optional browser management console
|
||||
```
|
||||
|
||||
(No `SigLevel` line needed — pacman's default `Required DatabaseOptional` verifies the signed
|
||||
packages against the key you just trusted. Arch is rolling, so the packages are built against
|
||||
current Arch sonames — keep the box itself updated too.)
|
||||
|
||||
Step 2 **appends**, so running it twice leaves two `[punktfunk]` blocks and every later pacman
|
||||
run opens with `error: could not register 'punktfunk' database (database already registered)`.
|
||||
The repo-add **appends** to `/etc/pacman.conf`, so running it twice leaves two `[punktfunk]`
|
||||
blocks and every later pacman run opens with
|
||||
`error: could not register 'punktfunk' database (database already registered)`.
|
||||
It is harmless — pacman ignores the duplicate and carries on — but to silence it, delete the
|
||||
extra block from `/etc/pacman.conf`.
|
||||
|
||||
|
||||
+42
-402
@@ -1,16 +1,12 @@
|
||||
# Setting up punktfunk on Bazzite
|
||||
# punktfunk on Bazzite — packaging and install paths
|
||||
|
||||
A step-by-step setup guide for running the **punktfunk** low-latency streaming host on
|
||||
**Bazzite** (the immutable, Fedora-Atomic gaming distro). Everything below is grounded in this
|
||||
repo's packaging and ops files; where something is **not yet published or not in the repo**, it's
|
||||
flagged explicitly. For the higher-level packaging rationale ("why not Flatpak", the build), see
|
||||
[`../README.md`](../README.md).
|
||||
|
||||
> **What you get on Bazzite:** it already ships the three things punktfunk normally has to fight
|
||||
> for — **gamescope**, **PipeWire/WirePlumber**, and (on the `-nvidia` images) the **NVIDIA driver
|
||||
> with NVENC/EGL**. The only genuinely new runtime bits punktfunk adds are `ffmpeg-libs` (with
|
||||
> NVENC, from RPM Fusion **nonfree**), `opus`, and `libei`.
|
||||
> Source: `packaging/README.md`, `packaging/rpm/punktfunk.spec`.
|
||||
The packager/ops view of the Bazzite (Fedora Atomic) target: which install path to ship, how each
|
||||
one is built and updated, and the gotchas of layered/bootc hosts. **Setting a host up** — udev and
|
||||
the `input` group, `host.env`, the service, the firewall, streaming the KDE desktop, verifying —
|
||||
is the docs' job: follow **[docs.punktfunk.unom.io/docs/bazzite](https://docs.punktfunk.unom.io/docs/bazzite)**
|
||||
(firewall specifics are on [Fedora §4](https://docs.punktfunk.unom.io/docs/fedora), same firewalld
|
||||
service definitions). Nothing here duplicates those pages. For the higher-level packaging
|
||||
rationale ("why not Flatpak", the build), see [`../README.md`](../README.md).
|
||||
|
||||
> ⚠️ **COPR note (Path C only).** The legacy layering path's commands reference a COPR project
|
||||
> named `enricobuehler/punktfunk` that is operator-run and may not be published (see
|
||||
@@ -18,8 +14,6 @@ flagged explicitly. For the higher-level packaging rationale ("why not Flatpak",
|
||||
> the repo file `https://git.unom.io/api/packages/unom/rpm/bazzite.repo`) — it's what CI
|
||||
> actually publishes to. Paths A (sysext) and B (bootc) don't involve the COPR at all.
|
||||
|
||||
---
|
||||
|
||||
## 1. Choose an install path
|
||||
|
||||
There are three paths on Bazzite, driven by different files in `packaging/`:
|
||||
@@ -34,53 +28,28 @@ There are three paths on Bazzite, driven by different files in `packaging/`:
|
||||
every OS update slower and can **block upgrades entirely** until removed. A sysext never enters an
|
||||
rpm-ostree transaction: it merges/unmerges at runtime, survives OS updates, and updating punktfunk
|
||||
is one command with **no reboot** (layering needs one per update). It's the mechanism the Fedora
|
||||
Atomic maintainers ship via [fedora-sysexts](https://fedora-sysexts.github.io/). All paths require
|
||||
the **same first-run setup** (sections 3–6).
|
||||
Atomic maintainers ship via [fedora-sysexts](https://fedora-sysexts.github.io/).
|
||||
|
||||
### Path A — systemd-sysext (recommended)
|
||||
|
||||
Run on the Bazzite host:
|
||||
Install, day-2 commands, channel switching, rollback, feed-signature refusals and the
|
||||
major-rebase behavior are all on the [docs page](https://docs.punktfunk.unom.io/docs/bazzite) —
|
||||
that's the walkthrough to hand a user. Packager-side facts:
|
||||
|
||||
```sh
|
||||
# One-time bootstrap; afterwards the tool is on PATH as `punktfunk-sysext` (it ships inside
|
||||
# the image). `--channel canary` for rolling main-branch builds instead of releases.
|
||||
curl -fsSLO https://git.unom.io/unom/punktfunk/raw/branch/main/packaging/bazzite/punktfunk-sysext.sh
|
||||
sudo bash punktfunk-sysext.sh install
|
||||
```
|
||||
|
||||
This downloads the newest image for your Fedora base (host + tray + **web console**,
|
||||
SHA-256-verified against a signed manifest from the feed
|
||||
`…/packages/unom/generic/punktfunk-sysext/f<ver>[-canary]/`),
|
||||
installs it as `/var/lib/extensions/punktfunk.raw`, merges it, and immediately applies what the
|
||||
RPM scriptlets would have (udev reload, sysctl) plus the two `/etc` files a sysext can't carry
|
||||
(the gamescope-session drop-in and the tray autostart entry, staged under
|
||||
`/usr/share/punktfunk/etc/`). No reboot at any point. Day-2:
|
||||
|
||||
```sh
|
||||
sudo punktfunk-sysext update # fetch + merge the newest build (then restart the user service)
|
||||
sudo punktfunk-sysext status # merged?, installed vs latest, channel/feed
|
||||
sudo punktfunk-sysext remove # unmerge + delete; ~/.config/punktfunk is left alone
|
||||
```
|
||||
|
||||
Details worth knowing:
|
||||
|
||||
- **The feed is signed.** Each feed carries `SHA256SUMS` plus a detached OpenPGP signature
|
||||
`SHA256SUMS.asc` from `packages@unom.io` (`AF245C506F4E4763`) — the same key that signs our RPMs.
|
||||
`punktfunk-sysext` verifies that signature, with the public key baked into the script, *before*
|
||||
it believes the manifest, and refuses a feed it can't verify. The checksums alone never proved
|
||||
authorship: they sit on the same registry as the images they describe, so whatever could replace
|
||||
an image could replace its checksum in the same breath. If you are on a feed published before
|
||||
signing existed, it is sealed on the next publish to that Fedora major; to install from it
|
||||
meanwhile — accepting that the image is unauthenticated — set `PUNKTFUNK_SYSEXT_ALLOW_UNSIGNED=1`.
|
||||
- CI (`.gitea/workflows/rpm.yml`) builds the image per Fedora major and publishes it to the feed
|
||||
`…/packages/unom/generic/punktfunk-sysext/f<ver>[-canary]/` — `SHA256SUMS` plus a detached
|
||||
OpenPGP signature from `packages@unom.io` (`AF245C506F4E4763`, the RPM signing key). The public
|
||||
key is baked into `punktfunk-sysext.sh`; the script refuses a feed it can't verify
|
||||
(`PUNKTFUNK_SYSEXT_ALLOW_UNSIGNED=1` is the documented escape hatch for pre-signing feeds).
|
||||
- The image embeds `ID=fedora` + `VERSION_ID` (matched through Bazzite's `ID_LIKE`), so after a
|
||||
**major Bazzite rebase** (F43 → F44) the old image is **refused** instead of merging
|
||||
soname-broken binaries — `punktfunk-sysext update` then fetches the image built for the new
|
||||
base (feeds exist per Fedora major, from the same CI matrix as the RPM groups).
|
||||
- SELinux labels are baked into the image at build time (squashfs pseudo-xattrs computed from
|
||||
the targeted policy) — without them udev couldn't read the gamepad rule under enforcing.
|
||||
Validated live on Bazzite 43.
|
||||
- **Migrating from layering (path C):** install the sysext (it shadows the layered copy at
|
||||
once), then `sudo rpm-ostree uninstall punktfunk punktfunk-web && systemctl reboot`.
|
||||
major rebase the old image is refused instead of merging soname-broken binaries; feeds exist
|
||||
per Fedora major, from the same CI matrix as the RPM groups.
|
||||
- SELinux labels are baked into the image at build time (squashfs pseudo-xattrs computed from the
|
||||
targeted policy) — without them udev couldn't read the gamepad rule under enforcing. Validated
|
||||
live on Bazzite 43.
|
||||
- Install also applies what the RPM scriptlets would have (udev reload, sysctl) and seeds the two
|
||||
`/etc` files a sysext can't carry (the gamescope-session drop-in, the tray autostart entry),
|
||||
staged under `/usr/share/punktfunk/etc/`.
|
||||
|
||||
### Path B — bootc image (`FROM bazzite-nvidia`)
|
||||
|
||||
@@ -169,340 +138,29 @@ sudo bash packaging/bazzite/update-punktfunk.sh --reboot
|
||||
> `punktfunk.repo`, canary's `<next-minor>.0-0.ciN` **outranks** the stable `X.Y.Z-1` and the box
|
||||
> silently tracks canary. Enable exactly one channel — set `enabled=0` in the other repo file.
|
||||
|
||||
---
|
||||
## 2. First-run setup
|
||||
|
||||
## 2. Prerequisites — what Bazzite gives you vs. what you must still do
|
||||
All of it — `input`/`punktfunk` groups, `host.env`, enabling the service, the firewall, KDE
|
||||
desktop mode, verifying the first stream — lives on the
|
||||
[docs page](https://docs.punktfunk.unom.io/docs/bazzite) and applies identically to all three
|
||||
install paths. Don't restate it here; a fact stated twice is a fact that drifts (see
|
||||
"Where facts live" in [`CONTRIBUTING.md`](../../CONTRIBUTING.md)).
|
||||
|
||||
**Already satisfied on Bazzite (`-nvidia` images):**
|
||||
## 3. Gotchas the docs don't carry (yet)
|
||||
|
||||
- NVIDIA driver: `libnvidia-encode` (NVENC) + `libEGL_nvidia` for the zero-copy path.
|
||||
- `gamescope` — the default compositor backend punktfunk uses on Bazzite.
|
||||
- PipeWire + WirePlumber — the capture/audio graph.
|
||||
<!-- WP2 of the docs overhaul moves these into /docs/troubleshooting; until then this is their
|
||||
only home, so they stay. -->
|
||||
|
||||
**You must still do (covered below):**
|
||||
|
||||
1. **Reboot** after layering / rebasing (section 1).
|
||||
2. **Join the `input` group** and ensure the **udev rule** is installed (section 3) — required for
|
||||
virtual gamepads / DualSense.
|
||||
3. **Place `host.env`** and **enable the systemd user service** (sections 4–5).
|
||||
4. **Open firewall ports** (section 6).
|
||||
|
||||
RPM Fusion's `ffmpeg-libs` is a **weak dependency** (`Recommends:` in the spec) — the package
|
||||
installs without it, but **NVENC encoding will fail at runtime** if it's missing. The RPM Fusion
|
||||
step in section 1 covers this.
|
||||
|
||||
---
|
||||
|
||||
## 3. udev rule + the `input` group
|
||||
|
||||
punktfunk creates **virtual X-Box-360 gamepads** via `/dev/uinput` and **virtual DualSense** pads
|
||||
via `/dev/uhid` (kernel `hid-playstation` driver — LEDs, adaptive triggers, touchpad, gyro). The
|
||||
udev rule grants the `input` group access to both nodes.
|
||||
|
||||
The RPM **already installs** the rule to `/usr/lib/udev/rules.d/60-punktfunk.rules` and its `%post`
|
||||
reloads udev. So on a packaged install (Path A or B) **you only need to join the `input` group**:
|
||||
|
||||
```sh
|
||||
ujust add-user-to-input-group # then LOG OUT and back in (or reboot)
|
||||
```
|
||||
|
||||
> ⚠️ **On Bazzite use `ujust add-user-to-input-group`, NOT `sudo usermod -aG input $USER`.** Bazzite
|
||||
> is an atomic (rpm-ostree) OS where `/etc/group` is managed declaratively — a plain `usermod` either
|
||||
> doesn't stick or gets reverted on the next update. The `ujust` recipe edits the group the
|
||||
> immutable-OS-correct way (and reloads udev). (`ujust` ships with Bazzite; `ujust --list` shows all
|
||||
> recipes.)
|
||||
|
||||
> 🔁 **The group change does not apply to your current login session** — you must re-login (or
|
||||
> reboot). Until then, gamepad creation fails with a permission error on `/dev/uinput`. This is the
|
||||
> single most common "why don't my gamepads work" gotcha.
|
||||
|
||||
If you installed from a tarball/source instead of the RPM (so the rule isn't in place), install it
|
||||
manually — the exact commands from the rule file's header (`scripts/60-punktfunk.rules`):
|
||||
|
||||
```sh
|
||||
sudo cp scripts/60-punktfunk.rules /etc/udev/rules.d/
|
||||
ujust add-user-to-input-group # NOT `usermod` on Bazzite (see the note above); then re-login
|
||||
sudo udevadm control --reload-rules && sudo udevadm trigger
|
||||
```
|
||||
|
||||
The core rule contents, for reference (the full file additionally grants the `input` group access
|
||||
to the vhci attach files and to the hidraw nodes of the virtual pads the host creates — Steam/SDL
|
||||
need hidraw to send a DualSense's adaptive-trigger/lightbar feedback and reliable rumble):
|
||||
|
||||
```
|
||||
KERNEL=="uinput", SUBSYSTEM=="misc", OPTIONS+="static_node=uinput", GROUP="input", MODE="0660", TAG+="uaccess"
|
||||
KERNEL=="uhid", SUBSYSTEM=="misc", OPTIONS+="static_node=uhid", GROUP="input", MODE="0660", TAG+="uaccess"
|
||||
KERNEL=="hidraw*", KERNELS=="*054C:0CE6*", GROUP="input", MODE="0660", TAG+="uaccess" # + 0DF2/09CC/2009/1205/1102
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Configure `host.env`
|
||||
|
||||
The systemd user unit reads its environment from **`~/.config/punktfunk/host.env`**
|
||||
(`EnvironmentFile=-%h/.config/punktfunk/host.env` in `scripts/punktfunk-host.service` — the `-`
|
||||
makes it optional, since no package creates the file; absent just means every knob at its default).
|
||||
The RPM ships a Bazzite-tuned template at `/usr/share/punktfunk/host.env.bazzite`. Copy it into
|
||||
place:
|
||||
|
||||
```sh
|
||||
mkdir -p ~/.config/punktfunk
|
||||
cp /usr/share/punktfunk/host.env.bazzite ~/.config/punktfunk/host.env
|
||||
# then edit ~/.config/punktfunk/host.env
|
||||
```
|
||||
|
||||
The Bazzite template (`packaging/bazzite/host.env`) contains:
|
||||
|
||||
```sh
|
||||
PUNKTFUNK_VIDEO_SOURCE=virtual
|
||||
|
||||
# GPU zero-copy capture (dmabuf -> CUDA -> NVENC) is ON by default and auto-falls back to CPU if
|
||||
# unavailable. No need to set it. Set to 0 only to force the CPU path.
|
||||
# PUNKTFUNK_ZEROCOPY=0
|
||||
|
||||
#RUST_LOG=info
|
||||
```
|
||||
|
||||
**What each knob means and why these are the Bazzite defaults:**
|
||||
|
||||
| Knob | Value | Meaning |
|
||||
|---|---|---|
|
||||
| *(no compositor / no anchors)* | — | The host **auto-detects** the live session per connect (Gaming Mode gamescope vs the KDE desktop) and follows switches mid-stream; a `systemctl --user` service inherits the right `XDG_RUNTIME_DIR` and the host derives the bus itself. Pinning `PUNKTFUNK_COMPOSITOR` or hardcoding uid-1000 anchors only breaks this — leave them out. |
|
||||
| `PUNKTFUNK_VIDEO_SOURCE` | `virtual` | Create a per-client virtual output at the client's exact WxH@Hz (the flagship "native resolution, no scaling" mode), vs. `portal` which captures an existing monitor. |
|
||||
| `PUNKTFUNK_GAMESCOPE_ATTACH` | *(unset)* | Gaming Mode model. Left unset the host picks one per connect, which on Bazzite is **managed**: it relaunches the gaming session headless at the **client's** exact mode, so the client gets a display of its own. Set `=1` to force **attach** instead — the box keeps its session and the client is served a mirror of the box's own screen at the box's mode (see the template's comments). |
|
||||
| `PUNKTFUNK_ZEROCOPY` | `on` *(default)* | GPU zero-copy capture (dmabuf → CUDA → NVENC), on by default. Falls back to CPU automatically if unavailable; set `0` to force the CPU path. |
|
||||
| `RUST_LOG` | (commented) | Uncomment `RUST_LOG=info` for verbose logs while debugging. |
|
||||
|
||||
**Optional — a real DualSense for clients holding one:** add `PUNKTFUNK_GAMEPAD=dualsense` to present
|
||||
games a virtual Sony DualSense (lightbar, adaptive triggers, touchpad, motion) instead of the
|
||||
default X-Box-360 pad. The feedback flows back to a real DualSense on the client.
|
||||
|
||||
**The Plasma desktop needs no extra config:** the same auto-detection streams the KDE Desktop
|
||||
session whenever that's what's live — no compositor pin, no `WAYLAND_DISPLAY` /
|
||||
`XDG_CURRENT_DESKTOP` (the host retargets those per connect). The full knob list (FEC %, per-stage
|
||||
timing, etc.) is in `scripts/host.env.example` / `/usr/share/punktfunk/host.env.example`.
|
||||
|
||||
> Auto-detection is what makes Bazzite the easy path: the host follows the box between Gaming Mode
|
||||
> and the Desktop — even mid-stream — with a one-line config. You don't need any of the
|
||||
> headless-KDE bring-up scripts (`scripts/headless/run-headless-kde.sh`) on Bazzite.
|
||||
|
||||
---
|
||||
|
||||
## 5. Enable and start the service
|
||||
|
||||
punktfunk runs as a **systemd `--user` service** (not root) — it needs your graphical/user
|
||||
session's PipeWire and D-Bus. The unit (`scripts/punktfunk-host.service`) is installed by the RPM
|
||||
into the user unit directory.
|
||||
|
||||
```sh
|
||||
systemctl --user daemon-reload
|
||||
systemctl --user enable --now punktfunk-host
|
||||
# Management web console (pairing + status), if you installed punktfunk-web (it ships in the Gitea
|
||||
# RPM registry / bootc image — COPR can't build it; see ../rpm/README.md). Read the login password:
|
||||
systemctl --user enable --now punktfunk-web
|
||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p' # then open https://<host-ip>:47992
|
||||
```
|
||||
|
||||
Check health and logs:
|
||||
|
||||
```sh
|
||||
systemctl --user status punktfunk-host
|
||||
journalctl --user -u punktfunk-host -f
|
||||
```
|
||||
|
||||
> **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
|
||||
> --gamestream` (or bare `serve` for a secure native-only host).
|
||||
|
||||
---
|
||||
|
||||
## 6. Firewall
|
||||
|
||||
Bazzite runs **firewalld**, so the ports must be opened. The `punktfunk-host` package installs
|
||||
firewalld **service definitions** (`/usr/lib/firewalld/services/punktfunk-gamestream.xml` and
|
||||
`punktfunk-native.xml`), so enabling is one command — reload first so firewalld picks up the
|
||||
definition, add the service, reload to apply:
|
||||
|
||||
```sh
|
||||
sudo firewall-cmd --reload
|
||||
sudo firewall-cmd --permanent --add-service=punktfunk-gamestream # Moonlight/GameStream host
|
||||
# --add-service=punktfunk-native # …or the native-only host
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
`punktfunk-gamestream` opens the fixed Moonlight ports + mDNS; `punktfunk-native` opens the QUIC
|
||||
control port (UDP 9777) + mDNS + the mgmt/library API (TCP 47990, HTTPS + mTLS). Enable both if the
|
||||
host runs `serve --gamestream` (both planes). The
|
||||
per-port breakdown below is for reference (or for opening ports by hand); the ports are the code
|
||||
constants (`crates/punktfunk-host/src/gamestream/mod.rs`, `mgmt.rs`) and the GameStream-host port-map
|
||||
(punktfunk-planning: `gamestream-host-plan.md`).
|
||||
|
||||
**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 |
|
||||
|---|---|---|
|
||||
| 47984 | TCP | HTTPS nvhttp (paired, mutual-TLS) |
|
||||
| 47989 | TCP | HTTP nvhttp (`/serverinfo`, `/pair` PIN flow) |
|
||||
| 48010 | TCP | RTSP handshake |
|
||||
| 47998 | UDP | Video RTP (+ FEC) |
|
||||
| 47999 | UDP | ENet control stream + remote input |
|
||||
| 48000 | UDP | Audio (Opus) |
|
||||
| 5353 | UDP | mDNS — so Moonlight auto-discovers the host (`_nvstream._tcp.local.`) |
|
||||
|
||||
**Management REST API:** **TCP 47990** (HTTPS + mTLS) — `serve` **binds it to all interfaces by
|
||||
default** so paired clients can browse the game library over the LAN; the `punktfunk-native` profile
|
||||
(above) opens it, or open it by hand with `firewall-cmd --add-port=47990/tcp`. Off-loopback it serves
|
||||
only read-only status/library to a paired client cert; every admin action stays loopback-only. Pass
|
||||
`--mgmt-bind 127.0.0.1:47990` to keep it loopback-only instead.
|
||||
|
||||
To open the GameStream ports by hand instead of the service (equivalent):
|
||||
|
||||
```sh
|
||||
sudo firewall-cmd --permanent --add-port=47984/tcp \
|
||||
--add-port=47989/tcp \
|
||||
--add-port=48010/tcp
|
||||
sudo firewall-cmd --permanent --add-port=47998/udp \
|
||||
--add-port=47999/udp \
|
||||
--add-port=48000/udp \
|
||||
--add-port=5353/udp
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
**If you also run the native `punktfunk/1` host** (`punktfunk-host punktfunk1-host`, not started by the
|
||||
default unit):
|
||||
|
||||
- **QUIC control plane: UDP 9777** (default `--port`; change with `--port N`).
|
||||
- **Data plane: a separate UDP port** — by default *random* (`0.0.0.0:0`), so there is **no fixed
|
||||
port to open**. Video flows host → client, but the client sends the first packet (a hole-punch): if
|
||||
firewalld drops it, the host waits ~2.5 s and falls back to the client-reported address and streams
|
||||
anyway, so you normally **leave the data port closed**. To skip that ~2.5 s fallback, pin it with
|
||||
`serve --data-port <PORT>` (or `PUNKTFUNK_DATA_PORT`) and open exactly that one port with
|
||||
`firewall-cmd --add-port=<PORT>/udp`. A fixed port serves one session at a time (concurrent ones
|
||||
fall back to random + hole-punch) and streams to the client's reported address (flat LAN /
|
||||
non-remapping forward only).
|
||||
|
||||
```sh
|
||||
# Only if you run `punktfunk1-host`:
|
||||
sudo firewall-cmd --permanent --add-port=9777/udp && sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6.5 Desktop (KDE) mode — stream the desktop at the client's resolution (optional)
|
||||
|
||||
The host **auto-detects** the live session per connect: in **Steam Gaming Mode** it attaches to the
|
||||
running gamescope (no setup); switch the box to the **KDE Desktop** and it drives a KWin *virtual
|
||||
output at the connecting client's exact resolution* (no TV-stretch, churn-free). The Desktop path
|
||||
needs one one-shot setup the first time, because a normal KDE login withholds two things the
|
||||
headless host needs — the privileged `zkde_screencast` virtual-output protocol, and an
|
||||
auto-approved RemoteDesktop input grant:
|
||||
|
||||
```sh
|
||||
bash /usr/share/punktfunk/bazzite/kde-desktop-setup.sh
|
||||
# then log out + back into the KDE Desktop session once (or reboot) so KWin restarts with the flag
|
||||
```
|
||||
|
||||
That seeds the `kde-authorized` RemoteDesktop grant into `~/.local/share/flatpak/db/` — the input
|
||||
half. The **video** half needs no session-wide override: the image ships
|
||||
`io.unom.Punktfunk.Host.desktop`, whose `X-KDE-Wayland-Interfaces` grants the host KWin's
|
||||
`zkde_screencast` protocol on a normal Plasma login (least-privilege — only this binary, only that
|
||||
interface). Older versions of the script wrote a session-wide
|
||||
`KWIN_WAYLAND_NO_PERMISSION_CHECKS=1` into `~/.config/environment.d/10-punktfunk-kwin.conf`; it now
|
||||
*removes* that file as an over-broad leftover. Gaming Mode is unaffected. To connect from Desktop
|
||||
Mode, switch to it (Steam → Power → Switch to Desktop), then connect the client; switching
|
||||
**mid-stream** requires a reconnect (the host resolves the backend per connect).
|
||||
|
||||
> **On 0.26.0-1 specifically, Desktop mode is broken and no amount of this setup fixes it.** That
|
||||
> image shipped `cap_sys_nice=ep` on `/usr/bin/punktfunk-host`, and a capability-carrying process is
|
||||
> one KWin cannot identify (it resolves `/proc/<pid>/exe` to match the `.desktop`, and the kernel
|
||||
> refuses that read), so the session dies with `KWin does not expose zkde_screencast_unstable_v1 to
|
||||
> this client`. A merged sysext's `/usr` is read-only, so it cannot be repaired in place — take the
|
||||
> next image (`sudo punktfunk-sysext update`). `KWIN_WAYLAND_NO_PERMISSION_CHECKS=1` works around it
|
||||
> meanwhile by disabling the check that needs the identification.
|
||||
|
||||
---
|
||||
|
||||
## 7. Verify it's working
|
||||
|
||||
**1. Watch the startup log:**
|
||||
|
||||
```sh
|
||||
journalctl --user -u punktfunk-host -f
|
||||
```
|
||||
|
||||
A healthy `serve` startup logs the `punktfunk-host (punktfunk_core ABI v…)` banner, then `mDNS
|
||||
advertising`, and an RTSP listening line on port 48010. No NVENC/EGL errors on the first connection.
|
||||
|
||||
**2. Pair a stock Moonlight client (recommended first test):**
|
||||
|
||||
- Open Moonlight on your phone/PC on the **same LAN** — the host should appear automatically (mDNS).
|
||||
- Select it; Moonlight shows a 4-digit PIN. The host completes the GameStream pairing handshake (it
|
||||
persists across restarts).
|
||||
- Launch the app — you should get video at your client's native resolution/refresh, with the nested
|
||||
`steam -gamepadui` (or whatever `PUNKTFUNK_GAMESCOPE_APP` you set) running inside gamescope.
|
||||
|
||||
**3. (Optional) native punktfunk/1 client** — only if you're running the separate `punktfunk1-host`. The
|
||||
repo's reference client is `punktfunk-probe`, e.g. `punktfunk-probe --mode 1280x720x120 --out
|
||||
/tmp/a.h265` (add `--pin HEX` for PIN pairing). This is a headless/decode-to-file reference, not a
|
||||
desktop viewer.
|
||||
|
||||
---
|
||||
|
||||
## 8. Troubleshooting (grounded in the repo's real gotchas)
|
||||
|
||||
- **Gamepads don't appear / permission denied on `/dev/uinput` or `/dev/uhid`.** Either you haven't
|
||||
joined the `input` group, or you haven't re-logged-in since. On Bazzite join it with
|
||||
`ujust add-user-to-input-group` (a plain `sudo usermod -aG input` doesn't stick on an atomic OS —
|
||||
see section 3), then log out and back in (or reboot): group membership only takes effect on a new
|
||||
session. The host log makes this unambiguous — it prints `virtual gamepad created` /
|
||||
`virtual DualSense created` on success, or `… creation failed — controller input disabled` when
|
||||
the device node isn't writable. (`scripts/60-punktfunk.rules`, `packaging/README.md`.)
|
||||
|
||||
- **No video / NVENC fails to encode.** RPM Fusion's `ffmpeg-libs` (with NVENC) is missing — it's a
|
||||
weak dependency, so the package installed without it. Re-run the RPM Fusion step in section 1.
|
||||
- **No video / NVENC fails to encode.** RPM Fusion's `ffmpeg-libs` (with NVENC) is a **weak
|
||||
dependency** (`Recommends:` in the spec) — the package installs without it, and NVENC then
|
||||
fails at runtime. Re-run the RPM Fusion step in Path C step 1.
|
||||
(`packaging/rpm/punktfunk.spec`: `Recommends: ffmpeg-libs`.)
|
||||
|
||||
- **gamescope session won't come up / capture deadlocks.** punktfunk needs **gamescope ≥ 3.16.22** —
|
||||
older versions (e.g. the broken 3.16.20 some bases shipped) **deadlock on PipeWire ≥ 1.6**, and a
|
||||
wedged capture link can head-block the whole PipeWire daemon system-wide. Check with `gamescope
|
||||
--version`. Bazzite tracks recent gamescope, but verify if you hit hangs. (Project notes.)
|
||||
|
||||
- **NVENC/EGL silently stops working after a system update.** punktfunk's reference box uses the
|
||||
NVIDIA **open** kernel module, and a kernel update can silently drop it. On Bazzite the NVIDIA
|
||||
stack is image-managed (`bazzite-nvidia`), so this is **far less likely** — but if NVENC dies right
|
||||
after an `rpm-ostree`/`bootc` update, confirm the NVIDIA driver still loads (`nvidia-smi`) before
|
||||
blaming punktfunk.
|
||||
|
||||
- **Zero-copy falls back to CPU.** The zero-copy path (on by default) needs working EGL/CUDA from the
|
||||
NVIDIA driver. The code falls back to CPU automatically; check the log for the fallback line and
|
||||
verify the `-nvidia` image / driver is healthy.
|
||||
|
||||
- **Session anchors in `host.env`.** The template no longer sets `XDG_RUNTIME_DIR` /
|
||||
`DBUS_SESSION_BUS_ADDRESS` — a `systemctl --user` service inherits the right values. If an older
|
||||
config hardcodes them with the wrong uid (`/run/user/1000` when `id -u` isn't 1000), the host
|
||||
points at another user's PipeWire/D-Bus and everything fails (`pw audio connect … Creation
|
||||
failed`, no capture). Delete both lines, or fix the uid.
|
||||
|
||||
- **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 --gamestream` (or bare `serve` for native-only) if needed
|
||||
(section 5).
|
||||
- **Dev-layout unit.** `scripts/punktfunk-host.service` (the upstream/dev unit) assumes the binary
|
||||
at `%h/punktfunk/target/release/punktfunk-host`; the packaged binary is `/usr/bin/punktfunk-host`.
|
||||
If `systemctl --user cat punktfunk-host` shows `ExecStart` pointing into a home dir, drop an
|
||||
override (`systemctl --user edit punktfunk-host`) setting
|
||||
`ExecStart=/usr/bin/punktfunk-host serve --gamestream` (or bare `serve` for native-only).
|
||||
|
||||
- **Stream lags, then freezes, with a DualSense-type client pad (SELinux enforcing).** The virtual
|
||||
DualSense / DualShock 4 binds the kernel's `hid-playstation` driver, and Valve's `ds_inhibit`
|
||||
@@ -527,11 +185,6 @@ desktop viewer.
|
||||
no `hid-playstation`) — costs adaptive triggers, lightbar and touchpad. The host-side
|
||||
`PUNKTFUNK_GAMEPAD` knob does **not** help: an explicit client choice outranks it.
|
||||
|
||||
- **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.
|
||||
|
||||
---
|
||||
|
||||
## Appendix — if the COPR isn't published yet
|
||||
|
||||
The COPR (`enricobuehler/punktfunk`) is **operator-run and may not be live**. If `rpm-ostree install
|
||||
@@ -549,16 +202,3 @@ To publish the COPR for others (so `rpm-ostree install punktfunk` / the bootc im
|
||||
`packaging/copr/README.md` — create the project, point build-from-SCM at the repo with spec path
|
||||
`packaging/rpm/punktfunk.spec`, add RPM Fusion nonfree as an external repo, and select chroots
|
||||
matching your Bazzite Fedora base (`rpm -E %fedora`).
|
||||
|
||||
---
|
||||
|
||||
### Accuracy flags
|
||||
|
||||
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 `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) **binds all interfaces by default** and is opened for paired clients by the
|
||||
`punktfunk-native` profile (game-library browsing over mTLS); off-loopback it serves only read-only
|
||||
status/library and keeps admin loopback-only. `--mgmt-bind 127.0.0.1:47990` restores loopback-only.
|
||||
|
||||
+16
-102
@@ -80,112 +80,26 @@ Two things make the one package serve both Debian 13 and Ubuntu 26.04:
|
||||
|
||||
**Ubuntu 24.04 gets no gamescope package** and cannot: the wayland floor is a runtime one too.
|
||||
|
||||
## Install on a host (one-time)
|
||||
## Install on a host
|
||||
|
||||
The registry is public, so no apt auth is needed — just trust the repo's signing key:
|
||||
|
||||
```sh
|
||||
sudo install -d -m 0755 /etc/apt/keyrings
|
||||
curl -fsSL https://git.unom.io/api/packages/unom/debian/repository.key \
|
||||
| sudo tee /etc/apt/keyrings/punktfunk.asc >/dev/null
|
||||
|
||||
echo "deb [signed-by=/etc/apt/keyrings/punktfunk.asc] https://git.unom.io/api/packages/unom/debian stable main" \
|
||||
| sudo tee /etc/apt/sources.list.d/punktfunk.list
|
||||
|
||||
sudo apt update
|
||||
sudo apt install punktfunk-host
|
||||
```
|
||||
|
||||
Then, as the desktop user:
|
||||
|
||||
```sh
|
||||
sudo usermod -aG input "$USER" # virtual gamepads (re-login to take effect)
|
||||
mkdir -p ~/.config/punktfunk
|
||||
cp /usr/share/punktfunk-host/host.env.example ~/.config/punktfunk/host.env # then edit
|
||||
systemctl --user enable --now punktfunk-host
|
||||
# Web console — enable it and read the auto-generated login password (then open https://<host-ip>:47992):
|
||||
systemctl --user enable --now punktfunk-web
|
||||
journalctl --user -u punktfunk-web-init | sed -n 's/.*password generated: //p'
|
||||
```
|
||||
The user-facing walkthrough — repo key, sources line, `apt install`, first-run setup — lives on
|
||||
the docs pages ([Debian](https://docs.punktfunk.unom.io/docs/debian) /
|
||||
[Ubuntu](https://docs.punktfunk.unom.io/docs/ubuntu)), stated once so it can't drift (see "Where
|
||||
facts live" in [`CONTRIBUTING.md`](../../CONTRIBUTING.md)). Packager-relevant: the registry is
|
||||
public (no apt auth — only the repo's signing key), and `stable` / `canary` are separate apt
|
||||
distributions, so a stable box never jumps to a canary build.
|
||||
|
||||
## Firewall
|
||||
|
||||
**Debian ships no firewall and Ubuntu's `ufw` is installed-but-inactive by default**, so out of the
|
||||
box there is nothing to open. If you turn one on, the `punktfunk-host` package ships a one-liner
|
||||
opener for both **ufw** and **firewalld** (neither auto-enabled):
|
||||
|
||||
```sh
|
||||
# ufw (Ubuntu) — profile at /etc/ufw/applications.d/punktfunk, read at once (no reload):
|
||||
sudo ufw allow punktfunk-native # the default native host
|
||||
sudo ufw allow punktfunk-gamestream # …add for Moonlight compat
|
||||
|
||||
# firewalld — service definitions at /usr/lib/firewalld/services/:
|
||||
sudo firewall-cmd --reload # load the installed definition
|
||||
sudo firewall-cmd --permanent --add-service=punktfunk-native
|
||||
# --add-service=punktfunk-gamestream # …add for Moonlight compat
|
||||
sudo firewall-cmd --reload
|
||||
```
|
||||
|
||||
If you installed the **web console** (`punktfunk-web`) and want it reachable from another device,
|
||||
open its port with the matching one-liner — `sudo ufw allow punktfunk-web` or `sudo firewall-cmd
|
||||
--permanent --add-service=punktfunk-web && sudo firewall-cmd --reload` — which opens **TCP 47992**
|
||||
(HTTPS, login-gated). The mgmt API (47990) is opened for paired clients by the `punktfunk-native`
|
||||
profile (game-library browsing over mTLS); off-loopback it serves only read-only status/library and
|
||||
keeps admin loopback-only.
|
||||
|
||||
Prefer explicit rules? Open the ports directly. The **native `punktfunk/1`** plane:
|
||||
|
||||
- **QUIC control plane: UDP 9777** (`serve --native-port N` to change).
|
||||
- **Data plane: a separate UDP port.** By default it's *random* — the host binds `0.0.0.0:0` and
|
||||
tells the client which port it got. Video flows host → client, but the **client sends the first
|
||||
packet** (a hole-punch), so the host learns the client's real source and streams back — this
|
||||
traverses NAT / inter-VLAN with no forwarded port. **You normally don't open it:** if a deny-inbound
|
||||
firewall drops the punch, the host waits ~2.5 s and falls back to the client-reported address, and a
|
||||
stateful firewall then admits the return (it just adds ~2.5 s to session start). To skip that delay,
|
||||
pin it with **`serve --data-port <PORT>`** (or `PUNKTFUNK_DATA_PORT`): the host binds that fixed
|
||||
port and streams direct (no punch-wait) — open exactly that one port. A fixed port serves one
|
||||
session at a time (concurrent ones fall back to random + hole-punch), and direct mode needs the
|
||||
client's reported address to be reachable (flat LAN / a non-remapping port-forward).
|
||||
|
||||
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 |
|
||||
|---|---|---|
|
||||
| 47984 | TCP | HTTPS nvhttp (paired, mutual-TLS) |
|
||||
| 47989 | TCP | HTTP nvhttp (`/serverinfo`, `/pair` PIN flow) |
|
||||
| 48010 | TCP | RTSP handshake |
|
||||
| 47998–48010 | UDP | Video RTP (+ FEC), ENet control (47999), audio (48000) |
|
||||
| 5353 | UDP | mDNS auto-discovery |
|
||||
|
||||
The mgmt API (TCP 47990, HTTPS + mTLS) binds all interfaces by default so paired clients can browse the
|
||||
game library — the `punktfunk-native` profile opens it. Off-loopback it serves only read-only
|
||||
status/library to a paired client cert; the admin surface stays loopback-only. Pass
|
||||
`--mgmt-bind 127.0.0.1:47990` to keep it loopback-only (then leave 47990 closed).
|
||||
|
||||
With `ufw` (explicit ports, instead of the shipped profile):
|
||||
|
||||
```sh
|
||||
sudo ufw allow 9777/udp # punktfunk/1 control plane
|
||||
sudo ufw allow 47990/tcp # mgmt/library API (HTTPS + mTLS; LAN = read-only, paired)
|
||||
sudo ufw allow 47984/tcp && sudo ufw allow 47989/tcp && sudo ufw allow 48010/tcp
|
||||
sudo ufw allow 47998,47999,48000/udp # GameStream video/control/audio
|
||||
sudo ufw allow 5353/udp # mDNS discovery
|
||||
# The punktfunk/1 data plane uses a random UDP port; leave it closed on a LAN — the host hole-punches
|
||||
# and falls back (~2.5s at session start if firewalled). To skip that, pin it: `serve --data-port
|
||||
# 9778` and `ufw allow 9778/udp`.
|
||||
```
|
||||
|
||||
With raw `nftables` (add to your `inet filter input` chain):
|
||||
|
||||
```
|
||||
udp dport 9777 accept # punktfunk/1 control plane
|
||||
tcp dport 47990 accept # mgmt/library API (HTTPS + mTLS; LAN = read-only, paired)
|
||||
tcp dport { 47984, 47989, 48010 } accept
|
||||
udp dport { 47998-48010, 5353 } accept
|
||||
# The punktfunk/1 data plane is a random UDP port — normally left closed (hole-punch + ~2.5s
|
||||
# fallback). Pin it with `serve --data-port <PORT>` to open exactly one instead.
|
||||
```
|
||||
Debian ships no firewall and Ubuntu's `ufw` is installed-but-inactive by default, so out of the
|
||||
box there is nothing to open. The package ships openers for both — ufw profiles at
|
||||
`/etc/ufw/applications.d/punktfunk` and firewalld service definitions at
|
||||
`/usr/lib/firewalld/services/` (neither auto-enabled). The commands, per-port breakdown and the
|
||||
data-plane hole-punch behavior are documented once:
|
||||
[Debian](https://docs.punktfunk.unom.io/docs/debian) for the openers, port facts in
|
||||
[`data/platforms.json`](../../data/platforms.json) and on
|
||||
[Moonlight](https://docs.punktfunk.unom.io/docs/moonlight), hole-punch mechanics in
|
||||
[Troubleshooting](https://docs.punktfunk.unom.io/docs/troubleshooting).
|
||||
|
||||
## Updates
|
||||
|
||||
|
||||
@@ -33,26 +33,12 @@ flatpak fallback).
|
||||
|
||||
## Install (recommended): the hosted repo
|
||||
|
||||
One command adds the signed `unom` remote and installs the client; it auto-adds Flathub for the
|
||||
GNOME runtime, and `flatpak update` tracks new builds from then on:
|
||||
|
||||
```sh
|
||||
flatpak install --user https://flatpak.unom.io/io.unom.Punktfunk.flatpakref
|
||||
flatpak run io.unom.Punktfunk
|
||||
```
|
||||
|
||||
Equivalent two-step (add the whole remote, then install by app id):
|
||||
|
||||
```sh
|
||||
flatpak remote-add --user --if-not-exists unom https://flatpak.unom.io/unom.flatpakrepo
|
||||
flatpak install --user unom io.unom.Punktfunk
|
||||
```
|
||||
|
||||
Updates — the whole point of the hosted repo:
|
||||
|
||||
```sh
|
||||
flatpak update # or: flatpak update io.unom.Punktfunk
|
||||
```
|
||||
The user-facing walkthrough (the `.flatpakref` one-liner, updates) lives on the docs pages —
|
||||
[Install a client](https://docs.punktfunk.unom.io/docs/install-client) /
|
||||
[Steam Deck](https://docs.punktfunk.unom.io/docs/steam-deck) — stated once so it can't drift (see
|
||||
"Where facts live" in [`CONTRIBUTING.md`](../../CONTRIBUTING.md)). Mechanically: the flatpakref
|
||||
adds the signed `unom` remote, auto-adds Flathub for the GNOME runtime, and `flatpak update`
|
||||
tracks new builds from then on.
|
||||
|
||||
## Install on the Deck via the bundle (no-remote fallback)
|
||||
|
||||
|
||||
+11
-21
@@ -17,28 +17,18 @@ This is the same package as the [COPR](../copr/README.md) / [bootc](../bootc/Con
|
||||
paths — same spec (`punktfunk.spec`) — just self-hosted in Gitea instead of COPR, mirroring the
|
||||
[Debian/apt](../debian/README.md) setup.
|
||||
|
||||
## Install on a Bazzite host (one-time)
|
||||
## Install on a host (one-time)
|
||||
|
||||
```sh
|
||||
# Add the repo. Packages are GPG-signed (gpgcheck=1, the packages@unom.io key) AND the repo
|
||||
# metadata is Gitea-signed (repo_gpgcheck=1); gpgkey lists both so dnf/rpm-ostree imports each.
|
||||
sudo tee /etc/yum.repos.d/punktfunk.repo >/dev/null <<'REPO'
|
||||
[gitea-unom-bazzite]
|
||||
name=punktfunk (unom, Bazzite)
|
||||
baseurl=https://git.unom.io/api/packages/unom/rpm/bazzite
|
||||
enabled=1
|
||||
gpgcheck=1
|
||||
repo_gpgcheck=1
|
||||
gpgkey=https://git.unom.io/api/packages/unom/rpm/repository.key
|
||||
https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk
|
||||
REPO
|
||||
|
||||
# Layer the host + the web console (pairing/status), then reboot into the new deployment.
|
||||
# (punktfunk Recommends punktfunk-web; list it explicitly so it's pulled regardless of weak-dep
|
||||
# settings. The registry carries punktfunk-web because CI builds the spec --with web; COPR can't.)
|
||||
rpm-ostree install punktfunk punktfunk-web
|
||||
systemctl reboot
|
||||
```
|
||||
The user-facing walkthrough — the `.repo` file (same shape for the `fedora-NN` and `bazzite`
|
||||
baseurl groups) and the install command — lives on the docs pages
|
||||
([Fedora](https://docs.punktfunk.unom.io/docs/fedora) /
|
||||
[Bazzite](https://docs.punktfunk.unom.io/docs/bazzite), where the sysext, not layering, is the
|
||||
supported default), stated once so it can't drift (see "Where facts live" in
|
||||
[`CONTRIBUTING.md`](../../CONTRIBUTING.md)). Packager notes: packages are GPG-signed
|
||||
(`gpgcheck=1`, the packages@unom.io key) AND the repo metadata is Gitea-signed
|
||||
(`repo_gpgcheck=1`) — the `gpgkey` line lists both so dnf/rpm-ostree imports each; on a layered
|
||||
box list `punktfunk-web` explicitly (weak-dep settings vary), and the registry — not COPR —
|
||||
carries it because CI builds the spec `--with web` (COPR's chroot has no `bun`).
|
||||
|
||||
> If `rpm-ostree` can't complete the metadata GPG check non-interactively, set `repo_gpgcheck=0`
|
||||
> (TLS-only trust to the self-hosted registry).
|
||||
|
||||
Executable
+102
@@ -0,0 +1,102 @@
|
||||
#!/bin/sh
|
||||
# Docs drift gates (docs-and-onboarding-overhaul WP1). The docs rotted through four unchecked
|
||||
# duplication channels; these are the cheap textual halves of closing them. No cargo, no bun
|
||||
# install — pure git grep, so the gate runs on every push in seconds (the deep half, regenerating
|
||||
# the OpenAPI spec from the host binary, lives in ci.yml's `rust` job where the build already
|
||||
# exists).
|
||||
#
|
||||
# 1. docs-site/public/openapi.json must be a byte-for-byte copy of api/openapi.json. The
|
||||
# snapshot is a manual `cp` (docs-site/README.md); it sat stale on main for weeks once,
|
||||
# publishing an /api reference missing the whole self-update surface.
|
||||
# 2. Every PUNKTFUNK_* variable the docs mention must still exist somewhere in the tree
|
||||
# (docs describing a removed knob is drift a reader pays for). Historical records —
|
||||
# docs/releases/, CHANGELOG.md — don't count as existence: a knob that lives only in old
|
||||
# release notes is gone.
|
||||
# 3. Ratchet: the count of PUNKTFUNK_* env vars in code that the docs never mention must not
|
||||
# grow. 300+ internal/debug knobs are deliberately undocumented today, so this can't be a
|
||||
# hard list — but a NEW knob must either be documented in docs-site (configuration.md or
|
||||
# the page owning its feature) or the baseline below raised in the same commit, making
|
||||
# "undocumented" a decision instead of an accident. Shrink the gap? Lower the baseline.
|
||||
# 4. Every command the host-cli.md tables list must still exist as a string literal in
|
||||
# crates/punktfunk-host (same "docs describing removed things" class as gate 2).
|
||||
# 5. data/platforms.json (the single source for install/port facts that docs, the website
|
||||
# download page and the guided installer consume) must parse.
|
||||
#
|
||||
# Textual gates, so textual limits: gate 2/3 match token spelling, not env reads — a var name in
|
||||
# a code comment counts as "exists", and a quoted constant that isn't an env var counts toward
|
||||
# the ratchet. Both err toward false calm on removal and a one-line baseline bump on addition,
|
||||
# which is the cheap side to be wrong on.
|
||||
|
||||
set -u
|
||||
LC_ALL=C
|
||||
export LC_ALL
|
||||
cd "$(dirname "$0")/../.." || exit 2
|
||||
|
||||
fail=0
|
||||
tmp="${TMPDIR:-/tmp}/docs-drift.$$"
|
||||
mkdir -p "$tmp"
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
|
||||
# ---------------------------------------------------------------- gate 1: openapi snapshot
|
||||
if [ "$(cksum < api/openapi.json)" != "$(cksum < docs-site/public/openapi.json)" ]; then
|
||||
echo "::error::docs-site/public/openapi.json is not a copy of api/openapi.json — re-sync it:"
|
||||
echo " cp api/openapi.json docs-site/public/openapi.json"
|
||||
fail=1
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------- gate 2: docs env vars exist
|
||||
git grep -ohE 'PUNKTFUNK_[A-Z0-9_]+' -- docs-site/content | sort -u > "$tmp/docs-vars"
|
||||
while IFS= read -r var; do
|
||||
if ! git grep -qF "$var" -- ':!docs-site' ':!docs/releases' ':!CHANGELOG.md'; then
|
||||
echo "::error::docs-site documents $var but nothing outside the docs mentions it — the knob was removed or renamed; fix the docs page"
|
||||
fail=1
|
||||
fi
|
||||
done < "$tmp/docs-vars"
|
||||
|
||||
# ---------------------------------------------------------------- gate 3: undocumented ratchet
|
||||
# Quoted occurrences only: env reads are string literals ("PUNKTFUNK_X"); bare identifiers are
|
||||
# Rust constants / C symbols, not knobs. The committed baseline enumerates today's undocumented
|
||||
# set so a violation names exactly the new knob.
|
||||
baseline=scripts/ci/docs-undocumented-env-baseline.txt
|
||||
sort -u "$baseline" > "$tmp/baseline" # re-sort under our LC_ALL=C, whatever locale wrote it
|
||||
git grep -ohE '"PUNKTFUNK_[A-Z0-9_]+"' -- ':!docs-site' ':!*.md' | tr -d '"' | sort -u > "$tmp/code-vars"
|
||||
comm -23 "$tmp/code-vars" "$tmp/docs-vars" > "$tmp/undocumented"
|
||||
comm -23 "$tmp/undocumented" "$tmp/baseline" > "$tmp/new-undocumented"
|
||||
if [ -s "$tmp/new-undocumented" ]; then
|
||||
echo "::error::new PUNKTFUNK_* vars are neither documented in docs-site nor in the baseline:"
|
||||
sed 's/^/ /' "$tmp/new-undocumented"
|
||||
echo "Document each in docs-site (configuration.md or the page owning the feature), or — for a deliberately internal knob — add it to $baseline in the same commit."
|
||||
fail=1
|
||||
fi
|
||||
comm -13 "$tmp/undocumented" "$tmp/baseline" > "$tmp/stale-baseline"
|
||||
if [ -s "$tmp/stale-baseline" ]; then
|
||||
echo "baseline entries no longer undocumented (removed or now documented) — prune them from $baseline:"
|
||||
sed 's/^/ /' "$tmp/stale-baseline"
|
||||
fi
|
||||
|
||||
# ---------------------------------------------------------------- gate 4: documented CLI exists
|
||||
# First table cell of every row in host-cli.md: subcommands, sub-actions and flags. Multi-word
|
||||
# cells (flag + argument) are skipped — they don't map to one string literal.
|
||||
grep -E '^\|' docs-site/content/docs/host-cli.md | awk -F'|' '{print $2}' \
|
||||
| grep -oE '`[a-z0-9-]+`|`--[a-z-]+`' | tr -d '`' | sort -u > "$tmp/cli-cmds"
|
||||
while IFS= read -r cmd; do
|
||||
if ! git grep -qF "\"$cmd\"" -- crates/punktfunk-host; then
|
||||
echo "::error::host-cli.md documents \`$cmd\` but crates/punktfunk-host has no \"$cmd\" literal — removed or renamed; fix the docs page"
|
||||
fail=1
|
||||
fi
|
||||
done < "$tmp/cli-cmds"
|
||||
|
||||
# ---------------------------------------------------------------- gate 5: platforms.json parses
|
||||
# Explicit try/exit: `bun -e` (1.3.x) exits 0 on an uncaught JSON.parse throw, so relying on
|
||||
# the default uncaught-exception exit code silently disarms the gate.
|
||||
json_check='try{JSON.parse(require("fs").readFileSync("data/platforms.json","utf8"))}catch(e){console.error(e.message);process.exit(1)}'
|
||||
if [ ! -f data/platforms.json ]; then
|
||||
echo "::error::data/platforms.json is missing"
|
||||
fail=1
|
||||
elif command -v bun >/dev/null 2>&1; then
|
||||
bun -e "$json_check" || { echo "::error::data/platforms.json is not valid JSON"; fail=1; }
|
||||
elif command -v node >/dev/null 2>&1; then
|
||||
node -e "$json_check" || { echo "::error::data/platforms.json is not valid JSON"; fail=1; }
|
||||
fi
|
||||
|
||||
exit "$fail"
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/bin/sh
|
||||
# Internal-link checker (docs-and-onboarding-overhaul WP1). Two classes, both cheap and exact:
|
||||
#
|
||||
# 1. Docs-site pages linking other docs-site pages: every `](/docs/<slug>…)` markdown link and
|
||||
# `href="/docs/<slug>…"` attribute in docs-site/content must resolve to a content page.
|
||||
# Fumadocs 404s these at runtime only — a renamed page leaves silent dead links behind.
|
||||
# 2. Relative file links in the repo's markdown (READMEs, CONTRIBUTING, docs/): the target file
|
||||
# must exist in the tree. Same failure mode: a moved file, a dead link, no CI signal.
|
||||
#
|
||||
# Deliberately NOT checked: external URLs (flaky third-party servers must not gate pushes),
|
||||
# `#anchor` fragments (heading anchors need a markdown renderer to verify; ponytail: add if dead
|
||||
# anchors actually bite), and site-absolute non-/docs paths like /api (three of them, all routes
|
||||
# in docs-site/src). Historical records — docs/releases/, CHANGELOG.md — are exempt from class 2:
|
||||
# they describe the tree as it was.
|
||||
|
||||
set -u
|
||||
LC_ALL=C
|
||||
export LC_ALL
|
||||
cd "$(dirname "$0")/../.." || exit 2
|
||||
|
||||
fail=0
|
||||
tmp="${TMPDIR:-/tmp}/docs-links.$$"
|
||||
mkdir -p "$tmp"
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
|
||||
# ---------------------------------------------------------------- class 1: /docs/* page links
|
||||
git ls-files 'docs-site/content/docs' > "$tmp/pages"
|
||||
git grep -ohE '\]\(/docs[^)]*\)|href="/docs[^"]*"' -- docs-site/content \
|
||||
| sed -e 's/^](\(.*\))$/\1/' -e 's/^href="\(.*\)"$/\1/' \
|
||||
| sed -e 's/[#?].*$//' | sort -u > "$tmp/links"
|
||||
while IFS= read -r link; do
|
||||
slug=${link#/docs}
|
||||
slug=${slug#/}
|
||||
slug=${slug%/}
|
||||
if [ -z "$slug" ]; then
|
||||
target="docs-site/content/docs/index"
|
||||
else
|
||||
target="docs-site/content/docs/$slug"
|
||||
fi
|
||||
if ! grep -qxE "$target\.(md|mdx)|$target/index\.(md|mdx)" "$tmp/pages"; then
|
||||
echo "::error::dead docs link: $link (no $target.md/.mdx) — referenced from:"
|
||||
git grep -lF "$link" -- docs-site/content | sed 's/^/ /'
|
||||
fail=1
|
||||
fi
|
||||
done < "$tmp/links"
|
||||
|
||||
# ---------------------------------------------------------------- class 2: relative file links
|
||||
# Vendored trees are third-party docs describing their upstream repo, not ours (same exclusion
|
||||
# as check-unsafe-hygiene.sh).
|
||||
git ls-files '*.md' ':!docs-site/content' ':!docs/releases' ':!CHANGELOG.md' \
|
||||
':!clients' ':!*/vendor/*' > "$tmp/mdfiles"
|
||||
while IFS= read -r f; do
|
||||
dir=$(dirname "$f")
|
||||
# Markdown links whose target is a plain relative path: skip URLs (://), mailto:, pure
|
||||
# anchors, site-absolute paths, and anything with spaces or template syntax.
|
||||
grep -oE '\]\([^)]+\)' "$f" 2>/dev/null | sed 's/^](\(.*\))$/\1/' | sed 's/#.*$//' \
|
||||
| grep -vE '^$|://|^mailto:|^/|[ $]' | sort -u > "$tmp/rels" || true
|
||||
while IFS= read -r rel; do
|
||||
if [ ! -e "$dir/$rel" ]; then
|
||||
echo "::error::$f links $rel — no such file"
|
||||
fail=1
|
||||
fi
|
||||
done < "$tmp/rels"
|
||||
done < "$tmp/mdfiles"
|
||||
|
||||
exit "$fail"
|
||||
@@ -0,0 +1,330 @@
|
||||
PUNKTFUNK_444_FULLRANGE
|
||||
PUNKTFUNK_ABI_VERSION
|
||||
PUNKTFUNK_ACCESS_EXPIRED_CLOSE_CODE
|
||||
PUNKTFUNK_AMF_BENCH
|
||||
PUNKTFUNK_AMF_USAGE
|
||||
PUNKTFUNK_APP_EXITED_CLOSE_CODE
|
||||
PUNKTFUNK_AUDIO_BITS_16
|
||||
PUNKTFUNK_AUDIO_BITS_24
|
||||
PUNKTFUNK_AUDIO_CODEC_FLAC_RESERVED
|
||||
PUNKTFUNK_AUDIO_CODEC_OPUS
|
||||
PUNKTFUNK_AUDIO_CODEC_PCM
|
||||
PUNKTFUNK_AUDIO_FRAME_MS
|
||||
PUNKTFUNK_AUDIO_FRAME_US_LADDER
|
||||
PUNKTFUNK_AUDIO_MAGIC
|
||||
PUNKTFUNK_AUDIO_PCM_HEADER
|
||||
PUNKTFUNK_AUDIO_PCM_HEADER_LEN
|
||||
PUNKTFUNK_AUDIO_PCM_MAGIC
|
||||
PUNKTFUNK_AUDIO_RED_HEADER
|
||||
PUNKTFUNK_AUDIO_RED_MAGIC
|
||||
PUNKTFUNK_AUDIO_SAMPLE_RATE_HZ
|
||||
PUNKTFUNK_AUDIO_SINK
|
||||
PUNKTFUNK_AUDIO_SOURCE
|
||||
PUNKTFUNK_AUTOCONNECT
|
||||
PUNKTFUNK_AU_FAULT
|
||||
PUNKTFUNK_AXIS_LS_X
|
||||
PUNKTFUNK_AXIS_LS_Y
|
||||
PUNKTFUNK_AXIS_LT
|
||||
PUNKTFUNK_AXIS_RS_X
|
||||
PUNKTFUNK_AXIS_RS_Y
|
||||
PUNKTFUNK_AXIS_RT
|
||||
PUNKTFUNK_BILINEAR_LUMA
|
||||
PUNKTFUNK_BITRATE_KBPS
|
||||
PUNKTFUNK_BTN_A
|
||||
PUNKTFUNK_BTN_B
|
||||
PUNKTFUNK_BTN_BACK
|
||||
PUNKTFUNK_BTN_DPAD_DOWN
|
||||
PUNKTFUNK_BTN_DPAD_LEFT
|
||||
PUNKTFUNK_BTN_DPAD_RIGHT
|
||||
PUNKTFUNK_BTN_DPAD_UP
|
||||
PUNKTFUNK_BTN_GUIDE
|
||||
PUNKTFUNK_BTN_LB
|
||||
PUNKTFUNK_BTN_LS_CLICK
|
||||
PUNKTFUNK_BTN_MISC1
|
||||
PUNKTFUNK_BTN_PADDLE1
|
||||
PUNKTFUNK_BTN_PADDLE2
|
||||
PUNKTFUNK_BTN_PADDLE3
|
||||
PUNKTFUNK_BTN_PADDLE4
|
||||
PUNKTFUNK_BTN_RB
|
||||
PUNKTFUNK_BTN_RS_CLICK
|
||||
PUNKTFUNK_BTN_START
|
||||
PUNKTFUNK_BTN_TOUCHPAD
|
||||
PUNKTFUNK_BTN_X
|
||||
PUNKTFUNK_BTN_Y
|
||||
PUNKTFUNK_CAPTURE_HDR_PTS
|
||||
PUNKTFUNK_CHROMA_IDC_420
|
||||
PUNKTFUNK_CHROMA_IDC_444
|
||||
PUNKTFUNK_CIPHER_AES_128_GCM
|
||||
PUNKTFUNK_CIPHER_CHACHA20_POLY1305
|
||||
PUNKTFUNK_CLIENT_10BIT
|
||||
PUNKTFUNK_CLIENT_444
|
||||
PUNKTFUNK_CLIENT_CAP_AUDIO_HIRES
|
||||
PUNKTFUNK_CLIENT_CAP_AUDIO_RED
|
||||
PUNKTFUNK_CLIENT_CAP_CURSOR
|
||||
PUNKTFUNK_CLIENT_CAP_PHASE_LOCK
|
||||
PUNKTFUNK_CLIENT_CHACHA20
|
||||
PUNKTFUNK_CLIENT_PEAK_NITS
|
||||
PUNKTFUNK_CLIP_CANCELLED_CODE
|
||||
PUNKTFUNK_CLIP_CHUNK
|
||||
PUNKTFUNK_CLIP_FETCH_CAP
|
||||
PUNKTFUNK_CLIP_FETCH_DENIED
|
||||
PUNKTFUNK_CLIP_FETCH_OK
|
||||
PUNKTFUNK_CLIP_FETCH_STALE
|
||||
PUNKTFUNK_CLIP_FETCH_UNAVAILABLE
|
||||
PUNKTFUNK_CLIP_FILE_INDEX_NONE
|
||||
PUNKTFUNK_CLIP_FLAG_FILES
|
||||
PUNKTFUNK_CLIP_MAX_KINDS
|
||||
PUNKTFUNK_CLIP_MAX_MIME
|
||||
PUNKTFUNK_CLIP_POLICY_FILES
|
||||
PUNKTFUNK_CLIP_POLICY_TEXT
|
||||
PUNKTFUNK_CLIP_REASON_BACKEND_UNAVAILABLE
|
||||
PUNKTFUNK_CLIP_REASON_NOT_PERMITTED
|
||||
PUNKTFUNK_CLIP_REASON_NO_FILES
|
||||
PUNKTFUNK_CLIP_REASON_OK
|
||||
PUNKTFUNK_CLIP_REASON_POLICY_DISABLED
|
||||
PUNKTFUNK_CLIP_REASON_TAKEN_OVER
|
||||
PUNKTFUNK_CLIP_STREAM_KIND_FETCH
|
||||
PUNKTFUNK_CODEC_AV1
|
||||
PUNKTFUNK_CODEC_H264
|
||||
PUNKTFUNK_CODEC_HEVC
|
||||
PUNKTFUNK_CODEC_PYROWAVE
|
||||
PUNKTFUNK_CORE_H
|
||||
PUNKTFUNK_CURSOR_RELATIVE_HINT
|
||||
PUNKTFUNK_CURSOR_SHAPE_MAX_SIDE
|
||||
PUNKTFUNK_CURSOR_STATE_MAGIC
|
||||
PUNKTFUNK_CURSOR_VISIBLE
|
||||
PUNKTFUNK_D3D11_NO_MUTEX
|
||||
PUNKTFUNK_DEBUG_RECONFIGURE
|
||||
PUNKTFUNK_DRAWABLE_COUNT
|
||||
PUNKTFUNK_DRM_CARD
|
||||
PUNKTFUNK_DUALSENSE_USBIP_GRACE_MS
|
||||
PUNKTFUNK_DUMP_VIDEO
|
||||
PUNKTFUNK_FAKE_LIBRARY
|
||||
PUNKTFUNK_FEATURE_QUIC
|
||||
PUNKTFUNK_FFWIN_POLL_MS
|
||||
PUNKTFUNK_FLAG_EOF
|
||||
PUNKTFUNK_FLAG_PIC
|
||||
PUNKTFUNK_FLAG_PROBE
|
||||
PUNKTFUNK_FLAG_SOF
|
||||
PUNKTFUNK_FORCE_GAMEPAD_UI
|
||||
PUNKTFUNK_FORCE_SHM
|
||||
PUNKTFUNK_FRAME_DRIVEN
|
||||
PUNKTFUNK_FRAME_LATENCY
|
||||
PUNKTFUNK_GAMESCOPE_BIND
|
||||
PUNKTFUNK_GAMESCOPE_WSI
|
||||
PUNKTFUNK_GAMESCOPE_WSI_DISABLE
|
||||
PUNKTFUNK_GATE_DEPTH
|
||||
PUNKTFUNK_GRANT_ALL
|
||||
PUNKTFUNK_GRANT_CLIPBOARD
|
||||
PUNKTFUNK_GRANT_GAMEPAD
|
||||
PUNKTFUNK_GRANT_KEYBOARD
|
||||
PUNKTFUNK_GRANT_LAUNCH
|
||||
PUNKTFUNK_GRANT_MIC
|
||||
PUNKTFUNK_GRANT_POINTER
|
||||
PUNKTFUNK_GRANT_PRESET_CONTROLLER_ONLY
|
||||
PUNKTFUNK_GRANT_PRESET_FULL
|
||||
PUNKTFUNK_GRANT_PRESET_VIEW_ONLY
|
||||
PUNKTFUNK_GRANT_RESERVED
|
||||
PUNKTFUNK_GUESS_PIN
|
||||
PUNKTFUNK_GUESS_PORT
|
||||
PUNKTFUNK_HDR10
|
||||
PUNKTFUNK_HDR_META_BODY_LEN
|
||||
PUNKTFUNK_HDR_META_MAGIC
|
||||
PUNKTFUNK_HELLO_LAUNCH_MAX
|
||||
PUNKTFUNK_HELLO_NAME_MAX
|
||||
PUNKTFUNK_HIDOUT_MAGIC
|
||||
PUNKTFUNK_HID_RAW_FEATURE
|
||||
PUNKTFUNK_HID_RAW_OUTPUT
|
||||
PUNKTFUNK_HID_REPORT_MAX
|
||||
PUNKTFUNK_HOST_CAP_AUDIO_HIRES
|
||||
PUNKTFUNK_HOST_CAP_AUDIO_RED
|
||||
PUNKTFUNK_HOST_CAP_CLIPBOARD
|
||||
PUNKTFUNK_HOST_CAP_CURSOR
|
||||
PUNKTFUNK_HOST_CAP_GAMEPAD_STATE
|
||||
PUNKTFUNK_HOST_CAP_PEN
|
||||
PUNKTFUNK_HOST_CAP_TEXT_INPUT
|
||||
PUNKTFUNK_HOST_TIMING_MAGIC
|
||||
PUNKTFUNK_HW_FAULT
|
||||
PUNKTFUNK_IDD_ADAPTIVE
|
||||
PUNKTFUNK_INBOUND_REQ_FLAG
|
||||
PUNKTFUNK_INPUT_DEBUG
|
||||
PUNKTFUNK_INPUT_MAGIC
|
||||
PUNKTFUNK_INPUT_TEST_ABS
|
||||
PUNKTFUNK_INPUT_WIRE_LEN
|
||||
PUNKTFUNK_INTRA_REFRESH
|
||||
PUNKTFUNK_IR_PERIOD_FRAMES
|
||||
PUNKTFUNK_LAUNCH_NOT_PERMITTED_CLOSE_CODE
|
||||
PUNKTFUNK_LEGACY_STALE_MS
|
||||
PUNKTFUNK_LOOPBACK_PORT
|
||||
PUNKTFUNK_LTR_FORCE_AT
|
||||
PUNKTFUNK_LTR_INTERVAL_FRAMES
|
||||
PUNKTFUNK_MAX_DATAGRAM_BYTES
|
||||
PUNKTFUNK_MAX_PADS
|
||||
PUNKTFUNK_MAX_SCALE
|
||||
PUNKTFUNK_MGMT_URL
|
||||
PUNKTFUNK_MIC_ALWAYS_ON
|
||||
PUNKTFUNK_MIC_MAGIC
|
||||
PUNKTFUNK_MIN_SCALE
|
||||
PUNKTFUNK_MIN_SHARD_PAYLOAD
|
||||
PUNKTFUNK_MIN_STREAM_BLOCK_SHARDS
|
||||
PUNKTFUNK_MODE
|
||||
PUNKTFUNK_MSG_ACCESS_UPDATE
|
||||
PUNKTFUNK_MSG_BITRATE_CHANGED
|
||||
PUNKTFUNK_MSG_CLIP_CONTROL
|
||||
PUNKTFUNK_MSG_CLIP_FETCH
|
||||
PUNKTFUNK_MSG_CLIP_FETCH_HDR
|
||||
PUNKTFUNK_MSG_CLIP_OFFER
|
||||
PUNKTFUNK_MSG_CLIP_STATE
|
||||
PUNKTFUNK_MSG_CLOCK_ECHO
|
||||
PUNKTFUNK_MSG_CLOCK_PROBE
|
||||
PUNKTFUNK_MSG_CURSOR_RENDER
|
||||
PUNKTFUNK_MSG_CURSOR_SHAPE
|
||||
PUNKTFUNK_MSG_LOSS_REPORT
|
||||
PUNKTFUNK_MSG_PAIR_CHALLENGE
|
||||
PUNKTFUNK_MSG_PAIR_PROOF
|
||||
PUNKTFUNK_MSG_PAIR_REQUEST
|
||||
PUNKTFUNK_MSG_PAIR_RESULT
|
||||
PUNKTFUNK_MSG_PHASE_REPORT
|
||||
PUNKTFUNK_MSG_PIPELINE_GAP
|
||||
PUNKTFUNK_MSG_PROBE_REQUEST
|
||||
PUNKTFUNK_MSG_PROBE_RESULT
|
||||
PUNKTFUNK_MSG_RECONFIGURE
|
||||
PUNKTFUNK_MSG_RECONFIGURED
|
||||
PUNKTFUNK_MSG_REQUEST_KEYFRAME
|
||||
PUNKTFUNK_MSG_RFI_REQUEST
|
||||
PUNKTFUNK_MSG_SET_BITRATE
|
||||
PUNKTFUNK_MSG_SHARD_PAYLOAD_ACK
|
||||
PUNKTFUNK_MSG_SHARD_PAYLOAD_CHANGED
|
||||
PUNKTFUNK_NO_AMF_LTR
|
||||
PUNKTFUNK_NO_AV_SYNC
|
||||
PUNKTFUNK_NO_OUTPUT_KEYFRAME_STREAK
|
||||
PUNKTFUNK_NO_QSV_LTR
|
||||
PUNKTFUNK_NO_VIRTUAL_MOUSE
|
||||
PUNKTFUNK_NV12
|
||||
PUNKTFUNK_NVENC_ASYNC
|
||||
PUNKTFUNK_NVENC_ASYNC_DEPTH
|
||||
PUNKTFUNK_NVENC_DIRECT
|
||||
PUNKTFUNK_NVENC_MAX_SESSIONS
|
||||
PUNKTFUNK_NVENC_SLICES
|
||||
PUNKTFUNK_NVENC_STREAM_ORDERED
|
||||
PUNKTFUNK_NV_PROFILE
|
||||
PUNKTFUNK_OVERLAY_MASK
|
||||
PUNKTFUNK_PACE_BURST_KB
|
||||
PUNKTFUNK_PACE_FACTOR
|
||||
PUNKTFUNK_PAD_AUDIO_STAMPS
|
||||
PUNKTFUNK_PAD_CHANNEL_TRUST_MAILBOX
|
||||
PUNKTFUNK_PAD_SINK_PARENT_CLASS
|
||||
PUNKTFUNK_PAD_SINK_VOLUME
|
||||
PUNKTFUNK_PAIRING_PIN
|
||||
PUNKTFUNK_PAIRING_PORT
|
||||
PUNKTFUNK_PAIR_APPROVAL_TIMEOUT_CLOSE_CODE
|
||||
PUNKTFUNK_PAIR_BOUND_OTHER_CLOSE_CODE
|
||||
PUNKTFUNK_PAIR_DENIED_CLOSE_CODE
|
||||
PUNKTFUNK_PAIR_NOT_ARMED_CLOSE_CODE
|
||||
PUNKTFUNK_PAIR_NO_IDENTITY_CLOSE_CODE
|
||||
PUNKTFUNK_PAIR_RATE_LIMITED_CLOSE_CODE
|
||||
PUNKTFUNK_PAIR_SUPERSEDED_CLOSE_CODE
|
||||
PUNKTFUNK_PEN_ANGLE_UNKNOWN
|
||||
PUNKTFUNK_PEN_BARREL1
|
||||
PUNKTFUNK_PEN_BARREL2
|
||||
PUNKTFUNK_PEN_BATCH_MAX
|
||||
PUNKTFUNK_PEN_DISTANCE_UNKNOWN
|
||||
PUNKTFUNK_PEN_IN_RANGE
|
||||
PUNKTFUNK_PEN_PREDICTED
|
||||
PUNKTFUNK_PEN_SAMPLE_WIRE_LEN
|
||||
PUNKTFUNK_PEN_TILT_UNKNOWN
|
||||
PUNKTFUNK_PEN_TOUCHING
|
||||
PUNKTFUNK_PEN_TOUCH_TIMEOUT_MS
|
||||
PUNKTFUNK_PF_VDISPLAY_LIVE
|
||||
PUNKTFUNK_PHASE_LOCK
|
||||
PUNKTFUNK_PIN_CLOCKS
|
||||
PUNKTFUNK_PIPEWIRE_NV12
|
||||
PUNKTFUNK_PRESETS
|
||||
PUNKTFUNK_PW_FIXED_POD
|
||||
PUNKTFUNK_PYROWAVE_CHUNK_KIB
|
||||
PUNKTFUNK_PYROWAVE_STREAMED_AU
|
||||
PUNKTFUNK_QUIT_CLOSE_CODE
|
||||
PUNKTFUNK_REANCHOR_MARKS_TO_LIFT
|
||||
PUNKTFUNK_RECVMSG_X
|
||||
PUNKTFUNK_REJECT_BUSY_CLOSE_CODE
|
||||
PUNKTFUNK_REMOTE_COMPOSITOR
|
||||
PUNKTFUNK_REMOTE_GAMEPAD
|
||||
PUNKTFUNK_REMOTE_HOST
|
||||
PUNKTFUNK_REMOTE_PIN
|
||||
PUNKTFUNK_REMOTE_PORT
|
||||
PUNKTFUNK_RFI_MAX_RANGE
|
||||
PUNKTFUNK_RICH_INPUT_MAGIC
|
||||
PUNKTFUNK_RUMBLE_ENVELOPE
|
||||
PUNKTFUNK_RUMBLE_IDLE_MS
|
||||
PUNKTFUNK_RUMBLE_MAGIC
|
||||
PUNKTFUNK_RUMBLE_TTL_MS
|
||||
PUNKTFUNK_RUMBLE_V1_LEN
|
||||
PUNKTFUNK_RUMBLE_V2_LEN
|
||||
PUNKTFUNK_RUMBLE_V3_LEN
|
||||
PUNKTFUNK_SDR_COLORSPACE
|
||||
PUNKTFUNK_SEAL_LANES
|
||||
PUNKTFUNK_SESSION_LAYOUT
|
||||
PUNKTFUNK_SETUP_FAILED_CLOSE_CODE
|
||||
PUNKTFUNK_SHOT_EDITING
|
||||
PUNKTFUNK_SHOT_HERO
|
||||
PUNKTFUNK_SHOT_INTERACTIVE
|
||||
PUNKTFUNK_SHOT_OUT
|
||||
PUNKTFUNK_SHOT_PALETTE
|
||||
PUNKTFUNK_SHOT_SCENE
|
||||
PUNKTFUNK_SHOT_SELFCAPTURE
|
||||
PUNKTFUNK_SHOT_SETTINGS_PAGE
|
||||
PUNKTFUNK_SHOT_SETTINGS_SCOPE
|
||||
PUNKTFUNK_SHOT_SETTLE_MS
|
||||
PUNKTFUNK_SLICE_STREAM
|
||||
PUNKTFUNK_SPIKE_HDR
|
||||
PUNKTFUNK_STALL_PROBES
|
||||
PUNKTFUNK_STATS_STDOUT
|
||||
PUNKTFUNK_STEAM_FORCE
|
||||
PUNKTFUNK_STEAM_REMAP
|
||||
PUNKTFUNK_STEAM_USBIP
|
||||
PUNKTFUNK_STREAMED_AU
|
||||
PUNKTFUNK_SYNTH_NOISE
|
||||
PUNKTFUNK_TAG_LEN
|
||||
PUNKTFUNK_TEST_FEEDBACK
|
||||
PUNKTFUNK_TONEMAP_PEAK
|
||||
PUNKTFUNK_TRIGGER_EFFECT_MAX
|
||||
PUNKTFUNK_TXN_PRESENT
|
||||
PUNKTFUNK_UI_SECURE
|
||||
PUNKTFUNK_UI_TLS_CERT
|
||||
PUNKTFUNK_UI_TLS_KEY
|
||||
PUNKTFUNK_UPDATE_FEED
|
||||
PUNKTFUNK_USBIP_ATTACH
|
||||
PUNKTFUNK_USBIP_TRACE
|
||||
PUNKTFUNK_USER_FLAG_CHUNK_ALIGNED
|
||||
PUNKTFUNK_USER_FLAG_RECOVERY_ANCHOR
|
||||
PUNKTFUNK_USER_FLAG_RECOVERY_POINT
|
||||
PUNKTFUNK_USER_FLAG_SLICE_STREAM
|
||||
PUNKTFUNK_VAAPI_ASYNC_DEPTH
|
||||
PUNKTFUNK_VBV_FRAMES
|
||||
PUNKTFUNK_VERSION
|
||||
PUNKTFUNK_VIDEO_CAP_10BIT
|
||||
PUNKTFUNK_VIDEO_CAP_444
|
||||
PUNKTFUNK_VIDEO_CAP_CHACHA20
|
||||
PUNKTFUNK_VIDEO_CAP_HDR
|
||||
PUNKTFUNK_VIDEO_CAP_HOST_TIMING
|
||||
PUNKTFUNK_VIDEO_CAP_MULTI_SLICE
|
||||
PUNKTFUNK_VIDEO_CAP_PROBE_SEQ
|
||||
PUNKTFUNK_VIDEO_CAP_STREAMED_AU
|
||||
PUNKTFUNK_VK_ADAPTER
|
||||
PUNKTFUNK_VK_DEVICE
|
||||
PUNKTFUNK_VK_QUEUE_PRIORITY
|
||||
PUNKTFUNK_VULKAN_INFLIGHT
|
||||
PUNKTFUNK_VULKAN_QUALITY
|
||||
PUNKTFUNK_VULKAN_RC
|
||||
PUNKTFUNK_VULKAN_RGB_DIRECT
|
||||
PUNKTFUNK_VULKAN_RGB_TRUE_EXTENT
|
||||
PUNKTFUNK_WASAPI_LIVE
|
||||
PUNKTFUNK_WEB_CONSOLE
|
||||
PUNKTFUNK_WINDOWED_PRESENT
|
||||
PUNKTFUNK_WIN_SEPARATE
|
||||
PUNKTFUNK_WIRE_VERSION
|
||||
PUNKTFUNK_WIRE_VERSION_CLOSE_CODE
|
||||
PUNKTFUNK_XBOX_BACKEND
|
||||
PUNKTFUNK_ZEROCOPY_HOLD
|
||||
PUNKTFUNK_ZEROCOPY_INPROC
|
||||
PUNKTFUNK_ZEROCOPY_RENDER_NODE
|
||||
Reference in New Issue
Block a user