Explorer
/opt/struktur/buzz/.github/workflows/linux-canary.yml
← Zurück ↓ Download
name: Linux Canary

# Produces unsigned Linux .deb and .AppImage packages from main 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:
#   - fix-appimage.sh is run without signing env vars; the script detects
#     their absence and skips re-signing, repacking only (documented inline).
#   - Build tools match release.yml; cache keys derive the concrete linker and
#     native library identity rather than assuming the moving runner image.
#   - pnpm store restore/save pattern mirrors ci.yml:149-196.
on:
  workflow_dispatch:

permissions:
  contents: read

jobs:
  build:
    name: Build Linux canary
    if: github.repository == 'block/buzz'
    runs-on: ubuntu-latest
    container: ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90
    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: Require main
        env:
          SOURCE_REF: ${{ github.ref }}
        run: |
          if [[ "$SOURCE_REF" != "refs/heads/main" ]]; then
            echo "::error::Canary builds must run from main; got $SOURCE_REF"
            exit 1
          fi

      - 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:
          persist-credentials: false

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

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

      - 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
        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 $GITHUB_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: Resolve native toolchain identity
        id: native_toolchain
        run: echo "id=$(scripts/desktop-native-toolchain-id.sh linux)" >> "$GITHUB_OUTPUT"

      # Compute this after cargo update so the key describes the graph that is
      # actually compiled. The helper normalizes only Buzz Desktop's release
      # version, allowing a canary to warm an otherwise identical tag build.
      - name: Compute exact release cache key
        id: rust_cache_key
        env:
          NATIVE_TOOLCHAIN_ID: ${{ steps.native_toolchain.outputs.id }}
        run: |
          KEY=$(scripts/desktop-release-cache-key.py \
            --platform "$RUNNER_OS" \
            --target x86_64-unknown-linux-gnu \
            --features mesh-llm \
            --native-inputs "$NATIVE_TOOLCHAIN_ID")
          echo "key=$KEY" >> "$GITHUB_OUTPUT"
          echo "Release cache key: $KEY"

      - name: Restore exact release Cargo cache
        id: rust_cache
        uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
            desktop/src-tauri/target
            !desktop/src-tauri/target/**/release/bundle
          key: ${{ steps.rust_cache_key.outputs.key }}

      - 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-backend-kubernetes -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 --features mesh-llm --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: Measure release Cargo cache inputs
        if: always()
        run: du -sh ~/.cargo/registry ~/.cargo/git target desktop/src-tauri/target 2>/dev/null || true

      # Only this trusted, main-bound canary writes the cache. Excluding bundle
      # output prevents installers from entering it.
      - name: Save exact release Cargo cache
        if: steps.rust_cache.outputs.cache-hit != 'true'
        uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5
        with:
          path: |
            ~/.cargo/registry
            ~/.cargo/git
            target
            desktop/src-tauri/target
            !desktop/src-tauri/target/**/release/bundle
          key: ${{ steps.rust_cache_key.outputs.key }}

      - 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') }}

      - 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-${{ github.sha }}
          path: |
            ${{ steps.artifacts.outputs.deb }}
            ${{ steps.artifacts.outputs.appimage }}
          if-no-files-found: error
          retention-days: 7