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
57 changes: 35 additions & 22 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ permissions:
contents: read

jobs:
build:
name: Build and smoke test
validate:
name: Validate source and release metadata
runs-on: ubuntu-latest

steps:
Expand Down Expand Up @@ -64,6 +64,25 @@ jobs:
- name: Validate scripts and pinned metadata
run: make check

build:
name: Build and smoke test (${{ matrix.arch }})
needs: validate
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
- arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}

steps:
- name: Checkout
uses: actions/checkout@v5

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

Expand All @@ -72,29 +91,33 @@ jobs:
with:
context: .
load: true
platforms: linux/amd64
platforms: ${{ matrix.platform }}
push: false
tags: pdparchitect/buzznode:ci
cache-from: type=gha
cache-to: type=gha,mode=max
tags: pdparchitect/buzznode:ci-${{ matrix.arch }}
cache-from: type=gha,scope=buzznode-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=buzznode-${{ matrix.arch }}

- name: Smoke test
shell: bash
env:
ARCH: ${{ matrix.arch }}
IMAGE: pdparchitect/buzznode:ci-${{ matrix.arch }}
PLATFORM: ${{ matrix.platform }}
run: |
set -euo pipefail

container="buzznode-ci"
container="buzznode-ci-${ARCH}"
cleanup() {
docker rm --force "$container" >/dev/null 2>&1 || true
}
trap cleanup EXIT

docker run --detach \
--name "$container" \
--platform linux/amd64 \
--platform "$PLATFORM" \
--shm-size 1g \
--publish 127.0.0.1:16903:6901 \
pdparchitect/buzznode:ci
"$IMAGE"

ready=false
for attempt in $(seq 1 60); do
Expand All @@ -112,16 +135,6 @@ jobs:
exit 1
fi

docker exec "$container" bash -ec '
for command in agent-runtime-login buzznode buzz buzz-acp \
buzz-agent buzz-dev-mcp codex codex-acp claude \
claude-agent-acp goose; do
command -v "$command" >/dev/null
done
! command -v buzz-desktop >/dev/null
! command -v buzz-relay >/dev/null
! command -v postgres >/dev/null
curl -fsS http://127.0.0.1:6901/ >/dev/null
'

echo "Buzznode is ready with its desktop and agent runtimes"
bash tests/smoke-container.sh "$container" "$ARCH"

echo "Buzznode is ready with its desktop and agent runtimes on $PLATFORM"
121 changes: 102 additions & 19 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,23 @@ jobs:
exit 1
fi

publish:
build:
name: Build release image (${{ matrix.arch }})
needs: validate
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- arch: amd64
platform: linux/amd64
runner: ubuntu-24.04
- arch: arm64
platform: linux/arm64
runner: ubuntu-24.04-arm
runs-on: ${{ matrix.runner }}
permissions:
contents: read
packages: write

steps:
- name: Checkout
Expand All @@ -58,6 +72,69 @@ jobs:
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Build and push image by digest
id: build
uses: docker/build-push-action@v7
with:
context: .
platforms: ${{ matrix.platform }}
push: true
outputs: type=image,name=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }},push-by-digest=true,name-canonical=true,push=true
labels: |
org.opencontainers.image.title=Buzznode
org.opencontainers.image.description=One persistent browser-accessible computer for one Buzz agent
cache-from: type=gha,scope=buzznode-${{ matrix.arch }}
cache-to: type=gha,mode=max,scope=buzznode-${{ matrix.arch }}
provenance: mode=max
sbom: true

- name: Export image digest
shell: bash
env:
DIGEST: ${{ steps.build.outputs.digest }}
run: |
mkdir -p /tmp/digests
touch "/tmp/digests/${DIGEST#sha256:}"

- name: Upload image digest
uses: actions/upload-artifact@v4
with:
name: digests-${{ matrix.arch }}
path: /tmp/digests/*
if-no-files-found: error
retention-days: 1

publish:
name: Publish multi-architecture release
needs:
- validate
- build
runs-on: ubuntu-24.04
permissions:
contents: write
packages: write

steps:
- name: Checkout
uses: actions/checkout@v5

- name: Download image digests
uses: actions/download-artifact@v5
with:
path: /tmp/digests
pattern: digests-*
merge-multiple: true

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4

- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract image metadata
id: meta
uses: docker/metadata-action@v6
Expand All @@ -70,30 +147,36 @@ jobs:
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable=${{ !contains(github.ref_name, '-') }}
labels: |
org.opencontainers.image.title=Buzznode
org.opencontainers.image.description=One persistent browser-accessible computer for one Buzz agent

- name: Build and publish image
id: build
uses: docker/build-push-action@v7
with:
context: .
platforms: linux/amd64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: mode=max
sbom: true
- name: Create multi-architecture image
id: manifest
shell: bash
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
run: |
set -euo pipefail

docker buildx imagetools create \
$(jq -cr '.tags | map("-t " + .) | join(" ")' \
<<< "$DOCKER_METADATA_OUTPUT_JSON") \
$(printf "${IMAGE}@sha256:%s " /tmp/digests/* | \
sed 's|/tmp/digests/||g')

digest="$(
docker buildx imagetools inspect \
"${IMAGE}:${GITHUB_REF_NAME}" |
sed -n 's/^Digest:[[:space:]]*//p' |
head -1
)"
test -n "$digest"
echo "digest=$digest" >> "$GITHUB_OUTPUT"

- name: Generate release notes
id: notes
shell: bash
env:
IMAGE: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
DIGEST: ${{ steps.build.outputs.digest }}
DIGEST: ${{ steps.manifest.outputs.digest }}
run: |
set -euo pipefail

Expand Down
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,61 @@ All notable changes to Buzznode are documented here, following

## [Unreleased]

## [0.2.0] - 2026-07-27

### Added

- Publish one multi-architecture Buzznode image for `linux/amd64` and
`linux/arm64`, with native builds and headed-browser smoke tests for both
architectures before their digests are combined into a release manifest.
- Build the pinned headless Buzz tools from their exact upstream source commit
on ARM64, where upstream does not publish a Linux package.

### Changed

- Keep Google Chrome on AMD64 and use signed Debian Chromium on ARM64 behind
the same launcher, Buzz-branded GTK theme, managed policy, and desktop
integration.
- Select native Buzz, Goose, yq, Cortile, and KasmVNC artifacts for the target
architecture, and let local builds select the host architecture by default.
- Pin GTK and Chrome's Linux UI typography to Noto Sans 9, matching every
Openbox title, menu, and on-screen-display font declaration instead of
inheriting GTK's larger Sans 10 default.
- Give Chrome a self-contained, Buzz-branded near-black GTK system theme that
darkens native menus and popups as well as the tab strip, active tab,
toolbar, controls, and address field; render Chrome's window controls from
the same XBM masks and state colors as Openbox, square the GTK-controlled
outer frame corners, and replace the bundled welcome card with the
terminal's ASCII banner on pure black.
- Remove the window handle, which drew a second line under the client area
with a resize grip boxed off at each end. Resizing stays available through
the window edges and corners and through Alt+right-drag anywhere on the
frame.
- Declare Codex's sandbox mode as `danger-full-access` at boot. Codex sandboxes
commands with bubblewrap, which cannot create a user namespace inside the
container, so no sandbox mode is enforceable and Codex warned on every start
about falling back to its bundled copy. Override with `BUZZNODE_CODEX_SANDBOX_MODE`.
- Start terminals in `/workspace` instead of the home directory, so the desktop
and the agent harness work in the same tree. Openbox chdirs to `$HOME` at
startup whatever directory it was started from and hands that to everything
it launches, so this is set in the shell - the one place every terminal
passes through - and only when the shell landed in `$HOME`, which leaves
non-interactive shells and deliberate directories alone.
- Widen the window grab margin with client padding. With the handle gone the
frame offered 1px to grab at the bottom against a 28px titlebar, so the
bottom corners were nearly unhittable. Client padding adds frame around the
client and paints it in the frame background, taking the grabbable ring from
1px to 7px without drawing anything new.

### Fixed

- Record the workspace as trusted for Codex and Claude Code at boot. Both
prompt once per directory before working in it, and `codex-acp` consults the
same `trust_level`, so the harness `buzznode launch` starts unattended in
`/workspace` would stop on a prompt nobody is present to answer, with the
reason buried in its log. Set `BUZZNODE_TRUST_WORKSPACE=false` to keep the
prompts.

## [0.1.0] - 2026-07-26

### Added
Expand Down
Loading