Files
unibus_admin/app.md
egutierrez ea35c11f19 docs(unibus_admin): bump 0.2.0 — account creation via invites + hard-delete
Document the wallet-model account flow in the Users tab: invite-link account
creation (with the configurable client base URL for the join link), permanent
hard-delete with strong confirmation, and the deploy gap (cluster still on bus
v0.11.0; this branch merges after the bus reaches master).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-07 22:29:51 +02:00

10 KiB

name, lang, domain, version, description, tags, uses_functions, uses_types, framework, entry_point, dir_path, repo_url, service, e2e_checks
name lang domain version description tags uses_functions uses_types framework entry_point dir_path repo_url service e2e_checks
unibus_admin go infra 0.2.0 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 (alta por invitación + baja por hard-delete) y el estado del cluster.
service
messaging
admin
nats
e2e
sign_ed25519_go_cybersecurity
main.go projects/message_bus/apps/unibus_admin https://gitea-dgg044oo04woo4ggcsws4gk0.organic-machine.com/dataforge/unibus_admin
port health_endpoint health_timeout_s systemd_unit systemd_scope restart_policy runtime pc_targets is_local_only
8480 /healthz 3 unibus-admin.service system always systemd-system
magnus
false
id cmd timeout_s
build CGO_ENABLED=0 go build ./... 180
id cmd timeout_s
vet CGO_ENABLED=0 go vet ./... 120
id cmd timeout_s
web_build cd web && pnpm install --frozen-lockfile && pnpm build 240
id cmd timeout_s
smoke_mock ./unibus_admin --mock --port 18490 & sleep 2 && curl -fsS http://127.0.0.1:18490/healthz && curl -fsS http://127.0.0.1:18490/api/cluster >/dev/null && kill %1 30

unibus_admin

Panel web de administración del bus de mensajería unibus (NATS + JetStream con cifrado E2E por room). Un único binario Go que:

  1. 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.
  2. 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 la allowlist; crear usuario por enlace de invitación (modelo wallet, sin manejar claves); añadir por clave conocida; revocar (status flip, auditable) y eliminar (hard-delete permanente, con confirmación fuerte) pkg/client (ListUsers/AddUser/RevokeUser/DeleteUser/CreateInvite/ListInvites) contra la API admin-only del plano de control, firmando como el operador. Funciona en cluster 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/.

Creación de usuarios (modelo wallet) y enlace de invitación

La pestaña Users crea usuarios SIN que el operador maneje claves: «Crear usuario» acuña un enlace de invitación de un solo uso (POST /api/invites → bus POST /invites, admin-only). El gateway construye el enlace <<APP_BASE>>/join?token=XXX, donde <<APP_BASE>> es la URL del cliente final (la página que hospeda /join), NO la del panel. Se configura en el gateway con --join-base-url https://chat.unibus.example o la variable UNIBUS_JOIN_BASE_URL; el valor se expone en /api/me (join_base_url). Si no se configura, la SPA usa su propio origen como respaldo y avisa de configurarlo. El usuario abre el enlace en su dispositivo, genera ahí su par de claves (la privada nunca sale del equipo) y se registra (POST /register, lo consume la página /join del cliente web — ver el contrato en el report del bus).

«Eliminar» es un hard-delete permanente (DELETE /api/users/{pub} → bus DELETE /users/{signpub}), distinto de «Revocar» (status flip auditable). La UI exige teclear el handle para confirmarlo (no se dispara por un clic accidental).

Gaps conocidos

  • Cuentas (invites + hard-delete) contra el cluster desplegado: el gateway y la SPA ya consumen POST/GET /api/invites y DELETE /api/users/{pub}, verificados end-to-end contra un membershipd v0.12.0 local (crear invite → registrar por /register sin firma → aparece en /users → hard-delete → desaparece) y vía --mock. El gap es de despliegue: el cluster corre hoy v0.11.0 (sin /invites//register/DELETE /users), así que la pestaña Users sólo creará/ eliminará cuentas en producción cuando el bus se actualice a v0.12.0 (rollout pendiente). Verificado contra el cluster vivo: /register devuelve 401 en magnus/homer/datardos (ruta aún no desplegada) frente a 400 en el nodo v0.12.0 local. El merge de esta rama del panel a master debe seguir al merge del bus.
  • 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.

Capability growth log

  • v0.2.0 (2026-06-07) — capa de CUENTAS estilo WhatsApp sobre el bus v0.12.0. El gateway gana POST/GET /api/invites (acuñar/listar invitaciones de un solo uso, consumiendo client.CreateInvite/ListInvites) y DELETE /api/users/{pub} (hard-delete, client.DeleteUser), con la misma doble vía que el resto de users (plano de control firmado en cluster / store directo en single-node). El enlace de invitación <<APP_BASE>>/join?token=… lo construye el gateway desde --join-base-url / UNIBUS_JOIN_BASE_URL (URL del cliente final, no del panel), expuesto en /api/me. La SPA añade a la pestaña Users: botón «Crear usuario» (modal handle+rol+caducidad → enlace copiable), card de invitaciones pendientes (handle, rol, token parcial, caducidad, copiar enlace), y botón «Eliminar» con confirmación FUERTE (teclear el handle) que distingue el borrado permanente del revoke. Verificado: flujo e2e contra membershipd v0.12.0 local (invite → register por curl sin firma → aparece en /users → re-register 409 → hard-delete → desaparece) y gateway vía --mock (las rutas nuevas + el SPA embebido sirven). El cluster vivo sigue en v0.11.0 (rollout del bus pendiente del orquestador); esta rama del panel se mergea a master tras el merge del bus a master. build/vet/web-build verdes.