Files
chemavxandClaude Opus 5 97ff13cc83 docs: leer la sqlite de n8n con cp se salta el WAL
Un barrido hecho con cp dijo que un cambio recien escrito no estaba, media
hora despues de escribirlo. La copia no tiene los ultimos commits y no da
ningun error.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 16:15:30 +00:00

127 lines
6.3 KiB
Markdown

# Notas para agentes que trabajan en este cluster
Este es el directorio de trabajo de **Hermes** (el agente de Telegram) y de
cualquier sesión de Claude Code sobre el homelab. Aquí van solo las cosas que
**engañan al mirarlas**: lo que parece una avería y no lo es, y lo que parece
inofensivo y no lo es. Lo demás está en el skill `k8s-infra`.
## n8n: las ejecuciones «running» que nunca terminan NO están colgadas
Desde el **2026-07-30** el deployment lleva `EXECUTIONS_DATA_SAVE_ON_SUCCESS=none`.
Cuando un workflow sale **bien**, n8n crea igualmente la fila en
`execution_entity`, le pone `deletedAt` al instante y **no actualiza el estado**,
porque la va a tirar. El barrido duro se las lleva cada ~15 min.
O sea que `SELECT ... FROM execution_entity` enseña filas con
`status='running'` y `stoppedAt=NULL` que son **ejecuciones exitosas ya
borradas**. No hay nada que arreglar. Ya provocó un diagnóstico en falso el
mismo día que se puso.
Para saber si un workflow corre de verdad, mirar **`workflow_statistics`**:
```sql
SELECT name, count FROM workflow_statistics WHERE workflowId='<id>';
-- production_success sube en cada vuelta aunque la ejecución no se guarde
```
Sale también en `deletedAt IS NOT NULL`. Y ojo: `startedAt` y `deletedAt` no
llevan el mismo huso, así que restarlas no significa nada.
## n8n: lo que se ejecuta es la versión PUBLICADA, no el borrador
`workflow_entity.nodes` es el **borrador**. Producción usa la versión de
`workflow_history` apuntada por `workflow_entity.activeVersionId`. Editar
`nodes` a mano y reiniciar **no cambia nada y no da ningún error**. La API
pública (`PUT /api/v1/workflows/{id}`) sí publica; parcheando SQLite hay que
escribir en las dos tablas.
Diagnóstico rápido: comparar las dos columnas. Si difieren, lo que corre no es
lo que estás leyendo.
**Y aun escribiendo las dos, hay que REINICIAR el pod.** n8n carga los workflows
activos en memoria al arrancar; un `UPDATE` en SQLite no se los recarga, y la
ejecución siguiente sigue usando la versión vieja **sin dar ningún error**. Pasó
el 2026-07-31: la primera publicación coló solo porque ArgoCD reinició el pod
justo después por otro motivo, y la segunda se probó contra el código viejo
durante media hora. Regla: `kubectl -n n8n rollout restart deploy/n8n` y
comprobar que el `startTime` del pod es POSTERIOR a la escritura, antes de dar
por buena ninguna prueba.
## Leer `/data/n8n/database.sqlite` con `cp` enseña el pasado
La BD va en **WAL**. Un `cp` del fichero principal deja fuera el `-wal`, así que
la copia no tiene los últimos commits y **no da ningún error**: enseña
tranquilamente valores viejos. El 2026-07-31 un barrido hecho así dijo que un
cambio recién escrito no estaba, media hora después de escribirlo.
Para leer siempre en frío y completo:
```python
src = sqlite3.connect("file:/data/n8n/database.sqlite?mode=ro", uri=True)
dst = sqlite3.connect(":memory:"); src.backup(dst) # o a un fichero
```
Vale igual para el `sqlite3` del host: apuntarlo al fichero directamente sí lee
el WAL, pero **cualquier copia previa con `cp`, no**. Es la misma razón por la
que las copias de seguridad usan `.backup` y no `cp`.
## Antes de tocar una BD de un pod: desarmar ArgoCD
Casi todo está bajo Applications con `automated{selfHeal,prune}`. Un
`kubectl scale --replicas=0` para hacer un `VACUUM` se revierte solo a mitad.
```sh
kubectl -n argocd patch app <app> --type json \
-p '[{"op":"remove","path":"/spec/syncPolicy/automated"}]'
# ... trabajo ...
kubectl -n argocd patch app <app> --type merge \
-p '{"spec":{"syncPolicy":{"automated":{"prune":true,"selfHeal":true}}}}'
```
Rearmarlo al final levanta el pod y aplica la config nueva de una vez.
## ArgoCD: una Application de directorio se traga los `.json`
`path: n8n` incluye `*.yaml`, `*.yml` **y `*.json`** como manifiestos. Un JSON
que no sea un objeto de Kubernetes deja la app en `ComparisonError` («Object
'Kind' is missing»). Por eso `application-n8n.yaml` tiene
`directory.exclude: "workflow-*.json"`. Los `.md` no se parsean: este fichero es
seguro.
## El auto-reinicio de Uptime Kuma sí funciona (desde 2026-07-30) y va cerrado
Kuma cae → notificación «n8n Auto-Restart» → webhook público de n8n → PATCH del
`restartedAt`. Antes no reiniciaba nada (sin RBAC: 403), y sus «éxitos» eran el
aviso de FALLO enviándose bien.
- La SA es `n8n-restarter`. Desde el **2026-07-31** son **Roles por namespace
con `resourceNames`** sobre los 12 workloads exactos del `SERVICE_MAP` (ver
`n8n/rbac-restarter.yaml`), no un ClusterRole abierto. Si añades un servicio
al mapa, **hay que añadirlo también ahí** o dará 403 — deliberado: el mapa y
el permiso se mueven juntos.
Motivo: esa SA va montada en el **pod entero** de n8n y los nodos Code leen el
token de `/var/run/secrets/…`, así que cualquier workflow con un nodo Code
hereda lo que se conceda aquí. El límite no es el mapa, es el RBAC.
- La clave del mapa es el **nombre exacto del monitor en Kuma**. Renombrarlo
allí lo desconecta en silencio.
- El webhook exige la cabecera `X-Kuma-Token` (secret `n8n-kuma-webhook`, fuera
de git) y desde el **2026-07-31 falla en CERRADO**: el `env` ya no lleva
`optional`, así que sin secreto el pod no arranca, y la puerta lanza error si
la variable falta. Antes se saltaba la comprobación «para no quedarte sin
avisos», y ese argumento era falso: el aviso de caída lo manda Kuma por su
notificación 1, directa, no por este webhook.
- Para apps bajo ArgoCD, el `restartedAt` se **revierte** tras el reinicio. El
pod sí se reinicia; la anotación no queda. No es un fallo. Por eso el éxito se
comprueba con `observedGeneration >= la generation que devolvió el PATCH`, y
con `>=` y no con `==`: la reversión vuelve a subir la generation.
- **Cómo saber por qué rama fue una ejecución** cuando `SAVE_ON_SUCCESS=none` ya
la ha vaciado: por la duración. `deletedAt - startedAt` (con el offset fijo de
1 h) da ~0,02 s si la puerta rechazó, ~0,3 s si el PATCH falló y se fue por la
rama de fallo, y ~60 s si hizo el ciclo completo con su espera.
## Un workflow que termina en verde no dice que haya hecho su trabajo
Vale para n8n y para cualquier automatismo de aquí: comprobar el **efecto**
(que el `restartedAt` cambió, que el pod es nuevo, que el fichero está), no el
código de salida ni el contador de éxitos.