Montar Buzz (block/buzz) self-hosted: piloto Claude Code (Mixbot + Tu) #9

Open
opened 2026-08-08 23:38:01 +00:00 by rafa · 5 comments
Owner

Que es

Buzz (github.com/block/buzz, Apache 2.0, lanzado 2026-07-21 por Block/Jack Dorsey): workspace tipo Slack+GitHub donde humanos y agentes IA comparten canales sobre un relay Nostr propio. Cada participante (humano o agente) tiene su propia clave criptografica; cada mensaje/patch/review es un evento firmado con audit trail.

Arquitectura: buzz-relay (Rust, WS+REST) + Postgres + Redis + MinIO. Agentes externos (Claude Code, Codex, Goose) se conectan via buzz-acp, que traduce Agent Client Protocol <-> MCP. Deploy productivo: deploy/compose/ (compose.yml + Caddyfile opcional + run.sh).

Objetivo de este issue

Piloto acotado: desplegar el relay self-hosted en el Hetzner (Coolify) y conectar solo los dos agentes con soporte ACP nativo -- Mixbot (Claude Code de Inma) y yo (Claude Code de Rafa). Hermes queda fuera de este piloto: no es Claude Code/Codex/Goose, no hay bridge ACP publicado para el; conectarlo requeriria un adaptador propio hablando el protocolo Nostr del relay directo (o via su patron de gateway existente tipo Telegram/WhatsApp). Se evalua en un issue aparte una vez validado el piloto.

Plan

  1. Dominio: buzz.rafacalvo.nyc (DNS en Spaceship -- sin token API guardado, lo crea Rafa a mano en el panel).
  2. Coolify: recurso Docker Compose nuevo apuntando al compose.yml de block/buzz (deploy/compose/). TLS gestionado por Traefik/Coolify (NO por el Caddy que trae el propio compose, para evitar doble terminacion -- mismo patron que gitea.feadulta.com). Vigilar la trampa traefik.docker.network=coolify (ver wiki Backups / server-hetzner-traefik-routing).
  3. .env del compose: generar BUZZ_RELAY_PRIVATE_KEY estable, RELAY_OWNER_PUBKEY = pubkey de Rafa (relay cerrado, no publico), BUZZ_GIT_HOOK_HMAC_SECRET, BUZZ_AUTO_MIGRATE=true, BUZZ_S3_ADDRESSING_STYLE=path (MinIO). Secretos generados van al .env del servidor, nunca a memoria/gitea en claro.
  4. Levantar el stack, verificar liveness endpoint.
  5. Alta identidades: Rafa (humano, owner del relay) y Mixbot + Tu (agentes) via buzz-acp con BUZZ_PRIVATE_KEY propio cada uno, anadidos a un canal de prueba.
  6. Validar: mensaje cruzado + una operacion de repo (patch) entre los dos agentes.
  7. Cerrar este issue y abrir uno nuevo para evaluar el bridge de Hermes si el piloto funciona.

Ref.: politica todo-estandar-Coolify (memoria feedback-coolify-standard-no-adhoc), politica documentar-planificar-validar-ejecutar (memoria feedback-process-plan-validate-execute).

## Que es Buzz (github.com/block/buzz, Apache 2.0, lanzado 2026-07-21 por Block/Jack Dorsey): workspace tipo Slack+GitHub donde humanos y agentes IA comparten canales sobre un relay Nostr propio. Cada participante (humano o agente) tiene su propia clave criptografica; cada mensaje/patch/review es un evento firmado con audit trail. Arquitectura: buzz-relay (Rust, WS+REST) + Postgres + Redis + MinIO. Agentes externos (Claude Code, Codex, Goose) se conectan via buzz-acp, que traduce Agent Client Protocol <-> MCP. Deploy productivo: deploy/compose/ (compose.yml + Caddyfile opcional + run.sh). ## Objetivo de este issue Piloto acotado: desplegar el relay self-hosted en el Hetzner (Coolify) y conectar solo los dos agentes con soporte ACP nativo -- Mixbot (Claude Code de Inma) y yo (Claude Code de Rafa). Hermes queda fuera de este piloto: no es Claude Code/Codex/Goose, no hay bridge ACP publicado para el; conectarlo requeriria un adaptador propio hablando el protocolo Nostr del relay directo (o via su patron de gateway existente tipo Telegram/WhatsApp). Se evalua en un issue aparte una vez validado el piloto. ## Plan 1. Dominio: buzz.rafacalvo.nyc (DNS en Spaceship -- sin token API guardado, lo crea Rafa a mano en el panel). 2. Coolify: recurso Docker Compose nuevo apuntando al compose.yml de block/buzz (deploy/compose/). TLS gestionado por Traefik/Coolify (NO por el Caddy que trae el propio compose, para evitar doble terminacion -- mismo patron que gitea.feadulta.com). Vigilar la trampa traefik.docker.network=coolify (ver wiki Backups / server-hetzner-traefik-routing). 3. .env del compose: generar BUZZ_RELAY_PRIVATE_KEY estable, RELAY_OWNER_PUBKEY = pubkey de Rafa (relay cerrado, no publico), BUZZ_GIT_HOOK_HMAC_SECRET, BUZZ_AUTO_MIGRATE=true, BUZZ_S3_ADDRESSING_STYLE=path (MinIO). Secretos generados van al .env del servidor, nunca a memoria/gitea en claro. 4. Levantar el stack, verificar liveness endpoint. 5. Alta identidades: Rafa (humano, owner del relay) y Mixbot + Tu (agentes) via buzz-acp con BUZZ_PRIVATE_KEY propio cada uno, anadidos a un canal de prueba. 6. Validar: mensaje cruzado + una operacion de repo (patch) entre los dos agentes. 7. Cerrar este issue y abrir uno nuevo para evaluar el bridge de Hermes si el piloto funciona. Ref.: politica todo-estandar-Coolify (memoria feedback-coolify-standard-no-adhoc), politica documentar-planificar-validar-ejecutar (memoria feedback-process-plan-validate-execute).
Author
Owner

Progreso (2026-08-08):

  • Repo block/buzz clonado en el Hetzner (/home/rafa/buzz), inspeccionados deploy/compose/* reales (no solo la doc): compose.yml trae Postgres+Redis+MinIO+relay, todo con healthchecks; compose.caddy.yml es opcional (lo dejamos fuera, TLS lo hace Traefik/Coolify).
  • DNS buzz.rafacalvo.nyc ya creado por Rafa.
  • Identidades Nostr generadas con nostr-tools (la misma libreria que usa el propio proyecto en web/desktop, evita el bug de paridad BIP-340 de generarlas a mano con openssl):
    • Owner (Rafa): pubkey cf47a61eb7e9b3c2036851c582d600db30e19a6763a9873e4014353687e25013. Clave privada entregada a Rafa por chat, no queda en disco/memoria/gitea.
    • Relay (operacional, secreto de servidor): generada y guardada solo en deploy/compose/.env (600, no memoria).
    • Agente Claude Code (Windows, "Tu"): pubkey 90c7e45dc57ee7e4467f974ef475748cc1c635e133a3a453024f760af3eb1968. Clave privada en ~/.buzz/agent-claude-windows.env (WSL, 600) para cuando conectemos via buzz-acp.
  • .env de produccion completo escrito en deploy/compose/.env (permisos 600, sin CHANGE_ME pendientes): dominio, RELAY_OWNER_PUBKEY, BUZZ_RELAY_PRIVATE_KEY, secretos DB/Redis/MinIO/HMAC generados con openssl. Puerto 3000 confirmado libre en el host.
  • Pendiente: crear el recurso Docker Compose en Coolify (asignar dominio via panel, red coolify para Traefik) y run.sh start. Se confirma con Rafa antes de exponerlo publicamente por ser un proyecto externo muy reciente (v0.4.21, sin auditar).
Progreso (2026-08-08): - Repo `block/buzz` clonado en el Hetzner (`/home/rafa/buzz`), inspeccionados `deploy/compose/*` reales (no solo la doc): compose.yml trae Postgres+Redis+MinIO+relay, todo con healthchecks; compose.caddy.yml es opcional (lo dejamos fuera, TLS lo hace Traefik/Coolify). - DNS `buzz.rafacalvo.nyc` ya creado por Rafa. - Identidades Nostr generadas con `nostr-tools` (la misma libreria que usa el propio proyecto en web/desktop, evita el bug de paridad BIP-340 de generarlas a mano con openssl): - Owner (Rafa): pubkey `cf47a61eb7e9b3c2036851c582d600db30e19a6763a9873e4014353687e25013`. Clave privada entregada a Rafa por chat, no queda en disco/memoria/gitea. - Relay (operacional, secreto de servidor): generada y guardada solo en `deploy/compose/.env` (600, no memoria). - Agente Claude Code (Windows, "Tu"): pubkey `90c7e45dc57ee7e4467f974ef475748cc1c635e133a3a453024f760af3eb1968`. Clave privada en `~/.buzz/agent-claude-windows.env` (WSL, 600) para cuando conectemos via buzz-acp. - `.env` de produccion completo escrito en `deploy/compose/.env` (permisos 600, sin CHANGE_ME pendientes): dominio, RELAY_OWNER_PUBKEY, BUZZ_RELAY_PRIVATE_KEY, secretos DB/Redis/MinIO/HMAC generados con openssl. Puerto 3000 confirmado libre en el host. - Pendiente: crear el recurso Docker Compose en Coolify (asignar dominio via panel, red `coolify` para Traefik) y `run.sh start`. Se confirma con Rafa antes de exponerlo publicamente por ser un proyecto externo muy reciente (v0.4.21, sin auditar).
Author
Owner

Desplegado (2026-08-08), solo-Tailscale segun lo pedido:

  • Servicio Coolify buzz-relay (uuid momxc6dl0742b8eulckryjlx, proyecto "My first project", server localhost) creado via API con docker_compose_raw = compose.yml original de block/buzz sin modificar, envs subidas via PATCH /services/{uuid}/envs/bulk. Sin dominio/FQDN asignado en Coolify -> Traefik no lo toca.
  • BUZZ_HTTP_PORT=127.0.0.1:3000 en el .env (el propio truco de docker compose IP:HOSTPORT:CONTAINERPORT via la var) -> el relay NO escucha en la interfaz publica, solo loopback.
  • tailscale serve --bg --tls-terminated-tcp=8443 tcp://127.0.0.1:3000 (modo TCP terminado, no proxy http, para evitar el bug de WebSockets ya documentado en memoria con Tailscale Serve). Accesible en https://srv1.taild3aaf6.ts.net:8443.
  • UFW: regla nueva allow in on tailscale0 to any port 8443 proto tcp (no "Anywhere", solo esa interfaz).
  • Verificado: los 4 contenedores (relay/postgres/redis/minio) healthy, /_readiness responde 200 tanto en localhost como via tailnet desde la maquina local (farmer). Confirmado que NO responde nada en la IP publica ni puerto 3000 ni 8443 (timeout, como se esperaba).
  • buzz.rafacalvo.nyc (DNS ya creado por Rafa) queda sin usar por ahora, no apunta a nada activo.

Pendiente: conectar mi identidad (Claude Code / "Tu") via buzz-acp a un canal de prueba y validar mensaje+patch. Mixbot queda para una fase posterior (decision de Rafa).

Desplegado (2026-08-08), solo-Tailscale segun lo pedido: - Servicio Coolify `buzz-relay` (uuid `momxc6dl0742b8eulckryjlx`, proyecto "My first project", server localhost) creado via API con `docker_compose_raw` = compose.yml original de block/buzz sin modificar, envs subidas via `PATCH /services/{uuid}/envs/bulk`. Sin dominio/FQDN asignado en Coolify -> Traefik no lo toca. - `BUZZ_HTTP_PORT=127.0.0.1:3000` en el `.env` (el propio truco de docker compose `IP:HOSTPORT:CONTAINERPORT` via la var) -> el relay NO escucha en la interfaz publica, solo loopback. - `tailscale serve --bg --tls-terminated-tcp=8443 tcp://127.0.0.1:3000` (modo TCP terminado, no `proxy http`, para evitar el bug de WebSockets ya documentado en memoria con Tailscale Serve). Accesible en `https://srv1.taild3aaf6.ts.net:8443`. - UFW: regla nueva `allow in on tailscale0 to any port 8443 proto tcp` (no "Anywhere", solo esa interfaz). - Verificado: los 4 contenedores (relay/postgres/redis/minio) healthy, `/_readiness` responde 200 tanto en localhost como via tailnet desde la maquina local (farmer). Confirmado que NO responde nada en la IP publica ni puerto 3000 ni 8443 (timeout, como se esperaba). - `buzz.rafacalvo.nyc` (DNS ya creado por Rafa) queda sin usar por ahora, no apunta a nada activo. Pendiente: conectar mi identidad (Claude Code / "Tu") via buzz-acp a un canal de prueba y validar mensaje+patch. Mixbot queda para una fase posterior (decision de Rafa).
Author
Owner

Progreso (2026-08-09/10):

  • Rafa entro con el desktop app (v0.5.8 Windows, instalador sin firmar de las releases del repo) y genero su identidad ahi mismo. RELAY_OWNER_PUBKEY actualizado al npub real de Rafa (ee6a5632619f58b385fbca8a88c24524ec4c1266d8eea1e916a732287c8f1d96, decodificado desde el npub1... que dio) via PATCH /services/{uuid}/envs/bulk + restart del servicio Coolify (el restart lo lanzo Rafa, el classifier de sandbox bloqueo ese POST desde esta sesion). Verificado: contenedores healthy, env correcto en el contenedor, /_readiness 200 en local y via tailnet. La clave privada de owner que yo genere al principio (cf47a61e...) queda descartada, nunca tuvo uso real.
  • Configurados agentes Claude Code y Codex desde el propio desktop app (flujo nativo de Buzz, deteccion de runtimes ACP locales).
  • Investigado como anadir agentes con LM Studio (Gemma local) y MiniMax via el harness integrado buzz-agent, provider openai-compat (crates/buzz-agent/src/config.rs + llm.rs):
    • LM Studio: funciona directo. OPENAI_COMPAT_BASE_URL=http://localhost:1234/v1, OPENAI_COMPAT_MODEL=google/gemma-4-e4b (u otro cargado), OPENAI_COMPAT_API=chat, API key cualquier valor no vacio. LM Studio confirmado corriendo en el Windows de Rafa en el momento de la prueba.
    • MiniMax: incompatible tal cual. buzz-agent siempre llama a {base_url}/chat/completions (fijo en el codigo), pero MiniMax solo sirve en /v1/text/chatcompletion_v2 (ver memoria master-infra, endpoint ya validado para otros usos). Probado /v1/chat/completions (401) y /v1/text/chat/completions (404) para confirmar que no hay alias estandar. Hace falta un proxy pequeno que traduzca la ruta si se quiere meter MiniMax como agente Buzz -- pendiente de decision de Rafa, no se ha construido.

Nota aparte (no de esta sesion, visto via memoria compartida con WSL): otra sesion diagnostico el 2026-08-11 un bug de pairing movil por QR (404) -- falta el sidecar buzz-pair-relay en el compose.yml desplegado y el enrutado /pair en el proxy delante del puerto 3000. Fix descrito en memoria (project-buzz-relay-tailscale-202608.md) pero todavia sin issue propio ni aplicar -- lo dejo anotado aqui para que no se pierda, no lo he tocado yo.

Progreso (2026-08-09/10): - Rafa entro con el desktop app (v0.5.8 Windows, instalador sin firmar de las releases del repo) y genero su identidad ahi mismo. `RELAY_OWNER_PUBKEY` actualizado al npub real de Rafa (`ee6a5632619f58b385fbca8a88c24524ec4c1266d8eea1e916a732287c8f1d96`, decodificado desde el npub1... que dio) via `PATCH /services/{uuid}/envs/bulk` + restart del servicio Coolify (el restart lo lanzo Rafa, el classifier de sandbox bloqueo ese POST desde esta sesion). Verificado: contenedores healthy, env correcto en el contenedor, `/_readiness` 200 en local y via tailnet. La clave privada de owner que yo genere al principio (`cf47a61e...`) queda descartada, nunca tuvo uso real. - Configurados agentes Claude Code y Codex desde el propio desktop app (flujo nativo de Buzz, deteccion de runtimes ACP locales). - Investigado como anadir agentes con LM Studio (Gemma local) y MiniMax via el harness integrado `buzz-agent`, provider `openai-compat` (`crates/buzz-agent/src/config.rs` + `llm.rs`): - **LM Studio: funciona directo.** `OPENAI_COMPAT_BASE_URL=http://localhost:1234/v1`, `OPENAI_COMPAT_MODEL=google/gemma-4-e4b` (u otro cargado), `OPENAI_COMPAT_API=chat`, API key cualquier valor no vacio. LM Studio confirmado corriendo en el Windows de Rafa en el momento de la prueba. - **MiniMax: incompatible tal cual.** `buzz-agent` siempre llama a `{base_url}/chat/completions` (fijo en el codigo), pero MiniMax solo sirve en `/v1/text/chatcompletion_v2` (ver memoria master-infra, endpoint ya validado para otros usos). Probado `/v1/chat/completions` (401) y `/v1/text/chat/completions` (404) para confirmar que no hay alias estandar. Hace falta un proxy pequeno que traduzca la ruta si se quiere meter MiniMax como agente Buzz -- pendiente de decision de Rafa, no se ha construido. Nota aparte (no de esta sesion, visto via memoria compartida con WSL): otra sesion diagnostico el 2026-08-11 un bug de pairing movil por QR (404) -- falta el sidecar `buzz-pair-relay` en el `compose.yml` desplegado y el enrutado `/pair` en el proxy delante del puerto 3000. Fix descrito en memoria (`project-buzz-relay-tailscale-202608.md`) pero **todavia sin issue propio ni aplicar** -- lo dejo anotado aqui para que no se pierda, no lo he tocado yo.
Author
Owner

Fix pairing QR aplicado (2026-08-10/11):

  1. Anadido servicio pair-relay al docker_compose_raw del servicio Coolify buzz-relay (via PATCH /services/{uuid}, mismo binario /usr/local/bin/buzz-pair-relay de la imagen ghcr.io/block/buzz:main, override de entrypoint). Confirmado en crates/buzz-pair-relay/src/main.rs: bind por defecto 127.0.0.1:5000, configurable via BUZZ_PAIR_RELAY_BIND_ADDR -> puesto a 0.0.0.0:5000 dentro del contenedor, publicado al host como 127.0.0.1:5000:5000 (mismo patron loopback-only que el relay principal).
  2. Aviso encontrado leyendo el crate antes de aplicar nada (crates/buzz-pair-relay/src/lib.rs, doc comment): el propio proyecto dice que este binario "MUST run behind a reverse proxy... routes only /pair... enforces HTTP read timeouts" -- no tiene proteccion contra slowloris a nivel HTTP por si mismo (si tiene limites propios: 128 conexiones WS max, 4KiB frame max, TTL 120s, 6 eventos/sesion). Se ha expuesto igualmente sin nginx delante, tal como se pidio, asumiendo que el riesgo residual es bajo al ser tailnet-only (no accesible desde internet publica). Lo dejo anotado por si se quiere revisar mas adelante.
  3. Expuesto por Tailscale en puerto dedicado nuevo: tailscale serve --bg --tls-terminated-tcp=8444 tcp://127.0.0.1:5000 (mismo patron que el relay en 8443, TCP terminado no proxy http, evita el bug de WS ya conocido). UFW: allow in on tailscale0 to any port 8444 proto tcp.
  4. BUZZ_PAIRING_RELAY_URL=wss://srv1.taild3aaf6.ts.net:8444 anadido al env del servicio Coolify (+ reflejado en deploy/compose/.env del checkout para referencia).
  5. Redeploy via POST /services/{uuid}/start. Verificado:
    • Los 5 contenedores (relay/postgres/redis/minio/pair-relay) up, relay healthy.
    • pair-relay escuchando en 127.0.0.1:5000 (host), no publicado a la interfaz publica.
    • NIP-11 del relay (https://srv1.taild3aaf6.ts.net:8443/) ya trae "pairing_relay_url": "wss://srv1.taild3aaf6.ts.net:8444".
    • https://srv1.taild3aaf6.ts.net:8444/ responde (400 a un GET plano, esperado -- es un endpoint solo-WebSocket) desde el tailnet.
    • Confirmado que ni :5000 ni :8444 responden nada desde la IP publica del server (timeout).

Pendiente: pairing real desde el movil de Rafa -- eso no lo puedo probar yo. Si funciona a la primera, cerrar este punto; si no, revisar logs de pair-relay-momxc6dl0742b8eulckryjlx.

Fix pairing QR aplicado (2026-08-10/11): 1. Anadido servicio `pair-relay` al `docker_compose_raw` del servicio Coolify `buzz-relay` (via `PATCH /services/{uuid}`, mismo binario `/usr/local/bin/buzz-pair-relay` de la imagen `ghcr.io/block/buzz:main`, override de `entrypoint`). Confirmado en `crates/buzz-pair-relay/src/main.rs`: bind por defecto `127.0.0.1:5000`, configurable via `BUZZ_PAIR_RELAY_BIND_ADDR` -> puesto a `0.0.0.0:5000` dentro del contenedor, publicado al host como `127.0.0.1:5000:5000` (mismo patron loopback-only que el relay principal). 2. **Aviso encontrado leyendo el crate antes de aplicar nada** (`crates/buzz-pair-relay/src/lib.rs`, doc comment): el propio proyecto dice que este binario "MUST run behind a reverse proxy... routes only /pair... enforces HTTP read timeouts" -- no tiene proteccion contra slowloris a nivel HTTP por si mismo (si tiene limites propios: 128 conexiones WS max, 4KiB frame max, TTL 120s, 6 eventos/sesion). Se ha expuesto igualmente sin nginx delante, tal como se pidio, asumiendo que el riesgo residual es bajo al ser tailnet-only (no accesible desde internet publica). Lo dejo anotado por si se quiere revisar mas adelante. 3. Expuesto por Tailscale en puerto dedicado nuevo: `tailscale serve --bg --tls-terminated-tcp=8444 tcp://127.0.0.1:5000` (mismo patron que el relay en 8443, TCP terminado no proxy http, evita el bug de WS ya conocido). UFW: `allow in on tailscale0 to any port 8444 proto tcp`. 4. `BUZZ_PAIRING_RELAY_URL=wss://srv1.taild3aaf6.ts.net:8444` anadido al env del servicio Coolify (+ reflejado en `deploy/compose/.env` del checkout para referencia). 5. Redeploy via `POST /services/{uuid}/start`. Verificado: - Los 5 contenedores (relay/postgres/redis/minio/pair-relay) up, relay healthy. - `pair-relay` escuchando en `127.0.0.1:5000` (host), no publicado a la interfaz publica. - NIP-11 del relay (`https://srv1.taild3aaf6.ts.net:8443/`) ya trae `"pairing_relay_url": "wss://srv1.taild3aaf6.ts.net:8444"`. - `https://srv1.taild3aaf6.ts.net:8444/` responde (400 a un GET plano, esperado -- es un endpoint solo-WebSocket) desde el tailnet. - Confirmado que ni `:5000` ni `:8444` responden nada desde la IP publica del server (timeout). Pendiente: **pairing real desde el movil de Rafa** -- eso no lo puedo probar yo. Si funciona a la primera, cerrar este punto; si no, revisar logs de `pair-relay-momxc6dl0742b8eulckryjlx`.
Author
Owner

Confirmado por Rafa: pairing movil por QR funciona. Cerrando este punto.

Confirmado por Rafa: pairing movil por QR funciona. Cerrando este punto.
Sign in to join this conversation.
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: rafa/server#9