Update the architecture diagram, capability table, run examples and known
gaps to describe user management via the signed control-plane API instead
of the old store-only/--db path. Record the remaining gap honestly: the
membershipd binaries currently deployed on the cluster predate the /users
route and return 404, so the Users tab becomes functional in production
once the bus is updated to v0.10.0; the gateway already connects and signs
correctly against those nodes.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Panel web de administración de unibus: un binario Go que sirve una SPA Mantine embebida y expone una REST API. Tiene la identidad ADMIN del operador, firma cada petición al plano de control del bus, y gestiona rooms, miembros, claves, usuarios y el estado del cluster.
Panel web de administración del bus de mensajería unibus (NATS + JetStream con
cifrado E2E por room). Un único binario Go que:
Sirve la SPA Mantine compilada y embebida (embed.FS sobre web/dist), con
el mismo look índigo/oscuro que la web del cliente del bus.
Expone una REST API de administración bajo /api. El binario tiene la
identidad ADMIN del operador y media cada acción privilegiada contra el plano de
control de unibus, firmando cada petición. El navegador nunca firma, nunca
habla NATS y nunca ve una clave privada.
Arquitectura
navegador (SPA Mantine)
│ fetch /api/* (basic auth de Caddy + bind loopback)
▼
unibus_admin (gateway Go, identidad ADMIN del operador)
├── pkg/client (unibus) → CreateRoom / Invite / Kick / ListMyRooms (firma + cripto E2E)
├── pkg/client (unibus) → ListUsers / AddUser / RevokeUser (API admin firmada; funciona en cluster)
├── GET firmado → /rooms/{id}/members (CanonicalRequest + SignEd25519, reusa la construcción del bus)
├── GET /healthz (CA-pinned)→ estado + posture de los 3 nodos del cluster
└── membership.Store (opc.) → users (allowlist) como fallback single-node con --db
▼
cluster unibus (magnus + homer + datardos, enforce + ACL + TLS + KV)
El gateway reutiliza el cliente del bus (github.com/enmanuel/unibus/pkg/client)
para todo lo que lleva criptografía (sellar la clave de room, firmar invite/rekey),
y construye GETs firmados con la única fuente de verdad de la firma del bus
(membership.CanonicalRequest + cs.SignEd25519) para las lecturas que el cliente
no expone. Nunca reimplementa firma ni cripto.
Capacidades
Pestaña
Qué hace
Vía
Cluster
up/down + posture (enforce/acl/tls/cluster/store) + latencia de cada nodo
GET /healthz (auth-exempt) de los nodos en --nodes, TLS pin a la CA del bus
Rooms
listar (rooms del admin), crear (subject + E2E/persist/firmado), ver miembros, invitar, expulsar+rekey
pkg/client (mutaciones) + GET firmado (miembros)
Users
listar/añadir/revocar la allowlist del bus
pkg/client (ListUsers/AddUser/RevokeUser) contra la API admin-only del plano de control, firmando como el operador. Funciona en cluster (los nodos escriben al mismo store que las rooms) sin acceso directo al store. --db queda como fallback single-node opcional
Cómo arrancar
# Mock (iterar la SPA sin bus):
./unibus_admin --mock --port 8480# Real contra un membershipd local (dev, sin TLS). Users vía la API del plano de# control; añade --db sólo si quieres gestionar users contra un SQLite local:
./unibus_admin --port 8480\
--ctrl-url http://127.0.0.1:8470 --nats-url nats://127.0.0.1:4250 \
--identity-pass unibus/operator-identity
# Producción (cluster magnus, enforce + TLS + nkey). Sin --db: la pestaña Users# gestiona la allowlist por la API admin firmada del plano de control:
./unibus_admin --port 8480 --bind 127.0.0.1 \
--ctrl-url https://127.0.0.1:8470 --nats-url tls://127.0.0.1:4250 \
--ca /opt/unibus/tls/ca.crt \
--identity-file /opt/unibus_admin/identity.json \
--nodes "magnus=https://127.0.0.1:8470,homer=https://141.94.69.66:8470,datardos=https://51.91.100.142:8470"
Build
cd web && pnpm install && pnpm build # compila la SPA a web/dist (embebida)cd .. &&CGO_ENABLED=0 go build -o unibus_admin .
Seguridad
La identidad admin se carga de pass (unibus/operator-identity) o de un fichero
0600 (--identity-file); nunca va hardcodeada, ni a git, ni a argv.
El panel exige autenticación: en producción lo fronta Caddy con basic auth sobre
un subdominio ofuscado, y el gateway bindea sólo a loopback.
Acciones destructivas (revocar user, expulsar miembro + rekey) piden confirmación
explícita en la UI.
El operador debe estar en la allowlist del bus (rol admin) para que el gateway
pueda conectar bajo enforce.
Despliegue actual
Desplegado en magnus como unibus-admin.service (systemd system, Restart=always),
puerto 8480 en loopback, fronteado por Caddy con basic auth en un subdominio
ofuscado (admin-<hash>.organic-machine.com). Credenciales en pass
(unibus/admin-panel-password, unibus/admin-panel-url). Artefactos de deploy en
deploy/.
Gaps conocidos
Users contra el cluster desplegado: el código del plano de control (unibus
master, v0.10.0) ya expone la API admin-only de users (GET/POST /users,
POST /users/{signpub}/revoke) y el gateway la consume firmando como el operador.
La cadena completa (list/add/revoke + idempotencia 409) está verificada end-to-end
contra un membershipd master local. El gap restante es de despliegue: los
binarios membershipd que corren hoy en el cluster (magnus/homer/datardos) son
anteriores al merge de esta ruta y devuelven 404 en /users, así que la pestaña
Users sólo será funcional en producción cuando el bus se actualice a v0.10.0. El
gateway ya conecta y firma correctamente contra esos nodos (verificado: /api/me
responde con el endpoint real del operador y pasa el enforce de magnus). Para
gestión single-node sin esperar al cluster, --db sigue disponible.
meta-leader / tamaño de quórum del cluster: /healthz no los expone; requieren
el endpoint de monitoreo de NATS (varz/jsz). La pestaña Cluster muestra up/posture.
Invite a room E2E: requiere las claves públicas (sign_pub + kex_pub) del
invitado en hex, porque la clave de room se sella contra su X25519. La UI las pide
manualmente; no hay directorio de claves públicas todavía.