Tres agujeros que encontro una review adversarial de Codex sobre el trabajo
de ayer. Los tres verificados contra el fichero antes de tocar nada.
1. El webhook publico fallaba en ABIERTO. Con `optional: true` y un
`if (secreto && ...)`, la ausencia del secreto equivalia a autorizacion:
cualquier POST con heartbeat.status=0 y un monitor.name del mapa reiniciaba
n8n, Gitea, ArgoCD, Vaultwarden o los blogs. El argumento para dejarlo asi
era "que un despiste no te deje sin avisos", y era falso: el aviso de caida
lo manda Kuma por su notificacion 1, directa, no por este webhook.
Ahora el secreto es obligatorio (sin `optional`) y la puerta rechaza si
falta. Secreto ausente da error ruidoso; cabecera que no casa, vacio.
2. El ClusterRole permitia patch sobre CUALQUIER deployment de 10 namespaces,
y la SA va montada en el pod entero de n8n: cualquier workflow con un nodo
Code heredaba eso. Ahora son Roles por namespace con resourceNames sobre
los 12 workloads exactos del SERVICE_MAP.
3. El flujo daba exito aunque el PATCH fallara. `restartOk` se calculaba y se
tiraba: `K8s API Check Status` construia un objeto nuevo sin el, y el IF
final solo miraba `statusOk`. Un 403 sobre un servicio ya sano mandaba
"reiniciado correctamente". Ahora hay rama de fallo tras el PATCH y el
exito exige observedGeneration >= la generation que devolvio el PATCH.
Se compara con >= porque selfHeal de ArgoCD revierte la anotacion y vuelve
a subir la generation; con == daria un fallo falso.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
/webhook/uptime-kuma-restart es publico y sin autenticar. Mientras no reiniciaba
nada daba igual; desde hoy reinicia doce servicios, los dos blogs incluidos, asi
que cualquiera que diera con la ruta podia zarandear el cluster.
Uptime Kuma manda ahora la cabecera X-Kuma-Token (webhookAdditionalHeaders de la
notificacion "n8n Auto-Restart") y el primer nodo Code la compara con
KUMA_WEBHOOK_TOKEN antes de hacer nada.
La comprobacion falla en ABIERTO si el secreto no esta configurado en n8n: un
despiste de configuracion degrada al comportamiento de antes en vez de dejar de
avisar en silencio, que es el fallo que no se ve venir. Si esta configurado y no
coincide, se devuelve vacio: ni reinicio, ni Telegram, ni fila de error que se
pueda llenar a base de peticiones.
El secret n8n-kuma-webhook se crea con kubectl y NO esta en git; el env va con
optional: true para que la ausencia no impida arrancar el pod.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Los workflows viven solo en la SQLite, que ya se revirtio sola una vez (16-jul,
a un snapshot de abril) y se llevo por delante mes y medio de trabajo. Este en
concreto es el que reacciona a las caidas, asi que conviene tenerlo fuera.
Ya no hay nada secreto que ocultar: los tres nodos de Telegram leen el token de
$env.TELEGRAM_BOT_TOKEN en vez de llevarlo incrustado.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ghost EN y ES son lo unico del mapa que da la cara al publico y lo que peor se
lleva con dos semanas sin nadie mirando. Entran en el SERVICE_MAP con la clave
igual al nombre exacto del monitor de Kuma.
El monitor "www.zonadeexclusion.com (301->apex)" se queda fuera a proposito:
que ese 301 falle es cosa de Traefik o del DNS, no de Ghost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
El workflow "Uptime Kuma -> K8s Auto-Restart" lleva desde siempre sin poder
reiniciar nada: hacia el PATCH con el token de la ServiceAccount "default" del
namespace, que no tiene ni un permiso, y el API contestaba 403. Se iba entonces
por la rama de "reinicio fallido" y mandaba el aviso por Telegram -- que se
enviaba bien, y por eso los 23 "exitos" de workflow_statistics. La integracion
parecia viva y no lo estaba.
SA propia (n8n-restarter) en vez de la default, y un RoleBinding por namespace
en lugar de un ClusterRoleBinding: el webhook que dispara esto es publico y sin
autenticar, asi que el alcance se limita al SERVICE_MAP del workflow.
El ClusterRole incluye get ademas de patch porque el workflow relee el objeto a
los 60 s para comprobar readyReplicas (sin get: "Cannot read properties of
undefined"), y statefulsets ademas de deployments porque Gitea es lo primero.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tres workflows ACTIVOS fallaban con "Module 'https' is disallowed" sin que
saltara ninguna alerta, porque estaba NODE_FUNCTION_ALLOW_EXTERNAL (paquetes
npm) pero no NODE_FUNCTION_ALLOW_BUILTIN (modulos internos):
- Uptime Kuma -> K8s Auto-Restart ultimo exito 2026-07-23 07:51
- TLS Certs -> Alerta Telegram ultimo exito 2026-04-13
- Resumen Diario - Metricas K8s 30 fallos, 0 exitos
Es decir: el auto-reinicio que dispara Uptime Kuma llevaba una semana
muerto. Se rompio al reiniciarse el pod la tarde del 23-jul, cuando el task
runner empezo a bloquear require() de modulos internos.
Lista explicita (fs,http,https) en vez de '*': es justo lo que piden los
nodos Code de esos workflows, y asi un modulo nuevo falla a la vista en vez
de estar concedido de antemano.
La poda por edad ya estaba activa de serie (n8n 2.15.1, prune=true, 336 h):
los IDs de ejecucion empiezan en 22.634 con 7.052 filas, o sea que ya habia
borrado ~22.600 el solo. El tar diario crecia 0,4 MB/dia y estaba llegando a
meseta, asi que no habia desbocamiento: la premisa era falsa.
El problema real era la composicion. De 322 MB de execution_data, 246 MB
(76%) eran las 3.436 ejecuciones EXITOSAS de 'Real Madrid RSS -> Twitter',
que corre cada 5 min y guarda 73 KB por vuelta. Sus errores -lo unico que
sirve para depurar- ocupaban 2 MB.
Y no es un problema de disco (21%, 699 GB libres) sino de backup: n8n esta
en CROWN, asi que ese .tar.gz de 101 MB viajaba entero a MEGA cada dia y a
B2 en las dos ultimas copias. Es justo el gasto que revento la cuota el
2026-07-25.
Nada depende de ese historico: el monitor de reversion solo lee
workflow_entity, el panel de Grafana mira replicas del deployment y
'Resumen Diario' saca metricas de k8s por un nodo Code.
CronJob externo a n8n que lee la SQLite en solo-lectura por el hostPath y avisa
si el workflow del autopost desaparece (firma de la reversión del 2026-07-16) o
si el total de workflows se desploma. La reversión SUBE el nº de activos, así que
un umbral de activos no la cazaría; el disparador es la desaparición del autopost.
Cada 6h. Reutiliza telegram-notify-infisical.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Elimina manifests openclaw/ y argocd/application-openclaw.yaml
- backup-system: fuera del backup diario, crown-jewels, expected y retain;
fuera el mount hostPath del cronjob y la fila de RESTORE.md
- monitoring: eliminado panel openclaw del dashboard homelab-overview
- n8n: openclaw y polymarket fuera del health-check (espejo del workflow vivo)
- cluster-wide/namespaces.yaml: fuera openclaw + añadidos separadores '---'
que faltaban (el archivo era un único doc YAML donde ganaba el último ns)
Cluster ya limpio: app ArgoCD, namespace, PVC/PV, RBAC cluster-scoped y
monitor de Uptime Kuma. Backup final: ~/decommissioned/openclaw-final-2026-07-18.tar.gz
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Para que el workflow 'Health Check General' lea el token del bot de notificaciones
via process.env en vez de hardcodearlo (rotación 2026-07-09).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- openclaw/golden/openclaw.json: botToken → placeholder (referencia Infisical)
- n8n/health-check-code-node.js: literal → process.env.TELEGRAM_BOT_TOKEN
Token del bot @chemavx_bot rotado 2026-07-09 (BotFather). El valor vivo se
gestiona en Infisical homelab/prod/telegram. NOTA: el token viejo sigue en el
HISTORIAL de git; la revocación en BotFather es lo que lo neutraliza.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
3c: n8n now sources the encryption key from n8n-secret-infisical (Infisical).
Remove the empty CreateOnly stub manifest and the now-dead ignoreDifferences
/data stanza so ArgoCD prunes the old out-of-band n8n-secret.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cut n8n over to the Infisical-synced encryption key (byte-identical, key never
changes -> credentials stay decryptable) and switch RollingUpdate -> Recreate
(485MB SQLite + WAL on RWO must not have two writers during a roll).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Out-of-band Secret -> Infisical homelab/prod /n8n. Parallel-name target
n8n-secret-infisical (Owner), passthrough of the single key encryption-key
(n8n's irrecoverable credentials-encryption root, lifted byte-identical).
Old secret stays live until decommission. ArgoCD-managed -> git->sync.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3c: hooks now read telegram-notify-infisical (synced from homelab/prod
/telegram). Deleting these 4 manifests prunes the old plaintext secrets — the
bot token leaves git for the remaining 4 namespaces. Completes migration #1.
NOTE: polymarket-bot/bot-secrets still embeds the same token (migration #3).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3b: all 4 hooks' secretKeyRefs now read the Infisical-managed secret.
Old plaintext telegram-notify secrets stay as fallback until 3c.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase 3a of telegram-notify migration: each syncs TELEGRAM_BOT_TOKEN/
TELEGRAM_CHAT_ID from shared homelab/prod /telegram via read-only
infisical-operator/infisical-auth, into parallel-name telegram-notify-infisical.
Old plaintext secrets + hooks untouched (repoint=3b, removal=3c).
polymarket-bot: standalone secret only; bot-secrets copy is migration #3.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Create telegram-notify secret in n8n, portfolio, polymarket-bot, researchowl, openclaw
namespaces (values mirrored from monitoring/grafana-telegram)
- Update existing smoke tests (n8n, portfolio, polymarket-bot) to send [OK]/[FAIL]
Telegram notifications on success/failure
- Add postsync-smoke-test for openclaw (curl GET / on port 18789)
- Add postsync-smoke-test for researchowl (no HTTP port; checks readyReplicas via
k8s API using a smoke-test-reader ServiceAccount + Role + RoleBinding)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>