Actualizada: 2026-09-02 (v2.25.0 publicado: acciones de router en tabla, vitales condicionales, timeline compacta de upgrades, comparación de builds; v2.26.0 planificado: #448 bugfix, #451 apply seguro con ownership UCI, #452 channel planning, #453 firmware upgrades).
Hecho y en producción (CT 226 + 4 routers, agentes v2.8.0):
- Fase 14 — Visibilidad WiFi/roaming ✅ COMPLETA (v2.8.0): matriz clientes×APs (14.2b), estado 802.11r por SSID (14.3), utilización por canal (14.4), eventos de roaming persistentes con 30 días de histórico (14.5).
- Fase 15.1 — Informes de disponibilidad: pestañas día/semana/mes sobre
metrics_daily. - Fase 9 — On-box (v2.7.2): config UCI, bootstrap AUTH_PASS, journal DELETE (R4/R5/R6), TLS autofirmado + SPKI pinning (R2), pairing token (R3), paquete server OpenWrt (R1).
- Fase 10 — Orquestación (fundación hecha): motor plan→apply→state + agent executor (10.1), módulo AdGuard Home (10.2, E2E verificado). La Fase 17 amplía este motor a despliegues completos (instalador + scripts + firewall).
- Fase 13 — Auditoría Fable de robustez (v2.7.2): single-flight, SSE deadline, sshpool race, %w wrapping.
- Fase 12 — Auditoría de seguridad (v2.7.0): TRUST_PROXY, anti-replay, body cap, password mínima 10.
- Fase 11 — LuCI (v2.7.2):
luci-app-netpulsevista de nodo + test connection + server on-box package. - Fase 1-8: topología v5, alertas, agente nativo, Node→Go, view-model, resiliencia, seguridad HMAC, consolidación, retención, recharts v3, actualización de stack (TS7/Vite8/Tailwind4/RR8).
- Dead Man's Switch (P6): histéresis 3 min en alertas de agente caído.
- slog log levels (P7): Info/Debug en agente.
- Updater layout-aware: rolling (git) vs stable (install.sh), release tags.
- Tooltip fix: topología no recorta hover cards.
- WAN traffic header fix: selector de rango no se amontona.
- Editar router (v2.8.0):
PUT /api/config/routers/{id}+ UI, evita borrar+recrear para cambiar host/agent_only/gateway.
Dormido en producción:
- Web Push: sin HTTPS en CT 226 → bloqueante para notificaciones push.
- .apk para 25.12: routers .2-.4 con instalación manual; CI listo.
Deuda menor:
- npm run lint roto (typescript-eslint no soporta TS7).
- Informe semanal de disponibilidad (daily ya tiene up_min/up_count).
Mapa semántico real basado en evidencia de red:
- FDB + LLDP en vivo: puertos cableados, switches gestionados vs. inferidos.
- Backhaul real (wifi/cable) detectado por sonda ubus.
- Hipervisores con sus CTs anidados (OUI de hipervisor + exactamente un host).
- Collector de series temporales (latencia TCP, heartbeat, timeseries SQLite).
Deploy: collector Go independiente con install-collector.sh.
- Motor de alertas con 6 categorías y config por categoría (urgente/todo/nada).
- Web Push nativo (VAPID) con SW propio (injectManifest).
- Agente OpenWrt piloto: ingesta con tokens, fallback SSH, procd con respawn.
- Refresco bajo demanda (POST /api/refresh con rate limit).
Deploy: agentes instalados en gateway + 3 APs vía install-agent.sh.
Base funcional y migración de backend a Go. Cronológicamente fue el primer hito mayor del proyecto, pero se ubica aquí como cimiento de las Fases 1 y 2:
- PWA en React con sondeo SSH de solo lectura (ubus,
/proc, iwinfo). - AdGuard Home + WireGuard (stats y estado).
- Auth multi-usuario con roles, discovery LAN.
- Backend migrado de Node a Go (
server-go), binario único con la app embebida. - Collector Go piloto y one-liner install.sh.
Deploy: install.sh one-liner, systemd en CT 226, goreleaser estable.
API como view-model de presentación para consumo directo por clientes ligeros:
vm: 1en overview, canon demo single-source en Go → JSON.Device.infrasellado server-side (hypervisor, ct, managed-switch).- Topología semántica en el snapshot.
- Remodel de Preferencias: nombre de saludo, info de sistema real, AdGuard/usuarios/routers simplificados con alta colapsada.
Deploy: sin cambios (mismo servidor).
Cierre de los huecos de supervisión detectados en la auditoría del piloto:
- Plan A (auto-supervisión en el router): watchdog cron con heartbeat.
- Plan B (rearme desde el servidor): POST /api/agents/{slug}/rearm.
- Auto-rearme tras TTL (v2.4.0): supervisor cada 30 s, opt-in con flag.
- Endurecimiento de permisos (v2.4.1): rutas de mutación solo-admin.
Deploy: agentes reinstalados con watchdog incluido.
Cierre de los huecos de seguridad detectados en la auditoría del piloto:
- HMAC-SHA256 en la ingesta (R4): el agente firma cada payload con
HMAC-SHA256(token, body)en la cabeceraX-Agent-Signature. El servidor rechaza peticiones sin firma o con firma inválida (401). - Binario del agente desde el servidor (R3): endpoint
GET /api/agents/{slug}/binary?arch=...con auth por token de agente (Bearer). Los binarios van embebidos en el servidor víago:embed(construidos por CI para amd64/arm64/armv7). El one-liner de instalación descarga el binario del servidor en vez de GitHub.
Deploy: reinstalar agentes con la nueva release para que usen HMAC.
Objetivo: pasar del agente "sondista" a un componente de red local de verdad, con eventos en tiempo real, comunicación bidireccional y empaquetado oficial de OpenWrt.
Entregables:
-
✅ 7.1 — Eventos nl80211 en tiempo real:
iw event -tcomo proceso hijo persistente; assoc/disassoc disparan push inmediato (min gap 3s). Desplegado en 4 routers, verificado end-to-end. -
⏸️ 7.2 — Netlink/nl80211 nativo: APLAZADO. Sustituiría
brctl/iwinfo/ip neighpor rtnetlink/nl80211. ~1 MB extra. Para futura iteración. -
✅ 7.3 — Comunicación bidireccional SSE:
GET /api/agents/{slug}/streamPOST /api/agents/{slug}/refresh. Agente mantiene SSE abierta, recibe comandos del servidor. Infraestructura lista para Fase 10 (escritura). Verificado:refresh → {"ok":true}en gateway y APs.
-
✅ 7.4 — Empaquetado
.ipk: Makefile + procd init + UCI config + uci-defaults (migración .env→UCI + watchdog). Construido con SDK 24.10. Instalado víaopkgen gateway (aarch64_cortex-a53). Routers 25.12 usan instalación manual (mismo binario + UCI); .apk pendiente de CI automático (workflowopenwrt-package.ymlcreado, commit 18bf47b). -
✅ 7.5 — Profiling: RSS 11.1–12.4 MB (4 routers), CPU <1% idle, watchdog con
pidof(fix para BusyBox), sin errores en logs.
Criterios de aceptación:
- ✅ WiFi disconnect → push < 3s
- ✅ CPU < 1% idle (muy por debajo del 5%)
⚠️ RSS 11-12 MB (por encima del criterio de 10 MB, aceptado; dentro del presupuesto de 40 MB del skill go-collector-stack)- ✅ opkg install funcional en gateway (24.10)
⚠️ Compatibilidad OpenWrt: probado 24.10 y 25.12, no 23.05.
Fixes post-auditoría (5-Ago-2026):
--versionflag en agente (57e6f9c) — ya no cuelga SSH- BuildCommit en servidor (57e6f9c) — updater sin falso positivo
- CI de empaquetado .ipk/.apk (18bf47b) — automatizado para próximas releases
- Landing web actualizada (16cc4d9) — posicionamiento OpenWrt nativo
Deploy: servidor v2.6.0 en CT 226, agentes v2.6.0 en 4 routers, .ipk en gateway.
Frente de trabajo previo al paquete del gateway (Fase 9). Cada ítem es un PR independiente; ordenados por valor/esfuerzo.
-
✅ Métricas operativas en
/api/health(barato, alto valor operativo):- Añadir
agentsConnected,sseConnections,devicesTotalal health. Los datos ya existen (AgentRegistry, hub SSE, poller) — solo exponerlos. Punto de mejora nº2 de la auditoría Fase 7. HECHO: PR #20.
- Añadir
-
✅ Persistir el registry de agentes (R8) (prerrequisito de pairing de la Fase 9):
- Hoy
AgentRegistryes unmapen RAM →lastSeen/payloads se pierden al reiniciar el servidor. Deuda transversal ya anotada. - Persistir último push por slug en SQLite (kv
agent.state.<slug>); cargar al arrancar (NewStateRestorer). HECHO: PR #22. Los slugs sin token ya se rechazaban (401) → "solo slugs conocidos" cubierto.
- Hoy
-
✅ Histórico largo en server-go (RETENCIÓN, no sidecar) (el gap de histórico largo, deuda desde merge v2; decisión 6-Ago: descartada la frontera sidecar-readonly — el tráfico/cpu/ram/temp ya los captura el propio server-go en
metrics, ampliar el sidecar duplicaría la adquisición, justo lo que evita go-collector-stack):- La tabla
metricsdel server-go solo conserva 7 días (RetentionMS en db.go) mientras la webapp ofrece selector 1h/24h/7d/30d → el 30d está vacío de datos. Resolver con la escalera del skill sqlite-timeseries-daemon sobre la MISMA tabla: raw (7d, ya existe) → buckets 5min (1 año) → daily (∞), job nocturno de rollup→purga→checkpoint (referencia references/jobs.md). Aditivo, sin tocar el sidecar. HECHO: PR #24. - El endpoint de histórico (
metricsHistory) sirve corto demetricsy largo (30d) demetrics_bucketscon downsampling (ventanas 6h). - ⏸️ Bonus pendiente: informe semanal de disponibilidad (deuda auditoría
v2.1.0; el
dailyya llevaup_min/up_countpreparados). - Colateral pendiente: reinstalar el collector con release nueva (reporta
0.1.0: nunca se aplicó el fix goreleaser-X main.Version).
- La tabla
-
✅ recharts v2 → v3 (deuda de mantenimiento):
app/package.jsonusa^2.15.4(deprecated en npm). PR con verificación visual de las gráficas (Playwright). HECHO: PR #29 (v3.10.1; se eliminó el wrapperchart.tsxmuerto de shadcn).
-
✅ Limpieza de ramas (trivial):
origin/feat/go-backend,origin/fix/issues-3-4-5(yfix/netpulse-jwt-cvesi quedó tras el squash) — borrar las ya mergeadas. HECHO: ramas locales y remotas purgadas; 3 PRs de dependabot cerrados con motivo (react-router v8, hono legacy ×2).
-
✅ Auditoría de majors de frontend (6-Ago-2026):
- Estado real verificado: React 19.2.8 ✅ al día; recharts 2.15.4→3.10.1 (2 majors detrás, deprecated — PR con verificación visual); Vite 7.3.6→8.2.0, Tailwind 3.4→4.3.3, TypeScript 5.9→7.0.2, react-router 7.18.2→8.3.0.
- DECISIÓN POSTERIOR (6-Ago noche, orden del usuario): subir TODO el stack — modernc.org/sqlite 1.56, TypeScript 7 (tsgo), Vite 8, Tailwind 4, react-router 8.3.0 — revirtiendo la decisión previa de no subir estos majors. Cada uno en su PR con verificación completa (build + tsc + PWA + Playwright). Riesgo asumido por el usuario. → Ejecutado en el ítem 7 (abajo).
-
✅ Actualización del stack a las últimas versiones (issue #32, 6-Ago-2026, orden del usuario — revierte la decisión de no subir majors):
Componente De → A Estado modernc.org/sqlite 1.55 → 1.56.0 ✅ HECHO (PR #33) TypeScript 5.9.3 → 7.0.2 (tsgo) ✅ HECHO (PR #34) Vite 7.3.6 → 8.2.0 ✅ HECHO (PR #36) Tailwind 3.4.19 → 4.3.3 (CSS-first) ✅ HECHO (PR #35) react-router 7.18.2 → 8.3.0 ✅ HECHO (PR #37) - Cada bump en su PR con build + tsc + PWA + verificación Playwright antes de mergear; deploy en CT 226 tras cada merge. Riesgo asumido por el usuario.
- Lecciones del bloque (6-Ago): (1) Vite 8 (lightningcss) es incompatible
con Tailwind 3 → orden forzado: Tailwind 4 antes; (2) react-router v8
FUSIONA react-router-dom (el paquete @8 no existe; replace de imports +
eliminar la dep); (3) recharts v3 necesita
react-isexplícito (lo dejaba fuera --legacy-peer-deps → build roto); (4) Tailwind 4: colores en @theme PUROS (sin<alpha-value>), opacidad vía color-mix automático; (5) TS7 eliminabaseUrl(paths relativos). Todo desplegado en CT 226, producción==main verificado (binario 5ce0c4ad, bundle index-wipKAvlJ). ⚠️ DEUDA DE ECOSISTEMA: typescript-eslint@8.66 NO soporta TS7 (peer<6.1.0) →npm run lintmanual ROTO hasta que publiquen soporte. No bloquea build ni CI (el lint no corre en CI). Fix aplicado en main:overridesen app/package.json para quenpm cifuncione.
-
Webhooks y API de ingesta (movido de Fase 10, 6-Ago-2026; verificada: no implementados en el código, solo planificación del commit 4a4428e):
- Webhook saliente ✅ HECHO (PR #31): notificaciones de alertas a URLs
externas con firma HMAC-SHA256, reintentos con backoff y DLQ — patrón
EasyZFS v2.4.0, skill
email-webhook-notifications. Endpoint adminGET /api/webhook/dlq. INACTIVO en producción (falta WEBHOOK_URL/ SECRET en .env — decisión del usuario). - API de ingesta (webhook entrante) ⏸️ DIFERIDO (decisión 6-Ago): endpoint HTTP firmado para que sistemas externos envíen eventos a NetPulse sin agente (lista de orígenes permitida, anti-SSRF). Reutiliza el patrón HMAC de Fase 6 (el único HMAC que existe hoy es el de la ingesta del agente).
- ✅ Skill
api-ingest-goCREADA (6-Ago): cubre el hueco de conocimiento del entrante (firma, orígenes, anti-SSRF, idempotencia por event_id, rate limit, body cap).api-stackes solo Node (no aplica a backend Go);email-webhook-notificationssolo cubre saliente. - Prácticas del curado
learn/skills_go_api_development.md(6-Ago): de ese catálogo solo aplica al entrante el request ID (UUID por request en headers/logs; hoy no existex-request-iden server-go). Ya cubiertos: rate-limit por IP en ingesta (ipRateLimit, ventana 1 min), envelope de error propio con i18n (no RFC 7807), samber golang-* instalados. El resto del catálogo (pgx/sqlc/Redis, Gin/Echo/Fiber, gRPC/OTel) NO aplica: stack SQLite + stdlib net/http.
- Webhook saliente ✅ HECHO (PR #31): notificaciones de alertas a URLs
externas con firma HMAC-SHA256, reintentos con backoff y DLQ — patrón
EasyZFS v2.4.0, skill
Quedan fuera de esta fase (a propósito): Web Push (requiere decisión de infra HTTPS en CT 226, no es micromejora) y el informe semanal de disponibilidad (cubierto en el ítem 3 como bonus pendiente).
Objetivo: NetPulse deja de ser un panel externo en un CT para convertirse en una app que vive en el propio router, alcanzable desde la IP del gateway sin dependencia de infraestructura externa.
Contexto:
- Hoy el servidor vive en CT 226 y los agentes le hablan por IP fija.
- Eso rompe si cambias de red, apagas el servidor o no tienes un CT disponible.
- El Flint 2 tiene 8 GB de RAM y flash eMMC; los APs de 512 MB solo pueden correr el agente.
Desarrollo:
- Modo on-box del servidor:
- Build de
server-gocon tagonboxo flag de compilación que excluya collector independiente y mantenga solo API web + ingesta de agentes. - Arranque vía procd con UCI config (
/etc/config/netpulse). - Detección automática: si el router tiene interfaces WAN y LAN, actúa como hub (server + agent); si solo LAN/STA, actúa solo como agente.
- Build de
- Persistencia fuera de NAND:
- SQLite en USB o overlay con WAL desactivado (
PRAGMA journal_mode=DELETEoMEMORY) para no desgastar flash. - Migraciones automáticas al arrancar; backup diario de la BD.
- SQLite en USB o overlay con WAL desactivado (
- TLS local:
- Cert autofirmado generado en primer arranque + pantalla de "confiar" en el navegador, similar a Home Assistant o un NAS.
- Alternativa: Tailscale funnel/certs para HTTPS automático si hay salida.
- El agente debe validar el cert del servidor (Fase 6 HMAC) aunque sea
autofirmado, sin
--insecure.
- Pairing cero-fricción:
- Al instalar un agente, se genera un token de emparejamiento mostrado en
LuCI o en un log accesible (
logread). - El servidor on-box escanea/adopta agentes de la misma LAN con ese token.
- No más
curl | shcon token en argv.
- Al instalar un agente, se genera un token de emparejamiento mostrado en
LuCI o en un log accesible (
luci-app-netpulse: ver Fase 11 (paquete LuCI dedicado, se construye después de la Fase 9; no reemplaza la PWA, permite diagnóstico local sin la app).- Presupuesto y roles:
- Gateway (Flint 2): server (~20 MB RSS) + agent.
- APs (Redmi AX6, 512 MB): solo agente.
Criterios de aceptación:
- Con el CT 226 apagado, la app es accesible desde
https://192.168.1.1. - Desde un móvil en la red se puede adoptar un AP nuevo sin editar env files.
- El gateway sobrevive a un reinicio conservando configuración y últimas 24 h de datos (en USB/overlay).
Bloqueantes previos: TLS resuelto en Fase 6 + agente bidireccional de Fase 7. Ver SPEC-FASE8.md (retos R1-R6 y criterios).
Deploy: instalar server on-box en el gateway, agentes en APs.
Objetivo: pasar de leer la red a actuar sobre ella de forma declarativa, segura y reversible, con reglas que impidan quedarse sin wifi por un error.
Contexto:
- Hasta Fase 9 NetPulse es un panel de observación y alertas.
- Cualquier cambio en routers (AdGuard, WireGuard, roaming) requiere LuCI/SSH.
- Un error en un script puede dejar la red inaccesible.
Reglas de diseño innegociables (detalle en docs/AUDITORIA-FASE65.md §5):
- Plan → apply → state (patrón Terraform): siempre mostrar diff antes de aplicar; nada se ejecuta sin confirmación explícita del usuario.
ucitransaccional; nuncasedsobre ficheros.- Snapshot automático antes de apply; rollback si el healthcheck falla.
- Idempotencia: declarar el estado deseado, no acumular comandos.
- Allowlist estricta de operaciones; nada de shell libre.
- HMAC en todas las órdenes de escritura (Fase 6) para autenticar origen.
Desarrollo:
- Modelo de recursos: definir recursos NetPulse (
AdGuard,WireGuardPeer,DawnConfig,BatmanMesh) con schema versionado y validación server-side. - Motor de orquestación:
POST /api/plans→ genera diff contra el estado actual del router.POST /api/apply/{planId}→ aplica tras confirmación del usuario.POST /api/rollback/{planId}→ revierte al snapshot UCI anterior.
- Ejecutor sandboxeado en el agente:
- Traduce recursos a comandos UCI/booleanos concretos de la allowlist.
- Cada apply crea un snapshot de
/etc/configantes de tocar nada. - Healthcheck post-apply: si ping/gateway/wifi falla, rollback automático.
- Módulos por orden de riesgo/beneficio:
- AdGuard Home: despliegue one-click (descarga binario + YAML base + reenvío DNS desde dnsmasq + enable + firewall) + stats ya existentes. Riesgo bajo.
- WireGuard: alta de peers desde la app (clave + IP + allowed_ips + ajustar firewall). El túnel ya existe; solo se añaden/quitan peers. Riesgo medio.
- DAWN / 802.11r: roaming enterprise. Riesgo alto; un error deja APs sin wifi → modo rescate con fallback a config previa.
- Batman-adv: mesh L2. Riesgo muy alto; dry-run obligatorio + ventana de confirmación con auto-rollback si se pierde conectividad.
- Despliegue de servicios desde la app: además de configurar, poder
instalar/activar servicios sin tocar LuCI:
- AdGuard Home: instalar, activar, desactivar, actualizar.
- WireGuard: crear túnel nuevo (cliente o sitio-a-sitio), añadir/quitar peers.
- Posibles futuros: VLAN simple, WiFi guest, QoS básico.
- Cada despliegue es atómico: o se completa entero o se revierte.
- La app deja de ser "solo lectura" para estos flujos asistidos.
Criterios de aceptación:
- Cualquier apply muestra diff y pide confirmación antes de ejecutar.
- Un apply fallido deja la red en el estado anterior en < 60 s.
- Solo comandos de la allowlist pueden ejecutarse; shell libre está bloqueado.
- Auditoría: quién, qué y cuándo se aplicó cada cambio.
- Activar AdGuard desde la app sin abrir LuCI ni editar un solo fichero a mano.
Deploy: se despliega módulo a módulo, empezando por AdGuard Home.
Objetivo: paquete luci-app-netpulse instalable junto al .ipk/.apk del
agente. NO repite la webapp: verificado en los feeds oficiales
(openwrt/luci y openwrt/packages) que LuCI es per-dispositivo y no existe
ningún NOC multi-router; la webapp (Go + SSE) sigue siendo el visor de red.
Alcance acordado — vista de nodo (lo local, complementa sin solapar):
- Estado y gestión del agente en ESTE router:
- Estado del servicio procd (vivo/muerto), versión del binario, uptime, RSS.
- Config UCI (
/etc/config/netpulse): server, slug, token presente, interval — editable. - Heartbeat / última conexión al servidor y si es alcanzable.
- Logs del agente (
logread -e netpulse) — hoy no visibles en la webapp. - Botones: restart del agente, rearm, ver token.
- Puente a la webapp central (vista de red agregada): entrada de menú con enlace/iframe a la app (URL configurable en UCI). Cero UI duplicada.
Implementación:
- Luci modern (OpenWrt 24.10/25.x = ucode + JS/Vue, NO Luci legacy).
- El visor lee UCI + procd + logread directamente — sin listener HTTP extra en el agente (cero superficie/RAM).
- Build vía SDK OpenWrt; empaquetar para opkg y apk (25.12). El
.apkdel agente sigue pendiente de construir (Fase 7). - Cobra pleno sentido tras la Fase 9 (server on-box en el gateway); para terceros con OpenWrt estándar, ya es útil sin ella.
Criterios de aceptación:
opkg install luci-app-netpulse(24.10) y equivalente.apk(25.12).- Sin HTTP extra en el agente; RSS del router no sube por el paquete.
- El paquete lee estado real del agente (procd/UCI/logread) y lo muestra sin login adicional de la webapp.
Deploy: paquete junto al agente en gateway y APs.
Objetivo: pasada sistemática de caza de bugs y endurecimiento de seguridad sobre el código actual (server-go + app + agentes), materializada en una release de mantenimiento. Cada hallazgo se abre como issue y se cierra con su PR (convención issue→PR).
Alcance (áreas a revisar):
- Autenticación y autorización: revisar que todas las rutas de mutación
(crear/revocar agentes, routers, rearm) exijan
RequireAdmin; que no haya rutas sensibles solo conrequireAuth. (Relacionado con issue #6.) - CSRF: los endpoints de mutación autenticados por cookie deben proteger contra CSRF (SameSite + verificación de Origin cuando aplique).
- Cabeceras de seguridad HTTP:
X-Content-Type-Options,X-Frame-Optionso CSP en respuestas del server y del SPA embebido. - Ingesta de agentes: firma HMAC por mensaje + anti-replay (timestamp/nonce) para que un token robado no baste para inyectar datos falsos.
- Path traversal y servido estático: revisar rutas de estáticos, descargas del binario del agente y cualquier acceso a ficheros.
- Secretos: confirmar que tokens/secretos no aparecen en logs ni en
respuestas; que el
.envde producción no contiene nada imprimible. - Dependencias:
go list -mynpm auditpara deps del server-go y del front; cerrar cualquier CVE HIGH/CRITICAL real. - Bugs latentes: revisar manejo de errores, cierres de conexiones, races en el poller y caminos de error poco probados.
Criterios de aceptación:
- Ninguna ruta de mutación sensible alcanzable con rol
user. - Las respuestas del server llevan cabeceras de seguridad básicas.
- 0 CVEs HIGH/CRITICAL reales en deps (audit verde o justificado).
- Tests nuevos cubriendo cada fix (test que falla primero).
Deploy: release + install.sh en CT 226 y agentes.
Objetivo: auditoría dirigida de concurrencia, manejo de errores y fronteras en server-go. Cada hallazgo con mecanismo completo, test de regresión y fix.
Hallazgos corregidos (4 PRs → main):
- Single-flight de
GetOverview(#66 → #69): un panic enbuildOverviewcongelaba el poller para siempre (sfInFlightsin reset) y los seguidores 2..N recibían(nil, nil)del canal cerrado. Reescrito con structsfCallcompartido +close(done)como pura señal +recoverdel líder. - Cliente SSE zombi bloquea broadcast (#67 → #70): un peer desaparecido
sin cerrar TCP llenaba el buffer del kernel y
fmt.Fprintbloqueaba ~15 min el Broadcast síncrono de todos los clientes. Write deadline 10 s víaResponseController+ReadHeaderTimeoutenhttp.Server. - Carrera en
SSHPool.dial(#68 → #71): dos dialers concurrentes al mismo host filtraban una conexión SSH. Single-flight por host (canaldialing) + chequeo dep.closed. %v→%wen envolturas de error (#72 → #73): dos sitios rompíanerrors.Is/Asaguas arriba.
Criterios de aceptación:
- Tests de regresión con
-racepara cada fix (fallan contra el código original, pasan con el fix). go vetlimpio en server-go, collector y agent.- 0 cambios de API (retrocompatible).
Deploy: release v2.7.2.
- Series del collector en server-go: hoy son independientes (ARCHITECTURE.md lo recoge desde hace tiempo).
- Retención de series + informe semanal de disponibilidad (deuda desde la auditoría v2.1.0).
- recharts v2 → v3 (la v2 está deprecated en npm; única dependencia caducada de verdad).
- Retirar
NETPULSE_INSECURE_TLScuando TLS esté consolidado (R5). - Registry de agentes persistente (hoy
lastSeense pierde al reiniciar el servidor) y adopción solo de slugs conocidos (evitar huérfanos, R8).
Objetivo: dar visibilidad del estado del roaming y WiFi que hoy solo es accesible por SSH. El diagnóstico del incidente DAWN (22-Jul, 30-45s de corte) habría sido trivial con esta visibilidad.
Todo es lectura: no se modifica config de router. Cero riesgo.
- 14.1 — DAWN probe en agente: el agente añade
ubus call dawn get_network+get_hearing_mapal payload de push. Nuevo campodata.dawncon APs vistos, clientes, matriz de señal. Solo si DAWN está instalado (fail-soft silencioso si no). - 14.2 — Roaming matrix (UI): heatmap clientes × APs con señal (-dBm). Verde ≤-65, ámbar -75, rojo ≥-85. Identifica clientes en zonas límite candidatos a roaming.
- 14.3 — 802.11r status: por AP: ieee80211r on/off, mobility_domain, ft_over_ds. Alerta visual si los APs tienen domains distintos (inconsistencia = roaming roto).
- 14.4 — WiFi survey: por radio: canal, anchura, potencia,
utilización (
ubus call iwinfo scanpara redes vecinas). Detecta solapamiento de canales. - 14.5 — Eventos de roaming: timeline con assoc/disassoc/roam (cruzando hearing maps consecutivos + iw events ya capturados).
- 14.6 — Polish de la gráfica del análisis de canales (#618): la vista "plan de canales" (montañas) funciona, pero el polish visual se puede mejorar: claridad de la montaña de la red propia, legibilidad de la curva por canal y layout general. Subjetivo, sin bug funcional, baja prioridad. Refinamiento sobre la vista del sub-item 14.4 (WiFi survey).
- 14.7 — Tabla de la matriz de roaming: agrupar por AP/SSID y contador de APs (#626): revisita de #600 tras el feedback del foro (crowedavid). La vista "Roaming → Matriz" muestra instancias de wifi virtual sueltas sin indicar su AP/SSID. Pide: agrupar las instancias bajo su AP y su SSID, etiquetar el AP por su nombre (no el modelo de hardware), fila de cabecera agrupada por SSID, ordenación por columna (cliente/AP/SSID/banda) y corregir el contador "N APs" (debe contar APs físicos, no instancias de wifi virtual). Subjetivo/UX, sin bug funcional, baja prioridad. Refinamiento del sub-item 14.2 (Roaming matrix).
Deploy: agentes actualizados (ya en v2.8.0). UI nueva sin tocar routers.
Objetivo: convertir los datos acumulados (7d raw + 1año buckets + ∞ daily) en informes accionables.
- 15.1 — Disponibilidad ampliada: vista diaria/mensual (no solo
semanal). % uptime por router, detalles de caídas. Datos:
metrics_daily(ya tieneup_min/up_count). - 15.2 — Tráfico histórico: tendencias WAN (down/up por día/semana/mes).
Top N dispositivos por consumo. Datos:
metrics_buckets. - 15.3 — Actividad de dispositivos: cuándo conecta/desconecta cada dispositivo. Horas activo/día. Datos: FDB/DHCP histórico.
- 15.4 — Resumen de alertas: conteo por categoría/severidad/tiempo. Tendencia (¿va mejorando la red?).
- 15.5 — Exportación: CSV de tablas, PNG de gráficas.
- 15.6 — Insights de red (por cliente/VLAN) (#588):
respuesta del foro que pide "más insights de red": protocolos, conexiones,
tráfico, broadcasts, agrupados por cliente y por VLAN. Histórico a largo
plazo (sobre los datos ya colectados por
metrics_buckets/metrics_daily)- inspección en vivo de flujos (conversaciones por cliente/VLAN). La parte de "live flows" es un complemento: se apoya en el **DPI/ntopng (17.11)** una vez que ese módulo exista, pero el núcleo (histórico por cliente/VLAN) se puede abordar sin tocar los routers.
Deploy: sin cambios en routers (los datos ya se colectan).
Objetivo: alertas más inteligentes y menos ruidosas.
- 16.1 — Reglas custom: umbrales configurables por router/dispositivo (CPU >80%, señal < -75, etc.).
- 16.2 — Tipos nuevos: fallo de roaming (cliente rebota entre APs), congestión de canal (utilización >70%), dispositivo offline >Xh, DAWN kick detectado.
- 16.3 — Historial filtrable: filtro por fecha, router, severidad. Búsqueda libre. Exportación.
- 16.4 — Silencio programado: "no molestar" en ventanas de mantenimiento.
- 16.5 — Email: SMTP además de webhook (skill
email-webhook-notifications).
La fundación (10.1) y el módulo AdGuard (10.2) están hechos y verificados E2E. Pendiente:
- 10.3 - WireGuard peers ✅ (24-Ago-2026): alta/baja de peers desde la
app. Riesgo medio. El plan crea la interfaz (network.wg0) si falta,
reconcilia las secciones wgpeer<N> (public_key + allowed_ips) y termina con
service network reload+ healthcheckwg show(auto-rollback si el túnel que estaba arriba no levanta). Anti-lockout: el peer con el pubkey del admin nunca se borra. Módulo: server-go/internal/orchestr/wireguard.go. - 10.4 — DAWN/802.11r config (write): modificar config de roaming desde la app con snapshot+rollback. Solo tras Fase 14 estabilice (necesitamos la visibilidad para diagnosticar si algo va mal).
- 10.5 — Batman-adv (futuro lejano): mesh sobre LAN. Dry-run obligatorio.
En desarrollo: #451, #452, #453. Esqueleto acordado 8-Ago-2026; el detalle de cada módulo vive en su propio issue.
Antes de desplegar módulos completos se estabilizan tres primitivas de seguridad:
- Ownership UCI + apply seguro con rollback (#451): las secciones gestionadas por NetPulse se marcan con
np_managed, el executor rechaza tocar secciones ajenas y el apply usa el rollback nativo de OpenWrt (o snapshot+revert) con healthcheck post-cambio. - Channel planning (#452): el agente recoge scan de vecinos (
iwinfo scan) y la UI recomienda canal óptimo por radio, aplicable vía #451. - Firmware upgrades (#453): descarga de imagen oficial, verificación,
sysupgradevía agente y recuperación si se pierde conectividad. Reutiliza el motor de #451.
Estos tres issues cierran los prerrequisitos comunes listados más abajo y desbloquean los módulos de bajo riesgo (Fase 18).
| # | Módulo | Fase | Riesgo |
|---|---|---|---|
| 17.1 | AdGuard Home (full) | 18 | Bajo |
| 17.2 | WiFi guest / aislada | 18 | Bajo |
| 17.3 | DDNS (ddns-scripts) | 18 | Bajo |
| 17.4 | QoS (sqm-scripts) | 18 | Bajo |
| 17.5 | WireGuard peers | 19 | Medio |
| 17.6 | WireGuard túnel nuevo | 19 | Medio |
| 17.7 | OpenVPN | 19 | Medio |
| 17.8 | Tailscale | 19 | Medio |
| 17.9 | DAWN + 802.11r (write) | 20 | Alto |
| 17.10 | Batman-adv mesh | 20 | Muy alto |
| 17.11 | DPI (nDPI / ntopng) | 20 | Alto |
| 17.12 | Firmware update in-app (labs) | 20 | Alto |
Actualización de firmware de los routers desde la app, como funcionalidad labs (mismo patrón que la orquestación: badge "labs" en el nav, opt-in vía flag). Activable hoy mismo aunque aún no esté la vista completa de gestión; el paso previo (detección de firmware desactualizado: alerta + campo en routers + impacto leve en salud) vive en el issue #241.
- Detección (ya modelada):
Router.firmwarese puebla en live desdeboard.Release.Description(live.go:777); la demo ya modelaFirmwareUpdated/FirmwareAvailable(demo_extras.go:38-41) y una alerta de firmware (demo_dataset.go:153). Fuente del "última versión": a decidir (feed estable OpenWrt, API de fabricante GL.iNet o target configurable). - Aplicación: reutiliza el motor plan→apply→state de la Fase 10 con
snapshot + healthcheck + auto-rollback: descarga de imagen oficial con
verificación de checksum,
sysupgradevía agente, y recuperación si se pierde conectividad. - Riesgo: Alto (un fallo puede dejar el router incomunicado); por eso arranca como labs y no entra en el canal stable hasta beta-testing.
Prerrequisitos comunes (se estabilizan al implementar 17.1 como spike):
- Instalador de paquetes unificado:
opkg(24.10),apk(25.12), binario oficial (AdGuard/Tailscale) o scripts firmware- específicos (GL.iNet). - Provisioner de scripts
/etc/init.d/,/etc/uci-defaults/, helpers. - Healthcheck post-apply robusto (SSH + WAN + WiFi) con auto-rollback <60s.
- Auditoría en
orchestr_audit+ rollback manualPOST /api/plans/{id}/rollback. - Modo
dry_runque muestra el plan completo sin tocar nada.
Los módulos de "escritura en router" son los más arriesgados del proyecto: un bug puede dejar la red incomunicada, requiriendo acceso físico o consola serie. Para llevarlos a producción segura necesitamos beta-testers externos con hardware variado antes de marcarlos como estables.
Canal de release dual:
stable(tagvX.Y.Z): solo módulos que han pasado el ciclo completo de beta-testing.unstable(prerelease rolling): todos los módulos disponibles, warning claro en la UI antes de apply, sin garantía de no romper la red.
Reclutamiento de beta-testers:
- Anuncio en r/openwrt, foro GL.iNet, foro OpenWrt y Discord de self-hosted.
- Criterios: hardware variado (GL.iNet, Xiaomi, Raspberry, x86_64), red real en uso, dispuesto a reportar fallos con logs, idealmente con consola serie o acceso físico para recuperación.
- Grupo pequeño inicial (5-10 personas) con canal directo (Matrix o Discord).
- Cada beta-tester recibe release notes + checklist + procedimiento de rollback documentado.
Ciclo de release por módulo:
- Sandbox (autor): development + tests en su propia red.
- Alpha (1-2 beta-testers de confianza): primer deploy en hardware distinto al del autor. Auto-rollback obligatorio.
- Beta (3-5 beta-testers): refinamiento de UX y manejo de errores.
- RC (release candidate, público
unstable): cualquier interesado. - Stable: tras 2 semanas sin incidentes en
unstable.
Sandbox + alpha suficiente, sin beta-tester estricto. Estabiliza el motor común de la Fase 17 (instalador, provisioner, healthcheck).
- 17.1 AdGuard Home (full) — spike inicial, patrón referencia.
- 17.2 WiFi guest / aislada.
- 17.3 DDNS.
- 17.4 QoS (sqm-scripts).
Beta obligatorio antes de unstable.
- 17.5 WireGuard peers.
- 17.6 WireGuard túnel nuevo.
- 17.7 OpenVPN.
- 17.8 Tailscale.
RC + 2 semanas en unstable sin incidentes antes de stable.
- 17.9 DAWN + 802.11r (write).
- 17.10 Batman-adv mesh.
- 17.11 DPI (nDPI / ntopng).
| Fase | Módulos | Estimación | Beta-testers |
|---|---|---|---|
| 18 | 4 | ~6-8 semanas | 1-2 (alpha) |
| 19 | 4 | ~8-10 semanas | 3-5 |
| 20 | 3 | ~10-12 semanas | 5+ (+ ciclo RC de 2 sem) |
Dentro de cada fase los módulos son independientes y pueden paralelizarse. Conviene empezar por la Fase 18 porque sus módulos estabilizan el motor común que las Fases 19-20 reusan.
Cada ítem de esta hoja de ruta se materializa como issue en GitHub antes de tocar código, y se cierra con el PR que lo resuelve (ver convención issue→PR acordada el 2026-08-03).
Separación desarrollo/deploy: cada fase indica qué es código y qué es despliegue en el servidor/routers. El desarrollo se commitea y pushea; el deploy se ejecuta aparte (install.sh, reinstall de agentes, etc.).