diff --git a/.gitea/workflows/rpm.yml b/.gitea/workflows/rpm.yml index 424be65f..0e76ef88 100644 --- a/.gitea/workflows/rpm.yml +++ b/.gitea/workflows/rpm.yml @@ -110,7 +110,9 @@ jobs: # 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 - - name: Sign RPMs (dormant until RPM_GPG_PRIVATE_KEY is set — see packaging/rpm/README.md) + # 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 }} diff --git a/packaging/rpm/README.md b/packaging/rpm/README.md index effba626..27d73d55 100644 --- a/packaging/rpm/README.md +++ b/packaging/rpm/README.md @@ -52,6 +52,16 @@ The public key is served from the registry (the `gpgkey=` URL above) and committ `packaging/rpm/RPM-GPG-KEY-punktfunk`. (This is a GPG/OpenPGP key — a `step-ca`/X.509 cert can't sign RPMs; step-ca is only for registry/console TLS.) +> `RPM_GPG_PRIVATE_KEY` is an **org-level** secret on `unom`, not a repo secret — it will not show +> up under this repository's Actions secrets. Verify it end to end instead of by its absence there: +> `curl -O /package/punktfunk-web//x86_64/…rpm && rpm -qp --qf '%{RSAHEADER:pgpsig}\n'` +> (or `rpmkeys --checksig`, which reports `NOKEY` until you import the public key — `NOKEY` still +> means *signed*, just by a key that box doesn't have yet). + +On a `v*` tag build, a missing key **fails** the build: `sign-rpms.sh` will not publish unsigned +RPMs into a repo whose own instructions say `gpgcheck=1`, because every user's `dnf upgrade` would +then break on them. Non-release builds still fall through unsigned so forks and local builds work. + How it was set up (and how to rotate the key): ```sh @@ -68,8 +78,9 @@ EOF gpg --armor --export-secret-keys packages@unom.io # -> the RPM_GPG_PRIVATE_KEY CI secret gpg --armor --export packages@unom.io > packaging/rpm/RPM-GPG-KEY-punktfunk # public half -# 2. Add the armored PRIVATE key as the RPM_GPG_PRIVATE_KEY Gitea Actions secret. Commit the public -# half and publish it to the registry so the gpgkey= URL resolves: +# 2. Add the armored PRIVATE key as the RPM_GPG_PRIVATE_KEY Gitea Actions secret, at the ORG level +# (git.unom.io/org/unom/settings/actions/secrets) so every repo's workflows inherit it. Commit +# the public half and publish it to the registry so the gpgkey= URL resolves: curl --user ":" --upload-file packaging/rpm/RPM-GPG-KEY-punktfunk \ https://git.unom.io/api/packages/unom/generic/punktfunk-keys/1/RPM-GPG-KEY-punktfunk ``` diff --git a/packaging/rpm/sign-rpms.sh b/packaging/rpm/sign-rpms.sh index be9dc3e0..7c577e30 100755 --- a/packaging/rpm/sign-rpms.sh +++ b/packaging/rpm/sign-rpms.sh @@ -1,9 +1,16 @@ #!/usr/bin/env bash # Detached-GPG-sign the built dist/*.rpm so the Gitea RPM registry can be served with gpgcheck=1. # -# DORMANT by default: if RPM_GPG_PRIVATE_KEY is unset this exits 0 and leaves the RPMs unsigned — -# exactly today's behaviour — so it is SAFE to ship before a key exists. The signing only activates -# once you add the key as a CI secret (see packaging/rpm/README.md "Enabling per-package signing"). +# 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 @@ -13,7 +20,13 @@ set -euo pipefail if [ -z "${RPM_GPG_PRIVATE_KEY:-}" ]; then - echo "RPM_GPG_PRIVATE_KEY unset — leaving dist/*.rpm UNSIGNED (registry stays gpgcheck=0)." + 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