Files
punktfunk/packaging/flatpak/README.md
T
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

179 lines
9.5 KiB
Markdown

# 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`](../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).
## 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
```
## 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:
```sh
# 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:
```sh
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`](../../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":
```sh
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):
```sh
# 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`](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`](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.