chore(release): release Buzz Desktop version 0.3.45 #8325
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main, release] | |
| pull_request: | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.sha }} | |
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | |
| env: | |
| CARGO_TERM_COLOR: always | |
| PLAYWRIGHT_BROWSERS_PATH: ${{ github.workspace }}/.cache/ms-playwright | |
| jobs: | |
| changes: | |
| name: Detect Changed Paths | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 2 | |
| permissions: | |
| contents: read | |
| pull-requests: read | |
| outputs: | |
| rust: ${{ steps.filter.outputs.rust }} | |
| desktop: ${{ steps.filter.outputs.desktop }} | |
| desktop-rust: ${{ steps.filter.outputs.desktop-rust }} | |
| web: ${{ steps.filter.outputs.web }} | |
| mobile: ${{ steps.filter.outputs.mobile }} | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2 | |
| id: filter | |
| with: | |
| filters: | | |
| rust: | |
| - 'crates/**' | |
| - 'migrations/**' | |
| - 'schema/**' | |
| - 'Cargo.toml' | |
| - 'Cargo.lock' | |
| - 'rust-toolchain.toml' | |
| - 'deny.toml' | |
| - '.github/workflows/ci.yml' | |
| - 'scripts/run-tests.sh' | |
| - 'justfile' | |
| desktop: | |
| - 'desktop/**' | |
| - '!desktop/src-tauri/**' | |
| - 'pnpm-lock.yaml' | |
| desktop-rust: | |
| - 'desktop/src-tauri/**' | |
| web: | |
| - 'web/**' | |
| - 'pnpm-lock.yaml' | |
| mobile: | |
| - 'mobile/**' | |
| rust-lint: | |
| name: Rust Lint | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.rust == 'true' || needs.changes.outputs.desktop-rust == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| save-if: ${{ github.event_name != 'pull_request' }} | |
| - name: Format check | |
| run: just fmt-check | |
| - name: Desktop Tauri format check | |
| run: just desktop-tauri-fmt-check | |
| - name: Clippy | |
| run: just clippy | |
| unit-tests: | |
| name: Unit Tests | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.rust == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| save-if: ${{ github.event_name != 'pull_request' }} | |
| - name: Install cargo-nextest | |
| uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15 | |
| with: | |
| tool: cargo-nextest@0.9.136 | |
| - name: Unit tests | |
| run: just test-unit | |
| desktop-core: | |
| name: Desktop Core | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 45 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| workspaces: desktop/src-tauri | |
| save-if: ${{ github.event_name != 'pull_request' }} | |
| - name: Install Tauri dependencies (Linux) | |
| env: | |
| DEBIAN_FRONTEND: noninteractive | |
| run: | | |
| sudo apt-get update \ | |
| -o Acquire::Retries=3 \ | |
| -o Acquire::http::Timeout=30 \ | |
| -o Acquire::https::Timeout=30 | |
| sudo apt-get install -y --no-install-recommends \ | |
| -o Acquire::Retries=3 \ | |
| -o Acquire::http::Timeout=30 \ | |
| -o Acquire::https::Timeout=30 \ | |
| -o DPkg::Lock::Timeout=120 \ | |
| build-essential \ | |
| curl \ | |
| file \ | |
| libasound2-dev \ | |
| libayatana-appindicator3-dev \ | |
| libgtk-3-dev \ | |
| librsvg2-dev \ | |
| libssl-dev \ | |
| libwebkit2gtk-4.1-dev \ | |
| libxdo-dev \ | |
| patchelf \ | |
| wget | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" | |
| - name: Restore pnpm store cache | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: pnpm-${{ runner.os }}- | |
| - name: Install desktop dependencies | |
| run: just desktop-install-ci | |
| - name: Desktop lint and format | |
| run: just desktop-check | |
| - name: Desktop unit tests | |
| run: just desktop-test | |
| - name: Desktop build | |
| run: just desktop-build | |
| - name: Desktop Tauri check | |
| run: just desktop-tauri-check | |
| env: | |
| CMAKE_POLICY_VERSION_MINIMUM: "3.5" | |
| - name: Desktop Tauri tests | |
| run: just desktop-tauri-test | |
| env: | |
| CMAKE_POLICY_VERSION_MINIMUM: "3.5" | |
| - name: Upload desktop e2e artifacts | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: desktop-e2e-artifacts | |
| path: | | |
| desktop/playwright-report | |
| desktop/test-results | |
| if-no-files-found: ignore | |
| - name: Save pnpm store cache | |
| if: github.event_name == 'push' | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| desktop-smoke-e2e: | |
| name: Desktop Smoke E2E (${{ matrix.shard }}) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2, 3, 4] | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" | |
| - name: Restore pnpm store cache | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: pnpm-${{ runner.os }}- | |
| - name: Install desktop dependencies | |
| run: just desktop-install-ci | |
| - name: Get Playwright version | |
| id: pw-version | |
| run: echo "version=$(cd desktop && node -e "console.log(require('@playwright/test/package.json').version)")" >> "$GITHUB_OUTPUT" | |
| - name: Restore Playwright browser cache | |
| id: playwright-cache | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} | |
| key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }} | |
| - name: Install Playwright Chromium | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| run: cd desktop && pnpm exec playwright install chromium | |
| - name: Install Playwright system dependencies | |
| run: cd desktop && pnpm exec playwright install-deps chromium | |
| - name: Save Playwright browser cache | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' && github.event_name == 'push' && matrix.shard == 1 | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} | |
| key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }} | |
| - name: Desktop build | |
| run: just desktop-build | |
| - name: Desktop smoke e2e | |
| run: cd desktop && pnpm exec playwright test --project=smoke --shard=${{ matrix.shard }}/4 | |
| - name: Upload desktop smoke e2e artifacts | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: desktop-smoke-e2e-artifacts-${{ matrix.shard }} | |
| path: | | |
| desktop/playwright-report | |
| desktop/test-results | |
| if-no-files-found: ignore | |
| desktop: | |
| name: Desktop | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: [changes, desktop-core, desktop-smoke-e2e] | |
| if: always() && (github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true') | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Check desktop jobs | |
| run: | | |
| if [ "${{ needs.desktop-core.result }}" != "success" ]; then | |
| echo "Desktop Core finished with: ${{ needs.desktop-core.result }}" | |
| exit 1 | |
| fi | |
| if [ "${{ needs.desktop-smoke-e2e.result }}" != "success" ]; then | |
| echo "Desktop Smoke E2E shards finished with: ${{ needs.desktop-smoke-e2e.result }}" | |
| exit 1 | |
| fi | |
| echo "Desktop jobs passed" | |
| desktop-e2e-relay: | |
| name: Desktop E2E Relay | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| workspaces: | | |
| . | |
| desktop/src-tauri | |
| save-if: ${{ github.event_name != 'pull_request' }} | |
| - name: Build relay | |
| run: cargo build --profile ci -p buzz-relay | |
| - name: Upload relay binary | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: desktop-e2e-relay | |
| path: target/ci/buzz-relay | |
| if-no-files-found: error | |
| retention-days: 1 | |
| desktop-e2e-integration-shard: | |
| name: Desktop E2E Integration (${{ matrix.shard }}/2) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| needs: [changes, desktop-e2e-relay] | |
| if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true' | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| shard: [1, 2] | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - name: Start integration services | |
| run: | | |
| for attempt in 1 2 3; do | |
| if docker compose up -d postgres redis minio minio-init; then | |
| break | |
| fi | |
| if [ "$attempt" -eq 3 ]; then | |
| echo "docker compose up failed after 3 attempts" >&2 | |
| exit 1 | |
| fi | |
| echo "docker compose up failed (attempt $attempt), retrying in $((attempt * 5))s..." >&2 | |
| sleep $((attempt * 5)) | |
| done | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" | |
| - name: Restore pnpm store cache | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: pnpm-${{ runner.os }}- | |
| - name: Install desktop dependencies | |
| run: just desktop-install-ci | |
| - name: Get Playwright version | |
| id: pw-version | |
| run: echo "version=$(cd desktop && node -e "console.log(require('@playwright/test/package.json').version)")" >> "$GITHUB_OUTPUT" | |
| - name: Restore Playwright browser cache | |
| id: playwright-cache | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} | |
| key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }} | |
| - name: Install Playwright Chromium | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' | |
| run: cd desktop && pnpm exec playwright install chromium | |
| - name: Install Playwright system dependencies | |
| run: cd desktop && pnpm exec playwright install-deps chromium | |
| - name: Save Playwright browser cache | |
| if: steps.playwright-cache.outputs.cache-hit != 'true' && github.event_name == 'push' && matrix.shard == 1 | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} | |
| key: playwright-${{ runner.os }}-${{ steps.pw-version.outputs.version }} | |
| - name: Desktop build | |
| run: just desktop-build | |
| - name: Wait for integration services | |
| run: | | |
| wait_healthy() { | |
| local service="$1" | |
| local container="$2" | |
| for attempt in $(seq 1 60); do | |
| status=$(docker inspect --format='{{.State.Health.Status}}' "${container}" 2>/dev/null || echo "not_found") | |
| if [ "${status}" = "healthy" ]; then | |
| echo "${service} is healthy" | |
| return 0 | |
| fi | |
| sleep 2 | |
| done | |
| docker logs "${container}" || true | |
| return 1 | |
| } | |
| wait_healthy "Postgres" "buzz-postgres" | |
| wait_healthy "Redis" "buzz-redis" | |
| wait_healthy "MinIO" "buzz-minio" | |
| - name: Download relay binary | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: desktop-e2e-relay | |
| path: target/ci | |
| - name: Apply schema and seed deployment community | |
| # MT: the relay resolves each request's tenant from the communities host | |
| # map and fails closed on an unmapped host. The channel reconciler binds | |
| # the deployment community ONCE at boot (outside its retry loop) and | |
| # exits permanently on an unmapped host, so the 'localhost:3000' | |
| # community MUST exist before the relay starts — the retry loop only | |
| # handles late-seeded channels, not a late-seeded community. The relay | |
| # migrates at boot via BUZZ_AUTO_MIGRATE, but that's too late for the | |
| # pre-boot seed, so apply the schema here first (then drop AUTO_MIGRATE | |
| # below). lower(host) is the unique index → ON CONFLICT target. psql | |
| # isn't on PATH in hermit → exec into the buzz-postgres container. | |
| env: | |
| PGHOST: localhost | |
| PGPORT: "5432" | |
| PGUSER: buzz | |
| PGPASSWORD: buzz_dev | |
| PGDATABASE: buzz | |
| # Use the already-running docker postgres for desired-state planning instead of | |
| # downloading an embedded Postgres from Maven Central (transient-fetch flake source). | |
| PGSCHEMA_PLAN_HOST: localhost | |
| PGSCHEMA_PLAN_PORT: "5432" | |
| PGSCHEMA_PLAN_DB: buzz | |
| PGSCHEMA_PLAN_USER: buzz | |
| PGSCHEMA_PLAN_PASSWORD: buzz_dev | |
| run: | | |
| ./bin/pgschema apply --file schema/schema.sql --auto-approve | |
| docker exec -i -e PGPASSWORD=buzz_dev buzz-postgres \ | |
| psql -U buzz -d buzz -v ON_ERROR_STOP=1 < scripts/attach-schema-partitions.sql | |
| docker exec -e PGPASSWORD=buzz_dev buzz-postgres \ | |
| psql -U buzz -d buzz -qtA -c " | |
| INSERT INTO communities (id, host) | |
| VALUES ('00000000-0000-4000-8000-00000000c0de', 'localhost:3000') | |
| ON CONFLICT (lower(host)) DO NOTHING | |
| ;" | |
| - name: Start relay | |
| run: | | |
| chmod +x ./target/ci/buzz-relay | |
| nohup env \ | |
| DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz \ | |
| REDIS_URL=redis://localhost:6379 \ | |
| RELAY_URL=ws://localhost:3000 \ | |
| BUZZ_BIND_ADDR=0.0.0.0:3000 \ | |
| BUZZ_REQUIRE_AUTH_TOKEN=false \ | |
| BUZZ_RECONCILE_CHANNELS=true \ | |
| BUZZ_GIT_PROBE_WRITERS=8 \ | |
| SPROUT_REMINDER_SCHEDULER_INTERVAL_SECS=1 \ | |
| ./target/ci/buzz-relay > /tmp/buzz-relay.log 2>&1 & | |
| echo $! > /tmp/buzz-relay.pid | |
| for attempt in $(seq 1 60); do | |
| if ! kill -0 "$(cat /tmp/buzz-relay.pid)" 2>/dev/null; then | |
| cat /tmp/buzz-relay.log | |
| exit 1 | |
| fi | |
| status_code=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/_readiness || true) | |
| if [ "${status_code}" = "200" ]; then | |
| exit 0 | |
| fi | |
| sleep 1 | |
| done | |
| cat /tmp/buzz-relay.log | |
| exit 1 | |
| - name: Seed desktop e2e data | |
| run: bash scripts/setup-desktop-test-data.sh | |
| - name: Desktop relay-backed e2e | |
| run: cd desktop && pnpm exec playwright test --project=integration --shard=${{ matrix.shard }}/2 | |
| - name: Upload desktop integration artifacts | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: desktop-e2e-integration-artifacts-${{ matrix.shard }} | |
| path: | | |
| desktop/playwright-report | |
| desktop/test-results | |
| /tmp/buzz-relay.log | |
| if-no-files-found: ignore | |
| - name: Save pnpm store cache | |
| if: github.event_name == 'push' | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| desktop-e2e-integration: | |
| name: Desktop E2E Integration | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| needs: [changes, desktop-e2e-integration-shard] | |
| if: always() && (github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true') | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Check integration shards | |
| run: | | |
| if [ "${{ needs.desktop-e2e-integration-shard.result }}" != "success" ]; then | |
| echo "Desktop E2E Integration shards finished with: ${{ needs.desktop-e2e-integration-shard.result }}" | |
| exit 1 | |
| fi | |
| echo "Desktop E2E Integration shards passed" | |
| backend-integration: | |
| name: Backend Integration (relay e2e) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| needs: [changes, desktop-e2e-relay] | |
| if: github.event_name == 'push' || needs.changes.outputs.rust == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| save-if: ${{ github.event_name != 'pull_request' }} | |
| - name: Start integration services | |
| run: | | |
| for attempt in 1 2 3; do | |
| if docker compose up -d postgres redis minio minio-init; then | |
| break | |
| fi | |
| if [ "$attempt" -eq 3 ]; then | |
| echo "docker compose up failed after 3 attempts" >&2 | |
| exit 1 | |
| fi | |
| echo "docker compose up failed (attempt $attempt), retrying in $((attempt * 5))s..." >&2 | |
| sleep $((attempt * 5)) | |
| done | |
| - name: Wait for integration services | |
| run: | | |
| wait_healthy() { | |
| local service="$1" | |
| local container="$2" | |
| for attempt in $(seq 1 60); do | |
| status=$(docker inspect --format='{{.State.Health.Status}}' "${container}" 2>/dev/null || echo "not_found") | |
| if [ "${status}" = "healthy" ]; then | |
| echo "${service} is healthy" | |
| return 0 | |
| fi | |
| sleep 2 | |
| done | |
| docker logs "${container}" || true | |
| return 1 | |
| } | |
| wait_healthy "Postgres" "buzz-postgres" | |
| wait_healthy "Redis" "buzz-redis" | |
| wait_healthy "MinIO" "buzz-minio" | |
| - name: Download relay binary | |
| uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: desktop-e2e-relay | |
| path: target/ci | |
| - name: Apply schema and seed deployment community | |
| # MT: the relay resolves each request's tenant from the communities host | |
| # map and fails closed on an unmapped host. The reminder scheduler binds | |
| # the deployment community ONCE at boot and exits permanently on an | |
| # unmapped host (no retry, unlike the channel reconciler), so the | |
| # 'localhost:3000' community MUST exist before the relay starts — seeding | |
| # after boot leaves the scheduler dead. The relay migrates at boot via | |
| # BUZZ_AUTO_MIGRATE, but that's too late for the pre-boot seed, so apply | |
| # the schema here first (then drop AUTO_MIGRATE below). lower(host) is the | |
| # unique index → ON CONFLICT target. psql isn't on PATH in hermit → exec | |
| # into the buzz-postgres container. | |
| env: | |
| PGHOST: localhost | |
| PGPORT: "5432" | |
| PGUSER: buzz | |
| PGPASSWORD: buzz_dev | |
| PGDATABASE: buzz | |
| # Use the already-running docker postgres for desired-state planning instead of | |
| # downloading an embedded Postgres from Maven Central (transient-fetch flake source). | |
| PGSCHEMA_PLAN_HOST: localhost | |
| PGSCHEMA_PLAN_PORT: "5432" | |
| PGSCHEMA_PLAN_DB: buzz | |
| PGSCHEMA_PLAN_USER: buzz | |
| PGSCHEMA_PLAN_PASSWORD: buzz_dev | |
| run: | | |
| ./bin/pgschema apply --file schema/schema.sql --auto-approve | |
| docker exec -i -e PGPASSWORD=buzz_dev buzz-postgres \ | |
| psql -U buzz -d buzz -v ON_ERROR_STOP=1 < scripts/attach-schema-partitions.sql | |
| docker exec -e PGPASSWORD=buzz_dev buzz-postgres \ | |
| psql -U buzz -d buzz -qtA -c " | |
| INSERT INTO communities (id, host) | |
| VALUES ('00000000-0000-4000-8000-00000000c0de', 'localhost:3000') | |
| ON CONFLICT (lower(host)) DO NOTHING | |
| ;" | |
| - name: Start relay | |
| run: | | |
| chmod +x ./target/ci/buzz-relay | |
| nohup env \ | |
| DATABASE_URL=postgres://buzz:buzz_dev@localhost:5432/buzz \ | |
| REDIS_URL=redis://localhost:6379 \ | |
| RELAY_URL=ws://localhost:3000 \ | |
| BUZZ_BIND_ADDR=0.0.0.0:3000 \ | |
| BUZZ_REQUIRE_AUTH_TOKEN=false \ | |
| BUZZ_RECONCILE_CHANNELS=true \ | |
| BUZZ_GIT_PROBE_WRITERS=8 \ | |
| SPROUT_REMINDER_SCHEDULER_INTERVAL_SECS=1 \ | |
| ./target/ci/buzz-relay > /tmp/buzz-relay.log 2>&1 & | |
| echo $! > /tmp/buzz-relay.pid | |
| for attempt in $(seq 1 60); do | |
| if ! kill -0 "$(cat /tmp/buzz-relay.pid)" 2>/dev/null; then | |
| cat /tmp/buzz-relay.log | |
| exit 1 | |
| fi | |
| status_code=$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:3000/_readiness || true) | |
| if [ "${status_code}" = "200" ]; then | |
| exit 0 | |
| fi | |
| sleep 1 | |
| done | |
| cat /tmp/buzz-relay.log | |
| exit 1 | |
| - name: NIP-ER reminder e2e | |
| # Feature e2e for NIP-ER (Event Reminders, kind:30300): write-path | |
| # validation, author-only read filtering, and scheduler delivery against | |
| # a live relay. The schema-drift / migration-version guarantee is owned | |
| # by the buzz-db migration.rs unit tests, not this suite. | |
| run: cargo test --profile ci -p buzz-test-client --test e2e_event_reminder -- --ignored | |
| env: | |
| RELAY_URL: ws://localhost:3000 | |
| - name: Upload relay log | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: backend-integration-relay-log | |
| path: /tmp/buzz-relay.log | |
| if-no-files-found: ignore | |
| relay-e2e: | |
| name: Relay E2E | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.rust == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| save-if: ${{ github.event_name != 'pull_request' }} | |
| - name: Start relay | |
| run: ./scripts/start-relay-for-tests.sh | |
| - name: Relay E2E tests | |
| run: cargo test -p buzz-test-client --test e2e_persona --test e2e_nostr_interop -- --ignored --nocapture | |
| env: | |
| RELAY_URL: ws://localhost:3000 | |
| GIT_CREDENTIAL_NOSTR_BIN: ${{ github.workspace }}/target/ci/git-credential-nostr | |
| - name: Upload relay logs | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 | |
| with: | |
| name: relay-e2e-artifacts | |
| path: /tmp/buzz-relay.log | |
| if-no-files-found: ignore | |
| web: | |
| name: Web | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.web == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - name: Get pnpm store directory | |
| id: pnpm-cache | |
| run: echo "STORE_PATH=$(pnpm store path --silent)" >> "$GITHUB_OUTPUT" | |
| - name: Restore pnpm store cache | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| restore-keys: pnpm-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: pnpm install --frozen-lockfile | |
| - name: Web lint and format | |
| run: just web-check | |
| - name: Web build | |
| run: just web-build | |
| - name: Save pnpm store cache | |
| if: github.event_name == 'push' | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} | |
| key: pnpm-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} | |
| mobile: | |
| name: Mobile | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.mobile == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - name: Compute Hermit cache key | |
| id: hermit-bin-hash | |
| run: | | |
| hash="$(find ./bin ! -type d | sort | xargs openssl sha256 | openssl sha256 -r | cut -d' ' -f1)" | |
| echo "hash=$hash" >> "$GITHUB_OUTPUT" | |
| - name: Restore Hermit package cache | |
| id: hermit-cache | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ~/.cache/hermit/pkg | |
| key: ${{ runner.os }}-hermit-cache-${{ steps.hermit-bin-hash.outputs.hash }} | |
| restore-keys: ${{ runner.os }}-hermit-cache- | |
| - name: Prime Flutter SDK | |
| run: flutter --version | |
| - name: Save Hermit package cache | |
| if: always() && steps.hermit-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| continue-on-error: true | |
| with: | |
| path: ~/.cache/hermit/pkg | |
| key: ${{ runner.os }}-hermit-cache-${{ steps.hermit-bin-hash.outputs.hash }} | |
| - name: Restore pub cache | |
| id: pub-cache | |
| uses: actions/cache/restore@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| with: | |
| path: ~/.pub-cache | |
| key: pub-${{ runner.os }}-${{ hashFiles('mobile/pubspec.lock') }} | |
| restore-keys: pub-${{ runner.os }}- | |
| - name: Install dependencies | |
| run: cd mobile && flutter pub get | |
| - name: Save pub cache | |
| if: always() && steps.pub-cache.outputs.cache-hit != 'true' | |
| uses: actions/cache/save@caa296126883cff596d87d8935842f9db880ef25 # v5 | |
| continue-on-error: true | |
| with: | |
| path: ~/.pub-cache | |
| key: pub-${{ runner.os }}-${{ hashFiles('mobile/pubspec.lock') }} | |
| - name: Format check | |
| run: cd mobile && dart format --output=none --set-exit-if-changed . | |
| - name: Analyze | |
| run: cd mobile && flutter analyze | |
| - name: Test | |
| run: cd mobile && flutter test | |
| security: | |
| name: Security | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.rust == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - name: Dependency policy | |
| run: cargo-deny check | |
| dead-token-guard: | |
| name: Dead Token Reference Guard | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - name: Check for dead API token references in client code | |
| run: | | |
| # Fail if dead API token patterns reappear in desktop, mobile, docs, or config. | |
| # Relay crates are excluded — they still use token auth internally. | |
| PATTERNS='TokenScope|MintTokenResponse|hasApiToken|spr_tok_' | |
| PATHS='desktop/src/ desktop/tests/ mobile/test/ mobile/lib/ .env.example' | |
| EXCLUDES='--exclude-dir=node_modules --exclude-dir=.dart_tool' | |
| if grep -rn $EXCLUDES -E "$PATTERNS" $PATHS 2>/dev/null; then | |
| echo "::error::Dead API token references found in client code. See above." | |
| exit 1 | |
| fi | |
| echo "No dead token references found." | |
| server-cross-compile: | |
| name: Server Cross-Compile | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 30 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.rust == 'true' | |
| permissions: | |
| contents: read | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| target: | |
| - x86_64-unknown-linux-musl | |
| - aarch64-unknown-linux-musl | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| key: cross-${{ matrix.target }} | |
| save-if: ${{ github.event_name != 'pull_request' }} | |
| - name: Install cross | |
| uses: taiki-e/install-action@0fd46367812ee04360509b4169d9f659d6892bb2 # v2.79.15 | |
| with: | |
| tool: cross@0.2.5 | |
| - name: Build server binaries | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| # PRs: compile + build-script gate only (no codegen/link). Main: full link gate. | |
| CARGO_CMD: ${{ github.event_name == 'pull_request' && 'check' || 'build' }} | |
| run: | | |
| cross "$CARGO_CMD" --release --target "$TARGET" \ | |
| -p buzz-relay \ | |
| -p buzz-acp \ | |
| -p buzz-agent \ | |
| -p buzz-dev-mcp \ | |
| -p git-credential-nostr \ | |
| -p git-sign-nostr | |
| windows-rust: | |
| name: Windows Rust (x86_64-pc-windows-msvc) | |
| runs-on: windows-latest | |
| # Windows runners are slow and this compiles the workspace + Tauri crate | |
| # cold across four steps; budget generously. | |
| timeout-minutes: 45 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.rust == 'true' || needs.changes.outputs.desktop-rust == 'true' | |
| permissions: | |
| contents: read | |
| env: | |
| TARGET: x86_64-pc-windows-msvc | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| # MSVC needs windows.h (aws-lc-sys et al.), so this runs on a real Windows | |
| # runner — hermit, used by the Linux jobs, does not provide MSVC. The | |
| # toolchain (1.95.0 + clippy via profile = default) comes from the | |
| # repo-root rust-toolchain.toml, which the runner's preinstalled rustup | |
| # honors on demand; the host triple already is x86_64-pc-windows-msvc. | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| workspaces: | | |
| . | |
| desktop/src-tauri | |
| key: windows-msvc | |
| save-if: ${{ github.event_name != 'pull_request' }} | |
| # Tauri validates externalBin at compile time, so the Tauri-crate steps | |
| # below fail without these stubs. Mirrors scripts/bundle-sidecars.sh's | |
| # Windows naming (binaries/<bin>-<triple>.exe); empty files suffice for a | |
| # type-check since nothing executes them. | |
| - name: Create sidecar placeholders | |
| shell: bash | |
| run: | | |
| mkdir -p desktop/src-tauri/binaries | |
| for bin in buzz-acp buzz-agent buzz-dev-mcp git-credential-nostr buzz; do | |
| touch "desktop/src-tauri/binaries/${bin}-${TARGET}.exe" | |
| done | |
| - name: Clippy (workspace) | |
| run: cargo clippy --workspace --all-targets --target $env:TARGET -- -D warnings | |
| - name: Check (workspace) | |
| run: cargo check --workspace --all-targets --target $env:TARGET | |
| - name: Test (buzz-dev-mcp) | |
| # The Windows-only bash resolver lives in buzz-dev-mcp; its unit tests | |
| # only gate if this crate is tested ON Windows. | |
| # Serial: windows_resolver_tests mutate process-global env | |
| # (BUZZ_SHELL/GIT_BASH/SystemRoot) that SharedState::new reads. | |
| run: cargo test -p buzz-dev-mcp --target $env:TARGET -- --test-threads=1 | |
| # Smoke-test the new host-prereq contract: Git for Windows (which provides | |
| # bash) is available on the runner, a shell command round-trips, and bash | |
| # does NOT resolve from System32 (so WSL's launcher is never picked up). | |
| # windows-latest runners have Git for Windows pre-installed; the unit tests | |
| # above exercise the MCP resolver itself. This step verifies the host env. | |
| - name: Smoke-test host Git Bash prereq (host env check) | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| # Git for Windows ships bash.exe under its bin/ directory; confirm it | |
| # resolves from the standard location the runtime resolver probes first. | |
| bash_path=$(command -v bash 2>/dev/null || true) | |
| [[ -n "$bash_path" ]] || { echo "ERROR: bash not found on PATH — host Git for Windows missing" >&2; exit 1; } | |
| echo "Resolved bash: $bash_path" | |
| [[ "$bash_path" != *System32* ]] || { echo "ERROR: resolved bash is WSL's System32 launcher" >&2; exit 1; } | |
| # Run a basic pipeline through the resolved bash (same invocation the | |
| # agent uses: bash -c '...'). | |
| out=$(bash -c 'echo hello | tr a-z A-Z') | |
| [[ "$out" == "HELLO" ]] || { echo "bash pipeline failed: got '$out'" >&2; exit 1; } | |
| # Confirm git itself works — agents run git commands frequently. | |
| git --version | |
| repo=$(mktemp -d) | |
| cd "$repo" | |
| git init -q | |
| git -c user.name=ci -c user.email=ci@example.com commit -q --allow-empty -m smoke | |
| git log -1 --format=%s | grep -qx smoke | |
| echo "Host bash resolved and functional; git commit round-trip passed" | |
| - name: Check (Tauri crate) | |
| run: cargo check --manifest-path desktop/src-tauri/Cargo.toml --target $env:TARGET | |
| env: | |
| CMAKE_POLICY_VERSION_MINIMUM: "3.5" | |
| - name: Test (Tauri crate) | |
| run: cargo test --manifest-path desktop/src-tauri/Cargo.toml --target $env:TARGET | |
| env: | |
| CMAKE_POLICY_VERSION_MINIMUM: "3.5" | |
| desktop-build-macos: | |
| name: Desktop Build (macOS) | |
| runs-on: macos-latest | |
| timeout-minutes: 45 | |
| needs: [changes] | |
| if: github.event_name == 'push' || needs.changes.outputs.desktop == 'true' || needs.changes.outputs.desktop-rust == 'true' || needs.changes.outputs.rust == 'true' | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6 | |
| - uses: cashapp/activate-hermit@cea9af7913204a965fd488637a8d1811bba2e616 # v1 | |
| - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 | |
| with: | |
| workspaces: desktop/src-tauri | |
| save-if: ${{ github.event_name != 'pull_request' }} | |
| - name: Install desktop dependencies | |
| run: just desktop-install-ci | |
| - name: Create sidecar placeholders | |
| run: | | |
| TARGET=$(rustc -vV | sed -n 's|host: ||p') | |
| mkdir -p desktop/src-tauri/binaries | |
| touch "desktop/src-tauri/binaries/buzz-acp-$TARGET" | |
| touch "desktop/src-tauri/binaries/buzz-agent-$TARGET" | |
| touch "desktop/src-tauri/binaries/buzz-dev-mcp-$TARGET" | |
| touch "desktop/src-tauri/binaries/git-credential-nostr-$TARGET" | |
| touch "desktop/src-tauri/binaries/buzz-$TARGET" | |
| # Mesh rev is derived from Cargo.lock so a dependency bump needs no | |
| # lockstep edit here; the cache key tracks it automatically. | |
| - name: Resolve mesh-llm rev | |
| id: mesh_rev | |
| run: | | |
| set -euo pipefail | |
| REV=$(grep -oE 'mesh-llm\.git\?rev=[0-9a-f]{40}' Cargo.lock | head -1 | grep -oE '[0-9a-f]{40}') | |
| [[ -n "$REV" ]] || { echo "::error::could not resolve mesh-llm rev from Cargo.lock"; exit 1; } | |
| echo "rev=$REV" >> "$GITHUB_OUTPUT" | |
| echo "short=${REV:0:7}" >> "$GITHUB_OUTPUT" | |
| - name: Restore mesh llama build cache | |
| id: llama_cache | |
| uses: actions/cache/restore@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 Tauri app | |
| run: cd desktop && pnpm tauri build | |
| env: | |
| 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" |