From 70f6748f3c077066d14c2a174c338575cc6ae2a2 Mon Sep 17 00:00:00 2001 From: Felitendo Date: Mon, 24 Aug 2026 10:53:30 +0200 Subject: [PATCH] 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. --- .github/workflows/release.yml | 126 ++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 13 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a4a1c47..d34d662 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,6 +4,14 @@ on: push: tags: ['v*'] 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: contents: write @@ -56,18 +64,26 @@ jobs: - run: packaging/build-rpm.sh # 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 env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + DRY_RUN: ${{ inputs.dry_run }} run: | - if [ -z "${GPG_PRIVATE_KEY:-}" ]; then + if [ "$DRY_RUN" = "true" ]; then + gpg --batch --passphrase '' --quick-generate-key \ + 'dry run ' 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." exit 0 fi - printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import keyid="$(gpg --list-secret-keys --with-colons | awk -F: '/^sec:/ { print $5; exit }')" 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 - name: Look inside what was built @@ -78,13 +94,15 @@ jobs: - uses: actions/upload-artifact@v7 with: name: rpm - path: dist/*.rpm + path: | + dist/*.rpm + dist/rpm-signer.asc if-no-files-found: error publish: name: Release and repositories needs: [deb, rpm] - if: startsWith(github.ref, 'refs/tags/v') + if: startsWith(github.ref, 'refs/tags/v') || inputs.dry_run runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 @@ -95,14 +113,16 @@ jobs: merge-multiple: true - name: Attach the packages to the release + if: ${{ !inputs.dry_run }} env: GH_TOKEN: ${{ github.token }} run: | gh release create "${{ github.ref_name }}" \ --title "${{ github.ref_name }}" \ --generate-notes \ - incoming/* \ - || gh release upload "${{ github.ref_name }}" incoming/* --clobber + incoming/*.deb incoming/*.rpm \ + || gh release upload "${{ github.ref_name }}" \ + incoming/*.deb incoming/*.rpm --clobber - name: Install the repository tools run: | @@ -110,21 +130,26 @@ jobs: sudo apt-get install -y --no-install-recommends \ dpkg-dev apt-utils createrepo-c - - name: Import the signing key + - name: Get a signing key id: key env: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} + DRY_RUN: ${{ inputs.dry_run }} run: | - if [ -z "${GPG_PRIVATE_KEY:-}" ]; then + if [ "$DRY_RUN" = "true" ]; then + gpg --batch --passphrase '' --quick-generate-key \ + 'dry run ' rsa2048 sign never + elif [ -n "${GPG_PRIVATE_KEY:-}" ]; then + printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import + else 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." exit 0 fi - printf '%s' "$GPG_PRIVATE_KEY" | gpg --batch --import echo "present=yes" >> "$GITHUB_OUTPUT" - 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 with: ref: gh-pages @@ -134,16 +159,48 @@ jobs: - name: Update the repositories if: steps.key.outputs.present == 'yes' 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 rm -rf pages && mkdir pages git -C pages init -q -b gh-pages git -C pages remote add origin "https://github.com/${{ github.repository }}.git" fi + rm -f incoming/rpm-signer.asc packaging/publish-repos.sh pages incoming - - name: Push them + - name: Check that what was written can be verified 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: GH_TOKEN: ${{ github.token }} run: | @@ -154,3 +211,46 @@ jobs: git diff --quiet --cached && { echo "nothing changed"; exit 0; } git commit -q -m "Publish ${{ github.ref_name }}" 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 <