Skip to content
Open
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
8 changes: 8 additions & 0 deletions deploy/compose/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@ BUZZ_CORS_ORIGINS=https://buzz.example.com
BUZZ_REQUIRE_AUTH_TOKEN=true
BUZZ_REQUIRE_RELAY_MEMBERSHIP=true
BUZZ_ALLOW_NIP_OA_AUTH=true
# BUZZ_REQUIRE_RELAY_MEMBERSHIP=true makes the relay advertise NIP-43 (device
# pairing) in its NIP-11 doc, which the mobile/desktop app requires for
# pairing. The compose bundle runs a pairing-relay sidecar for this — see
# compose.yml and (with BUZZ_COMPOSE_TLS=true) the Caddyfile's /pair route.
# This URL is what gets advertised; leave it pointed at your domain's /pair
# path so pairing "just works". Set to empty only if you provide your own
# /pair routing elsewhere, or if you set BUZZ_REQUIRE_RELAY_MEMBERSHIP=false.
BUZZ_PAIRING_RELAY_URL=wss://buzz.example.com/pair
BUZZ_AUTO_MIGRATE=true
BUZZ_GIT_CONFORMANCE_PROBE=true
RUST_LOG=buzz_relay=info,buzz_db=info,buzz_auth=info,buzz_pubsub=info,tower_http=info
Expand Down
10 changes: 9 additions & 1 deletion deploy/compose/Caddyfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
{$BUZZ_DOMAIN} {
encode zstd gzip

reverse_proxy relay:3000
# route {} disables Caddy's automatic directive sorting for this block, so
# the /pair match below is always tried before the catch-all reverse_proxy.
# Caddy proxies WebSocket upgrades transparently — no extra directive needed.
route {
@pairing path /pair /pair/*
reverse_proxy @pairing pairing-relay:5000

reverse_proxy relay:3000
}
}
29 changes: 29 additions & 0 deletions deploy/compose/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,35 @@ keypair.

Run `./run.sh backup-hint` for the backup checklist.

## Mobile pairing

`BUZZ_REQUIRE_RELAY_MEMBERSHIP=true` (the production default in `.env.example`)
makes the relay advertise NIP-43 device pairing in its NIP-11 document,
regardless of whether a pairing relay is actually reachable. Without the
wiring below, the desktop/mobile app's pairing probe finds NIP-43 advertised,
falls back to the legacy `<relay>/pair` convention, and gets
`HTTP error: 404 Not Found` — there is nothing listening on `/pair`.

To make mobile pairing work out of the box, this bundle includes:

- A `pairing-relay` service in `compose.yml` — the same image as `relay`,
running the bundled `buzz-pair-relay` binary instead. It's stateless and
needs no database/Redis/secrets.
- A `BUZZ_PAIRING_RELAY_URL` env var (see `.env.example`) that the main relay
advertises directly in NIP-11, so the client connects to the pairing relay
without needing the legacy fallback at all.
- With `BUZZ_COMPOSE_TLS=true` (`compose.caddy.yml`), a `/pair` route in the
`Caddyfile` that proxies to `pairing-relay:5000`, including the WebSocket
upgrade.

If you're **not** using `compose.caddy.yml` (bringing your own reverse
proxy), you must add an equivalent `/pair` → `pairing-relay:5000` route
yourself — see `crates/buzz-pair-relay`'s module docs for the requirements
(route only `/pair`, terminate TLS, enforce read timeouts). Without that
route, either wire it up or set `BUZZ_REQUIRE_RELAY_MEMBERSHIP=false` and
clear `BUZZ_PAIRING_RELAY_URL` to disable device pairing entirely on an open
relay.

## Validation

Before sharing an install link publicly, verify a fresh install with:
Expand Down
2 changes: 2 additions & 0 deletions deploy/compose/compose.caddy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ services:
depends_on:
relay:
condition: service_healthy
pairing-relay:
condition: service_healthy
environment:
BUZZ_DOMAIN: ${BUZZ_DOMAIN:?set BUZZ_DOMAIN}
ports:
Expand Down
34 changes: 34 additions & 0 deletions deploy/compose/compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,14 @@ services:
BUZZ_GIT_REPO_PATH: /data/git
BUZZ_AUTO_MIGRATE: ${BUZZ_AUTO_MIGRATE:-false}
BUZZ_GIT_CONFORMANCE_PROBE: ${BUZZ_GIT_CONFORMANCE_PROBE:-true}
# Advertised in NIP-11 so the desktop/mobile pairing client connects
# directly instead of falling back to the legacy "<relay>/pair"
# convention. Required for mobile pairing whenever
# BUZZ_REQUIRE_RELAY_MEMBERSHIP=true, since that flag makes the relay
# advertise NIP-43 (device pairing) whether or not a pairing relay is
# actually wired up. Points at the bundled `pairing-relay` service
# below, routed through /pair — see compose.caddy.yml + Caddyfile.
BUZZ_PAIRING_RELAY_URL: ${BUZZ_PAIRING_RELAY_URL:-}
ports:
- "${BUZZ_HTTP_PORT:-3000}:3000"
volumes:
Expand Down Expand Up @@ -46,6 +54,32 @@ services:
networks:
- buzz-net

# Sidecar relay for NIP-AB mobile device pairing (kind:24134 handshake).
# Same image as `relay` (the runtime bundles buzz-relay, buzz-admin, and
# buzz-pair-relay), just a different entrypoint. Stateless/ephemeral — no
# DB, Redis, or secrets required. Not published to the host: it must sit
# behind a reverse proxy that routes only /pair to it (see
# crates/buzz-pair-relay's module docs for why). compose.caddy.yml wires
# that route automatically; a custom reverse proxy needs the equivalent
# rule pointed at pairing-relay:5000 over the buzz-net network.
pairing-relay:
image: ${BUZZ_IMAGE:-ghcr.io/block/buzz:main}
entrypoint: ["/usr/local/bin/buzz-pair-relay"]
environment:
BUZZ_PAIR_RELAY_BIND_ADDR: 0.0.0.0:5000
# Plain TCP connect check (buzz-pair-relay has no HTTP health endpoint —
# matches the tcpSocket probe used for this same binary in
# deploy/charts/buzz/templates/pairing-relay.yaml).
healthcheck:
test: ["CMD-SHELL", "bash -ec 'exec 3<>/dev/tcp/127.0.0.1/5000'"]
interval: 10s
timeout: 3s
retries: 12
start_period: 10s
restart: unless-stopped
networks:
- buzz-net

postgres:
image: postgres:17-alpine
environment:
Expand Down