feat: import Chinese-localized Buzz source snapshot
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Docker image / Build (linux/amd64) (push) Has been cancelled
Docker image / Build (linux/arm64) (push) Has been cancelled
Docker image / Merge release multi-arch manifest (push) Has been cancelled
Docker image / Merge debug multi-arch manifest (push) Has been cancelled
Docker image / Build public push gateway (linux/amd64) (push) Has been cancelled
Docker image / Build public push gateway (linux/arm64) (push) Has been cancelled
Docker image / Publish public push gateway image (push) Has been cancelled
Sprig image / Build (linux/amd64) (push) Has been cancelled
Sprig image / Build (linux/arm64) (push) Has been cancelled
Sprig image / Merge multi-arch manifest (push) Has been cancelled
Harbor Buzz Orchestra / Python tests and lint (push) Has been cancelled
CI / Detect Changed Paths (push) Has been cancelled
CI / Rust Lint (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Desktop Core (push) Has been cancelled
CI / Desktop Smoke E2E (1) (push) Has been cancelled
CI / Desktop Smoke E2E (2) (push) Has been cancelled
CI / Desktop Smoke E2E (3) (push) Has been cancelled
CI / Desktop Smoke E2E (4) (push) Has been cancelled
CI / Desktop (push) Has been cancelled
CI / Desktop E2E Relay (push) Has been cancelled
CI / Desktop E2E Integration (1/2) (push) Has been cancelled
CI / Desktop E2E Integration (2/2) (push) Has been cancelled
CI / Desktop E2E Integration (push) Has been cancelled
CI / Backend Integration (relay e2e) (push) Has been cancelled
CI / Relay E2E (push) Has been cancelled
CI / Web (push) Has been cancelled
CI / Mobile (push) Has been cancelled
CI / Security (push) Has been cancelled
CI / Dead Token Reference Guard (push) Has been cancelled
CI / Server Cross-Compile (aarch64-unknown-linux-musl) (push) Has been cancelled
CI / Server Cross-Compile (x86_64-unknown-linux-musl) (push) Has been cancelled
CI / Windows Rust (x86_64-pc-windows-msvc) (push) Has been cancelled
CI / Desktop Build (macOS) (push) Has been cancelled
helm chart / lint + unittest + render matrix (push) Has been cancelled
helm chart / install on kind (gated) (push) Has been cancelled
helm chart / publish chart to GHCR (push) Has been cancelled
Mesh Lifecycle / Relay-Driven Mesh Lifecycle Smoke (push) Has been cancelled
Sprig / Build (aarch64-unknown-linux-musl) (push) Has been cancelled
Sprig / Build (x86_64-unknown-linux-musl) (push) Has been cancelled
Sprig / Publish rolling release (push) Has been cancelled
Sprig / Publish tagged release (push) Has been cancelled
Signed-off-by: cls_宁波本机 <908705107@qq.com>
This commit is contained in:
@@ -0,0 +1,952 @@
|
||||
name: Release
|
||||
|
||||
concurrency:
|
||||
group: desktop-release-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- 'desktop-v[0-9]*'
|
||||
|
||||
jobs:
|
||||
# Shared setup: verify the immutable release tag, determine the version, and
|
||||
# create the release objects all four platform jobs upload into.
|
||||
setup:
|
||||
name: Setup
|
||||
if: github.repository == 'block/buzz'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
permissions:
|
||||
contents: read
|
||||
outputs:
|
||||
version: ${{ steps.version.outputs.version }}
|
||||
source_sha: ${{ steps.source.outputs.source_sha }}
|
||||
steps:
|
||||
- name: Determine version
|
||||
id: version
|
||||
run: echo "version=${GITHUB_REF_NAME#desktop-v}" >> "$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
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
id: source
|
||||
env:
|
||||
VERSION: ${{ steps.version.outputs.version }}
|
||||
run: |
|
||||
scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
echo "source_sha=$(git rev-parse 'HEAD^{commit}')" >> "$GITHUB_OUTPUT"
|
||||
|
||||
release:
|
||||
name: Release
|
||||
if: github.repository == 'block/buzz'
|
||||
runs-on: macos-latest
|
||||
needs: setup
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
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.0.3
|
||||
with:
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # 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-backend-kubernetes -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=$(python3 -c 'import tomllib; d=tomllib.load(open("Cargo.lock", "rb")); p=next(p for p in d["package"] if p["name"] == "mesh-llm-sdk"); print(p["source"].rsplit("#", 1)[1])')
|
||||
[[ -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@caa296126883cff596d87d8935842f9db880ef25 # 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@caa296126883cff596d87d8935842f9db880ef25 # 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"
|
||||
TAURI_BUNDLER_DMG_IGNORE_CI: "true"
|
||||
|
||||
- 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: Set DMG Finder label text size
|
||||
env:
|
||||
DMG_PATH: ${{ steps.unsigned.outputs.dmg }}
|
||||
run: desktop/scripts/set-dmg-finder-text-size.sh "$DMG_PATH" 14
|
||||
|
||||
# mdx-ios-codesign-helper discovers this file by its exact lowercase basename.
|
||||
- name: Stage signing entitlements
|
||||
run: cp desktop/src-tauri/Entitlements.plist "${RUNNER_TEMP}/entitlements.plist"
|
||||
|
||||
- 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: ${{ runner.temp }}/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
|
||||
desktop/scripts/verify-macos-entitlements.sh \
|
||||
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. Give each architecture a unique
|
||||
# release basename before artifacts are merged by the final writer.
|
||||
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
|
||||
RENAMED="$(dirname "$ARCHIVE")/Buzz_${VERSION}_aarch64.app.tar.gz"
|
||||
mv "$ARCHIVE" "$RENAMED"
|
||||
mv "$SIG" "${RENAMED}.sig"
|
||||
ARCHIVE="$RENAMED"
|
||||
SIG="${RENAMED}.sig"
|
||||
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: Stage Apple Silicon release artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: desktop-release-macos-arm64
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
${{ steps.artifacts.outputs.dmg }}
|
||||
${{ steps.artifacts.outputs.archive }}
|
||||
${{ 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: read
|
||||
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.0.3
|
||||
with:
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # 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-backend-kubernetes -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"
|
||||
TAURI_BUNDLER_DMG_IGNORE_CI: "true"
|
||||
|
||||
- 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: Set DMG Finder label text size
|
||||
env:
|
||||
DMG_PATH: ${{ steps.unsigned.outputs.dmg }}
|
||||
run: desktop/scripts/set-dmg-finder-text-size.sh "$DMG_PATH" 14
|
||||
|
||||
# mdx-ios-codesign-helper discovers this file by its exact lowercase basename.
|
||||
- name: Stage signing entitlements
|
||||
run: cp desktop/src-tauri/Entitlements.plist "${RUNNER_TEMP}/entitlements.plist"
|
||||
|
||||
- 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: ${{ runner.temp }}/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"
|
||||
desktop/scripts/verify-macos-entitlements.sh "$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
|
||||
RENAMED="$(dirname "$ARCHIVE")/Buzz_${VERSION}_x64.app.tar.gz"
|
||||
mv "$ARCHIVE" "$RENAMED"
|
||||
mv "$SIG" "${RENAMED}.sig"
|
||||
ARCHIVE="$RENAMED"
|
||||
SIG="${RENAMED}.sig"
|
||||
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: Stage Intel macOS release artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: desktop-release-macos-x64
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
${{ steps.unsigned.outputs.dmg }}
|
||||
${{ steps.artifacts.outputs.archive }}
|
||||
${{ steps.artifacts.outputs.sig }}
|
||||
|
||||
release-linux:
|
||||
name: Release Linux
|
||||
if: github.repository == 'block/buzz'
|
||||
runs-on: ubuntu-latest
|
||||
# Digest-pinned like the SHA-pinned actions below; Renovate keeps it fresh.
|
||||
container: ubuntu:24.04@sha256:4fbb8e6a8395de5a7550b33509421a2bafbc0aab6c06ba2cef9ebffbc7092d90
|
||||
needs: setup
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
# AppImage tools (linuxdeploy, appimagetool) are themselves AppImages.
|
||||
# Containers lack FUSE, so we must use the extract-and-run fallback.
|
||||
APPIMAGE_EXTRACT_AND_RUN: "1"
|
||||
# This job runs in a container where the default run shell is dash;
|
||||
# the AppImage steps below use bash-only syntax ([[ ]], mapfile, arrays).
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
outputs:
|
||||
archive_name: ${{ steps.linux-artifacts.outputs.archive_name }}
|
||||
sig: ${{ steps.read-sig.outputs.sig }}
|
||||
steps:
|
||||
- name: Install system dependencies
|
||||
env:
|
||||
DEBIAN_FRONTEND: noninteractive
|
||||
run: |
|
||||
# Must run first: bare ubuntu:24.04 ships without curl, wget, git, or
|
||||
# ca-certificates. activate-hermit bootstraps via curl+HTTPS (needs
|
||||
# both), and actions/checkout falls back to a REST tarball without git.
|
||||
# Running as root — no sudo needed.
|
||||
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
|
||||
# Install GitHub CLI — preinstalled on runners but absent in containers.
|
||||
# wget and ca-certificates are now available from the step above.
|
||||
mkdir -p -m 755 /etc/apt/keyrings
|
||||
wget -q --tries=3 --timeout=30 -O /usr/share/keyrings/githubcli-archive-keyring.gpg \
|
||||
https://cli.github.com/packages/githubcli-archive-keyring.gpg
|
||||
# Pin the keyring like appimagetool below. If GitHub rotates the
|
||||
# keyring this fails loudly — recompute and update the hash.
|
||||
echo "6084d5d7bd8e288441e0e94fc6275570895da18e6751f70f057485dc2d1a811b /usr/share/keyrings/githubcli-archive-keyring.gpg" | sha256sum -c
|
||||
chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
||||
> /etc/apt/sources.list.d/github-cli.list
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends gh
|
||||
|
||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
with:
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Mark workspace safe for git (containerized job)
|
||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
env:
|
||||
VERSION: ${{ needs.setup.outputs.version }}
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
|
||||
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
with:
|
||||
workspaces: desktop/src-tauri
|
||||
lookup-only: true
|
||||
|
||||
- name: Install appimagetool
|
||||
run: |
|
||||
# Pin to an immutable release tag to avoid supply-chain drift from the
|
||||
# mutable `continuous` tag. Tag: 1.9.1, asset: appimagetool-<arch>.AppImage
|
||||
# (https://github.com/AppImage/appimagetool/releases/tag/1.9.1)
|
||||
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"
|
||||
# SHA256 integrity check. Refuse to run an unverified binary: if a new
|
||||
# arch (e.g. aarch64) is enabled in CI, compute its hash and add it here.
|
||||
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
|
||||
# appimagetool otherwise fetches the AppImage type2 runtime from the
|
||||
# MUTABLE `continuous` tag at repack time — the runtime is the first
|
||||
# code users execute, so pin it too. Tag: 20251108, hash is for the
|
||||
# x86_64 asset (non-x86_64 already hard-fails above).
|
||||
# (https://github.com/AppImage/type2-runtime/releases/tag/20251108)
|
||||
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: 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-backend-kubernetes -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 --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
|
||||
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: Fix AppImage (remove infra libs, shim host GStreamer)
|
||||
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]}"
|
||||
env:
|
||||
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: Stage Linux release artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: desktop-release-linux-x64
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
${{ steps.linux-artifacts.outputs.deb }}
|
||||
${{ steps.linux-artifacts.outputs.appimage }}
|
||||
${{ steps.linux-artifacts.outputs.archive }}
|
||||
${{ steps.linux-artifacts.outputs.sig }}
|
||||
|
||||
release-windows:
|
||||
name: Release Windows
|
||||
runs-on: windows-latest
|
||||
needs: setup
|
||||
timeout-minutes: 60
|
||||
permissions:
|
||||
contents: read
|
||||
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.0.3
|
||||
with:
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
shell: bash
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- 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.1
|
||||
# Disable dependency caching: a writable cache in this release workflow
|
||||
# (contents: read, 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: Stage Windows release artifacts
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: desktop-release-windows-x64
|
||||
if-no-files-found: error
|
||||
path: |
|
||||
${{ steps.artifacts.outputs.exe }}
|
||||
${{ steps.artifacts.outputs.sig }}
|
||||
|
||||
assemble-manifest:
|
||||
name: Assemble multi-platform latest.json
|
||||
# Only the tag-bound setup path can reach this job.
|
||||
if: |
|
||||
always() &&
|
||||
needs.setup.result == 'success' &&
|
||||
needs.release.result == 'success' &&
|
||||
needs.release-macos-x64.result == 'success' &&
|
||||
needs.release-linux.result == 'success' &&
|
||||
needs.release-windows.result == 'success' &&
|
||||
github.ref == format('refs/tags/desktop-v{0}', needs.setup.outputs.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.0.3
|
||||
with:
|
||||
ref: ${{ needs.setup.outputs.source_sha }}
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Verify tag-bound release source
|
||||
run: scripts/verify-release-ref.sh desktop-v "$VERSION"
|
||||
|
||||
- name: Download staged release artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
with:
|
||||
pattern: desktop-release-*
|
||||
path: staged-by-platform
|
||||
|
||||
- name: Flatten staged artifacts without basename collisions
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir staged
|
||||
while IFS= read -r -d '' file; do
|
||||
name="$(basename "$file")"
|
||||
[[ ! -e "staged/$name" ]] || {
|
||||
echo "::error::release artifact basename collision: $name"
|
||||
exit 1
|
||||
}
|
||||
cp "$file" "staged/$name"
|
||||
done < <(find staged-by-platform -type f -print0)
|
||||
|
||||
- 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 draft release has every updater archive
|
||||
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
|
||||
assets=$(find staged -type f -exec basename {} \;)
|
||||
for spec in \
|
||||
"$RESULT_ARM64:$ARCHIVE_ARM64" \
|
||||
"$RESULT_X64:$ARCHIVE_X64" \
|
||||
"$RESULT_LINUX:$ARCHIVE_LINUX" \
|
||||
"$RESULT_WIN:$ARCHIVE_WIN"; do
|
||||
result="${spec%%:*}"
|
||||
archive="${spec#*:}"
|
||||
if [[ "$result" == success ]]; then
|
||||
[[ -n "$archive" ]] || { echo "::error::successful platform has no archive"; exit 1; }
|
||||
grep -Fxq "$archive" <<<"$assets" || { echo "::error::draft release missing $archive"; exit 1; }
|
||||
fi
|
||||
done
|
||||
|
||||
- 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/desktop-v${VERSION}"
|
||||
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: Create or verify versioned draft
|
||||
run: |
|
||||
set -euo pipefail
|
||||
NOTES_FILE="${RUNNER_TEMP}/release-notes.md"
|
||||
awk "/^## v${VERSION}\$/{found=1; next} found && /^## v/{exit} found" CHANGELOG.md > "$NOTES_FILE"
|
||||
[[ -s "$NOTES_FILE" ]] || { echo "::error::missing non-empty changelog block for v${VERSION}"; exit 1; }
|
||||
PRERELEASE_FLAGS=()
|
||||
if [[ "$VERSION" == *-* ]]; then
|
||||
PRERELEASE_FLAGS=(--prerelease --latest=false)
|
||||
fi
|
||||
if gh release view "desktop-v${VERSION}" >/dev/null 2>&1; then
|
||||
EXISTING_SHA=$(gh release view "desktop-v${VERSION}" --json targetCommitish --jq .targetCommitish)
|
||||
IS_DRAFT=$(gh release view "desktop-v${VERSION}" --json isDraft --jq .isDraft)
|
||||
[[ "$EXISTING_SHA" == "${{ needs.setup.outputs.source_sha }}" ]] || {
|
||||
echo "::error::existing release targets $EXISTING_SHA, not the immutable source"; exit 1;
|
||||
}
|
||||
if [[ "$IS_DRAFT" != true ]]; then
|
||||
echo "already_published=true" >> "$GITHUB_ENV"
|
||||
fi
|
||||
else
|
||||
gh release create "desktop-v${VERSION}" \
|
||||
--draft \
|
||||
--target "${{ needs.setup.outputs.source_sha }}" \
|
||||
--title "Buzz Desktop v${VERSION}" \
|
||||
--notes-file "$NOTES_FILE" \
|
||||
"${PRERELEASE_FLAGS[@]}"
|
||||
fi
|
||||
|
||||
- name: Upload complete artifact set to versioned draft
|
||||
if: env.already_published != 'true'
|
||||
run: |
|
||||
mapfile -t files < <(find staged -type f -print)
|
||||
[[ "${#files[@]}" -gt 0 ]] || { echo "::error::no staged release artifacts"; exit 1; }
|
||||
gh release upload "desktop-v${VERSION}" "${files[@]}" --clobber
|
||||
|
||||
- name: Publish complete versioned release
|
||||
if: env.already_published != 'true'
|
||||
run: gh release edit "desktop-v${VERSION}" --draft=false
|
||||
|
||||
- name: Upload latest.json to rolling release last
|
||||
if: ${{ !contains(needs.setup.outputs.version, '-') }}
|
||||
run: gh release upload buzz-desktop-latest latest.json --clobber
|
||||
Reference in New Issue
Block a user