fix(void): build the xbps release from the tag, not an attached sdist #37

Merged
xavierk merged 6 commits from fix/release-sdist-build into main 2026-11-06 15:31:47 +00:00
Owner

Description

The signed Void xbps release depended on a fork sdist attached to the release before the tag existed, and the app template carried a PENDING_FORK_RELEASE_SHA256 marker that had to be filled by hand. Every release is now built from its tag alone, by one script that the tag workflow and a local dry run both call.

  • packaging/void/build-release.sh is the release path. It gates the tag (exact v plus __version__, and the committed template must name that release), gates the key (RSA 4096, public half equal to repository-key.plist) before any container starts, builds the canonical sdist from the tag, then runs three podman phases: build without the key, sign with the key mounted read-only and no network, and install-smoke in a clean image without the key. It stages the complete set and checks it.
  • render-voculinux.sh pins version, _tag and checksum in the container's build tree only. The committed template keeps a zero placeholder; no build commits.
  • The xbps version is __version__ with the hyphen removed (0.19.0-beta becomes 0.19.0beta), which xbps sorts below the release. Only X.Y.Z and X.Y.Z-alpha|beta|rcN are release forms.
  • The workflow checks the tag and tools first, stages the key on tmpfs (0600, noclobber), publishes only what the script staged (sdist, packages, signatures, repodata, client key), and removes the key in an always-run step. It no longer installs packages with apt.
  • smoke.sh runs the same path with a throwaway key.
  • The template bump is split: version and _tag go in the release PR, the checksum goes in a pull request after the tag. The "attach the sdist first" instruction is gone from the docs.

Related issue

Refs #16 (the Void spec) and its release-pipeline tickets #28 and #29. Deliberately not Fixes: merging must not close the spec, whose hardware checklist and upstream intake are separate.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Refactor
  • Tests
  • Packaging / CI

Checklist

  • Code follows project style (Black, isort, flake8)
  • Documentation updated when user-facing behavior or install steps change
  • Tests added or updated for the change
  • Local tests pass (pytest) for the touched modules (223 passed)
  • Conventional commit message style

Dry run (HEAD d4ac89f, a clean clone, real release key, output outside the repository)

Command: packaging/void/build-release.sh release v0.18.5 --key-file ~/.local/share/taema-release/privkey.pem --out-dir <scratch>/dryrun-repo3, run from git clone of d4ac89f. Result: RELEASE BUILT.

  • Gate: signing key: RSA 4096, fingerprint fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25 (release); the key's public half is byte-identical to repository-key.plist.
  • sdist: voculinux-0.18.5.tar.gz, sha256 5d1a1493d94df45122a1ff3d22bbe28a86bf0fce93f66687ed857fbe834183b7. The build tree was pinned with checksum=5d1a1493..., and xbps-src resolved the distfile (xbps-src show).
  • Build: python3-PyAudio-0.2.14_1, python3-pynput-1.8.2_1, python3-pywhispercpp-1.5.0_1 (Vulkan backend present), voculinux-0.18.5_1. Each template passed xlint in the container.
  • Sign: 4 packages signed, repodata signed, 9 files staged in xbps/repo.
  • Install smoke (clean image, no key): an unsigned install is refused (rc=1) and prints the pinned fingerprint; the signed repository installs voculinux-0.18.5_1; voculinux --version prints voculinux 0.18.5; payload checks pass; imports load (pynput via its dummy backend, evdev, gi with Gtk 3.24, pywhispercpp.model, pyaudio, vocalinux).
  • Complete set: OK: complete signed glibc x86_64 set for v0.18.5, key fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25.

Independent check in a separate clean Void container, repository mounted read-only, only the repository's own key trusted after the refusal:

  • xbps refused the repository without the key; the fingerprint it printed equals the pinned fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25.
  • After trusting the .plist, xbps-install returned 0 and verified 214 RSA signatures; pkgver: voculinux-0.18.5_1.
  • voculinux --version: voculinux 0.18.5. Imports: pynput ok, evdev ok, gi ok 3 24, pywhispercpp ok, vocalinux ok.

xlint on the four committed templates: exit 0 with no output.

Findings and decisions for a human

  • sdist is not byte-reproducible. Two independent sdist builds of one commit have identical contents, but their tar and gzip timestamps differ (scripts/build_reproducible_wheel.py compares only the wheel, as before). The release records the digest of the sdist it publishes, which is the real source of truth. Normalising timestamps would change the canonical sdist for GitHub and AUR too, so it is not done here.
  • Template version lands in the release PR. The tag gate refuses a tag whose template names another release. The spec described the version bump as post-release; only the checksum is post-release now.
  • Signer identity differs. SIGNEDBY is Voculinux <xbps@voculinux.com>; repository-key.plist says Bongbetic <soubarna@live.in>. Same key, same fingerprint. Left as is.
  • Runner. bongbetic-ci must provide podman with user namespaces, tmpfs /dev/shm, python3.11, uv 0.12/0.13, and the egress in packaging/void/README.md. The runbooks I could read do not describe this runner, so this is unverified on the runner itself.
  • Not tagged or published by this PR.

Notes for reviewers

  • The branch HEAD is d4ac89f, the exact commit the dry run used. Nothing was changed after it.
  • The X backend of pynput is not exercised in a container (needs an X server); the manual hardware checklist covers it.
## Description The signed Void xbps release depended on a fork sdist attached to the release before the tag existed, and the app template carried a `PENDING_FORK_RELEASE_SHA256` marker that had to be filled by hand. Every release is now built from its tag alone, by one script that the tag workflow and a local dry run both call. - `packaging/void/build-release.sh` is the release path. It gates the tag (exact `v` plus `__version__`, and the committed template must name that release), gates the key (RSA 4096, public half equal to `repository-key.plist`) before any container starts, builds the canonical sdist from the tag, then runs three podman phases: build without the key, sign with the key mounted read-only and no network, and install-smoke in a clean image without the key. It stages the complete set and checks it. - `render-voculinux.sh` pins `version`, `_tag` and `checksum` in the container's build tree only. The committed template keeps a zero placeholder; no build commits. - The xbps version is `__version__` with the hyphen removed (`0.19.0-beta` becomes `0.19.0beta`), which xbps sorts below the release. Only `X.Y.Z` and `X.Y.Z-alpha|beta|rcN` are release forms. - The workflow checks the tag and tools first, stages the key on tmpfs (`0600`, noclobber), publishes only what the script staged (sdist, packages, signatures, repodata, client key), and removes the key in an always-run step. It no longer installs packages with apt. - `smoke.sh` runs the same path with a throwaway key. - The template bump is split: `version` and `_tag` go in the release PR, the checksum goes in a pull request after the tag. The "attach the sdist first" instruction is gone from the docs. ## Related issue Refs #16 (the Void spec) and its release-pipeline tickets #28 and #29. Deliberately not `Fixes`: merging must not close the spec, whose hardware checklist and upstream intake are separate. ## Type of change - [x] Bug fix - [ ] New feature - [ ] Breaking change - [x] Documentation - [ ] Refactor - [x] Tests - [x] Packaging / CI ## Checklist - [x] Code follows project style (Black, isort, flake8) - [x] Documentation updated when user-facing behavior or install steps change - [x] Tests added or updated for the change - [x] Local tests pass (`pytest`) for the touched modules (223 passed) - [x] Conventional commit message style ## Dry run (HEAD d4ac89f, a clean clone, real release key, output outside the repository) Command: `packaging/void/build-release.sh release v0.18.5 --key-file ~/.local/share/taema-release/privkey.pem --out-dir <scratch>/dryrun-repo3`, run from `git clone` of `d4ac89f`. Result: `RELEASE BUILT`. - Gate: `signing key: RSA 4096, fingerprint fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25 (release)`; the key's public half is byte-identical to `repository-key.plist`. - sdist: `voculinux-0.18.5.tar.gz`, sha256 `5d1a1493d94df45122a1ff3d22bbe28a86bf0fce93f66687ed857fbe834183b7`. The build tree was pinned with `checksum=5d1a1493...`, and xbps-src resolved the distfile (`xbps-src show`). - Build: `python3-PyAudio-0.2.14_1`, `python3-pynput-1.8.2_1`, `python3-pywhispercpp-1.5.0_1` (Vulkan backend present), `voculinux-0.18.5_1`. Each template passed `xlint` in the container. - Sign: 4 packages signed, repodata signed, 9 files staged in `xbps/repo`. - Install smoke (clean image, no key): an unsigned install is refused (`rc=1`) and prints the pinned fingerprint; the signed repository installs `voculinux-0.18.5_1`; `voculinux --version` prints `voculinux 0.18.5`; payload checks pass; imports load (pynput via its dummy backend, evdev, gi with Gtk 3.24, pywhispercpp.model, pyaudio, vocalinux). - Complete set: `OK: complete signed glibc x86_64 set for v0.18.5, key fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25`. Independent check in a separate clean Void container, repository mounted read-only, only the repository's own key trusted after the refusal: - xbps refused the repository without the key; the fingerprint it printed equals the pinned `fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25`. - After trusting the `.plist`, `xbps-install` returned 0 and verified 214 RSA signatures; `pkgver: voculinux-0.18.5_1`. - `voculinux --version`: `voculinux 0.18.5`. Imports: `pynput ok`, `evdev ok`, `gi ok 3 24`, `pywhispercpp ok`, `vocalinux ok`. `xlint` on the four committed templates: exit 0 with no output. ## Findings and decisions for a human - **sdist is not byte-reproducible.** Two independent sdist builds of one commit have identical contents, but their tar and gzip timestamps differ (`scripts/build_reproducible_wheel.py` compares only the wheel, as before). The release records the digest of the sdist it publishes, which is the real source of truth. Normalising timestamps would change the canonical sdist for GitHub and AUR too, so it is not done here. - **Template version lands in the release PR.** The tag gate refuses a tag whose template names another release. The spec described the version bump as post-release; only the checksum is post-release now. - **Signer identity differs.** `SIGNEDBY` is `Voculinux <xbps@voculinux.com>`; `repository-key.plist` says `Bongbetic <soubarna@live.in>`. Same key, same fingerprint. Left as is. - **Runner.** `bongbetic-ci` must provide podman with user namespaces, tmpfs `/dev/shm`, python3.11, uv 0.12/0.13, and the egress in `packaging/void/README.md`. The runbooks I could read do not describe this runner, so this is unverified on the runner itself. - Not tagged or published by this PR. ## Notes for reviewers - The branch HEAD is `d4ac89f`, the exact commit the dry run used. Nothing was changed after it. - The X backend of pynput is not exercised in a container (needs an X server); the manual hardware checklist covers it.
xavierk added 6 commits 2026-11-06 15:25:58 +00:00
The signed Void release depended on a fork sdist attached to the release
before the tag existed, and the app template carried a PENDING checksum that
had to be filled by hand. Every release is now built from its tag alone:

- packaging/void/build-release.sh is the one release path. The Gitea workflow,
  the local dry run and the smoke gate all call it.
- The canonical reproducible sdist is built from the tag with
  scripts/build_reproducible_wheel.py (Python 3.11, pinned uv).
- render-voculinux.sh pins version, _tag and checksum in the container's build
  tree only, and places the sdist where xbps-src resolves its distfile.
  The committed template keeps a zero placeholder until the post-release
  checksum pull request.
- xbps version is __version__ with the hyphen removed (0.19.0-beta becomes
  0.19.0beta), which xbps sorts below the final release.
- Builds run without the key; signing runs with the key mounted read-only and
  no network; the install smoke runs in a clean image without the key.
- The workflow gates the tag and the key before any container starts, writes
  the key to a 0600 tmpfs file, and publishes only the staged set (sdist,
  packages, signatures, repodata, client key).
- smoke.sh runs the same pipeline with a throwaway key.
- build-release.sh: POSIX-only checks (dash runs the release), a release form
  is exactly X.Y.Z or X.Y.Z-alpha|beta|rcN, and the install smoke refuses a
  package that has no smoke recipe.
- Tests: names and tag gate (exact match, committed template must agree), key
  refusals before any container starts (fake podman records calls), the
  render script pins only the build tree, podman wiring (only the sign phase
  sees the key, and it has no network), the xbps ordering of pre-release
  spellings, the workflow gates, tmpfs key staging, and the publish set
  (sdist, packages, signatures, repodata, client key).
The sdist is built from the tag by the release path and never attached by
hand. The release PR sets version and _tag in the template; the checksum is
recorded by a pull request after the tag. Runner requirements, the three
container phases and the local dry run are documented.
xbps packages are zstd-compressed. The clean install image had no zstd, so
tar could not list the payload and the smoke failed on a package that was
fine. Install zstd in the install phase.
The canonical sdist is built from the tag, but a second build of the same
commit differs in tar and gzip timestamps, so its digest is recorded from the
published asset, not from a rebuild. Correct the comments and docs that said
"reproducible sdist". No behaviour change.
fix(void): stage the sdist into the publish set before verifying it
CI / security (pull_request) Successful in 1m22s
CI / lint (pull_request) Successful in 23s
CI / ai-review (pull_request) Successful in 8s
d4ac89f0a1
The completeness check requires sdist/<name> in the output directory, but
nothing copied the built sdist there. Copy it from the build work directory
before the signing phase, and pin that order in a test.
xavierk merged commit b47a65c91e into main 2026-11-06 15:31:47 +00:00
Sign in to join this conversation.