Files
punktfunk/packaging/rpm/sign-rpms.sh
enricobuehler bc9201d136 fix(packaging/gamescope): bump the pin past upstream's capture-format probe, and sign the RPM
Three things, one delivery path — a Fedora/Nobara box getting the patched gamescope.

**The pin moves 8c676c39 -> 5fb8dce4** (3.16.25-1 -> 3.16.25-11). The commit that matters
is ff6b924, `rendervulkan: fall back to XBGR2101010 when XRGB2101010 is unsupported`: it
probes `linearTilingFeatures` for STORAGE+SAMPLED and captures as XBGR2101010 where
A2R10G10B10 linear storage is unavailable — which is every NVIDIA. That covers the paths
that are upstream's rather than ours: the RGB intermediate `paint_pipewire()` acquires when
the stream is YCbCr, and AVIF screenshots. #143 fixed our own node host-side; this is the
other half, and its commit message asked for exactly this bump.

All six patches rebased. Only 0006 conflicted: upstream's f8be7ee added
`vulkan_has_drm_modifiers_for_features()` immediately above the `g_device` declaration our
patch turns into a reference — both kept. 0003 and 0005 come out byte-identical; 0006 also
picks up the `--zero-commit --no-signature` form 0001-0005 already used.

**Patch 0001 now offers `xBGR_210LE` BEFORE `xRGB_210LE`**, mirroring the host-side
`HDR_FORMAT_ORDER` rationale on the producer end. A consumer takes the first pod it can use,
and we were handing third-party consumers (OBS and friends) the one format NVIDIA fills
byte-reversed under a correct-looking label. Deliberately NOT done by calling upstream's
`vulkan_get_rgb10_capture_format()`, which is what pw_pods.rs proposes: that symbol landed
after 3.16.25, so it would break `packaging/nix/gamescope.nix` — which applies these patches
to whatever gamescope nixpkgs pins — with an opaque C++ error instead of a patch conflict.
The reorder gets the same outcome on any base. Note added there so the next reader does not
"fix" it.

**And the RPM was never signed.** `Sign RPMs` runs right after `Build RPM`; the gamescope
RPM is built ~90 steps later, behind its own ~10-minute cache, so it missed the signing pass
entirely — every punktfunk-gamescope RPM ever published went out unsigned. The repo file we
tell users to install carries `gpgcheck=1`, so `dnf install punktfunk-gamescope` failed with
"The package is not signed" on every Fedora and Nobara box. The package was in the channel
the whole time and could not be installed from it, which is worse than absent: the notes and
the docs-site both say it is there. `sign-rpms.sh` now takes explicit paths (defaulting to
`dist/*.rpm` as before) and a second pass signs this one before publish, fail-closed on a tag
like the first.

Verified on Nobara 44 (VM 123, RTX 5070 Ti passthrough), canary 0.27.0-0.ci12611.g516a2954:

* Builds clean in the fc44 CI image; banner `3.16.25-17-ga87390d+pfhdr4` (11 upstream + our
  6), so the marker the host probes still reads 4 — no capability moved, hence pkgrel 3 and
  `.pfhdrN` staying put.
* `pw-cli enum-params` on the live node: BGRx, NV12, **xBGR_210LE (81), xRGB_210LE (80)** —
  8-bit consumers still negotiate bit-for-bit, 10-bit now leads with the safe one.
* All four patched flags present, `--pipewire-composite-external-overlay` included.
* Patch 0006 confirmed working by comparison, which is the only way to see it: the new build
  exits 0 where both the pre-0006 `+pfhdr2` build and the stock 3.16.23.2 abort with 134.
* Signing fix proven with a throwaway key: `Signature: (none)` -> `digests signatures OK`.
* Host health on the canary: synthetic spike 300/300 encoded, loopback 300 recovered, 0
  mismatches.

One unexplained one-off: the very first headless run after install segfaulted at exit
(SIGSEGV, after "Primary child shut down!"). Not reproduced in 11 subsequent runs across
every flag combination, so it is recorded rather than diagnosed — the binary is stripped and
there is no symbolised core.
2026-08-09 18:00:28 +02:00

74 lines
3.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Detached-GPG-sign the built dist/*.rpm so the Gitea RPM registry can be served with gpgcheck=1.
#
# ACTIVE: RPM_GPG_PRIVATE_KEY is set as an ORG-level secret on `unom` (packages@unom.io,
# AF245C506F4E4763 — the public half is committed at packaging/rpm/RPM-GPG-KEY-punktfunk and served
# from the registry). Every published RPM carries its EdDSA header signature; the repo file in
# README.md tells users gpgcheck=1, which only works because of that.
#
# Without the key this exits 0 and leaves the RPMs unsigned, so a fork or a local build still works
# — but NOT on a release. On refs/tags/v* a missing key is a hard failure: an org secret that got
# rotated, renamed, or not inherited would otherwise publish an unsigned release into a repo whose
# own instructions say gpgcheck=1, and every user's `dnf upgrade` would break on it. Better to fail
# the build than to find out from users. (Same fail-closed rule as the Windows pack scripts.)
#
# Requires a DEDICATED, PASSPHRASE-LESS signing key (the one the runbook generates with
# %no-protection), distinct from the Gitea instance's repo-metadata key — rpm's default signer
# can't supply a passphrase non-interactively here.
#
# Usage (in rpm.yml, after build-rpm.sh): RPM_GPG_PRIVATE_KEY=... bash packaging/rpm/sign-rpms.sh
#
# Takes the RPMs to sign as arguments, defaulting to `dist/*.rpm`. The argument form exists because
# punktfunk-gamescope is built LATER in the job than the host RPMs — it is a ~10-minute C++ build
# behind its own cache — so it misses the main signing pass entirely and needs a second one just
# for itself. It shipped unsigned into a `gpgcheck=1` repo that way, which made
# `dnf install punktfunk-gamescope` fail with "The package is not signed" for every Fedora/Nobara
# user: the package was in the channel and still uninstallable.
set -euo pipefail
# Default target, and a real glob rather than a literal when nothing matched.
if [ "$#" -gt 0 ]; then
RPMS=("$@")
else
shopt -s nullglob
RPMS=(dist/*.rpm)
fi
[ "${#RPMS[@]}" -gt 0 ] || { echo "no RPMs to sign" >&2; exit 1; }
if [ -z "${RPM_GPG_PRIVATE_KEY:-}" ]; then
case "${GITHUB_REF:-}" in
refs/tags/v*)
echo "RPM_GPG_PRIVATE_KEY unset on a release build (${GITHUB_REF}) — refusing to publish" >&2
echo "unsigned RPMs into a gpgcheck=1 repo. Restore the org secret (packaging/rpm/README.md)." >&2
exit 1 ;;
esac
echo "RPM_GPG_PRIVATE_KEY unset — leaving dist/*.rpm UNSIGNED (non-release build)."
exit 0
fi
command -v rpmsign >/dev/null 2>&1 || dnf -y install rpm-sign >/dev/null
GNUPGHOME="$(mktemp -d)"; export GNUPGHOME; chmod 700 "$GNUPGHOME"
trap 'rm -rf "$GNUPGHOME"' EXIT
# Non-interactive in CI (no TTY): force loopback pinentry via gpg.conf so even rpm's default
# signing macro's gpg call won't try to prompt. The passphrase-less key needs no prompt anyway.
printf 'pinentry-mode loopback\n' > "$GNUPGHOME/gpg.conf"
printf 'allow-loopback-pinentry\n' > "$GNUPGHOME/gpg-agent.conf"
printf '%s' "$RPM_GPG_PRIVATE_KEY" | gpg --batch --import
KEYID="$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/{print $5; exit}')"
[ -n "$KEYID" ] || { echo "no secret key imported from RPM_GPG_PRIVATE_KEY" >&2; exit 1; }
# Sign with rpm's DEFAULT __gpg_sign_cmd — it expands %{__signature_filename}/%{__plaintext_filename}
# correctly. (A custom __gpg_sign_cmd passed via --define reached gpg with those filename macros
# UNEXPANDED -> "No such file or directory".) Just point rpm at our key; the GNUPGHOME above
# (passphrase-less key + loopback) lets gpg sign headless.
for rpm in "${RPMS[@]}"; do
rpmsign --define "_gpg_name $KEYID" --addsign "$rpm"
done
# Verify locally so a bad signature fails the build before publishing.
rpm --import <(gpg --export --armor "$KEYID")
rpmkeys --checksig "${RPMS[@]}"
echo "signed + verified ${#RPMS[@]} RPM(s) with key $KEYID"