Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
253 changes: 253 additions & 0 deletions .github/workflows/linux-canary.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
name: Linux Canary

# Produces unsigned Linux .deb and .AppImage packages from any ref without
# creating a tag, GitHub Release, or auto-updater artifact. Artifacts are
# available as a short-lived GitHub Actions artifact for explicit testing.
#
# Design notes vs. signed-macos-canary.yml:
# - Accepts a `ref` input (default: main) so pre-merge branches can be
# tested. The macOS canary requires main because it runs OIDC
# signing/notarization; this job is unsigned, contents: read only, and
# zero secrets — building arbitrary refs is safe and is the entire point.
# - No `Require main` guard for the same reason.
# - fix-appimage.sh is run without signing env vars; the script detects
# their absence and skips re-signing, repacking only (documented inline).
# - mold linker added (rui314/setup-mold) to reduce link time, matching
# the Linux Rust CI jobs in ci.yml.
# - pnpm store restore/save pattern mirrors ci.yml:149-196.
#
# Cache safety: workflow_dispatch executes on the dispatch ref (github.ref /
# github.sha stay main-scoped even when inputs.ref points at a feature branch).
# A cache write with the standard pnpm key from a non-main inputs.ref would
# land in the main-scoped namespace and could be restored by CI later — a
# cache-poisoning path. To close it:
# - Cache restores are unconditional (cold miss for untrusted refs is safe).
# - Cache saves (rust-cache save-if, pnpm actions/cache/save if:) only write
# when BOTH the dispatch ref and inputs.ref are trusted main.
on:
workflow_dispatch:
inputs:
ref:
description: "Branch, tag, or SHA to build (default: main)"
required: false
default: main

permissions:
contents: read

jobs:
build:
name: Build Linux canary
if: github.repository == 'block/buzz'
runs-on: ubuntu-latest
container: ubuntu:22.04@sha256:0e0a0fc6d18feda9db1590da249ac93e8d5abfea8f4c3c0c849ce512b5ef8982
timeout-minutes: 60
permissions:
contents: read
env:
# AppImage tools are themselves AppImages; containers lack FUSE so we
# must use the extract-and-run fallback.
APPIMAGE_EXTRACT_AND_RUN: "1"
defaults:
run:
shell: bash
steps:
- name: Install system dependencies
env:
DEBIAN_FRONTEND: noninteractive
run: |
apt-get update \
-o Acquire::Retries=3 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30
apt-get install -y --no-install-recommends \
-o Acquire::Retries=3 \
-o Acquire::http::Timeout=30 \
-o Acquire::https::Timeout=30 \
-o DPkg::Lock::Timeout=120 \
build-essential \
ca-certificates \
curl \
desktop-file-utils \
file \
git \
libasound2-dev \
libayatana-appindicator3-dev \
libgtk-3-dev \
librsvg2-dev \
libssl-dev \
libwebkit2gtk-4.1-dev \
libxdo-dev \
patchelf \
pkg-config \
squashfs-tools \
wget \
xdg-utils

- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
ref: ${{ inputs.ref }}
persist-credentials: false

- name: Mark workspace safe for git (containerized job)
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"

# Capture the checked-out commit SHA for artifact naming and log lines.
# github.sha stays the dispatch-ref (main) SHA regardless of inputs.ref;
# this step resolves the actual source SHA so artifacts are unambiguous.
- name: Capture source SHA
id: source
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"

- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1

- uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1

# Rust cache covering both the workspace sidecar build and the Tauri
# crate build. shared-key scoped to linux-canary-release so canary runs
# warm each other without colliding with CI's debug-profile keys.
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: |
.
desktop/src-tauri
shared-key: linux-canary-release
# Save only when both the dispatch ref and the checked-out input are
# trusted main; restores are unconditional (a cold miss is safe).
save-if: ${{ github.ref == 'refs/heads/main' && inputs.ref == 'main' }}

- name: Install appimagetool
run: |
case "$(uname -m)" in
x86_64) ARCH_SUFFIX="x86_64" ;;
aarch64) ARCH_SUFFIX="aarch64" ;;
*)
echo "::error::Unsupported architecture: $(uname -m)"
exit 1
;;
esac
wget -q --tries=3 --timeout=30 -O /tmp/appimagetool \
"https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-${ARCH_SUFFIX}.AppImage"
if [[ "$ARCH_SUFFIX" == "x86_64" ]]; then
echo "ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 /tmp/appimagetool" | sha256sum -c
else
echo "::error::No pinned SHA256 for appimagetool-${ARCH_SUFFIX} — add it before enabling this architecture"
exit 1
fi
install -m 755 /tmp/appimagetool /usr/local/bin/appimagetool
wget -q --tries=3 --timeout=30 -O /tmp/appimage-runtime \
"https://github.com/AppImage/type2-runtime/releases/download/20251108/runtime-${ARCH_SUFFIX}"
echo "2fca8b443c92510f1483a883f60061ad09b46b978b2631c807cd873a47ec260d /tmp/appimage-runtime" | sha256sum -c
install -D -m 644 /tmp/appimage-runtime /usr/local/lib/appimage-runtime
echo "APPIMAGETOOL_RUNTIME_FILE=/usr/local/lib/appimage-runtime" >> "$GITHUB_ENV"

- name: Get pnpm store directory
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"

- name: Restore pnpm store cache
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-

- name: Install desktop dependencies
run: just desktop-install-ci

- name: Derive canary version
id: version
env:
SOURCE_SHA: ${{ steps.source.outputs.sha }}
run: |
set -euo pipefail
BASE_VERSION=$(node -p "require('./desktop/package.json').version")
if ! [[ "$BASE_VERSION" =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(-[0-9A-Za-z.-]+)?$ ]]; then
echo "::error::Desktop version '$BASE_VERSION' is not semver"
exit 1
fi
VERSION="${BASH_REMATCH[1]}.${BASH_REMATCH[2]}.$((BASH_REMATCH[3] + 1))-test.${GITHUB_RUN_NUMBER}"
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "Building canary version $VERSION from $SOURCE_SHA"

- name: Patch canary version
env:
VERSION: ${{ steps.version.outputs.version }}
run: |
cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
cd src-tauri && cargo update --workspace

- name: Generate non-updating bundle config
run: |
cat > desktop/src-tauri/tauri.canary.conf.json <<'JSON'
{
"bundle": {
"createUpdaterArtifacts": false
}
}
JSON

- name: Build sidecars
run: |
cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
./scripts/bundle-sidecars.sh

- name: Build Linux Tauri app
run: cd desktop && pnpm tauri build --ci --bundles deb,appimage --config src-tauri/tauri.canary.conf.json
env:
CMAKE_POLICY_VERSION_MINIMUM: "3.5"

- name: Fix AppImage (remove infra libs, symlink system GStreamer)
# fix-appimage.sh checks for TAURI_SIGNING_PRIVATE_KEY and skips
# re-signing when absent, so no signing env vars are needed here.
# Repacking still runs, removing the Mesa/GLib/GStreamer conflict libs.
run: |
mapfile -t APPIMAGES < <(find desktop/src-tauri/target/release/bundle/appimage -name '*.AppImage' -type f)
if [[ ${#APPIMAGES[@]} -eq 0 ]]; then
echo "::error::No AppImage found to post-process"
exit 1
fi
if [[ ${#APPIMAGES[@]} -gt 1 ]]; then
echo "::error::Expected exactly one AppImage, found ${#APPIMAGES[@]}: ${APPIMAGES[*]}"
exit 1
fi
bash desktop/scripts/fix-appimage.sh "${APPIMAGES[0]}"

- name: Save pnpm store cache
# Write only when both the dispatch ref and the checked-out input are
# trusted main; restores above are unconditional (a cold miss is safe).
if: ${{ github.ref == 'refs/heads/main' && inputs.ref == 'main' }}
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Locate Linux build artifacts
id: artifacts
run: |
set -euo pipefail
BUNDLE_DIR="desktop/src-tauri/target/release/bundle"

DEB=$(find "$BUNDLE_DIR/deb" -name '*.deb' -type f | head -1)
if [[ -z "$DEB" ]]; then
echo "::error::No DEB found in $BUNDLE_DIR/deb"
exit 1
fi
echo "deb=$DEB" >> "$GITHUB_OUTPUT"

APPIMAGE=$(find "$BUNDLE_DIR/appimage" -name '*.AppImage' -type f | head -1)
if [[ -z "$APPIMAGE" ]]; then
echo "::error::No AppImage found in $BUNDLE_DIR/appimage"
exit 1
fi
echo "appimage=$APPIMAGE" >> "$GITHUB_OUTPUT"

- name: Upload Linux canary packages
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: buzz-linux-canary-${{ steps.source.outputs.sha }}
path: |
${{ steps.artifacts.outputs.deb }}
${{ steps.artifacts.outputs.appimage }}
if-no-files-found: error
retention-days: 7
29 changes: 28 additions & 1 deletion .github/workflows/signed-macos-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,33 @@ jobs:
exit 1
fi

- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
with:
persist-credentials: false

- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1

# Rust cache covering both the workspace sidecar build and the Tauri
# crate build. shared-key scoped to macos-canary-release so canary runs
# warm each other without colliding with CI's debug-profile keys.
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
with:
workspaces: |
.
desktop/src-tauri
shared-key: macos-canary-release

- name: Get pnpm store directory
id: pnpm-cache
run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT"

- name: Restore pnpm store cache
uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: pnpm-${{ runner.os }}-

- name: Install desktop dependencies
run: just desktop-install-ci

Expand Down Expand Up @@ -188,3 +209,9 @@ jobs:
path: ${{ steps.artifact.outputs.path }}
if-no-files-found: error
retention-days: 7

- name: Save pnpm store cache
uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
with:
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}
Loading
Loading