Files
punktfunk/packaging/flatpak
enricobuehler d9d495a53e
apple / swift (push) Failing after 40s
ci / rust (push) Has been cancelled
ci / web (push) Has been cancelled
ci / docs-site (push) Has been cancelled
ci / bench (push) Has been cancelled
deb / build-publish (push) Has been cancelled
decky / build-publish (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
rpm / build-publish (bazzite, punktfunk-fedora-rpm) (push) Has been cancelled
rpm / build-publish (fedora-44, punktfunk-fedora44-rpm) (push) Has been cancelled
flatpak / build-publish (push) Has been cancelled
android / android (push) Successful in 4m53s
feat(flatpak): host a signed OSTree repo at flatpak.unom.io for flatpak update
The CI only shipped a single-file .flatpak bundle, which has no remote — users
couldn't `flatpak update`. Keep the bundle (Decky fallback) but also sign the
OSTree repo flatpak-builder already produces and publish it to a shared,
reusable unom-wide remote.

- flatpak.yml: pin --default-branch=stable; import the signing key and
  build-update-repo --gpg-sign; generate unom.flatpakrepo + the app .flatpakref
  + index.html; rsync the repo to unom-1 and bring up a static Caddy container.
  The step no-ops until FLATPAK_GPG_PRIVATE_KEY/DEPLOY_* exist (build stays green).
- packaging/flatpak/server/: compose.production.yml + Caddyfile (static file
  server on :3230, mirrors docker.yml deploy-docs).
- unom-flatpak.gpg: committed public signing key (base64 -> GPGKey= in the descriptors).
- README: hosted repo is now the recommended install; documents the one-time
  infra (edge Caddy vhost, infra port 3230, DNS, the GPG secret).

Edge Caddy vhost + infra port allowlist + the secret are applied out-of-band.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 21:07:27 +00:00
..

punktfunk client — Flatpak (Steam Deck / SteamOS, and any flatpak distro)

The native Linux client (crate punktfunk-client-linux, binary punktfunk-client) is published two ways by CI (.gitea/workflows/flatpak.yml), on every push to main (a rolling 0.0.1-ciN.<sha> build) and on v* tags (a clean X.Y.Z):

  1. Hosted OSTree repo at https://flatpak.unom.io (recommended) — a GPG-signed Flatpak remote served by a static Caddy container on unom-1, so users install once and then flatpak update. Shared unom-wide repo (remote name unom), reusable by other unom apps under the same signing key. See "Install (recommended)" below.
  2. Single-file .flatpak bundle in Gitea's generic package registry (unom org) — the no-remote fallback the Decky plugin consumes (stable latest/punktfunk-client.flatpak URL) and the offline/manual path. On tags it's also attached to the Gitea release.

The host is NOT a flatpak (it needs unsandboxed /dev/uinput + zero-copy NVENC — see ../README.md "Why not Flatpak"). Only the client is sandbox-friendly.

Why flatpak for the Steam Deck

SteamOS /usr is read-only and image-based, and the system is missing libadwaita and libSDL3 — so a bare punktfunk-client binary dropped into ~/.local/bin won't run. Flatpak is the Deck's native, update-survivable app path (the user already runs Moonlight and chiaki-ng as flatpaks), and the bundle carries libadwaita (from org.gnome.Platform//50) + a bundled SDL3, with HEVC-capable FFmpeg supplied automatically by the runtime's codecs-extra extension.

App id: io.unom.Punktfunk (matches the Apple bundle id family and the Decky plugin's flatpak fallback).

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:

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):

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:

flatpak update                    # or: flatpak update io.unom.Punktfunk

Install on the Deck via the bundle (no-remote fallback)

The generic registry is a plain HTTP file store, so just download the bundle and install it per-user (no root, survives SteamOS updates). This is what the Decky plugin uses; the hosted repo above is the better path for a human on the Deck:

# Pick a version: a tag like 1.2.3, or the newest main build's 0.0.1-ciN.gSHA.
VER=1.2.3
URL="https://git.unom.io/api/packages/unom/generic/punktfunk-client-flatpak/$VER/punktfunk-client-$VER.flatpak"

# Flathub must be enabled (it is on the Deck) so the GNOME runtime + ffmpeg-full pull in:
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo

curl -fL -o /tmp/punktfunk-client.flatpak "$URL"
flatpak install --user --bundle /tmp/punktfunk-client.flatpak

Run it:

flatpak run io.unom.Punktfunk                 # GUI host list (mDNS)
flatpak run io.unom.Punktfunk --connect HOST:PORT

The Decky plugin launches exactly this (flatpak run io.unom.Punktfunk --connect …) once installed — see ../../clients/decky/README.md.

Updating the bundle install

If you installed from the bundle (not the hosted repo), it has no remote to track, so updates are "download the newer bundle and reinstall":

flatpak install --user --bundle /tmp/punktfunk-client.flatpak   # same command, newer file

Installs from https://flatpak.unom.io instead just take flatpak update (see "Install (recommended)" above).

Build locally / the CI fallback

CI builds this in a --privileged Fedora container, because flatpak-builder runs bubblewrap, which needs user namespaces the default Docker executor denies. If the Gitea runner can't grant --privileged (the job fails at flatpak-builder with "Creating new namespace failed: Operation not permitted"), build it out-of-band and upload by hand. The easiest place is on the Deck itself (it can run org.flatpak.Builder user-scope, no root):

# On the Deck (or any flatpak box), one-time:
flatpak install --user -y flathub org.flatpak.Builder

# build-flatpak.sh auto-detects org.flatpak.Builder, generates cargo-sources.json (or reuses an
# existing one — see below), builds, and exports dist/punktfunk-client-<version>.flatpak:
bash packaging/flatpak/build-flatpak.sh

# Upload to the generic registry (PAT with write:package):
curl -fsS --user "enricobuehler:$REGISTRY_TOKEN" \
  --upload-file dist/punktfunk-client-*.flatpak \
  "https://git.unom.io/api/packages/unom/generic/punktfunk-client-flatpak/0.0.1-manual/punktfunk-client.flatpak"

cargo-sources.json generation needs python3 + aiohttp + tomlkit, which the Deck lacks. Generate it on a dev box (build-flatpak.sh does it, or run the upstream flatpak-cargo-generator.py Cargo.lock -o packaging/flatpak/cargo-sources.json), rsync it next to the manifest, and build-flatpak.sh reuses it (it only regenerates when the file is absent or FORCE_GEN=1).

The Mac build host cannot build a Linux flatpak (no flatpak-builder for macOS), and home-worker-2 has no flatpak and no passwordless sudo to install it — so the Deck or the privileged CI container are the only two viable build sites.

Manifest

io.unom.Punktfunk.yml. Runtime org.gnome.Platform//50 (GTK 4.20 + libadwaita 1.8 ≥ the crate floors of v4_16 / v1_5), built on freedesktop-sdk 25.08, with two build-time SDK extensions: org.freedesktop.Sdk.Extension.rust-stable (→ //25.08, rustc 1.96 — the GTK4 dep chain, e.g. pango-sys 0.22, needs ≥ 1.92, which the EOL GNOME-48 / 24.08 rust-stable at 1.89 could not provide) and org.freedesktop.Sdk.Extension.llvm20 (libclang, needed by bindgen in ffmpeg-sys-next / sdl3-sys). HEVC-capable libavcodec (soname 61, accepted by ffmpeg-next 8.x) is supplied automatically at runtime by the freedesktop codecs-extra extension point (auto-downloaded with the runtime; no app-side codec declaration). A bundled SDL3 3.4.10 module (pinned to match sdl3-sys 0.6.6+SDL-3.4.10), and finish-args for Wayland + --device=all (GPU/VAAPI render node + evdev + the hidraw char-devices SDL3 needs for DualSense)

  • --socket=pulseaudio (PipeWire-pulse: playback + mic) + --share=network. Alongside it: io.unom.Punktfunk.desktop, io.unom.Punktfunk.metainfo.xml, io.unom.Punktfunk.svg (all installed by the manifest). cargo-sources.json (the offline crate cache) is a pure function of Cargo.lock; CI regenerates it each build and it is gitignored — generate it on any box with network + python3/aiohttp/tomlkit (build-flatpak.sh does this automatically) and, for a build host that lacks those (the Deck), rsync the generated file in alongside the manifest.

Hosting the repo (unom-1) + one-time setup

The OSTree repo flatpak-builder produces is GPG-signed in CI and rsynced to unom-1, where a tiny static Caddy container (server/compose.production.yml + server/Caddyfile, port 3230) serves the ./site tree (repo/ + unom.flatpakrepo + io.unom.Punktfunk.flatpakref + index.html). The edge Caddy on home-reverse-proxy-1 fronts it at https://flatpak.unom.io. The CI deploy step no-ops until the secret + infra exist, so it won't redden builds mid-setup.

Signing key: dedicated RSA-4096 key unom Flatpak Repo <flatpak@unom.io>. Public key committed at unom-flatpak.gpg (its base64 goes into the .flatpakrepo/.flatpakref GPGKey=); private key (ASCII-armored, then base64) lives only in the CI secret.

One-time setup (mirrors any new unom DMZ service — see the deploy-infra notes):

  1. Secret FLATPAK_GPG_PRIVATE_KEY on this repo = base64 of the armored private key (gpg --armor --export-secret-keys <fpr> | base64 -w0). DEPLOY_* + REGISTRY_TOKEN already exist.
  2. Edge Caddy on home-reverse-proxy-1 (/home/caddy/caddy/Caddyfile, apply by hand + ./reload.sh): flatpak.unom.io { reverse_proxy 192.168.50.50:3230 }
  3. Port allowlist: add 3230 to caddy_target_ports in unom/infra (proxmox/unom-1) + terraform apply.
  4. DNS: ensure flatpak.unom.io resolves to the edge proxy.

Re-signing/rotation: regenerate the key, replace unom-flatpak.gpg + the secret; every client must re-add the remote (the GPGKey changed), so rotate rarely.

Alternatives considered

  • Hosted OSTree repo (chosen): the only option that gives flatpak update. We self-host the static tree on unom-1 behind Caddy (Gitea has no flatpak/ostree registry); the build already produces the repo, so the marginal cost is GPG signing + an rsync + a 10-line static container.
  • Generic registry bundle (kept as fallback): one curl to publish, one flatpak install --bundle to consume; mirrors the deb/rpm curl-upload pattern. No auto-update — this is what the Decky plugin pulls (stable latest/punktfunk-client.flatpak), plus the offline/manual path.
  • Release attachment: also done on tags, good for a human-facing download page.
  • Flathub (deferred): best discoverability + zero hosting, but a separate submission/review process and less control; revisit once the client is past scaffold quality.