cargo tree -p punktfunk-client-session finds no ffmpeg. The host still does, which is the whole point: pf-encode keeps libavcodec unconditionally and no host workflow, packaging script or licence file was touched. Deleted: crates/pf-ffvk, video_vulkan.rs, video_vaapi.rs, video_libav.rs, the libavcodec half of video_d3d11.rs, the av_log machinery, ffmpeg::codec::Id as the decoder's vocabulary (the quic CODEC_* wire constants now serve, which is why the evidence table was keyed on them), DecodedImage::VkFrame and ::Dmabuf, the presenter's AVVkFrame lane, and the ffmpeg-fallback feature with everything behind it. DrmFrameGuard collapses from an enum to a newtype, which removes an unsafe impl Send. Roughly 25,000 lines. Then the CI, packaging, licensing and docs work the plan's §6 lists: the Windows workflows lose FFMPEG_DIR, PF_FFVK_VULKAN_INCLUDE and their PATH prepend; the MSIX loses its DLL wildcard; the client .deb stops emitting libav sonames on its own because depends come from dpkg-shlibdeps; arch, flatpak and nix drop the dependency; and the README's "FFmpeg 7 or 8" contract narrows to the host. Three defects reached users' machines in the first cut, and none was in the deletion itself. All three desktop Settings UIs offer vulkan, vaapi and d3d11va as stored decoder values, so those strings sit in shipped settings files today. Refusing them by name — which is the correct rule for a stale pin — would have bricked every upgraded client whose owner ever touched that dropdown. They now migrate onto the native rung for the same hardware family, at decoder construction AND at each dialog's lookup, because a legacy value that matches no preset displays as "Automatic" and silently rewrites the user's preference on the next save. M9's evidence filter was deleted on the argument that with no libavcodec twin below, barring an unproven rung removes hardware decode rather than moving down one rung. That is true on Windows and false on Linux for Intel and every unknown vendor id, where prefer_vulkan_first is false and the order is native-vaapi → native-vk: a rung that has decoded nothing anywhere sitting above one that is 250/250 on three drivers. Every Intel Linux desktop would have moved from libavcodec VAAPI, shipping for years, onto pf-vaadec by default — and a rung that constructs and then produces wrong pixels leaves only by the error-streak demotion, which this codebase already documents as not tripping on the B580's strobing. The filter is restored as a narrow, pure, testable rule: an unproven rung yields to a proven one, and to nothing else. Windows deliberately passes no rung below, because that vendor family is the one with a measured wrong-pixel report against Vulkan decode, and trading no evidence for evidence of corruption is the wrong direction. And the notices still said FFmpeg was bundled. The root file is what both desktop clients include_str! and what the MSIX ships, three lines under the new card saying no FFmpeg is bundled; Apple's Acknowledgements said it too, on iOS, tvOS and macOS. The generator now emits four per-client files scoped by transitive closure — 0 FFmpeg mentions in each, verified — while the root file keeps it for the host. That also ends the standing false attribution of ffmpeg-next, GTK4, windows-rs and the NVENC SDK to an iPhone. Windows has no reachable box, so it was compiled instead: a cross clippy at -D warnings on x86_64 and aarch64-pc-windows-msvc with the C toolchain stubbed so build scripts run without linking. That gate immediately caught an include_str! path one directory too deep, which nothing else could have. Gates: container clippy -D warnings, 160 tests, workspace check, both Windows targets clean, client ffmpeg count 0 and host 2. The four decode crates are untouched, so the hardware rungs' 250/250 stands. ⚠ Owed and unrun: no GPU has executed any of this milestone. M8's on-glass software check, M7's D3D11 and VAAPI AV1 hardware legs, and M9's field bake all still want hardware, and the bake window and criteria remain the user's.
279 lines
15 KiB
YAML
279 lines
15 KiB
YAML
# Build the punktfunk-host RPM and publish it to Gitea's RPM package registry, so Bazzite /
|
|
# Fedora Atomic hosts layer + update it with rpm-ostree. Counterpart to deb.yml (apt). Runs in
|
|
# the Fedora 43 builder image (ci/fedora-rpm.Dockerfile) so the RPM's auto library Requires
|
|
# (libavcodec.so.NN, …) match the target's sonames.
|
|
#
|
|
# Registry (public, unom org), group "bazzite":
|
|
# repo file https://git.unom.io/api/packages/unom/rpm/bazzite.repo
|
|
# Box setup (once): see packaging/rpm/README.md
|
|
#
|
|
# REGISTRY_TOKEN: repo Actions secret, a PAT with write:package scope (shared with docker.yml).
|
|
name: rpm
|
|
# 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]
|
|
# Scope canary builds to what this artifact is built FROM — a docs-only or
|
|
# web-only push should not light up the whole fleet. Applies to branch pushes;
|
|
# tag runs are matched by `tags:` (proven by flatpak/windows-msix releases).
|
|
paths:
|
|
- 'crates/**'
|
|
- 'web/**'
|
|
- 'sdk/**'
|
|
- 'packaging/rpm/**'
|
|
- 'packaging/gamescope/**'
|
|
- 'packaging/bazzite/**'
|
|
- 'Cargo.toml'
|
|
- 'Cargo.lock'
|
|
- 'rust-toolchain.toml'
|
|
- 'scripts/ci/**'
|
|
- '.gitea/workflows/rpm.yml'
|
|
# Single project version: a `vX.Y.Z` tag is THE release. main publishes to the `*-canary` rpm
|
|
# groups, tags to the base groups (`bazzite`/`fedora-44`) — separate repos, so the old
|
|
# version-shadow (a release outranking rolling builds in one group) is structurally gone.
|
|
tags: ['v*']
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
REGISTRY: git.unom.io
|
|
OWNER: unom
|
|
RUSTC_WRAPPER: sccache
|
|
SCCACHE_BUCKET: unom-ci-sccache
|
|
SCCACHE_ENDPOINT: https://storage.unom.io
|
|
SCCACHE_REGION: home-central
|
|
AWS_ACCESS_KEY_ID: ${{ secrets.SCCACHE_ACCESS_KEY_ID }}
|
|
AWS_SECRET_ACCESS_KEY: ${{ secrets.SCCACHE_SECRET_ACCESS_KEY }}
|
|
# sccache and incremental compilation are mutually exclusive; CI wants the shared
|
|
# cache, dev boxes keep incremental.
|
|
CARGO_INCREMENTAL: "0"
|
|
|
|
jobs:
|
|
build-publish:
|
|
runs-on: ubuntu-24.04
|
|
# One RPM per target whose ffmpeg soname must match (a binary RPM is soname-coupled to its
|
|
# base): Fedora 43 == Bazzite (libavcodec.so.61), Fedora 44 == the Fedora KDE spin (.so.62).
|
|
# Each builds in its matching builder image and publishes to its own registry group.
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- image: punktfunk-fedora-rpm # Fedora 43 == Bazzite base
|
|
group: bazzite
|
|
fedver: 43
|
|
- image: punktfunk-fedora44-rpm # Fedora 44 == Fedora KDE spin
|
|
group: fedora-44
|
|
fedver: 44
|
|
container:
|
|
image: 192.168.1.58:5010/${{ matrix.image }}:latest
|
|
timeout-minutes: 90
|
|
env:
|
|
CARGO_HOME: /usr/local/cargo
|
|
steps:
|
|
- uses: actions/checkout@v4
|
|
|
|
# Shared compile cache (sccache -> RustFS S3 over the LAN). Baked into the builder
|
|
# images; this fetch keeps the job green while the running :latest predates the bake.
|
|
- name: sccache (no-op once the image bakes it)
|
|
run: |
|
|
command -v sccache >/dev/null 2>&1 || {
|
|
curl -fsSL https://github.com/mozilla/sccache/releases/download/v0.10.0/sccache-v0.10.0-x86_64-unknown-linux-musl.tar.gz \
|
|
| tar -xz --wildcards --strip-components=1 -C /usr/local/bin '*/sccache'
|
|
}
|
|
sccache --version
|
|
|
|
# rpmbuild + git archive need the checkout trusted; cache the crates download.
|
|
# The client link deps are also baked into the fedora-rpm image, but this job runs
|
|
# against the image from the PREVIOUS push (docker.yml bootstrap note) — keep it
|
|
# green across image changes; a no-op once the image has them.
|
|
- name: Prep
|
|
run: |
|
|
git config --global --add safe.directory "$PWD"
|
|
# No vulkan-headers: nothing in the workspace compiles against the system Vulkan headers.
|
|
# The host's Vulkan encode hand-rolls its structs, pyrowave-sys bindgens its own vendored
|
|
# copy, and both host and client reach Vulkan through ash, which dlopens the loader. (The
|
|
# HDR gamescope leg further down does need them, and pulls them itself via `dnf builddep
|
|
# gamescope`.) Matches packaging/rpm/punktfunk.spec, which dropped its BuildRequires too.
|
|
dnf -y install gtk4-devel libadwaita-devel SDL3-devel
|
|
# sysext build (packaging/bazzite/build-sysext.sh): squashfs + SELinux labeling.
|
|
dnf -y install squashfs-tools cpio libselinux-utils selinux-policy-targeted
|
|
# Fedora's own gamescope, for its RUNTIME libraries only — never shipped, never run. The
|
|
# sysext folds in our punktfunk-gamescope and verifies it by executing `--version`, and
|
|
# on a cache hit (the common case) nothing else in this job would have pulled libavif /
|
|
# luajit / seatd / SDL2 in. Cheap, and it tracks gamescope's dep list for us.
|
|
dnf -y install gamescope || true
|
|
# bun builds the punktfunk-web console (--with web). Baked into the image; install it
|
|
# here too so the job stays green against the PREVIOUS image (docker.yml bootstrap note).
|
|
command -v bun >/dev/null || {
|
|
dnf -y install unzip
|
|
curl -fsSL https://bun.sh/install | bash
|
|
install -m0755 "$HOME/.bun/bin/bun" /usr/local/bin/bun
|
|
}
|
|
bun --version
|
|
- uses: actions/cache@v4
|
|
with:
|
|
path: /usr/local/cargo/registry
|
|
key: cargo-home-fedora-${{ hashFiles('Cargo.lock') }}
|
|
restore-keys: cargo-home-fedora-
|
|
|
|
- name: Version + channel
|
|
# vX.Y.Z tag -> X.Y.Z-1 in the base group (a real release); main push -> <next-minor>-0.ciN.g<sha>
|
|
# in the `<base>-canary` group, whose "0." release sorts below the eventual <next-minor>-1 yet
|
|
# climbs by run number. The canary base is derived one minor ahead of the latest stable tag
|
|
# (scripts/ci/pf-version.sh) so a stable->canary box re-point still moves forward. The spec %build stamps
|
|
# PUNKTFUNK_BUILD_VERSION from these macros into the binary (--version provenance).
|
|
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}"; R="1"; GROUP="${{ matrix.group }}" ;;
|
|
*) V="$PF_BASE"; R="0.ci${GITHUB_RUN_NUMBER}.g${SHORT}"; GROUP="${{ matrix.group }}-canary" ;;
|
|
esac
|
|
echo "PF_VERSION=$V" >> "$GITHUB_ENV"
|
|
echo "PF_RELEASE=$R" >> "$GITHUB_ENV"
|
|
echo "GROUP=$GROUP" >> "$GITHUB_ENV"
|
|
echo "rpm $V-$R -> group '$GROUP'"
|
|
|
|
- name: Build RPM
|
|
# PF_WITH_WEB=1 / PF_WITH_SCRIPTING=1 → also build the punktfunk-web console + the
|
|
# punktfunk-scripting runner subpackages (the publish loop globs them in; the host RPM
|
|
# Recommends both). Both need bun (ensured in Prep).
|
|
run: PF_VERSION="$PF_VERSION" PF_RELEASE="$PF_RELEASE" PF_WITH_WEB=1 PF_WITH_SCRIPTING=1 bash packaging/rpm/build-rpm.sh
|
|
|
|
# Signs with packages@unom.io (org secret) and self-verifies before publish. On a v* tag a
|
|
# missing key FAILS the build rather than publishing unsigned RPMs into a gpgcheck=1 repo.
|
|
- name: Sign RPMs
|
|
env:
|
|
RPM_GPG_PRIVATE_KEY: ${{ secrets.RPM_GPG_PRIVATE_KEY }}
|
|
RPM_GPG_PASSPHRASE: ${{ secrets.RPM_GPG_PASSPHRASE }}
|
|
run: bash packaging/rpm/sign-rpms.sh
|
|
|
|
- name: Publish to the Gitea RPM registry
|
|
env:
|
|
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
run: |
|
|
# Publish only the main package (skip -debuginfo/-debugsource subpackages).
|
|
for rpm in dist/*.rpm; do
|
|
case "$rpm" in *debuginfo*|*debugsource*) echo "skip $rpm"; continue;; esac
|
|
echo "uploading $rpm"
|
|
# A re-tagged release re-fires this workflow and the rpm registry 409s on duplicate
|
|
# package versions — delete any prior copy of this exact name/version-release/arch
|
|
# first (404 on the first publish is fine).
|
|
NAME=$(rpm -qp --qf '%{NAME}' "$rpm" 2>/dev/null)
|
|
VR=$(rpm -qp --qf '%{VERSION}-%{RELEASE}' "$rpm" 2>/dev/null)
|
|
ARCH=$(rpm -qp --qf '%{ARCH}' "$rpm" 2>/dev/null)
|
|
curl -fsS -o /dev/null --user "enricobuehler:$TOKEN" -X DELETE \
|
|
"https://$REGISTRY/api/packages/$OWNER/rpm/$GROUP/package/$NAME/$VR/$ARCH" || true
|
|
curl -fsS --user "enricobuehler:$TOKEN" --upload-file "$rpm" \
|
|
"https://$REGISTRY/api/packages/$OWNER/rpm/$GROUP/upload"
|
|
done
|
|
echo "published to $OWNER/rpm/$GROUP"
|
|
|
|
# The HDR-capable gamescope the sysext carries (packaging/gamescope) — what lets the
|
|
# gamescope backend stream 10-bit BT.2020 PQ instead of 8-bit SDR.
|
|
#
|
|
# CACHED, and that is the whole reason this is affordable: it is a ~10-minute C++ meson build
|
|
# of an entirely separate tree that depends on NOTHING in this repo except
|
|
# `packaging/gamescope/**` (the patches and the upstream pin, which lives in the build
|
|
# script). So the key is that directory's hash and a normal push restores a binary instead of
|
|
# building one. Per-Fedora-major, because the binary is soname-coupled to its base exactly
|
|
# like the RPM is — an f43 build does not start on f44 (libavutil.so.59 vs .60).
|
|
- uses: actions/cache@v4
|
|
id: gamescope
|
|
with:
|
|
path: gs-cache
|
|
key: punktfunk-gamescope-f${{ matrix.fedver }}-${{ hashFiles('packaging/gamescope/**') }}
|
|
|
|
- name: Build the HDR gamescope
|
|
if: steps.gamescope.outputs.cache-hit != 'true'
|
|
# Best-effort ON PURPOSE. The sysext is the primary Bazzite delivery path and works without
|
|
# this binary (the host just stays SDR on the gamescope backend, which is what every
|
|
# release before this one did) — so a hiccup building someone else's tree must not cost the
|
|
# whole image. It is loud, though: the warning below, and `--gamescope` silently absent
|
|
# downstream is impossible because build-sysext.sh verifies the +pfhdr marker itself.
|
|
run: |
|
|
set -x
|
|
# `dnf builddep` resolves Fedora's PACKAGED gamescope, which is older than the master we
|
|
# pin, so it can come up short — xorg-x11-server-Xwayland-devel is the one that actually
|
|
# bites (wlroots' configure dies on a missing xserver.wrap several minutes in).
|
|
dnf -y install dnf-plugins-core meson ninja-build glslc || true
|
|
dnf builddep -y gamescope || true
|
|
dnf -y install xorg-x11-server-Xwayland-devel || true
|
|
if bash packaging/gamescope/build-punktfunk-gamescope.sh \
|
|
--destdir "$PWD/gs-stage" --prefix /usr --jobs "$(nproc)"; then
|
|
install -Dm0755 gs-stage/usr/bin/punktfunk-gamescope gs-cache/punktfunk-gamescope
|
|
else
|
|
echo "::warning::punktfunk-gamescope failed to build for f${{ matrix.fedver }} — the sysext ships without it (gamescope sessions stay SDR)"
|
|
fi
|
|
|
|
# The no-layering Bazzite path: wrap the just-built host + web RPMs into a systemd-sysext
|
|
# image and publish it to the per-Fedora-major feed (punktfunk-sysext/f43[-canary], …) that
|
|
# `punktfunk-sysext install|update` reads. Same RPMs, same channels — just no rpm-ostree.
|
|
- name: Build the sysext image
|
|
run: |
|
|
# Execute it here rather than only handing it over: build-sysext.sh treats an unusable
|
|
# --version as fatal (rightly — it is how the +pfhdr marker is read), and a cached binary
|
|
# whose runtime libs are missing from this container must cost the image its HDR, not the
|
|
# image itself.
|
|
gs=()
|
|
if [ -x gs-cache/punktfunk-gamescope ] && gs-cache/punktfunk-gamescope --version >/dev/null 2>&1; then
|
|
gs=(--gamescope gs-cache/punktfunk-gamescope)
|
|
echo "folding in $(gs-cache/punktfunk-gamescope --version 2>&1 | head -1)"
|
|
else
|
|
echo "::warning::no usable punktfunk-gamescope for f${{ matrix.fedver }} — the sysext ships without it (gamescope sessions stay SDR)"
|
|
fi
|
|
bash packaging/bazzite/build-sysext.sh --version-id "${{ matrix.fedver }}" \
|
|
--out "dist-sysext/punktfunk-${PF_VERSION}-${PF_RELEASE}-x86-64.raw" \
|
|
"${gs[@]}" \
|
|
dist/punktfunk-"${PF_VERSION}-${PF_RELEASE}"*.rpm \
|
|
dist/punktfunk-web-"${PF_VERSION}-${PF_RELEASE}"*.rpm \
|
|
dist/punktfunk-scripting-"${PF_VERSION}-${PF_RELEASE}"*.rpm
|
|
|
|
# The feed's SHA256SUMS is OpenPGP-signed with the same packages@unom.io key as the RPMs, and
|
|
# punktfunk-sysext(8) refuses a feed it can't verify — the checksums alone never proved
|
|
# anything, sitting on the same registry as the images they describe.
|
|
- name: Publish the sysext feed
|
|
env:
|
|
TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
RPM_GPG_PRIVATE_KEY: ${{ secrets.RPM_GPG_PRIVATE_KEY }}
|
|
run: |
|
|
case "$GROUP" in
|
|
*-canary) FEED="f${{ matrix.fedver }}-canary"; KEEP=6; OTHER="f${{ matrix.fedver }}" ;;
|
|
*) FEED="f${{ matrix.fedver }}"; KEEP=0; OTHER="f${{ matrix.fedver }}-canary" ;;
|
|
esac
|
|
KEEP=$KEEP bash packaging/bazzite/publish-sysext-feed.sh "$FEED" \
|
|
"dist-sysext/punktfunk-${PF_VERSION}-${PF_RELEASE}-x86-64.raw"
|
|
# Re-seal this Fedora major's OTHER channel too. Stable feeds only publish on a tag, so
|
|
# without this a stable box would sit in front of an unsigned (hence refused) feed until
|
|
# the next release; canary pushes are frequent, so every live feed gets sealed within a
|
|
# day of this landing, and a key rotation propagates without rebuilding any image.
|
|
# Best-effort: a channel that has never published yet has no manifest to seal.
|
|
bash packaging/bazzite/publish-sysext-feed.sh --seal "$OTHER" \
|
|
|| echo "::warning::could not seal the $OTHER feed (no manifest yet?)"
|
|
|
|
# On a real release, also attach the .rpms to the unified Gitea Release. Both Fedora bases
|
|
# (bazzite=F43, fedora-44) build the SAME filename, so suffix the asset with the base to keep
|
|
# both on the release; canary builds live in the `*-canary` rpm groups (no release page).
|
|
- name: Attach .rpms 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)
|
|
for rpm in dist/*.rpm; do
|
|
case "$rpm" in *debuginfo*|*debugsource*) continue;; esac
|
|
base="$(basename "$rpm" .rpm)"
|
|
upsert_asset "$RID" "$rpm" "${base}.${{ matrix.group }}.rpm"
|
|
done
|
|
for raw in dist-sysext/*.raw; do
|
|
upsert_asset "$RID" "$raw" "$(basename "$raw" .raw).f${{ matrix.fedver }}.raw"
|
|
done
|