73c911cae4d068e7a47eb37fa6be8ab0a7ac3733
143 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
313f194c63 |
fix(ci/flatpak): the DNS failures are runner-load packet drops, not nsswitch — widen the retry budget
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 1m9s
decky / build-publish (push) Successful in 17s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 8s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
ci / bench (push) Successful in 5m45s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5m20s
arch / build-publish (push) Successful in 11m35s
docker / deploy-docs (push) Successful in 22s
android / android (push) Successful in 13m7s
deb / build-publish (push) Successful in 12m36s
flatpak / build-publish (push) Failing after 8m5s
ci / rust (push) Successful in 17m55s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 12m17s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m15s
apple / swift (push) Successful in 5m13s
apple / screenshots (push) Successful in 21m49s
Live-debugged on home-runner-1: confirmed the dnf install in Tooling does NOT actually rewrite /etc/nsswitch.conf (no authselect trigger fires), so the nss-resolve sed from 68b5376f/5f687a70 was never the actual fix. gitea-runner-fleet on this box is a shared, resource-capped fleet (3 replicas, --cpus 5/--memory 7g each, on 16c/24G) serving punktfunk AND several other orgs' repos concurrently. A push to main fans out ~8 punktfunk workflows at once on top of that other traffic, and the box's Docker embedded DNS resolver (127.0.0.11) drops UDP lookups under the combined load — exactly what retry.sh's own header already documented. Manually dispatching this job while the box was idle succeeded instantly, with or without the sed. retry.sh's 5-attempt (~100s) budget isn't always enough to outlast a synchronized multi-org burst; bumped the three flathub-facing calls (remote-add, install-deps-only, download-only) to 10 attempts (~9min). Verified end-to-end on a live re-run (run 8993) after this change: full success including publish + OSTree deploy. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
9e6bd790a3 |
feat(ci): accept deploy_host input so deploy-all can target a fresh box
ci / web (pull_request) Successful in 52s
ci / docs-site (pull_request) Successful in 1m2s
ci / bench (pull_request) Successful in 7m44s
android / android (pull_request) Successful in 12m47s
apple / swift (pull_request) Successful in 4m42s
ci / rust (pull_request) Successful in 18m33s
apple / screenshots (pull_request) Has been skipped
When unom/infra deploy-all provisions a new unom-1, it dispatches this deploy with the just-created box IP. Falls back to the DEPLOY_HOST secret on push-triggered runs, so existing behavior is unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
68b5376fb8 |
fix(ci/flatpak): reapply nss-resolve DNS fix after dnf pulls systemd upgrade
ci / web (push) Successful in 49s
ci / docs-site (push) Successful in 55s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 10s
decky / build-publish (push) Successful in 18s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 7s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 8s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 59s
flatpak / build-publish (push) Failing after 2m12s
ci / bench (push) Successful in 6m49s
docker / deploy-docs (push) Successful in 21s
apple / swift (push) Successful in 4m35s
android / android (push) Successful in 12m53s
arch / build-publish (push) Successful in 14m4s
deb / build-publish (push) Successful in 15m28s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 13m33s
ci / rust (push) Successful in 18m16s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 15m30s
apple / screenshots (push) Successful in 18m50s
The earlier fix (
|
||
|
|
9b0aac4b5c |
ci: fix deploy-services.yml content (was double-encoded)
ci / web (pull_request) Successful in 51s
ci / docs-site (pull_request) Successful in 1m6s
apple / swift (pull_request) Successful in 4m31s
apple / screenshots (pull_request) Has been skipped
ci / bench (pull_request) Successful in 6m36s
android / android (pull_request) Successful in 11m4s
ci / rust (pull_request) Failing after 14m45s
|
||
|
|
124a81e861 |
ci: deploy-only workflow for docs + flatpak server (no rebuild)
docker.yml (5-image matrix) and flatpak.yml (full flatpak-builder) couple the docs + flatpak-server deploys to heavy builds. This adds a dispatchable, build-free deploy-services.yml that just (re)places the compose files and pulls the already-published images, so unom/infra's deploy-all can bring a fresh unom-1 fully up in one dispatch. The flatpak ./site OSTree content is restored from the unom-1 backup or regenerated by flatpak.yml's next publish. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
5f687a7083 |
fix(ci/flatpak): drop nss-resolve so flatpak can resolve DNS in the container
ci / web (push) Successful in 45s
ci / docs-site (push) Successful in 56s
decky / build-publish (push) Successful in 15s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 7s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 8s
flatpak / build-publish (push) Failing after 2m20s
ci / bench (push) Successful in 13m9s
arch / build-publish (push) Successful in 20m58s
docker / deploy-docs (push) Successful in 24s
android / android (push) Successful in 26m8s
deb / build-publish (push) Successful in 28m2s
ci / rust (push) Successful in 31m15s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 31m22s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 29m11s
apple / swift (push) Has been cancelled
apple / screenshots (push) Has been cancelled
fedora:43 nsswitch routes host lookups through the resolve module (systemd-resolved), absent in a CI container. git/curl/dnf fall through to the dns module (Docker 127.0.0.11); flatpak/ostree's resolver trips [!UNAVAIL=return] and dies with '[6] Could not resolve hostname'. Was masked as flaky 'busy runner drops DNS' + retry.sh, but it's deterministic on runners where the resolve module tips that way (started failing when jobs landed on the newly-added home-runner-2). Drop the resolve entry -> plain dns lookups. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3d6c06bb06 |
fix(ci): flatpak job rides out the runner's network flake — retry every single-shot fetch
ci / web (push) Successful in 52s
apple / swift (push) Successful in 1m13s
ci / docs-site (push) Successful in 1m8s
decky / build-publish (push) Successful in 18s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 8s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 7s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 6s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
ci / bench (push) Successful in 6m13s
windows-host / package (push) Successful in 7m50s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, --no-default-features, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 2m0s
docker / deploy-docs (push) Successful in 1m16s
arch / build-publish (push) Successful in 11m17s
windows-msix / package (x64, C:\Users\Public\ffmpeg, , x86_64-pc-windows-msvc, C:\t) (push) Successful in 2m19s
release / apple (push) Successful in 11m7s
android / android (push) Successful in 13m1s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 1m2s
windows / build (x86_64-pc-windows-msvc) (push) Successful in 1m18s
deb / build-publish (push) Successful in 14m52s
apple / screenshots (push) Successful in 5m41s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Successful in 12m13s
ci / rust (push) Successful in 22m12s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Successful in 15m18s
flatpak / build-publish (push) Successful in 5m18s
The flatpak run dies most pushes at `flatpak remote-add` with an instant "[6] Could not resolve hostname" (runs 8755/8702/8689), 25ms after dnf pulled 329 packages fine — and deploy-docs separately hits "dial tcp: i/o timeout" to unom-1 (8716/8679). The busy runner drops UDP DNS + TCP dials under parallel-job load; tools with built-in retries (dnf) ride it out, single-shot fetches killed the whole 2h build slot. * scripts/ci/retry.sh: linear-backoff wrapper (5 tries, attempt*10s); stdout passes through untouched so $(…) capture works. * Tooling: retry the flathub remote-add. * Build split: a retried prefetch phase (--install-deps-only, then --download-only for every crate in cargo-sources.json) warms the state dir; the long compile then runs with no network left to flake on. * Seed step: probe (retried) whether the server repo exists — ONLY first publish may continue fresh. The old blanket `rsync || warn` swallowed transient failures too, producing the single-branch summary that clobbers the other channel (the exact bug the step's comment documents). * Deploy: retry the idempotent ssh/rsync calls to unom-1. * raw.githubusercontent curl: --retry 5 --retry-all-errors. docker.yml's deploy-docs has the same disease but sits on drone-ssh; left for a follow-up. Real root cause is runner-host networking (conntrack / upstream DNS under burst load) — retries make the jobs indifferent to it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e707a962b6 |
fix(security): anti-replay, 0600 client key, open redirect, supply-chain
Address findings from a repo security review:
- core: add a sliding-window anti-replay filter over the AEAD-authenticated
sequence in Session (poll_input/poll_frame), closing the input-replay gap the
data plane previously left to the LAN/VPN trust assumption. 4096-deep window,
unit-tested; the encrypted loopback suite confirms no false drops.
- clients: write the mTLS client private key 0600 and lock the config dir 0700
on Unix (it was world-readable at the umask default), re-locking existing
stores on load. pf-client-core::trust plus the probe's own identity writer.
Windows keeps the %APPDATA% ACL; Android/Apple already wrap the key.
- web: fix a post-login open redirect — resolve `next` via URL and require it to
stay same-origin, rejecting `/\evil.com` and tab/encoding variants the old
`!startsWith("//")` guard missed. Also fixes the dead safeNextPath helper.
- ci: SHA-256-pin the BtbN FFmpeg DLLs bundled into the signed Windows installer
(were fetched from the rolling `latest` tag unverified); fails closed on a
re-roll, matching the VB-CABLE gate.
- ci: fail-open fork-guard on the Windows/Apple host-mode PR build jobs that
share runner labels with the signing jobs. Definitive fix stays server-side
(Gitea outside-collaborator approval / isolated PR runners) — see the notes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
d6647b9183 |
feat(clients/windows): port the Vulkan session client to Windows — session-always
The punktfunk-session Vulkan client (clients/linux-session, now clients/session) builds and runs on Windows; the WinUI shell spawns it for every stream. Verified live: 10-bit HEVC via Vulkan Video on both AMD (iGPU) and NVIDIA, 5120x1440 at 130 fps / 8 ms end-to-end on the RTX 4090. - pf-ffvk: Windows bindgen branch (FFMPEG_DIR + PF_FFVK_VULKAN_INCLUDE, no pkg-config); provisioning fetches Vulkan-Headers (pinned v1.4.309). - pf-client-core: builds on Windows — WASAPI audio (audio_wasapi.rs, cfg-swapped via #[path], same surface as the PipeWire twin), VAAPI/dmabuf gated inline (chain = vulkan -> software), trust reads the WinUI shell's %APPDATA% stores (parity tests pin both serialized shapes), Settings gains adapter/hdr_enabled (serde-defaulted; Linux stores unaffected). - pf-presenter: builds on Windows — dmabuf module Linux-gated; SDL keyboard grab while captured (Alt+Tab/Win reach the host); pick_device ranks discrete over integrated (device 0 was the iGPU on hybrid boxes — the silent footgun) and honors PUNKTFUNK_VK_ADAPTER (the Settings GPU pick, exported by the session). - run loop: block in one SDL wait woken by input AND decoded frames (a per- session forwarder pushes a FrameWake user event) instead of a 1 ms poll — measured 111%% -> 5%% of a core (NVIDIA), 86%% -> 3.5%% (AMD), stats unchanged. The pump's decode-fence wait became once-per-window sampling (no per-frame pipeline stall; the stat now shows true backlog). - pf-console-ui: builds on Windows (skia-safe msvc prebuilts); font lookup falls through fontconfig aliases to concrete DirectWrite families (Consolas/Segoe UI) — browse/coverflow works, verified against a live host. - WinUI shell: session-always via new src/spawn.rs (GTK spawn.rs port — CREATE_NO_WINDOW, stdout contract, kill handle); the Stream screen is a status card (chips + stage lines from the child's stats). The legacy in-process D3D11VA path stays behind Settings "Streaming engine" / PUNKTFUNK_BUILTIN_ STREAM=1 as the A/B baseline until Phase 8 deletes it. SessionParams.video_caps makes the HDR toggle real. - clients/linux-session renamed to clients/session (builds for both OSes). - CI/MSIX: both workflows build/test both bins with widened path filters; the MSIX ships punktfunk-session.exe. ARM64 session builds --no-default-features (rust-skia has no aarch64-pc-windows-msvc prebuilts; flip when it does). A/B on this box (5120x1440 HEVC vs home-worker-5): NVIDIA Vulkan 130 fps / 8 ms e2e / 1.6 ms decode — clearly better than the built-in path. The AMD iGPU VCN saturates at ~52 fps where its own D3D11VA does ~70 — Adrenalin Vulkan decode is slower on APU silicon; discrete RDNA validation gates Phase 8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
0052a6ae30 |
fix(packaging): vulkan-headers build dep + ship punktfunk-session across desktop packages
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Failing after 13s
windows / build (x86_64-pc-windows-msvc) (push) Failing after 8s
apple / swift (push) Successful in 1m13s
apple / screenshots (push) Successful in 5m57s
android / android (push) Successful in 3m58s
ci / rust (push) Successful in 4m10s
ci / web (push) Successful in 51s
ci / docs-site (push) Successful in 1m0s
arch / build-publish (push) Successful in 9m35s
deb / build-publish (push) Successful in 3m59s
decky / build-publish (push) Successful in 15s
ci / bench (push) Successful in 4m51s
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Successful in 4s
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Successful in 2m38s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 5s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 2m31s
flatpak / build-publish (push) Successful in 4m24s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 9m13s
docker / deploy-docs (push) Successful in 6s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
windows-host / package (push) Successful in 23m50s
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Successful in 1m15s
windows / build (aarch64-pc-windows-msvc) (push) Successful in 4m56s
The re-architecture split the Linux client into two binaries (shell + Vulkan session streamer) and added the pf-ffvk crate, whose build.rs runs bindgen over FFmpeg's libavutil/hwcontext_vulkan.h (#include <vulkan/vulkan.h>). Two release regressions fell out of that on the desktop-package legs: 1. Missing build dep. The Arch build (archlinux:base-devel + its own pacman list) fatal-errored at `vulkan/vulkan.h file not found`. Add the Vulkan dev headers wherever the client compiles from a self-managed dep set: arch (PKGBUILD makedepends + arch.yml), rpm (spec BuildRequires + rpm.yml + fedora image), deb.yml (belt-and-suspenders; the rust-ci image already bakes libvulkan-dev), and the screenshots job. The flatpak builds offline against the GNOME SDK, so install Vulkan-Headers into /app as a pinned module and point pf-ffvk's bindgen at it via PF_FFVK_VULKAN_INCLUDE. 2. Only the shell shipped. arch/deb/rpm built and installed just punktfunk-client; the shell execs its sibling punktfunk-session for a connect, so desktop streaming would break exactly as Decky's did. Build and install BOTH binaries in all three, and declare the runtime Vulkan loader the session binary dlopens (vulkan-icd-loader / vulkan-loader / libvulkan1 — not a DT_NEEDED, so shlibdeps/auto-requires can't see it). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
b2451e6aea |
fix(flatpak): ship punktfunk-session so Decky streaming survives the two-binary split
The re-architected Linux client is now two binaries: the punktfunk-client shell execs its sibling punktfunk-session (ash/Vulkan presenter + Skia console UI) for --connect/--browse. The Decky plugin's stream and browse paths launch the shell with exactly those flags, but the flatpak built and installed only the shell, so streaming and the gamepad library from the Deck failed at exec with "punktfunk-session: No such file" (pair/wake/library still worked — the shell handles them in-process). Build and install both binaries. The session binary pulls in Skia (skia-safe), whose build script downloads a prebuilt libskia — dead in the offline sandbox — so point skia-bindings at a pinned, vendored archive via SKIA_BINARIES_URL=file:// (read directly, no curl); the tarball rides along as a sha256-pinned flatpak source. Widen the flatpak CI path filters to the session binary's crates (linux-session, pf-presenter, pf-console-ui, pf-client-core) and fix the moved library.rs path in the Decky error-classifier comment. All other plugin↔client contracts (flags, pairing/library output, config files, env vars, exit codes, the 47990 mgmt port) already match — no changes needed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
655ec31ef9 |
ci(android): attach the built APK to the workflow run
apple / swift (push) Successful in 1m9s
android / android (push) Successful in 4m39s
arch / build-publish (push) Successful in 5m25s
ci / web (push) Successful in 53s
apple / screenshots (push) Successful in 5m32s
ci / rust (push) Successful in 2m11s
ci / docs-site (push) Successful in 1m0s
ci / bench (push) Successful in 5m9s
deb / build-publish (push) Successful in 4m59s
decky / build-publish (push) Successful in 18s
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 6s
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 5s
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 3s
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 2m28s
rpm / build-publish (43, bazzite, punktfunk-fedora-rpm) (push) Failing after 8m52s
docker / deploy-docs (push) Successful in 18s
rpm / build-publish (44, fedora-44, punktfunk-fedora44-rpm) (push) Failing after 8m27s
The APK was only reachable via the generic registry (or attached to a Gitea Release on a vX.Y.Z tag) — a main-push canary or a PR/dispatch run surfaced no downloadable APK on the run page itself. Add an upload-artifact step (v3, per Gitea's GHES-identifying artifact backend, like apple.yml) that grabs whichever APKs were built — the signed universal release APK on a main/tag push, else the debug APK — so any run is a one-click sideload download. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
8c541ecf10 |
ci: SHA-pin secret-bearing third-party actions + audit the web dep tree
- Pin android-actions/setup-android, appleboy/scp-action, and appleboy/ssh-action to commit SHAs (version kept in a trailing comment). These run in jobs holding the Android signing keystore, Play service-account, and deploy SSH key, so a moved tag on a third-party action could exfiltrate them. - Add a bun-audit job to audit.yml over web/bun.lock — the console holds the login gate, session sealing, and mgmt token, so its deps matter too — and trigger the workflow on web/bun.lock changes alongside Cargo.lock. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
216619176c |
build: pin the exact Rust toolchain (1.96.0) to stop rustfmt drift
rust-toolchain.toml pinned the floating "stable" channel, so the CI image baked whatever stable existed at image-build time. When the image is rebuilt onto a newer stable, rustfmt's rules shift and `cargo fmt --all --check` fails on files nobody touched — the recurring format-drift that keeps red-lighting CI. Pin channel = "1.96.0" (== today's stable: rustc ac68faa20, rustfmt 1.9.0-stable), the exact build CI already runs, so this is a no-op now but locks formatting for good: local dev, the Linux CI image, and the Windows runner all use rustup and honor this file, so they converge on one rustfmt. Formatting now only changes in a deliberate bump-this-pin- and-reformat commit. ci.yml cache comment updated to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3775a39e70 |
feat(android): console UI, wake-on-LAN wait-until-up, host edit + TV/tablet polish
Bring the Android client to parity with Apple's gamepad experience and finish Wake-on-LAN. - Console/gamepad home: host carousel, aurora chrome, mTLS game-library coverflow, and an input-aware legend that switches between gamepad face buttons and a TV-remote select-ring + arrows based on the last-used input. - Wake-on-LAN: the fire-and-forget send is upgraded to wait-until-up (WakeController/WakeOverlay: resend + mDNS poll, 90s timeout, cancel/retry, fingerprint-matched so a host that cold-boots onto a new DHCP IP still connects), plus host edit (touch dialog + console form) with an auto-filled MAC. - Android TV: brand banner (android:banner), density-aware console scaling, D-pad/ remote nav (Up = Settings, Down or the pad Select button = host Options), emergency stream-exit chord, and 120Hz console refresh. - Touch UI: settings split into subpages with a tablet NavigationRail, axis-aware tab animation (horizontal on phones, vertical on the tablet rail), animated settings navigation, and a licenses screen with a back button + the real workspace version (read from Cargo.toml). - Vector Lock/controller icons (no emoji); bundled Geist font. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
2e1e3d44b3 |
docs(ci/arch): correct the header's pacman setup (key import, not TrustAll) + note the trust root
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
dd4e2d4fa8 |
fix(packaging/arch): correct pacman setup — import the registry key, cache cargo git
The Gitea Arch registry signs its DB + packages, so 'SigLevel = Optional TrustAll' fails non-interactively (pacman still needs the key to verify). Document the one-time pacman-key import instead; install is then signature-validated under pacman's default SigLevel (verified end-to-end: clean archlinux container -> repo sync -> install, 'Validated By: Signature'). Also cache /usr/local/cargo/git in arch.yml: the workspace pulls clients/windows' git-pinned windows-reactor/windows deps to resolve, cloning windows-rs (huge) every run otherwise — same registry+git cache deb.yml uses. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
a4ebacafd4 |
fix(ci/arch): create CARGO_HOME before chown — actions/cache doesn't on a miss
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3993561c4d |
fix(ci/arch): install nodejs before actions/checkout — act_runner doesn't inject node
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b622e53dc1 |
feat(packaging/arch): distribute binary packages via the Gitea Arch registry
New arch.yml builds the split PKGBUILD (host/client/web, PF_WITH_WEB=1) in an
archlinux:base-devel container on every push and publishes to the pacman repos
'punktfunk' (tags) / 'punktfunk-canary' (main, X.Y.Z-0.<run#> — pkgrel allows
only digits+dots, so the run number carries the ordering). Consumers add one
pacman.conf section; no more build-it-yourself as the only Arch path.
PKGBUILD: pkgver/pkgrel env-driven (PF_PKGVER/PF_PKGREL), source=() when
PF_SRCDIR is set (a canary version has no tag to clone), stale NVENC-only
header fixed, and options=('!lto' '!debug') — makepkg's lto option injects
-flto=auto into CFLAGS, aws-lc-sys compiles its C with it, and rust's lld
cannot read GCC LTO bitcode: 'undefined symbol: aws_lc_*' at link (reproduced
minimally on Arch + rust 1.90). Full build + clean-container install
smoke-tested locally (binaries run, payload + scriptlets intact).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
f299aaeb3d |
feat(packaging/bazzite): systemd-sysext replaces rpm-ostree layering as the primary install path
Layering is a last resort per the Bazzite docs (slows every OS update, can block upgrades until removed); a sysext never enters an rpm-ostree transaction, survives OS updates, and installs/updates with no reboot — the mechanism Fedora Atomic ships via fedora-sysexts. - build-sysext.sh wraps the built host+web RPMs into punktfunk-<V-R>-x86-64.raw: /etc payload relocated to /usr/share/punktfunk/etc (a sysext carries only /usr), the punktfunk-sysext helper embedded, ID=fedora + VERSION_ID pinned (merges on Bazzite via ID_LIKE; REFUSED after a major rebase instead of running soname-broken binaries — both behaviors validated live on Bazzite 43). SELinux labels are baked in as squashfs pseudo-xattrs from matchpathcon: unlabeled files run fine for user units but system daemons are DENIED (udev couldn't read the gamepad rule under enforcing) — validated on-glass. Refuses duplicate input package names (a stale noarch punktfunk-web next to the x86_64 one built a chimera image with the dead node launcher once). - punktfunk-sysext.sh: install/update/status/remove against per-Fedora-major feeds (…/generic/punktfunk-sysext/f43[-canary]), SHA-256-verified, applies the udev/sysctl scriptlet work + /etc copies, prints the layering-migration hint. Live-validated on the .41 Bazzite box incl. service restart + web console. - publish-sysext-feed.sh + rpm.yml: build + publish the image per matrix leg (fedver 43/44), canary feeds pruned to 6, stable release assets attached. - update-punktfunk.sh warns when the sysext shadows a layered install. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
e8db589289 |
fix(apple/release): embed Developer ID provisioning profile in the DMG
android-screenshots / screenshots (push) Has been cancelled
windows-msix / package (arm64, C:\Users\Public\ffmpeg-arm64, aarch64-pc-windows-msvc, C:\t-a64) (push) Has been cancelled
windows-msix / package (x64, C:\Users\Public\ffmpeg, x86_64-pc-windows-msvc, C:\t) (push) Has been cancelled
windows-host / package (push) Has been cancelled
web-screenshots / screenshots (push) Has been cancelled
release / apple (push) Has been cancelled
linux-client-screenshots / screenshots (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
deb / build-publish (push) Has been cancelled
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (--build-arg FEDORA_VERSION=44, ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm) (push) Has been cancelled
docker / build-push (., web/Dockerfile, punktfunk-web) (push) Has been cancelled
docker / build-push (ci, ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Has been cancelled
docker / build-push (ci, ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Has been cancelled
docker / build-push (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Has been cancelled
docker / deploy-docs (push) Has been cancelled
decky / build-publish (push) Has been cancelled
android / android (push) Has been cancelled
The notarized Developer ID .dmg was SIGKILLed at launch ("Launchd job spawn
failed", POSIX errno 163) before main() ran: the sandboxed macOS app declares
the MANAGED keychain-access-groups entitlement, which AMFI only honors when an
embedded provisioning profile authorizes it. The DMG embedded none — App Sandbox
and the network/device keys are self-asserted for Developer ID, but a keychain
access group is not — so every launch was killed at spawn. Validly signed and
notarized (Gatekeeper accepted it), which is why this looked like a mystery. ⌘R
and the App Store build hid it: Xcode embeds a development / App Store profile;
the raw-codesign DMG path did not, so "⌘R == DMG" never held for this entitlement.
Embed a "Punktfunk macOS Developer ID" profile (Keychain Sharing) into
Contents/embedded.provisionprofile before codesign so its entitlements authorize
the access group, exactly like the App Store build's profile does. If the profile
isn't installed on the runner, warn and strip keychain-access-groups instead so
the app still launches via ClientIdentityStore's legacy file-keychain fallback —
a missing/expired profile can never reship the errno-163 brick again.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
5610f4f210 |
fix(flatpak): keep both channels in the OSTree summary (fixes stable "No such ref")
The surfaced install command flatpak install --user https://flatpak.unom.io/io.unom.Punktfunk.flatpakref failed with "No such ref 'app/io.unom.Punktfunk/x86_64/stable'". The stable commit's objects are on the server, but the repo *summary* (what flatpak reads to resolve refs) listed only canary. Root cause: each CI run builds a fresh SINGLE-branch local OSTree repo, build-update-repo regenerates the summary from that one branch, and rsync uploads it without --delete. Objects for both channels accumulate, but the summary is overwritten every run and only names that run's branch. Canary runs on every main push, stable only on tags — so a tag published stable, then the next canary push clobbered the summary back to canary-only. Fix: seed the local repo from the live server (rsync repo/ DOWN) before the build, so it carries every published branch; the build only adds this run's commit and the regenerated+signed summary keeps both channels. Single shared repo kept (no URL/Caddyfile change; existing installs fixed transparently). Adds a refs log after build-update-repo as a clobber tripwire. Also adopts scripts/ci/pf-version.sh for the canary base (see previous commit). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e459109818 |
ci(release): derive canary version from git tags (single source of truth)
Every release workflow hardcoded a canary base version (0.5.0 in
Apple/Android/rpm/flatpak/deb, 0.3 in windows-msix/windows-host/decky) that
had to be hand-bumped on each stable release and wasn't. With stable at
v0.6.0, every canary was a version *behind* stable — e.g. the Apple canary
showed up on TestFlight as 0.5.0 while 0.6.0 was already published.
Add scripts/ci/pf-version.{sh,ps1} (bash + pwsh twin) as the single source of
truth: stable = the vX.Y.Z tag; canary = latest stable tag with minor+1,
patch 0 (v0.6.0 -> 0.7.0), so canary is always exactly one minor ahead of the
newest release with zero maintenance. Falls back to the workspace Cargo.toml
version when no tag is fetchable. All workflows now eval/call it and format
their own channel suffix off $PF_BASE; only the canary branch changed, stable
branches and per-channel suffixes are untouched. channels.md drops the old
manual "bump the canary base" release step.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
ebada804b6 |
feat(tray): system-tray status icon for the host (Windows + Linux)
New crates/punktfunk-tray — a small per-user companion showing the host service state at a glance (running / stopped / starting / degraded / failed + the live session in the tooltip) with one-click actions: open web console, approve a pending pairing request, start/stop/restart, open logs. No more digging through logs to learn whether the service came back after a reboot or an update. Status is service-manager-FIRST (SCM / systemd user unit — a port squatter can never fake Running), then the new loopback-only unauthenticated GET /api/v1/local/summary (counts/booleans only; the mgmt token and cert.pem are SYSTEM/Admins-DACL'd on Windows, so a non-elevated tray cannot bearer-auth). Windows: windows_subsystem binary (a console exe in the Run key would flash a terminal at sign-in), Shell_NotifyIcon + hidden window, per-session single instance, TaskbarCreated re-add, --quit for the uninstaller; service actions elevate per click via ShellExecuteW "runas" onto the new `punktfunk-host service restart` (stop → wait Stopped → start). Linux: ksni/StatusNotifierItem over zbus, systemctl --user actions (no polkit), /etc/xdg/autostart entry whose --autostart self-gates to actual host users. Icons: scripts/gen-tray-icons.py (pure stdlib) renders the brand lens + status dot into committed .ico/hicolor assets; deb/rpm/arch ship binary+autostart+icons. Live-validated: Linux on the headless KDE session (SNI registration, state transitions, menu-driven start, dbusmenu layout); Windows on the RTX box (session-1 launch with no NIM_ADD failure, single instance, --quit, restart round-trip, summary loopback-200/LAN-401). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8b47be668f |
feat(host/windows): seal the host↔driver channels (frame + gamepad, proto v2)
Frame ring (pf-vdisplay) and both gamepad SHM channels move off named Global\ objects (openable by any sibling LocalService) to UNNAMED sections/events whose handles the host DuplicateHandles into the driver's verified WUDFHost with least access — frame delivery over the SYSTEM+admins-only IOCTL_SET_FRAME_CHANNEL, pads over a 32-byte named bootstrap mailbox (pid + handle value only, DoS-bounded; HID minidrivers have no control device). Driver-validated pad_index kills cross-pad redirects; v1↔v2 mixes fail closed with diagnosis logs on both sides. Sibling-LocalService denial proven empirically (design/idd-push-security.md, design/gamepad-channel-sealing.md). Driver-side raw ops now live behind pf-umdf-util (checked shm accessors, the forbid(unsafe_code) ChannelClient state machine, WDF request tokens) — the pad drivers' logic is 100% safe Rust; whole drivers workspace clippy-gated in CI. driver install --gamepad now sweeps SWD\punktfunk phantom devnodes: a re-created SwDevice REVIVES the old devnode with its previously-bound driver (never re-ranks), so an upgrade otherwise leaves the old driver serving — or, across the v1→v2 fence, a dead pad (found live on the RTX box). On-glass validated on the RTX 4090 box: frame path 7007 frames p50 2.06 ms cross-machine; DualSense + XUSB "sealed pad channel mapped"/proto=2 attach via both the test harness and a real streaming session; phantom-sweep repro. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3a813b79e1 |
ci(release): idempotent registry publish — survive re-tagged releases
A moved release tag re-fires the publish workflows, and the Gitea registries reject duplicate uploads with 409 (deb pool, rpm group, and the generic packages' versioned URLs; the channel aliases already pre-deleted). Delete any prior copy of the exact version before uploading (404 on first publish tolerated), so a republished tag overwrites instead of wedging — v0.5.0's retag left stale no-port-change artifacts published and every re-run red. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
6de4128298 |
feat(host/windows,packaging): installer overhaul - branding, VB-CABLE, GameStream choice, driver uninstall
- Modern branded wizard: WizardStyle=modern dynamic windows11 (Inno >= 6.6,
plain-modern fallback for older compilers; CI provisioning upgrades a
pre-6.6 Inno). Brand-mark wizard side panels + header tiles (100-200% DPI)
and a multi-size punktfunk.ico (SetupIconFile + Apps & Features), generated
AND committed by branding/gen-branding.ps1 from the canonical brand geometry.
Gotcha encoded in the script: ISCC rejects all-PNG icons, so entries <= 64px
are classic DIBs (PNG only at 128/256), and the ICO is load-verified.
- VB-CABLE actually ships now: windows-host.yml never set VBCABLE_DIR, so every
published installer silently omitted the virtual mic (broken mic passthrough
in the field). CI provisions the pinned, SHA-256-verified official Pack45
(provision-windows-punktfunk-extras.ps1) and the pack now FAILS on a
supplied-but-invalid dir instead of shipping mic-less again. Attribution per
VB-Audio's bundling grant surfaced in the visible wizard task text (vendor,
vb-cable.com, donationware) on top of the licenses notice.
- GameStream (Moonlight) compat is a wizard task (checked by default) ->
service install --gamestream=on|off writes PUNKTFUNK_HOST_CMD=
serve[ --gamestream] into host.env. Only the two canonical values are ever
rewritten - a hand-customized command line survives upgrades. Silent
installs: /MERGETASKS="!gamestream".
- Driver uninstall (field report: our virtual-device drivers survived
uninstall): new `driver uninstall [--gamepad]` removes the pf-vdisplay
device node(s) + the pf-vdisplay/pf-dualsense/pf-xusb driver-store packages,
wired into [UninstallRun] after service uninstall. Locale-safe by
construction: devices matched on unlocalized VALUES (never pnputil's
localized labels), packages found by INF content scan - validated against a
German-locale box ("Instanz-ID:" parse; 7/7 punktfunk INFs matched, no
foreign hits). VB-CABLE is deliberately left installed (shared third-party
component with its own uninstaller).
Installer compile, cargo check/clippy/fmt, and the ASCII locale gate are green;
the wizard look + uninstall flow still need one on-glass pass on a disposable
box (this box runs the live host).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
168e903e31 |
fix(windows-ci): add FFmpeg's bin dir to PATH explicitly, don't rely on daemon env
FFMPEG_DIR alone satisfies the linker, but the test binary needs the actual DLLs on PATH at runtime. The daemon's own env (project-env.ps1, written by the punktfunk-extras provisioning step) only takes effect on daemon *restart*, so a freshly cloned/registered runner's first-ever job runs before that file has ever been written, let alone picked up - confirmed live as STATUS_DLL_NOT_FOUND on the new home-windows-runner-1's first real CI run. Setting PATH via GITHUB_PATH here makes the workflow self-sufficient regardless of daemon restart timing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
9eebadff2a |
feat(windows-ci): self-provisioning toolchain for the shared unom/infra runner
The Windows CI runner (home-windows-runner-1, vmid 210) is now provisioned/owned by unom/infra and can be rebuilt or joined by additional windows-amd64-labeled runners at any time - a manually-dispatched provisioning workflow has no way to target a specific runner instance, so it could land on an already-provisioned box instead of the one that needed it. Replace windows-drivers-provision.yml / windows-punktfunk-provision.yml with scripts/ci/ensure-windows-toolchain.ps1, a shared idempotent pre-flight (WDK/cargo-wdk, FFmpeg, Inno Setup, ARM64 rustup target) that every Windows workflow now runs at job start - a fast no-op once already provisioned, so any runner self-heals on first real use. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
641ef6e1e1 |
fix(ci/windows): tolerate 409 on the immutable generic-registry upload
Both Windows publish steps threw on any non-zero curl exit, so re-running a vX.Y.Z tag (e.g. after a force-push) failed at the versioned generic-registry path — that path is immutable and 409s a re-upload of an already-published version. The channel alias right below already delete-then-reuploads to dodge this; mirror that intent for the versioned path by reading the HTTP status and treating 409 as a no-op. The MSIX/installer still build, sign, and attach to the release fine — this only unbreaks the redundant re-publish on a tag re-run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
a384f7db45 |
chore(release): bump workspace version to 0.4.1; canary base to 0.5.0
The [workspace.package] version (inherited by every crate via version.workspace) lagged at 0.3.0 through the 0.4.0 release — bump it to 0.4.1, the release being cut, and refresh the 8 workspace entries in Cargo.lock to match (CI builds --locked). Also advance the CI canary-base fallbacks (deb/rpm/flatpak/android/release workflows + build-rpm.sh) from 0.3.0 to 0.5.0 so main/canary builds sort one minor ahead of the latest stable line, per the documented channel convention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
ae51276a03 |
feat(web): consolidate paired devices, self-contained sections, docs + lint
Web console - Pairing/Library/Stats refactored into self-contained subsections that each own their own queries + mutations; a shared slot-based layout (view.tsx) is filled by the live page (containers) and Storybook (pure cards + fixtures) so the layout can't drift. - All paired devices in one list on Pairing with a protocol column (punktfunk/1 + Moonlight), routing each unpair to the right endpoint; the redundant Clients page is removed. - Library: overview grid split from the add/edit form into separate files. - Login screen links out to the docs. Docs - "Console login password" section on every host page (apt/RPM/Bazzite/SteamOS/Windows) plus a new "Forgot your Password?" troubleshooting page, linked from the login screen. - Console served as HTTP/1.1 over TLS (drop the unusable HTTP/3 advertising) across the Bun entry, launchers, systemd units, and packaging. Tooling - Biome now respects .gitignore (stops linting generated code), config migrated to 2.5.1; all lint issues fixed cleanly. Also includes this branch's in-progress host, Apple client, packaging, and CI changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
7a6ce2ae0b |
fix(ci): archive Apple release builds with Automatic signing
The in-app OSS license screens (
|
||
|
|
f7a9549cd1 |
chore(licensing): LGPL FFmpeg swap, third-party notices, attribution hygiene
The MIT OR Apache-2.0 SOURCE license is clean (audit found no copied copyleft); the
gaps were all binary-distribution (Layer-2). This makes the shipped artifacts honest:
- Windows host + client: bundled FFmpeg BtbN gpl-shared -> lgpl-shared (AMF/QSV/decode
unaffected; the GPL-only x264/x265 were never used), and ship the FFmpeg LGPL notice
+ license text in the installer + MSIX (licenses/).
- THIRD-PARTY-NOTICES.txt generated + bundled into installer/MSIX/deb/rpm. Offline
generator (scripts/gen-third-party-notices.{py,sh}) + cargo-about config (about.toml/
.hbs) with a permissive-only accepted-license allow-list as a copyleft regression gate.
- Reword the win32u GPU-preference hook comments to reflect independent reimplementation
(no Apollo/Sunshine GPL-3.0 source copied).
- README dual-license + inbound=outbound contributor clause + non-affiliation trademark
disclaimer; new CONTRIBUTING.md.
- LICENSE files into the standalone driver + vk-layer workspaces; deb copyright holder
aligned to "unom and the punktfunk contributors".
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
6a9a1c40d1 |
fix(ci/apple): set CMAKE_POLICY_VERSION_MINIMUM=3.5 for the vendored libopus
With cmake now found, Homebrew's CMake 4 refuses the vendored libopus's
`cmake_minimum_required(VERSION <3.5)` ("Compatibility with CMake < 3.5 has been
removed"). Export CMAKE_POLICY_VERSION_MINIMUM=3.5 (the same knob the Windows
build uses) so the cmake crate's child cmake configures the audiopus_sys libopus.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
bcc5ec57f0 |
fix(ci/apple): locate Homebrew explicitly for the cmake install
The self-hosted macOS runner runs steps with `bash --noprofile --norc`, so Homebrew's bin dir is not on PATH — the previous `brew install cmake` died with `brew: command not found` (exit 127). Find brew at its known prefix, install cmake only if missing, and export the brew bin dir to $GITHUB_PATH so the subsequent xcframework build (audiopus_sys → vendored libopus) actually finds `cmake`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
d631d5219d |
fix(ci): unbreak the Apple + Windows-client builds after the surround-audio merge
The 5.1/7.1 surround commit (
|
||
|
|
30b9bf3a6f |
feat(client/android): CI screenshot capture via Roborazzi
Play-listing/marketing screenshots of the Compose client rendered on the host JVM by Roborazzi (Robolectric Native Graphics) — no emulator, GPU, KVM, host, or JNI core. Five scenes render the REAL composables with embedded mock state under a forced brand palette (Material You has no wallpaper to seed from on the JVM): hosts grid, settings, TOFU + PIN dialogs, and the live stats HUD. Validated 5/5 locally. - New JVM unit-test source set (app/src/test) + Roborazzi/Robolectric test deps; @Config(sdk=36) is mandatory (no android-all jar for compileSdk 37) and the animation clock is paused so a text-bearing scene reaches idle. - kit: `-PskipRustBuild` skips the cargo-ndk native build so the JVM-only test job needs no Rust/NDK; normal APK/AAR builds are unchanged. - Widen BrandDark / StatsOverlay to internal so the tests can use them. - Standalone best-effort tag-gated workflow; PNGs upload as a 30-day artifact. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
bd8a978902 |
feat(client/linux): CI screenshot capture
Host-free UI screenshots of the GTK4/libadwaita client under a virtual X display (clients/linux/tools/screenshots.sh) — Xvfb + software GL (llvmpipe) + a root-window grab, one app launch per scene. PUNKTFUNK_SHOT_SCENE routes build_ui to render one mock-populated REAL view (hosts grid / settings dialog / TOFU + PIN dialogs) and print PF_SHOT_READY once it has settled; the saved-hosts grid is driven by a seeded client-known-hosts.json. NON_UNIQUE in shot mode so back-to-back launches don't collide. The stream scene is deferred — its page needs a live NativeClient. Gated to stable release tags in a standalone best-effort workflow that builds the client in the rust-ci image and captures under Xvfb; PNGs upload as a 30-day artifact, not committed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
3628ea7547 |
feat(web): CI screenshot capture for the mgmt console
Marketing/store screenshots of the console, captured from the built Storybook with headless Chromium (web/tools/screenshots.mjs) — every Pages/* + Shell/* story rendered at 1440x900@2x. The page stories render from fixtures, so no live mgmt API, login, or GPU is needed (the web analogue of apple.yml's screenshots job). Gated to stable release tags in a standalone best-effort workflow; PNGs upload as a 30-day artifact, not committed. - Add Stats + Pairing stories (the two pages that lacked them) with stats/pairing fixtures typed against the generated models. - Extract a pure PairingView (index.tsx -> view.tsx), matching the Dashboard/Clients/Stats split, so the page renders host-free from mock state instead of racing its polling queries. Container wiring is behaviour-identical. - Playwright driver + a chromium-capable tag-gated job. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
9faa3f319a |
feat(decky): self-update without the store + Gaming-Mode launch polish, and ship the Steam Deck docs
Plugin self-update (no Decky store): CI publishes a per-channel manifest.json
({version, immutable per-version artifact, sha256}) beside the zip and bakes
update.json {channel, manifest} into the plugin. main.py `check_update` reads the
installed version from package.json (the value Decky reports — not plugin.json),
fetches the channel manifest, and the frontend shows an "Update to vX" button that
drives Decky Loader's own install RPC (root downloads + SHA-256-verifies + hot-reloads).
CI now stamps a plain-numeric semver (0.3.<run> canary / X.Y.Z stable) into
package.json — a -ciN suffix would mis-order under compare-versions.
Linux client: `--fullscreen` (plus SteamDeck/gamescope env fallback) enters GTK
fullscreen on stream start so Gaming-Mode chrome is hidden; native-mode resolution
falls back to the display's first monitor when the window isn't mapped yet (was
dropping to the 1080p floor — wrong on the Deck's 1280×800); add a confirmed
"Remove saved host" action (KnownHosts::remove_by_fp).
Docs: new docs/steam-deck.md (Decky install/pair/stream/self-update/troubleshooting),
wired into meta.json nav, and cross-linked from clients/install-client/channels. This
is the page docs.punktfunk.unom.io/docs/steam-deck — the website's download link
pointed at it before it existed; committing it makes that link resolve.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
b2acebdf58 |
chore(windows): clean up build/packaging - drop vendored driver binaries + the LLVM-21 pin
Now that the drivers build from source in CI, remove the dead checked-in binaries and
the toolchain cruft they left behind:
- Delete packaging/windows/{pf-vdisplay,gamepad-drivers}/ (the prebuilt .dll/.inf/.cat/.cer).
pack-host-installer.ps1 builds + signs all three drivers from the drivers/ workspace and
nothing reads the vendored dirs anymore; stage-pf-vdisplay.ps1's -VendorDir is now a
mandatory build-output path, not a vendored default.
- Drop the LLVM-21 pin. The vendored bindgen 0.71->0.72 bump (the shipping pack already
builds green on the runner-default clang 22) retired the bindgen-0.71 layout-test overflow
that needed LLVM 21.1.2, so windows-drivers.yml + provision-windows-wdk.ps1 no longer
install/point at C:\llvm-21 (~898 MB off a fresh provision) - both driver builds now use one
toolchain (clang 22 + bindgen 0.72).
- pack -SkipBuild on the gamepad build (build-pf-vdisplay.ps1 already builds the whole
workspace), build-web.ps1 reaps a stale node too, deploy-dev.ps1 nefconc path + comments.
- Reword the vendored-driver references (build scripts, .iss, READMEs, the vite web-bundle
comment) to the build-from-source reality.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
546b8e2e29 |
fix(windows-installer): build pf-vdisplay from source in CI; ASCII scripts; upgrade-safe web console
The pf-vdisplay virtual-display driver shipped as a checked-in PREBUILT binary
that went stale - two field failures on a fresh install (live-repro'd on a
German-locale Dell laptop):
* Bug A (every box): a repo-wide rename edited the vendored pf_vdisplay.inf
but never re-signed pf_vdisplay.cat, so the catalog stopped covering the INF
-> `pnputil /add-driver` fails SPAPI_E_FILE_HASH_NOT_IN_CATALOG -> driver
never installs -> every session dies "pf-vdisplay driver interface not
found".
* the prebuilt binary also predated IOCTL_SET_RENDER_ADAPTER (added to the
driver source after the vendor freeze) that the host needs to pin the IDD
render GPU on hybrid/Optimus boxes.
Fix: build the driver FROM SOURCE every release (build-pf-vdisplay.ps1, wired
into pack-host-installer.ps1) so .dll/.inf/.cat are always in lockstep and
current driver features ship. The runner's clang 22 made the driver's pinned
bindgen 0.71 emit opaque structs (157 layout-assert errors), so bump the
vendored wdk-sys/wdk-build bindgen 0.71 -> 0.72 (+ lock). The build self-signs
the driver per build (installer trusts the bundled .cer); a stable
DRIVER_CERT_PFX_B64 secret can override.
* Bug B (non-English boxes): the installer runs install-pf-vdisplay.ps1 etc.
via powershell.exe (5.1), which reads a BOM-less script in the ANSI codepage
- an em-dash's trailing 0x94 byte becomes a curly quote on German
Windows-1252 and the script aborts "unterminated string", so the driver
never installed (the gamepad script survived only because it was already
ASCII). Scrub every installer-run .ps1/.cmd to ASCII + add a CI gate that
fails on any non-ASCII so it can't regress.
* Bug C (upgrades): nothing stopped the OLD web console before re-registering
its task, so a stale server kept :3000 (the new one restart-looped on
EADDRINUSE) and served a broken old bundle (500 on /login). Stop + reap it
(runtime-agnostic, by the :3000 listener owner) in web-setup.ps1 and in the
.iss before the file copy + on uninstall.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
0925f1aaa1 |
fix: complete the docs/→design/ and openapi→api/ rename references
The file moves (docs/ → design/, docs/api/openapi.json → api/openapi.json) landed
in
|
||
|
|
8897286aec |
feat(host): HDR Vulkan layer so Vulkan games get HDR on the virtual display
NVIDIA/AMD Vulkan ICDs refuse to *advertise* an HDR color space for a surface on an
IddCx indirect/virtual display, so Vulkan games (Doom: The Dark Ages, id Tech, Indiana
Jones, …) report "device does not support HDR" — even though Windows HDR, DWM compose,
and the client PQ stream all work, and the ICD happily *accepts + presents* a forced HDR
swapchain there. The whole gap is enumeration; the community (Apollo/Sunshine/VDD) wrote
this off as kernel-side / unfixable.
Add VK_LAYER_PUNKTFUNK_hdr_inject (packaging/windows/pf-vkhdr-layer/): a standalone
cdylib Vulkan implicit layer that appends {A2B10G10R10, HDR10_ST2084} + {RGBA16F, scRGB}
to vkGetPhysicalDeviceSurfaceFormats[2]KHR (no need to hook vkCreateSwapchainKHR — the
ICD doesn't validate the color space there). Self-gated on the surface monitor's actual
advanced-color state (DisplayConfig GET_ADVANCED_COLOR_INFO), so it is a complete no-op
on SDR sessions and real monitors (dedup). Always-on (registry-discovered) so it works
regardless of how a game is launched — env-scoping silently fails for already-running
Steam. Escape hatches: DISABLE_PF_VKHDR, PF_VKHDR_EXCLUDE, and a built-in kernel-anti-
cheat denylist.
The installer builds/signs/stages it and registers it under
HKLM64\SOFTWARE\Khronos\Vulkan\ImplicitLayers (opt-out "Install the HDR Vulkan layer"
task); windows-host CI fmt+clippy-gates it (msvc-only FFI).
Live-validated on the RTX box: Doom: The Dark Ages enables HDR over the pf-vdisplay
virtual display.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
764c814483 |
refactor: rename pf-vdisplay-proto -> pf-driver-proto (it spans all drivers)
The shared host<->driver ABI crate already contains more than the virtual display: the IDD-push frame ring + control plane AND the gamepad shared-memory layouts (XusbShm / PadShm). "pf-vdisplay-proto" was a misnomer — the name now represents all the drivers it serves. Mechanical rename, no behavior change: - git mv crates/pf-vdisplay-proto -> crates/pf-driver-proto (package name + path-deps in the host crate and the driver workspace). - pf_vdisplay_proto -> pf_driver_proto across host + driver Rust, both Cargo.lock files, the workspace members, the CI path triggers (windows-drivers.yml), and the docs/INF comments. The runtime Global\pfvd-* shared-object names are a SEPARATE contract and are deliberately untouched (host<->driver name matching). - The pf-vdisplay DRIVER crate + its INF service name (Root\pf_vdisplay, UmdfService=pf_vdisplay, pf_vdisplay.dll) are unchanged — only the full `pf_vdisplay_proto` token was replaced, never the `pf_vdisplay` driver name. Linux-verified: cargo test -p pf-driver-proto (const size-asserts compile) + cargo clippy -p punktfunk-host -D warnings clean; Cargo.lock regenerated. The driver-workspace side (path-dep + imports + its Cargo.lock) is Windows-CI-gated. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
33a7ee165b |
fix(windows-drivers): size_of config size (1.0 IddCxStub lacks the version table) + CI builds pf-vdisplay
The versioned IDD_STRUCTURE_SIZE path referenced IddClientVersionHigherThanFramework/ IddStructureCount/IddStructures — LNK2019 unresolved, because the WDK links the iddcx 1.0 IddCxStub which lacks those (they are >=1.4). We target 1.10 against a current framework (higher==false) where size_of is exactly the versioned result, so use it directly (the surface-assert refs linked only because they were DCE-eliminated). pf-vdisplay now COMPILES + LINKS IddCxStub on the box (263,680B). Point windows-drivers.yml at the whole workspace + clear FORCE_INTEGRITY on pf_vdisplay.dll; drop the obsolete UINT diagnostic dump. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
304a374325 |
ci(windows-drivers): dump generated iddcx.rs structure on failure (diagnostic)
UINT fails to resolve despite a top-level `pub type UINT` in the same scope as the working `use crate::types::*` — error count byte-identical before/after the fix. Add an if:always() step dumping the generated module structure + UINT-use context to pinpoint the scope mismatch (RTX box rebooted to Proxmox, so CI is the only validator). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
19ca676549 |
feat(windows-drivers): clear FORCE_INTEGRITY for self-signed driver load (M0)
wdk-build links UMDF drivers with /INTEGRITYCHECK unconditionally (no opt-out), so the self-signed DLL would be refused by Code Integrity (3004/3089). Add a deterministic, idempotent, reusable packaging step (packaging/windows/clear-force-integrity.ps1) that clears the PE IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY bit (0x0080 @ e_lfanew+0x5e) and verifies — the gamepad recipe, no longer hand-run. driver-build now inspects the bit (before) then clears+verifies it. Real drivers will: build -> clear -> sign .dll -> Inf2Cat -> sign .cat. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |