# 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