diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index b8dfd87..285aa6a 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -1,6 +1,6 @@ -# Fenris release workflow — dormant (no runner registered yet). -# When a runner is provisioned, this replicates `make release` automatically. -# Spec: §5, issue #52 +# Fenris release workflow — release path on Coolify-hosted Gitea runner. +# The runner is repository-scoped and executes package build, signing, validation, +# registry publication, and release attachment. Spec: §5, issue #52 name: Release on: @@ -43,58 +43,97 @@ jobs: - name: Build packages run: make package - - name: Sign RPM payload + - name: Import packaging key env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} run: | - echo "$GPG_PRIVATE_KEY" | gpg --batch --import - make sign-rpm + set -euo pipefail + if [ -z "${GPG_PRIVATE_KEY}" ]; then + echo "::error::GPG_PRIVATE_KEY repository secret is not configured" + exit 1 + fi + printf '%s\n' "${GPG_PRIVATE_KEY}" | gpg --batch --import + SECRET_FINGERPRINT="$(gpg --batch --list-secret-keys --with-colons 'packaging@bongbetic.com' | awk -F: '$1 == "fpr" { print $10; exit }')" + PUBLIC_FINGERPRINT="$(gpg --batch --show-keys --with-colons packaging/keys/fenris-packaging.asc | awk -F: '$1 == "fpr" { print $10; exit }')" + if [ -z "${PUBLIC_FINGERPRINT}" ]; then + echo "::error::packaging/keys/fenris-packaging.asc has no OpenPGP key" + exit 1 + fi + if [ "${SECRET_FINGERPRINT}" != "${PUBLIC_FINGERPRINT}" ]; then + echo "::error::packaging public key does not match imported private key" + exit 1 + fi + echo "Packaging key fingerprint verified: ${PUBLIC_FINGERPRINT}" + + - name: Sign RPM payload + run: make sign-rpm - name: Generate and clearsign SHA256SUMS run: make clearsign + - name: Validate signatures and checksums + run: | + set -euo pipefail + VERSION="$(sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml)" + RPM="fenris-${VERSION}-1.x86_64.rpm" + RPM_VERIFY="$(rpm -Kv "dist/${RPM}" 2>&1)" + printf '%s\n' "${RPM_VERIFY}" + printf '%s\n' "${RPM_VERIFY}" | grep -Eiq 'signature.*: *ok' + gpg --batch --verify dist/SHA256SUMS.asc dist/SHA256SUMS + (cd dist && sha256sum -c SHA256SUMS) + + - name: Remove packaging key material + if: always() + run: | + set +e + FINGERPRINT="$(gpg --batch --list-secret-keys --with-colons 'packaging@bongbetic.com' 2>/dev/null | awk -F: '$1 == "fpr" { print $10; exit }')" + if [ -n "${FINGERPRINT}" ]; then + gpg --batch --yes --delete-secret-keys "${FINGERPRINT}" + gpg --batch --yes --delete-keys "${FINGERPRINT}" + fi + - name: Determine version id: version run: echo "version=$(sed -n 's/^version = "\(.*\)"/\1/p' pyproject.toml)" >> "$GITHUB_OUTPUT" - name: Upload deb packages to registry env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_TOKEN: ${{ gitea.token }} run: | VERSION=${{ steps.version.outputs.version }} DEB="fenris_${VERSION}_amd64.deb" for CODENAME in bookworm jammy noble; do curl --fail -X PUT \ - -u "xavierk:${GITEA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ -T "dist/${DEB}" \ "https://git.bongbetic.com/api/packages/xavierk/debian/pool/${CODENAME}/main/upload" done - name: Upload RPM to registry env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_TOKEN: ${{ gitea.token }} run: | VERSION=${{ steps.version.outputs.version }} RPM="fenris-${VERSION}-1.x86_64.rpm" curl --fail -X PUT \ - -u "xavierk:${GITEA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ -T "dist/${RPM}" \ "https://git.bongbetic.com/api/packages/xavierk/rpm/fenris/upload" - name: Create Gitea release env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_TOKEN: ${{ gitea.token }} run: | VERSION=${{ steps.version.outputs.version }} # Check if release already exists (idempotent re-runs) EXISTING=$(curl -s -o /dev/null -w '%{http_code}' \ - -u "xavierk:${GITEA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ "https://git.bongbetic.com/api/v1/repos/xavierk/Fenris/releases/tags/v${VERSION}") if [ "$EXISTING" = "200" ]; then echo "Release v${VERSION} already exists, skipping creation" else curl --fail -X POST \ - -u "xavierk:${GITEA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ -H "Content-Type: application/json" \ -d "{\"tag_name\":\"v${VERSION}\",\"name\":\"v${VERSION}\"}" \ "https://git.bongbetic.com/api/v1/repos/xavierk/Fenris/releases" @@ -102,12 +141,12 @@ jobs: - name: Attach artifacts to release env: - GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_TOKEN: ${{ gitea.token }} run: | VERSION=${{ steps.version.outputs.version }} # Get release ID for this tag RELEASE_ID=$(curl -s \ - -u "xavierk:${GITEA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ "https://git.bongbetic.com/api/v1/repos/xavierk/Fenris/releases/tags/v${VERSION}" \ | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") # Attach deb, rpm, and clearsigned checksums @@ -115,7 +154,7 @@ jobs: "dist/fenris-${VERSION}-1.x86_64.rpm" \ "dist/SHA256SUMS.asc"; do curl --fail -X POST \ - -u "xavierk:${GITEA_TOKEN}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ -F "attachment=@${FILE}" \ "https://git.bongbetic.com/api/v1/repos/xavierk/Fenris/releases/${RELEASE_ID}/assets" done