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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 145 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
name: macOS prerelease
name: Desktop previews

on:
workflow_dispatch:
inputs:
candidates:
description: Build Windows/Linux candidates only (no publishing or macOS signing)
type: boolean
default: false
schedule:
- cron: "17 */6 * * *"

permissions:
contents: read

concurrency:
group: macos-prerelease
group: desktop-preview-${{ inputs.candidates && github.ref || 'macos-prerelease' }}
cancel-in-progress: false

jobs:
build:
if: github.repository == 'block/buzz-app'
if: github.repository == 'block/buzz-app' && !inputs.candidates
name: Build, sign, and notarize
runs-on: macos-latest
timeout-minutes: 120
Expand Down Expand Up @@ -151,3 +156,140 @@ jobs:
--target "$SOURCE_SHA" --prerelease --latest=false \
--title "Buzz $VERSION (macOS preview)" \
--notes "Apple Silicon macOS preview from commit $SOURCE_SHA. Built, signed, and notarized by Actions run $GITHUB_RUN_ID."

windows:
if: github.repository == 'block/buzz-app' && inputs.candidates
name: Windows x64 candidate (unsigned)
runs-on: windows-2025
timeout-minutes: 120
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
# Reuse the native CI pin selection; Hermit does not run on Windows.
- name: Read repository tool pins
id: pins
shell: pwsh
run: |
foreach ($tool in @('rust', 'node', 'pnpm')) {
$pins = @(Get-ChildItem "bin/.$tool-*.pkg")
if ($pins.Count -ne 1) { throw "Expected one $tool pin" }
$version = $pins[0].Name -replace "^\.$tool-(.*)\.pkg$", '$1'
"$tool=$version" >> $env:GITHUB_OUTPUT
}
- name: Select pinned Rust
shell: pwsh
env:
RUST_VERSION: ${{ steps.pins.outputs.rust }}
run: |
rustup toolchain install $env:RUST_VERSION --profile minimal
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
"RUSTUP_TOOLCHAIN=$env:RUST_VERSION" >> $env:GITHUB_ENV
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
with:
node-version: ${{ steps.pins.outputs.node }}
- uses: pnpm/action-setup@b906affcce14559ad1aafd4ab0e942779e9f58b1 # v4.3.0
with:
version: ${{ steps.pins.outputs.pnpm }}
- run: pnpm install --frozen-lockfile
- name: Set candidate version
run: node scripts/candidate-version.mjs
- name: Build NSIS installer
run: pnpm tauri build --ci --no-sign --bundles nsis --config "$RUNNER_TEMP/candidate.json" -- --locked
- name: Install into disposable runner and verify resource payload
shell: pwsh
run: |
$installers = @(Get-ChildItem target/release/bundle/nsis/*.exe)
if ($installers.Count -ne 1) { throw 'Expected exactly one NSIS installer' }
$destination = "$env:RUNNER_TEMP\candidate-install"
# NSIS /D must be last and unquoted, including paths containing spaces.
$process = Start-Process $installers[0].FullName -ArgumentList "/S /D=$destination" -Wait -PassThru
if ($process.ExitCode -ne 0) { throw "Installer failed: $($process.ExitCode)" }
if (!(Test-Path "$destination\buzz-foundation.exe")) { throw 'App executable missing' }
node scripts/verify-runtime-bundle.mjs "$destination\agent-runtime" x86_64-pc-windows-msvc
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
- name: Stage candidate and checksum
run: |
mkdir release-assets
installers=(target/release/bundle/nsis/*.exe)
test "${#installers[@]}" -eq 1
cp "${installers[0]}" "release-assets/Buzz_${VERSION}_x64_unsigned.exe"
printf '%s\n' "$GITHUB_SHA" > release-assets/SOURCE_COMMIT
cd release-assets
sha256sum ./*.exe > SHA256SUMS
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: windows-x64-candidate
path: release-assets/
if-no-files-found: error
retention-days: 7

linux:
if: github.repository == 'block/buzz-app' && inputs.candidates
name: Linux x64 candidates
runs-on: ubuntu-latest
# Shipped old-Buzz recipe, without its mesh/Kubernetes/updater dependencies.
container: ubuntu:24.04@sha256:33ceb71981b602c1a7443a53469e4dba065f7503eab3078a2d7a57a2ab987517
timeout-minutes: 120
defaults:
run:
shell: bash
env:
APPIMAGE_EXTRACT_AND_RUN: "1"
steps:
- name: Install build and packaging dependencies
run: |
apt-get update
DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential ca-certificates curl desktop-file-utils file git \
libasound2-dev libayatana-appindicator3-dev libgtk-3-dev librsvg2-dev \
libssl-dev libwebkit2gtk-4.1-dev libxdo-dev patchelf pkg-config \
squashfs-tools wget xdg-utils
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
# No shared writable release caches. Use existing pins and frozen inputs.
- uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1
- run: pnpm install --frozen-lockfile
- name: Install pinned AppImage repacking tools
run: |
wget -q -O /tmp/appimagetool https://github.com/AppImage/appimagetool/releases/download/1.9.1/appimagetool-x86_64.AppImage
echo 'ed4ce84f0d9caff66f50bcca6ff6f35aae54ce8135408b3fa33abfc3cb384eb0 /tmp/appimagetool' | sha256sum -c
install -m 755 /tmp/appimagetool /usr/local/bin/appimagetool
wget -q -O /tmp/appimage-runtime https://github.com/AppImage/type2-runtime/releases/download/20251108/runtime-x86_64
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: Set candidate version
run: node scripts/candidate-version.mjs
- name: Build deb and AppImage
run: pnpm tauri build --ci --no-sign --bundles deb,appimage --config "$RUNNER_TEMP/candidate.json" -- --locked
- name: Repair AppImage and verify both packaged resource payloads
run: |
shopt -s nullglob
debs=(target/release/bundle/deb/*.deb)
images=(target/release/bundle/appimage/*.AppImage)
test "${#debs[@]}" -eq 1 && test "${#images[@]}" -eq 1
bash scripts/fix-appimage.sh "${images[0]}"
dpkg-deb -x "${debs[0]}" "$RUNNER_TEMP/deb"
node scripts/verify-runtime-bundle.mjs "$RUNNER_TEMP/deb/usr/lib/Buzz Foundation/agent-runtime" x86_64-unknown-linux-gnu
image=$(realpath "${images[0]}")
mkdir "$RUNNER_TEMP/appimage"
(cd "$RUNNER_TEMP/appimage" && "$image" --appimage-extract >/dev/null)
node scripts/verify-runtime-bundle.mjs "$RUNNER_TEMP/appimage/squashfs-root/usr/lib/Buzz Foundation/agent-runtime" x86_64-unknown-linux-gnu
mkdir release-assets
cp "${debs[0]}" "release-assets/Buzz_${VERSION}_amd64.deb"
cp "${images[0]}" "release-assets/Buzz_${VERSION}_x86_64.AppImage"
printf '%s\n' "$GITHUB_SHA" > release-assets/SOURCE_COMMIT
cd release-assets
sha256sum ./*.deb ./*.AppImage > SHA256SUMS
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: linux-x64-candidates
path: release-assets/
if-no-files-found: error
retention-days: 7
60 changes: 58 additions & 2 deletions docs/releases.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# macOS test releases
# Desktop test releases

The **macOS prerelease** workflow builds and signs Apple Silicon test builds from
The **Desktop previews** workflow builds and signs Apple Silicon test builds from
`main`. It publishes a DMG and `SHA256SUMS` as a GitHub prerelease, tagged
`v<app-version>-preview.<run-number>.<attempt>` at the built commit.

Expand All @@ -27,3 +27,59 @@ ID signature. The app retains the signed files' hashes in memory and checks them
before each launch. Development builds and other platforms still require the
manifest hashes to match. The release workflow verifies the signed seal,
notarization, and manifest identity before publishing.

## Windows and Linux installer candidates

The same **Desktop previews** workflow has a manual `candidates` switch. It builds
unsigned Windows x64 NSIS `.exe` and Ubuntu 24.04 x64 `.deb`/AppImage artifacts,
without running macOS signing or the release publisher:

```sh
gh workflow run release.yml --repo block/buzz-app \
--ref <candidate-branch> -f candidates=true
```

Scheduled runs and dispatches without this switch retain the existing macOS
publication path. Candidates use the same preview version, source commit, pinned
runtime revision and five-tool manifest; they never use legacy Buzz's sidecars,
updater feed, application identifier, or signing secrets. The workflow records
`SOURCE_COMMIT` and checksums over final installer bytes. Download the
`windows-x64-candidate` and `linux-x64-candidates` artifacts from that Actions run
within seven days. These are **ready to try only after their build and payload
checks pass**, not accepted releases.

Windows uses the repository's Rust, Node and pnpm pins on the hosted MSVC runner
because Hermit does not run there. NSIS retains Tauri's current-user install and
WebView2 download-bootstrapper defaults (network needed if WebView2 is absent).
The job silently installs into a disposable runner directory and verifies the
installed runtime manifest and each tool's hash, without launching the app.

Linux reuses old Buzz's Ubuntu 24.04 recipe and guarded Wayland/GStreamer AppImage
repair from `block/buzz` tag `desktop-v0.5.25`. Repacking tools and the type2 runtime
are checksum-pinned. Resource binaries must retain their manifest hashes; the
repair restores the verified original tools after linuxdeploy rewrites ELF RPATHs,
then the workflow verifies both extracted package payloads after repacking.
Compatibility guards fail rather than silently omitting a fix.
AppImage still relies on host desktop/media libraries; this is not a promise of
universal distro compatibility. Neither candidate job writes shared build caches.

### Acceptance still required

Use disposable Windows 11 and Ubuntu 24.04 GNOME accounts with throwaway keys.
Do not replace an everyday machine's `buzz://` handler without agreement.

1. Install and launch without a developer toolchain; respect Windows security
policy for unsigned apps. Linux needs a working Secret Service desktop session.
2. Create/import identity, quit and relaunch with the same key. Check unavailable
storage fails safely. Join the intended community, send/receive and reconnect.
3. Check cold/warm `buzz://` links, install a newer preview over the previous one,
verify identity/settings survive, and record uninstall/retained-data behavior.
4. Have a human repeat install → messaging → restart before accepting the build.

Two Windows native failures were last observed at `a68b39d6` (legacy-import path
separator assertion and model-auth recovery). Re-run the existing manual Windows
CI lane on the candidate and diagnose any surviving failures separately from
installer success. The packaging jobs do not waive them or enable local-agent
hosting. Builderlab/NIP-FI admission remains a separate product limitation.
There is no Windows/Linux publication or auto-update in this first candidate
slice; accepted artifact publication follows native acceptance.
21 changes: 5 additions & 16 deletions scripts/build-agent-runtime.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,12 @@ import { rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { dirname, join, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { runtimeBuildPlatform } from "./runtime-build-platform.mjs";
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
const spec = JSON.parse(
await readFile(join(root, "runtime/agent-runtime.json"), "utf8"),
);
// Drop injected credentials and per-shell compiler overrides so cache entries
// for a key come from the same pin, toolchain and build arguments. User-level
// Cargo config and native compiler inputs (CC, CFLAGS) still apply unkeyed.
const env = Object.fromEntries(
Object.entries(process.env).filter(
([key]) =>
!/^(BUZZ_|BUZZODZ_|NOSTR_|DATABRICKS_|CARGO_(BUILD|ENCODED|PROFILE|TARGET)_|RUSTC$|RUSTC_|RUSTFLAGS$|RUSTDOCFLAGS$)/.test(
key,
),
),
);
env.PATH = `${join(root, "bin")}:${env.PATH ?? ""}`;
const { env, cargo, rustc } = runtimeBuildPlatform(root);
async function run(command, args, capture = false, cwd = root) {
return new Promise((accept, reject) => {
const child = spawn(command, args, {
Expand All @@ -53,8 +43,8 @@ async function run(command, args, capture = false, cwd = root) {
);
});
}
const toolchain = await run(join(root, "bin/rustc"), ["-vV"], true);
const target = toolchain.match(/^host: (.+)$/m)?.[1];
const toolchain = await run(rustc, ["-vV"], true);
const target = toolchain.match(/^host: (.+)$/m)?.[1]?.trim();
if (!target) throw new Error("Could not resolve pinned Rust target");
const destination = join(root, "src-tauri/resources/agent-runtime");
const filenames = spec.tools.map((name) =>
Expand Down Expand Up @@ -205,8 +195,7 @@ try {
false,
source,
);
env.CARGO_TARGET_DIR = join(root, "target/agent-runtime-build");
await run(join(root, "bin/cargo"), buildArgs, false, source);
await run(cargo, buildArgs, false, source);
await publish(join(env.CARGO_TARGET_DIR, target, "release"), destination);
console.log(
`Verified inputs staged at ${destination} (${spec.revision}, ${target})`,
Expand Down
20 changes: 20 additions & 0 deletions scripts/candidate-version.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import assert from "node:assert/strict";
import { readFileSync, writeFileSync, appendFileSync } from "node:fs";
import { join } from "node:path";

const config = JSON.parse(readFileSync("src-tauri/tauri.conf.json", "utf8"));
const base = config.version.split("-")[0];
assert.match(base, /^\d+\.\d+\.\d+$/);
const {
GITHUB_RUN_NUMBER: run,
GITHUB_RUN_ATTEMPT: attempt,
RUNNER_TEMP: temp,
} = process.env;
assert.match(run ?? "", /^[1-9]\d*$/);
assert.match(attempt ?? "", /^[1-9]\d*$/);
const version = `${base}-preview.${run}.${attempt}`;
writeFileSync(
join(temp, "candidate.json"),
JSON.stringify({ version, bundle: { createUpdaterArtifacts: false } }),
);
appendFileSync(process.env.GITHUB_ENV, `VERSION=${version}\n`);
Loading
Loading