test: make the release path runnable without cutting a tag

The publish job had never run. It is the part that produces the apt and dnf
repositories, which is to say it is the whole update mechanism, and finding
out whether it works when a tag is already pushed is the wrong time.

A dry run now builds both repositories with a key generated on the spot,
verifies the three signatures it wrote, and then installs the packages back
out of them - apt on the runner, dnf in a Fedora container - so the thing
being tested is the thing that runs. Nothing is pushed and no release is
created.
This commit is contained in:
Felitendo committed 2026-08-24 10:53:30 +02:00
1 parent aaa41f0384
commit 70f6748f3c
1 file changed
+113 -13
+113 -13
View File
@@ -4,6 +4,14 @@ on:
push: push:
tags: ['v*'] tags: ['v*']
workflow_dispatch: workflow_dispatch:
inputs:
dry_run:
description: >-
Build the repositories with a throwaway key and install from them,
without publishing anything. This is how the release path gets
exercised without cutting a tag.
type: boolean
default: false
permissions: permissions:
contents: write contents: write
@@ -56,18 +64,26 @@ jobs:
- run: packaging/build-rpm.sh - run: packaging/build-rpm.sh
# Signed here rather than alongside the APT repository, because this is # Signed here rather than alongside the APT repository, because this is
# the one place with a native rpm-sign. # the one place with a native rpm-sign. A dry run signs with a key it
# makes on the spot, so the command itself is still exercised.
- name: Sign the package - name: Sign the package
env: env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
DRY_RUN: ${{ inputs.dry_run }}
run: | run: |
if [ -z "${GPG_PRIVATE_KEY:-}" ]; then if [ "$DRY_RUN" = "true" ]; then
gpg --batch --passphrase '' --quick-generate-key \
'dry run <dry-run@example.invalid>' rsa2048 sign never
elif [ -n "${GPG_PRIVATE_KEY:-}" ]; then
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
else
echo "No GPG_PRIVATE_KEY secret; leaving the package unsigned." echo "No GPG_PRIVATE_KEY secret; leaving the package unsigned."
exit 0 exit 0
fi fi
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
keyid="$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ { print $5; exit }')" keyid="$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ { print $5; exit }')"
rpmsign --define "_gpg_name $keyid" --addsign dist/*.rpm rpmsign --define "_gpg_name $keyid" --addsign dist/*.rpm
gpg --armor --export "$keyid" > dist/rpm-signer.asc
rpm --import dist/rpm-signer.asc
rpm --checksig dist/*.rpm rpm --checksig dist/*.rpm
- name: Look inside what was built - name: Look inside what was built
@@ -78,13 +94,15 @@ jobs:
- uses: actions/upload-artifact@v7 - uses: actions/upload-artifact@v7
with: with:
name: rpm name: rpm
path: dist/*.rpm path: |
dist/*.rpm
dist/rpm-signer.asc
if-no-files-found: error if-no-files-found: error
publish: publish:
name: Release and repositories name: Release and repositories
needs: [deb, rpm] needs: [deb, rpm]
if: startsWith(github.ref, 'refs/tags/v') if: startsWith(github.ref, 'refs/tags/v') || inputs.dry_run
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v7 - uses: actions/checkout@v7
@@ -95,14 +113,16 @@ jobs:
merge-multiple: true merge-multiple: true
- name: Attach the packages to the release - name: Attach the packages to the release
if: ${{ !inputs.dry_run }}
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
run: | run: |
gh release create "${{ github.ref_name }}" \ gh release create "${{ github.ref_name }}" \
--title "${{ github.ref_name }}" \ --title "${{ github.ref_name }}" \
--generate-notes \ --generate-notes \
incoming/* \ incoming/*.deb incoming/*.rpm \
|| gh release upload "${{ github.ref_name }}" incoming/* --clobber || gh release upload "${{ github.ref_name }}" \
incoming/*.deb incoming/*.rpm --clobber
- name: Install the repository tools - name: Install the repository tools
run: | run: |
@@ -110,21 +130,26 @@ jobs:
sudo apt-get install -y --no-install-recommends \ sudo apt-get install -y --no-install-recommends \
dpkg-dev apt-utils createrepo-c dpkg-dev apt-utils createrepo-c
- name: Import the signing key - name: Get a signing key
id: key id: key
env: env:
GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }}
DRY_RUN: ${{ inputs.dry_run }}
run: | run: |
if [ -z "${GPG_PRIVATE_KEY:-}" ]; then if [ "$DRY_RUN" = "true" ]; then
gpg --batch --passphrase '' --quick-generate-key \
'dry run <dry-run@example.invalid>' rsa2048 sign never
elif [ -n "${GPG_PRIVATE_KEY:-}" ]; then
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
else
echo "present=no" >> "$GITHUB_OUTPUT" echo "present=no" >> "$GITHUB_OUTPUT"
echo "::warning::No GPG_PRIVATE_KEY secret - the apt and dnf repositories were not updated. The packages are on the release." echo "::warning::No GPG_PRIVATE_KEY secret - the apt and dnf repositories were not updated. The packages are on the release."
exit 0 exit 0
fi fi
printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import
echo "present=yes" >> "$GITHUB_OUTPUT" echo "present=yes" >> "$GITHUB_OUTPUT"
- name: Check out the published repositories - name: Check out the published repositories
if: steps.key.outputs.present == 'yes' if: steps.key.outputs.present == 'yes' && !inputs.dry_run
uses: actions/checkout@v7 uses: actions/checkout@v7
with: with:
ref: gh-pages ref: gh-pages
@@ -134,16 +159,48 @@ jobs:
- name: Update the repositories - name: Update the repositories
if: steps.key.outputs.present == 'yes' if: steps.key.outputs.present == 'yes'
run: | run: |
# First release: the branch does not exist yet. # First release, or a dry run: there is no branch to start from.
if [ ! -d pages/.git ]; then if [ ! -d pages/.git ]; then
rm -rf pages && mkdir pages rm -rf pages && mkdir pages
git -C pages init -q -b gh-pages git -C pages init -q -b gh-pages
git -C pages remote add origin "https://github.com/${{ github.repository }}.git" git -C pages remote add origin "https://github.com/${{ github.repository }}.git"
fi fi
rm -f incoming/rpm-signer.asc
packaging/publish-repos.sh pages incoming packaging/publish-repos.sh pages incoming
- name: Push them - name: Check that what was written can be verified
if: steps.key.outputs.present == 'yes' if: steps.key.outputs.present == 'yes'
run: |
find pages -type f -not -path '*/.git/*' | sort
echo '--- Release ---'; cat pages/deb/Release
echo '--- Packages ---'; cat pages/deb/Packages
gpg --verify pages/deb/InRelease
gpg --verify pages/deb/Release.gpg pages/deb/Release
gpg --verify pages/rpm/repodata/repomd.xml.asc pages/rpm/repodata/repomd.xml
# The proof that the repository works is apt reading it: the signature,
# the index, the dependencies and the program that comes out the far end.
- name: Install from the repository that was just built
if: inputs.dry_run
run: |
sudo install -d -m 0755 /etc/apt/keyrings
sudo gpg --dearmor -o /etc/apt/keyrings/middleclick-autoscroll.gpg < pages/KEY.gpg
echo "deb [signed-by=/etc/apt/keyrings/middleclick-autoscroll.gpg] file://$PWD/pages/deb ./" \
| sudo tee /etc/apt/sources.list.d/middleclick-autoscroll.list
sudo apt-get update
sudo apt-get install -y middleclick-autoscroll
middleclick-autoscroll --version
middleclick-autoscroll list
- uses: actions/upload-artifact@v7
if: inputs.dry_run
with:
name: pages
path: pages
include-hidden-files: true
- name: Push them
if: steps.key.outputs.present == 'yes' && !inputs.dry_run
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
run: | run: |
@@ -154,3 +211,46 @@ jobs:
git diff --quiet --cached && { echo "nothing changed"; exit 0; } git diff --quiet --cached && { echo "nothing changed"; exit 0; }
git commit -q -m "Publish ${{ github.ref_name }}" git commit -q -m "Publish ${{ github.ref_name }}"
git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:gh-pages git push "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git" HEAD:gh-pages
verify-dnf:
name: Install from the RPM repository
needs: publish
if: inputs.dry_run
runs-on: ubuntu-latest
container: fedora:latest
steps:
- uses: actions/download-artifact@v8
with:
name: pages
path: pages
# A real run signs the package and the repository metadata with the one
# key from the secret. A dry run has no secret, so each job made a key of
# its own and both public halves are needed to check both signatures.
- uses: actions/download-artifact@v8
with:
name: rpm
path: signer
- name: Install from the repository that was just built
run: |
rpm --import pages/KEY.gpg
rpm --import signer/rpm-signer.asc
cat > /etc/yum.repos.d/middleclick-autoscroll.repo <<EOF
[middleclick-autoscroll]
name=middleclick-autoscroll
baseurl=file://$PWD/pages/rpm
enabled=1
gpgcheck=1
repo_gpgcheck=1
gpgkey=file://$PWD/pages/KEY.gpg
EOF
# util-linux is for runuser below; the base image does not carry it,
# and util-linux-core is not the half that has it.
dnf install -y middleclick-autoscroll util-linux
# As somebody, not as root: running it as root is refused, which is
# the point of it, and a container is root by default.
useradd -m tester
runuser -u tester -- middleclick-autoscroll --version
runuser -u tester -- middleclick-autoscroll list