Files
voculinux/docs/RELEASE_PROCESS.md
xavierk d1a80e6ead
CI / security (push) Successful in 1m21s
CI / lint (push) Successful in 22s
CI / ai-review (push) Skipped
chore(release): prepare v0.19.1 (#55)
2026-11-06 21:18:15 +00:00

34 KiB
Raw Permalink Blame History

Voculinux Release Process

This document outlines the step-by-step process for creating a new release of Voculinux.

Overview

Voculinux uses Semantic Versioning with pre-release suffixes:

  • Alpha: Early testing phase (e.g., 0.4.1-alpha)
  • Beta: Feature-complete, testing phase (e.g., 0.5.0-beta)
  • RC (Release Candidate): Final testing before stable (e.g., 0.5.0-rc1)
  • Stable: Production-ready (e.g., 1.0.0)

Release Infrastructure (one-time setup)

These are configured once per repository. A tag pushed before they exist will fail in the corresponding job.

PyPI trusted publishing (required)

publish-pypi authenticates with a short-lived OIDC token instead of a stored API token, so there is no PYPI_API_TOKEN secret to leak or rotate. It only works once PyPI knows about this workflow. On https://pypi.org/manage/project/voculinux/settings/publishing/ add a GitHub publisher:

field value
Owner VocaHQ
Repository voculinux
Workflow name release.yml
Environment pypi

Until that publisher exists, publish-pypi fails with invalid-publisher. The old PYPI_API_TOKEN secret can be deleted from the repository once the first trusted publish succeeds.

AUR (optional)

publish-aur skips itself when AUR_SSH_PRIVATE_KEY is unset. See docs/AUR.md.

Void xbps repository (required for a Void release)

The signed Void repository is built and published from the maintainer's own x86_64 Void host, not from CI. The whole build is one script, packaging/void/build-release.sh, and the publish is one more, packaging/void/publish-release.sh. The Gitea workflow .gitea/workflows/xbps-release.yml is manual dispatch only: a tag push does not start it, because its only runner (label bongbetic-ci) is ARM64 and cannot build the glibc x86_64 release. Set this up once on the release host:

what value
Signing key the PEM-encoded RSA 4096 repository private key, kept in the maintainer's own backup. It is passed to build-release.sh as a file on tmpfs (/dev/shm) and removed after the run; it is never committed
Public key packaging/void/repository-key.plist, committed, fingerprint fa:d1:00:32:ce:b1:b9:fd:73:05:05:76:75:54:37:25
Host x86_64 Void with rootless podman (user namespaces), python3.11, uv 0.12/0.13 and tmpfs /dev/shm. Requirements: packaging/void/README.md, "Runner requirements". Never Docker
Gitea login a tea login for https://git.bongbetic.com whose token may create releases and upload assets, or GITEA_TOKEN in the environment for publish-release.sh

Without a valid key the build stops before any container starts. It never signs with a different key: the release path refuses any key whose public half is not repository-key.plist. Only the public .plist is committed. Key generation and the check that a private key matches the committed public key are in packaging/void/README.md.

The carried Void templates in packaging/void/srcpkgs/ track the project's own pins. python3-PyAudio and python3-pysocks replace stock Void packages below the floors in pyproject.toml, and voculinux depends on each with a >= constraint. When a floor or a carried version changes, change the template and its constraint in the same pull request: the offline tests in tests/test_void_small_deps.py and tests/test_void_app_template.py fail until they agree. The reasoning is in packaging/void/README.md, "Version floors".

Quick Release Checklist

Use this checklist for every release:

## Version Bump Checklist for X.Y.Z-PHASE

### Core Version Files
- [ ] `src/vocalinux/version.py` - Update `__version__` and `__version_info__`
- [ ] `pyproject.toml` - Confirm `Development Status` classifier and `requires-python` are correct for this release phase

### Documentation
- [ ] `README.md` - Update short current-release blurb (not full notes)
- [ ] `CHANGELOG.md` - Point "Current stable" at the new tag
- [ ] `docs/INSTALL.md` - Verify install examples use `main/install.sh` (not version-pinned raw URLs)
- [ ] `docs/UPDATE.md` - Add "What's New" section for new version
- [ ] `SECURITY.md` - Update supported versions table
- [ ] `packaging/void/srcpkgs/voculinux/template` - set `version` (the xbps spelling, the hyphen removed) and `_tag` to this release, in the same release PR as `version.py`. The tag gate refuses a tag whose template names another release. The checksum is recorded after the release (section 3.5)

### Website
- [ ] `web/src/app/page.tsx` - Update `softwareVersion` in schema
- [ ] `web/src/app/page.tsx` - Update version badge / live version surfaces if present
- [ ] `web/src/app/changelog/page.tsx` - Add new release entry to changelog
- [ ] `web/package.json` - Update version field
- [ ] `web/package-lock.json` - Keep top-level/version metadata aligned with `web/package.json`
- [ ] `web/PRODUCT.md` - Keep capabilities aligned with shipped features
- [ ] Confirm install docs/website mention **AppImage** when this release ships AppImages

### Testing & Verification
Install `just` from https://just.systems or the distro package `just`.
- [ ] Run `just test` - All tests pass
- [ ] Run `just lint` - No linting errors
- [ ] Run `just typecheck` - No type errors
- [ ] Run `just build` - Package builds successfully
- [ ] Run `PYTHON=python3.11 just reproducible-wheel` - Independent wheels match
- [ ] Run `npm run build` in `web/` - Website builds successfully

> Note: CI workflows also sync website version metadata from `src/vocalinux/version.py`, but source files should still be updated in-repo during release prep for review clarity.

### Git Operations
- [ ] Create release branch: `git checkout -b release/vX.Y.Z-PHASE`
- [ ] Commit all version changes
- [ ] Push branch and create PR
- [ ] After PR merge, create tag: `git tag -a vX.Y.Z-PHASE -m "Release X.Y.Z-PHASE"`
- [ ] Create and push the annotated tag on the verified `origin/main` commit: `git push origin refs/tags/vX.Y.Z-PHASE`, never `--tags`. Pushing a tag starts no workflow; the Void release is built and published by hand (Step 8)

### After the release run finishes
- [ ] Run `just verify-release` - the published release verifies as published
- [ ] Re-run it after any hand edit to the release: attaching an asset, or rewriting the notes

verify-release.yml runs the same check when the release workflow finishes. It cannot see a hand edit made afterwards, which is how every v0.16.2 defect arrived, so run it yourself after touching a published release. The one exception is snap-backfill.yml: that workflow attaches the snap, rewrites SHA256SUMS, and then runs scripts/verify_release.py itself.

Pre-publication wheel reproducibility

PR CI and the release workflow run scripts/build_reproducible_wheel.py before publication. It checks lock/export drift, installs the hash-pinned release-build toolchain into two fresh virtualenvs without system site-packages, and independently builds an sdist and its wheel in each source tree with --no-isolation. SOURCE_DATE_EPOCH comes from the checked-out commit. Both builds use the same Python 3.11 interpreter and platform; this checks repeatability under controlled inputs, not cross-platform or cross-Python-version reproducibility. Only wheels are compared byte-for-byte.

Build-only environments deliberately exclude host packages and setuptools plugins. Unlike runtime/development virtualenvs, they do not need distro PyGObject: the backend reads version metadata without importing GTK.

The script runs twine check --strict with the locked tools and stages only the canonical wheel and sdist in dist/ after comparison succeeds. All release consumers reuse those distributions. Snap Store publication also waits for this gate. Verification builds never enter the release checksum manifest.

Locally, PYTHON=python3.11 just reproducible-wheel includes tracked working-tree edits and non-ignored new files, but excludes ignored build/egg-info/venv state. Repeat runs automatically clean the gate's previous logs and build archives. Staged files in dist/ are removed only if their names and digests match the retained canonical archives. Unrelated or modified files, unexpected diagnostic files, and symlinks cause cleanup to stop before deleting anything; move those files aside first. The rest of build/ is never cleaned. Logs, hashes and both builds are retained in build/reproducible-wheel/; CI uploads that directory on failure. Refresh the toolchain through just lock, not by editing its export by hand. Cleanup removes staged copies before their canonical originals, so an interrupted cleanup can safely be retried. The gate holds a checkout lock from before cleanup through final staging; a second active run fails without changing the first run's outputs. Archives are copied to an ignored temporary sibling of dist/ and then atomically renamed into place, so interrupted staging leaves only complete archives in dist/ and can be retried.

AUR source publication gate

After publish-checksums, aur-source-test downloads the published canonical sdist and SHA256SUMS. scripts/prepare_aur_source.py checks the digest and cryptographically verifies provenance against this repository's release workflow, tag ref and source commit before preparing the checksum-pinned PKGBUILD. The exact prepared recipe is built, checked with namcap, installed and smoked on current Arch. publish-aur downloads only that verified PKGBUILD artifact and pushes it without further version/checksum rewrites (updpkgsums: false).

This gate runs even without AUR publication credentials. Failure prevents the AUR push, but does not unpublish GitHub/PyPI artifacts already released. It is separate from PR/local --local-unattested fixtures, which exercise the same preparation/build path but are never used for publication. See AUR.md for the trust model and the existing AUR-provider smoke-test limitations. voculinux-bin publication waits only for release creation and both AppImages; source-gate failures or unrelated checksum producers do not block it.

Detailed Release Steps

Step 1: Determine Version Number

Follow semantic versioning:

Given version MAJOR.MINOR.PATCH-PHASE:

MAJOR - Breaking changes (for 1.0.0: public API stable)
MINOR - New features, backwards compatible
PATCH - Bug fixes, backwards compatible
PHASE - alpha, beta, rc1, rc2, etc. (omit for stable releases)

Examples:

  • Alpha → Beta: 0.4.1-alpha → 0.5.0-beta (new features, phase change)
  • Beta → RC: 0.5.0-beta → 0.5.0-rc1 (same features, testing phase)
  • RC → Stable: 0.5.0-rc1 → 1.0.0 (first stable release)
  • Stable patch: 1.0.0 → 1.0.1 (bug fixes only)
  • Stable minor: 1.0.0 → 1.1.0 (new features)

Step 2: Update Core Version Files

2.1 Update src/vocalinux/version.py

__version__ = "0.5.0-beta"
__version_info__ = (0, 5, 0, "beta")

2.2 Update pyproject.toml

Change the Development Status classifier:

# For Alpha:
"Development Status :: 3 - Alpha",

# For Beta:
"Development Status :: 4 - Beta",

# For Stable:
"Development Status :: 5 - Production/Stable",

Patch releases within a minor line (e.g. 0.14.x)

When shipping bug fixes only (PATCH bump, same MINOR):

  • README / docs/UPDATE.md "What's New" keeps the minor-series feature list (everything introduced in that 0.Y line) and adds a short Bug fixes in this patch subsection for the delta only. Do not make the marketing highlight table look featureless because this tag only contains fixes.
  • Website changelog (web/src/app/changelog/page.tsx) may list only the delta for the new entry; older entries stay historical.
  • AppStream (packaging/flatpak/com.voculinux.Voculinux.metainfo.xml): add a short <release> note for the patch delta only; leave the long product feature <ul> as series/product-level.
  • SECURITY.md supported versions table usually stays on the minor line (0.14.x) without a row per patch.
  • AUR source PKGBUILD: the release gate prepares pkgver, _tag, the normalized _version, and the canonical sdist digest, then tests that exact file. Do not introduce SKIP or rehash it during publication. The separate voculinux-bin path still pins both AppImage digests before its own publication.
  • Dates: patch release dates must not predate the previous release in changelog / AppStream / version history.

GitHub Release Notes Writing Rules

Use these rules for every GitHub Release body (and for the draft pasted into the release-prep PR). Website changelog entries stay shorter; see below.

Sources of truth

  • Delta commits: git log vPREV..HEAD plus merged PR titles/bodies.
  • Closed issues via PR Fixes / Closes references only - do not invent issue numbers.
  • Do not invent benchmarks, user counts, testimonials, or features not in the tree.

Required structure

  1. # Voculinux vX.Y.Z title
  2. One to three plain sentences: what this release is for (no hype)
  3. ## Highlights - markdown table, about 4-8 rows
  4. ## New Features - bullets with PR + author; include issue closes when real
  5. ## Bug Fixes - group by area (IBus, Installer, AUR, Text injection, …)
  6. Optional: ## Improvements, ## Docs, ## Packaging
  7. ## Thanks - external PR authors and issue reporters by @handle
  8. ## Install / Upgrade - install.sh, AUR, PyPI, AppImage, Flatpak, Snap, .deb / .rpm (Store channels plus GitHub .snap sideload when Store review is pending)
  9. ### Verifying what you downloaded (required, and easy to lose). release.yml generates it, with sha256sum -c --ignore-missing SHA256SUMS and gh attestation verify. A hand-written body replaces the generated one, so carry this block over deliberately. v0.16.2 published a checksum manifest and build provenance and mentioned neither, so nothing a user reads told them either existed
  10. Footer compare link: https://github.com/VocaHQ/vocalinux/compare/vPREV...vX.Y.Z

Include / exclude

  • Include: user-visible features, install/packaging changes, desktop reliability fixes, docs that change user instructions.
  • Exclude or demote: Dependabot-only bumps, CI matrix tweaks, agent-env docs, pure refactors - short “CI / maintenance” subsection at most.

Attribution and voice

  • Feature/fix bullets: (#N by @author; fixes #M reported by @reporter) when known.
  • Practical, specific, Linux-native. Name the symptom users hit.
  • Avoid promotional filler (“seamless”, “pivotal”, “game-changer”, fake significance).

Website changelog vs GitHub Release

  • Website (web/src/app/changelog/page.tsx): 3-10 concise user-facing bullets for the new entry.
  • GitHub Release: fuller narrative + install block + thanks. Draft in the release-prep PR body; paste/edit onto the release after the tag workflow runs (workflow install stub + generated notes are a starting point only).

Minor vs patch (reminder)

  • Minor (e.g. 0.15.0): rewrite README / UPDATE “What’s New” for the new series; full website changelog entry; AppStream release note summarizes the series.
  • Patch (e.g. 0.15.1): keep series feature table; add “Bug fixes in this patch” only; website changelog lists delta only.

Draft delivery

  • Release-prep PRs should include a ## Draft GitHub Release Notes section in the PR description for review before tag.
  • Do not tag from the prep branch alone; tag after merge to main.

Step 3: Update Documentation

3.1 Update README.md

README current-release blurb: Keep a short one-line pointer to the new tag and docs/UPDATE.md. Do not paste the full release notes into the README.

Install commands: Keep install examples on main/install.sh (download-then-run preferred; installer resolves latest release tag):

curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/main/install.sh -o /tmp/vl.sh
bash /tmp/vl.sh

3.2 Update docs/INSTALL.md

Confirm install examples point to main/install.sh and not release-tag raw URLs.

3.3 Update docs/UPDATE.md

Add a new "What's New" section at the top:

## What's New in v0.5.0-beta

- Feature 1
- Feature 2
- Bug fix 1

See the [full changelog](https://github.com/VocaHQ/vocalinux/releases/tag/v0.5.0-beta).

---

## Previous Versions

### v0.4.1-alpha
...

3.4 Update SECURITY.md

Update the supported versions table:

| Version | Supported          |
| ------- | ------------------ |
| 0.5.x   | :white_check_mark: |
| 0.4.x   | :x:                |
| < 0.4   | :x:                |

3.5 Void template: version in the release PR, checksum after the tag (pull request)

The Void template packaging/void/srcpkgs/voculinux/template names one release. It has two parts, handled at two times, and both go through a normal pull request:

  • Version and tag: in the release pull request, with version.py. Set version to the xbps spelling of __version__ (the hyphen removed: 0.19.0-beta becomes 0.19.0beta) and _tag to the tag (v0.19.0-beta). build-release.sh check-tag refuses any tag whose template names another release, so a stale template stops the tag, before any build.
  • Checksum: after the tag, in a pull request. The release build replaces checksum= in its build tree only, with the sha256 of the sdist it builds, and prints checksum=<sha256>, and the published release notes carry it as sdist sha256:. Copy that value into the template in a pull request through the normal test, lint and type gates. Never commit it directly to main, and never hand-edit a release asset.

The committed template carries a 64-zero placeholder for checksum= until that first pull request lands. The placeholder is never a real digest and is never published: every release build replaces it. tests/test_void_app_template.py fails if the template's version or _tag drifts from src/vocalinux/version.py.

The release build never commits or pushes. Its signed repository is built from the templates as committed at the tag, so a checksum pull request merged after tag vX.Y.Z is recorded for the next release, and the next tag builds with it (the build tree always takes the digest of the sdist it builds).

Step 4: Update Website

4.1 Update web/src/app/page.tsx

Update the schema.org softwareVersion (around line 68):

"softwareVersion": "vX.Y.Z-PHASE",

Update the version badge in the header (around line 293):

vX.Y.Z Beta

4.2 Update web/src/app/changelog/page.tsx

Add new release entry to the releases array at the top of the file.

4.3 Update web/package.json

{
  "name": "voculinux-website",
  "version": "0.5.0"
}

Also align web/package-lock.json top-level version fields with web/package.json.

Step 5: Verify Everything Works

Run the full verification suite. Install just from https://just.systems or the distro package just.

# Run tests
just test

# Run linting
just lint

# Run type checking
just typecheck

# Build package
just build

# Website build
cd web && npm run build && cd ..

# Install script sanity checks (optional but recommended)
bash -n install.sh
./install.sh --help | grep -- --tag

Upstream Fix Intake (Cherry-Pick Policy)

Upstream Vocalinux fixes enter this fork only as hand cherry-picks: taken at defined sync points, never as continuous tracking and never as wholesale merges. Like every other change, each port goes through the pull request flow with the standard test, lint, and type gates. Never land one as a direct or bot commit to main, and follow the worktree rules in AGENTS.md (one worktree per branch, every change via a pull request, never commit on main — only the pull request base changes).

  • When intake happens. At each release-preparation cycle, while working through the steps above on the release-prep branch, plus on demand for critical fixes: crashes, data loss, and security issues. Critical fixes follow the Emergency Hotfix Process below.
  • When intake freezes. Intake freezes once the release branch opens (Step 6). After the freeze, only justified release-blockers are admitted; everything else waits for the next cycle.
  • One upstream fix per pull request. Each port is its own pull request, referencing both the upstream fix and the fork issue/PR numbers.
  • How conflicts resolve. Keep the upstream logic and re-apply the fork's renames on top. Conflicts concentrate at the renamed user-visible surfaces (application name, entry points, desktop and D-Bus identity, configuration and data directories, package, website) precisely because the internal Python module name stays vocalinux (src/vocalinux/) as a retained internal name, so only the user-visible surfaces diverge from upstream, and that split is what keeps hand cherry-picks nearly conflict-free everywhere else. Upstream URLs and changelog history stay untouched for provenance.
  • What qualifies. Fixes and documentation that changes user instructions port. Features are re-decided per release, not ported automatically. Dependency and CI churn ports only when a security advisory names it. Out-of-scope material is dropped at triage.

Step 6: Create Release Branch and PR

# Create branch
git checkout -b release/v0.5.0-beta

# Stage all changes
git add -A

# Commit with conventional commit format
git commit -m "chore(release): prepare v0.5.0-beta

- Update version from 0.4.1-alpha to 0.5.0-beta
- Update status badges from Alpha to Beta
- Update documentation version references
- Update website schema and badges
- Update pyproject.toml classifiers
- Update SECURITY.md supported versions table"

# Push branch
git push origin release/v0.5.0-beta

# Create PR (use GitHub CLI or web interface)
gh pr create --title "chore(release): prepare v0.5.0-beta" \
  --body "This PR prepares the repository for the v0.5.0-beta release.

## Changes
- Bumped version to 0.5.0-beta
- Updated status badges from Alpha to Beta
- Updated all documentation version references
- Updated website version info
- Updated SECURITY.md supported versions

## Checklist
- [x] All tests passing
- [x] Linting clean
- [x] Type checking clean
- [x] Package builds successfully"

Step 7: Create and Push Tag (After PR Merge)

Once the PR is merged to main:

# Checkout main
git checkout main
git pull origin main

# Create annotated tag
git tag -a v0.5.0-beta -m "Voculinux Beta Release 0.5.0

Key changes since 0.4.1-alpha:
- Feature 1
- Feature 2
- Bug fix 1

Full changelog: https://github.com/VocaHQ/vocalinux/releases"

# Push the one tag by its ref, never with --tags
git push origin refs/tags/v0.5.0-beta

Void release (this fork): tag the verified origin/main commit by its full SHA, then push that one ref: git tag -a v0.19.0 <sha> -m "Voculinux 0.19.0" and git push origin refs/tags/v0.19.0. Never git push --tags: this clone holds upstream tags that must not reach the fork's Gitea host. Then follow the Void section of Step 8.

Step 8: Monitor Release

After pushing the tag, the GitHub Actions workflow will automatically:

  1. Build the Python package (wheel and sdist) - once, with SOURCE_DATE_EPOCH pinned to the tagged commit. Every later job downloads that artifact instead of rebuilding, so the wheel on PyPI is byte-for-byte the wheel on the release
  2. Build and attach AppImages for x86_64 and aarch64, both from that same wheel
  3. Create a GitHub Release with auto-generated notes
  4. Attach SHA256SUMS covering every GitHub asset (wheel, sdist, both AppImages, both Flatpaks, the amd64 snap, the .deb and .rpm for both arches) and generate build provenance from that manifest (runs after the aarch64 AppImage, both Flatpaks, and the snap land, so a partial manifest never gets published)
  5. Publish to PyPI via trusted publishing
  6. Publish the AUR package (when the AUR_SSH_PRIVATE_KEY secret is configured)
  7. Publish the signed self-hosted Flatpak remote to VocaHQ/vocalinux-flatpak (stable tags only, once the GitHub Release exists, when FLATPAK_GPG_PRIVATE_KEY + FLATPAK_REPO_TOKEN are configured — see docs/FLATPAK_REMOTE.md)
  8. Deploy the website to voculinux.com
  9. Build the amd64 snap from locked Python exports and run the installed-artifact gate described in SNAP.md, then attach it to the GitHub Release and try Snap Store edge/candidate. Store human review (for example a super-privileged allow-installation plug) must not block the GitHub .snap. stable does not auto-release (#783): after candidate QA, dispatch snap-promote.yml, which releases the candidate revision to latest/stable
  10. Mark as pre-release if version contains alpha/beta/rc

Monitor at: https://github.com/VocaHQ/vocalinux/actions

Void xbps repository (local release)

Pushing the tag starts nothing. The Void release is run by hand on the release host, from a clean checkout of the tag, in this order. Replace vX.Y.Z with the release.

  1. Merge the release pull request to main, then confirm which commit origin/main is: git fetch origin && git rev-parse origin/main.
  2. Create and push the annotated tag on that commit (Step 7).
  3. Check out the tag cleanly: git worktree add /tmp/voculinux-vX.Y.Z vX.Y.Z. A worktree at a tag creates no tag. Work only in that worktree.
  4. Build: from that checkout, run packaging/void/build-release.sh release vX.Y.Z --key-file /dev/shm/xbps-sign-key.pem --out-dir /path/to/out. The out-dir must be absent or empty. The run refuses an uncommitted checkout and a key that is not the committed one, both before any container starts. It writes sdist/, xbps/, SDIST_SHA256 and BUILT_FROM (the tag and the commit it was built from), and prints checksum=<sha256>. Remove the key file afterwards.
  5. Dry-run the publish: bash packaging/void/publish-release.sh vX.Y.Z --out-dir /path/to/out --dry-run. It runs every check (the tag is on origin at the built commit, the digest, the key) and prints the API calls it would make. It sends no request and reads no token.
  6. Publish: bash packaging/void/publish-release.sh vX.Y.Z --out-dir /path/to/out. It attaches the sdist, packages, signatures, FINGERPRINT and the client .plist to the tag's release, then replaces the assets of the rolling latest release that the client registration URL serves. The repodata is uploaded last. The token comes from GITEA_TOKEN, or from the tea login for https://git.bongbetic.com.
  7. Record the checksum in a post-release pull request (section 3.5).

Neither script commits or pushes to any branch or tag, and nothing is built or uploaded by hand. Publish tags in version order: publishing an older tag refreshes latest with the older repository.

Step 9: Post-Release Tasks

  • Verify GitHub Release was created correctly
  • Verify SHA256SUMS is attached and lists every GitHub asset (wheel, sdist, both AppImages, both Flatpaks, the amd64 snap, the .deb and .rpm for both arches) - the release notes tell users to run sha256sum -c against it
  • If the Store holds the snap for human review, confirm the GitHub .snap is still attached and the notes document snap install --dangerous
  • After candidate QA (install, tray, mic, model download, typing into a real app), promote the snap to stable: gh workflow run snap-promote.yml -f tag=vX.Y.Z - it fails unless latest/candidate carries a revision whose version matches the tag. An optional snap-stable environment with required reviewers adds a second approval gate on the dispatch
  • Verify provenance: gh attestation verify <artifact> --repo VocaHQ/vocalinux
  • Verify PyPI package was published (if applicable), and that its wheel sha256 matches the line for that wheel in SHA256SUMS
  • Verify website was deployed (check voculinux.com)
  • Verify the Void publish succeeded, and that both the tag release and latest carry the full signed set (sdist/voculinux-*.tar.gz, *.x86_64.xbps, *.sig2, x86_64-repodata, FINGERPRINT, the .plist), that the fingerprint matches the one in README.md, and that the sdist sha256 in the release notes matches the checksum= line the run printed
  • Open the Void checksum pull request (section 3.5) with that checksum= value; never commit it directly to main
  • If the Flatpak remote is configured, verify it picked up the tag: flatpak remote-info vocahq com.voculinux.Voculinux (docs/FLATPAK_REMOTE.md)
  • Announce on social media/communities
  • Update any pinned issues or discussions

Emergency Hotfix Process

For critical bugs in a released version:

# Create hotfix branch from the tag
git checkout -b hotfix/v0.5.1-beta v0.5.0-beta

# Make fix, commit, push
git add -A
git commit -m "fix: critical bug description"
git push origin hotfix/v0.5.1-beta

# Create PR targeting main
# After merge, tag new version
git tag -a v0.5.1-beta -m "Hotfix release v0.5.1-beta"
git push origin v0.5.1-beta

Version History

Version Date Phase Notes
0.19.1 2026-11-07 Stable Patch: a leftover upstream Vocalinux launcher is reported at startup (#52)
0.19.0 2026-11-07 Stable First Voculinux release: a hard fork for Void Linux (glibc, x86_64) with a signed xbps repository, niri and Dank Material Shell support, a per-backend memory-aware model default, and PipeWire system audio that never falls back to the microphone
0.2.0-alpha 2024 Alpha Initial alpha
0.3.0-alpha 2024 Alpha -
0.4.0-alpha 2024 Alpha Multi-language support
0.4.1-alpha 2024 Alpha Language selector UI
0.5.0-beta 2025 Beta First beta release
0.6.0-beta 2026-02-11 Beta Web SEO, mobile responsiveness, improved Fedora support
0.6.1-beta 2026-02-12 Beta Device handling improvements
0.6.2-beta 2026-02-17 Beta Bug fixes
0.6.3-beta 2026-02-19 Beta GPU detection improvements
0.7.0-beta 2026 Beta Autostart, tabbed settings, Intel GPU support, single instance
0.8.0-beta 2026-03-01 Beta Push-to-talk mode, optional voice commands, input/audio compatibility fixes
0.9.0-beta 2026-03-14 Beta Left/right modifier keys, sound effects toggle, Wayland clipboard fallback, leading-space fix
0.10.0-beta 2026-03-25 Beta Keyboard/audio reliability hardening, IBus/tray stability fixes, CI + test coverage improvements
0.10.1-beta 2026-03-27 Beta Stability patch: tray resource bundling, engine-switch segfault prevention, settings close action, XKB layout preservation
0.10.2-beta 2026-04-08 Beta Non-ASCII text injection, IBus Wayland detection, Pop!_OS deps, code quality refactor
0.11.0-beta 2026-05-30 Beta Advanced Settings tab, IBus/recognition hardening, installer fixes, distro compatibility
0.12.0-beta 2026-06-07 Beta Remote API engine, Silero VAD, threading/IBus hardening, settings and installer fixes
0.13.0-beta 2026-06-30 Beta Guided whisper.cpp model variants, Wayland text-injection reliability, hotplug keyboard support, dictation spacing fix, website docs refresh
0.14.0-beta 2026-07-13 Beta Configurable modifier+key hotkeys, FunASR/SenseVoice remote-API support, GNOME/KDE Wayland IBus reliability fixes, audio crash fix, hybrid-CPU efficiency fix
0.14.1 2026-07-17 Stable Flatpak packaging, AUR package, layout-aware hotkeys, installer/text-injection fixes
0.14.2 2026-07-17 Stable IBus engine launch + FocusIn gate; settings tabs scroll to fit monitor
0.15.0 2026-07-28 Stable Searchable settings + sidebar dictation footer, AppImage, expanded languages, dictation polish, auto-pause/keepalive, Vulkan device selection, ibus-wayland, Bluetooth mic + shortcut UI fixes
0.16.0 2026-08-23 Stable Update checker + tray notify, Right Alt PTT default (new installs), searchable language list, delete unused models, Voca tone picker (family preview WAVs), About page, AGPL-3.0, family mic icons, installer/just/uv pinning, Test Dictation missing-model message, IBus/audio/clipboard/GPU/AppImage reliability, voculinux.com family workbench restyle
0.17.0 2026-09-16 Stable Faster Whisper + Parakeet engines, Speech Model simple setup, first-run system language, Snap with ydotool/uinput, Flatpak bundles on the GitHub Release, XWayland/layout paste and settings/audio fixes
0.18.0 2026-10-02 Stable Dictation Pad, evdev hotkey suppression, PipeWire capture + system audio, RemoteDesktop portal injection, per-language shortcuts + tray history, TinyDiarize file transcription, D-Bus activation, custom dictionary, postprocessing hook, floating overlay, audio ducking, AUR -bin + self-hosted Flatpak remote + snap-promote
0.18.1 2026-10-08 Stable Settings Proxy page, opt-in JSONL transcript persistence, .deb/.rpm release packages, slimmer tray menu, dictation-pad Wayland ghost fix, evdev keyboard-discovery tightening, KWin 6 case fix, staged model picker
0.18.5 2026-10-27 Stable Patch: racing stop can no longer leave the mic recording, stalled-mic reads cannot hang Stop, keyboards survive suspend and the GNOME lock screen, Wayland dialogs stay on their own workspace, Ctrl+wheel zoom restored, installer upgrade mode keeps existing config, paste copies marked sensitive on wl-copy 2.2+, AUR source gate on attested sdists
0.16.2 2026-09-05 Stable Patch: KDE leftover IBus skip, Wayland/IBus shortcuts via wtype/ydotool, BackSpace delete that, installer glslc on Fedora/Arch, nightly version stamp, release integrity pins, AUR PKGBUILD CI gate, distro CI/docs drift, Gateway Beta on site
0.16.1 2026-08-30 Stable Patch: per-engine model at startup, leftover tray idle, terminal Ctrl+Shift+V paste, GNOME XWayland layout after IBus, Python 3.11 floor + verified downloads, AppImage glibc that boots on Debian 12, Settings/About polish

Questions?

If you're unsure about any step:

  1. Check previous releases for examples
  2. Review the GitHub Actions workflows in .github/workflows/
  3. Ask in the project's discussions

Note: This document should be updated when the release process changes.