mirror of
https://github.com/block/buzz.git
synced 2026-10-27 12:40:30 +00:00
feat(release): all-OS desktop builds + universal auto-update manifest (#1011)
Signed-off-by: Will Pfleger <wpfleger@block.xyz> Signed-off-by: Will Pfleger <pfleger.will@gmail.com> Co-authored-by: npub1mn7jgtj4w2pd0g0zeuhxsa6jy6p0rewxz4kujt98my82ahfmp72sxjexk7 <dcfd242e557282d7a1e2cf2e6877522682f1e5c6156dc92ca7d90eaedd3b0f95@sprout-oss.stage.blox.sqprod.co>
This commit is contained in:
306
.github/workflows/release.yml
vendored
306
.github/workflows/release.yml
vendored
@@ -25,6 +25,8 @@ jobs:
|
|||||||
id-token: write # required by block/apple-codesign-action for OIDC
|
id-token: write # required by block/apple-codesign-action for OIDC
|
||||||
outputs:
|
outputs:
|
||||||
version: ${{ steps.version.outputs.version }}
|
version: ${{ steps.version.outputs.version }}
|
||||||
|
archive_name: ${{ steps.artifacts.outputs.archive_name }}
|
||||||
|
sig: ${{ steps.read-sig.outputs.sig }}
|
||||||
steps:
|
steps:
|
||||||
- name: Determine version
|
- name: Determine version
|
||||||
id: version
|
id: version
|
||||||
@@ -69,7 +71,7 @@ jobs:
|
|||||||
run: cd desktop && node scripts/build-release-config.mjs
|
run: cd desktop && node scripts/build-release-config.mjs
|
||||||
env:
|
env:
|
||||||
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
||||||
BUZZ_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/buzz-desktop-latest/latest.json
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
|
||||||
|
|
||||||
- name: Build sidecars
|
- name: Build sidecars
|
||||||
run: |
|
run: |
|
||||||
@@ -120,7 +122,7 @@ jobs:
|
|||||||
run: cd desktop && pnpm tauri build --verbose --no-sign --config src-tauri/tauri.release.conf.json
|
run: cd desktop && pnpm tauri build --verbose --no-sign --config src-tauri/tauri.release.conf.json
|
||||||
env:
|
env:
|
||||||
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
||||||
BUZZ_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/buzz-desktop-latest/latest.json
|
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: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
||||||
@@ -210,18 +212,11 @@ jobs:
|
|||||||
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
|
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
|
||||||
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
|
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Generate latest.json
|
- name: Read updater signature
|
||||||
run: |
|
id: read-sig
|
||||||
bash desktop/scripts/generate-oss-latest-json.sh \
|
run: echo "sig=$(cat "$SIG_PATH")" >> "$GITHUB_OUTPUT"
|
||||||
"$VERSION" \
|
|
||||||
"$SIG_PATH" \
|
|
||||||
"https://github.com/block/sprout/releases/download/buzz-desktop-latest/$ARCHIVE_NAME" \
|
|
||||||
> latest.json
|
|
||||||
cat latest.json
|
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ steps.version.outputs.version }}
|
|
||||||
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
||||||
ARCHIVE_NAME: ${{ steps.artifacts.outputs.archive_name }}
|
|
||||||
|
|
||||||
- name: Create versioned GitHub release
|
- name: Create versioned GitHub release
|
||||||
env:
|
env:
|
||||||
@@ -243,7 +238,7 @@ jobs:
|
|||||||
--notes "$NOTES" \
|
--notes "$NOTES" \
|
||||||
"$DMG_PATH"
|
"$DMG_PATH"
|
||||||
|
|
||||||
- name: Update rolling release for auto-updater
|
- name: Upload updater archive to rolling release
|
||||||
run: |
|
run: |
|
||||||
gh release create buzz-desktop-latest \
|
gh release create buzz-desktop-latest \
|
||||||
--prerelease \
|
--prerelease \
|
||||||
@@ -251,7 +246,6 @@ jobs:
|
|||||||
--notes "Rolling release for the Tauri auto-updater. Do not download manually — use the versioned release instead." \
|
--notes "Rolling release for the Tauri auto-updater. Do not download manually — use the versioned release instead." \
|
||||||
2>/dev/null || true
|
2>/dev/null || true
|
||||||
gh release upload buzz-desktop-latest \
|
gh release upload buzz-desktop-latest \
|
||||||
latest.json \
|
|
||||||
"$ARCHIVE_PATH" \
|
"$ARCHIVE_PATH" \
|
||||||
"$SIG_PATH" \
|
"$SIG_PATH" \
|
||||||
--clobber
|
--clobber
|
||||||
@@ -269,6 +263,9 @@ jobs:
|
|||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
id-token: write # required by block/apple-codesign-action for OIDC
|
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:
|
env:
|
||||||
VERSION: ${{ needs.release.outputs.version }}
|
VERSION: ${{ needs.release.outputs.version }}
|
||||||
TARGET: x86_64-apple-darwin
|
TARGET: x86_64-apple-darwin
|
||||||
@@ -295,7 +292,7 @@ jobs:
|
|||||||
run: cd desktop && node scripts/build-release-config.mjs
|
run: cd desktop && node scripts/build-release-config.mjs
|
||||||
env:
|
env:
|
||||||
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
||||||
BUZZ_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/buzz-desktop-latest/latest.json
|
BUZZ_UPDATER_ENDPOINT: https://github.com/block/buzz/releases/download/buzz-desktop-latest/latest.json
|
||||||
|
|
||||||
- name: Build sidecars
|
- name: Build sidecars
|
||||||
run: |
|
run: |
|
||||||
@@ -306,7 +303,7 @@ jobs:
|
|||||||
run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --config src-tauri/tauri.release.conf.json
|
run: cd desktop && pnpm tauri build --verbose --no-sign --target "$TARGET" --config src-tauri/tauri.release.conf.json
|
||||||
env:
|
env:
|
||||||
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
BUZZ_UPDATER_PUBLIC_KEY: ${{ secrets.BUZZ_UPDATER_PUBLIC_KEY || secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
||||||
BUZZ_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/buzz-desktop-latest/latest.json
|
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: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||||
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
||||||
@@ -334,11 +331,13 @@ jobs:
|
|||||||
entitlements-plist-path: desktop/src-tauri/Entitlements.plist
|
entitlements-plist-path: desktop/src-tauri/Entitlements.plist
|
||||||
artifact-name: buzz-${{ github.sha }}-${{ github.run_id }}-x64
|
artifact-name: buzz-${{ github.sha }}-${{ github.run_id }}-x64
|
||||||
|
|
||||||
- name: Replace DMG and signed .app
|
- name: Replace DMG and rebuild updater archive
|
||||||
env:
|
env:
|
||||||
SIGNED_DMG: ${{ steps.codesign.outputs.signed-dmg-path }}
|
SIGNED_DMG: ${{ steps.codesign.outputs.signed-dmg-path }}
|
||||||
SIGNED_APP_ZIP: ${{ steps.codesign.outputs.signed-artifact-path }}
|
SIGNED_APP_ZIP: ${{ steps.codesign.outputs.signed-artifact-path }}
|
||||||
UNSIGNED_DMG: ${{ steps.unsigned.outputs.dmg }}
|
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: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos"
|
APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos"
|
||||||
@@ -346,26 +345,63 @@ jobs:
|
|||||||
# Replace the unsigned DMG with the signed/notarized one.
|
# Replace the unsigned DMG with the signed/notarized one.
|
||||||
cp "$SIGNED_DMG" "$UNSIGNED_DMG"
|
cp "$SIGNED_DMG" "$UNSIGNED_DMG"
|
||||||
|
|
||||||
# Swap the unsigned .app for the signed .app from the action's zip,
|
# Swap the unsigned .app for the signed .app from the action's zip.
|
||||||
# so the verify step below checks the signed bundle (matches arm64).
|
|
||||||
EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract-x64"
|
EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract-x64"
|
||||||
rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR"
|
rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR"
|
||||||
ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR"
|
ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR"
|
||||||
rm -rf "${APP_DIR}/Buzz.app"
|
rm -rf "${APP_DIR}/Buzz.app"
|
||||||
cp -R "${EXTRACT_DIR}/Buzz.app" "${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
|
- name: Verify code signature
|
||||||
run: |
|
run: |
|
||||||
APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos/Buzz.app"
|
APP_DIR="desktop/src-tauri/target/${TARGET}/release/bundle/macos/Buzz.app"
|
||||||
codesign --verify --deep --strict --verbose=2 "$APP_DIR"
|
codesign --verify --deep --strict --verbose=2 "$APP_DIR"
|
||||||
spctl --assess --type execute --verbose=4 "$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
|
- name: Upload Intel DMG to versioned GitHub release
|
||||||
run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
|
run: gh release upload "v${VERSION}" "$DMG_PATH" --clobber
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
DMG_PATH: ${{ steps.unsigned.outputs.dmg }}
|
DMG_PATH: ${{ steps.unsigned.outputs.dmg }}
|
||||||
|
|
||||||
|
- name: Upload updater archive to rolling release
|
||||||
|
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:
|
release-linux:
|
||||||
name: Release Linux
|
name: Release Linux
|
||||||
if: github.repository == 'block/sprout'
|
if: github.repository == 'block/sprout'
|
||||||
@@ -374,6 +410,9 @@ jobs:
|
|||||||
timeout-minutes: 60
|
timeout-minutes: 60
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
outputs:
|
||||||
|
archive_name: ${{ steps.linux-artifacts.outputs.archive_name }}
|
||||||
|
sig: ${{ steps.read-sig.outputs.sig }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
|
||||||
with:
|
with:
|
||||||
@@ -428,10 +467,18 @@ jobs:
|
|||||||
cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
|
cargo build --release -p buzz-acp -p buzz-agent -p buzz-dev-mcp -p git-credential-nostr -p buzz-cli
|
||||||
./scripts/bundle-sidecars.sh
|
./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
|
- name: Build Linux Tauri app
|
||||||
run: cd desktop && pnpm tauri build --verbose --ci --bundles deb,appimage
|
run: cd desktop && pnpm tauri build --verbose --ci --bundles deb,appimage --config src-tauri/tauri.release.conf.json
|
||||||
env:
|
env:
|
||||||
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
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
|
- name: Locate Linux build artifacts
|
||||||
id: linux-artifacts
|
id: linux-artifacts
|
||||||
@@ -452,6 +499,24 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "appimage=$APPIMAGE" >> "$GITHUB_OUTPUT"
|
echo "appimage=$APPIMAGE" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Updater archive: Tauri emits <name>.AppImage.tar.gz + .sig when signing is enabled
|
||||||
|
ARCHIVE=$(find "$BUNDLE_DIR/appimage" -name '*.AppImage.tar.gz' ! -name '*.sig' -type f | head -1)
|
||||||
|
SIG="${ARCHIVE}.sig"
|
||||||
|
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
|
- name: Upload Linux artifacts to versioned GitHub release
|
||||||
env:
|
env:
|
||||||
VERSION: ${{ needs.release.outputs.version }}
|
VERSION: ${{ needs.release.outputs.version }}
|
||||||
@@ -463,3 +528,204 @@ jobs:
|
|||||||
"$DEB_PATH" \
|
"$DEB_PATH" \
|
||||||
"$APPIMAGE_PATH" \
|
"$APPIMAGE_PATH" \
|
||||||
--clobber
|
--clobber
|
||||||
|
|
||||||
|
- name: Upload updater archive to rolling release
|
||||||
|
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: release
|
||||||
|
timeout-minutes: 60
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
outputs:
|
||||||
|
archive_name: ${{ steps.artifacts.outputs.archive_name }}
|
||||||
|
sig: ${{ steps.read-sig.outputs.sig }}
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.release.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: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
|
||||||
|
|
||||||
|
- name: Install desktop dependencies
|
||||||
|
shell: bash
|
||||||
|
run: just desktop-install-ci
|
||||||
|
|
||||||
|
- name: Add Rust target
|
||||||
|
shell: bash
|
||||||
|
run: rustup target add "$TARGET"
|
||||||
|
|
||||||
|
- 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:
|
||||||
|
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
|
||||||
|
|
||||||
|
# Rename with _alpha-unsigned marker
|
||||||
|
EXE_DIR=$(dirname "$EXE")
|
||||||
|
EXE_BASE=$(basename "$EXE" .exe)
|
||||||
|
MARKED_EXE="${EXE_DIR}/${EXE_BASE}_alpha-unsigned.exe"
|
||||||
|
mv "$EXE" "$MARKED_EXE"
|
||||||
|
echo "exe=$MARKED_EXE" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
|
# Find the updater .nsis.zip and .sig
|
||||||
|
ARCHIVE=$(find "$BUNDLE_DIR/nsis" -name '*.nsis.zip' ! -name '*.sig' -type f | head -1)
|
||||||
|
SIG="${ARCHIVE}.sig"
|
||||||
|
if [[ -z "$ARCHIVE" || ! -f "$SIG" ]]; then
|
||||||
|
echo "::error::NSIS updater archive or signature not found in $BUNDLE_DIR/nsis"
|
||||||
|
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
|
||||||
|
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
|
||||||
|
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
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [release, release-macos-x64, release-linux, release-windows]
|
||||||
|
timeout-minutes: 10
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
env:
|
||||||
|
VERSION: ${{ needs.release.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:
|
||||||
|
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: |
|
||||||
|
mkdir -p /tmp/sigs
|
||||||
|
echo "$SIG_ARM64" > /tmp/sigs/darwin-aarch64.sig
|
||||||
|
echo "$SIG_X64" > /tmp/sigs/darwin-x86_64.sig
|
||||||
|
echo "$SIG_LINUX" > /tmp/sigs/linux-x86_64.sig
|
||||||
|
echo "$SIG_WIN" > /tmp/sigs/windows-x86_64.sig
|
||||||
|
|
||||||
|
- name: Verify archive URLs are accessible
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest"
|
||||||
|
|
||||||
|
for name in \
|
||||||
|
"${{ needs.release.outputs.archive_name }}" \
|
||||||
|
"${{ needs.release-macos-x64.outputs.archive_name }}" \
|
||||||
|
"${{ needs.release-linux.outputs.archive_name }}" \
|
||||||
|
"${{ needs.release-windows.outputs.archive_name }}"; 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
|
||||||
|
run: |
|
||||||
|
BASE="https://github.com/block/buzz/releases/download/buzz-desktop-latest"
|
||||||
|
|
||||||
|
bash desktop/scripts/generate-oss-latest-json.sh "$VERSION" \
|
||||||
|
"darwin-aarch64:/tmp/sigs/darwin-aarch64.sig:${BASE}/${{ needs.release.outputs.archive_name }}" \
|
||||||
|
"darwin-x86_64:/tmp/sigs/darwin-x86_64.sig:${BASE}/${{ needs.release-macos-x64.outputs.archive_name }}" \
|
||||||
|
"linux-x86_64:/tmp/sigs/linux-x86_64.sig:${BASE}/${{ needs.release-linux.outputs.archive_name }}" \
|
||||||
|
"windows-x86_64:/tmp/sigs/windows-x86_64.sig:${BASE}/${{ needs.release-windows.outputs.archive_name }}" \
|
||||||
|
> latest.json
|
||||||
|
cat latest.json
|
||||||
|
|
||||||
|
- name: Upload latest.json to rolling release
|
||||||
|
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
|
||||||
|
gh release upload buzz-desktop-latest latest.json --clobber
|
||||||
|
|||||||
@@ -58,9 +58,6 @@ hex = { workspace = true }
|
|||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
tracing-subscriber = { workspace = true }
|
tracing-subscriber = { workspace = true }
|
||||||
|
|
||||||
# Process-group kill (safe wrapper around killpg)
|
|
||||||
nix = { version = "0.31", default-features = false, features = ["signal"] }
|
|
||||||
|
|
||||||
# Error handling
|
# Error handling
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
anyhow = { workspace = true }
|
anyhow = { workspace = true }
|
||||||
@@ -74,5 +71,10 @@ toml = "1.0"
|
|||||||
# Filter expressions
|
# Filter expressions
|
||||||
evalexpr = { workspace = true }
|
evalexpr = { workspace = true }
|
||||||
|
|
||||||
|
# Process-group kill (safe wrapper around killpg) — Unix-only; kill_process_group
|
||||||
|
# has a #[cfg(not(unix))] fallback in acp.rs.
|
||||||
|
[target.'cfg(unix)'.dependencies]
|
||||||
|
nix = { version = "0.31", default-features = false, features = ["signal"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["test-util"] }
|
tokio = { workspace = true, features = ["test-util"] }
|
||||||
|
|||||||
@@ -74,6 +74,7 @@
|
|||||||
use std::fs;
|
use std::fs;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::mem::ManuallyDrop;
|
use std::mem::ManuallyDrop;
|
||||||
|
#[cfg(unix)]
|
||||||
use std::os::unix::io::FromRawFd;
|
use std::os::unix::io::FromRawFd;
|
||||||
use std::process;
|
use std::process;
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
@@ -338,12 +339,20 @@ impl StatusWriter {
|
|||||||
eprintln!("warning: --status-fd={fd} is not a valid open fd, using stderr");
|
eprintln!("warning: --status-fd={fd} is not a valid open fd, using stderr");
|
||||||
return Ok(Self { file: None });
|
return Ok(Self { file: None });
|
||||||
}
|
}
|
||||||
|
// SAFETY EXCEPTION: Required for Unix fd operations; no safe Rust API
|
||||||
|
// exists for from_raw_fd. The fd is >= 1 (validated by parse_status_fd),
|
||||||
|
// confirmed open by fcntl above, and git owns its lifetime. We use
|
||||||
|
// ManuallyDrop to prevent Rust from closing the inherited fd on drop.
|
||||||
|
Some(ManuallyDrop::new(unsafe { fs::File::from_raw_fd(fd) }))
|
||||||
|
}
|
||||||
|
// git's --status-fd passes an inherited numeric fd, which has no
|
||||||
|
// equivalent on Windows. Fall back to stderr; write_line already
|
||||||
|
// handles the None case.
|
||||||
|
#[cfg(not(unix))]
|
||||||
|
{
|
||||||
|
let _ = (fd, strict);
|
||||||
|
None
|
||||||
}
|
}
|
||||||
// SAFETY EXCEPTION: Required for Unix fd operations; no safe Rust API
|
|
||||||
// exists for from_raw_fd. The fd is >= 1 (validated by parse_status_fd),
|
|
||||||
// confirmed open by fcntl above, and git owns its lifetime. We use
|
|
||||||
// ManuallyDrop to prevent Rust from closing the inherited fd on drop.
|
|
||||||
Some(ManuallyDrop::new(unsafe { fs::File::from_raw_fd(fd) }))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(Self { file })
|
Ok(Self { file })
|
||||||
|
|||||||
@@ -1,21 +1,44 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
if [[ $# -lt 3 ]]; then
|
if [[ $# -lt 2 ]]; then
|
||||||
echo "Usage: generate-oss-latest-json.sh <version> <sig-file> <archive-url>" >&2
|
echo "Usage: generate-oss-latest-json.sh <version> <platform-key:sig-file:archive-url>..." >&2
|
||||||
|
echo " e.g. generate-oss-latest-json.sh 1.2.3 \\" >&2
|
||||||
|
echo " darwin-aarch64:/path/to/app.sig:https://example.com/app.tar.gz \\" >&2
|
||||||
|
echo " windows-x86_64:/path/to/setup.sig:https://example.com/setup.exe" >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
VERSION="$1"
|
VERSION="$1"
|
||||||
SIG_FILE="$2"
|
shift
|
||||||
ARCHIVE_URL="$3"
|
|
||||||
|
# Build the jq `platforms` object from N triples. Each triple is
|
||||||
|
# `platform-key:sig-file:archive-url`; archive URLs contain colons, so split
|
||||||
|
# only on the first two so the URL stays intact.
|
||||||
|
platform_args=()
|
||||||
|
platforms_obj="{}"
|
||||||
|
i=0
|
||||||
|
for triple in "$@"; do
|
||||||
|
key="${triple%%:*}"
|
||||||
|
rest="${triple#*:}"
|
||||||
|
sig_file="${rest%%:*}"
|
||||||
|
url="${rest#*:}"
|
||||||
|
if [[ "$key" == "$triple" || "$sig_file" == "$rest" || -z "$key" || -z "$sig_file" || -z "$url" ]]; then
|
||||||
|
echo "Error: malformed triple '$triple' (expected platform-key:sig-file:archive-url)" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
sig_arg="sig$i"
|
||||||
|
url_arg="url$i"
|
||||||
|
platform_args+=(--arg "$sig_arg" "$(cat "$sig_file")" --arg "$url_arg" "$url")
|
||||||
|
# Splice each platform into the accumulating object so no platform is hardcoded.
|
||||||
|
platforms_obj="$platforms_obj + { \"$key\": { signature: \$$sig_arg, url: \$$url_arg } }"
|
||||||
|
i=$((i + 1))
|
||||||
|
done
|
||||||
|
|
||||||
# Only darwin-aarch64 is included because the workflow builds on ARM64 runners
|
|
||||||
# only. Supporting Intel Macs (darwin-x86_64) would require a matrix build.
|
|
||||||
jq -n \
|
jq -n \
|
||||||
--arg version "$VERSION" \
|
--arg version "$VERSION" \
|
||||||
--arg notes "Buzz v$VERSION" \
|
--arg notes "Buzz v$VERSION" \
|
||||||
--arg pub_date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
--arg pub_date "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||||
--arg signature "$(cat "$SIG_FILE")" \
|
"${platform_args[@]}" \
|
||||||
--arg url "$ARCHIVE_URL" \
|
"{ version: \$version, notes: \$notes, pub_date: \$pub_date, platforms: ($platforms_obj) }"
|
||||||
'{ version: $version, notes: $notes, pub_date: $pub_date, platforms: { "darwin-aarch64": { signature: $signature, url: $url } } }'
|
|
||||||
|
|||||||
@@ -14,9 +14,16 @@ else
|
|||||||
SRC_DIR="target/${TARGET}/release"
|
SRC_DIR="target/${TARGET}/release"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# MSVC emits <name>.exe; Tauri's externalBin then expects binaries/<name>-<triple>.exe.
|
||||||
|
if [[ "$TARGET" == *windows* ]]; then
|
||||||
|
EXE=".exe"
|
||||||
|
else
|
||||||
|
EXE=""
|
||||||
|
fi
|
||||||
|
|
||||||
missing=()
|
missing=()
|
||||||
for bin in "${SIDECARS[@]}"; do
|
for bin in "${SIDECARS[@]}"; do
|
||||||
[[ -f "$SRC_DIR/$bin" ]] || missing+=("$bin")
|
[[ -f "$SRC_DIR/${bin}${EXE}" ]] || missing+=("${bin}${EXE}")
|
||||||
done
|
done
|
||||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||||
echo "Error: missing release binaries in $SRC_DIR: ${missing[*]}" >&2
|
echo "Error: missing release binaries in $SRC_DIR: ${missing[*]}" >&2
|
||||||
@@ -26,6 +33,6 @@ fi
|
|||||||
|
|
||||||
mkdir -p "$BINARIES_DIR"
|
mkdir -p "$BINARIES_DIR"
|
||||||
for bin in "${SIDECARS[@]}"; do
|
for bin in "${SIDECARS[@]}"; do
|
||||||
cp "$SRC_DIR/$bin" "$BINARIES_DIR/${bin}-${TARGET}"
|
cp "$SRC_DIR/${bin}${EXE}" "$BINARIES_DIR/${bin}-${TARGET}${EXE}"
|
||||||
done
|
done
|
||||||
echo "Sidecars bundled for $TARGET"
|
echo "Sidecars bundled for $TARGET"
|
||||||
|
|||||||
Reference in New Issue
Block a user