From ef75792c18c98859cd9f7f73b1043e28eeb93cf8 Mon Sep 17 00:00:00 2001 From: chemavx Date: Thu, 23 Jul 2026 16:45:20 +0000 Subject: [PATCH] n8n: vigilante contra reversiones de BD (alerta Telegram) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- n8n/cronjob-workflow-monitor.yaml | 157 ++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 n8n/cronjob-workflow-monitor.yaml diff --git a/n8n/cronjob-workflow-monitor.yaml b/n8n/cronjob-workflow-monitor.yaml new file mode 100644 index 0000000..0a00e08 --- /dev/null +++ b/n8n/cronjob-workflow-monitor.yaml @@ -0,0 +1,157 @@ +# Vigilante de n8n contra reversiones de BD (como la del 2026-07-16, en que la +# SQLite de n8n se revirtió a un snapshot de abril durante los reinicios de +# kernel: desaparecieron los workflows de julio —incluido el autopost de Ghost +# EN— y volvieron activados los bots viejos, sin que nadie lo notara en una +# semana, hasta que la copia buena se purgó de los backups por la retención). +# +# Lee la SQLite EN SOLO LECTURA por el hostPath /data/n8n (independiente del pod +# de n8n: funciona aunque n8n esté roto) y avisa por Telegram si el workflow del +# autopost DESAPARECE (la firma de la reversión; ojo: la reversión SUBE el nº de +# activos, así que un umbral de "activos" no la cazaría) o si el total de +# workflows se desploma. Cada 6 h: caza dentro del día, deja margen de sobra +# antes de que la retención de 7 días borre la última copia buena. +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: n8n-monitor-script + namespace: n8n +data: + n8n_monitor.py: | + #!/usr/bin/env python3 + """Vigilante de n8n: caza una reversión de BD. Sin deps externas.""" + import os + import sqlite3 + import sys + import urllib.parse + import urllib.request + + DB = os.environ.get("N8N_DB", "/n8n/database.sqlite") + TOTAL_FLOOR = int(os.environ.get("TOTAL_FLOOR", "18")) + AUTOPOST_MATCH = os.environ.get("AUTOPOST_MATCH", "%X Autopost%") + + + def send_telegram(text): + token = os.environ["TELEGRAM_BOT_TOKEN"] + chat = os.environ["TELEGRAM_CHAT_ID"] + data = urllib.parse.urlencode( + {"chat_id": chat, "text": text, "disable_web_page_preview": "true"} + ).encode() + req = urllib.request.Request( + f"https://api.telegram.org/bot{token}/sendMessage", data=data + ) + with urllib.request.urlopen(req, timeout=20) as r: + r.read() + + + def main(): + if "--install-test" in sys.argv: + send_telegram("✅ n8n workflow monitor instalado y funcionando (prueba).") + print("mensaje de prueba enviado") + return + + try: + con = sqlite3.connect(f"file:{DB}?mode=ro", uri=True, timeout=30) + total = con.execute("SELECT COUNT(*) FROM workflow_entity").fetchone()[0] + active = con.execute( + "SELECT COUNT(*) FROM workflow_entity WHERE active=1" + ).fetchone()[0] + ap = con.execute( + "SELECT active FROM workflow_entity WHERE name LIKE ?", (AUTOPOST_MATCH,) + ).fetchone() + con.close() + except Exception as e: # noqa: BLE001 + send_telegram( + f"🚨 n8n monitor: no pude leer la BD de n8n ({e}). ¿Pod/DB caídos?" + ) + print("ERROR leyendo la BD:", e) + sys.exit(1) + + problems = [] + if ap is None: + problems.append( + "el workflow «ExclusionZone → X Autopost» ha DESAPARECIDO — " + "firma de reversion de BD (como el 2026-07-16)" + ) + if total < TOTAL_FLOOR: + problems.append( + f"solo {total} workflows (esperados >={TOTAL_FLOOR}) — posible perdida de datos" + ) + + if problems: + msg = ( + "🚨 ALERTA n8n:\n" + + "\n".join("• " + p for p in problems) + + f"\n\n(total={total}, activos={active})\n" + + "Runbook: k8s-manifests/backup-system/RESTORE.md (Escenario A)" + ) + send_telegram(msg) + print("ALERTA enviada:", problems) + sys.exit(1) + + print(f"OK: total={total} activos={active} autopost={'ON' if ap[0] else 'OFF'}") + + + if __name__ == "__main__": + main() +--- +apiVersion: batch/v1 +kind: CronJob +metadata: + name: n8n-workflow-monitor + namespace: n8n +spec: + schedule: "0 */6 * * *" + concurrencyPolicy: Forbid + successfulJobsHistoryLimit: 3 + failedJobsHistoryLimit: 3 + jobTemplate: + spec: + backoffLimit: 1 + activeDeadlineSeconds: 120 + template: + metadata: + labels: + app: n8n-workflow-monitor + spec: + restartPolicy: Never + # /data/n8n vive en chemavx-k8; hay que leerlo desde ahí. + nodeSelector: + kubernetes.io/hostname: chemavx-k8 + securityContext: + runAsUser: 1000 + runAsGroup: 1000 + containers: + - name: monitor + image: python:3.12-alpine@sha256:6d43704baacd1bfbe7c295d7f13079d5d8104ed33568873133f8fc69980419df + command: ["python3", "/scripts/n8n_monitor.py"] + envFrom: + # TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID (sincronizado de Infisical) + - secretRef: + name: telegram-notify-infisical + env: + - name: N8N_DB + value: /n8n/database.sqlite + - name: TOTAL_FLOOR + value: "18" + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 64Mi + volumeMounts: + - name: n8n-data + mountPath: /n8n + readOnly: true + - name: script + mountPath: /scripts + volumes: + - name: n8n-data + hostPath: + path: /data/n8n + type: Directory + - name: script + configMap: + name: n8n-monitor-script