mirror of
https://github.com/block/buzz.git
synced 2026-11-02 07:22:55 +00:00
GitHub-hosted Ubuntu 24.04 runners include Microsoft package repos (azure-cli, packages.microsoft.com/ubuntu/24.04/prod) in their default apt sources. These repos intermittently return 403 Forbidden, causing apt-get update to fail with exit code 100 even though none of our dependencies come from those repos. Remove the Microsoft source list files and their signing keys before running apt-get update so transient Microsoft CDN outages don't break unrelated CI jobs. Co-authored-by: Will Pfleger <pfleger.will@gmail.com> Signed-off-by: Will Pfleger <pfleger.will@gmail.com>
856 lines
35 KiB
YAML
856 lines
35 KiB
YAML
name: Release
|
|
|
|
on:
|
|
push:
|
|
tags:
|
|
- 'v[0-9]*'
|
|
workflow_dispatch:
|
|
inputs:
|
|
version:
|
|
description: "Semver version (e.g. 0.4.0) — only for manual runs"
|
|
required: true
|
|
ref:
|
|
description: "Branch, tag, or SHA to build — only for manual runs"
|
|
default: main
|
|
required: true
|
|
|
|
jobs:
|
|
# Shared setup: determine the version and create the release objects all four
|
|
# platform jobs upload into. Runs on ubuntu with no OIDC so a branch
|
|
# workflow_dispatch can exercise the platform builds without the codesign
|
|
# AssumeRole wall that gates the arm64 job.
|
|
setup:
|
|
name: Setup
|
|
if: github.repository == 'block/buzz'
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: write
|
|
outputs:
|
|
version: ${{ steps.version.outputs.version }}
|
|
steps:
|
|
- name: Determine version
|
|
id: version
|
|
env:
|
|
EVENT_NAME: ${{ github.event_name }}
|
|
INPUT_VERSION: ${{ inputs.version }}
|
|
run: |
|
|
if [[ "$EVENT_NAME" == "push" ]]; then
|
|
VERSION="${GITHUB_REF_NAME#v}"
|
|
else
|
|
VERSION="$INPUT_VERSION"
|
|
fi
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Validate version
|
|
env:
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
run: |
|
|
if ! echo "$VERSION" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$'; then
|
|
echo "::error::Invalid version '$VERSION'. Expected semver (e.g. 0.4.0 or 1.0.0-beta.1)"
|
|
exit 1
|
|
fi
|
|
|
|
# Checkout at the same ref the platform jobs use so git rev-parse HEAD
|
|
# pins the release to the right commit via --target.
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
|
persist-credentials: false
|
|
|
|
- name: Create versioned GitHub release
|
|
env:
|
|
VERSION: ${{ steps.version.outputs.version }}
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
RELEASE_SHA=$(git rev-parse HEAD)
|
|
NOTES=""
|
|
if [[ -f CHANGELOG.md ]]; then
|
|
NOTES=$(awk "/^## v${VERSION}\$/{found=1; next} found && /^## v/{exit} found && !/^\$/" CHANGELOG.md)
|
|
fi
|
|
if [[ -z "$NOTES" ]]; then
|
|
NOTES="Buzz Desktop v${VERSION}"
|
|
fi
|
|
PRERELEASE_FLAGS=()
|
|
if [[ "$VERSION" =~ -(test|alpha|beta|rc)([.-]|$) ]]; then
|
|
PRERELEASE_FLAGS=(--prerelease --latest=false)
|
|
fi
|
|
gh release create "v${VERSION}" \
|
|
--target "$RELEASE_SHA" \
|
|
--title "Buzz Desktop v${VERSION}" \
|
|
--notes "$NOTES" \
|
|
"${PRERELEASE_FLAGS[@]}"
|
|
|
|
- name: Create rolling auto-update release
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
gh release create buzz-desktop-latest \
|
|
--prerelease \
|
|
--title "Buzz Desktop Auto-Update" \
|
|
--notes "Rolling release for the Tauri auto-updater. Do not download manually — use the versioned release instead." \
|
|
2>/dev/null || true
|
|
|
|
release:
|
|
name: Release
|
|
if: github.repository == 'block/buzz'
|
|
runs-on: macos-latest
|
|
needs: setup
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: write
|
|
id-token: write # required by block/apple-codesign-action for OIDC
|
|
outputs:
|
|
archive_name: ${{ steps.artifacts.outputs.archive_name }}
|
|
sig: ${{ steps.read-sig.outputs.sig }}
|
|
env:
|
|
VERSION: ${{ needs.setup.outputs.version }}
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
|
persist-credentials: false
|
|
|
|
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
|
|
|
|
- name: Install desktop dependencies
|
|
run: just desktop-install-ci
|
|
|
|
- name: Patch version
|
|
run: |
|
|
cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
|
|
cd src-tauri && cargo update --workspace
|
|
|
|
- name: Generate release config
|
|
run: cd desktop && node scripts/build-release-config.mjs
|
|
env:
|
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.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
|
|
|
|
# Mesh rev derived from Cargo.lock (no lockstep edit on dep bump); cache key tracks it.
|
|
- name: Resolve mesh-llm rev
|
|
id: mesh_rev
|
|
run: |
|
|
set -euo pipefail
|
|
REV=$(grep -oE 'mesh-llm\.git\?rev=[0-9a-f]{40}' Cargo.lock | head -1 | grep -oE '[0-9a-f]{40}')
|
|
[[ -n "$REV" ]] || { echo "::error::could not resolve mesh-llm rev from Cargo.lock"; exit 1; }
|
|
echo "rev=$REV" >> "$GITHUB_OUTPUT"
|
|
echo "short=${REV:0:7}" >> "$GITHUB_OUTPUT"
|
|
- name: Restore mesh llama build cache
|
|
id: llama_cache
|
|
uses: actions/cache/restore@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
|
with:
|
|
path: ${{ github.workspace }}/.cache/mesh-llama
|
|
key: mesh-llama-${{ runner.os }}-metal-${{ steps.mesh_rev.outputs.rev }}
|
|
- name: Build mesh llama native libraries
|
|
if: steps.llama_cache.outputs.cache-hit != 'true'
|
|
env:
|
|
MESH_REV_SHORT: ${{ steps.mesh_rev.outputs.short }}
|
|
run: |
|
|
set -euo pipefail
|
|
cargo fetch --manifest-path desktop/src-tauri/Cargo.toml
|
|
SHORT="$MESH_REV_SHORT"
|
|
MESH_ROOT=$(find "${CARGO_HOME:-$HOME/.cargo}/git/checkouts" -path "*/$SHORT" -type d -name "$SHORT" | head -1)
|
|
if [[ -z "$MESH_ROOT" ]]; then
|
|
echo "::error::mesh-llm checkout for $SHORT not found after cargo fetch"
|
|
exit 1
|
|
fi
|
|
export LLAMA_STAGE_BACKEND=metal
|
|
export LLAMA_STAGE_BUILD_DIR="$GITHUB_WORKSPACE/.cache/mesh-llama/build-stage-abi-metal"
|
|
export CMAKE_OSX_DEPLOYMENT_TARGET=10.15
|
|
"$MESH_ROOT/scripts/prepare-llama.sh" pinned
|
|
"$MESH_ROOT/scripts/build-llama.sh" -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15
|
|
- name: Save mesh llama build cache
|
|
if: steps.llama_cache.outputs.cache-hit != 'true'
|
|
uses: actions/cache/save@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5
|
|
with:
|
|
path: ${{ github.workspace }}/.cache/mesh-llama
|
|
key: mesh-llama-${{ runner.os }}-metal-${{ steps.mesh_rev.outputs.rev }}
|
|
|
|
- name: Build unsigned Tauri app
|
|
run: cd desktop && pnpm tauri build --verbose --no-sign --features mesh-llm --config src-tauri/tauri.release.conf.json
|
|
env:
|
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
|
MACOSX_DEPLOYMENT_TARGET: "10.15"
|
|
CMAKE_OSX_DEPLOYMENT_TARGET: "10.15"
|
|
LLAMA_STAGE_BACKEND: metal
|
|
LLAMA_STAGE_BUILD_DIR: ${{ github.workspace }}/.cache/mesh-llama/build-stage-abi-metal
|
|
SKIPPY_LLAMA_AUTO_BUILD: "0"
|
|
|
|
- name: Locate unsigned DMG
|
|
id: unsigned
|
|
run: |
|
|
BUNDLE_DIR="desktop/src-tauri/target/release/bundle"
|
|
DMG=$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -type f | head -1)
|
|
if [[ -z "$DMG" ]]; then
|
|
echo "::error::No DMG found in $BUNDLE_DIR/dmg"
|
|
exit 1
|
|
fi
|
|
echo "dmg=$DMG" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Codesign and Notarize
|
|
id: codesign
|
|
uses: block/apple-codesign-action@679535d1ab7c5a7c18e6f9afcba3464512cc3dde # v1.1.0
|
|
with:
|
|
osx-codesign-role: ${{ secrets.OSX_CODESIGN_ROLE }}
|
|
codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }}
|
|
unsigned-artifact-path: ${{ steps.unsigned.outputs.dmg }}
|
|
entitlements-plist-path: desktop/src-tauri/Entitlements.plist
|
|
artifact-name: buzz-${{ github.sha }}-${{ github.run_id }}-arm64
|
|
|
|
- name: Replace DMG and rebuild updater archive
|
|
env:
|
|
SIGNED_DMG: ${{ steps.codesign.outputs.signed-dmg-path }}
|
|
SIGNED_APP_ZIP: ${{ steps.codesign.outputs.signed-artifact-path }}
|
|
UNSIGNED_DMG: ${{ steps.unsigned.outputs.dmg }}
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
BUNDLE_DIR="desktop/src-tauri/target/release/bundle"
|
|
APP_DIR="${BUNDLE_DIR}/macos"
|
|
|
|
# Replace unsigned DMG with the signed/notarized one.
|
|
cp "$SIGNED_DMG" "$UNSIGNED_DMG"
|
|
|
|
# Swap the unsigned .app for the signed .app extracted from the action's zip.
|
|
EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract"
|
|
rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR"
|
|
ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR"
|
|
rm -rf "${APP_DIR}/Buzz.app"
|
|
cp -R "${EXTRACT_DIR}/Buzz.app" "${APP_DIR}/Buzz.app"
|
|
|
|
# Rebuild the updater archive from the signed .app and re-sign it with the Tauri updater key.
|
|
rm -f "${APP_DIR}/Buzz.app.tar.gz" "${APP_DIR}/Buzz.app.tar.gz.sig"
|
|
(cd "$APP_DIR" && tar -czf Buzz.app.tar.gz Buzz.app)
|
|
TARBALL_ABS="$(pwd)/${APP_DIR}/Buzz.app.tar.gz"
|
|
(cd desktop && pnpm tauri signer sign "$TARBALL_ABS")
|
|
|
|
- name: Verify code signature
|
|
run: |
|
|
codesign --verify --deep --strict --verbose=2 \
|
|
desktop/src-tauri/target/release/bundle/macos/Buzz.app
|
|
spctl --assess --type execute --verbose=4 \
|
|
desktop/src-tauri/target/release/bundle/macos/Buzz.app
|
|
|
|
- name: Locate build artifacts
|
|
id: artifacts
|
|
run: |
|
|
BUNDLE_DIR="desktop/src-tauri/target/release/bundle"
|
|
|
|
# Find the DMG (Tauri names it Buzz_<version>_<arch>.dmg)
|
|
DMG=$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -type f | head -1)
|
|
if [[ -z "$DMG" ]]; then
|
|
echo "::error::No DMG found in $BUNDLE_DIR/dmg"
|
|
exit 1
|
|
fi
|
|
echo "dmg=$DMG" >> "$GITHUB_OUTPUT"
|
|
|
|
# Find the updater .tar.gz and .sig
|
|
ARCHIVE=$(find "$BUNDLE_DIR/macos" -name '*.tar.gz' ! -name '*.sig' -type f | head -1)
|
|
SIG="${ARCHIVE}.sig"
|
|
if [[ -z "$ARCHIVE" || ! -f "$SIG" ]]; then
|
|
echo "::error::Updater archive or signature not found in $BUNDLE_DIR/macos"
|
|
exit 1
|
|
fi
|
|
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
|
|
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
|
|
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Read updater signature
|
|
id: read-sig
|
|
run: echo "sig=$(cat "$SIG_PATH")" >> "$GITHUB_OUTPUT"
|
|
env:
|
|
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
|
|
|
- name: Upload arm64 DMG to versioned GitHub release
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
DMG_PATH: ${{ steps.artifacts.outputs.dmg }}
|
|
run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
|
|
|
|
- name: Upload updater archive to rolling release
|
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version))
|
|
run: |
|
|
gh release upload buzz-desktop-latest \
|
|
"$ARCHIVE_PATH" \
|
|
"$SIG_PATH" \
|
|
--clobber
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
|
|
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
|
|
|
release-macos-x64:
|
|
name: Release macOS (Intel)
|
|
if: github.repository == 'block/buzz'
|
|
runs-on: macos-latest
|
|
needs: setup
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: write
|
|
id-token: write # required by block/apple-codesign-action for OIDC
|
|
outputs:
|
|
archive_name: ${{ steps.artifacts.outputs.archive_name }}
|
|
sig: ${{ steps.read-sig.outputs.sig }}
|
|
env:
|
|
VERSION: ${{ needs.setup.outputs.version }}
|
|
TARGET: x86_64-apple-darwin
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
|
persist-credentials: false
|
|
|
|
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
|
|
|
|
- name: Install desktop dependencies
|
|
run: just desktop-install-ci
|
|
|
|
- name: Add Rust target
|
|
run: rustup target add "$TARGET"
|
|
|
|
- name: Patch version
|
|
run: |
|
|
cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
|
|
cd src-tauri && cargo update --workspace
|
|
|
|
- name: Generate release config
|
|
run: cd desktop && node scripts/build-release-config.mjs
|
|
env:
|
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
|
|
|
|
- name: Build sidecars
|
|
run: |
|
|
cargo build --release --target "$TARGET" -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
|
|
./scripts/bundle-sidecars.sh "$TARGET"
|
|
|
|
- name: Build unsigned Tauri app
|
|
run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --config src-tauri/tauri.release.conf.json
|
|
env:
|
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
|
MACOSX_DEPLOYMENT_TARGET: "10.15"
|
|
CMAKE_OSX_DEPLOYMENT_TARGET: "10.15"
|
|
|
|
- name: Locate unsigned DMG
|
|
id: unsigned
|
|
run: |
|
|
BUNDLE_DIR="desktop/src-tauri/target/${TARGET}/release/bundle"
|
|
DMG=$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -type f | head -1)
|
|
if [[ -z "$DMG" ]]; then
|
|
echo "::error::No DMG found in $BUNDLE_DIR/dmg"
|
|
exit 1
|
|
fi
|
|
echo "dmg=$DMG" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Codesign and Notarize
|
|
id: codesign
|
|
uses: block/apple-codesign-action@679535d1ab7c5a7c18e6f9afcba3464512cc3dde # v1.1.0
|
|
with:
|
|
osx-codesign-role: ${{ secrets.OSX_CODESIGN_ROLE }}
|
|
codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }}
|
|
unsigned-artifact-path: ${{ steps.unsigned.outputs.dmg }}
|
|
entitlements-plist-path: desktop/src-tauri/Entitlements.plist
|
|
artifact-name: buzz-${{ github.sha }}-${{ github.run_id }}-x64
|
|
|
|
- name: Replace DMG and rebuild updater archive
|
|
env:
|
|
SIGNED_DMG: ${{ steps.codesign.outputs.signed-dmg-path }}
|
|
SIGNED_APP_ZIP: ${{ steps.codesign.outputs.signed-artifact-path }}
|
|
UNSIGNED_DMG: ${{ steps.unsigned.outputs.dmg }}
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
run: |
|
|
set -euo pipefail
|
|
APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos"
|
|
|
|
# Replace the unsigned DMG with the signed/notarized one.
|
|
cp "$SIGNED_DMG" "$UNSIGNED_DMG"
|
|
|
|
# Swap the unsigned .app for the signed .app from the action's zip.
|
|
EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract-x64"
|
|
rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR"
|
|
ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR"
|
|
rm -rf "${APP_DIR}/Buzz.app"
|
|
cp -R "${EXTRACT_DIR}/Buzz.app" "${APP_DIR}/Buzz.app"
|
|
|
|
# Rebuild the updater archive from the signed .app and re-sign with the Tauri updater key.
|
|
rm -f "${APP_DIR}/Buzz.app.tar.gz" "${APP_DIR}/Buzz.app.tar.gz.sig"
|
|
(cd "$APP_DIR" && tar -czf Buzz.app.tar.gz Buzz.app)
|
|
TARBALL_ABS="$(pwd)/${APP_DIR}/Buzz.app.tar.gz"
|
|
(cd desktop && pnpm tauri signer sign "$TARBALL_ABS")
|
|
|
|
- name: Verify code signature
|
|
run: |
|
|
APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos/Buzz.app"
|
|
codesign --verify --deep --strict --verbose=2 "$APP_DIR"
|
|
spctl --assess --type execute --verbose=4 "$APP_DIR"
|
|
|
|
- name: Locate updater archive
|
|
id: artifacts
|
|
run: |
|
|
BUNDLE_DIR="desktop/src-tauri/target/${TARGET}/release/bundle"
|
|
|
|
ARCHIVE=$(find "$BUNDLE_DIR/macos" -name '*.tar.gz' ! -name '*.sig' -type f | head -1)
|
|
SIG="${ARCHIVE}.sig"
|
|
if [[ -z "$ARCHIVE" || ! -f "$SIG" ]]; then
|
|
echo "::error::Updater archive or signature not found in $BUNDLE_DIR/macos"
|
|
exit 1
|
|
fi
|
|
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
|
|
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
|
|
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Read updater signature
|
|
id: read-sig
|
|
run: echo "sig=$(cat "$SIG_PATH")" >> "$GITHUB_OUTPUT"
|
|
env:
|
|
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
|
|
|
- name: Upload Intel DMG to versioned GitHub release
|
|
run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
DMG_PATH: ${{ steps.unsigned.outputs.dmg }}
|
|
|
|
- name: Upload updater archive to rolling release
|
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version))
|
|
run: |
|
|
gh release upload buzz-desktop-latest \
|
|
"$ARCHIVE_PATH" \
|
|
"$SIG_PATH" \
|
|
--clobber
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
|
|
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
|
|
|
release-linux:
|
|
name: Release Linux
|
|
if: github.repository == 'block/buzz'
|
|
runs-on: ubuntu-latest
|
|
needs: setup
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: write
|
|
outputs:
|
|
archive_name: ${{ steps.linux-artifacts.outputs.archive_name }}
|
|
sig: ${{ steps.read-sig.outputs.sig }}
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
|
persist-credentials: false
|
|
|
|
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
|
|
|
|
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
|
with:
|
|
workspaces: desktop/src-tauri
|
|
lookup-only: true
|
|
|
|
- name: Remove broken Microsoft apt sources
|
|
run: |
|
|
sudo rm -f /etc/apt/sources.list.d/microsoft-prod.list \
|
|
/etc/apt/sources.list.d/azure-cli.list \
|
|
/etc/apt/sources.list.d/packages.microsoft.com.list
|
|
sudo rm -f /etc/apt/trusted.gpg.d/microsoft.gpg \
|
|
/etc/apt/trusted.gpg.d/microsoft-prod.gpg
|
|
- name: Install Tauri dependencies (Linux)
|
|
env:
|
|
DEBIAN_FRONTEND: noninteractive
|
|
run: |
|
|
sudo apt-get update \
|
|
-o Acquire::Retries=3 \
|
|
-o Acquire::http::Timeout=30 \
|
|
-o Acquire::https::Timeout=30
|
|
sudo 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 \
|
|
curl \
|
|
file \
|
|
libasound2-dev \
|
|
libayatana-appindicator3-dev \
|
|
libgtk-3-dev \
|
|
librsvg2-dev \
|
|
libssl-dev \
|
|
libwebkit2gtk-4.1-dev \
|
|
libxdo-dev \
|
|
patchelf \
|
|
wget
|
|
|
|
- name: Install desktop dependencies
|
|
run: just desktop-install-ci
|
|
|
|
- name: Patch version
|
|
env:
|
|
VERSION: ${{ needs.setup.outputs.version }}
|
|
run: |
|
|
cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
|
|
cd src-tauri && cargo update --workspace
|
|
|
|
- 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: Generate release config
|
|
run: cd desktop && node scripts/build-release-config.mjs
|
|
env:
|
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
|
|
|
|
- name: Build Linux Tauri app
|
|
run: cd desktop && pnpm tauri build --verbose --ci --bundles deb,appimage --config src-tauri/tauri.release.conf.json
|
|
env:
|
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
|
|
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
|
|
- name: Locate Linux build artifacts
|
|
id: linux-artifacts
|
|
run: |
|
|
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"
|
|
|
|
# Updater archive: Tauri 2.11+ with createUpdaterArtifacts signs the
|
|
# AppImage directly (*.AppImage + *.AppImage.sig). Earlier versions
|
|
# wrapped it in a tar.gz. Try the new format first, fall back to legacy.
|
|
ARCHIVE=$(find "$BUNDLE_DIR/appimage" -name '*.AppImage.tar.gz' ! -name '*.sig' -type f | head -1)
|
|
if [[ -n "$ARCHIVE" ]]; then
|
|
SIG="${ARCHIVE}.sig"
|
|
else
|
|
ARCHIVE="$APPIMAGE"
|
|
SIG="${APPIMAGE}.sig"
|
|
fi
|
|
if [[ -z "$ARCHIVE" || ! -f "$SIG" ]]; then
|
|
echo "::error::AppImage updater archive or signature not found in $BUNDLE_DIR/appimage"
|
|
exit 1
|
|
fi
|
|
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
|
|
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
|
|
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Read updater signature
|
|
id: read-sig
|
|
run: echo "sig=$(cat "$SIG_PATH")" >> "$GITHUB_OUTPUT"
|
|
env:
|
|
SIG_PATH: ${{ steps.linux-artifacts.outputs.sig }}
|
|
|
|
# NOTE: .deb is NOT auto-updatable (Tauri updater constraint — only AppImage supports it on Linux)
|
|
- name: Upload Linux artifacts to versioned GitHub release
|
|
env:
|
|
VERSION: ${{ needs.setup.outputs.version }}
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
DEB_PATH: ${{ steps.linux-artifacts.outputs.deb }}
|
|
APPIMAGE_PATH: ${{ steps.linux-artifacts.outputs.appimage }}
|
|
run: |
|
|
gh release upload "v$VERSION" \
|
|
"$DEB_PATH" \
|
|
"$APPIMAGE_PATH" \
|
|
--clobber
|
|
|
|
- name: Upload updater archive to rolling release
|
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version))
|
|
run: |
|
|
gh release upload buzz-desktop-latest \
|
|
"$ARCHIVE_PATH" \
|
|
"$SIG_PATH" \
|
|
--clobber
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
ARCHIVE_PATH: ${{ steps.linux-artifacts.outputs.archive }}
|
|
SIG_PATH: ${{ steps.linux-artifacts.outputs.sig }}
|
|
|
|
release-windows:
|
|
name: Release Windows
|
|
runs-on: windows-latest
|
|
needs: setup
|
|
timeout-minutes: 60
|
|
permissions:
|
|
contents: write
|
|
outputs:
|
|
archive_name: ${{ steps.artifacts.outputs.archive_name }}
|
|
sig: ${{ steps.read-sig.outputs.sig }}
|
|
env:
|
|
VERSION: ${{ needs.setup.outputs.version }}
|
|
TARGET: x86_64-pc-windows-msvc
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
|
persist-credentials: false
|
|
|
|
- uses: dtolnay/rust-toolchain@e081816240890017053eacbb1bdf337761dc5582 # 1.95.0
|
|
with:
|
|
targets: ${{ env.TARGET }}
|
|
|
|
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
with:
|
|
node-version: 24.14.0
|
|
# Disable dependency caching: a writable cache in this release workflow
|
|
# (contents: write, feeds a signed installer) is a poisoning vector. pnpm
|
|
# install runs uncached below.
|
|
package-manager-cache: false
|
|
|
|
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
|
|
with:
|
|
version: 11.4.0
|
|
|
|
- name: Install desktop dependencies
|
|
shell: bash
|
|
run: pnpm install --frozen-lockfile
|
|
|
|
- name: Patch version
|
|
shell: bash
|
|
run: |
|
|
cd desktop && node scripts/set-version-from-tag.mjs "$VERSION"
|
|
cd src-tauri && cargo update --workspace
|
|
|
|
- name: Generate release config
|
|
shell: bash
|
|
run: cd desktop && node scripts/build-release-config.mjs
|
|
env:
|
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
|
|
|
|
- name: Build sidecars
|
|
shell: bash
|
|
run: |
|
|
cargo build --release --target "$TARGET" -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
|
|
./scripts/bundle-sidecars.sh "$TARGET"
|
|
|
|
- name: Build Windows NSIS installer (unsigned)
|
|
shell: bash
|
|
run: cd desktop && pnpm tauri build --verbose --target "$TARGET" --bundles nsis --config src-tauri/tauri.release.conf.json
|
|
env:
|
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
|
|
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
|
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
|
|
|
- name: Locate Windows build artifacts
|
|
id: artifacts
|
|
shell: bash
|
|
run: |
|
|
BUNDLE_DIR="desktop/src-tauri/target/${TARGET}/release/bundle"
|
|
|
|
# Find the NSIS installer .exe
|
|
EXE=$(find "$BUNDLE_DIR/nsis" -name '*.exe' -type f | head -1)
|
|
if [[ -z "$EXE" ]]; then
|
|
echo "::error::No NSIS installer found in $BUNDLE_DIR/nsis"
|
|
exit 1
|
|
fi
|
|
|
|
# Tauri 2.x with createUpdaterArtifacts: true signs the NSIS
|
|
# installer in place (<name>-setup.exe + <name>-setup.exe.sig).
|
|
SIG="${EXE}.sig"
|
|
if [[ ! -f "$SIG" ]]; then
|
|
echo "::error::NSIS installer signature not found: $SIG"
|
|
exit 1
|
|
fi
|
|
|
|
# Rename with _alpha-unsigned marker, keeping the detached signature
|
|
# in lockstep so latest.json matches the uploaded updater artifact.
|
|
EXE_DIR=$(dirname "$EXE")
|
|
EXE_BASE=$(basename "$EXE" .exe)
|
|
MARKED_EXE="${EXE_DIR}/${EXE_BASE}_alpha-unsigned.exe"
|
|
MARKED_SIG="${MARKED_EXE}.sig"
|
|
mv "$EXE" "$MARKED_EXE"
|
|
mv "$SIG" "$MARKED_SIG"
|
|
echo "exe=$MARKED_EXE" >> "$GITHUB_OUTPUT"
|
|
echo "archive=$MARKED_EXE" >> "$GITHUB_OUTPUT"
|
|
echo "archive_name=$(basename "$MARKED_EXE")" >> "$GITHUB_OUTPUT"
|
|
echo "sig=$MARKED_SIG" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Read updater signature
|
|
id: read-sig
|
|
shell: bash
|
|
run: echo "sig=$(cat "$SIG_PATH")" >> "$GITHUB_OUTPUT"
|
|
env:
|
|
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
|
|
|
- name: Upload Windows installer to versioned GitHub release
|
|
shell: bash
|
|
run: gh release upload "v${VERSION}" "$EXE_PATH" --clobber
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
EXE_PATH: ${{ steps.artifacts.outputs.exe }}
|
|
|
|
- name: Upload updater archive to rolling release
|
|
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version))
|
|
shell: bash
|
|
run: |
|
|
gh release upload buzz-desktop-latest \
|
|
"$ARCHIVE_PATH" \
|
|
"$SIG_PATH" \
|
|
--clobber
|
|
env:
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
|
|
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
|
|
|
assemble-manifest:
|
|
name: Assemble multi-platform latest.json
|
|
# Only tag-backed releases assemble latest.json. Branch workflow_dispatch
|
|
# runs skip rolling-release uploads, so their archive URLs never exist.
|
|
if: |
|
|
always() &&
|
|
needs.setup.result == 'success' &&
|
|
(github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.ref == format('v{0}', inputs.version)))
|
|
runs-on: ubuntu-latest
|
|
needs: [setup, release, release-macos-x64, release-linux, release-windows]
|
|
timeout-minutes: 10
|
|
permissions:
|
|
contents: write
|
|
env:
|
|
VERSION: ${{ needs.setup.outputs.version }}
|
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
steps:
|
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
|
with:
|
|
ref: ${{ github.event_name == 'push' && github.ref || inputs.ref }}
|
|
persist-credentials: false
|
|
|
|
- name: Write signature files
|
|
env:
|
|
RESULT_ARM64: ${{ needs.release.result }}
|
|
RESULT_X64: ${{ needs.release-macos-x64.result }}
|
|
RESULT_LINUX: ${{ needs.release-linux.result }}
|
|
RESULT_WIN: ${{ needs.release-windows.result }}
|
|
SIG_ARM64: ${{ needs.release.outputs.sig }}
|
|
SIG_X64: ${{ needs.release-macos-x64.outputs.sig }}
|
|
SIG_LINUX: ${{ needs.release-linux.outputs.sig }}
|
|
SIG_WIN: ${{ needs.release-windows.outputs.sig }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p /tmp/sigs
|
|
|
|
write_sig() {
|
|
local result="$1" platform="$2" sig="$3"
|
|
if [[ "$result" == "success" ]]; then
|
|
[[ -n "$sig" ]] || { echo "::error::Missing signature for successful platform: $platform"; exit 1; }
|
|
printf '%s' "$sig" > "/tmp/sigs/${platform}.sig"
|
|
fi
|
|
}
|
|
|
|
write_sig "$RESULT_ARM64" darwin-aarch64 "$SIG_ARM64"
|
|
write_sig "$RESULT_X64" darwin-x86_64 "$SIG_X64"
|
|
write_sig "$RESULT_LINUX" linux-x86_64 "$SIG_LINUX"
|
|
write_sig "$RESULT_WIN" windows-x86_64 "$SIG_WIN"
|
|
|
|
- name: Verify archive URLs are accessible
|
|
env:
|
|
RESULT_ARM64: ${{ needs.release.result }}
|
|
RESULT_X64: ${{ needs.release-macos-x64.result }}
|
|
RESULT_LINUX: ${{ needs.release-linux.result }}
|
|
RESULT_WIN: ${{ needs.release-windows.result }}
|
|
ARCHIVE_ARM64: ${{ needs.release.outputs.archive_name }}
|
|
ARCHIVE_X64: ${{ needs.release-macos-x64.outputs.archive_name }}
|
|
ARCHIVE_LINUX: ${{ needs.release-linux.outputs.archive_name }}
|
|
ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest"
|
|
ARCHIVES=()
|
|
|
|
add_archive() {
|
|
local result="$1" platform="$2" archive="$3"
|
|
if [[ "$result" == "success" ]]; then
|
|
[[ -n "$archive" ]] || { echo "::error::Missing archive name for successful platform: $platform"; exit 1; }
|
|
ARCHIVES+=("$archive")
|
|
fi
|
|
}
|
|
|
|
add_archive "$RESULT_ARM64" darwin-aarch64 "$ARCHIVE_ARM64"
|
|
add_archive "$RESULT_X64" darwin-x86_64 "$ARCHIVE_X64"
|
|
add_archive "$RESULT_LINUX" linux-x86_64 "$ARCHIVE_LINUX"
|
|
add_archive "$RESULT_WIN" windows-x86_64 "$ARCHIVE_WIN"
|
|
|
|
for name in "${ARCHIVES[@]}"; do
|
|
echo "Checking $BASE/$name ..."
|
|
success=false
|
|
for attempt in 1 2 3; do
|
|
if curl -fsI "$BASE/$name" > /dev/null 2>&1; then
|
|
success=true
|
|
break
|
|
fi
|
|
echo "Attempt $attempt failed for $name, retrying in 10s..."
|
|
sleep 10
|
|
done
|
|
if [ "$success" != "true" ]; then
|
|
echo "::error::Archive not accessible after 3 attempts: $BASE/$name"
|
|
exit 1
|
|
fi
|
|
done
|
|
echo "All archive URLs verified."
|
|
|
|
- name: Generate unified latest.json
|
|
env:
|
|
RESULT_ARM64: ${{ needs.release.result }}
|
|
RESULT_X64: ${{ needs.release-macos-x64.result }}
|
|
RESULT_LINUX: ${{ needs.release-linux.result }}
|
|
RESULT_WIN: ${{ needs.release-windows.result }}
|
|
ARCHIVE_ARM64: ${{ needs.release.outputs.archive_name }}
|
|
ARCHIVE_X64: ${{ needs.release-macos-x64.outputs.archive_name }}
|
|
ARCHIVE_LINUX: ${{ needs.release-linux.outputs.archive_name }}
|
|
ARCHIVE_WIN: ${{ needs.release-windows.outputs.archive_name }}
|
|
run: |
|
|
set -euo pipefail
|
|
BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest"
|
|
TRIPLES=()
|
|
|
|
add_triple() {
|
|
local result="$1" platform="$2" archive="$3"
|
|
if [[ "$result" == "success" ]]; then
|
|
[[ -n "$archive" ]] || { echo "::error::Missing archive name for successful platform: $platform"; exit 1; }
|
|
TRIPLES+=("${platform}:/tmp/sigs/${platform}.sig:${BASE}/${archive}")
|
|
fi
|
|
}
|
|
|
|
add_triple "$RESULT_ARM64" darwin-aarch64 "$ARCHIVE_ARM64"
|
|
add_triple "$RESULT_X64" darwin-x86_64 "$ARCHIVE_X64"
|
|
add_triple "$RESULT_LINUX" linux-x86_64 "$ARCHIVE_LINUX"
|
|
add_triple "$RESULT_WIN" windows-x86_64 "$ARCHIVE_WIN"
|
|
|
|
[ "${#TRIPLES[@]}" -ge 3 ] || { echo "::error::too few platforms (${#TRIPLES[@]})"; exit 1; }
|
|
bash desktop/scripts/generate-oss-latest-json.sh "$VERSION" "${TRIPLES[@]}" > latest.json
|
|
cat latest.json
|
|
|
|
- name: Upload latest.json to rolling release
|
|
run: |
|
|
gh release upload buzz-desktop-latest latest.json --clobber
|