From 9da310fa42e6ed6f9f97b22051a536afb39d9f2c Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 31 Jul 2026 11:11:12 +0300 Subject: [PATCH 1/7] fix(compose): stand up buzz-pair-relay sidecar for QR pairing Membership-enforcing relays need a pairing sidecar; without it mobile/desktop QR pairing 404s on the legacy /pair path. Signed-off-by: Taksh --- deploy/compose/compose.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/deploy/compose/compose.yml b/deploy/compose/compose.yml index 15337c92a2..64dc3a6058 100644 --- a/deploy/compose/compose.yml +++ b/deploy/compose/compose.yml @@ -121,6 +121,33 @@ services: networks: - buzz-net + # Stateless NIP-AB pairing sidecar. Desktop/mobile QR pairing needs this when + # the main relay enforces membership (NIP-43) — without it, clients fall back + # to a legacy /pair path on the relay and get 404 (#3842). + # Use entrypoint (not command): the image ENTRYPOINT is buzz-relay, and + # Compose `command:` only appends args. + 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 + ports: + # Exposed for non-Caddy installs; TLS setups reach it via Caddy /pair*. + - "${BUZZ_PAIR_RELAY_PORT:-5000}:5000" + 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 + volumes: buzz-postgres-data: labels: From 555a69892cbb531ead77934d48885b2a691b44a7 Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 31 Jul 2026 11:11:51 +0300 Subject: [PATCH 2/7] fix(compose): advertise BUZZ_PAIRING_RELAY_URL in NIP-11 Point clients at the sidecar URL instead of inferring a broken /pair path from NIP-43 membership advertisement alone. Signed-off-by: Taksh --- deploy/compose/compose.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/deploy/compose/compose.yml b/deploy/compose/compose.yml index 64dc3a6058..9173469459 100644 --- a/deploy/compose/compose.yml +++ b/deploy/compose/compose.yml @@ -20,6 +20,9 @@ services: BUZZ_GIT_REPO_PATH: /data/git BUZZ_AUTO_MIGRATE: ${BUZZ_AUTO_MIGRATE:-false} BUZZ_GIT_CONFORMANCE_PROBE: ${BUZZ_GIT_CONFORMANCE_PROBE:-true} + # Advertise the pairing sidecar in NIP-11 so clients don't fall back to a + # non-existent legacy /pair path on the main relay (see #3842). + BUZZ_PAIRING_RELAY_URL: ${BUZZ_PAIRING_RELAY_URL:?set BUZZ_PAIRING_RELAY_URL} ports: - "${BUZZ_HTTP_PORT:-3000}:3000" volumes: From 710ec14eefba48fdf9a72021af70ab4f252dbcbd Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 31 Jul 2026 11:11:51 +0300 Subject: [PATCH 3/7] fix(compose): proxy /pair* to pairing-relay in Caddy TLS installs terminate on Caddy; route pairing traffic to the sidecar before the catch-all reverse_proxy to the main relay. Signed-off-by: Taksh --- deploy/compose/Caddyfile | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deploy/compose/Caddyfile b/deploy/compose/Caddyfile index 205cf4c5bc..cc3577d359 100644 --- a/deploy/compose/Caddyfile +++ b/deploy/compose/Caddyfile @@ -1,5 +1,11 @@ {$BUZZ_DOMAIN} { encode zstd gzip + # Device-pairing sidecar (buzz-pair-relay). Must be before the catch-all so + # NIP-AB QR sessions do not hit the main relay and 404 (#3842). + handle_path /pair* { + reverse_proxy pairing-relay:5000 + } + reverse_proxy relay:3000 } From fcd53c0a13b107df2eb933cfd9c18d1e01c9831f Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 31 Jul 2026 11:11:51 +0300 Subject: [PATCH 4/7] docs(compose): document BUZZ_PAIRING_RELAY_URL and pair port Give operators a copy-pasteable NIP-11 pairing URL for both Caddy and direct-port deployments. Signed-off-by: Taksh --- deploy/compose/.env.example | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/deploy/compose/.env.example b/deploy/compose/.env.example index f6ab4fcab9..bb2cb27805 100644 --- a/deploy/compose/.env.example +++ b/deploy/compose/.env.example @@ -8,6 +8,10 @@ BUZZ_IMAGE=ghcr.io/block/buzz:main # Public host name. Used by compose.caddy.yml and URL-derived settings below. BUZZ_DOMAIN=buzz.example.com RELAY_URL=wss://buzz.example.com +# Dedicated NIP-AB pairing URL advertised in NIP-11. With compose.caddy.yml this +# is same-host /pair (proxied to pairing-relay). Without Caddy, point at the +# published pairing port instead, e.g. ws://buzz.example.com:5000. +BUZZ_PAIRING_RELAY_URL=wss://buzz.example.com/pair BUZZ_MEDIA_BASE_URL=https://buzz.example.com/media BUZZ_MEDIA_SERVER_DOMAIN=buzz.example.com BUZZ_CORS_ORIGINS=https://buzz.example.com @@ -38,6 +42,8 @@ BUZZ_S3_ADDRESSING_STYLE=path # Optional host ports. Base compose publishes the relay directly on BUZZ_HTTP_PORT. BUZZ_HTTP_PORT=3000 +# Pairing sidecar host port (non-Caddy). Caddy TLS installs use /pair on 443 instead. +BUZZ_PAIR_RELAY_PORT=5000 # Caddy host ports. Only used with compose.caddy.yml. CADDY_HTTP_PORT=80 From 321398e0e3dfa8d8bb6e009527cef1aed0f50685 Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 31 Jul 2026 11:11:52 +0300 Subject: [PATCH 5/7] docs(compose): explain pairing-relay setup for mobile QR Call out the TLS vs non-TLS URL shapes so self-hosters do not ship a membership relay that advertises a dead pairing path. Signed-off-by: Taksh --- deploy/compose/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/deploy/compose/README.md b/deploy/compose/README.md index bb0e63fe15..b80f353991 100644 --- a/deploy/compose/README.md +++ b/deploy/compose/README.md @@ -38,6 +38,13 @@ keypair. migrations. - The stack uses Postgres, Redis, MinIO, and a git data volume because those are real Buzz dependencies today. Minimal mode can simplify this later. +- Mobile QR pairing needs the `pairing-relay` sidecar. Compose stands it up on + port 5000 and advertises `BUZZ_PAIRING_RELAY_URL` in NIP-11. With + `BUZZ_COMPOSE_TLS=true`, Caddy proxies `/pair*` to that sidecar — set + `BUZZ_PAIRING_RELAY_URL=wss:///pair`. Without Caddy, point + `BUZZ_PAIRING_RELAY_URL` at the published pairing port instead + (e.g. `ws://:5000`). Skipping this leaves membership-enforcing relays + advertising a pairing path they cannot serve. - The bundled Compose stack fixes the relay endpoint to `http://minio:9000` and `BUZZ_S3_ADDRESSING_STYLE=path`: Docker DNS resolves `minio`, not `.minio`. It is not configurable for an external S3 provider through From dcf86131a08a05c679198b7cbd1ba5bc0f63e668 Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 31 Jul 2026 11:12:06 +0300 Subject: [PATCH 6/7] fix(compose): wait for pairing-relay before starting Caddy Avoid racing /pair traffic against a sidecar that is still binding its listen port on first boot. Signed-off-by: Taksh --- deploy/compose/compose.caddy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/deploy/compose/compose.caddy.yml b/deploy/compose/compose.caddy.yml index c7dcbf106c..cf7c4f372a 100644 --- a/deploy/compose/compose.caddy.yml +++ b/deploy/compose/compose.caddy.yml @@ -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: From fa0aa79b4decef1a3ae3647c19231315b752f16d Mon Sep 17 00:00:00 2001 From: Taksh Date: Fri, 31 Jul 2026 11:12:06 +0300 Subject: [PATCH 7/7] docs(compose): mention pairing URL shapes in run.sh help Operators reading ./run.sh help should see how TLS vs direct-port pairing URLs differ before they open .env.example. Signed-off-by: Taksh --- deploy/compose/run.sh | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/deploy/compose/run.sh b/deploy/compose/run.sh index d5465ea1f5..84613b20b6 100755 --- a/deploy/compose/run.sh +++ b/deploy/compose/run.sh @@ -122,7 +122,12 @@ Commands: Environment switches: BUZZ_COMPOSE_TLS=true Include compose.caddy.yml for automatic HTTPS + (also proxies /pair* to the pairing-relay sidecar) BUZZ_COMPOSE_DEV=true Include compose.dev.yml for local admin ports/tools + +Pairing: + Set BUZZ_PAIRING_RELAY_URL in .env (see .env.example). TLS installs use + wss:///pair; non-Caddy installs use the published pair port. MSG ;; *)