fix(void): wire signed xbps repo into release, README, and release process

Add build-xbps tag job to release.yml: verifies the tag against
src/vocalinux/version.py, fails closed when XBPS_SIGNING_KEY is unset,
builds and signs via podman-only repo.sh, and attaches packages,
signatures, repodata, and client key files to the release. Fold the
xbps-repo artifact into publish-checksums (manifest + SHA256SUMS).

Document the Void install in the root README (glibc x86_64 scope,
fingerprint check, third-party repo caveat) and the post-tag template
version/checksum bump as a pull request in docs/RELEASE_PROCESS.md.

Proof: local podman run with a throwaway RSA4096 key built all four
templates, signed packages + repodata, and verified voculinux installs
from the signed repo in a clean rootdir (fingerprint
eb:29:5d:9e:5c:94:c1:63:ae:35:cd:35:db:7a:28:da).
This commit is contained in:
soubarnak
2026-11-06 09:15:22 +05:30
parent da97b676a7
commit da450e32db
5 changed files with 255 additions and 4 deletions
+113 -3
View File
@@ -289,6 +289,21 @@ jobs:
`--dangerous` is required because this file is not a Store revision. It will not
refresh from the Store. amd64 only.
### Void Linux (glibc x86_64, signed xbps repo)
Also attached below: the signed repository files (`*.x86_64.xbps`
with signatures, `x86_64-repodata`, and the client key
`FINGERPRINT` plus the fingerprint-named `.plist`). This is a
third-party repository published by this project, not upstream
Void Linux:
```bash
# as root (the files come from this release's assets):
cp <fingerprint>.plist /var/db/xbps/keys/
echo 'repository=<repo-url-from-release-notes>' > /etc/xbps.d/voculinux.conf
sudo xbps-install -Sy voculinux
```
Answer `Y` at the fingerprint prompt only after checking it
matches the attached `FINGERPRINT` file.
### Verifying what you downloaded
`SHA256SUMS` covers every file here and is attached once the aarch64 AppImage,
both Flatpak builds, and the snap finish, a few minutes after this release appears.
@@ -789,7 +804,7 @@ jobs:
# missing one looks like the tampered one.
publish-checksums:
name: Checksums and Provenance
needs: [build-and-release, build-appimage-arm64, build-native, build-native-arm64, build-flatpak-amd64, build-flatpak-arm64, attach-flatpak, attach-snap]
needs: [build-and-release, build-appimage-arm64, build-native, build-native-arm64, build-flatpak-amd64, build-flatpak-arm64, attach-flatpak, attach-snap, build-xbps]
runs-on: ubuntu-latest
# An attach-job failure must not skip checksums: this job hashes the build
# artifacts, which exist regardless of whether an upload to the release
@@ -805,7 +820,7 @@ jobs:
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
with:
# Verification-only artifacts must never enter the release manifest.
pattern: '@(python-dist|appimage-*|native-*|flatpak-*|snap-amd64)'
pattern: '@(python-dist|appimage-*|native-*|flatpak-*|snap-amd64|xbps-repo)'
merge-multiple: true
path: dist
@@ -816,6 +831,9 @@ jobs:
for pat in '*.whl' '*.tar.gz' '*.AppImage' '*.deb' '*.rpm' '*.flatpak' '*.snap'; do
compgen -G "$pat" > /dev/null || { echo "missing artifact: $pat"; exit 1; }
done
for pat in 'repo/*.x86_64.xbps' 'repo/*.sig2' 'repo/*-repodata*' 'keys/FINGERPRINT' 'keys/*.plist'; do
compgen -G "$pat" > /dev/null || { echo "missing artifact: $pat"; exit 1; }
done
test "$(compgen -G '*.AppImage' | wc -l)" -eq 2
test "$(compgen -G '*.deb' | wc -l)" -eq 2
test "$(compgen -G '*.rpm' | wc -l)" -eq 2
@@ -827,8 +845,12 @@ jobs:
set -euo pipefail
ls -la
# Bare names, so `sha256sum -c` works in whatever directory the user
# downloaded the release into.
# downloaded the release into. The xbps repo files hash from their
# own directories for the same reason: the release attaches them
# flat, and RSA signatures stay the primary trust root.
sha256sum -- *.whl *.tar.gz *.AppImage *.deb *.rpm *.flatpak *.snap | tee SHA256SUMS
(cd repo && sha256sum -- *.x86_64.xbps *.sig2 *-repodata*) >> SHA256SUMS
(cd keys && sha256sum -- FINGERPRINT *.plist) >> SHA256SUMS
# Attesting from the manifest rather than a second glob: the file we
# publish and the set we attest cannot drift apart.
@@ -1021,6 +1043,94 @@ jobs:
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
commit_message: 'Release ${{ github.ref_name }}'
# The signed Void xbps repository (ticket #29): every template builds in
# smoke-gate order in the pinned Void container via podman, the full
# repository index is signed with the RSA4096 repository key, and the
# packages, signatures, repodata, and client key land on this release.
# XBPS_SIGNING_KEY is required — an unsigned repository can never
# publish, so a missing key fails the job instead of skipping it (unlike
# the opt-in Flatpak remote and AUR publishes). The voculinux template
# builds the canonical release sdist, so this waits for build-and-release,
# and the tag must match src/vocalinux/version.py or the job aborts.
build-xbps:
name: Build signed xbps repository
needs: build-and-release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Verify tag matches the version source of truth
run: |
set -euo pipefail
TAG="${GITHUB_REF#refs/tags/v}"
PACKAGE_VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' src/vocalinux/version.py)
echo "Tag version: $TAG"
echo "Package version: $PACKAGE_VERSION"
if [[ "$PACKAGE_VERSION" != *"$TAG"* ]] && [[ "$TAG" != *"$PACKAGE_VERSION"* ]]; then
echo "::error::Tag v$TAG does not match src/vocalinux/version.py ($PACKAGE_VERSION); refusing to ship the signed repo"
exit 1
fi
# Fail-closed: no key, no repo, no publish — never a silent skip.
- name: Require the repository signing key
env:
XBPS_SIGNING_KEY: ${{ secrets.XBPS_SIGNING_KEY }}
run: |
if [ -z "$XBPS_SIGNING_KEY" ]; then
echo "::error::XBPS_SIGNING_KEY is unset; refusing to publish an unsigned repository (packaging/void/README.md)"
exit 1
fi
- name: Install podman
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends podman
- name: Build and sign the repository
env:
XBPS_SIGNING_KEY: ${{ secrets.XBPS_SIGNING_KEY }}
run: |
set -euo pipefail
printf '%s' "$XBPS_SIGNING_KEY" > /tmp/xbps-sign-key.pem
chmod 600 /tmp/xbps-sign-key.pem
XBPS_SIGNING_KEY_FILE=/tmp/xbps-sign-key.pem bash packaging/void/repo.sh
rm -f /tmp/xbps-sign-key.pem
ls -la publish/xbps/repo publish/xbps/keys
- name: Upload the signed repository
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: xbps-repo
path: |
publish/xbps/repo/*
publish/xbps/keys/*
if-no-files-found: error
retention-days: 7
- name: Attach the signed repository to the release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
cd publish/xbps
# Full signed repo or nothing: binary packages (*.x86_64.xbps)
# with their signatures, the signed repository metadata
# (*-repodata), and the client key files (FINGERPRINT plus the
# fingerprint-named .plist). Each class is required below, so a
# partial repo can never attach.
compgen -G 'repo/*.x86_64.xbps' > /dev/null || { echo "missing packages"; exit 1; }
compgen -G 'repo/*.sig2' > /dev/null || { echo "missing signatures"; exit 1; }
compgen -G 'repo/*-repodata*' > /dev/null || { echo "missing repodata"; exit 1; }
compgen -G 'keys/FINGERPRINT' > /dev/null || { echo "missing FINGERPRINT"; exit 1; }
compgen -G 'keys/*.plist' > /dev/null || { echo "missing client key"; exit 1; }
gh release upload "${{ github.ref_name }}" \
repo/*.x86_64.xbps repo/*.sig2 repo/*-repodata* keys/FINGERPRINT keys/*.plist \
--repo ${{ github.repository }} \
--clobber
# GitHub Release first, Store second. A super-privileged plug can still
# queue a human review and refuse edge/candidate; the .snap still
# has to land on the tag for sideload. grade: stable allows candidate; do