Model recommendation keyed to the installed build and free memory, fork-sourced Void templates, signed xbps release workflow on Gitea with the pinned repository key, niri injection guard and wtype-first order, tightened naming sweep, web copy fixes, and CI security and duplication fixes.
251 lines
12 KiB
Markdown
251 lines
12 KiB
Markdown
# Void packaging
|
|
|
|
Void Linux (glibc, x86_64) templates in void-packages layout, built with the
|
|
standard Void source tooling. Submission to upstream void-packages is out of
|
|
scope for spec #16; these templates serve the signed personal xbps
|
|
repository first.
|
|
|
|
## Layout
|
|
|
|
```
|
|
packaging/void/
|
|
├── README.md this file
|
|
├── build.sh podman harness: lint + xbps-src build + import smoke
|
|
├── smoke.sh ticket #28 gate: every template, one command
|
|
├── repo.sh ticket #29 pipeline: build all, index, sign
|
|
├── container-repo.sh ticket #29 container half (index + sign + verify)
|
|
├── repository-key.plist the repository's PUBLIC key (never a private key)
|
|
├── tool_checksums.txt pinned base image and void-packages commit
|
|
└── srcpkgs/
|
|
├── python3-pywhispercpp/ ticket #26: whisper.cpp Python binding
|
|
├── ... ticket #25: small Python dependencies
|
|
└── voculinux ticket #27: the application
|
|
```
|
|
|
|
`srcpkgs/` mirrors void-packages so the tree overlays cleanly:
|
|
`cp -r packaging/void/srcpkgs/. /path/to/void-packages/srcpkgs/`
|
|
(`build.sh` does this inside the container.)
|
|
|
|
## Templates
|
|
|
|
| Template | Ticket | Notes |
|
|
|---|---|---|
|
|
| `python3-pywhispercpp` | #26 | Vulkan backend on by default; see below |
|
|
| `voculinux` | #27 | The app, from the canonical release sdist; see below |
|
|
|
|
## python3-pywhispercpp
|
|
|
|
Version `1.5.0`, pinned to the same version `install.sh`
|
|
(`PYWHISPERCPP_VERSION`) and `uv.lock` pin — never newest upstream.
|
|
The sdist checksum in the template must equal the `sha256` uv.lock records
|
|
for the sdist; `tests/test_void_pywhispercpp_template.py` fails otherwise.
|
|
|
|
- **Vulkan** is a build option, on by default. Rebuild CPU-only with
|
|
`build.sh python3-pywhispercpp -- -o ~vulkan pkg`. There is no CUDA
|
|
build: Void ships no CUDA toolkit package. Rebuilding locally with a
|
|
hand-installed toolkit (`GGML_CUDA=1`) is a documented, unsupported
|
|
escape hatch.
|
|
- **Portable CPU flags**: every ggml SIMD option is pinned off
|
|
(`-DGGML_NATIVE=OFF -DGGML_SSE42=OFF ...`), the plain x86-64 baseline
|
|
already used by the Flatpak (`#938`, `#957`). The offline test asserts
|
|
each pin in the template (the build configuration), and the harness
|
|
scans the shipped `libggml-cpu` (`.GCC.command.line`, `.comment`) for
|
|
recorded `-march=native`/`-mcpu=native`/`-mtune=native` flags and fails
|
|
on a match. The recorded-flags scan is a tripwire, not a proof: the
|
|
proof is the pinned configuration plus `setup.py`'s documented
|
|
`CMAKE_ARGS` passthrough.
|
|
- **Shared libraries**: `BUILD_SHARED_LIBS=ON` with an `$ORIGIN` rpath
|
|
(as `install.sh` does), so the extension resolves the whisper.cpp
|
|
libraries installed beside it with no `LD_LIBRARY_PATH`.
|
|
- **Offline build**: the sdist vendors whisper.cpp, pybind11, and the
|
|
ggml sources; the build fetches nothing beyond the declared distfiles.
|
|
`PYWHISPERCPP_VERSION` is injected because the sdist carries no
|
|
`version.txt` (its version otherwise comes from git metadata).
|
|
- **Runtime deps** (`python3-numpy`, `python3-requests`, `python3-tqdm`,
|
|
`python3-platformdirs`) are the sdist's own `install_requires` and were
|
|
verified to exist in current void-packages; the container build
|
|
re-verifies via dependency resolution.
|
|
|
|
## voculinux
|
|
|
|
Version `0.18.5`, matching `src/vocalinux/version.py` — never newest
|
|
upstream. The template builds the fork's canonical release source
|
|
distribution, `voculinux-0.18.5.tar.gz`, attached to the fork's `v0.18.5`
|
|
release on git.bongbetic.com, with a pinned `checksum` (see
|
|
[Checksum](#checksum)); `tests/test_void_app_template.py` fails if the
|
|
version drifts from `version.py`.
|
|
|
|
- **Runtime deps** are Void's own interpreter, GTK and desktop bindings,
|
|
the packaged Python libraries, the three templates from tickets #25
|
|
and #26 (`python3-pynput`, `python3-PyAudio`, `python3-pywhispercpp`),
|
|
and the helper tools the app shells out to: PipeWire (capture), `wtype`
|
|
(direct typing on niri), `wl-clipboard`, `xdotool` and
|
|
`pulseaudio-utils`. `ibus` is not a dependency: it cannot inject on
|
|
niri. Every name was verified to exist in current void-packages; the
|
|
container build re-verifies via dependency resolution.
|
|
- **No payload beyond the app**: no speech models (they download on
|
|
first run), no vendored wheels, no pip at build or install time. The
|
|
container smoke fails the build if the payload carries model weights
|
|
or wheels.
|
|
- **Installed names**: the `voculinux.desktop` entry, the hicolor icons,
|
|
and a `usr/share/voculinux/packaged` marker the app reads to present
|
|
package-manager updates. The fork's sdist already carries the final
|
|
names, so the template installs them as they are; the payload smoke
|
|
rejects any pre-rename leftovers in the OS-visible paths.
|
|
- **INSTALL.msg** covers what xbps cannot express: the `input` group for
|
|
the evdev hotkey backend, first-run model downloads, optional engines
|
|
as manual additions (vosk, OpenAI whisper, Parakeet, faster-whisper,
|
|
onnxruntime), the niri first run, the absent `ydotool` rung, and
|
|
updating through xbps.
|
|
- **Update checker**: packaged installs keep background checks as a
|
|
signal, but every update affordance (tray entry, notification, About
|
|
page) presents the package-manager path
|
|
(`src/vocalinux/utils/packaging.py`).
|
|
|
|
## Checksum
|
|
|
|
The template's `checksum=` is `PENDING_FORK_RELEASE_SHA256` until the
|
|
fork's `v0.18.5` release asset (`voculinux-0.18.5.tar.gz` on
|
|
git.bongbetic.com/xavierk/voculinux) exists. Once it does, set `checksum=`
|
|
to the `sha256sum` of that exact downloaded file, never a value copied from
|
|
upstream, and land the change in a pull request. Until then `xbps-src`
|
|
fails closed on the pending value, so neither the smoke gate nor the signed
|
|
repository can build `voculinux`.
|
|
|
|
## Building
|
|
|
|
```sh
|
|
packaging/void/build.sh python3-pywhispercpp
|
|
packaging/void/build.sh python3-pywhispercpp -- -o ~vulkan pkg
|
|
packaging/void/build.sh voculinux
|
|
```
|
|
|
|
Podman only, never Docker. The base image digest and the void-packages
|
|
commit come from `tool_checksums.txt`; bump them there, never in the
|
|
script.
|
|
|
|
## Smoke gate
|
|
|
|
One command runs the whole gate from a clean checkout — no host Void
|
|
install — linting, building, installing and smoke-testing every
|
|
template under `srcpkgs` in dependency order (the app last):
|
|
|
|
```sh
|
|
packaging/void/smoke.sh
|
|
# or: just void-smoke
|
|
```
|
|
|
|
Each package delegates to `build.sh`, so the pinned container and the
|
|
per-package stages (xlint, xbps-src, install, payload/version/import
|
|
smoke) stay implemented once. The gate fails fast: a broken package
|
|
prints `FAIL: <pkgname>` naming which package broke, above it the
|
|
harness echoes which stage broke. `tests/test_void_smoke_gate.py`
|
|
asserts offline that the gate stays wired to every `srcpkgs` template,
|
|
so a renamed or added template cannot silently drop out.
|
|
|
|
## Signed repository
|
|
|
|
`packaging/void/repo.sh` builds every template in smoke-gate order, indexes
|
|
the full repository (`x86_64-repodata`), and signs every binary package
|
|
(`*.sig2`) plus the repository metadata with the RSA 4096 repository key —
|
|
one command, same pinned container via podman, no host Void install:
|
|
|
|
```sh
|
|
# Run the smoke gate first: the repo assembles tested packages.
|
|
packaging/void/smoke.sh
|
|
# or: just void-smoke
|
|
|
|
printf '%s' "$XBPS_SIGNING_KEY" > /tmp/xbps-sign-key.pem
|
|
XBPS_SIGNING_KEY_FILE=/tmp/xbps-sign-key.pem packaging/void/repo.sh
|
|
# or: XBPS_SIGNING_KEY_FILE=/tmp/xbps-sign-key.pem just void-repo
|
|
```
|
|
|
|
The pipeline stages `publish/xbps/repo/` (packages, signatures, repodata)
|
|
plus `publish/xbps/keys/` (the client `.plist` named by key fingerprint,
|
|
and a `FINGERPRINT` file carrying the signer identity and fingerprint for
|
|
release notes). `publish/` is local build output and stays uncommitted.
|
|
|
|
Fail-closed: a missing, unreadable, or non-RSA4096 key aborts before any
|
|
container runs, so an unsigned repository can never publish.
|
|
|
|
### The repository key
|
|
|
|
One RSA 4096 key signs everything. Its fingerprint, which clients pin:
|
|
|
|
```text
|
|
fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25
|
|
```
|
|
|
|
- **Public half**: committed as `packaging/void/repository-key.plist`
|
|
(the same file xbps keeps in `/var/db/xbps/keys/`, named by the
|
|
fingerprint). Signer identity in the file: `Bongbetic <soubarna@live.in>`.
|
|
`tests/test_void_signed_repo.py` derives the fingerprint from this file
|
|
and fails if either README, or the release workflow, shows a different one.
|
|
- **Private half**: never committed, never in the repository, never on a
|
|
build host's disk beyond the run. It exists as the `XBPS_SIGNING_KEY`
|
|
CI secret (PEM text, newlines intact) and in the maintainer's own backup.
|
|
- **Generating a new key** (only if the key is ever replaced; clients then
|
|
have to re-pin, so this is a release-notes event):
|
|
|
|
```sh
|
|
openssl genrsa -out xbps-sign-key.pem 4096
|
|
# back the key up somewhere safe, then store it as XBPS_SIGNING_KEY
|
|
openssl rsa -in xbps-sign-key.pem -pubout # the public half, to build the .plist from
|
|
```
|
|
|
|
- **Checking a private key against the committed public key**, before it is
|
|
stored as the secret (both lines must print the same digest):
|
|
|
|
```sh
|
|
openssl rsa -in xbps-sign-key.pem -pubout 2>/dev/null | openssl md5
|
|
python3 -c "import plistlib,sys; sys.stdout.buffer.write(plistlib.load(open('packaging/void/repository-key.plist','rb'))['public-key'])" | openssl md5
|
|
```
|
|
|
|
### Releasing: the Gitea workflow
|
|
|
|
A human pushes a version tag; `.gitea/workflows/xbps-release.yml` does the
|
|
rest. It never runs on a schedule or a manual dispatch.
|
|
|
|
1. The tag must equal `v` plus `__version__` in `src/vocalinux/version.py`,
|
|
exactly; any other tag exits 1 and publishes nothing.
|
|
2. The CI secret `XBPS_SIGNING_KEY` must be set; empty exits 1 before any
|
|
container runs. The key is written to a 0600 temp file, handed to
|
|
`repo.sh` as `XBPS_SIGNING_KEY_FILE`, and removed by a final step that
|
|
always runs.
|
|
3. `repo.sh` builds and signs inside the pinned container (podman, never
|
|
Docker).
|
|
4. The output is checked: every class of file present (`*.x86_64.xbps`,
|
|
`*.sig2`, `*-repodata*`, `FINGERPRINT`, the `.plist`), every package
|
|
signed, nothing but glibc `x86_64` data, and the signing key's
|
|
fingerprint equal to the one above.
|
|
5. Everything attaches to the tag's release and to the rolling `latest`
|
|
release (replacing that release's old assets), through the Gitea API.
|
|
`latest` is what the client registration URL serves.
|
|
|
|
The workflow never commits or pushes. Bumping the templates to the new
|
|
release is a normal pull request (`docs/RELEASE_PROCESS.md`).
|
|
|
|
Secret setup, once: Gitea repository settings, Actions, Secrets, add
|
|
`XBPS_SIGNING_KEY` with the PEM private key. The runner (`bongbetic-ci`)
|
|
needs podman (the workflow installs it with apt when absent), and the job
|
|
token must be allowed to create releases.
|
|
|
|
### Client setup
|
|
|
|
```sh
|
|
# as root on the Void system
|
|
curl -fsSL -o '/var/db/xbps/keys/fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25.plist' \
|
|
https://git.bongbetic.com/xavierk/voculinux/raw/branch/main/packaging/void/repository-key.plist
|
|
echo 'repository=https://git.bongbetic.com/xavierk/voculinux/releases/download/latest' > /etc/xbps.d/voculinux.conf
|
|
xbps-install -S voculinux
|
|
```
|
|
|
|
The key file is named by its fingerprint, so xbps finds it on the first
|
|
sync. If xbps shows a fingerprint prompt anyway, answer `N` unless it
|
|
reads exactly the fingerprint above and matches the `FINGERPRINT` file on
|
|
the release; do not add `-y` to the first sync. `tests/test_void_signed_repo.py`
|
|
asserts offline that the pipeline stays wired to every `srcpkgs` template,
|
|
stays podman-only on the pinned inputs, and keeps the fail-closed key
|
|
contract; `tests/test_xbps_release_workflow.py` runs the workflow's gates
|
|
against fixtures.
|