Crafter.Build/.forgejo/workflows/ci.yaml
Jorijn van der Graaf 7eb837fd55
Some checks failed
Flatpak / flatpak (push) Failing after 1h23m15s
fix(ci): sign with a key kept on the runner host
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>
2026-10-02 23:52:13 +02:00

192 lines
8.3 KiB
YAML

name: Flatpak
# One job, three outcomes:
# pull request -> build the Flatpak and run the suite inside it
# push master -> also publish the result as the `beta` branch of the
# Catcrafts repo and refresh the rolling `latest` release
# 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 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), 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]
push:
branches: [master]
tags: ['[0-9]+.[0-9]+.[0-9]+']
workflow_dispatch:
env:
APP_ID: net.catcrafts.CrafterBuild
MANIFEST: packaging/flatpak/net.catcrafts.CrafterBuild.yaml
CI_MANIFEST: packaging/flatpak/ci-manifest.yaml
RUNTIME: org.freedesktop.Sdk//26.08
REPO_DIR: /srv/flatpak/repo
WEB_DIR: /srv/flatpak
jobs:
flatpak:
runs-on: flatpak
container:
image: archlinux:latest
options: >-
-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: |
pacman-key --init
pacman-key --populate archlinux
pacman -Sy --noconfirm --needed archlinux-keyring
pacman -Syu --noconfirm --needed flatpak flatpak-builder ccache git nodejs jq gnupg go-yq ostree
git config --global --add safe.directory '*'
- name: Sdk
# A no-op once the volume is warm; `update` follows point releases.
run: |
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
flatpak install -y --noninteractive flathub "$RUNTIME"
flatpak update -y --noninteractive
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: true
- name: Version and channel
id: meta
run: |
set -eu
if [ "${GITHUB_REF_TYPE}" = tag ]; then
version="$GITHUB_REF_NAME"
branch=stable
# The metainfo's newest release is the release notes and must be
# the version being tagged.
top=$(grep -m1 -oP '<release version="\K[^"]+' packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml)
if [ "$top" != "$version" ]; then
echo "metainfo lists $top as the newest release, but the tag is $version" >&2
exit 1
fi
else
version="0.0.0-dev+$(git rev-parse --short HEAD)"
branch=beta
fi
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "branch=$branch" >> "$GITHUB_OUTPUT"
echo "version $version, flatpak branch $branch"
- name: Build
# The manifest is rewritten beside the original so its relative
# source paths still resolve. The suite runs inside this build.
env:
VERSION: ${{ steps.meta.outputs.version }}
run: |
set -eu
yq '."build-options".env.CRAFTER_BUILD_VERSION = strenv(VERSION)' "$MANIFEST" > "$CI_MANIFEST"
grep -n 'CRAFTER_BUILD_VERSION' "$CI_MANIFEST"
flatpak-builder --force-clean --ccache --disable-rofiles-fuse \
--state-dir=/root/.cache/flatpak-builder \
--repo=repo --default-branch="${{ steps.meta.outputs.branch }}" \
build-dir "$CI_MANIFEST"
- name: Smoke test
run: |
flatpak-builder --run build-dir "$CI_MANIFEST" crafter-build --version
flatpak-builder --run build-dir "$CI_MANIFEST" sh -c 'cd examples/hello-world && crafter-build && ls bin/*/hello*'
# Everything below publishes and only runs on master and on tags.
- name: Bundle
if: github.event_name != 'pull_request'
run: |
mkdir -p dist
flatpak build-bundle repo "dist/crafter-build-${{ steps.meta.outputs.version }}.flatpak" "$APP_ID" "${{ steps.meta.outputs.branch }}"
cp packaging/flatpak/net.catcrafts.CrafterBuild.flatpakref dist/
(cd dist && sha256sum * > SHA256SUMS)
ls -la dist
- name: Upload bundle
if: github.event_name != 'pull_request'
uses: actions/upload-artifact@v3
with:
name: crafter-build-${{ steps.meta.outputs.version }}
path: dist/*.flatpak
if-no-files-found: error
- name: Publish to the Catcrafts repo
if: github.event_name != 'pull_request'
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)
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
flatpak build-commit-from --src-repo=repo \
--gpg-sign="$FLATPAK_GPG_KEY_ID" --gpg-homedir="$GNUPGHOME" \
"$REPO_DIR" "app/$APP_ID/x86_64/${{ steps.meta.outputs.branch }}"
flatpak build-update-repo --gpg-sign="$FLATPAK_GPG_KEY_ID" --gpg-homedir="$GNUPGHOME" \
--generate-static-deltas --prune "$REPO_DIR"
# The repo description and the app ref carry the public key.
key=$(gpg --export "$FLATPAK_GPG_KEY_ID" | base64 --wrap=0)
sed "s|GPGKey=.*|GPGKey=$key|" packaging/flatpak/catcrafts.flatpakrepo > "$WEB_DIR/catcrafts.flatpakrepo"
sed "s|GPGKey=.*|GPGKey=$key|" packaging/flatpak/net.catcrafts.CrafterBuild.flatpakref > "$WEB_DIR/net.catcrafts.CrafterBuild.flatpakref"
- name: Rolling 'latest' release
if: github.event_name != 'pull_request' && github.ref_type != 'tag'
run: |
git config user.email "ci@catcrafts.net"
git config user.name "Crafter Build CI"
git tag -f latest
git push origin latest --force
- name: Publish rolling 'latest' release
if: github.event_name != 'pull_request' && github.ref_type != 'tag'
uses: https://code.forgejo.org/actions/forgejo-release@v2
with:
direction: upload
url: ${{ github.server_url }}
repo: ${{ github.repository }}
tag: latest
title: Latest master build
prerelease: true
override: true
release-dir: dist
token: ${{ secrets.GITHUB_TOKEN }}
- name: Release notes
if: github.ref_type == 'tag'
run: |
# The newest <release> block's description, as plain text.
python3 - > release-notes.md <<'EOF'
import re, html
text = open("packaging/flatpak/net.catcrafts.CrafterBuild.metainfo.xml").read()
block = re.search(r"<release [^>]*>(.*?)</release>", text, re.S).group(1)
paras = re.findall(r"<p>(.*?)</p>", block, re.S)
print("\n\n".join(html.unescape(" ".join(p.split())) for p in paras))
print("\nInstall:\n\n flatpak remote-add --if-not-exists catcrafts https://flatpak.catcrafts.net/catcrafts.flatpakrepo\n flatpak install catcrafts net.catcrafts.CrafterBuild\n")
EOF
- name: Publish release
if: github.ref_type == 'tag'
uses: https://code.forgejo.org/actions/forgejo-release@v2
with:
direction: upload
url: ${{ github.server_url }}
repo: ${{ github.repository }}
tag: ${{ github.ref_name }}
title: crafter-build ${{ github.ref_name }}
release-notes: release-notes.md
release-dir: dist
token: ${{ secrets.GITHUB_TOKEN }}