fix(ci): sign with a key kept on the runner host
Some checks failed
Flatpak / flatpak (push) Failing after 1h23m15s

The runner is our own machine, so the repo signing key does not need to
pass through the forge as a secret. It is generated once under
/etc/crafter-flatpak on the host and bind-mounted read-only into the job
container, where each run imports it into a throwaway keyring. One less
secret to manage, and the private key never leaves the box that uses it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-10-02 23:51:23 +02:00
commit 7eb837fd55
Signed by: jorijnvdgraaf
GPG key ID: 2937E59CDCC1BCFB
2 changed files with 25 additions and 8 deletions

View file

@ -7,9 +7,10 @@ name: Flatpak
# tag X.Y.Z -> publish as `stable` and create release X.Y.Z
#
# The `flatpak` runner label is the privileged runner on the Hetzner box
# (bubblewrap needs it) with three volumes allowed: the Sdk install, the
# (bubblewrap needs it) with four volumes allowed: the Sdk install, the
# flatpak-builder state (ccache and the LLVM module, so only crafter-build
# itself rebuilds on a normal run) and the published repo's web root.
# itself rebuilds on a normal run), the published repo's web root, and the
# repo signing key, which lives on the runner host and never in the forge.
on:
pull_request:
branches: [master]
@ -35,6 +36,7 @@ jobs:
-v crafter-flatpak-system:/var/lib/flatpak
-v crafter-flatpak-builder:/root/.cache/flatpak-builder
-v /var/www/flatpak:/srv/flatpak
-v /etc/crafter-flatpak:/run/crafter-flatpak:ro
steps:
- name: Install tools
run: |
@ -118,13 +120,18 @@ jobs:
- name: Publish to the Catcrafts repo
if: github.event_name != 'pull_request'
env:
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
FLATPAK_GPG_KEY_ID: ${{ secrets.FLATPAK_GPG_KEY_ID }}
run: |
set -eu
# The signing key is generated once on the runner host
# (/etc/crafter-flatpak: signing-key.asc and key-id) and mounted
# read-only; it is imported into a throwaway keyring per run.
if [ ! -s /run/crafter-flatpak/signing-key.asc ] || [ ! -s /run/crafter-flatpak/key-id ]; then
echo "no signing key under /etc/crafter-flatpak on the runner host; generate it as described in AGENTS.md (CI and publishing)" >&2
exit 1
fi
export GNUPGHOME=$(mktemp -d)
printf '%s' "$FLATPAK_GPG_PRIVATE_KEY" | gpg --batch --quiet --import
gpg --batch --quiet --import /run/crafter-flatpak/signing-key.asc
FLATPAK_GPG_KEY_ID=$(cat /run/crafter-flatpak/key-id)
# The live repo is the canonical copy; builds are promoted into it,
# never built into it.
[ -d "$REPO_DIR" ] || ostree init --repo="$REPO_DIR" --mode=archive

View file

@ -143,12 +143,22 @@ These were decided deliberately. Do not undo them without the maintainer's say-s
- Unpinned dependencies `git pull` on every invocation, so builds and tests need network. A missing or private repo surfaces as a clone failure before anything is built.
- `--runner=cmd:<command>` executes the named wrapper around every test binary. Wrappers come from `--runner=` or `CRAFTER_BUILD_RUNNER_*`; do not hardcode hosts, wrappers or credentials in `project.cpp` or tests.
- The linter loads `libclang` at runtime by name. `CRAFTER_BUILD_LIBCLANG` pins an exact path and must fail loudly when wrong, never fall back to another library.
- CI force-pushes the rolling `latest` tag and publishes a prerelease on every push to `master` with the workflow's own token, and signs the Flatpak repo with the key in the `FLATPAK_GPG_PRIVATE_KEY` secret. Do not add other secrets to the workflow or echo the environment in it.
- CI force-pushes the rolling `latest` tag and publishes a prerelease on every push to `master` with the workflow's own token, and signs the Flatpak repo with the key mounted from the runner host. Do not add secrets to the workflow or echo the environment in it.
- Tests write only under `fs::temp_directory_path()` and the fixture's own `bin/` and `build/`. Keep it that way.
## CI and publishing
`.forgejo/workflows/ci.yaml` runs on the `flatpak` runner label: the privileged forgejo-runner on the Hetzner box (bubblewrap inside the job container needs it). Its `config.yml` allows three volumes, which the workflow mounts: `crafter-flatpak-system` (the Sdk install at `/var/lib/flatpak`), `crafter-flatpak-builder` (flatpak-builder state and ccache, so the LLVM and WASI modules are rebuilt only when their sources change) and `/var/www/flatpak`, the web root Caddy serves as `https://flatpak.catcrafts.net/`. Secrets: `FLATPAK_GPG_PRIVATE_KEY` (armored, no passphrase) and `FLATPAK_GPG_KEY_ID`. The live repo at `/var/www/flatpak/repo` is canonical; builds are promoted into it with `flatpak build-commit-from`, never built into it.
`.forgejo/workflows/ci.yaml` runs on the `flatpak` runner label: the privileged forgejo-runner on the Hetzner box (bubblewrap inside the job container needs it). Its `config.yml` allows three volumes, which the workflow mounts: `crafter-flatpak-system` (the Sdk install at `/var/lib/flatpak`), `crafter-flatpak-builder` (flatpak-builder state and ccache, so the LLVM and WASI modules are rebuilt only when their sources change) and `/var/www/flatpak`, the web root Caddy serves as `https://flatpak.catcrafts.net/`, and `/etc/crafter-flatpak`, which holds the repo signing key (`signing-key.asc`, no passphrase, and `key-id`), generated once on the host and mounted read-only into the job; nothing is stored in the forge. The live repo at `/var/www/flatpak/repo` is canonical; builds are promoted into it with `flatpak build-commit-from`, never built into it.
The key is made once, as root on the runner host, and the step fails with a message pointing here when it is missing:
```bash
install -d -m 700 /etc/crafter-flatpak/gnupg && export GNUPGHOME=/etc/crafter-flatpak/gnupg
gpg --batch --pinentry-mode loopback --passphrase '' --quick-generate-key 'Catcrafts Flatpak Repository <flatpak@catcrafts.net>' ed25519 sign never
KEYID=$(gpg --list-secret-keys --with-colons | awk -F: '/^sec/{print $5; exit}')
gpg --batch --pinentry-mode loopback --passphrase '' --export-secret-keys --armor "$KEYID" > /etc/crafter-flatpak/signing-key.asc
printf '%s\n' "$KEYID" > /etc/crafter-flatpak/key-id && chmod 600 /etc/crafter-flatpak/signing-key.asc /etc/crafter-flatpak/key-id
```
To release: add a `<release>` to `packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml` (its description becomes the release notes), merge, and push the tag `X.Y.Z`. CI refuses a tag that does not match the newest entry. Local builds: `flatpak-builder --user --force-clean --ccache --repo=repo build-dir packaging/flatpak/net.catcrafts.CrafterBuild.yaml`, then `flatpak-builder --run build-dir <manifest> crafter-build --version`.