From d1cd326e43710247779acf67675716dadc6b783a Mon Sep 17 00:00:00 2001 From: hermes-nyora Date: Sun, 23 Aug 2026 14:41:51 +0000 Subject: [PATCH] docs: runbook reglement-definitif fix worker timeout + MCP + hermes-tt (Gemini exec, Hermes verify) --- _INDEX.md | 1 + common/reglement-definitif-fix-mcp.md | 134 ++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 common/reglement-definitif-fix-mcp.md diff --git a/_INDEX.md b/_INDEX.md index 393dafd..5823679 100644 --- a/_INDEX.md +++ b/_INDEX.md @@ -13,6 +13,7 @@ | [Hermes Hub — Déploiement et Exploitation Multi-Univers sur VPS (FastAPI, proxy async, design tokens, Tailscale)](common/hermes-hub-deploiement.md) | 2026-08-19 | hermes, hub, multi-univers, vps, tailscale, cloudflare-access, fastapi | | [Déploiement Superpowers — Flotte Hermes Agent, Claude Code & Antigravity (procédure, piège TLS 1.3, piège SSH PATH Synology)](common/superpowers-hermes-installation.md) | 2026-08-18 | hermes, superpowers, plugins, skills, tls, ssh, synology, claude-code, antigravity | | [baserow-schema-mcp — 4 outils de gestion de schema (create/delete table/field), companion service au MCP natif Baserow](common/baserow-schema-mcp.md) | 2026-08-17 | baserow, mcp, schema, jwt | +| [reglement-definitif-api — fix WORKER TIMEOUT/OOM (lazy WeasyPrint, gunicorn 1 worker) + serveur MCP reglement-mcp + intégration hermes-tt](common/reglement-definitif-fix-mcp.md) | 2026-08-23 | mcp, docker, gunicorn, weasyprint, tunisie-telecom, gemini | | [Rotation des secrets — inventaire, portée réelle par empreinte, anomalies inter-sphères](common/rotation-secrets-inventaire.md) | 2026-07-18 | securite, secrets, rotation | | [Bifrost — routage texte par coût : état de la mesure, logs.db, clés virtuelles, incident 401](common/bifrost-routage-par-cout.md) | 2026-07-18 | bifrost, llm, cout, routage | | [Skills n8n officiels (n8n-io/skills) — socle 14 skills, tap auto 3 instances](common/n8n-skills-officiels.md) | 2026-07-10 | n8n, skills, hermes, tap | diff --git a/common/reglement-definitif-fix-mcp.md b/common/reglement-definitif-fix-mcp.md new file mode 100644 index 0000000..b9321ec --- /dev/null +++ b/common/reglement-definitif-fix-mcp.md @@ -0,0 +1,134 @@ +# reglement-definitif-api — Fix WORKER TIMEOUT/OOM + serveur MCP + intégration hermes-tt + +**Instance auteur** : hermes-nyora +**Date** : 2026-08-23 +**Tags** : [infra, mcp, docker, flask, gunicorn, weasyprint, tunisie-telecom, gemini] +**Statut** : valide + +--- + +## Probleme + +L'application Flask « Règlement Définitif » (Tunisie Telecom, DCZS) tournait en boucle de crash depuis fin juillet 2026 : + +``` +[CRITICAL] WORKER TIMEOUT (pid:7) +[ERROR] Worker (pid:7) was sent SIGKILL! Perhaps out of memory? +``` + +Le master gunicorn démarrait, les 2 workers bootaient, puis timeout à ~30s (défaut gunicorn) → SIGKILL → reboot workers → re-timeout, en boucle. Dernier incident avant fix : 22/08/2026. + +Cause racine confirmée : imports top-level lourds (`from weasyprint import HTML, CSS` dans `routes/reports.py` et `services/pdf_generator.py`) exécutés à chaque boot de worker via `create_app()`. Sur le Celeron J4125 du NAS DS920+, 2 workers concurrents chargeant WeasyPrint/CFFI + google-api-client dépassaient les 30s sans heartbeat → kill du master. Facteur aggravant RAM : WeasyPrint ~150-200 Mo/worker sur un NAS contraint. + +Deuxième objectif du projet : exposer les données de l'app comme outils MCP pour hermes-agent-tt (l'app n'a aucune API REST, que du web UI derrière Flask-Login). + +--- + +## Contexte et contraintes + +- Conteneur d'origine créé SANS Dockerfile visible, SANS volume (code cuit dans l'image), SANS labels compose → rebuild impossible tel quel, données non persistées. +- `credentials.json` (Google Service Account `excel-sync-483316`) et `users.db` n'existaient QUE dans le conteneur → extraction obligatoire AVANT suppression (perte définitive sinon). +- Réseau Docker partagé : `n8n` (external), où vivent aussi `hermes-agent-tt` et le reste de la stack. +- Ports host : api `5099:5055`, MCP `5098:8000` (5098 libre vérifié au préalable). +- Le projet est piloté par un workflow AntiGravity-Gemini (exécution complète, accès NAS via `ssh nas`) avec Hermes-Nyora en vérification/cadrage uniquement. + +--- + +## Ce qui NE fonctionne PAS + +| Tentative | Erreur obtenue | Raison de l'échec | +|-----------|----------------|-------------------| +| Gunicorn défaut (2 workers sync, timeout 30s) | `[CRITICAL] WORKER TIMEOUT` + SIGKILL OOM en boucle | Imports WeasyPrint top-level > 30s au boot ×2 workers concurrents | +| Repo Gitea `reglement-definitif` (état antérieur au projet) | Runbook 04/07 affirmait « source + Dockerfile sur Gitea » | FAUX — vérifié par API Gitea : aucun repo existant. Le conteneur était orphelin de toute source | +| Appel `tools/list` MCP direct sans session | `{"error":{"code":-32600,"message":"Bad Request: Missing session ID"}}` | StreamableHTTP exige initialize d'abord puis header `mcp-session-id` sur chaque appel | +| Endpoint `/mcp` sans Bearer | HTTP 401 | Normal — auth active (testé et validé) | + +--- + +## Solution validée + +### Architecture retenue (décisions Gemini, validées Hermes) + +1. **Fix app** (aucun changement métier) : + - Lazy import WeasyPrint (déplacé dans les fonctions de rendu) → boot < 0,5s + - `cachetools.TTLCache(maxsize=256, ttl=300)` remplace le dict global non borné + - Résilience credentials : classe `GoogleCredentialsError` + `check_credentials_status()` — l'app démarre même sans credentials.json (erreur claire sur les écrans Sheets, pas de crash) + - Gunicorn : `--workers 1 --timeout 120 --graceful-timeout 30`, PAS de `--preload` (WeasyPrint/CFFI + fork = risque deadlock) + - Dockerfile `python:3.11-slim` + libs système Pango/Cairo/GdkPixbuf + fonts-dejavu + healthcheck `python -c urllib` (wget absent des images slim) +2. **Serveur MCP séparé** (`reglement-mcp`) : FastMCP StreamableHTTP sur `/mcp`, port 5098:8000, auth Bearer `MCP_SECRET_TOKEN`. Importe directement `app/config.py` + `app/services/` (gsheets, cache) — ces fichiers n'importent PAS Flask (vérifié). Image slim sans WeasyPrint. +3. **8 outils MCP agrégés** : list_marches, get_marche, po_stats, detail_po, appels_commande, consommation (+alertes >90%), instance_validation, dashboard_summary. Format tunisien DT, pas de dump brut. + +### Ordre d'exécution (respecté) + +```bash +# 1. SAUVEGARDE avant toute suppression (critique) +docker cp reglement-definitif-api:/app/credentials.json /volume1/docker/reglement-definitif/credentials.json +docker cp reglement-definitif-api:/app/data/users.db /volume1/docker/reglement-definitif/data/users.db.bak + +# 2. Déployer l'API (sur le NAS, piège PATH Synology) +ssh nas "export PATH=/usr/local/bin:\$PATH; cd /volume1/docker/reglement-definitif && docker compose build --no-cache && docker compose up -d" +ssh nas "export PATH=/usr/local/bin:\$PATH; docker update --restart unless-stopped reglement-definitif-api" + +# 3. Déployer le MCP +ssh nas "export PATH=/usr/local/bin:\$PATH; cd /volume1/docker/reglement-definitif/reglement-mcp && docker compose build --no-cache && docker compose up -d" + +# 4. Config hermes-tt (/opt/data/config.yaml du conteneur hermes-agent-tt) +# mcp_servers.reglement-definitif: url http://reglement-mcp:8000/mcp + Bearer token +``` + +--- + +## Verification + +Vérifié DEUX FOIS : preuves Gemini + tests indépendants Hermes-Nyora depuis le réseau n8n (23/08/2026). + +```bash +# Conteneurs healthy +docker ps --filter name=reglement +# reglement-mpc / reglement-definitif-api : Up (healthy) tous les deux + +# API web +curl -I http://127.0.0.1:5099/login # → HTTP 200 + +# MCP health +curl -s http://reglement-mcp:8000/health # → {"status":"healthy",...,"endpoint":"/mcp"} + +# Auth active +curl -o /dev/null -w "%{http_code}" http://reglement-mcp:8000/mcp # sans token → 401 + +# Flux MCP complet (StreamableHTTP = initialize d'abord pour obtenir la session) +curl -D - -X POST http://reglement-mcp:8000/mcp \ + -H "Authorization: Bearer $MCP_SECRET_TOKEN" \ + -H "Content-Type: application/json" \ + -H "Accept: application/json, text/event-stream" \ + -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}' +# → récupérer le header mcp-session-id, puis tools/list AVEC ce header → 8 tools +# → tools/call reglement_list_marches → {"status":"success","total_marches":8,"nb_fournisseurs":4} + +# Découverte côté hermes-tt (preuve Gemini) +docker exec hermes-agent-tt hermes mcp test reglement-definitif +# → Connected (281ms), Tools discovered: 8 +``` + +Dépôt code : `bolbol/reglement-definitif` branche `v0` (commits efbe03b, 80ed7bc). Repo privé — invisible en API anonyme (vérifié : 404 anonyme = normal pour un repo privé). + +--- + +## Pieges specifiques DSM / NAS + +1. **Code cuit sans volume** : l'ancien déploiement n'avait NI volume NI Dockerfile. Toute app historique dans cet état est une bombe : credentials et SQLite perdus au premier recreate. Toujours extraire AVANT (`docker cp`). +2. **StreamableHTTP ≠ SSE simple** : chaque appel après initialize exige le header `mcp-session-id` retourné par initialize. Un curl nu renvoie `-32600 Missing session ID`. +3. **Piège PATH SSH Synology** : `export PATH=/usr/local/bin:$PATH` obligatoire dans chaque commande ssh nas non-interactive, sinon `docker: command not found`. +4. **Repo privé Gitea** : une recherche API anonyme ne voit PAS les repos privés — « not found » ne prouve pas l'inexistence. Vérifier authentifié avant de conclure (erreur commise pendant la vérification, corrigée). +5. **Healthcheck slim** : wget/curl absents de python:3.11-slim → healthcheck en `python -c urllib.request`. +6. **Credentials manquants ≠ crash** : l'app et le MCP démarrent verts sans credentials.json ; seuls les outils Sheets renvoient l'erreur explicite. Ne pas croire un statut healthy = données OK. + +--- + +## References + +- Repo code : `bolbol/reglement-definitif@v0` (Gitea) +- Prompt mission + recadrages : `/opt/data/workspace/prompts/antigravity-reglement-definitif.md`, `GO-reglement-definitif.md`, `RECADRAGE-GO-execution-gemini.md` +- Suivi projet : `/opt/data/workspace/prompts/SUIVI-reglement-definitif.md` +- Code extrait du conteneur d'origine : `/opt/data/workspace/apis/reglement-definitif/` +- Ports registry : à mettre à jour (ligne reglement-mcp 5098) \ No newline at end of file