fix(deploy): bundle buzz-pair-relay sidecar so mobile pairing works out of the box#2736
Open
webdevtodayjason wants to merge 1 commit into
Open
fix(deploy): bundle buzz-pair-relay sidecar so mobile pairing works out of the box#2736webdevtodayjason wants to merge 1 commit into
webdevtodayjason wants to merge 1 commit into
Conversation
…ut of the box 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://<domain>/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 block#2734 Signed-off-by: webdevtodayjason <jason@webdevtoday.com>
webdevtodayjason
force-pushed
the
fix/compose-pairing-relay
branch
from
July 24, 2026 19:28
eec233a to
10474f5
Compare
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Closes #2734.
On a self-hosted relay deployed from the official
deploy/composebundle,mobile pairing is broken for every user out of the box: the desktop generates a
QR, the phone scans it, and the connection errors out.
Root cause is a missing sidecar plus dead-endpoint fallback:
start_pairing(desktop) probes the main relay's NIP-11 forpairing_relay_url. The compose bundle sets noBUZZ_PAIRING_RELAY_URL, sothe field is absent (
crates/buzz-relay/src/config.rs:430→nip11.rs:243).pairing_relay_from_nip11(
desktop/src-tauri/src/commands/pairing.rs:469) resolves to the legacy/pairpath on the main relay:resolve_pairing_relay_urlturnswss://<domain>intowss://<domain>/pair.buzz-relaydoes not serve/pair— no such route — so the QR encodes adead endpoint (
GET /pair → 404, WS upgrade/pair → 404) and the phonefails to connect.
The relay image already ships the fix:
/usr/local/bin/buzz-pair-relayispresent in
ghcr.io/block/buzz:main; it simply was never wired into the bundle.Fix
Bundle- and docs-only. No relay/desktop source changes.
Add the
pairservice tocompose.ymlusing the same relay image withentrypoint: ["/usr/local/bin/buzz-pair-relay"]andBUZZ_PAIR_RELAY_BIND_ADDR=0.0.0.0:5000. It binds inside the compose networkonly — matching the sidecar's own security model, which documents it as
loopback-only behind a reverse proxy (
crates/buzz-pair-relay/src/lib.rs:9).Healthcheck is a raw-TCP
/dev/tcpprobe: the sidecar speaks only WebSocketupgrades and answers a plain
GETwith400(lib.rs:943), so an HTTPhealthcheck would never see a
200.Route
/pairto the sidecar in the Caddy TLS overlay (Caddyfile), via anamed path matcher
@pair path /pair /pair/*→reverse_proxy pair:5000,with everything else falling through to
relay:3000. This is the key designdecision: the desktop's NIP-43 legacy fallback already targets
wss://<domain>/pair, so a path proxy on the main domain makes pairingwork with zero extra DNS and nothing for the operator to set. Verified in
the pair-relay source that the sidecar ignores the request path entirely
(
http_serviceonly inspects the WS-upgrade headers —lib.rs:913), so apath proxy needs no prefix stripping.
compose.caddy.ymlalso gains adepends_on: pair(service_healthy) so Caddy waits for the sidecar.Document
BUZZ_PAIRING_RELAY_URLin.env.example(commented) and a newREADME "Device pairing" section, covering three operator cases:
compose.caddy.yml) — works out of the box via the/pairpathproxy; nothing to set.
BUZZ_PAIRING_RELAY_URL=wss://pair.<domain>and route that name at the
pairservice; the relay then advertises it inNIP-11 and the desktop uses it directly, skipping the
/pairfallback.Semantics match
config.rs:430: must bews://orwss://, else the relayrejects it at startup.
publish port
5000and setBUZZ_PAIRING_RELAY_URL=ws://<host>:5000, or useTLS mode. Documented rather than exposing an unauthenticated port by default,
consistent with the sidecar's loopback-only posture.
Why path-proxy over a dedicated
pair.<domain>The dedicated-subdomain approach (what we ran in production tonight,
wss://pair.<domain>+BUZZ_PAIRING_RELAY_URL) works but forces everyoperator to add a second DNS record and a cert. Routing
/pairon the existingdomain reuses the NIP-43 legacy fallback the desktop already emits, so the
common single-domain deploy needs no extra config at all. The split-domain path
remains fully supported via the documented env var for operators who want it.
Tests / Validation
Production-verified tonight (per #2734): the equivalent
pairservice +BUZZ_PAIRING_RELAY_URL=wss://pair.<domain>made NIP-11 advertisepairing_relay_url, the desktop QR encode the working endpoint, and pairingproceed end to end on a live self-hosted relay.
Bundle validation with a stub
.env(.env.examplecopied, CHANGE_MEs filledwith dummies):
a. Base compose renders clean, sidecar present:
b. Caddy overlay combo renders clean, ports reset + depends_on wired:
Dev overlay (
-f compose.dev.yml) and the full three-file combo also render clean.c. Caddyfile validates and routes correctly (via
caddy:2-alpine):d. Pre-push gate (all green — the branch touches no Rust/TS, but hooks ran the
full suite anyway):
Follow-up (out of scope for this PR)
The Helm chart (
squareup/block-coder-tf-stacks) has the same pairing gap — italso ships no pair sidecar and sets no
BUZZ_PAIRING_RELAY_URL. Worth aseparate change on the chart side; not bundled here to keep this diff surgical
(compose bundle + docs only).
Checklist
docker compose configrenders clean for base, Caddy overlay, and dev overlaycaddy validatepasses;/pairroutes to the sidecar, default to the relayBUZZ_PAIRING_RELAY_URL) documented in.env.example+ READMEunwrap()/unsafe(no code changes)