ci / rust-arm64 (push) Successful in 1m45s
docker / builders (--build-arg FEDORA_VERSION=44, ci/fedora-rpm.Dockerfile, punktfunk-fedora44-rpm, -f44) (push) Successful in 7s
docker / builders (ci/android-ci.Dockerfile, punktfunk-android-ci) (push) Successful in 6s
docker / builders (ci/arch-ci.Dockerfile, punktfunk-arch-ci) (push) Successful in 6s
docker / builders (ci/fedora-rpm.Dockerfile, punktfunk-fedora-rpm) (push) Successful in 6s
docker / builders (ci/rust-ci-noble.Dockerfile, punktfunk-rust-ci-noble) (push) Successful in 6s
docker / builders (ci/rust-ci.Dockerfile, punktfunk-rust-ci) (push) Successful in 8s
docker / apps (., web/Dockerfile, punktfunk-web) (push) Successful in 11s
docker / apps (docs-site, docs-site/Dockerfile, punktfunk-docs) (push) Successful in 13s
ci / web (push) Successful in 4m2s
ci / docs-site (push) Successful in 4m7s
docker / builders-arm64cross (push) Successful in 15s
docker / deploy-docs (push) Successful in 35s
ci / rust (push) Successful in 7m10s
flatpak / build-publish (push) Successful in 7m23s
The flathub fetch has failed 10/10 retries for months, blamed on fleet load and DNS tuning. It is neither. Measured on home-runner-2, all inside ONE container: getent resolved dl.flathub.org, curl fetched the same URL with HTTP 200 (auto and -4), and flatpak still died '[6] Could not resolve hostname'. Rewriting resolv.conf to a real nameserver didn't help; the default bridge failed too; --network host works every time. So it is ostree's own resolver against Docker's embedded 127.0.0.11, and removing that resolver from the path is the fix. retry.sh stays as the backstop for genuine upstream blips. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
407 lines
25 KiB
YAML
407 lines
25 KiB
YAML
# 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 <downloaded>.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/<version>/<file>
|
||
# GET https://git.unom.io/api/packages/unom/generic/punktfunk-client-flatpak/<version>/<file>
|
||
# 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
|
||
# One pending run per workflow+ref: a newer push supersedes the queued/running one and cancels
|
||
# it (a canary only needs the latest commit; each release tag is its own ref so tag runs never
|
||
# cancel each other). Keeps a busy push cadence from piling ~10 queued runs per commit onto the
|
||
# runner fleet. Gitea honors this for push triggers (PR triggers: see gitea#35933).
|
||
concurrency:
|
||
group: ${{ github.workflow }}-${{ github.ref }}
|
||
cancel-in-progress: true
|
||
|
||
|
||
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.
|
||
# The bundle ships BOTH client binaries (shell + Vulkan session), so every crate in either
|
||
# binary's dependency closure must be listed here.
|
||
paths:
|
||
- 'clients/linux/**'
|
||
- 'clients/session/**'
|
||
- 'crates/punktfunk-core/**'
|
||
- 'crates/pf-client-core/**'
|
||
- 'crates/pf-presenter/**'
|
||
- 'crates/pf-console-ui/**'
|
||
- '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 # ~/<dir> 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).
|
||
#
|
||
# --network host is what finally fixed the years-long "Could not resolve
|
||
# hostname" on every flathub fetch. MEASURED 2026-07-30 on home-runner-2, all
|
||
# in ONE container: `getent hosts dl.flathub.org` resolved, `curl` got HTTP
|
||
# 200 (both auto and -4), and flatpak still failed error [6] — so it was never
|
||
# DNS config, the resolver, the docker version, or the per-job network. It is
|
||
# ostree's own resolver refusing to work through Docker's embedded 127.0.0.11
|
||
# (proven: rewriting resolv.conf to a real nameserver did NOT help, and the
|
||
# default bridge failed too, while the host netns — no embedded resolver in the
|
||
# path at all — works every time). Host networking also means this job no
|
||
# longer needs the nsswitch surgery below to be lucky.
|
||
image: fedora:43
|
||
options: --privileged --network host
|
||
steps:
|
||
# DNS fix — MUST run before any network step. fedora:43's nsswitch.conf is
|
||
# `hosts: files myhostname resolve [!UNAVAIL=return] dns`: the `resolve`
|
||
# module is nss-resolve (systemd-resolved), which isn't running in a CI
|
||
# container. glibc getaddrinfo (git, curl, dnf) mostly falls through to the
|
||
# `dns` module -> Docker's embedded 127.0.0.11 -> works. flatpak/ostree's
|
||
# resolver does NOT fall through: the absent-daemon socket connect trips
|
||
# `[!UNAVAIL=return]` and it reports "[6] Could not resolve hostname". This
|
||
# was masked as an intermittent "busy runner drops DNS" and papered over
|
||
# with retry.sh — but it's deterministic on a runner where the resolve
|
||
# module tips that way (surfaced when jobs began landing on home-runner-2).
|
||
# Drop the `resolve` entry so host lookups use plain `dns`. NOTE: this alone is not
|
||
# sufficient — the Tooling step's dnf install pulls a systemd package upgrade whose RPM
|
||
# trigger re-runs authselect and regenerates this file, undoing the fix. It's reapplied
|
||
# there, right before the first `flatpak` network call.
|
||
- name: Fix container DNS (drop nss-resolve)
|
||
run: |
|
||
sed -i 's/resolve \[!UNAVAIL=return\] //' /etc/nsswitch.conf
|
||
# History: this step used to ALSO force glibc onto TCP DNS (`options use-vc`) because
|
||
# the runner fleet's Docker embedded resolver dropped UDP lookups under parallel-job
|
||
# load (investigated 2026-07-11; v0.15.0/v0.16.0 each burned retry.sh's whole budget).
|
||
# That root cause is now fixed at the infra level (2026-07-22): the runner host runs a
|
||
# local dnsmasq cache on the docker bridge and daemon.json points every job container
|
||
# at it, so lookups terminate on-box instead of crossing the saturated uplink — the
|
||
# UDP path is reliable again. The TCP path through the same chain proved FLAKY under
|
||
# fleet concurrency (flatpak remote-add failed 10/10 with instant NXDOMAIN while dnf
|
||
# in the same container resolved fine), so `use-vc` flipped from mitigation to sole
|
||
# cause of this leg's failures — removed. retry.sh (10×) stays as the backstop for
|
||
# genuine upstream blips.
|
||
cat /etc/resolv.conf || true
|
||
|
||
# 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
|
||
# Belt-and-suspenders: keep nsswitch on plain `dns` even if this dnf transaction pulled
|
||
# in a fresh systemd-resolved (it does — flatpak recommends xdg-desktop-portal ->
|
||
# pipewire/wireplumber -> systemd-networkd/-resolved). Verified on the real runner
|
||
# (2026-07-11) this dnf install does NOT actually rewrite /etc/nsswitch.conf — no
|
||
# authselect trigger fires — so this line alone was never the fix for the failures
|
||
# below. See the retry.sh bump for the real cause.
|
||
sed -i 's/resolve \[!UNAVAIL=return\] //' /etc/nsswitch.conf
|
||
# Flathub provides the GNOME runtime/SDK + the rust-stable + ffmpeg-full extensions.
|
||
#
|
||
# ROOT CAUSE (confirmed 2026-07-11 by watching a live run on home-runner-1): this is
|
||
# NOT a deterministic nsswitch/DNS-config bug. gitea-runner-fleet on home-runner-1 is
|
||
# a SHARED, resource-capped fleet (3 replicas, --cpus 5 --memory 7g each, on a 16c/24G
|
||
# box) serving punktfunk AND several other orgs' repos (chatwoot, website, cms, data,
|
||
# infra, tempblade, played...). A push to punktfunk's main fans out ~8 workflows at
|
||
# once, and this runner box's Docker embedded DNS resolver (127.0.0.11) genuinely
|
||
# drops UDP lookups under that concurrent load — exactly what retry.sh's own header
|
||
# comment already documented. Reproduced: manually dispatching this job when the box
|
||
# was idle (1 container running) succeeded immediately, with or without the sed above.
|
||
# 5 attempts (~100s total) isn't always enough to outlast a synchronized multi-org
|
||
# burst, so bump the bootstrap fetch's budget — never fail the job on a single-shot
|
||
# fetch. Same treatment for every network command below.
|
||
bash scripts/ci/retry.sh 10 flatpak remote-add --user --if-not-exists flathub \
|
||
https://dl.flathub.org/repo/flathub.flatpakrepo
|
||
git config --global --add safe.directory "$PWD"
|
||
|
||
# This job was the fleet's single heaviest network consumer: every run re-downloaded
|
||
# the GNOME runtime + SDK + llvm/rust/ffmpeg extensions (multi-GB from Flathub) and
|
||
# every crate source. Both live in well-defined directories, both are idempotently
|
||
# verified/extended by the steps below, and the central cache server restores them
|
||
# at LAN speed — so cache them. Keyed on what actually pins them: the manifest tree
|
||
# (runtimes/extensions) and manifest+Cargo.lock (crate sources + builder state).
|
||
- name: Cache Flathub runtimes
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: ~/.local/share/flatpak
|
||
key: flatpak-runtimes-${{ hashFiles('packaging/flatpak/**') }}
|
||
restore-keys: flatpak-runtimes-
|
||
- name: Cache flatpak-builder state (crate sources, ccache)
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: .flatpak-builder
|
||
key: flatpak-builder-state-${{ hashFiles('Cargo.lock', 'packaging/flatpak/**') }}
|
||
restore-keys: flatpak-builder-state-
|
||
|
||
- name: Version + channel
|
||
# Tag vX.Y.Z -> X.Y.Z on the OSTree `stable` branch (a real release); a main push ->
|
||
# <next-minor>-ciN.g<sha> on the `canary` branch (base one minor ahead of the latest stable
|
||
# tag via scripts/ci/pf-version.sh). 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: |
|
||
eval "$(bash scripts/ci/pf-version.sh)" # -> PF_BASE (one minor ahead of the latest stable tag)
|
||
SHORT=$(echo "$GITHUB_SHA" | cut -c1-8)
|
||
case "$GITHUB_REF" in
|
||
refs/tags/v*) V="${GITHUB_REF_NAME#v}"; BRANCH=stable; ALIAS=latest ;;
|
||
*) V="${PF_BASE}-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 --retry 5 --retry-all-errors --retry-delay 5 -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: Seed the local OSTree repo from the live server (keep BOTH channels in the summary)
|
||
# Each CI run builds only ONE branch (canary on main, stable on a tag). The deploy step's
|
||
# `flatpak build-update-repo` regenerates the repo SUMMARY from whatever refs are in the
|
||
# LOCAL repo, and the rsync publishes it (without --delete). A fresh single-branch local
|
||
# repo therefore produces a single-branch summary that CLOBBERS the other channel on the
|
||
# server — the exact bug that made `app/io.unom.Punktfunk/x86_64/stable` unresolvable
|
||
# ("No such ref") after a canary main-push overwrote the post-release summary, even though
|
||
# the stable commit's objects were still on disk. Fix: mirror the published repo DOWN first,
|
||
# so the local repo carries every existing branch; the build below then only ADDS this run's
|
||
# commit and the regenerated+signed summary keeps both channels. No-op on a fresh repo (first
|
||
# publish) or when the deploy secrets aren't set (the build still produces a valid bundle).
|
||
env:
|
||
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 "${DEPLOY_HOST:-}" ] || [ -z "${DEPLOY_SSH_KEY:-}" ]; then
|
||
echo "::warning::DEPLOY_* not set — no seed; building a fresh single-branch repo."
|
||
exit 0
|
||
fi
|
||
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}"
|
||
mkdir -p "$PWD/repo"
|
||
# Pull the currently-published repo (all channels' objects + refs) into the repo the build
|
||
# will extend. No --delete: the local repo starts empty, so this only ADDS.
|
||
# Probe first (retried) whether a published repo exists at all: ONLY that case may
|
||
# continue with a fresh repo. A transient network failure must FAIL the job instead —
|
||
# a blanket `rsync || continue` here is exactly how a flaky link produces the
|
||
# single-branch summary that clobbers the other channel (the bug described above).
|
||
PRESENT=$(bash scripts/ci/retry.sh 5 $SSH "$DEST" \
|
||
"[ -d $DEPLOY_DIR/site/repo/refs ] && echo present || echo absent")
|
||
if [ "$PRESENT" = present ]; then
|
||
bash scripts/ci/retry.sh 5 rsync -az --info=stats1 -e "$SSH" \
|
||
"$DEST:$DEPLOY_DIR/site/repo/" "$PWD/repo/"
|
||
else
|
||
echo "::warning::no published repo on the server (first publish) — continuing fresh"
|
||
fi
|
||
echo "seeded refs:"; ls "$PWD/repo/refs/heads/app/$APP_ID/x86_64/" 2>/dev/null || echo " (none)"
|
||
|
||
- name: Prefetch deps + sources (retried — the network phase, split off the build)
|
||
run: |
|
||
# All of the job's heavy network I/O happens HERE, retried, so a dropped DNS lookup
|
||
# or TCP dial costs a backoff-retry instead of the whole (long) compile:
|
||
# 1) --install-deps-only pulls everything the manifest declares from Flathub: 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).
|
||
# 2) --download-only fetches every source (all crates in cargo-sources.json) into
|
||
# the .flatpak-builder state dir. Both are resumable/idempotent, so re-running
|
||
# after a partial failure is safe and cheap.
|
||
# --disable-rofiles-fuse is the container-safe path (no FUSE).
|
||
# 10 attempts (~9min budget), matching the remote-add bootstrap above — same shared,
|
||
# load-sensitive runner, same flathub.org resolution path.
|
||
bash scripts/ci/retry.sh 10 flatpak-builder --user --force-clean --disable-rofiles-fuse \
|
||
--install-deps-from=flathub --install-deps-only \
|
||
"$PWD/build-dir" "$MANIFEST"
|
||
bash scripts/ci/retry.sh 10 flatpak-builder --user --force-clean --disable-rofiles-fuse \
|
||
--download-only \
|
||
"$PWD/build-dir" "$MANIFEST"
|
||
|
||
- name: Build the flatpak (offline — deps + sources prefetched above)
|
||
run: |
|
||
# Everything is already local (state dir warmed by the prefetch step), so this long
|
||
# step needs no network; --install-deps-from stays as a no-op safety net.
|
||
# --default-branch=$FLATPAK_BRANCH pins the ref to app/io.unom.Punktfunk/x86_64/<branch>
|
||
# (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) Versioned URL. A re-tagged release re-fires this workflow and the registry 409s on
|
||
# duplicate uploads — delete any prior copy first (404 on the first publish is fine).
|
||
curl -fsS -o /dev/null --user "enricobuehler:$TOKEN" -X DELETE \
|
||
"$BASE/$VERSION/$BUNDLE" || true
|
||
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"
|
||
# The regenerated summary advertises exactly these refs — must include EVERY channel that
|
||
# has ever published (the seed step ensures the other channel's commit is present). If this
|
||
# ever shows only one branch on a repo that had two, the seed didn't run — investigate.
|
||
echo "published summary advertises:"; ls "$PWD/repo/refs/heads/app/$APP_ID/x86_64/" 2>/dev/null || echo " (none)"
|
||
# 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 <<EOF
|
||
[Flatpak Repo]
|
||
Title=unom
|
||
Url=$REPO_URL/repo/
|
||
Homepage=https://punktfunk.unom.io
|
||
Comment=unom Flatpak applications
|
||
GPGKey=$GPGKEY
|
||
EOF
|
||
# Two refs, one per channel. Both descriptor files are regenerated every run and rsync'd
|
||
# without --delete; the repo SUMMARY carries both branches because the build was seeded
|
||
# from the live repo above (so build-update-repo below re-signs a summary listing every
|
||
# published channel, not just this run's). The stable ref resolves for good once any
|
||
# release has built the `stable` branch. A box installs ONE; `flatpak update` then tracks
|
||
# that channel's branch.
|
||
write_ref() { # <filename> <branch> <title>
|
||
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</title>
|
||
<h1>unom Flatpak repository</h1>
|
||
<p>Install the Punktfunk Linux client (auto-adds Flathub for the GNOME runtime, then tracks updates).</p>
|
||
<p><b>Stable</b> (recommended — only moves on releases):</p>
|
||
<pre>flatpak install --user $REPO_URL/${APP_ID}.flatpakref
|
||
flatpak run $APP_ID</pre>
|
||
<p><b>Canary</b> (latest main build, unstable):</p>
|
||
<pre>flatpak install --user $REPO_URL/${APP_ID}.Canary.flatpakref</pre>
|
||
<p>Or add the whole remote: <code>flatpak remote-add --user --if-not-exists unom $REPO_URL/unom.flatpakrepo</code></p>
|
||
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}"
|
||
# All idempotent — retried because the runner's link to unom-1 drops TCP dials under
|
||
# load (the same flake that hits docker.yml's deploy-docs with "dial tcp: i/o timeout").
|
||
bash scripts/ci/retry.sh 5 $SSH "$DEST" "mkdir -p ~/$DEPLOY_DIR/site/repo"
|
||
bash scripts/ci/retry.sh 5 rsync -az --info=stats1 -e "$SSH" repo/ "$DEST:$DEPLOY_DIR/site/repo/"
|
||
bash scripts/ci/retry.sh 5 rsync -az -e "$SSH" site/unom.flatpakrepo "site/${APP_ID}.flatpakref" "site/${APP_ID}.Canary.flatpakref" site/index.html "$DEST:$DEPLOY_DIR/site/"
|
||
bash scripts/ci/retry.sh 5 rsync -az -e "$SSH" packaging/flatpak/server/compose.production.yml packaging/flatpak/server/Caddyfile "$DEST:$DEPLOY_DIR/"
|
||
bash scripts/ci/retry.sh 5 $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"
|