fix(deploy): bundle buzz-pair-relay sidecar so mobile pairing works out of the box - #2736
fix(deploy): bundle buzz-pair-relay sidecar so mobile pairing works out of the box#2736webdevtodayjason wants to merge 2 commits 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>
eec233a to
10474f5
Compare
|
Independent verification — this fixes it on a real self-hosted deployment. I hit #2734 on a self-hosted relay ( Before (no sidecar): Desktop "pair mobile device" failed with After (pair sidecar + Desktop pairing then completed successfully. Two details in this PR are load-bearing, and I can confirm both the hard way:
One small doc gap. The README's "Split domain or your own reverse proxy" bullet is the right home for operators who don't use One sentence would cover it: if your reverse proxy runs outside this compose project, attach it to the Not a blocker — the PR is correct as written. Worth landing: as of today every Compose-based deployment ships with mobile pairing broken. |
danvernon
left a comment
There was a problem hiding this comment.
Independent current-main validation from a Compose self-hoster:
- Cherry-picked
10474f5b946cfb6a4de82dfde8472f46741e4b58cleanly onto currentmainat485d03a35(135 upstream commits ahead of the PR base). docker compose configrendered successfully for the base bundle, the Caddy overlay, and the dev overlay.- The rendered base service uses
entrypoint: [/usr/local/bin/buzz-pair-relay], binds0.0.0.0:5000, retains the TCP healthcheck, and joinsbuzz-net. - The rendered TLS overlay makes Caddy depend on both healthy services and removes the relay's direct published port as expected.
git diff --checkpassed.
This also matches the failure I encountered on a real self-hosted deployment: the desktop selected /pair, which returned a WebSocket 404 until the pairing sidecar and scoped proxy route were added.
Limitation: Docker Desktop was not running during this fresh current-main check, so I did not rerun the Caddy container validation or a new live WebSocket smoke test here. The Compose render validation itself does not require the daemon.
No blocking issue found in the patch.
|
Deployed this pattern on a self-hosted instance today and hit one gap worth catching before merge. The Suggested fix, matching the ports:
- "${BUZZ_PAIR_RELAY_PORT:-5000}:5000"Validation: running this (plus the port mapping) in production behind an nginx reverse proxy, TLS terminated externally, Also confirms the underlying bug in #2734: with no |
The base bundle publishes the relay directly on BUZZ_HTTP_PORT, but kept the pairing sidecar internal-only, so split-domain and non-TLS deployments had to hand-edit compose.yml to route anything to it — and those edits conflict on the next pull of the bundle. Publish the sidecar the same way the relay is published: a BUZZ_PAIR_RELAY_PORT mapping (default 5000) in the base compose, unpublished by compose.caddy.yml where Caddy routes /pair internally. Also document the network-attach option for reverse proxies running outside this compose project, which otherwise have no way to reach the sidecar. Signed-off-by: webdevtodayjason <jason@webdevtoday.com>
7df5b3d to
7a93396
Compare
|
Good catches, all three of you. You're right that the split domain and direct paths couldn't work as documented. The docs told you to publish a port the compose file gave you no way to publish, and hand edits to a git managed bundle get clobbered on the next pull. Pushed 7a93396. It takes quentindecock's suggestion with one addition: the base compose publishes the sidecar on BUZZ_PAIR_RELAY_PORT (default 5000), same convention as BUZZ_HTTP_PORT, and compose.caddy.yml now resets the pair port the same way it already resets the relay's. So direct and split domain deployments get a real port out of the box and TLS mode still exposes nothing but Caddy. Also added genesis1tech's README note. If your reverse proxy runs outside this compose project you can either hit the published port or attach the proxy to buzz-net and target pair:5000 directly. Re-validated all three renders (base, caddy, dev), the BUZZ_PAIR_RELAY_PORT override, and that the caddy overlay resets both ports to empty. Thanks for the production reports, that's exactly the signal this needed. |
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. The base bundle publishes it onBUZZ_PAIR_RELAY_PORT(default 5000) — the same convention as the relay'sBUZZ_HTTP_PORT— andcompose.caddy.ymlresets the mapping(
ports: !reset []) exactly as it already does for the relay, so in TLS modeCaddy's
/pairroute is the only way in.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 sidecar's published port (or, for a proxy running
outside this compose project that keeps the port unpublished, attach it to
the
buzz-netnetwork and targetpair:5000); the relay then advertises itin NIP-11 and the desktop uses it directly, skipping the
/pairfallback.Semantics match
config.rs:430: must bews://orwss://, else the relayrejects it at startup.
BUZZ_PAIR_RELAY_PORT; setBUZZ_PAIRING_RELAY_URL=ws://<host>:5000.(The first revision of this PR kept the sidecar network-internal by default.
Operator reports below showed that made the split-domain and direct paths
un-followable without hand-editing
compose.yml, so7a933968switched itto the relay's published-by-default posture, reset by the Caddy overlay.)
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.b2. (
7a933968) port-publish follow-up: base render showspair.ports: 5000:5000and honors aBUZZ_PAIR_RELAY_PORT=6789override;the Caddy overlay render resets both
relay.portsandpair.portsto empty.Base, Caddy, and dev combos all re-rendered 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)