feat(packaging): build thin .deb and .rpm and attach them to releases (#913)

* feat(packaging): build thin .deb and .rpm with nfpm and attach them to releases

Adds packaging/native: an nfpm config that produces a .deb and an .rpm
from the release wheel. The packages stay thin - Python, GTK, PyGObject
and the typelibs are distro Depends; only pywhispercpp and pynput are
vendored because the first is unpackaged on both distros and the second
is missing on Fedora. The Debian 12 build image and the nfpm download
are pinned in tool_checksums.txt.

just native-packages builds both in the pinned container, and
native-smoke installs the built package per distro: debian:12 and
ubuntu:24.04 exercise the .deb, fedora:42 the .rpm, then each runs
vocalinux --version plus an import smoke of the app and the vendored
extension. unified-pipeline.yml gates on the same three-distro matrix,
and release.yml attaches both arches to the release and covers them in
SHA256SUMS and the provenance manifest.

Refs #600

* fix(packaging): preserve launcher args and document Wayland input group

The launcher joined $* into an unquoted exec, re-splitting file paths
that contain spaces before argparse saw them. Exec "$@" directly and
re-quote each argument for the inner shell of 'sg input -c'.

On Wayland the packages could not grant the 'input' group membership
evdev hotkeys need; name the usermod step and its caveat in INSTALL.md
and the release notes.

* test(packaging): add return types to the native packaging tests

---------

Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
This commit is contained in:
devin-ai-integration[bot]
2026-10-07 17:10:00 -07:00
committed by GitHub
co-authored by Jatin K Malik
parent d53300841d
commit 4b7d57c077
14 changed files with 1021 additions and 13 deletions
+124 -5
View File
@@ -257,6 +257,26 @@ jobs:
```
Still requires `xdotool`/`wtype`/`ydotool` on the host for text injection, same as the PyPI path.
### Distro packages (.deb / .rpm)
Download the package matching your distro and CPU (`amd64`/`x86_64`, or `arm64`/`aarch64`
attached shortly after this release is published):
```bash
# Debian / Ubuntu
sudo apt install ./vocalinux_*_amd64.deb
# Fedora
sudo dnf install ./vocalinux-*.x86_64.rpm
```
Thin packages: Python, GTK, PyGObject and the AppIndicator typelib come from the
distro, and `apt`/`dnf` resolves them. Only `pywhispercpp` and `pynput` are
vendored (the first is packaged by neither distro, the second is missing on
Fedora). whisper.cpp runs on CPU in these packages; the Vulkan build is the
AppImage. A PPA and a COPR are not published yet.
On Wayland the default Right Alt hotkey uses evdev: `sudo usermod -aG input $USER`,
then log out and back in (the `input` group can read every keyboard and pointer
device; X11 needs nothing).
${{ steps.flatpak_notes.outputs.flatpak }}
### Snap
@@ -353,6 +373,104 @@ jobs:
--repo ${{ github.repository }} \
--clobber
# The .deb and .rpm are thin: they depend on the distro's Python, GTK,
# PyGObject and typelibs and vendor only pywhispercpp and pynput (#600).
# nfpm builds both from one config inside the pinned Debian 12 image, and
# the same wheel PyPI ships is the payload.
build-native:
name: Build native packages (x86_64)
needs: build-and-release
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Extract version from tag
id: get_version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
# Same reason the aarch64 AppImage downloads the artifact: one wheel,
# everywhere, or the checksums cannot describe what the packages hold.
- name: Download the built distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: python-dist
path: dist
- name: Build .deb and .rpm
run: |
bash packaging/native/docker-build.sh dist/*.whl "${{ steps.get_version.outputs.VERSION }}" dist
ls -la dist/*.deb dist/*.rpm
- name: Upload the x86_64 packages
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: native-x86_64
path: |
dist/*.deb
dist/*.rpm
if-no-files-found: error
retention-days: 7
- name: Attach to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ github.ref_name }}" \
dist/*.deb dist/*.rpm \
--repo ${{ github.repository }} \
--clobber
build-native-arm64:
name: Build native packages (aarch64)
needs: build-and-release
runs-on: ubuntu-24.04-arm
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Extract version from tag
id: get_version
run: |
VERSION=${GITHUB_REF#refs/tags/v}
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
- name: Download the built distributions
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: python-dist
path: dist
- name: Build .deb and .rpm (aarch64)
run: |
bash packaging/native/docker-build.sh dist/*.whl "${{ steps.get_version.outputs.VERSION }}" dist
ls -la dist/*.deb dist/*.rpm
- name: Upload the aarch64 packages
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: native-aarch64
path: |
dist/*.deb
dist/*.rpm
if-no-files-found: error
retention-days: 7
- name: Attach to release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
gh release upload "${{ github.ref_name }}" \
dist/*.deb dist/*.rpm \
--repo ${{ github.repository }} \
--clobber
# Pins match .github/workflows/flatpak.yml. These jobs start with the tag
# (no needs) so they overlap the wheel / x86_64 AppImage job. The builder
# image is Freedesktop SDK, not a GitHub-hosted runner: no `gh`, so attach
@@ -656,12 +774,13 @@ jobs:
--repo ${{ github.repository }} \
--clobber
# Runs last because the aarch64 AppImage, both Flatpak bundles, and the snap
# land after the release is created: a checksum manifest that covers some of
# the files is worse than none, since the missing one looks like the tampered one.
# Runs last because the aarch64 AppImage, both Flatpak bundles, the native
# packages, and the snap land after the release is created: a checksum
# manifest that covers some of the files is worse than none, since the
# missing one looks like the tampered one.
publish-checksums:
name: Checksums and Provenance
needs: [build-and-release, build-appimage-arm64, build-flatpak-amd64, build-flatpak-arm64, attach-flatpak, attach-snap]
needs: [build-and-release, build-appimage-arm64, build-native, build-native-arm64, build-flatpak-amd64, build-flatpak-arm64, attach-flatpak, attach-snap]
runs-on: ubuntu-latest
permissions:
contents: write
@@ -681,7 +800,7 @@ jobs:
ls -la
# Bare names, so `sha256sum -c` works in whatever directory the user
# downloaded the release into.
sha256sum -- *.whl *.tar.gz *.AppImage *.flatpak *.snap | tee SHA256SUMS
sha256sum -- *.whl *.tar.gz *.AppImage *.deb *.rpm *.flatpak *.snap | tee SHA256SUMS
# Attesting from the manifest rather than a second glob: the file we
# publish and the set we attest cannot drift apart.
+78
View File
@@ -24,6 +24,7 @@ jobs:
web: ${{ steps.filter.outputs.web }}
python: ${{ steps.filter.outputs.python }}
aur: ${{ steps.filter.outputs.aur }}
native: ${{ steps.filter.outputs.native }}
deploy_website: ${{ steps.deploy_check.outputs.deploy }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
@@ -66,6 +67,13 @@ jobs:
aur:
- 'packaging/aur/**'
- '.github/workflows/unified-pipeline.yml'
# Same carve-out for the .deb/.rpm gate: nfpm.yaml mirrors
# pyproject.toml's dependencies against distro package names, so a
# native-only change runs its own build and smoke without the
# python matrix.
native:
- 'packaging/native/**'
- '.github/workflows/unified-pipeline.yml'
- name: Check if website should deploy
id: deploy_check
env:
@@ -360,6 +368,76 @@ jobs:
archlinux:latest \
bash /repo/packaging/aur/build-test.sh
# The .deb and .rpm exist to be installed and run, so the gate does exactly
# that: build both in the pinned Debian 12 image release.yml uses, then
# install them in distro containers and run vocalinux --version plus an
# import smoke. debian:12 and ubuntu:24.04 exercise the .deb's dependency
# list (and the vendored cp311/cp312 wheels), fedora:42 the .rpm's.
native-build:
name: 📦 Build native packages (x86_64)
runs-on: ubuntu-latest
needs: changes
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.native == 'true'
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Set up Python
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.11'
cache: 'pip'
- name: Build wheel
run: |
python -m pip install --upgrade pip build
python -m build
ls -la dist/
- name: Build .deb and .rpm
run: |
VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' src/vocalinux/version.py)
WHEEL=$(ls dist/*.whl)
bash packaging/native/docker-build.sh "$WHEEL" "$VERSION" dist
ls -la dist/*.deb dist/*.rpm
- name: Upload the x86_64 packages
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: native-x86_64
path: |
dist/*.deb
dist/*.rpm
retention-days: 7
native-smoke:
name: 🐧 Install native package (${{ matrix.distro }})
runs-on: ubuntu-latest
needs: [changes, native-build]
if: needs.changes.outputs.python == 'true' || needs.changes.outputs.native == 'true'
strategy:
fail-fast: false
matrix:
distro:
- debian:12
- ubuntu:24.04
- fedora:42
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: Download the packages built above
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: native-x86_64
path: dist
- name: Install and smoke on ${{ matrix.distro }}
run: |
docker run --rm \
-v "$PWD/dist:/out:ro" \
-v "$PWD/packaging/native:/pk:ro" \
"${{ matrix.distro }}" \
bash /pk/smoke-test.sh
web-build:
name: 🌐 Build Website
runs-on: ubuntu-latest