Files
Tha.Les b9437115ca Only offer an update once the release has been promoted (#429)
Two halves of the same bug.

The upload steps passed no `prerelease` to softprops/action-gh-release,
which defaults it to false and writes it back. Attaching assets therefore
promoted the release it was attaching them to. v0.13.0 and v0.14.0 were
both published as pre-releases and both ended up marked as the latest
release within the hour, which also fired `released` and pushed :latest
to GHCR. Each workflow now carries the release's own flag.

The updater took the newest non-draft release, pre-releases included.
That was deliberate when it was written, because /releases/latest hides
pre-releases and every release was one, so tracking stable meant nobody
would ever be notified. With the flag preserved, a pre-release is now
genuinely a pre-release, and offering it would push unverified builds to
everyone. It now takes the newest release that is neither draft nor
pre-release, so a release reaches users only once it has been promoted.

The e2e stub grew an unpromoted pre-release ahead of the stable one, and
the spec pins which of the two the card names.

Co-authored-by: Thales <>
2026-08-24 13:12:04 +01:00

161 lines
6.6 KiB
YAML

name: Linux Release
on:
release:
types: [published]
# Manual test build: builds and scans both variants on the self-hosted runner
# but does NOT upload (no release to attach to). Lets you validate the runner
# toolchain and the CUDA build without cutting a release tag.
workflow_dispatch:
inputs:
version:
description: "Version for the test build (must be valid PEP 440, e.g. 0.0.0)"
required: false
default: "0.0.0"
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
build-and-upload:
# Runner must be linux/x64 with rustup, Node.js, Docker, and sudo apt access.
runs-on: [self-hosted, linux, x64]
timeout-minutes: 90
permissions:
contents: write
defaults:
run:
shell: bash
steps:
- name: clean workspace
run: rm -rf .build dist
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- name: install build dependencies
env:
DEBIAN_FRONTEND: noninteractive
run: |
# Tauri v2 build deps + tooling. webkit2gtk-4.1 matches the tauri = "2"
# crate; ffmpeg is a runtime dependency, not bundled.
#
# The self-hosted runner does NOT have passwordless sudo, so a bare
# `sudo apt-get` hangs forever at the password prompt. These packages are
# already installed on the persistent runner, so check first (dpkg needs
# no sudo) and skip apt entirely when everything is present. Only if a
# package is genuinely missing do we touch apt, via `sudo -n` (fails fast
# instead of prompting) so a release never hangs on sudo again.
PKGS="build-essential curl file wget libssl-dev libxdo-dev \
libwebkit2gtk-4.1-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev"
missing=""
for p in $PKGS; do
dpkg -s "$p" >/dev/null 2>&1 || missing="$missing $p"
done
if [ -z "$missing" ]; then
echo "All build dependencies already installed; skipping apt."
exit 0
fi
echo "Missing packages:$missing"
if ! sudo -n true 2>/dev/null; then
echo "ERROR: build deps missing and passwordless sudo is not available." >&2
echo "Install on the runner: sudo apt-get install -y$missing" >&2
exit 1
fi
sudo -n apt-get update -o DPkg::Lock::Timeout=120
sudo -n apt-get install -y --no-install-recommends -o DPkg::Lock::Timeout=120 $missing
- name: install uv
run: |
curl -LsSf https://astral.sh/uv/install.sh | sh
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
- name: install rust toolchain
run: rustup default stable
- name: write version files
env:
DISPATCH_VERSION: ${{ inputs.version }}
# github.ref_name (evaluated by Actions) instead of $GITHUB_REF_NAME,
# which is only injected by runner >= 2.290 — empty on older
# self-hosted runners.
REF_NAME: ${{ github.ref_name }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
VERSION="${DISPATCH_VERSION#v}"
else
VERSION="${REF_NAME#v}"
fi
if [ -z "$VERSION" ]; then
echo "Could not determine a version" >&2
exit 1
fi
printf '{ "version": "%s" }\n' "$VERSION" > static/version.json
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" desktop/src-tauri/Cargo.toml
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" desktop/src-tauri/tauri.conf.json
sed -i "s/^version = \".*\"/version = \"$VERSION\"/" pyproject.toml
sed -i "s/\"version\": \"[^\"]*\"/\"version\": \"$VERSION\"/" desktop/package.json
echo "VERSION=$VERSION" >> "$GITHUB_ENV"
echo "Wrote version $VERSION to all version files"
- name: build Linux CPU
run: |
# PUBLISH_UPDATER_ASSETS=1 also emits the slim app-layer asset the
# in-app updater downloads (#421). Built once here: StemDeck and
# backend/ are identical in both variants.
PACKAGE_NAME=StemDeck-Linux-x64 \
PACKAGE_VERSION="$VERSION" \
PUBLISH_UPDATER_ASSETS=1 \
bash scripts/linux/make-portable.sh
# Drop the uncompressed stage but keep the tarball; frees disk for the
# larger NVIDIA build. The Tauri binary under target/ is preserved.
rm -rf dist/StemDeck-Linux-x64
- name: build Linux NVIDIA
run: |
# CPU_ONLY=0 keeps the CUDA torch wheel; SKIP_TAURI_BUILD=1 reuses the
# binary built in the CPU step (identical for both variants).
PACKAGE_NAME=StemDeck-Linux-x64.NVIDIA \
PACKAGE_VERSION="$VERSION" \
CPU_ONLY=0 \
SKIP_TAURI_BUILD=1 \
bash scripts/linux/make-portable.sh
rm -rf dist/StemDeck-Linux-x64.NVIDIA
- name: scan artifacts
run: |
echo "Artifacts staged in: $PWD/dist"
ls -lh dist
echo "SHA256 checksums:"
( cd dist && sha256sum *.tar.gz )
echo "Pulling latest ClamAV scanner image..."
docker pull clamav/clamav:latest
echo "Running ClamAV scan over dist/..."
docker run --rm -v "$PWD/dist:/scan:ro" clamav/clamav:latest \
clamscan --recursive --infected --bell /scan
echo "ClamAV scan completed successfully. No infected files reported."
- name: upload artifacts
# Only attach to a real release; a manual test build has nothing to upload to.
if: github.event_name == 'release'
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
# Carry the release's own flag. The action defaults `prerelease` to
# false and writes it back, so attaching assets silently promoted a
# pre-release to the latest release -- which also fires `released`,
# pushing :latest to GHCR, and makes the in-app updater offer a build
# that was never verified.
prerelease: ${{ github.event.release.prerelease }}
files: |
dist/StemDeck-Linux-x64.tar.gz
dist/StemDeck-Linux-x64.tar.gz.sha256
dist/StemDeck-Linux-x64.NVIDIA.tar.gz
dist/StemDeck-Linux-x64.NVIDIA.tar.gz.sha256
dist/StemDeck-Linux-x64-app.tar.gz
dist/StemDeck-Linux-x64-app.tar.gz.sha256
dist/StemDeck-Linux-x64-runtime-version.json