How should Voculinux be packaged for Void Linux: template shape, CUDA and Python dependency handling, signed personal repository, and tag-driven releases without hand-uploaded assets?
Part of #1
## Question
How should Voculinux be packaged for Void Linux: template shape, CUDA and Python dependency handling, signed personal repository, and tag-driven releases without hand-uploaded assets?
Ticket: Gitea "Void xbps packaging route", part of map "Voculinux port"
(xavierk/voculinux on git.bongbetic.com; tracker conventions in docs/agents/issue-tracker.md).
Researched 2026-11-05 against primary sources. Local checkout inspected: packaging/ and scripts/distro-package-map.yaml. No repo files changed.
Naming note: this checkout is still Vocalinux everywhere in code
(pyproject.tomlname = "vocalinux", entry points vocalinux / vocalinux-gui, src/vocalinux/version.py__version__ = "0.18.5") [L1][L2].
"Voculinux" is the Gitea fork name. Every pkgname below is written as vocalinux; if the fork renames the binary, rename the template directory to
match, because a template must live at srcpkgs/<pkgname>/template with the
directory name equal to pkgname [S3].
Verdict
Dual track, personal repository first. Adopt the established Bongbetic
taema pattern [S10][S11][S12]:
Now — signed personal xbps repository, tag-driven. Keep srcpkgs/vocalinux/template (plus two dependency templates, below) in the
Voculinux repo for xbps-src users, and build release .xbps packages
with a tools/build-xbps.sh-style xbps-create script in CI inside ghcr.io/void-linux/void-glibc / void-musl containers. Sign with xbps-rindex, attach packages + signatures + repodata to the GitHub/Gitea
release, serve the repository from releases/latest/download. Never build
or upload release assets by hand [S12].
Later, optional — upstream void-packages PRs. Possible but a bigger
commitment: two missing dependency packages must land first, the app must
be runtime-tested, and Void prefers submitters with a contribution
history and software "submitted by someone other than the developer"
(exceptions possible) [S3]. Do not block the personal repo on this.
No CUDA package. Vulkan + CPU only. Void has no CUDA toolkit package, so
a GGML_CUDA build cannot be produced from Void repositories. Ship
pywhispercpp built with the Vulkan backend (full Vulkan stack exists in
Void), which matches the installer's existing priority
"Vulkan > CUDA fallback > CPU" and its rule that it never installs the CUDA
toolkit [L5].
Three facts drive everything else:
The two hard runtime dependencies pywhispercpp (default engine) and pynput are not in Void (template probes return HTTP 404, see table in
§4). Whichever route is chosen, packaging these two comes first.
Void python3-PyAudio is 0.2.12_5 [S14], but pyproject.toml requires pyaudio>=0.2.13 [L1]. Something must move: update the (orphaned) Void
template, vendor, or relax the floor after checking why it was raised.
Void has no optdepends/recommends. depends is hard. Optional
engines (Vosk, OpenAI Whisper/torch, Parakeet/sherpa-onnx,
faster-whisper — none of which are in Void either) cannot be expressed
like the AUR optdepends list [L3]; they stay separate packages the user
installs manually, documented in INSTALL.msg/README.
1. Local state (this checkout)
packaging/ contains appimage/, aur/, flatpak/, native/
(.deb/.rpm via nfpm), nix/ — no Void/xbps packaging at all [L7].
Void support today is only the install.sh path: docs/DISTRO_COMPATIBILITY.md
marks Void "Experimental — Untested, manual install required" [L6], and install.d/system_dependencies.sh installs the Void inventory with xbps-install -Sy [L6].
The Void entry in scripts/distro-package-map.yaml:177-184 is the
authoritative local inventory [L4]:
void:# cairo-devel is needed because the pinned pycairo builds from source.# base-devel compiles the pywhispercpp sdist and the evdev/PyAudio sdists.system:[python3-pip, python3-gobject, gtk+3, libappindicator-gtk3, gobject-introspection, portaudio-devel, cairo-devel, python3-devel, pkg-config, cmake, base-devel, wget, curl, unzip, shaderc, patchelf, Vulkan-Tools, xclip, xsel, wl-clipboard]text_input:xdotool:[xdotool]wtype:[wtype]ydotool:[ydotool]
Two entries look stale against current void-packages (probed 2026-11-05): libappindicator-gtk3 has no template (404) — the current package is libayatana-appindicator [S13] — and ydotool has no template (404).
Verify both with xbps-query -Rs on a real Void system before reusing
this list as depends.
packaging/native/nfpm.yaml — "thin" philosophy: interpreter, GTK,
PyGObject and every distro-packaged Python dep from the distro; only pywhispercpp and pynput vendored (under /usr/lib/vocalinux/vendor)
because Debian/Fedora don't package them [L8]. Void has the same two
gaps, so the same split applies.
packaging/nix/pywhispercpp.nix — a worked example of building
pywhispercpp without pip: PyPI sdist (vendors whisper.cpp + pybind11), GGML_NATIVE=OFF, GGML_VULKAN=ON, shared libs, dontUseCmakeConfigure
(CMake is driven by setup.py build_ext) [L9].
Releases are already tag-driven: .github/workflows/release.yml triggers
on v* tags, verifies the tag against src/vocalinux/version.py, and
builds one canonical reproducible wheel/sdist that every publisher
consumes [L10]. An xbps job fits the same shape.
Speech models are not package payload: they are verified against
digests pinned in src/vocalinux/utils/model_checksums.txt and downloaded
at first run (AGENTS.md "Speech models" row). The xbps package should not
bundle models.
2. void-packages template shape
Templates are bash scripts processed by xbps-src; by convention they start
with # Template file for '<pkg>', keep lines under ~80 columns, and define
the mandatory variables pkgname, version, revision, short_desc
(≤72 chars), maintainer ("name "), license (SPDX), homepage,
plus distfiles/checksum for source packages [S1]. AGPL (and MIT/BSD/
ISC/X11/custom) licenses require the license file inside the binary package —
use vlicense LICENSE in post_install() [S1].
For Python software the Manual prescribes python3-module if possible, or python3-pep517 for PEP 517 projects without setup.py; the pep517 style
provides no backend itself, so the backend goes in hostmakedepends [S1].
The pep517 environment automatically adds python3-build and python3-installer-bootstrap, builds with python3 -m build --no-isolation --wheel, and installs with python3 -m installer --destdir ${DESTDIR} [S2]. That is exactly the AUR
PKGBUILD's build [L3]. Dependency variables split for cross-building: hostmakedepends (run on host), makedepends (target libraries/headers;
add python3-devel when a compiled extension is built), depends
(runtime), checkdepends [S1]. Setuptools must never fetch missing build
deps at build time — anything it would download belongs in hostmakedepends [S1]. Void's python3-setuptools is 80.10.2 [S15], which
satisfies this repo's setuptools>=77 build requirement [L1].
Skeleton for the app (illustrative — versions/checksums to fill at build
time; the AUR recipe proves the canonical sdist URL shape [L3]):
# Template file for 'vocalinux'pkgname=vocalinux
version=0.18.5
revision=1build_style=python3-pep517
hostmakedepends="python3-setuptools python3-wheel"depends="python3 python3-gobject python3-cairo gtk+3 libayatana-appindicator
ibus libnotify python3-numpy python3-requests python3-pysocks python3-psutil
python3-evdev python3-PyAudio portaudio python3-pynput python3-pywhispercpp
xdotool wtype wl-clipboard xclip xsel"short_desc="Free, offline voice dictation for Linux"maintainer="<name> <email>"license="AGPL-3.0-only"homepage="https://github.com/VocaHQ/vocalinux"distfiles="https://github.com/VocaHQ/vocalinux/releases/download/v${version}/vocalinux-${version}.tar.gz"checksum=<sha256 of the canonical release sdist>
# GTK/hardware test suite cannot run in the xbps-src chroot:make_check=no
post_install(){
vlicense LICENSE
vinstall vocalinux.desktop 644 usr/share/applications
vinstall resources/icons/scalable/vocalinux.svg 644\
usr/share/icons/hicolor/scalable/apps
}
Notes on the skeleton:
Follow the AUR precedent and use the canonical release sdist (same
bytes as PyPI, checksum-verified), not a git-archive tarball [L3].
AGENTS.md applies the same rule to AUR publication. For the personal-repo
track the taema template instead points distfiles at its own tag archive
plus a vendor tarball [S10]; pick one source of truth and keep version/checksum bumped by the release workflow (§6).
make_check=no (or extended) must carry a comment explaining why — the
Manual requires the justification [S1]. Upstream Void additionally
requires the package to be runtime tested by installing and running
it, built for at least x86, cross-built e.g. -a armv6l recommended, and xlint template clean [S3].
Pure-Python app code is architecture-independent, but the old noarch
value was removed from archs [S1]; leave archs unset for the template
route. For the xbps-create personal-repo route the staged tree contains
the compiled pywhispercpp extension, so packages are arch-specific
(x86_64, x86_64-musl, …) [S11].
Triggers (gtk-icon-cache, update-desktopdb, pycompile) fire
automatically from file placement; no template code needed [S1].
An INSTALL.msg is the right place for the notes xbps cannot express as
dependencies: add the user to the input group for the evdev hotkey
backend, optional engines are separate packages, models download on first
run.
Two dependency templates must be written first (same style):
python3-pynput — easy. Pure Python PEP 517 package; upstream 1.8.2
needs six, and on Linux evdev>=1.3 and python-xlib>=0.17 [S16], all
already in Void (python3-evdev 1.7.1, python3-xlib; verify python3-six). Model it on python3-evdev's template [S14].
python3-pywhispercpp — the real work. Compiled extension: makedepends="python3-devel cmake …" (CMake is invoked by the sdist's setup.py build_ext, so do not use the cmake build style — the Nix
recipe documents the same trap [L9]). Build env, following [L9] and the
AppImage rules (portable CPU flags; a host-native build SIGILLs on older
CPUs): CMAKE_ARGS="-DBUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DGGML_VULKAN=ON", PYWHISPERCPP_VERSION=${version}. Runtime deps per PyPI metadata: numpy, requests, tqdm, platformdirs [S17] — all in Void. Pin the
version to PYWHISPERCPP_VERSION="1.5.0" in install.sh [L5] / uv.lock,
not the newest PyPI release (1.5.1). Expose the GPU choice as a Void build option (build_options="vulkan", default on) so xbps-src
users can rebuild CPU-only [S1]. Verify with pythonImportsCheck-equivalents: pywhispercpp, pywhispercpp.model, _pywhispercpp [L9].
3. CUDA and GPU handling
No CUDA toolkit exists in Void. Probes of srcpkgs/cuda/template and srcpkgs/nvidia-cuda-toolkit/template return 404. The nvidia package
is repository="nonfree", archs="x86_64", and ships only the driver
runtime libcuda.so.1 — no nvcc, no cuda_runtime.h, no libcudart
[S9].
This repo's installer requires all three (bin/nvcc, include/cuda_runtime.h, libcudart.so*) before attempting a GGML_CUDA=1 build, and states "The installer never installs the CUDA
toolkit" [L5]. A Void binary package therefore cannot be built with CUDA
from Void repositories alone, on either track. A template build_options="cuda" would be dead weight upstream (Void's builders do
not build restricted packages, and there is no toolkit package to
depend on [S1]).
Vulkan is fully available: Vulkan-Headers, vulkan-loader, Vulkan-Tools, shaderc (provides glslc) and glslang all have
templates (probed 200). The local Void inventory already lists shaderc
and Vulkan-Tools [L4]. Build libggml-vulkan.so as the shared backend;
ggml loads backends at runtime, so the same binary still runs CPU-only on
machines without a Vulkan device. This matches installer priority
(Vulkan first, including on NVIDIA) [L5].
The OpenAI Whisper engine's CUDA path is equally out: no torch in Void
(404 for python3-torch/python3-pytorch), and this repo pins CPU torch
for the installer anyway (AGENTS.md). Treat the whisper extra as
unsupported on Void; document, don't block.
NVIDIA users who install the toolkit from NVIDIA's own tarball can
rebuild python3-pywhispercpp locally with xbps-src and GGML_CUDA=1 — an unsupported, documented escape hatch, mirroring how install.sh treats a user-provided toolkit [L5].
4. Python dependency handling
Void's interpreter is Python 3.14.8 [S8]; the app requires >=3.11 [L1], so
no version pinning is needed, but every pyproject.toml dependency must map
to a Void binary package (depends), and build-time requirements to hostmakedepends/makedepends [S1]. Presence probed 2026-11-05 via raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/<pkg>/template
HTTP status (200 = template exists, 404 = absent):
System (non-Python) side, from the local Void inventory [L4] cross-checked
the same way: present — gtk+3, gobject-introspection, portaudio
(+-devel), python3-cairo, ibus, libnotify, libayatana-appindicator, gtk-layer-shell, xdotool, wtype, wl-clipboard, xclip, xsel, shaderc, Vulkan-Tools, cmake, python3-build, python3-installer, python3-setuptools, python3-wheel, python3-tqdm, python3-platformdirs. Absent — libappindicator(-gtk3) (use libayatana-appindicator), ydotool.
Handling rules that follow:
No pip at build or install time. The template route forbids fetched
build deps [S1]; the xbps-create route stages files built in a Void
container from Void packages + the two source-built deps [S11]. The --system-site-packages venv dance in install.sh (AGENTS.md) does not
apply to a system package: gi comes from python3-gobject directly.
Fix the PyAudio gap deliberately. Options, in order: (a) update
Void's orphaned python3-PyAudio to 0.2.14 — also the contribution
history Void prefers before new-package submissions [S3]; (b) for the
personal repo, build python3-PyAudio 0.2.14 in-repo; (c) relax the >=0.2.13 floor only after finding why it was raised. Do not silently
set noverifypydeps to hide it.
musl is a first-class Void target. Glibc manylinux wheels are
unusable on x86_64-musl. The template route gets musl builds for free
(Void builds every template per-libc); the xbps-create route must
build pywhispercpp/pynput/evdev/PyAudio from sdist inside the
musl container and must avoid Node-based Actions steps there [S10].
Byte-compilation and shebangs are automatic via the pycompile
trigger for files under usr/lib/pythonX.Y/site-packages; use pycompile_module="vocalinux" only if the default guesses wrong [S1].
Version-locking policy differs from install.sh. The installer
consumes hash-pinned uv.lock exports (AGENTS.md); a Void package
deliberately floats on Void's package versions within the pyproject.toml ranges. Accept that, and let the release smoke test
(§6) catch drift — e.g. Void's Python moving from 3.14 to 3.15 rebuilds
every python3-* package's extension paths.
"Remote repositories must be signed"; xbps-install refuses unsigned
remote packages. Local repositories need no signature [S5][S6].
The signing key is a PEM-encoded RSA key (ssh-keygen -t rsa -b 4096 -m PEM
or openssl genrsa … 4096) [S5]. One-time repository initialisation
embeds the public half: xbps-rindex --privkey private.pem --sign --signedby "Name <email>" /path/to/repo; each package is then signed
individually: xbps-rindex --privkey private.pem --sign-pkg repo/*.xbps
(-S short form). "Future packages will not be automatically signed" —
signing is a per-release CI step, not a one-off [S5][S7].
Users add the repo with one line in /etc/xbps.d/*.conf: repository=<URL> [S6].
Verified working shape (taema): CI builds per-arch .xbps, then a sign
job runs XBPS_ARCH=<arch> xbps-rindex -a <pkg>.xbps, xbps-rindex --privkey "$key" --signedby "Bongbetic <…>" -s . and xbps-rindex --privkey "$key" -S <pkg>.xbps per arch, and uploads packages,
per-package .sig2 signatures and signed <arch>-repodata to the
release [S10][S12].
Serving trick: point the repository URL at https://github.com/<owner>/<repo>/releases/latest/download. xbps reads
repodata from the newest release, so every release must carry the full
repodata; no gh-pages branch needed [S12]. User install is then:
echo'repository=https://github.com/<owner>/<repo>/releases/latest/download'\
| sudo tee /etc/xbps.d/20-vocalinux.conf
sudo xbps-install -S vocalinux
Key hygiene (standing Bongbetic practice): RSA 4096 key, mode 600, never
committed, stored as the Actions secret XBPS_PRIVKEY; publish the key
fingerprint in the README so users can check it at first sync; reuse the
existing taema key so users accept one fingerprint [S12]. (Fingerprint
value itself lives in personal memory release-xbps.md [S12].)
Void's own caveat to repeat in the README: "The Void project does not
support any third-party package repositories" — users opt in knowingly
[S6].
Architecture matrix: start with x86_64 + x86_64-musl only. That is
what the current taema workflow actually builds ("Packages are built only
for x86_64 glibc and x86_64 musl", with a per-libc container matrix) [S10][S11].
Personal memory still describes a 9-arch release [S12]; treat that as stale
— adding aarch64 later means cross/QEMU builds of the pywhispercpp
extension plus a native smoke test, a separate ticket.
6. Tag-driven releases
Combine this repo's existing release discipline [L10] with taema's [S10][S12]:
Bump __version__ in src/vocalinux/version.py (the version source of
truth, AGENTS.md), commit, push a tag. The workflow fails unless the
tag equals the source version (taema uses <name>-vX.Y.Z and a sed
check against __version__; this repo's release.yml does the same for v*) [S10][L10]. For the fork, prefer voculinux-vX.Y.Z so xbps
releases never collide with upstream-style v* tags — decide once, in
the map ticket. Pushing commits/tags to GitHub needs the user's explicit
OK each time [S12].
Build jobs run in ghcr.io/void-linux/void-glibc / void-musl: install
build deps with xbps-install, run tests as a non-root user, run tools/build-xbps.sh (stage → xbps-create -A <arch> -n <pkg>-<ver>_1 -D "<depends>" … → xbps-rindex -a) [S10][S11]. For this app the stage
step installs the canonical wheel with python -m installer --destdir="$stage", adds the two source-built deps (or depends on
separately built python3-pynput/python3-pywhispercpp.xbps in the
same repo — cleaner, and what the template route needs anyway), the
desktop file, icons and license [L3][L8]. Add an import smoke inside the
container (vocalinux --version, import pywhispercpp.model), mirroring packaging/native/smoke-test.sh.
Sign job (glibc container): index, sign repo, sign packages, upload all assets (packages, .sig2, every <arch>-repodata) to the draft
release, publish [S10]. If XBPS_PRIVKEY is unset, fail rather than
publish unsigned (taema degrades to package-only; do not copy that —
an unsigned remote repo is uninstallable [S6]).
Post-release job bumps version and checksum in srcpkgs/vocalinux/template (and README download names) and commits to main [S10][S12], so xbps-src users always build the released bytes.
Never build or upload release assets by hand [S12].
7. Risks and open questions
Name.vocalinux vs voculinux for pkgname, repo file
(20-vocalinux.conf), tags (v* vs voculinux-v*), and release host
(GitHub VocaHQ/vocalinux vs Gitea xavierk/voculinux). Gitea releases
also expose a "latest download" style URL, but the verified taema pattern
is GitHub [S10][S12]. One decision in the map ticket settles all four.
PyAudio floor (>=0.2.13 vs Void 0.2.12) — find the commit that
raised it before relaxing anything (§4 rule 2).
Stale local Void inventory: libappindicator-gtk3 and ydotool in
[L4] do not resolve as templates today. Without ydotool, Wayland text
injection falls back to IBus/RemoteDesktop portal/wtype — say so in INSTALL.msg; packaging ydotool is a possible follow-up ticket.
No systemd on Void (runit). Anything in the app assuming a user
systemd service (e.g. per-user ydotool.service, autostart units) needs
the XDG-autostart path instead. Confirm during the runtime test [S3].
Upstream acceptance is uncertain: package requirements (system-wide,
compiled, or required-by-another), mature release history, non-author
submitter preference [S3]. The app arguably meets "system + compiled
(via pywhispercpp)", but budget it as a multi-PR campaign
(PyAudio update → python3-pynput → python3-pywhispercpp → app), each xlint-clean and runtime-tested [S3].
Update checker: the app self-checks GitHub releases; under xbps,
updates should arrive via xbps-install -Su [S10]. Decide whether the
package patches out or documents the built-in checker (packaging
convention only — not verified against app code in this research).
8. Implementation checklist (ordered)
Verify inventory on a real Void x86_64 (glibc) VM/container: xbps-query -Rs each [L4] entry; confirm libayatana-appindicator
typelib name and python3-six.
Resolve PyAudio (update template locally to 0.2.14 first).
Add tools/build-xbps.sh (podman-friendly: runs inside ghcr.io/void-linux/void-{glibc,musl}), staging the canonical wheel.
Add the tag workflow (voculinux-v* or v* per §7): build matrix →
sign with XBPS_PRIVKEY → publish release with full repodata →
template-checksum bump commit.
Method note: template presence (§4) was probed by HTTP status of each srcpkgs/<pkg>/template on void-packages master, 2026-11-05; a 404 means
no template under that exact name (subpackages of another source package
would not appear). Re-verify with xbps-query -Rs before implementation.
# Void xbps packaging route — research findings
Ticket: Gitea "Void xbps packaging route", part of map "Voculinux port"
(`xavierk/voculinux` on `git.bongbetic.com`; tracker conventions in
[docs/agents/issue-tracker.md](/media/Toto/Documents/bongbetic/vocalinux/docs/agents/issue-tracker.md)).
Researched 2026-11-05 against primary sources. Local checkout inspected:
`packaging/` and `scripts/distro-package-map.yaml`. No repo files changed.
Naming note: this checkout is still **Vocalinux** everywhere in code
(`pyproject.toml` `name = "vocalinux"`, entry points `vocalinux` /
`vocalinux-gui`, `src/vocalinux/version.py` `__version__ = "0.18.5"`) [L1][L2].
"Voculinux" is the Gitea fork name. Every `pkgname` below is written as
`vocalinux`; if the fork renames the binary, rename the template directory to
match, because a template must live at `srcpkgs/<pkgname>/template` with the
directory name equal to `pkgname` [S3].
## Verdict
**Dual track, personal repository first.** Adopt the established Bongbetic
taema pattern [S10][S11][S12]:
1. **Now — signed personal xbps repository, tag-driven.** Keep
`srcpkgs/vocalinux/template` (plus two dependency templates, below) in the
Voculinux repo for `xbps-src` users, and build release `.xbps` packages
with a `tools/build-xbps.sh`-style `xbps-create` script in CI inside
`ghcr.io/void-linux/void-glibc` / `void-musl` containers. Sign with
`xbps-rindex`, attach packages + signatures + repodata to the GitHub/Gitea
release, serve the repository from `releases/latest/download`. Never build
or upload release assets by hand [S12].
2. **Later, optional — upstream `void-packages` PRs.** Possible but a bigger
commitment: two missing dependency packages must land first, the app must
be runtime-tested, and Void prefers submitters with a contribution
history and software "submitted by someone other than the developer"
(exceptions possible) [S3]. Do not block the personal repo on this.
**No CUDA package. Vulkan + CPU only.** Void has no CUDA toolkit package, so
a `GGML_CUDA` build cannot be produced from Void repositories. Ship
pywhispercpp built with the Vulkan backend (full Vulkan stack exists in
Void), which matches the installer's existing priority
"Vulkan > CUDA fallback > CPU" and its rule that it never installs the CUDA
toolkit [L5].
Three facts drive everything else:
- The two hard runtime dependencies `pywhispercpp` (default engine) and
`pynput` are **not in Void** (template probes return HTTP 404, see table in
§4). Whichever route is chosen, packaging these two comes first.
- Void `python3-PyAudio` is `0.2.12_5` [S14], but `pyproject.toml` requires
`pyaudio>=0.2.13` [L1]. Something must move: update the (orphaned) Void
template, vendor, or relax the floor after checking why it was raised.
- Void has **no `optdepends`/recommends**. `depends` is hard. Optional
engines (Vosk, OpenAI Whisper/torch, Parakeet/sherpa-onnx,
faster-whisper — none of which are in Void either) cannot be expressed
like the AUR `optdepends` list [L3]; they stay separate packages the user
installs manually, documented in `INSTALL.msg`/README.
## 1. Local state (this checkout)
- `packaging/` contains `appimage/`, `aur/`, `flatpak/`, `native/`
(.deb/.rpm via nfpm), `nix/` — **no Void/xbps packaging at all** [L7].
- Void support today is only the `install.sh` path: `docs/DISTRO_COMPATIBILITY.md`
marks Void "Experimental — Untested, manual install required" [L6], and
`install.d/system_dependencies.sh` installs the Void inventory with
`xbps-install -Sy` [L6].
- The Void entry in `scripts/distro-package-map.yaml:177-184` is the
authoritative local inventory [L4]:
```yaml
void:
# cairo-devel is needed because the pinned pycairo builds from source.
# base-devel compiles the pywhispercpp sdist and the evdev/PyAudio sdists.
system: [python3-pip, python3-gobject, gtk+3, libappindicator-gtk3, gobject-introspection, portaudio-devel, cairo-devel, python3-devel, pkg-config, cmake, base-devel, wget, curl, unzip, shaderc, patchelf, Vulkan-Tools, xclip, xsel, wl-clipboard]
text_input:
xdotool: [xdotool]
wtype: [wtype]
ydotool: [ydotool]
```
Two entries look stale against current void-packages (probed 2026-11-05):
`libappindicator-gtk3` has no template (404) — the current package is
`libayatana-appindicator` [S13] — and `ydotool` has no template (404).
Verify both with `xbps-query -Rs` on a real Void system before reusing
this list as `depends`.
- Closest local analogues to copy from:
- AUR `packaging/aur/vocalinux/PKGBUILD` — canonical release sdist as
source, `python -m build --wheel --no-isolation`, `python -m installer`,
full `depends` list, desktop file + scalable icons [L3].
- `packaging/native/nfpm.yaml` — "thin" philosophy: interpreter, GTK,
PyGObject and every distro-packaged Python dep from the distro; only
`pywhispercpp` and `pynput` vendored (under `/usr/lib/vocalinux/vendor`)
because Debian/Fedora don't package them [L8]. Void has the same two
gaps, so the same split applies.
- `packaging/nix/pywhispercpp.nix` — a worked example of building
pywhispercpp without pip: PyPI sdist (vendors whisper.cpp + pybind11),
`GGML_NATIVE=OFF`, `GGML_VULKAN=ON`, shared libs, `dontUseCmakeConfigure`
(CMake is driven by `setup.py build_ext`) [L9].
- Releases are already tag-driven: `.github/workflows/release.yml` triggers
on `v*` tags, verifies the tag against `src/vocalinux/version.py`, and
builds one canonical reproducible wheel/sdist that every publisher
consumes [L10]. An xbps job fits the same shape.
- Speech models are **not** package payload: they are verified against
digests pinned in `src/vocalinux/utils/model_checksums.txt` and downloaded
at first run (AGENTS.md "Speech models" row). The xbps package should not
bundle models.
## 2. void-packages template shape
Templates are bash scripts processed by `xbps-src`; by convention they start
with `# Template file for '<pkg>'`, keep lines under ~80 columns, and define
the mandatory variables `pkgname`, `version`, `revision`, `short_desc`
(≤72 chars), `maintainer` ("name <email>"), `license` (SPDX), `homepage`,
plus `distfiles`/`checksum` for source packages [S1]. `AGPL` (and MIT/BSD/
ISC/X11/custom) licenses require the license file inside the binary package —
use `vlicense LICENSE` in `post_install()` [S1].
For Python software the Manual prescribes `python3-module` if possible, or
`python3-pep517` for PEP 517 projects without `setup.py`; the pep517 style
provides no backend itself, so the backend goes in `hostmakedepends` [S1].
The pep517 environment automatically adds `python3-build` and
`python3-installer-bootstrap`, builds with
`python3 -m build --no-isolation --wheel`, and installs with
`python3 -m installer --destdir ${DESTDIR}` [S2]. That is exactly the AUR
PKGBUILD's build [L3]. Dependency variables split for cross-building:
`hostmakedepends` (run on host), `makedepends` (target libraries/headers;
add `python3-devel` when a compiled extension is built), `depends`
(runtime), `checkdepends` [S1]. Setuptools must never fetch missing build
deps at build time — anything it would download belongs in
`hostmakedepends` [S1]. Void's `python3-setuptools` is 80.10.2 [S15], which
satisfies this repo's `setuptools>=77` build requirement [L1].
Skeleton for the app (illustrative — versions/checksums to fill at build
time; the AUR recipe proves the canonical sdist URL shape [L3]):
```bash
# Template file for 'vocalinux'
pkgname=vocalinux
version=0.18.5
revision=1
build_style=python3-pep517
hostmakedepends="python3-setuptools python3-wheel"
depends="python3 python3-gobject python3-cairo gtk+3 libayatana-appindicator
ibus libnotify python3-numpy python3-requests python3-pysocks python3-psutil
python3-evdev python3-PyAudio portaudio python3-pynput python3-pywhispercpp
xdotool wtype wl-clipboard xclip xsel"
short_desc="Free, offline voice dictation for Linux"
maintainer="<name> <email>"
license="AGPL-3.0-only"
homepage="https://github.com/VocaHQ/vocalinux"
distfiles="https://github.com/VocaHQ/vocalinux/releases/download/v${version}/vocalinux-${version}.tar.gz"
checksum=<sha256 of the canonical release sdist>
# GTK/hardware test suite cannot run in the xbps-src chroot:
make_check=no
post_install() {
vlicense LICENSE
vinstall vocalinux.desktop 644 usr/share/applications
vinstall resources/icons/scalable/vocalinux.svg 644 \
usr/share/icons/hicolor/scalable/apps
}
```
Notes on the skeleton:
- Follow the AUR precedent and use the **canonical release sdist** (same
bytes as PyPI, checksum-verified), not a git-archive tarball [L3].
AGENTS.md applies the same rule to AUR publication. For the personal-repo
track the taema template instead points `distfiles` at its own tag archive
plus a vendor tarball [S10]; pick one source of truth and keep
`version`/`checksum` bumped by the release workflow (§6).
- `make_check=no` (or `extended`) must carry a comment explaining why — the
Manual requires the justification [S1]. Upstream Void additionally
requires the package to be **runtime tested** by installing and running
it, built for at least x86, cross-built e.g. `-a armv6l` recommended, and
`xlint template` clean [S3].
- Pure-Python app code is architecture-independent, but the old `noarch`
value was removed from `archs` [S1]; leave `archs` unset for the template
route. For the `xbps-create` personal-repo route the staged tree contains
the compiled `pywhispercpp` extension, so packages are arch-specific
(`x86_64`, `x86_64-musl`, …) [S11].
- Triggers (`gtk-icon-cache`, `update-desktopdb`, `pycompile`) fire
automatically from file placement; no template code needed [S1].
- An `INSTALL.msg` is the right place for the notes xbps cannot express as
dependencies: add the user to the `input` group for the evdev hotkey
backend, optional engines are separate packages, models download on first
run.
Two dependency templates must be written first (same style):
- **`python3-pynput`** — easy. Pure Python PEP 517 package; upstream 1.8.2
needs `six`, and on Linux `evdev>=1.3` and `python-xlib>=0.17` [S16], all
already in Void (`python3-evdev` 1.7.1, `python3-xlib`; verify
`python3-six`). Model it on `python3-evdev`'s template [S14].
- **`python3-pywhispercpp`** — the real work. Compiled extension:
`makedepends="python3-devel cmake …"` (CMake is invoked by the sdist's
`setup.py build_ext`, so do not use the `cmake` build style — the Nix
recipe documents the same trap [L9]). Build env, following [L9] and the
AppImage rules (portable CPU flags; a host-native build SIGILLs on older
CPUs):
`CMAKE_ARGS="-DBUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DGGML_VULKAN=ON"`,
`PYWHISPERCPP_VERSION=${version}`. Runtime deps per PyPI metadata:
`numpy`, `requests`, `tqdm`, `platformdirs` [S17] — all in Void. Pin the
version to `PYWHISPERCPP_VERSION="1.5.0"` in `install.sh` [L5] / `uv.lock`,
not the newest PyPI release (1.5.1). Expose the GPU choice as a Void
**build option** (`build_options="vulkan"`, default on) so `xbps-src`
users can rebuild CPU-only [S1]. Verify with
`pythonImportsCheck`-equivalents: `pywhispercpp`, `pywhispercpp.model`,
`_pywhispercpp` [L9].
## 3. CUDA and GPU handling
- **No CUDA toolkit exists in Void.** Probes of `srcpkgs/cuda/template` and
`srcpkgs/nvidia-cuda-toolkit/template` return 404. The `nvidia` package
is `repository="nonfree"`, `archs="x86_64"`, and ships only the driver
runtime `libcuda.so.1` — no `nvcc`, no `cuda_runtime.h`, no `libcudart`
[S9].
- This repo's installer requires all three (`bin/nvcc`,
`include/cuda_runtime.h`, `libcudart.so*`) before attempting a
`GGML_CUDA=1` build, and states "The installer never installs the CUDA
toolkit" [L5]. A Void binary package therefore cannot be built with CUDA
from Void repositories alone, on either track. A template
`build_options="cuda"` would be dead weight upstream (Void's builders do
not build `restricted` packages, and there is no toolkit package to
depend on [S1]).
- **Vulkan is fully available**: `Vulkan-Headers`, `vulkan-loader`,
`Vulkan-Tools`, `shaderc` (provides `glslc`) and `glslang` all have
templates (probed 200). The local Void inventory already lists `shaderc`
and `Vulkan-Tools` [L4]. Build `libggml-vulkan.so` as the shared backend;
ggml loads backends at runtime, so the same binary still runs CPU-only on
machines without a Vulkan device. This matches installer priority
(Vulkan first, including on NVIDIA) [L5].
- The OpenAI Whisper engine's CUDA path is equally out: no `torch` in Void
(404 for `python3-torch`/`python3-pytorch`), and this repo pins CPU torch
for the installer anyway (AGENTS.md). Treat the `whisper` extra as
unsupported on Void; document, don't block.
- NVIDIA users who install the toolkit from NVIDIA's own tarball can
rebuild `python3-pywhispercpp` locally with `xbps-src` and
`GGML_CUDA=1` — an unsupported, documented escape hatch, mirroring how
`install.sh` treats a user-provided toolkit [L5].
## 4. Python dependency handling
Void's interpreter is Python 3.14.8 [S8]; the app requires `>=3.11` [L1], so
no version pinning is needed, but every `pyproject.toml` dependency must map
to a Void binary package (`depends`), and build-time requirements to
`hostmakedepends`/`makedepends` [S1]. Presence probed 2026-11-05 via
`raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/<pkg>/template`
HTTP status (200 = template exists, 404 = absent):
| `pyproject.toml` requirement [L1] | Void package | Status |
|---|---|---|
| `PyGObject` (distro `gi`, never pip) | `python3-gobject` 3.56.2, `provides="py3:gi-…"` [S13] | present |
| `numpy>=1.22,<3` | `python3-numpy` | present |
| `requests>=2.28` | `python3-requests` | present |
| `PySocks>=1.7.1,<2` | `python3-pysocks` | present |
| `psutil>=5.9,<8` | `python3-psutil` | present |
| `evdev>=1.6,<2` | `python3-evdev` 1.7.1 (`python3-pep517`) [S14] | present |
| `pyaudio>=0.2.13` | `python3-PyAudio` **0.2.12_5** (`python3-module`, orphaned) [S14] | **version too old** |
| `pywhispercpp>=1.5,<2` (default engine) | — (also no `whisper.cpp`/`ggml`) | **absent — package it** |
| `pynput>=1.7.6,<2` | — (its deps `python3-evdev`, `python3-xlib` present) | **absent — package it** |
| extra `vad`: `onnxruntime>=1.16` | — | absent (optional) |
| extra `parakeet`: `sherpa-onnx` | — | absent (optional) |
| extra `faster_whisper` (+ `ctranslate2`) | — | absent (optional) |
| extra `vosk` | — | absent (optional) |
| extra `whisper`: `openai-whisper`, `torch` | — | absent (optional) |
System (non-Python) side, from the local Void inventory [L4] cross-checked
the same way: present — `gtk+3`, `gobject-introspection`, `portaudio`
(+`-devel`), `python3-cairo`, `ibus`, `libnotify`, `libayatana-appindicator`,
`gtk-layer-shell`, `xdotool`, `wtype`, `wl-clipboard`, `xclip`, `xsel`,
`shaderc`, `Vulkan-Tools`, `cmake`, `python3-build`, `python3-installer`,
`python3-setuptools`, `python3-wheel`, `python3-tqdm`,
`python3-platformdirs`. Absent — `libappindicator(-gtk3)` (use
`libayatana-appindicator`), `ydotool`.
Handling rules that follow:
1. **No pip at build or install time.** The template route forbids fetched
build deps [S1]; the `xbps-create` route stages files built in a Void
container from Void packages + the two source-built deps [S11]. The
`--system-site-packages` venv dance in `install.sh` (AGENTS.md) does not
apply to a system package: `gi` comes from `python3-gobject` directly.
2. **Fix the PyAudio gap deliberately.** Options, in order: (a) update
Void's orphaned `python3-PyAudio` to 0.2.14 — also the contribution
history Void prefers before new-package submissions [S3]; (b) for the
personal repo, build `python3-PyAudio` 0.2.14 in-repo; (c) relax the
`>=0.2.13` floor only after finding why it was raised. Do not silently
set `noverifypydeps` to hide it.
3. **musl is a first-class Void target.** Glibc manylinux wheels are
unusable on `x86_64-musl`. The template route gets musl builds for free
(Void builds every template per-libc); the `xbps-create` route must
build `pywhispercpp`/`pynput`/`evdev`/`PyAudio` from sdist inside the
musl container and must avoid Node-based Actions steps there [S10].
4. **Byte-compilation and shebangs are automatic** via the `pycompile`
trigger for files under `usr/lib/pythonX.Y/site-packages`; use
`pycompile_module="vocalinux"` only if the default guesses wrong [S1].
5. **Version-locking policy differs from `install.sh`.** The installer
consumes hash-pinned `uv.lock` exports (AGENTS.md); a Void package
deliberately floats on Void's package versions within the
`pyproject.toml` ranges. Accept that, and let the release smoke test
(§6) catch drift — e.g. Void's Python moving from 3.14 to 3.15 rebuilds
every `python3-*` package's extension paths.
## 5. Signed personal repository
Primary-source mechanics, Void Handbook + `xbps-rindex(1)`:
- "Remote repositories **must** be signed"; `xbps-install` refuses unsigned
remote packages. Local repositories need no signature [S5][S6].
- The signing key is a **PEM-encoded RSA key** (`ssh-keygen -t rsa -b 4096 -m PEM`
or `openssl genrsa … 4096`) [S5]. One-time repository initialisation
embeds the public half: `xbps-rindex --privkey private.pem --sign
--signedby "Name <email>" /path/to/repo`; each package is then signed
individually: `xbps-rindex --privkey private.pem --sign-pkg repo/*.xbps`
(`-S` short form). "Future packages will not be automatically signed" —
signing is a per-release CI step, not a one-off [S5][S7].
- Users add the repo with one line in `/etc/xbps.d/*.conf`:
`repository=<URL>` [S6].
- Verified working shape (taema): CI builds per-arch `.xbps`, then a sign
job runs `XBPS_ARCH=<arch> xbps-rindex -a <pkg>.xbps`, `xbps-rindex
--privkey "$key" --signedby "Bongbetic <…>" -s .` and `xbps-rindex
--privkey "$key" -S <pkg>.xbps` per arch, and uploads packages,
per-package `.sig2` signatures and signed `<arch>-repodata` to the
release [S10][S12].
- Serving trick: point the repository URL at
`https://github.com/<owner>/<repo>/releases/latest/download`. xbps reads
repodata from the newest release, so **every release must carry the full
repodata**; no gh-pages branch needed [S12]. User install is then:
```bash
echo 'repository=https://github.com/<owner>/<repo>/releases/latest/download' \
| sudo tee /etc/xbps.d/20-vocalinux.conf
sudo xbps-install -S vocalinux
```
- Key hygiene (standing Bongbetic practice): RSA 4096 key, mode 600, never
committed, stored as the Actions secret `XBPS_PRIVKEY`; publish the key
fingerprint in the README so users can check it at first sync; reuse the
existing taema key so users accept one fingerprint [S12]. (Fingerprint
value itself lives in personal memory `release-xbps.md` [S12].)
- Void's own caveat to repeat in the README: "The Void project does not
support *any* third-party package repositories" — users opt in knowingly
[S6].
Architecture matrix: start with **`x86_64` + `x86_64-musl` only**. That is
what the current taema workflow actually builds ("Packages are built only
for x86_64 glibc and x86_64 musl", with a per-libc container matrix) [S10][S11].
Personal memory still describes a 9-arch release [S12]; treat that as stale
— adding `aarch64` later means cross/QEMU builds of the `pywhispercpp`
extension plus a native smoke test, a separate ticket.
## 6. Tag-driven releases
Combine this repo's existing release discipline [L10] with taema's [S10][S12]:
1. Bump `__version__` in `src/vocalinux/version.py` (the version source of
truth, AGENTS.md), commit, push a tag. The workflow **fails unless the
tag equals the source version** (taema uses `<name>-vX.Y.Z` and a `sed`
check against `__version__`; this repo's `release.yml` does the same for
`v*`) [S10][L10]. For the fork, prefer `voculinux-vX.Y.Z` so xbps
releases never collide with upstream-style `v*` tags — decide once, in
the map ticket. Pushing commits/tags to GitHub needs the user's explicit
OK each time [S12].
2. Build jobs run in `ghcr.io/void-linux/void-glibc` / `void-musl`: install
build deps with `xbps-install`, run tests as a non-root user, run
`tools/build-xbps.sh` (stage → `xbps-create -A <arch> -n <pkg>-<ver>_1 -D
"<depends>" …` → `xbps-rindex -a`) [S10][S11]. For this app the stage
step installs the canonical wheel with `python -m installer
--destdir="$stage"`, adds the two source-built deps (or `depends` on
separately built `python3-pynput`/`python3-pywhispercpp` `.xbps` in the
same repo — cleaner, and what the template route needs anyway), the
desktop file, icons and license [L3][L8]. Add an import smoke inside the
container (`vocalinux --version`, `import pywhispercpp.model`), mirroring
`packaging/native/smoke-test.sh`.
3. Sign job (glibc container): index, sign repo, sign packages, upload
**all** assets (packages, `.sig2`, every `<arch>-repodata`) to the draft
release, publish [S10]. If `XBPS_PRIVKEY` is unset, fail rather than
publish unsigned (taema degrades to package-only; do not copy that —
an unsigned remote repo is uninstallable [S6]).
4. Post-release job bumps `version` and `checksum` in
`srcpkgs/vocalinux/template` (and README download names) and commits to
`main` [S10][S12], so `xbps-src` users always build the released bytes.
5. Never build or upload release assets by hand [S12].
## 7. Risks and open questions
- **Name.** `vocalinux` vs `voculinux` for `pkgname`, repo file
(`20-vocalinux.conf`), tags (`v*` vs `voculinux-v*`), and release host
(GitHub `VocaHQ/vocalinux` vs Gitea `xavierk/voculinux`). Gitea releases
also expose a "latest download" style URL, but the verified taema pattern
is GitHub [S10][S12]. One decision in the map ticket settles all four.
- **PyAudio floor** (`>=0.2.13` vs Void 0.2.12) — find the commit that
raised it before relaxing anything (§4 rule 2).
- **Stale local Void inventory**: `libappindicator-gtk3` and `ydotool` in
[L4] do not resolve as templates today. Without `ydotool`, Wayland text
injection falls back to IBus/RemoteDesktop portal/`wtype` — say so in
`INSTALL.msg`; packaging `ydotool` is a possible follow-up ticket.
- **No systemd on Void** (runit). Anything in the app assuming a user
systemd service (e.g. per-user `ydotool.service`, autostart units) needs
the XDG-autostart path instead. Confirm during the runtime test [S3].
- **Upstream acceptance is uncertain**: package requirements (system-wide,
compiled, or required-by-another), mature release history, non-author
submitter preference [S3]. The app arguably meets "system + compiled
(via pywhispercpp)", but budget it as a multi-PR campaign
(PyAudio update → `python3-pynput` → `python3-pywhispercpp` → app), each
`xlint`-clean and runtime-tested [S3].
- **Update checker**: the app self-checks GitHub releases; under xbps,
updates should arrive via `xbps-install -Su` [S10]. Decide whether the
package patches out or documents the built-in checker (packaging
convention only — not verified against app code in this research).
## 8. Implementation checklist (ordered)
1. Verify inventory on a real Void x86_64 (glibc) VM/container:
`xbps-query -Rs` each [L4] entry; confirm `libayatana-appindicator`
typelib name and `python3-six`.
2. Write `srcpkgs/python3-pynput/template`, `srcpkgs/python3-pywhispercpp/template`
(Vulkan build option, `GGML_NATIVE=OFF`), `srcpkgs/vocalinux/template` +
`INSTALL.msg`; `xlint` all three; `./xbps-src pkg` + runtime test.
3. Resolve PyAudio (update template locally to 0.2.14 first).
4. Add `tools/build-xbps.sh` (`podman`-friendly: runs inside
`ghcr.io/void-linux/void-{glibc,musl}`), staging the canonical wheel.
5. Add the tag workflow (`voculinux-v*` or `v*` per §7): build matrix →
sign with `XBPS_PRIVKEY` → publish release with full repodata →
template-checksum bump commit.
6. README section: repo URL, fingerprint, `xbps-install -S` line,
third-party-repo caveat [S6], CUDA/Vulkan note.
7. Smoke on real Void hardware: first-run model download, push-to-talk
(evdev + `input` group), tray icon, Wayland + X11 injection.
8. (Optional, later) Upstream PR campaign per §7; `aarch64` builds.
## Sources
Primary (Void / upstream):
- [S1] void-packages *Manual.md* — template variables, Python packages,
build options, `restricted`, triggers, `archs`:
https://github.com/void-linux/void-packages/blob/master/Manual.md
- [S2] `common/environment/build-style/python3-pep517.sh` +
`common/build-style/python3-pep517.sh` (auto hostmakedepends,
`python3 -m build --no-isolation --wheel`, `python3 -m installer`):
https://raw.githubusercontent.com/void-linux/void-packages/master/common/build-style/python3-pep517.sh
- [S3] void-packages *CONTRIBUTING.md* — package requirements, runtime
testing, `xlint`, `xnew`, orphaned-package adoption:
https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md
- [S5] Void Handbook, *Signing Repositories*:
https://docs.voidlinux.org/xbps/repositories/signing.html
- [S6] Void Handbook, *Custom Repositories*:
https://docs.voidlinux.org/xbps/repositories/custom.html
- [S7] `xbps-rindex(1)` man page (`--sign`, `--sign-pkg`, `--signedby`,
`--privkey`): https://man.voidlinux.org/xbps-rindex.1
- [S8] `srcpkgs/python3/template` (3.14.8):
https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/python3/template
- [S9] `srcpkgs/nvidia/template` (nonfree, x86_64, `libcuda.so.1` only):
https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/nvidia/template
- [S13] `srcpkgs/libayatana-appindicator/template`,
`srcpkgs/python3-gobject/template` (3.56.2, `py3:gi` provides):
https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/python3-gobject/template
- [S14] `srcpkgs/python3-evdev/template` (1.7.1, pep517),
`srcpkgs/python3-PyAudio/template` (0.2.12_5, orphaned):
https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/python3-PyAudio/template
- [S15] `srcpkgs/python3-setuptools/template` (80.10.2):
https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/python3-setuptools/template
- [S16] PyPI JSON, `pynput` 1.8.2 `requires_dist`:
https://pypi.org/pypi/pynput/json
- [S17] PyPI JSON, `pywhispercpp` `requires_dist`:
https://pypi.org/pypi/pywhispercpp/json
Precedent (Bongbetic, first-party):
- [S10] `Bongbetic/taema` `.github/workflows/release.yml` (tag `taema-v*`
== `__version__`, void-glibc/musl containers, sign job, template bump):
https://raw.githubusercontent.com/Bongbetic/taema/main/.github/workflows/release.yml
- [S11] `Bongbetic/taema` `taema/tools/build-xbps.sh` (`xbps-create`
without a void-packages checkout) and `taema/srcpkgs/taema/template`:
https://raw.githubusercontent.com/Bongbetic/taema/main/taema/tools/build-xbps.sh
- [S12] Personal memory `release-xbps.md` (tag-driven rule, key handling,
`releases/latest/download` repository URL; 9-arch list is stale vs [S10]).
Local checkout:
- [L1] [pyproject.toml](/media/Toto/Documents/bongbetic/vocalinux/pyproject.toml:40)
- [L2] [src/vocalinux/version.py](/media/Toto/Documents/bongbetic/vocalinux/src/vocalinux/version.py)
- [L3] [packaging/aur/vocalinux/PKGBUILD](/media/Toto/Documents/bongbetic/vocalinux/packaging/aur/vocalinux/PKGBUILD)
- [L4] [scripts/distro-package-map.yaml](/media/Toto/Documents/bongbetic/vocalinux/scripts/distro-package-map.yaml:177)
- [L5] [install.sh](/media/Toto/Documents/bongbetic/vocalinux/install.sh:829) (CUDA toolkit validation ~L817–950, backend priority ~L1156–1175, `PYWHISPERCPP_VERSION` L287)
- [L6] [docs/DISTRO_COMPATIBILITY.md](/media/Toto/Documents/bongbetic/vocalinux/docs/DISTRO_COMPATIBILITY.md:66), `install.d/system_dependencies.sh` (Void ~L484–503)
- [L7] `packaging/` directory listing (no `void/`/`xbps/` subtree)
- [L8] [packaging/native/nfpm.yaml](/media/Toto/Documents/bongbetic/vocalinux/packaging/native/nfpm.yaml:1)
- [L9] [packaging/nix/pywhispercpp.nix](/media/Toto/Documents/bongbetic/vocalinux/packaging/nix/pywhispercpp.nix)
- [L10] [.github/workflows/release.yml](/media/Toto/Documents/bongbetic/vocalinux/.github/workflows/release.yml:1)
Method note: template presence (§4) was probed by HTTP status of each
`srcpkgs/<pkg>/template` on void-packages `master`, 2026-11-05; a 404 means
no template under that exact name (subpackages of another source package
would not appear). Re-verify with `xbps-query -Rs` before implementation.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Part of #1
Question
How should Voculinux be packaged for Void Linux: template shape, CUDA and Python dependency handling, signed personal repository, and tag-driven releases without hand-uploaded assets?
Void xbps packaging route — research findings
Ticket: Gitea "Void xbps packaging route", part of map "Voculinux port"
(
xavierk/voculinuxongit.bongbetic.com; tracker conventions indocs/agents/issue-tracker.md).
Researched 2026-11-05 against primary sources. Local checkout inspected:
packaging/andscripts/distro-package-map.yaml. No repo files changed.Naming note: this checkout is still Vocalinux everywhere in code
(
pyproject.tomlname = "vocalinux", entry pointsvocalinux/vocalinux-gui,src/vocalinux/version.py__version__ = "0.18.5") [L1][L2]."Voculinux" is the Gitea fork name. Every
pkgnamebelow is written asvocalinux; if the fork renames the binary, rename the template directory tomatch, because a template must live at
srcpkgs/<pkgname>/templatewith thedirectory name equal to
pkgname[S3].Verdict
Dual track, personal repository first. Adopt the established Bongbetic
taema pattern [S10][S11][S12]:
srcpkgs/vocalinux/template(plus two dependency templates, below) in theVoculinux repo for
xbps-srcusers, and build release.xbpspackageswith a
tools/build-xbps.sh-stylexbps-createscript in CI insideghcr.io/void-linux/void-glibc/void-muslcontainers. Sign withxbps-rindex, attach packages + signatures + repodata to the GitHub/Gitearelease, serve the repository from
releases/latest/download. Never buildor upload release assets by hand [S12].
void-packagesPRs. Possible but a biggercommitment: two missing dependency packages must land first, the app must
be runtime-tested, and Void prefers submitters with a contribution
history and software "submitted by someone other than the developer"
(exceptions possible) [S3]. Do not block the personal repo on this.
No CUDA package. Vulkan + CPU only. Void has no CUDA toolkit package, so
a
GGML_CUDAbuild cannot be produced from Void repositories. Shippywhispercpp built with the Vulkan backend (full Vulkan stack exists in
Void), which matches the installer's existing priority
"Vulkan > CUDA fallback > CPU" and its rule that it never installs the CUDA
toolkit [L5].
Three facts drive everything else:
pywhispercpp(default engine) andpynputare not in Void (template probes return HTTP 404, see table in§4). Whichever route is chosen, packaging these two comes first.
python3-PyAudiois0.2.12_5[S14], butpyproject.tomlrequirespyaudio>=0.2.13[L1]. Something must move: update the (orphaned) Voidtemplate, vendor, or relax the floor after checking why it was raised.
optdepends/recommends.dependsis hard. Optionalengines (Vosk, OpenAI Whisper/torch, Parakeet/sherpa-onnx,
faster-whisper — none of which are in Void either) cannot be expressed
like the AUR
optdependslist [L3]; they stay separate packages the userinstalls manually, documented in
INSTALL.msg/README.1. Local state (this checkout)
packaging/containsappimage/,aur/,flatpak/,native/(.deb/.rpm via nfpm),
nix/— no Void/xbps packaging at all [L7].Void support today is only the
install.shpath:docs/DISTRO_COMPATIBILITY.mdmarks Void "Experimental — Untested, manual install required" [L6], and
install.d/system_dependencies.shinstalls the Void inventory withxbps-install -Sy[L6].The Void entry in
scripts/distro-package-map.yaml:177-184is theauthoritative local inventory [L4]:
Two entries look stale against current void-packages (probed 2026-11-05):
libappindicator-gtk3has no template (404) — the current package islibayatana-appindicator[S13] — andydotoolhas no template (404).Verify both with
xbps-query -Rson a real Void system before reusingthis list as
depends.Closest local analogues to copy from:
packaging/aur/vocalinux/PKGBUILD— canonical release sdist assource,
python -m build --wheel --no-isolation,python -m installer,full
dependslist, desktop file + scalable icons [L3].packaging/native/nfpm.yaml— "thin" philosophy: interpreter, GTK,PyGObject and every distro-packaged Python dep from the distro; only
pywhispercppandpynputvendored (under/usr/lib/vocalinux/vendor)because Debian/Fedora don't package them [L8]. Void has the same two
gaps, so the same split applies.
packaging/nix/pywhispercpp.nix— a worked example of buildingpywhispercpp without pip: PyPI sdist (vendors whisper.cpp + pybind11),
GGML_NATIVE=OFF,GGML_VULKAN=ON, shared libs,dontUseCmakeConfigure(CMake is driven by
setup.py build_ext) [L9].Releases are already tag-driven:
.github/workflows/release.ymltriggerson
v*tags, verifies the tag againstsrc/vocalinux/version.py, andbuilds one canonical reproducible wheel/sdist that every publisher
consumes [L10]. An xbps job fits the same shape.
Speech models are not package payload: they are verified against
digests pinned in
src/vocalinux/utils/model_checksums.txtand downloadedat first run (AGENTS.md "Speech models" row). The xbps package should not
bundle models.
2. void-packages template shape
Templates are bash scripts processed by
xbps-src; by convention they startwith
# Template file for '<pkg>', keep lines under ~80 columns, and definethe mandatory variables
pkgname,version,revision,short_desc(≤72 chars),
maintainer("name "),license(SPDX),homepage,plus
distfiles/checksumfor source packages [S1].AGPL(and MIT/BSD/ISC/X11/custom) licenses require the license file inside the binary package —
use
vlicense LICENSEinpost_install()[S1].For Python software the Manual prescribes
python3-moduleif possible, orpython3-pep517for PEP 517 projects withoutsetup.py; the pep517 styleprovides no backend itself, so the backend goes in
hostmakedepends[S1].The pep517 environment automatically adds
python3-buildandpython3-installer-bootstrap, builds withpython3 -m build --no-isolation --wheel, and installs withpython3 -m installer --destdir ${DESTDIR}[S2]. That is exactly the AURPKGBUILD's build [L3]. Dependency variables split for cross-building:
hostmakedepends(run on host),makedepends(target libraries/headers;add
python3-develwhen a compiled extension is built),depends(runtime),
checkdepends[S1]. Setuptools must never fetch missing builddeps at build time — anything it would download belongs in
hostmakedepends[S1]. Void'spython3-setuptoolsis 80.10.2 [S15], whichsatisfies this repo's
setuptools>=77build requirement [L1].Skeleton for the app (illustrative — versions/checksums to fill at build
time; the AUR recipe proves the canonical sdist URL shape [L3]):
Notes on the skeleton:
bytes as PyPI, checksum-verified), not a git-archive tarball [L3].
AGENTS.md applies the same rule to AUR publication. For the personal-repo
track the taema template instead points
distfilesat its own tag archiveplus a vendor tarball [S10]; pick one source of truth and keep
version/checksumbumped by the release workflow (§6).make_check=no(orextended) must carry a comment explaining why — theManual requires the justification [S1]. Upstream Void additionally
requires the package to be runtime tested by installing and running
it, built for at least x86, cross-built e.g.
-a armv6lrecommended, andxlint templateclean [S3].noarchvalue was removed from
archs[S1]; leavearchsunset for the templateroute. For the
xbps-createpersonal-repo route the staged tree containsthe compiled
pywhispercppextension, so packages are arch-specific(
x86_64,x86_64-musl, …) [S11].gtk-icon-cache,update-desktopdb,pycompile) fireautomatically from file placement; no template code needed [S1].
INSTALL.msgis the right place for the notes xbps cannot express asdependencies: add the user to the
inputgroup for the evdev hotkeybackend, optional engines are separate packages, models download on first
run.
Two dependency templates must be written first (same style):
python3-pynput— easy. Pure Python PEP 517 package; upstream 1.8.2needs
six, and on Linuxevdev>=1.3andpython-xlib>=0.17[S16], allalready in Void (
python3-evdev1.7.1,python3-xlib; verifypython3-six). Model it onpython3-evdev's template [S14].python3-pywhispercpp— the real work. Compiled extension:makedepends="python3-devel cmake …"(CMake is invoked by the sdist'ssetup.py build_ext, so do not use thecmakebuild style — the Nixrecipe documents the same trap [L9]). Build env, following [L9] and the
AppImage rules (portable CPU flags; a host-native build SIGILLs on older
CPUs):
CMAKE_ARGS="-DBUILD_SHARED_LIBS=ON -DGGML_NATIVE=OFF -DGGML_VULKAN=ON",PYWHISPERCPP_VERSION=${version}. Runtime deps per PyPI metadata:numpy,requests,tqdm,platformdirs[S17] — all in Void. Pin theversion to
PYWHISPERCPP_VERSION="1.5.0"ininstall.sh[L5] /uv.lock,not the newest PyPI release (1.5.1). Expose the GPU choice as a Void
build option (
build_options="vulkan", default on) soxbps-srcusers can rebuild CPU-only [S1]. Verify with
pythonImportsCheck-equivalents:pywhispercpp,pywhispercpp.model,_pywhispercpp[L9].3. CUDA and GPU handling
srcpkgs/cuda/templateandsrcpkgs/nvidia-cuda-toolkit/templatereturn 404. Thenvidiapackageis
repository="nonfree",archs="x86_64", and ships only the driverruntime
libcuda.so.1— nonvcc, nocuda_runtime.h, nolibcudart[S9].
bin/nvcc,include/cuda_runtime.h,libcudart.so*) before attempting aGGML_CUDA=1build, and states "The installer never installs the CUDAtoolkit" [L5]. A Void binary package therefore cannot be built with CUDA
from Void repositories alone, on either track. A template
build_options="cuda"would be dead weight upstream (Void's builders donot build
restrictedpackages, and there is no toolkit package todepend on [S1]).
Vulkan-Headers,vulkan-loader,Vulkan-Tools,shaderc(providesglslc) andglslangall havetemplates (probed 200). The local Void inventory already lists
shadercand
Vulkan-Tools[L4]. Buildlibggml-vulkan.soas the shared backend;ggml loads backends at runtime, so the same binary still runs CPU-only on
machines without a Vulkan device. This matches installer priority
(Vulkan first, including on NVIDIA) [L5].
torchin Void(404 for
python3-torch/python3-pytorch), and this repo pins CPU torchfor the installer anyway (AGENTS.md). Treat the
whisperextra asunsupported on Void; document, don't block.
rebuild
python3-pywhispercpplocally withxbps-srcandGGML_CUDA=1— an unsupported, documented escape hatch, mirroring howinstall.shtreats a user-provided toolkit [L5].4. Python dependency handling
Void's interpreter is Python 3.14.8 [S8]; the app requires
>=3.11[L1], sono version pinning is needed, but every
pyproject.tomldependency must mapto a Void binary package (
depends), and build-time requirements tohostmakedepends/makedepends[S1]. Presence probed 2026-11-05 viaraw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/<pkg>/templateHTTP status (200 = template exists, 404 = absent):
pyproject.tomlrequirement [L1]PyGObject(distrogi, never pip)python3-gobject3.56.2,provides="py3:gi-…"[S13]numpy>=1.22,<3python3-numpyrequests>=2.28python3-requestsPySocks>=1.7.1,<2python3-pysockspsutil>=5.9,<8python3-psutilevdev>=1.6,<2python3-evdev1.7.1 (python3-pep517) [S14]pyaudio>=0.2.13python3-PyAudio0.2.12_5 (python3-module, orphaned) [S14]pywhispercpp>=1.5,<2(default engine)whisper.cpp/ggml)pynput>=1.7.6,<2python3-evdev,python3-xlibpresent)vad:onnxruntime>=1.16parakeet:sherpa-onnxfaster_whisper(+ctranslate2)voskwhisper:openai-whisper,torchSystem (non-Python) side, from the local Void inventory [L4] cross-checked
the same way: present —
gtk+3,gobject-introspection,portaudio(+
-devel),python3-cairo,ibus,libnotify,libayatana-appindicator,gtk-layer-shell,xdotool,wtype,wl-clipboard,xclip,xsel,shaderc,Vulkan-Tools,cmake,python3-build,python3-installer,python3-setuptools,python3-wheel,python3-tqdm,python3-platformdirs. Absent —libappindicator(-gtk3)(uselibayatana-appindicator),ydotool.Handling rules that follow:
build deps [S1]; the
xbps-createroute stages files built in a Voidcontainer from Void packages + the two source-built deps [S11]. The
--system-site-packagesvenv dance ininstall.sh(AGENTS.md) does notapply to a system package:
gicomes frompython3-gobjectdirectly.Void's orphaned
python3-PyAudioto 0.2.14 — also the contributionhistory Void prefers before new-package submissions [S3]; (b) for the
personal repo, build
python3-PyAudio0.2.14 in-repo; (c) relax the>=0.2.13floor only after finding why it was raised. Do not silentlyset
noverifypydepsto hide it.unusable on
x86_64-musl. The template route gets musl builds for free(Void builds every template per-libc); the
xbps-createroute mustbuild
pywhispercpp/pynput/evdev/PyAudiofrom sdist inside themusl container and must avoid Node-based Actions steps there [S10].
pycompiletrigger for files under
usr/lib/pythonX.Y/site-packages; usepycompile_module="vocalinux"only if the default guesses wrong [S1].install.sh. The installerconsumes hash-pinned
uv.lockexports (AGENTS.md); a Void packagedeliberately floats on Void's package versions within the
pyproject.tomlranges. Accept that, and let the release smoke test(§6) catch drift — e.g. Void's Python moving from 3.14 to 3.15 rebuilds
every
python3-*package's extension paths.5. Signed personal repository
Primary-source mechanics, Void Handbook +
xbps-rindex(1):"Remote repositories must be signed";
xbps-installrefuses unsignedremote packages. Local repositories need no signature [S5][S6].
The signing key is a PEM-encoded RSA key (
ssh-keygen -t rsa -b 4096 -m PEMor
openssl genrsa … 4096) [S5]. One-time repository initialisationembeds the public half:
xbps-rindex --privkey private.pem --sign --signedby "Name <email>" /path/to/repo; each package is then signedindividually:
xbps-rindex --privkey private.pem --sign-pkg repo/*.xbps(
-Sshort form). "Future packages will not be automatically signed" —signing is a per-release CI step, not a one-off [S5][S7].
Users add the repo with one line in
/etc/xbps.d/*.conf:repository=<URL>[S6].Verified working shape (taema): CI builds per-arch
.xbps, then a signjob runs
XBPS_ARCH=<arch> xbps-rindex -a <pkg>.xbps,xbps-rindex --privkey "$key" --signedby "Bongbetic <…>" -s .andxbps-rindex --privkey "$key" -S <pkg>.xbpsper arch, and uploads packages,per-package
.sig2signatures and signed<arch>-repodatato therelease [S10][S12].
Serving trick: point the repository URL at
https://github.com/<owner>/<repo>/releases/latest/download. xbps readsrepodata from the newest release, so every release must carry the full
repodata; no gh-pages branch needed [S12]. User install is then:
Key hygiene (standing Bongbetic practice): RSA 4096 key, mode 600, never
committed, stored as the Actions secret
XBPS_PRIVKEY; publish the keyfingerprint in the README so users can check it at first sync; reuse the
existing taema key so users accept one fingerprint [S12]. (Fingerprint
value itself lives in personal memory
release-xbps.md[S12].)Void's own caveat to repeat in the README: "The Void project does not
support any third-party package repositories" — users opt in knowingly
[S6].
Architecture matrix: start with
x86_64+x86_64-muslonly. That iswhat the current taema workflow actually builds ("Packages are built only
for x86_64 glibc and x86_64 musl", with a per-libc container matrix) [S10][S11].
Personal memory still describes a 9-arch release [S12]; treat that as stale
— adding
aarch64later means cross/QEMU builds of thepywhispercppextension plus a native smoke test, a separate ticket.
6. Tag-driven releases
Combine this repo's existing release discipline [L10] with taema's [S10][S12]:
__version__insrc/vocalinux/version.py(the version source oftruth, AGENTS.md), commit, push a tag. The workflow fails unless the
tag equals the source version (taema uses
<name>-vX.Y.Zand asedcheck against
__version__; this repo'srelease.ymldoes the same forv*) [S10][L10]. For the fork, prefervoculinux-vX.Y.Zso xbpsreleases never collide with upstream-style
v*tags — decide once, inthe map ticket. Pushing commits/tags to GitHub needs the user's explicit
OK each time [S12].
ghcr.io/void-linux/void-glibc/void-musl: installbuild deps with
xbps-install, run tests as a non-root user, runtools/build-xbps.sh(stage →xbps-create -A <arch> -n <pkg>-<ver>_1 -D "<depends>" …→xbps-rindex -a) [S10][S11]. For this app the stagestep installs the canonical wheel with
python -m installer --destdir="$stage", adds the two source-built deps (ordependsonseparately built
python3-pynput/python3-pywhispercpp.xbpsin thesame repo — cleaner, and what the template route needs anyway), the
desktop file, icons and license [L3][L8]. Add an import smoke inside the
container (
vocalinux --version,import pywhispercpp.model), mirroringpackaging/native/smoke-test.sh.all assets (packages,
.sig2, every<arch>-repodata) to the draftrelease, publish [S10]. If
XBPS_PRIVKEYis unset, fail rather thanpublish unsigned (taema degrades to package-only; do not copy that —
an unsigned remote repo is uninstallable [S6]).
versionandchecksuminsrcpkgs/vocalinux/template(and README download names) and commits tomain[S10][S12], soxbps-srcusers always build the released bytes.7. Risks and open questions
vocalinuxvsvoculinuxforpkgname, repo file(
20-vocalinux.conf), tags (v*vsvoculinux-v*), and release host(GitHub
VocaHQ/vocalinuxvs Giteaxavierk/voculinux). Gitea releasesalso expose a "latest download" style URL, but the verified taema pattern
is GitHub [S10][S12]. One decision in the map ticket settles all four.
>=0.2.13vs Void 0.2.12) — find the commit thatraised it before relaxing anything (§4 rule 2).
libappindicator-gtk3andydotoolin[L4] do not resolve as templates today. Without
ydotool, Wayland textinjection falls back to IBus/RemoteDesktop portal/
wtype— say so inINSTALL.msg; packagingydotoolis a possible follow-up ticket.systemd service (e.g. per-user
ydotool.service, autostart units) needsthe XDG-autostart path instead. Confirm during the runtime test [S3].
compiled, or required-by-another), mature release history, non-author
submitter preference [S3]. The app arguably meets "system + compiled
(via pywhispercpp)", but budget it as a multi-PR campaign
(PyAudio update →
python3-pynput→python3-pywhispercpp→ app), eachxlint-clean and runtime-tested [S3].updates should arrive via
xbps-install -Su[S10]. Decide whether thepackage patches out or documents the built-in checker (packaging
convention only — not verified against app code in this research).
8. Implementation checklist (ordered)
xbps-query -Rseach [L4] entry; confirmlibayatana-appindicatortypelib name and
python3-six.srcpkgs/python3-pynput/template,srcpkgs/python3-pywhispercpp/template(Vulkan build option,
GGML_NATIVE=OFF),srcpkgs/vocalinux/template+INSTALL.msg;xlintall three;./xbps-src pkg+ runtime test.tools/build-xbps.sh(podman-friendly: runs insideghcr.io/void-linux/void-{glibc,musl}), staging the canonical wheel.voculinux-v*orv*per §7): build matrix →sign with
XBPS_PRIVKEY→ publish release with full repodata →template-checksum bump commit.
xbps-install -Sline,third-party-repo caveat [S6], CUDA/Vulkan note.
(evdev +
inputgroup), tray icon, Wayland + X11 injection.aarch64builds.Sources
Primary (Void / upstream):
build options,
restricted, triggers,archs:https://github.com/void-linux/void-packages/blob/master/Manual.md
common/environment/build-style/python3-pep517.sh+common/build-style/python3-pep517.sh(auto hostmakedepends,python3 -m build --no-isolation --wheel,python3 -m installer):https://raw.githubusercontent.com/void-linux/void-packages/master/common/build-style/python3-pep517.sh
testing,
xlint,xnew, orphaned-package adoption:https://github.com/void-linux/void-packages/blob/master/CONTRIBUTING.md
https://docs.voidlinux.org/xbps/repositories/signing.html
https://docs.voidlinux.org/xbps/repositories/custom.html
xbps-rindex(1)man page (--sign,--sign-pkg,--signedby,--privkey): https://man.voidlinux.org/xbps-rindex.1srcpkgs/python3/template(3.14.8):https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/python3/template
srcpkgs/nvidia/template(nonfree, x86_64,libcuda.so.1only):https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/nvidia/template
srcpkgs/libayatana-appindicator/template,srcpkgs/python3-gobject/template(3.56.2,py3:giprovides):https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/python3-gobject/template
srcpkgs/python3-evdev/template(1.7.1, pep517),srcpkgs/python3-PyAudio/template(0.2.12_5, orphaned):https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/python3-PyAudio/template
srcpkgs/python3-setuptools/template(80.10.2):https://raw.githubusercontent.com/void-linux/void-packages/master/srcpkgs/python3-setuptools/template
pynput1.8.2requires_dist:https://pypi.org/pypi/pynput/json
pywhispercpprequires_dist:https://pypi.org/pypi/pywhispercpp/json
Precedent (Bongbetic, first-party):
Bongbetic/taema.github/workflows/release.yml(tagtaema-v*==
__version__, void-glibc/musl containers, sign job, template bump):https://raw.githubusercontent.com/Bongbetic/taema/main/.github/workflows/release.yml
Bongbetic/taemataema/tools/build-xbps.sh(xbps-createwithout a void-packages checkout) and
taema/srcpkgs/taema/template:https://raw.githubusercontent.com/Bongbetic/taema/main/taema/tools/build-xbps.sh
release-xbps.md(tag-driven rule, key handling,releases/latest/downloadrepository URL; 9-arch list is stale vs [S10]).Local checkout:
PYWHISPERCPP_VERSIONL287)install.d/system_dependencies.sh(Void ~L484–503)packaging/directory listing (novoid//xbps/subtree)Method note: template presence (§4) was probed by HTTP status of each
srcpkgs/<pkg>/templateon void-packagesmaster, 2026-11-05; a 404 meansno template under that exact name (subpackages of another source package
would not appear). Re-verify with
xbps-query -Rsbefore implementation.