ADR: Plan Servicios Fase C ligera — Migración de 6 crons del Bibliotecario a STAGE Hetzner
Contexto
Tras el cierre operativo del Plan Servicios al 95% (ver [[decision—20260516—auditoria-simplificacion-servicios-auxiliares]]), Edu replanteó si valía la pena ejecutar la Fase C — Migración selectiva a Hetzner aún siendo OPCIONAL.
El análisis de consumo Actions real reveló:
- 22% del cap incluido Plan Pro (660/3000 min/mes estimados)
- $0 overage actual ($4/mes Plan Pro fijo, ningún gasto Actions adicional)
- Spending limit Actions a $20 era techo de seguridad, no gasto real
→ El driver económico para Fase C no existe hoy.
Sin embargo, Edu identificó valor cualitativo en migrar:
- Resiliencia: crons siguen si GitHub Actions cae (raro pero pasa)
- Margen futuro: si el proyecto crece a 5x consumo, ya está preparado
- Frescor del Bibliotecario: con cap Actions levantado, subir frecuencia de los crons “gratis al escalar” (Catalog/Utility/Reindex-TS) ya no tiene techo
Decisión
Ejecutar Fase C en variante ligera: migrar los 6 cron-workflows del Bibliotecario que son llamadas curl-MCP triviales. NO migrar self-hosted runner para CI (riesgo de saturación CPU/RAM STAGE durante runs). NO migrar workflows que requieren clone repo + Python/Node + git push (catalog, translate, weekly-report, bib-reindex-ts schedule) — se quedan en GH Actions hasta una iteración futura si es necesario.
Workflows migrados (6 · cron systemd STAGE)
| Workflow | Frecuencia | Llama LLM? | Coste estimado/mes |
|---|---|---|---|
wiki-curator.sh (Sonnet review drafts) | L+J 05:00 UTC | Sí | ~$4 |
wiki-lint.sh (bulk + contradictions incremental) | Diario 04:30 UTC | No | $0 |
wiki-lint-consolidation.sh (mode full) | Dom 03:00 UTC | No | $0 |
wiki-utility.sh (recompute scores) | L+M+V 06:00 UTC | No | $0 |
wiki-drift-check.sh (Haiku dry_run cap 5) | Diario 06:00 UTC | Sí (Haiku) | ~$0.5 (dry_run) |
drift-cron-escriba.sh (REST endpoint) | Diario 04:15 UTC | (verificar) | ~$0 |
Workflows NO migrados (4 · siguen en GH Actions)
wiki-catalog.yml— Python + git push (necesita clone workspace + PAT)wiki-translate.yml— Node + git push (Node no instalado en STAGE)wiki-weekly-report.yml— Python + git pushbib-reindex-ts.yml(schedule diario) — Node + MCP (Node no instalado · su push trigger se mantiene siempre)
Frecuencias mantenidas iguales
Decisión consciente: no subir frecuencias en la misma sesión que se migra. Principio de cambios pequeños iterativos. Tras validar que los crons systemd corren limpios 1-2 semanas, se puede decidir aumento de frecuencia de los workflows “gratis al escalar” (utility a diario, por ejemplo) en una iteración separada.
Implementación
Estado STAGE pre-migración
- 2 vCPUs · 3.7 GB RAM · 22 GB disco libre
- Load avg 0.12 (idle)
- Patrón cron ya establecido para Bibliotecario:
/etc/cron.d/bib-reindexcorriendo desde s50 sin incidentes - Tooling disponible: curl 8.5, jq 1.7, git 2.43, Python 3.12.3, systemd 255
- Node NO instalado (motivo de NO migrar workflows Tipo A)
Footprint añadido tras migración
- RAM idle: 0 MB (scripts ephemeral, mueren al acabar)
- RAM pico: 200-400 MB durante run (~1-2 min/run)
- CPU pico: 5-15s 1 core por run
- Disco: ~50 MB (logs + tmp)
- Veredicto: insignificante sobre el footprint actual
Estructura desplegada
/opt/biblioteca-crons/
├── _common.sh (lib compartida: token loading + mcp_call + mcp_verify)
├── wiki-curator.sh
├── wiki-lint.sh
├── wiki-lint-consolidation.sh
├── wiki-utility.sh
├── wiki-drift-check.sh
├── drift-cron-escriba.sh
└── logs/
└── <name>.log
/etc/cron.d/biblioteca-crons (6 líneas cron, frecuencias = workflows GH Actions originales)
Tokens reutilizados de /opt/bib-reindex/ (single source of truth):
.token(MCP_TOKEN).cf-access-id+.cf-access-secret(CF Access service token)
Smoke test (16-05-2026 noche)
5 de los 6 scripts ejecutados manualmente:
wiki-utility.sh→ OK (1s, MCP responde)wiki-drift-check.sh→ OK (25s · 5 pages procesadas · 2 drift detectados · $0.0148)drift-cron-escriba.sh→ OK (1s · REST endpoint OK)wiki-lint.sh→ OK (33s · 2 calls secuenciales bulk + contradictions)wiki-lint-consolidation.sh→ OK (3s · mode full)
wiki-curator.sh NO ejecutado manualmente (Sonnet con coste notable · misma lógica MCP que el resto, validada).
Desactivación schedule en GH Actions
Los 6 workflows .yml del workspace tienen schedule: comentado con bloque explicativo:
on:
# schedule deshabilitado 16-05-2026 (s66) — migrado a STAGE Hetzner (Plan Servicios Fase C ligera)
# Cron systemd activo: /etc/cron.d/biblioteca-crons → /opt/biblioteca-crons/<name>.sh
# ADR: decision--20260516--fase-c-crons-stage-ligera
# Para reactivar en GH Actions: descomentar bloque schedule abajo + desactivar línea en /etc/cron.d/biblioteca-crons.
# schedule:
# - cron: "..."
workflow_dispatch:
...
workflow_dispatch: mantenido para invocación manual y rollback trivial (descomentar schedule + commit).
Alternativas descartadas
Full Fase C (incluir self-hosted runner CI)
Rechazada. Self-runner persistente consume 200-300 MB RAM idle + 1-2 GB durante runs CI. Riesgo concreto: CI compite con Django STAGE durante runs (timeout web/worker o tests STAGE lentos). El valor añadido (minutos Actions ilimitados) no compensa cuando actualmente estamos al 22% del cap.
Incluir wiki-catalog/translate/weekly-report (Tipo A)
Postponed. Requieren clone del repo workspace en STAGE + Python deps + Node + PAT con permisos push + adaptar scripts. Estimado +2-3h trabajo. Se hará si emerge dolor real (consumo Actions creciendo o necesidad de subir frecuencia de catalog).
No hacer Fase C
Rechazada por Edu. El valor cualitativo (resiliencia, margen futuro) supera el coste mínimo de implementación cuando ya existe el patrón cron-bib-reindex.sh validado en STAGE.
Consecuencias
Positivas
- 6 workflows menos en consumo GH Actions (~36 min/mes ahorrados, marginal pero acumulativo)
- Resiliencia: estos 6 crons sobreviven a outages GitHub Actions
- Latencia: ~10-30s ahorro por run (no esperar runner managed)
- Sin gasto API Anthropic adicional (mismas llamadas, mismo flujo, solo cambia el caller)
Negativas / Riesgos
- Mantenimiento STAGE: ahora 8 cron jobs corren en STAGE (bib-reindex × 3 + biblioteca-crons × 6 + dr-backups + gh-actions-watchdog). Si STAGE cae, el Bibliotecario pierde ~50% de su procesamiento periódico.
- Logs no centralizados en GH UI: ahora hay que
ssh root@crearack-staging "tail -50 /opt/biblioteca-crons/logs/<name>.log"para troubleshoot. Mitigación: GitHub Issues open en error (futuro, no implementado todavía). - Pausa global perdida: el toggle
vars.BIBLIOTECARIO_PAUSADOya no afecta a los 6 migrados. Si se necesita pausar Bibliotecario completo, hay que tocar 2 sitios ahora: GH variable + comentar líneas en/etc/cron.d/biblioteca-crons.
Mitigaciones
- Rollback trivial: descomentar
schedule:en los.yml+ comentar líneas en/etc/cron.d/biblioteca-crons→ vuelven a GH Actions. 5 min máx. - Logs accesibles vía tailnet
ssh root@crearack-staging. - Si el patrón funciona 1-2 semanas, considerar Tipo A (catalog, translate, weekly-report) en iteración futura.
Estado
- 16-05-2026 (s66 noche): ADR firmado, implementación completada, smoke test 5/6 OK. Workflows GH Actions con
schedule:comentado pushed. Cron systemd activo en STAGE. Primer trigger automático esperado: 17-05-2026 03:00 UTC (wiki-lint-consolidation domingo).
Verificación a corto plazo (1-2 semanas)
- Confirmar primer run automático de cada cron systemd (logs en
/opt/biblioteca-crons/logs/). - Confirmar que el resultado del cron en STAGE = el resultado que producía el workflow en GH Actions (mismo content en D1, mismas pages procesadas).
- Si OK: cerrar verificación a 17-05-2026 +14d.
- Si emerge un fallo persistente: rollback rápido + investigar causa.
Reapertura / siguiente iteración
Si tras 1-2 semanas el patrón funciona y emerge interés:
- Sub-fase C.2: instalar Node en STAGE + clonar repo workspace + PAT push + migrar wiki-catalog/wiki-translate/wiki-weekly-report/bib-reindex-ts(schedule). Crear ADR independiente.
- Aumento de frecuencias: Catalog L+M+V → diario, Utility L+M+V → diario, Reindex-TS 24h → 12h. Todo gratis al escalar (no llaman LLM).
Véase también
- [[concept—workspace—supercontexto-05-crons]]
- [[concept—infra—staging-server]]
- [[decision—20260516—auditoria-simplificacion-servicios-auxiliares]]