# Build the native punktfunk Linux CLIENT as a single-file Flatpak bundle and publish it to # Gitea's GENERIC package registry, so the Steam Deck (and any flatpak distro) installs it # the SteamOS-native, update-survivable way: `flatpak install --user .flatpak`. # (The HOST stays an RPM/deb — it needs unsandboxed /dev/uinput + zero-copy NVENC; only the # CLIENT is sandbox-friendly. See packaging/README.md and packaging/flatpak/README.md.) # # Gitea has NO flatpak/ostree registry, so the bundle lives in the generic registry: # PUT https://git.unom.io/api/packages/unom/generic/punktfunk-client-flatpak// # GET https://git.unom.io/api/packages/unom/generic/punktfunk-client-flatpak// # On tags the bundle is ALSO attached to the Gitea release (mirrors release.yml's DMG). # # PRIVILEGED-BUILD CONSTRAINT: flatpak-builder runs bubblewrap, which needs user namespaces. # In a Gitea/act_runner Docker executor that means the job container must be --privileged # (the same runner already runs `docker build` in docker.yml, so its Docker daemon allows it). # If your runner CANNOT grant --privileged, this job will fail at `flatpak-builder` with # "Creating new namespace failed: Operation not permitted" — see the fallback in # packaging/flatpak/README.md (build on the Deck via org.flatpak.Builder, or on a Linux box, # then upload with the curl line below). # # REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope (shared with deb/rpm/docker). name: flatpak on: push: branches: [main] # The flatpak is the CLIENT — only rebuild when the client/core/manifest change, not on every # design/host push (this is a heavy flatpak-builder run). Tags (v*, the client release) build too. paths: - 'clients/linux/**' - 'crates/punktfunk-core/**' - 'packaging/flatpak/**' - 'Cargo.lock' - '.gitea/workflows/flatpak.yml' tags: ['v*'] workflow_dispatch: env: REGISTRY: git.unom.io OWNER: unom APP_ID: io.unom.Punktfunk MANIFEST: packaging/flatpak/io.unom.Punktfunk.yml PACKAGE: punktfunk-client-flatpak # generic-registry package name REPO_URL: https://flatpak.unom.io # shared unom OSTree repo (reusable across unom apps) DEPLOY_DIR: unom-flatpak # ~/ on unom-1 (compose + ./site tree) jobs: build-publish: runs-on: ubuntu-24.04 timeout-minutes: 120 container: # Fedora ships a recent flatpak + flatpak-builder + the kernel userns support. # --privileged is required for bubblewrap inside the Docker executor (see header). image: fedora:43 options: --privileged steps: # fedora:43 has no node, but actions/checkout (a JS action) needs it. A plain `run:` step # executes via the container shell (no node needed), so install node BEFORE checkout. - name: node for the JS actions run: dnf -y install nodejs - uses: actions/checkout@v4 - name: Tooling run: | # flatpak-cargo-generator.py (master) needs aiohttp + tomlkit (NOT the old `toml`). # gnupg2/rsync/openssh-clients: sign the OSTree repo + rsync it to unom-1 (see the deploy step). dnf -y install flatpak flatpak-builder git python3 python3-aiohttp python3-tomlkit curl jq \ gnupg2 rsync openssh-clients # Flathub provides the GNOME runtime/SDK + the rust-stable + ffmpeg-full extensions. flatpak remote-add --user --if-not-exists flathub \ https://dl.flathub.org/repo/flathub.flatpakrepo git config --global --add safe.directory "$PWD" - name: Version + channel # Tag vX.Y.Z -> X.Y.Z on the OSTree `stable` branch (a real release); a main push -> # 0.3.0-ciN.g on the `canary` branch. The two branches live side-by-side in one repo # (rsync runs without --delete), each tracked by its own .flatpakref, so `flatpak update` # on a stable box never jumps to a canary build. The generic-registry version string allows # letters/dots/hyphens. run: | SHORT=$(echo "$GITHUB_SHA" | cut -c1-8) case "$GITHUB_REF" in refs/tags/v*) V="${GITHUB_REF_NAME#v}"; BRANCH=stable; ALIAS=latest ;; *) V="0.3.0-ci${GITHUB_RUN_NUMBER}.g${SHORT}"; BRANCH=canary; ALIAS=canary ;; esac echo "VERSION=$V" >> "$GITHUB_ENV" echo "BUNDLE=punktfunk-client-${V}.flatpak" >> "$GITHUB_ENV" echo "FLATPAK_BRANCH=$BRANCH" >> "$GITHUB_ENV" echo "ALIAS=$ALIAS" >> "$GITHUB_ENV" echo "flatpak version $V -> branch '$BRANCH' alias '$ALIAS'" - name: Generate offline cargo sources # flatpak builds with no network; vendor every crate from Cargo.lock into # cargo-sources.json next to the manifest (referenced by the manifest's # punktfunk-client module). # # Prune the microsoft/windows-rs git crates first: they belong to # punktfunk-client-windows, which the flatpak never builds, and leaving them in makes # flatpak-builder full-clone that multi-GB repo at build time → "No space left on # device" (see packaging/flatpak/prune-windows-lock.py). The committed Cargo.lock is # untouched; cargo --offline only needs sources for the crates it compiles. run: | curl -fsSL -o /tmp/flatpak-cargo-generator.py \ https://raw.githubusercontent.com/flatpak/flatpak-builder-tools/master/cargo/flatpak-cargo-generator.py python3 packaging/flatpak/prune-windows-lock.py Cargo.lock /tmp/Cargo.flatpak.lock python3 /tmp/flatpak-cargo-generator.py /tmp/Cargo.flatpak.lock \ -o packaging/flatpak/cargo-sources.json - name: Build the flatpak (install deps from Flathub, offline build) run: | # --install-deps-from=flathub pulls everything the manifest declares: the GNOME 50 # runtime/SDK + the rust-stable (//25.08, rustc 1.96) and llvm20 SDK extensions, plus # the runtime's auto codecs-extra (HEVC libavcodec). --disable-rofiles-fuse is the # container-safe path (no FUSE). # --default-branch=$FLATPAK_BRANCH pins the ref to app/io.unom.Punktfunk/x86_64/ # (canary or stable) so the matching hosted .flatpakref resolves deterministically # (manifest sets no branch). flatpak-builder --user --force-clean --disable-rofiles-fuse \ --default-branch="$FLATPAK_BRANCH" \ --install-deps-from=flathub \ --repo="$PWD/repo" \ "$PWD/build-dir" "$MANIFEST" - name: Export single-file bundle run: | # Branch must be passed explicitly (matches --default-branch above); build-bundle # otherwise defaults to `master` and errors "Refspec … not found". flatpak build-bundle "$PWD/repo" "$BUNDLE" "$APP_ID" "$FLATPAK_BRANCH" ls -lh "$BUNDLE" - name: Publish to the Gitea generic registry env: TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | BASE="https://$REGISTRY/api/packages/$OWNER/generic/$PACKAGE" # 1) Immutable, versioned URL. curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$BUNDLE" \ "$BASE/$VERSION/$BUNDLE" echo "published $BASE/$VERSION/$BUNDLE" # 2) Channel alias (stable release -> latest/, canary main build -> canary/) for the # Decky fallback + scripts. The generic registry rejects re-uploading an existing # version/file (409), so delete the prior alias file first (ignore 404 on run #1). curl -fsS -o /dev/null --user "enricobuehler:$TOKEN" -X DELETE \ "$BASE/$ALIAS/punktfunk-client.flatpak" || true curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$BUNDLE" \ "$BASE/$ALIAS/punktfunk-client.flatpak" echo "published $BASE/$ALIAS/punktfunk-client.flatpak" # Sign the OSTree repo flatpak-builder already produced and publish it to flatpak.unom.io on # unom-1, so users get `flatpak update` (the single-file bundle above has no remote). Mirrors # docker.yml's deploy-docs (DEPLOY_* = the unom-ci-deploy key). No-ops cleanly until the GPG # secret + DEPLOY_* exist, so the bundle build stays green during setup. - name: Sign + deploy the OSTree repo to unom-1 (flatpak.unom.io) env: FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }} DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }} DEPLOY_USER: ${{ secrets.DEPLOY_USER }} DEPLOY_PORT: ${{ secrets.DEPLOY_PORT }} DEPLOY_SSH_KEY: ${{ secrets.DEPLOY_SSH_KEY }} run: | set -euo pipefail if [ -z "${FLATPAK_GPG_PRIVATE_KEY:-}" ] || [ -z "${DEPLOY_HOST:-}" ]; then echo "::warning::FLATPAK_GPG_PRIVATE_KEY/DEPLOY_* not set — skipping repo deploy (bundle still published)." exit 0 fi # 1) Import the signing key into a throwaway keyring; sign the repo. export GNUPGHOME="$(mktemp -d)"; chmod 700 "$GNUPGHOME" echo "$FLATPAK_GPG_PRIVATE_KEY" | base64 -d | gpg --batch --import KEYID="$(gpg --list-keys --with-colons | awk -F: '/^fpr:/{print $10; exit}')" # build-sign signs the COMMIT objects; build-update-repo signs the SUMMARY. Both are # required — clients with gpg-verify=true verify the commit, so summary-only signing # fails the pull with "GPG verification enabled, but no signatures found". flatpak build-sign "$PWD/repo" "$APP_ID" "$FLATPAK_BRANCH" \ --gpg-sign="$KEYID" --gpg-homedir="$GNUPGHOME" flatpak build-update-repo --generate-static-deltas \ --gpg-sign="$KEYID" --gpg-homedir="$GNUPGHOME" "$PWD/repo" # 2) Build the install descriptors (GPGKey = the committed public key, base64). GPGKEY="$(base64 -w0 packaging/flatpak/unom-flatpak.gpg)" rm -rf site && mkdir -p site cat > site/unom.flatpakrepo < cat > "site/$1" <<EOF [Flatpak Ref] Name=$APP_ID Branch=$2 Url=$REPO_URL/repo/ Title=$3 Homepage=https://punktfunk.unom.io IsRuntime=false GPGKey=$GPGKEY RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo EOF } write_ref "${APP_ID}.flatpakref" stable "Punktfunk" write_ref "${APP_ID}.Canary.flatpakref" canary "Punktfunk (Canary)" cat > site/index.html <<EOF <!doctype html><meta charset=utf-8><title>unom flatpak repo

unom Flatpak repository

Install the Punktfunk Linux client (auto-adds Flathub for the GNOME runtime, then tracks updates).

Stable (recommended — only moves on releases):

flatpak install --user $REPO_URL/${APP_ID}.flatpakref
          flatpak run $APP_ID

Canary (latest main build, unstable):

flatpak install --user $REPO_URL/${APP_ID}.Canary.flatpakref

Or add the whole remote: flatpak remote-add --user --if-not-exists unom $REPO_URL/unom.flatpakrepo

EOF # 3) Ship to unom-1 and (re)start the static server. rsync WITHOUT --delete keeps old # objects so clients mid-update aren't broken; the fresh signed summary advertises latest. install -d -m700 ~/.ssh printf '%s\n' "$DEPLOY_SSH_KEY" > ~/.ssh/deploy; chmod 600 ~/.ssh/deploy SSH="ssh -i $HOME/.ssh/deploy -p ${DEPLOY_PORT:-22} -o StrictHostKeyChecking=accept-new" DEST="${DEPLOY_USER}@${DEPLOY_HOST}" $SSH "$DEST" "mkdir -p ~/$DEPLOY_DIR/site/repo" rsync -az --info=stats1 -e "$SSH" repo/ "$DEST:$DEPLOY_DIR/site/repo/" rsync -az -e "$SSH" site/unom.flatpakrepo "site/${APP_ID}.flatpakref" "site/${APP_ID}.Canary.flatpakref" site/index.html "$DEST:$DEPLOY_DIR/site/" rsync -az -e "$SSH" packaging/flatpak/server/compose.production.yml packaging/flatpak/server/Caddyfile "$DEST:$DEPLOY_DIR/" $SSH "$DEST" "cd ~/$DEPLOY_DIR && docker compose -f compose.production.yml up -d" echo "deployed → $REPO_URL/${APP_ID}.flatpakref" - name: Attach bundle to the Gitea release (stable tags only) if: startsWith(gitea.ref, 'refs/tags/v') env: GITEA_TOKEN: ${{ secrets.REGISTRY_TOKEN }} run: | . scripts/ci/gitea-release.sh RID=$(ensure_release "$GITHUB_REF_NAME" "$GITHUB_REF_NAME" auto) upsert_asset "$RID" "$BUNDLE"