From 10474f5b946cfb6a4de82dfde8472f46741e4b58 Mon Sep 17 00:00:00 2001 From: webdevtodayjason Date: Fri, 24 Jul 2026 12:32:56 -0500 Subject: [PATCH] fix(deploy): bundle buzz-pair-relay sidecar so mobile pairing works out of the box MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The self-hosted deploy/compose bundle ran no pairing relay and advertised none, so the desktop mobile-pairing QR fell back to the legacy /pair path on the main relay — which does not serve /pair and returns 404, breaking pairing on every fresh deploy. The relay image already ships /usr/local/bin/buzz-pair-relay; it just was not wired into the bundle. - Add a pair service to compose.yml using the same image, bound inside the compose network (BUZZ_PAIR_RELAY_BIND_ADDR=0.0.0.0:5000) with a TCP healthcheck (the sidecar speaks only WS upgrades, so a plain GET can't return 200). - Route /pair on the main domain to the sidecar in the Caddy TLS overlay. The sidecar ignores the request path, so the desktop's NIP-43 legacy fallback (wss:///pair) works with zero extra DNS. - Document BUZZ_PAIRING_RELAY_URL in .env.example and README.md for split-domain and non-TLS setups. Closes #2734 Signed-off-by: webdevtodayjason --- deploy/compose/.env.example | 11 +++++++++++ deploy/compose/Caddyfile | 12 +++++++++++- deploy/compose/README.md | 26 ++++++++++++++++++++++++++ deploy/compose/compose.caddy.yml | 2 ++ deploy/compose/compose.yml | 23 +++++++++++++++++++++++ 5 files changed, 73 insertions(+), 1 deletion(-) diff --git a/deploy/compose/.env.example b/deploy/compose/.env.example index cebe879da0..7f50d87e8f 100644 --- a/deploy/compose/.env.example +++ b/deploy/compose/.env.example @@ -42,6 +42,17 @@ BUZZ_HTTP_PORT=3000 CADDY_HTTP_PORT=80 CADDY_HTTPS_PORT=443 +# Device pairing (mobile QR). The stack always runs the buzz-pair-relay sidecar. +# - TLS mode (compose.caddy.yml): Caddy routes /pair on your main domain to the +# sidecar, so the desktop's NIP-43 legacy fallback works with no config here. +# - Split-domain or your own proxy: advertise a dedicated URL so the desktop +# uses it directly instead of the /pair fallback. Must be ws:// or wss://. +# BUZZ_PAIRING_RELAY_URL=wss://pair.buzz.example.com +# - Non-TLS / direct: publish the pair service's port 5000 and point clients at +# it, e.g. BUZZ_PAIRING_RELAY_URL=ws://buzz.example.com:5000 — otherwise use +# TLS mode. See README.md § Device pairing. +# BUZZ_PAIRING_RELAY_URL= + # Dev override ports. Only used with compose.dev.yml. POSTGRES_PORT=5432 REDIS_PORT=6379 diff --git a/deploy/compose/Caddyfile b/deploy/compose/Caddyfile index 205cf4c5bc..3eef421fc3 100644 --- a/deploy/compose/Caddyfile +++ b/deploy/compose/Caddyfile @@ -1,5 +1,15 @@ {$BUZZ_DOMAIN} { encode zstd gzip - reverse_proxy relay:3000 + # Device-pairing sidecar. The desktop's NIP-43 legacy fallback resolves to + # wss://{$BUZZ_DOMAIN}/pair, so routing that path to buzz-pair-relay makes + # mobile pairing work with no extra DNS. The sidecar ignores the request path. + @pair path /pair /pair/* + handle @pair { + reverse_proxy pair:5000 + } + + handle { + reverse_proxy relay:3000 + } } diff --git a/deploy/compose/README.md b/deploy/compose/README.md index 0de524fb5b..81691e590a 100644 --- a/deploy/compose/README.md +++ b/deploy/compose/README.md @@ -41,6 +41,32 @@ keypair. Run `./run.sh backup-hint` for the backup checklist. +## Device pairing + +Mobile pairing needs a dedicated pairing relay (`buzz-pair-relay`). The desktop +generates the QR, then resolves where the phone should connect: it first reads +the main relay's NIP-11 `pairing_relay_url`, and if that is unset it falls back +to the legacy `/pair` path on the main relay. The base relay does **not** serve +`/pair`, so the stack runs a `pair` sidecar (bundled in the same relay image) +and the deployment just has to route traffic to it. Three cases: + +- **TLS (`compose.caddy.yml`) — works out of the box.** Caddy routes + `/pair` on your main domain to the sidecar, so the desktop's legacy fallback + (`wss:///pair`) Just Works with no extra DNS and nothing to set. +- **Split domain or your own reverse proxy.** Expose the sidecar at its own + host name and advertise it so the desktop uses it directly: + set `BUZZ_PAIRING_RELAY_URL=wss://pair.` in `.env` and point that + name at the `pair` service (port 5000) in your proxy. The relay then + advertises it in NIP-11 and the desktop skips the `/pair` fallback. +- **Non-TLS / direct (no Caddy).** The base stack keeps the sidecar on the + internal network only. Publish its port (add a `5000:5000` mapping to the + `pair` service) and set `BUZZ_PAIRING_RELAY_URL=ws://:5000`, or run TLS + mode. Without one of these, the desktop QR points at a `/pair` endpoint the + relay 404s and pairing fails. + +`BUZZ_PAIRING_RELAY_URL` must be a `ws://` or `wss://` URL; the relay rejects +anything else at startup. + ## Validation Before sharing an install link publicly, verify a fresh install with: diff --git a/deploy/compose/compose.caddy.yml b/deploy/compose/compose.caddy.yml index c7dcbf106c..0534e3662d 100644 --- a/deploy/compose/compose.caddy.yml +++ b/deploy/compose/compose.caddy.yml @@ -7,6 +7,8 @@ services: depends_on: relay: condition: service_healthy + pair: + condition: service_healthy environment: BUZZ_DOMAIN: ${BUZZ_DOMAIN:?set BUZZ_DOMAIN} ports: diff --git a/deploy/compose/compose.yml b/deploy/compose/compose.yml index bc3c27501e..5027198fa2 100644 --- a/deploy/compose/compose.yml +++ b/deploy/compose/compose.yml @@ -46,6 +46,29 @@ services: networks: - buzz-net + # Ephemeral NIP-AB device-pairing sidecar. The relay image already ships the + # binary; without this service the desktop QR resolves to a /pair endpoint the + # relay itself does not serve and mobile pairing fails. The sidecar binds only + # inside the compose network — a reverse proxy terminates TLS and routes /pair + # here (see compose.caddy.yml). To expose it directly, publish port 5000 and + # set BUZZ_PAIRING_RELAY_URL (see .env.example and README.md). + pair: + 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 + # The sidecar speaks only WebSocket upgrades and answers a plain GET with 400, + # so probe the raw TCP port over /dev/tcp rather than expecting an HTTP 200. + healthcheck: + test: ["CMD-SHELL", "bash -ec 'exec 3<>/dev/tcp/127.0.0.1/5000'"] + interval: 10s + timeout: 3s + retries: 6 + start_period: 5s + restart: unless-stopped + networks: + - buzz-net + postgres: image: postgres:17-alpine environment: