* fix(snap): drop uinput plug denied by the Snap Store The Snap Store rejected revisions 8-10 over the super-privileged uinput plug (allow-installation), and reviewers denied the grant request on forum.snapcraft.io/t/53528, pointing at the RemoteDesktop portal path the app already prefers. Remove the plug and the uinput-only ydotool so revisions pass automatic review again; Wayland typing continues through the RemoteDesktop portal (already the default backend ahead of ydotool), wtype, IBus Wayland on KDE, and clipboard paste. KDE Plasma loses its correct-case ydotool path on the store snap (KWin's portal mangles keysyms on some layouts, KDE bug 489021); the v0.18.x GitHub .snap still declares uinput for KDE users who sideload it. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(text-injection): stop telling snap users to connect uinput The Store snap no longer declares the plug, so the warning pointed at a command that cannot work. The Snap hints now explain that only the v0.18.x GitHub sideload declares uinput, and that native Wayland typing uses the RemoteDesktop portal or IBus Wayland on KDE. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Jatin K Malik <jk.malik@yahoo.com> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1151 lines
46 KiB
YAML
1151 lines
46 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v*'
|
|
|
|
# Least privilege, granted per job. `id-token: write` at the top level handed an
|
|
# OIDC token to every job in the workflow, including publish-aur, which runs a
|
|
# third-party action with our AUR signing key.
|
|
permissions:
|
|
contents: read
|
|
|
|
env:
|
|
# Build against the supported floor (requires-python in pyproject.toml).
|
|
PYTHON_VERSION: '3.11'
|
|
# The dedicated "tap" repo and the URL Pages serves it at. A custom
|
|
# domain (e.g. flatpak.vocalinux.com) overrides both via the vars.
|
|
FLATPAK_REMOTE_REPO: ${{ vars.FLATPAK_REMOTE_REPO || 'VocaHQ/vocalinux-flatpak' }}
|
|
FLATPAK_REMOTE_URL: ${{ vars.FLATPAK_REMOTE_URL || 'https://vocahq.github.io/vocalinux-flatpak/repo' }}
|
|
FLATPAKREPO_URL: ${{ vars.FLATPAKREPO_URL || 'https://vocahq.github.io/vocalinux-flatpak/vocahq.flatpakrepo' }}
|
|
FLATPAKREF_URL: ${{ vars.FLATPAKREF_URL || 'https://vocahq.github.io/vocalinux-flatpak/com.vocalinux.Vocalinux.flatpakref' }}
|
|
|
|
jobs:
|
|
build-and-release:
|
|
name: Build and Create Release
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
cache: 'pip'
|
|
|
|
- name: Install build dependencies
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install build wheel setuptools
|
|
|
|
- name: Extract version from tag
|
|
id: get_version
|
|
run: |
|
|
VERSION=${GITHUB_REF#refs/tags/v}
|
|
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
|
echo "Building version: $VERSION"
|
|
|
|
- name: Verify version consistency
|
|
run: |
|
|
# Get version from version.py file directly (avoid importing full package which needs gi)
|
|
PACKAGE_VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' src/vocalinux/version.py)
|
|
TAG_VERSION=${{ steps.get_version.outputs.VERSION }}
|
|
echo "Package version: $PACKAGE_VERSION"
|
|
echo "Tag version: $TAG_VERSION"
|
|
# Allow alpha/beta suffixes in package version
|
|
if [[ ! "$PACKAGE_VERSION" == *"$TAG_VERSION"* ]] && [[ ! "$TAG_VERSION" == *"$PACKAGE_VERSION"* ]]; then
|
|
echo "Warning: Version mismatch between tag ($TAG_VERSION) and package ($PACKAGE_VERSION)"
|
|
fi
|
|
|
|
# Archive timestamps are what made two builds of one tag differ. Pinned to
|
|
# the tagged commit, re-running this workflow reproduces the bytes we
|
|
# published, so a checksum stays true for the life of the release.
|
|
- name: Pin the build timestamp to the tagged commit
|
|
run: |
|
|
echo "SOURCE_DATE_EPOCH=$(git log -1 --format=%ct "$GITHUB_SHA")" >> "$GITHUB_ENV"
|
|
|
|
# The only `python -m build` in this workflow. PyPI, the aarch64 AppImage
|
|
# and the checksums all consume the artifact below, so the wheel on PyPI
|
|
# is the wheel on the release. It used to be built three times, on three
|
|
# runners, and nothing guaranteed the three agreed.
|
|
- name: Build package
|
|
run: |
|
|
python -m build
|
|
echo "Built packages:"
|
|
ls -la dist/
|
|
|
|
- name: Upload the built distributions
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: python-dist
|
|
path: |
|
|
dist/*.whl
|
|
dist/*.tar.gz
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
# glslc and the pywhispercpp Vulkan rebuild are native artifacts. Neither
|
|
# depends on this commit: the id script keys them to the base image,
|
|
# tool_checksums.txt, and the pywhispercpp pin.
|
|
- name: AppImage native cache id
|
|
id: native-cache
|
|
run: |
|
|
set -euo pipefail
|
|
id="$(bash packaging/appimage/native-cache-id.sh)"
|
|
echo "id=${id}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Cache AppImage build tools
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ~/.cache/vocalinux-appimage
|
|
key: appimage-native-${{ runner.arch }}-${{ steps.native-cache.outputs.id }}
|
|
|
|
- name: Build AppImage (x86_64)
|
|
env:
|
|
VOCALINUX_APPIMAGE_REQUIRE_VULKAN: "1"
|
|
run: |
|
|
bash packaging/appimage/docker-build.sh dist/*.whl "${{ steps.get_version.outputs.VERSION }}" dist
|
|
ls -la dist/*.AppImage
|
|
|
|
- name: Upload the x86_64 AppImage
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: appimage-x86_64
|
|
path: dist/*.AppImage
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
# The remote install commands only belong in the notes when this tag
|
|
# will actually publish the remote — the same two gates
|
|
# publish-flatpak-remote applies: a stable tag and both secrets
|
|
# configured. Otherwise the notes would send users to a remote that
|
|
# never carries this release, so they get the bundle path alone.
|
|
- name: Compose the Flatpak install notes
|
|
id: flatpak_notes
|
|
env:
|
|
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
|
|
FLATPAK_REPO_TOKEN: ${{ secrets.FLATPAK_REPO_TOKEN }}
|
|
VERSION: ${{ steps.get_version.outputs.VERSION }}
|
|
run: |
|
|
set -euo pipefail
|
|
notes="$RUNNER_TEMP/flatpak-notes.md"
|
|
case "$GITHUB_REF_NAME" in
|
|
*alpha*|*beta*|*rc*) prerelease=true ;;
|
|
*) prerelease=false ;;
|
|
esac
|
|
if [ "$prerelease" = false ] && [ -n "$FLATPAK_GPG_PRIVATE_KEY" ] && [ -n "$FLATPAK_REPO_TOKEN" ]; then
|
|
cat >"$notes" <<'EOF'
|
|
### Flatpak
|
|
Install and auto-update via the self-hosted VocaHQ remote (`flatpak update`
|
|
picks up each release):
|
|
```bash
|
|
flatpak install __FLATPAKREF_URL__
|
|
```
|
|
The `.flatpakref` adds the `vocahq` remote and resolves `org.gnome.Platform//50`
|
|
through Flathub itself — no separate Flathub setup needed. To add the
|
|
pieces by hand instead:
|
|
```bash
|
|
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
|
flatpak remote-add --if-not-exists vocahq __FLATPAKREPO_URL__
|
|
flatpak install vocahq com.vocalinux.Vocalinux
|
|
```
|
|
|
|
Or download the `.flatpak` matching your CPU (`x86_64` or `aarch64`, attached
|
|
shortly after this release is published) — a bundle with **no auto-update**:
|
|
```bash
|
|
# once: Flathub remote + GNOME runtime (the app itself is not on Flathub)
|
|
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
|
flatpak install flathub org.gnome.Platform//50
|
|
|
|
flatpak install --user ./Vocalinux-__VERSION__-x86_64.flatpak
|
|
flatpak run com.vocalinux.Vocalinux
|
|
```
|
|
EOF
|
|
else
|
|
cat >"$notes" <<'EOF'
|
|
### Flatpak
|
|
Download the `.flatpak` matching your CPU (`x86_64` or `aarch64`, attached
|
|
shortly after this release is published) — a bundle with **no auto-update**:
|
|
```bash
|
|
# once: Flathub remote + GNOME runtime (the app itself is not on Flathub)
|
|
flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
|
flatpak install flathub org.gnome.Platform//50
|
|
|
|
flatpak install --user ./Vocalinux-__VERSION__-x86_64.flatpak
|
|
flatpak run com.vocalinux.Vocalinux
|
|
```
|
|
EOF
|
|
if [ "$prerelease" = true ]; then
|
|
cat >>"$notes" <<'EOF'
|
|
Prereleases are not pushed to the `vocahq` auto-update remote — `flatpak update`
|
|
there stays on the latest stable release.
|
|
EOF
|
|
fi
|
|
fi
|
|
cat >>"$notes" <<'EOF'
|
|
Whisper.cpp engine only. Hotkeys need device access as documented in
|
|
packaging/flatpak/README.md. Not on Flathub (https://github.com/flathub/flathub/pull/9368).
|
|
EOF
|
|
sed -i \
|
|
-e "s|__VERSION__|$VERSION|g" \
|
|
-e "s|__FLATPAKREPO_URL__|$FLATPAKREPO_URL|g" \
|
|
-e "s|__FLATPAKREF_URL__|$FLATPAKREF_URL|g" \
|
|
"$notes"
|
|
{
|
|
echo 'flatpak<<VOCA_NOTES_EOF'
|
|
cat "$notes"
|
|
echo 'VOCA_NOTES_EOF'
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Create Release
|
|
id: create_release
|
|
uses: softprops/action-gh-release@efb35369e0ad2afab669f228072c1b0d510eae64 # v3.0.3
|
|
with:
|
|
files: |
|
|
dist/*.whl
|
|
dist/*.tar.gz
|
|
dist/*.AppImage
|
|
generate_release_notes: true
|
|
draft: false
|
|
prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') || contains(github.ref, 'rc') }}
|
|
body: |
|
|
## Installation
|
|
|
|
### Quick Install (Recommended)
|
|
```bash
|
|
curl -fsSL https://raw.githubusercontent.com/VocaHQ/vocalinux/${{ github.ref_name }}/install.sh | bash
|
|
```
|
|
|
|
### From Source
|
|
```bash
|
|
git clone https://github.com/VocaHQ/vocalinux.git
|
|
cd vocalinux
|
|
./install.sh
|
|
```
|
|
|
|
### From PyPI
|
|
```bash
|
|
# Install the Linux system packages first:
|
|
# GTK/AppIndicator, PortAudio, and xdotool/wtype are required.
|
|
python3 -m venv ~/.local/share/vocalinux-pypi/venv --system-site-packages
|
|
source ~/.local/share/vocalinux-pypi/venv/bin/activate
|
|
pip install vocalinux
|
|
vocalinux
|
|
```
|
|
|
|
PyPI installs the Python package only. For automatic system dependency,
|
|
desktop integration, and model setup, use the Quick Install command above.
|
|
See docs/INSTALL.md for distro-specific PyPI prerequisites.
|
|
|
|
### Arch Linux (AUR)
|
|
```bash
|
|
yay -S vocalinux
|
|
```
|
|
|
|
### AppImage (no install, no root)
|
|
Download the `.AppImage` matching your CPU (`x86_64` or `aarch64`, the latter attached
|
|
shortly after this release is published) below:
|
|
```bash
|
|
chmod +x Vocalinux-${{ steps.get_version.outputs.VERSION }}-x86_64.AppImage
|
|
./Vocalinux-${{ steps.get_version.outputs.VERSION }}-x86_64.AppImage
|
|
```
|
|
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
|
|
Tagged releases also attach `vocalinux_${{ steps.get_version.outputs.VERSION }}_amd64.snap`
|
|
and try the Snap Store (`edge` and `candidate`). `stable` is promoted
|
|
from candidate after QA by the `snap-promote.yml` dispatch workflow.
|
|
Wayland typing runs through the RemoteDesktop portal; the snap ships no `uinput` plug.
|
|
The GitHub `.snap` is also the sideload while a Store revision lags:
|
|
```bash
|
|
sudo snap install --dangerous ./vocalinux_${{ steps.get_version.outputs.VERSION }}_amd64.snap
|
|
```
|
|
`--dangerous` is required because this file is not a Store revision. It will not
|
|
refresh from the Store. amd64 only.
|
|
|
|
### Verifying what you downloaded
|
|
`SHA256SUMS` covers every file here and is attached once the aarch64 AppImage,
|
|
both Flatpak builds, and the snap finish, a few minutes after this release appears.
|
|
The wheel and the sdist below are the same bytes published to PyPI.
|
|
```bash
|
|
sha256sum -c --ignore-missing SHA256SUMS
|
|
```
|
|
Each artifact also carries GitHub build provenance, which ties it to this workflow run:
|
|
```bash
|
|
gh attestation verify Vocalinux-${{ steps.get_version.outputs.VERSION }}-x86_64.AppImage \
|
|
--repo ${{ github.repository }}
|
|
```
|
|
|
|
---
|
|
See [CHANGELOG](https://github.com/VocaHQ/vocalinux/releases) for detailed changes.
|
|
|
|
build-appimage-arm64:
|
|
name: Build and Attach AppImage (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
|
|
|
|
# The wheel is py3-none-any, so this is the wheel the release and PyPI
|
|
# carry. Building it again here made a third copy of the same version that
|
|
# no published checksum could describe.
|
|
- name: Download the built distributions
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: python-dist
|
|
path: dist
|
|
|
|
# glslc and the pywhispercpp Vulkan rebuild are native artifacts. Neither
|
|
# depends on this commit: the id script keys them to the base image,
|
|
# tool_checksums.txt, and the pywhispercpp pin.
|
|
- name: AppImage native cache id
|
|
id: native-cache
|
|
run: |
|
|
set -euo pipefail
|
|
id="$(bash packaging/appimage/native-cache-id.sh)"
|
|
echo "id=${id}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Cache AppImage build tools
|
|
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
|
|
with:
|
|
path: ~/.cache/vocalinux-appimage
|
|
key: appimage-native-${{ runner.arch }}-${{ steps.native-cache.outputs.id }}
|
|
|
|
- name: Build AppImage (aarch64)
|
|
env:
|
|
VOCALINUX_APPIMAGE_REQUIRE_VULKAN: "1"
|
|
run: |
|
|
bash packaging/appimage/docker-build.sh dist/*.whl "${{ steps.get_version.outputs.VERSION }}" dist
|
|
ls -la dist/*.AppImage
|
|
|
|
- name: Upload the aarch64 AppImage
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: appimage-aarch64
|
|
path: dist/*.AppImage
|
|
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/*.AppImage \
|
|
--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
|
|
# is a follow-up job on ubuntu-latest, same `gh release upload --clobber`
|
|
# as the aarch64 AppImage. The image is digest-pinned: it runs privileged.
|
|
build-flatpak-amd64:
|
|
name: Build Flatpak (x86_64)
|
|
runs-on: ubuntu-latest
|
|
container:
|
|
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50@sha256:1fb2df10a57276f90806e1f35454048e30bf1855b7b4ff4808c9ee55887bd852
|
|
options: --privileged
|
|
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: Build and bundle
|
|
uses: flatpak/flatpak-github-actions/flatpak-builder@79327416609af08178ad73b352877e51450790b3 # v6
|
|
with:
|
|
bundle: Vocalinux-${{ steps.get_version.outputs.VERSION }}-x86_64.flatpak
|
|
manifest-path: packaging/flatpak/com.vocalinux.Vocalinux.yml
|
|
# Same prefix rule as flatpak.yml: restore-keys is
|
|
# `flatpak-builder-${arch}`. The action appends -${arch} again.
|
|
cache-key: flatpak-builder-x86_64-${{ hashFiles('packaging/flatpak/**') }}
|
|
arch: x86_64
|
|
# Pinned actions/upload-artifact below, same digest as AppImage.
|
|
upload-artifact: false
|
|
mirror-screenshots-url: https://dl.flathub.org/media
|
|
|
|
- name: Upload the x86_64 Flatpak
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: flatpak-x86_64
|
|
path: Vocalinux-${{ steps.get_version.outputs.VERSION }}-x86_64.flatpak
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
build-flatpak-arm64:
|
|
name: Build Flatpak (aarch64)
|
|
runs-on: ubuntu-24.04-arm
|
|
container:
|
|
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50@sha256:1fb2df10a57276f90806e1f35454048e30bf1855b7b4ff4808c9ee55887bd852
|
|
options: --privileged
|
|
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: Build and bundle
|
|
uses: flatpak/flatpak-github-actions/flatpak-builder@79327416609af08178ad73b352877e51450790b3 # v6
|
|
with:
|
|
bundle: Vocalinux-${{ steps.get_version.outputs.VERSION }}-aarch64.flatpak
|
|
manifest-path: packaging/flatpak/com.vocalinux.Vocalinux.yml
|
|
cache-key: flatpak-builder-aarch64-${{ hashFiles('packaging/flatpak/**') }}
|
|
arch: aarch64
|
|
upload-artifact: false
|
|
mirror-screenshots-url: https://dl.flathub.org/media
|
|
|
|
- name: Upload the aarch64 Flatpak
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: flatpak-aarch64
|
|
path: Vocalinux-${{ steps.get_version.outputs.VERSION }}-aarch64.flatpak
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
# Self-hosted OSTree remote (#785), the Homebrew-tap analogue: the release
|
|
# bundles are imported into a signed repo and pushed to
|
|
# VocaHQ/vocalinux-flatpak, which GitHub Pages serves at FLATPAK_REMOTE_URL.
|
|
# Installs then auto-update via `flatpak update`. One-time setup — repo,
|
|
# Pages, signing key, PAT — is documented in docs/FLATPAK_REMOTE.md.
|
|
# build-and-release is a hard requirement: the remote is the update channel
|
|
# for the GitHub Release, so it must not publish when release creation
|
|
# failed. Stable tags only — a prerelease on `stable` would land in every
|
|
# user's `flatpak update`.
|
|
publish-flatpak-remote:
|
|
name: Publish Flatpak remote
|
|
needs: [build-and-release, build-flatpak-amd64, build-flatpak-arm64]
|
|
if: ${{ !contains(github.ref, 'alpha') && !contains(github.ref, 'beta') && !contains(github.ref, 'rc') }}
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
concurrency:
|
|
# Two tags publishing at once would race each other's gh-pages push.
|
|
group: flatpak-remote
|
|
steps:
|
|
# Same pattern as publish-aur: an unconfigured remote skips the publish
|
|
# instead of failing the release.
|
|
- name: Check remote credentials
|
|
id: creds
|
|
env:
|
|
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
|
|
FLATPAK_REPO_TOKEN: ${{ secrets.FLATPAK_REPO_TOKEN }}
|
|
run: |
|
|
if [ -z "$FLATPAK_GPG_PRIVATE_KEY" ] || [ -z "$FLATPAK_REPO_TOKEN" ]; then
|
|
echo "configured=false" >> "$GITHUB_OUTPUT"
|
|
echo "FLATPAK_GPG_PRIVATE_KEY and/or FLATPAK_REPO_TOKEN not set; skipping remote publish (docs/FLATPAK_REMOTE.md)"
|
|
else
|
|
echo "configured=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Checkout
|
|
if: steps.creds.outputs.configured == 'true'
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Install repo tooling
|
|
if: steps.creds.outputs.configured == 'true'
|
|
run: |
|
|
sudo apt-get update
|
|
sudo apt-get install -y --no-install-recommends flatpak ostree
|
|
|
|
- name: Download the Flatpak bundles
|
|
if: steps.creds.outputs.configured == 'true'
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
pattern: flatpak-*
|
|
merge-multiple: true
|
|
path: dist
|
|
|
|
# The import below falls back to `ostree init` when site/repo is absent,
|
|
# which is only safe on a confirmed first publish: a transient checkout
|
|
# failure taken as "no history" would publish an empty repo over the
|
|
# remote and drop every object and delta clients still need. Probe
|
|
# gh-pages directly — exit 2 means confirmed absent; anything else
|
|
# aborts instead of guessing.
|
|
- name: Check whether the remote was published before
|
|
id: published
|
|
if: steps.creds.outputs.configured == 'true'
|
|
env:
|
|
FLATPAK_REPO_TOKEN: ${{ secrets.FLATPAK_REPO_TOKEN }}
|
|
run: |
|
|
set -u
|
|
rc=0
|
|
git ls-remote --exit-code --heads \
|
|
"https://x-access-token:${FLATPAK_REPO_TOKEN}@github.com/${FLATPAK_REMOTE_REPO}.git" \
|
|
gh-pages >/dev/null 2>&1 || rc=$?
|
|
case $rc in
|
|
0) echo "published=true" >> "$GITHUB_OUTPUT" ;;
|
|
2) echo "published=false" >> "$GITHUB_OUTPUT" ;;
|
|
*)
|
|
echo "::error::Could not check ${FLATPAK_REMOTE_REPO} for gh-pages (git ls-remote exited $rc). Does the repo exist (docs/FLATPAK_REMOTE.md)?"
|
|
exit "$rc"
|
|
;;
|
|
esac
|
|
|
|
- name: Fetch the previously published repo
|
|
if: steps.creds.outputs.configured == 'true' && steps.published.outputs.published == 'true'
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
repository: ${{ env.FLATPAK_REMOTE_REPO }}
|
|
ref: gh-pages
|
|
token: ${{ secrets.FLATPAK_REPO_TOKEN }}
|
|
path: site
|
|
|
|
- name: Import the signing key
|
|
if: steps.creds.outputs.configured == 'true'
|
|
env:
|
|
FLATPAK_GPG_PRIVATE_KEY: ${{ secrets.FLATPAK_GPG_PRIVATE_KEY }}
|
|
FLATPAK_GPG_PASSPHRASE: ${{ secrets.FLATPAK_GPG_PASSPHRASE }}
|
|
run: |
|
|
set -euo pipefail
|
|
export GNUPGHOME="$RUNNER_TEMP/gnupg"
|
|
mkdir -m 700 "$GNUPGHOME"
|
|
gpg --batch --import <<<"$FLATPAK_GPG_PRIVATE_KEY"
|
|
KEYID=$(gpg --batch --with-colons --list-secret-keys | awk -F: '$1 == "sec" {print $5; exit}')
|
|
KEYGRIP=$(gpg --batch --with-colons --with-keygrip --list-secret-keys | awk -F: '$1 == "grp" {print $10; exit}')
|
|
if [ -n "${FLATPAK_GPG_PASSPHRASE:-}" ]; then
|
|
# libostree signs through gpg-agent, so the passphrase has to be
|
|
# preset on the keygrip — there is no --passphrase-fd to reach for.
|
|
PRESET=$(command -v gpg-preset-passphrase || find /usr/lib* /usr/libexec -name gpg-preset-passphrase 2>/dev/null | head -1 || true)
|
|
if [ -z "$PRESET" ]; then
|
|
echo "::error::FLATPAK_GPG_PASSPHRASE is set but gpg-preset-passphrase is unavailable; import a passphrase-less key instead"
|
|
exit 1
|
|
fi
|
|
echo "allow-preset-passphrase" >> "$GNUPGHOME/gpg-agent.conf"
|
|
gpgconf --kill gpg-agent || true
|
|
gpg-agent --daemon
|
|
printf '%s' "$FLATPAK_GPG_PASSPHRASE" | "$PRESET" --preset "$KEYGRIP"
|
|
fi
|
|
gpg --batch --export "$KEYID" > "$RUNNER_TEMP/pubkey.gpg"
|
|
echo "GNUPGHOME=$GNUPGHOME" >> "$GITHUB_ENV"
|
|
echo "KEYID=$KEYID" >> "$GITHUB_ENV"
|
|
|
|
# site/repo carries the prior objects back in, so clients pull deltas
|
|
# per release rather than re-downloading the app.
|
|
- name: Import bundles and sign the repo
|
|
if: steps.creds.outputs.configured == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
repo="$PWD/publish/repo"
|
|
if [ -d site/repo/objects ]; then
|
|
mkdir -p publish
|
|
cp -a site/repo "$repo"
|
|
else
|
|
mkdir -p "$repo"
|
|
ostree --repo="$repo" init --mode=archive-z2
|
|
fi
|
|
ls -la dist/*.flatpak
|
|
for bundle in dist/*.flatpak; do
|
|
flatpak build-import-bundle \
|
|
--gpg-sign="$KEYID" --gpg-homedir="$GNUPGHOME" \
|
|
--update-appstream --no-update-summary \
|
|
"$repo" "$bundle"
|
|
done
|
|
flatpak build-update-repo \
|
|
--gpg-sign="$KEYID" --gpg-homedir="$GNUPGHOME" \
|
|
--gpg-import="$RUNNER_TEMP/pubkey.gpg" \
|
|
--default-branch=stable \
|
|
--generate-static-deltas \
|
|
"$repo"
|
|
|
|
# RuntimeRepo lives on the .flatpakref — it is a "Flatpak Ref" key, not
|
|
# a "Flatpak Repo" key, so on the .flatpakrepo it was a silent no-op and
|
|
# clean installs without Flathub could not resolve the GNOME runtime.
|
|
# Installing the .flatpakref adds the remote and pulls the runtime in
|
|
# one command. The bare public key ships for remote-modify key rotation.
|
|
- name: Write the .flatpakrepo, .flatpakref, and index page
|
|
if: steps.creds.outputs.configured == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
touch publish/.nojekyll
|
|
{
|
|
echo '[Flatpak Repo]'
|
|
echo 'Title=VocaHQ'
|
|
echo "Url=${FLATPAK_REMOTE_URL}"
|
|
echo 'Homepage=https://vocalinux.com'
|
|
echo 'Comment=Official Vocalinux Flatpak remote'
|
|
echo 'Description=Self-hosted Vocalinux Flatpak repository'
|
|
echo 'Icon=https://vocalinux.com/icon-512x512.png'
|
|
echo 'DefaultBranch=stable'
|
|
echo "GPGKey=$(base64 -w0 "$RUNNER_TEMP/pubkey.gpg")"
|
|
} > publish/vocahq.flatpakrepo
|
|
{
|
|
echo '[Flatpak Ref]'
|
|
echo 'Version=1'
|
|
echo 'Title=Vocalinux'
|
|
echo 'Name=com.vocalinux.Vocalinux'
|
|
echo 'Branch=stable'
|
|
echo "Url=${FLATPAK_REMOTE_URL}"
|
|
echo 'SuggestRemoteName=vocahq'
|
|
echo 'RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo'
|
|
echo 'IsRuntime=false'
|
|
echo 'Homepage=https://vocalinux.com'
|
|
echo 'Icon=https://vocalinux.com/icon-512x512.png'
|
|
echo "GPGKey=$(base64 -w0 "$RUNNER_TEMP/pubkey.gpg")"
|
|
} > publish/com.vocalinux.Vocalinux.flatpakref
|
|
cp "$RUNNER_TEMP/pubkey.gpg" publish/vocahq.gpg
|
|
grep -v GPGKey publish/vocahq.flatpakrepo
|
|
sed -e "s|__FLATPAKREPO_URL__|${FLATPAKREPO_URL}|g" \
|
|
-e "s|__FLATPAKREF_URL__|${FLATPAKREF_URL}|g" \
|
|
-e "s|__REMOTE_URL__|${FLATPAK_REMOTE_URL}|g" \
|
|
packaging/flatpak/index.html > publish/index.html
|
|
|
|
- name: Publish the remote
|
|
if: steps.creds.outputs.configured == 'true'
|
|
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
|
|
with:
|
|
personal_token: ${{ secrets.FLATPAK_REPO_TOKEN }}
|
|
external_repository: ${{ env.FLATPAK_REMOTE_REPO }}
|
|
publish_dir: ./publish
|
|
publish_branch: gh-pages
|
|
commit_message: 'Vocalinux ${{ github.ref_name }}'
|
|
|
|
attach-flatpak:
|
|
name: Attach Flatpak bundles
|
|
needs: [build-and-release, build-flatpak-amd64, build-flatpak-arm64]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Download the x86_64 Flatpak
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: flatpak-x86_64
|
|
path: dist
|
|
|
|
- name: Download the aarch64 Flatpak
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: flatpak-aarch64
|
|
path: dist
|
|
|
|
- name: Attach to release
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
set -euo pipefail
|
|
ls -la dist/*.flatpak
|
|
gh release upload "${{ github.ref_name }}" \
|
|
dist/*.flatpak \
|
|
--repo ${{ github.repository }} \
|
|
--clobber
|
|
|
|
# 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-native, build-native-arm64, build-flatpak-amd64, build-flatpak-arm64, attach-flatpak, attach-snap]
|
|
runs-on: ubuntu-latest
|
|
# An attach-job failure must not skip checksums: this job hashes the build
|
|
# artifacts, which exist regardless of whether an upload to the release
|
|
# succeeded. A missing build artifact still fails the job via the guard
|
|
# below, so a partial manifest can never publish.
|
|
if: ${{ !cancelled() }}
|
|
permissions:
|
|
contents: write
|
|
id-token: write
|
|
attestations: write
|
|
steps:
|
|
- name: Collect every artifact this run published
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
merge-multiple: true
|
|
path: dist
|
|
|
|
- name: Require every expected artifact
|
|
working-directory: dist
|
|
run: |
|
|
set -euo pipefail
|
|
for pat in '*.whl' '*.tar.gz' '*.AppImage' '*.deb' '*.rpm' '*.flatpak' '*.snap'; do
|
|
compgen -G "$pat" > /dev/null || { echo "missing artifact: $pat"; exit 1; }
|
|
done
|
|
test "$(compgen -G '*.AppImage' | wc -l)" -eq 2
|
|
test "$(compgen -G '*.deb' | wc -l)" -eq 2
|
|
test "$(compgen -G '*.rpm' | wc -l)" -eq 2
|
|
test "$(compgen -G '*.flatpak' | wc -l)" -eq 2
|
|
|
|
- name: Generate SHA256SUMS
|
|
working-directory: dist
|
|
run: |
|
|
set -euo pipefail
|
|
ls -la
|
|
# Bare names, so `sha256sum -c` works in whatever directory the user
|
|
# downloaded the release into.
|
|
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.
|
|
- name: Attest build provenance
|
|
uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4
|
|
with:
|
|
subject-checksums: dist/SHA256SUMS
|
|
|
|
- name: Attach SHA256SUMS to the release
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
gh release upload "${{ github.ref_name }}" \
|
|
dist/SHA256SUMS \
|
|
--repo ${{ github.repository }} \
|
|
--clobber
|
|
|
|
publish-pypi:
|
|
name: Publish to PyPI
|
|
needs: build-and-release
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
id-token: write # PyPI trusted publishing; there is no API token to leak
|
|
environment:
|
|
name: pypi
|
|
url: https://pypi.org/project/vocalinux/
|
|
steps:
|
|
# Not a rebuild: the artifact attached to the GitHub release, so one
|
|
# checksum describes both.
|
|
- name: Download the built distributions
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: python-dist
|
|
path: dist
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
|
with:
|
|
python-version: ${{ env.PYTHON_VERSION }}
|
|
|
|
- name: Verify package
|
|
run: |
|
|
python -m pip install --upgrade pip
|
|
pip install twine
|
|
twine check dist/*
|
|
|
|
- name: Publish to PyPI
|
|
uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
|
|
with:
|
|
skip-existing: true
|
|
|
|
publish-aur:
|
|
name: Publish to AUR
|
|
needs: build-and-release
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
# Secrets: AUR_SSH_PRIVATE_KEY, AUR_USERNAME, AUR_EMAIL — see docs/AUR.md
|
|
steps:
|
|
- name: Check for AUR credentials
|
|
id: aur
|
|
env:
|
|
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
|
run: |
|
|
if [ -z "$AUR_SSH_PRIVATE_KEY" ]; then
|
|
echo "configured=false" >> "$GITHUB_OUTPUT"
|
|
echo "AUR_SSH_PRIVATE_KEY not set; skipping (docs/AUR.md)"
|
|
else
|
|
echo "configured=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Checkout code
|
|
if: steps.aur.outputs.configured == 'true'
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Bump PKGBUILD version
|
|
if: steps.aur.outputs.configured == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
TAG="${GITHUB_REF#refs/tags/v}"
|
|
for PKGBUILD in packaging/aur/vocalinux/PKGBUILD packaging/aur/vocalinux-bin/PKGBUILD; do
|
|
sed -i "s/^pkgver=.*/pkgver=${TAG//-/}/; s/^_tag=.*/_tag=${TAG}/; s/^pkgrel=.*/pkgrel=1/" "$PKGBUILD"
|
|
grep -E '^(pkgver|_tag|pkgrel)=' "$PKGBUILD"
|
|
done
|
|
|
|
- name: Publish AUR package
|
|
if: steps.aur.outputs.configured == 'true'
|
|
uses: KSXGitHub/github-actions-deploy-aur@084b0d9b15415bf9cdb65d44dad1efe37a354050 # v4.2.0
|
|
with:
|
|
pkgname: vocalinux
|
|
pkgbuild: packaging/aur/vocalinux/PKGBUILD
|
|
updpkgsums: 'true'
|
|
commit_username: ${{ secrets.AUR_USERNAME }}
|
|
commit_email: ${{ secrets.AUR_EMAIL }}
|
|
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
|
commit_message: 'Release ${{ github.ref_name }}'
|
|
|
|
# Separate job: the -bin PKGBUILD pins digests of BOTH AppImages, and the
|
|
# aarch64 build attaches its asset after build-and-release returns. Waiting
|
|
# for it here keeps the source package publishing early while the binary
|
|
# package never curls a 404.
|
|
publish-aur-bin:
|
|
name: Publish -bin to AUR
|
|
needs: [publish-aur, build-appimage-arm64]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Check for AUR credentials
|
|
id: aur
|
|
env:
|
|
AUR_SSH_PRIVATE_KEY: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
|
run: |
|
|
if [ -z "$AUR_SSH_PRIVATE_KEY" ]; then
|
|
echo "configured=false" >> "$GITHUB_OUTPUT"
|
|
echo "AUR_SSH_PRIVATE_KEY not set; skipping (docs/AUR.md)"
|
|
else
|
|
echo "configured=true" >> "$GITHUB_OUTPUT"
|
|
fi
|
|
|
|
- name: Checkout code
|
|
if: steps.aur.outputs.configured == 'true'
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Bump PKGBUILD version
|
|
if: steps.aur.outputs.configured == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
TAG="${GITHUB_REF#refs/tags/v}"
|
|
PKGBUILD=packaging/aur/vocalinux-bin/PKGBUILD
|
|
sed -i "s/^pkgver=.*/pkgver=${TAG//-/}/; s/^_tag=.*/_tag=${TAG}/; s/^pkgrel=.*/pkgrel=1/" "$PKGBUILD"
|
|
grep -E '^(pkgver|_tag|pkgrel)=' "$PKGBUILD"
|
|
|
|
# The -bin PKGBUILD carries per-arch sum arrays, but updpkgsums inside
|
|
# the publish action only regenerates the x86_64 one — pin both here so
|
|
# the published PKGBUILD never ships a stale aarch64 digest.
|
|
- name: Pin per-arch AppImage digests in the -bin PKGBUILD
|
|
if: steps.aur.outputs.configured == 'true'
|
|
run: |
|
|
set -euo pipefail
|
|
tag="${GITHUB_REF_NAME#v}"
|
|
for arch in x86_64 aarch64; do
|
|
url="https://github.com/VocaHQ/vocalinux/releases/download/${GITHUB_REF_NAME}/Vocalinux-${tag}-${arch}.AppImage"
|
|
sum="$(curl -fsSL "$url" | sha256sum | cut -d' ' -f1)"
|
|
sed -i "s/^sha256sums_${arch}=('[0-9a-f]*')/sha256sums_${arch}=('${sum}')/" \
|
|
packaging/aur/vocalinux-bin/PKGBUILD
|
|
done
|
|
|
|
- name: Publish AUR -bin package
|
|
if: steps.aur.outputs.configured == 'true'
|
|
uses: KSXGitHub/github-actions-deploy-aur@084b0d9b15415bf9cdb65d44dad1efe37a354050 # v4.2.0
|
|
with:
|
|
pkgname: vocalinux-bin
|
|
# The -bin PKGBUILD names local sources (vocalinux.desktop,
|
|
# vocalinux.svg, LICENSE); publishing the whole directory mirrors
|
|
# them into the AUR repo instead of shipping a lone PKGBUILD.
|
|
asset_dir: packaging/aur/vocalinux-bin
|
|
# Sums are pinned in the step above for BOTH arches; regenerating
|
|
# here would only refresh the x86_64 array.
|
|
updpkgsums: 'false'
|
|
commit_username: ${{ secrets.AUR_USERNAME }}
|
|
commit_email: ${{ secrets.AUR_EMAIL }}
|
|
ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }}
|
|
commit_message: 'Release ${{ github.ref_name }}'
|
|
|
|
# GitHub Release first, Store second. A super-privileged plug can still
|
|
# queue a human review and refuse edge/candidate; the .snap still
|
|
# has to land on the tag for sideload. grade: stable allows candidate; do
|
|
# not auto-release to stable — promote after QA via snap-promote.yml (#783).
|
|
# Secret: SNAPCRAFT_STORE_CREDENTIALS (snapcraft export-login).
|
|
build-snap:
|
|
name: Build Snap
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Build snap
|
|
id: build
|
|
uses: snapcore/action-build@3bdaa03e1ba6bf59a65f84a751d943d549a54e79 # v1
|
|
|
|
- name: Upload the snap
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: snap-amd64
|
|
path: ${{ steps.build.outputs.snap }}
|
|
if-no-files-found: error
|
|
retention-days: 7
|
|
|
|
attach-snap:
|
|
name: Attach Snap
|
|
needs: [build-and-release, build-snap]
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Download the snap
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: snap-amd64
|
|
path: dist
|
|
|
|
- name: Attach to release
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
TAG: ${{ github.ref_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
ls -la dist/*.snap
|
|
gh release upload "$TAG" dist/*.snap \
|
|
--repo ${{ github.repository }} \
|
|
--clobber
|
|
|
|
publish-snap:
|
|
name: Publish Snap to Store
|
|
needs: build-snap
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
steps:
|
|
- name: Require Snap Store credentials
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
|
run: |
|
|
if [ -z "$SNAPCRAFT_STORE_CREDENTIALS" ]; then
|
|
echo "SNAPCRAFT_STORE_CREDENTIALS is unset; cannot publish the snap"
|
|
exit 1
|
|
fi
|
|
|
|
- name: Download the snap
|
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
with:
|
|
name: snap-amd64
|
|
path: dist
|
|
|
|
- name: Locate the snap
|
|
id: snapfile
|
|
run: |
|
|
set -euo pipefail
|
|
file=$(ls dist/*.snap)
|
|
echo "file=$file" >> "$GITHUB_OUTPUT"
|
|
ls -la "$file"
|
|
|
|
- name: Install snapcraft
|
|
run: sudo snap install snapcraft --classic
|
|
|
|
- name: Publish snap to edge and candidate
|
|
env:
|
|
SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }}
|
|
SNAP_FILE: ${{ steps.snapfile.outputs.file }}
|
|
run: |
|
|
set -euo pipefail
|
|
log=/tmp/snap-upload.log
|
|
set +e
|
|
/snap/bin/snapcraft upload "$SNAP_FILE" --release edge,candidate >"$log" 2>&1
|
|
status=$?
|
|
set -e
|
|
cat "$log"
|
|
if [ "$status" -eq 0 ]; then
|
|
exit 0
|
|
fi
|
|
if grep -E -q "will need manual review|human review required" "$log" \
|
|
&& grep -F -q "allow-installation" "$log"; then
|
|
echo "::warning::Snap Store queued a human review (allow-installation). The .snap is on the GitHub Release."
|
|
exit 0
|
|
fi
|
|
echo "::error::Snap Store publish failed (exit $status) for a reason other than allow-installation review."
|
|
exit "$status"
|
|
|
|
deploy-website:
|
|
name: Deploy Website
|
|
runs-on: ubuntu-latest
|
|
needs: [build-and-release, publish-pypi]
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Checkout code
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: '24'
|
|
cache: 'npm'
|
|
cache-dependency-path: './web/package-lock.json'
|
|
|
|
- name: Install dependencies
|
|
working-directory: ./web
|
|
run: npm ci || npm install
|
|
|
|
- name: Sync version from source code
|
|
run: |
|
|
VERSION=$(grep -oP '__version__\s*=\s*"\K[^"]+' src/vocalinux/version.py)
|
|
echo "Syncing version: $VERSION"
|
|
# JSON-LD uses unquoted JS keys: softwareVersion: "x.y.z"
|
|
if grep -qE 'softwareVersion:\s*"' web/src/app/page.tsx; then
|
|
sed -i 's/softwareVersion: "[^"]*"/softwareVersion: "'"$VERSION"'"/' web/src/app/page.tsx
|
|
echo "Updated page.tsx version:"
|
|
grep -oP 'softwareVersion:\s*"\K[^"]+' web/src/app/page.tsx
|
|
elif grep -qE 'softwareVersion:\s*"' web/src/app/layout.tsx; then
|
|
sed -i 's/softwareVersion: "[^"]*"/softwareVersion: "'"$VERSION"'"/' web/src/app/layout.tsx
|
|
echo "Updated layout.tsx version:"
|
|
grep -oP 'softwareVersion:\s*"\K[^"]+' web/src/app/layout.tsx
|
|
else
|
|
echo "::warning::No softwareVersion field found in page.tsx or layout.tsx; skipping sync"
|
|
fi
|
|
# Update package.json version (remove -alpha/beta/rc suffix for npm)
|
|
NPM_VERSION=${VERSION%-*}
|
|
NPM_VERSION=${NPM_VERSION%-beta*}
|
|
NPM_VERSION=${NPM_VERSION%-rc*}
|
|
sed -i 's/"version": "[^"]*"/"version": "'"$NPM_VERSION"'"/' web/package.json
|
|
# Verify the changes
|
|
echo "Updated package.json version:"
|
|
grep -oP '"version":\s*"\K[^"]+' web/package.json
|
|
|
|
- name: Build and deploy website
|
|
working-directory: ./web
|
|
run: |
|
|
npm ci || npm install
|
|
npm run deploy
|
|
|
|
- name: Deploy to gh-pages
|
|
uses: peaceiris/actions-gh-pages@84c30a85c19949d7eee79c4ff27748b70285e453 # v4
|
|
with:
|
|
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
publish_dir: ./web/out
|
|
publish_branch: gh-pages
|
|
cname: vocalinux.com
|
|
force_orphan: true
|