From 77a476c8e7b68113e218f784f091ea006607a24c Mon Sep 17 00:00:00 2001 From: vicrodh <103399283+vicrodh@users.noreply.github.com> Date: Fri, 26 Jun 2026 08:32:26 -0600 Subject: [PATCH] ci(snap): all-hosted Snap pipeline for the Slint build (dump prebuilt binary) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The dev box is Gentoo/OpenRC with no systemd, hence no snapd, so it cannot build or upload snaps. This pipeline keeps everything on GitHub-hosted Ubuntu runners (which have snapd) instead: - packaging/snap-slint/snapcraft.yaml: a `plugin: dump` recipe that packages a PRE-BUILT qbz binary (no compilation), with the Slint runtime stack (Vulkan/GL via the gnome extension, winit Wayland/X11, alsa/pipewire, mpris). Kept separate from the Tauri snapcraft.yaml so neither clobbers the other. - .github/workflows/release-snap.yml: job 1 compiles the binary on a 16 GB runner using the same MIN-tier + 18 GB swap recipe as build-slint.yml; job 2 downloads it and runs snapcraft `dump` + snapcore/action-publish. The heavy compile is on a free public runner; the snap packaging is on a runner that has snapd — neither touches the OpenRC box. Requires the SNAPCRAFT_STORE_CREDENTIALS secret (generate once via `snapcraft export-login` on any snapd-capable host). The runtime confinement (stage-packages/plugs for the non-GTK GPU stack) is a first cut and will need a real `snapcraft build` + `snap run` on the runner to finalize; it cannot be tested locally without snapd. cc --- .github/workflows/release-snap.yml | 153 ++++++++++++++++++++++++++++ packaging/snap-slint/snapcraft.yaml | 115 +++++++++++++++++++++ 2 files changed, 268 insertions(+) create mode 100644 .github/workflows/release-snap.yml create mode 100644 packaging/snap-slint/snapcraft.yaml diff --git a/.github/workflows/release-snap.yml b/.github/workflows/release-snap.yml new file mode 100644 index 000000000..3d5adf179 --- /dev/null +++ b/.github/workflows/release-snap.yml @@ -0,0 +1,153 @@ +name: Release Snap (Slint, all-hosted) + +# All-hosted Snap pipeline for the native (Slint) qbz. NOTHING runs on the dev +# box, so the lack of systemd/snapd there does not matter: +# job 1 (build-binary): a 16 GB Ubuntu runner compiles the qbz binary using +# the same MIN-tier + 18 GB swap recipe as build-slint.yml (the ~30 GB +# qbz_ui peak fits in 16 GB RAM + swap), and uploads it as an artifact. +# job 2 (snap): a second Ubuntu runner (which HAS snapd) downloads that +# prebuilt binary and packages it with `plugin: dump` (NO compilation, runs +# in seconds), then publishes to the Snap Store. +# So the heavy compile is on a free public runner, and the snap packaging is on +# a runner that has snapd — neither needs your OpenRC box. +# +# NOTE: build-binary duplicates build-slint.yml's build. When the release +# pipeline is unified (build-once -> fan out to deb/rpm/appimage/snap/flatpak), +# this job becomes a `download-artifact` of the shared binary instead. + +on: + workflow_dispatch: + inputs: + tag: + description: "Release tag to package (e.g. v1.2.16). Defaults to the pushed tag." + required: false + type: string + channel: + description: "Snap Store channel" + required: false + default: edge + type: choice + options: [edge, beta, candidate, stable] + push: + tags: + - "v*" + +# A tag never builds twice; distinct tags never cancel each other. Never abort a +# half-published release. +concurrency: + group: release-snap-${{ github.ref }} + cancel-in-progress: false + +jobs: + build-binary: + runs-on: ubuntu-24.04 + timeout-minutes: 180 + steps: + - uses: actions/checkout@v4 + + - name: Free disk + swap (sized for the ~30 GB qbz_ui peak) + run: | + sudo rm -rf /usr/share/dotnet /opt/ghc /usr/local/lib/android \ + /opt/hostedtoolcache /usr/local/share/boost /usr/share/swift \ + /usr/local/lib/node_modules || true + sudo docker image prune --all --force || true + ( sudo fallocate -l 18G /swap_extra \ + && sudo chmod 600 /swap_extra \ + && sudo mkswap /swap_extra \ + && sudo swapon /swap_extra ) || echo "extra swap skipped (non-fatal)" + free -h; df -h / + + - name: System dependencies + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + build-essential pkg-config cmake clang libclang-dev mold nasm \ + libasound2-dev libjack-jackd2-dev \ + libfontconfig1-dev libfreetype-dev \ + libxkbcommon-dev libwayland-dev libxcb1-dev \ + libgl1-mesa-dev libegl1-mesa-dev \ + libdbus-1-dev libssl-dev \ + libharfbuzz-dev libjbig2dec0-dev libopenjp2-7-dev \ + libjpeg-dev zlib1g-dev libgumbo-dev + + - name: Rust toolchain (pinned dated nightly) + uses: dtolnay/rust-toolchain@master + with: + toolchain: nightly-2026-06-23 + + - name: Cargo cache (Swatinem/rust-cache) + uses: Swatinem/rust-cache@v2 + with: + workspaces: crates -> target + shared-key: slint-linux + cache-on-failure: true + # share the SAME cache as build-slint.yml (same shared-key) so the + # heavy qbz_ui rlib is reused across both workflows. + save-if: false + + - name: Build qbz (memory-tuned MIN tier, swap-backed) + env: + CARGO_INCREMENTAL: '0' + CARGO_BUILD_JOBS: '1' + CARGO_PROFILE_RELEASE_CODEGEN_UNITS: '256' + CARGO_PROFILE_RELEASE_OPT_LEVEL: '2' + RUSTFLAGS: '-C target-feature=+aes,+ssse3 -C link-arg=-fuse-ld=mold -Z threads=1' + run: | + cargo build --release --manifest-path crates/Cargo.toml -p qbz + strip crates/target/release/qbz || true + ls -lh crates/target/release/qbz + + - name: Upload binary artifact + uses: actions/upload-artifact@v4 + with: + name: qbz-bin-amd64 + path: crates/target/release/qbz + if-no-files-found: error + + snap: + needs: build-binary + runs-on: ubuntu-24.04 + timeout-minutes: 30 + steps: + - uses: actions/checkout@v4 + + - name: Download prebuilt binary + uses: actions/download-artifact@v4 + with: + name: qbz-bin-amd64 + path: dl + + - name: Stage snap payload (binary + desktop + icon) and recipe + run: | + set -eux + TAG="${{ github.event.inputs.tag || github.ref_name }}" + VER="${TAG#v}" + mkdir -p snap-payload + install -m755 dl/qbz snap-payload/qbz + cp packaging/snap/snap/gui/qbz.desktop snap-payload/qbz.desktop + cp packaging/snap/snap/gui/qbz.png snap-payload/qbz.png + # Place the dump recipe where snapcraft looks for it, and stamp version. + mkdir -p snap + cp packaging/snap-slint/snapcraft.yaml snap/snapcraft.yaml + sed -i "s/^version:.*/version: '${VER}'/" snap/snapcraft.yaml + echo "Built snap version: ${VER}" + cat snap/snapcraft.yaml | head -20 + + - name: Build the snap (no compilation — dump of the prebuilt binary) + id: build + uses: snapcore/action-build@v1 + + - name: Upload .snap artifact + uses: actions/upload-artifact@v4 + with: + name: qbz-snap-amd64 + path: ${{ steps.build.outputs.snap }} + if-no-files-found: error + + - name: Publish to the Snap Store + uses: snapcore/action-publish@v1 + env: + SNAPCRAFT_STORE_CREDENTIALS: ${{ secrets.SNAPCRAFT_STORE_CREDENTIALS }} + with: + snap: ${{ steps.build.outputs.snap }} + release: ${{ github.event.inputs.channel || 'edge' }} diff --git a/packaging/snap-slint/snapcraft.yaml b/packaging/snap-slint/snapcraft.yaml new file mode 100644 index 000000000..b67e1417c --- /dev/null +++ b/packaging/snap-slint/snapcraft.yaml @@ -0,0 +1,115 @@ +# QBZ (Slint) — Snap recipe that PACKAGES A PRE-BUILT BINARY (plugin: dump). +# +# This is NOT the Tauri snapcraft.yaml (../../snapcraft.yaml). That one builds +# from source (plugin: rust + npm tauri build, webkit2gtk). This one takes a +# qbz binary ALREADY built (e.g. on a 16 GB hosted runner with swap, or the +# self-hosted box) and just wraps it into a .snap — NO compilation here, so it +# runs in seconds on any runner that has snapd (the dev box does not need it). +# +# The CI job (release-snap.yml) stages the binary + .desktop + icon under +# ./snap-payload/ and substitutes `version:` from the release tag, then runs +# `snapcraft` (which lives on the hosted Ubuntu runner) + snapcore/action-publish. +# +# UNTESTED runtime confinement: the Slint app uses femtovg-wgpu (Vulkan/GL) + +# winit (Wayland/X11), NOT GTK/webkit. The stage-packages + plugs below are a +# best-effort first cut; the FIRST real `snapcraft` build + `snap run` on the +# runner will surface any missing lib / interface (I can't test snapd locally). + +name: qbz-player # keep the existing registered Snap Store name +title: "QBZ — Native Qobuz Client for Linux" +version: '0.0.0' # OVERWRITTEN by release-snap.yml from the tag (sed) +summary: Native Qobuz player for Linux with bit-perfect audio and DAC control +description: | + QBZ is a native Linux desktop client for Qobuz(TM), built for high-fidelity + playback with a native audio pipeline (direct ALSA and PipeWire), bit-perfect + output, exclusive device access, and per-track sample-rate switching for + external DACs — up to 24-bit / 192 kHz. + + This is the native (Slint) build: a single self-contained binary, no web + runtime. Features: native Qobuz(TM) streaming, bit-perfect ALSA exclusive + mode, DAC passthrough, Hi-Res FLAC, queue/playlists/favorites/offline cache, + local library, Chromecast and DLNA, MPRIS media keys and desktop integration. + + QBZ uses the Qobuz(TM) API but is not affiliated with, endorsed by, or + certified by Qobuz. + +license: MIT +contact: https://github.com/vicrodh/qbz/discussions +website: https://qbz.lol +source-code: https://github.com/vicrodh/qbz +issues: https://github.com/vicrodh/qbz/issues + +grade: stable +confinement: strict +base: core24 +icon: packaging/snap/snap/gui/qbz.png + +platforms: + amd64: + # arm64 is a separate job (needs an arm64 binary + runner); add later. + +apps: + qbz: + command: bin/qbz + desktop: usr/share/applications/qbz.desktop + # The gnome extension provides the graphics/font platform (Mesa GL, EGL, + # fontconfig, wayland) that a femtovg-wgpu + winit app needs — it is NOT + # about GTK here, just the runtime platform. It wires desktop/wayland/x11/ + # gsettings plugs automatically. + extensions: [gnome] + plugs: + - network + - network-status + - audio-playback + - home + - removable-media + - alsa + - pipewire + - opengl + slots: + - mpris + +slots: + mpris: + interface: mpris + name: qbz + +parts: + qbz: + plugin: dump + # release-snap.yml writes the prebuilt binary + desktop + icon here: + source: ./snap-payload + organize: + qbz: bin/qbz + qbz.desktop: usr/share/applications/qbz.desktop + qbz.png: usr/share/icons/hicolor/256x256/apps/qbz.png + stage-packages: + # Audio (the bit-perfect path) + - libasound2t64 + - libasound2-plugins + - libpipewire-0.3-0 + - libjack-jackd2-0 + # TLS / HTTP + - libssl3t64 + # GPU stack for femtovg-wgpu (Vulkan first, GL fallback). The gnome + # extension ships Mesa GL; add the Vulkan ICD + loader explicitly since + # wgpu prefers Vulkan and the platform may not expose the ICD. + - libvulkan1 + - mesa-vulkan-drivers + - libegl1 + - libgl1 + # winit windowing (Wayland + X11) + - libwayland-client0 + - libwayland-cursor0 + - libwayland-egl1 + - libxkbcommon0 + - libxcb1 + - libx11-6 + - libxcursor1 + - libxi6 + - libxrandr2 + # Fonts / text shaping (femtovg) + - libfontconfig1 + - libfreetype6 + # Tray (ksni) + portals + media controls over D-Bus + - libdbus-1-3