Skip to content

fix(deploy): wire mobile pairing relay into docker-compose self-host bundle - #4082

Open
Duncan-Slade wants to merge 1 commit into
block:mainfrom
Duncan-Slade:fix/self-hosted-pairing-404
Open

fix(deploy): wire mobile pairing relay into docker-compose self-host bundle#4082
Duncan-Slade wants to merge 1 commit into
block:mainfrom
Duncan-Slade:fix/self-hosted-pairing-404

Conversation

@Duncan-Slade

Copy link
Copy Markdown

Summary

Self-hosted relays deployed via deploy/compose/ with BUZZ_REQUIRE_RELAY_MEMBERSHIP=true (the production default in .env.example) advertise NIP-43 in their NIP-11 document regardless of whether a pairing relay is actually deployed (crates/buzz-relay/src/nip11.rs advertise_nip43). The desktop/mobile pairing client (desktop/src-tauri/src/commands/pairing.rs probe_pairing_relay / pairing_relay_from_nip11, ~lines 420-490) then falls back to the legacy "<relay>/pair" convention — but the compose bundle never ran buzz-pair-relay or routed /pair anywhere, so mobile pairing failed with WebSocket connection failed: HTTP error: 404 Not Found.

The published image already bundles the buzz-pair-relay binary alongside buzz-relay/buzz-admin (see Dockerfile), and the relay already reads BUZZ_PAIRING_RELAY_URL to advertise pairing_relay_url directly in NIP-11 (crates/buzz-relay/src/config.rs, nip11.rs) — so this is a deploy-config-only fix, no Rust changes needed. Mirrors the existing Helm chart's pattern (deploy/charts/buzz/templates/pairing-relay.yaml), which was not in scope here.

  • deploy/compose/compose.yml: add a pairing-relay service (same image, entrypoint override to buzz-pair-relay) and pass through BUZZ_PAIRING_RELAY_URL on the relay service.
  • deploy/compose/compose.caddy.yml: caddy now also depends on pairing-relay being healthy.
  • deploy/compose/Caddyfile: route /pair (and /pair/*) to pairing-relay:5000 ahead of the catch-all reverse_proxy relay:3000.
  • deploy/compose/.env.example: document and default BUZZ_PAIRING_RELAY_URL=wss://buzz.example.com/pair.
  • deploy/compose/README.md: new "Mobile pairing" section explaining the 404, what's wired automatically, and options for custom reverse proxies or open relays that don't want pairing.

Test plan

  • Validated all three compose files parse cleanly with PyYAML (registering Compose's !reset tag); pairing-relay service present in the merged service list.
  • Reviewed Caddyfile syntax by hand against Caddy's documented route {} + named-matcher pattern (no caddy/docker binaries available in this sandbox to run caddy validate / docker compose config).
  • docker compose -f deploy/compose/compose.yml -f deploy/compose/compose.caddy.yml config on a machine with Docker installed.
  • End-to-end: deploy with BUZZ_COMPOSE_TLS=true, confirm curl -s -H "Accept: application/nostr+json" https://<domain>/ returns pairing_relay_url: wss://<domain>/pair, then complete an actual mobile pairing handshake.

🤖 Generated with Claude Code

…bundle

Self-hosted relays with BUZZ_REQUIRE_RELAY_MEMBERSHIP=true (the production
default in deploy/compose/.env.example) advertise NIP-43 in their NIP-11
document regardless of whether a pairing relay is actually deployed
(crates/buzz-relay/src/nip11.rs advertise_nip43). The desktop/mobile pairing
client (desktop/src-tauri/src/commands/pairing.rs probe_pairing_relay /
pairing_relay_from_nip11) then falls back to the legacy "<relay>/pair"
convention, but the compose bundle never ran buzz-pair-relay or routed /pair
anywhere, so pairing failed with "WebSocket connection failed: HTTP error:
404 Not Found".

The published image already bundles the buzz-pair-relay binary alongside
buzz-relay/buzz-admin (Dockerfile), and the relay already reads
BUZZ_PAIRING_RELAY_URL to advertise pairing_relay_url directly in NIP-11
(crates/buzz-relay/src/config.rs, nip11.rs) — so this is a deploy-config-only
fix, no Rust changes needed. Mirrors the existing Helm chart's pattern
(deploy/charts/buzz/templates/pairing-relay.yaml).

- deploy/compose/compose.yml: add a pairing-relay service (same image,
  entrypoint override to buzz-pair-relay) and pass through
  BUZZ_PAIRING_RELAY_URL on the relay service.
- deploy/compose/compose.caddy.yml: caddy now also depends on
  pairing-relay being healthy.
- deploy/compose/Caddyfile: route /pair (and /pair/*) to pairing-relay:5000
  ahead of the catch-all reverse_proxy to relay:3000.
- deploy/compose/.env.example: document and default
  BUZZ_PAIRING_RELAY_URL=wss://buzz.example.com/pair.
- deploy/compose/README.md: new "Mobile pairing" section explaining the
  404, what's wired automatically, and the options for custom reverse
  proxies or open relays that don't want pairing.

Co-authored-by: Sladey <slade198@gmail.com>

Signed-off-by: Sladey <slade198@gmail.com>
@Duncan-Slade
Duncan-Slade requested a review from a team as a code owner August 1, 2026 09:37
@neuraljohnny

Copy link
Copy Markdown

Running the equivalent wiring in production on a self-hosted deploy (Caddy + buzz-pair-relay from the same image, relay on main). One difference from this PR that may matter:

This uses a named matcher, so the upstream path is not rewritten — buzz-pair-relay receives /pair. I used handle_path, which strips the prefix, so it receives /.

Measuring against the running container on the compose network, WebSocket upgrade, one path per row:

/                -> 101
/pair            -> no response at all (timed out, 0 bytes)
/pair/           -> 101
/anything-else   -> 101

So it isn't that the sidecar only serves the root — it answers essentially any path except exactly /pair, which is the one this config forwards.

That's also the path clients dial. resolve_pairing_relay_url in the desktop builds the legacy-path URL as {path}/pair with no trailing slash, and BUZZ_PAIRING_RELAY_URL=wss://host/pair in .env.example matches. So the request arrives as /pair.

If that reproduces for you, the symptom would be a hang rather than the 404 this fixes — which is harder to diagnose, since nothing is logged on either side.

Two options that both worked here:

handle_path /pair* {
  reverse_proxy pairing-relay:5000
}

or keeping the route block and adding uri strip_prefix /pair before the proxy.

One thing I can't explain: why /pair specifically gets no response while /pair/ and arbitrary paths return 101. That asymmetry may be worth a look independently of the Caddy config — it smells like a routing quirk in buzz-pair-relay rather than something this PR introduces.

Everything else here matches what I ended up with, including BUZZ_PAIRING_RELAY_URL and reusing the relay image with a different entrypoint. Thanks for picking this up — pairing against a NIP-43 relay is unusable without it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants