Document technique qui explique comment et pourquoi l'application agrimeca (pile autonome : Caddy local + FastAPI + Postgres + Odoo dans un seul conteneur) a été transformée en capbe-meca dans l'architecture CAPBE.
Le script source de cette migration est
playbook/12-migrate-projects.sh. Ce kit en contient un wrapper spécialisé :deploy/migrate-meca.sh.
1. L'architecture cible (CAPBE)
CAPBE mutualise les backends dans des conteneurs dédiés, connectés sur le
réseau privé capbe-br0 (10.10.10.0/24, NAT sortant). Un seul point d'entrée
public : capbe-caddy (TLS auto, On-Demand).
| Backend partagé | Conteneur | Rôle |
|---|---|---|
| PostgreSQL 18 + PostGIS | capbe-pgis |
base de données + hébergement des API migrées (uvicorn) |
| Oracle AI DB Free | capbe-oracle |
stack #2 (non utilisé par meca) |
| Odoo 19 multi-tenant | capbe-odoo |
ERP (un tenant = une base, dbfilter ^%d$) |
| Messagerie Stalwart | capbe-mail |
SMTP/IMAP/JMAP + webmail |
Le projet meca y est découpé en 3 morceaux :
agrimeca (1 conteneur autonome) capbe-meca (architecture CAPBE)
┌──────────────────────────┐ ┌──────────────────────────────────────┐
│ Caddy local (TLS) │ → │ capbe-caddy : https://meca.app.ci.capbe.ovh
│ Front Flutter │ → │ capbe-meca : nginx :80 (/var/www/html)
│ FastAPI + base Postgres │ → │ capbe-pgis :8000 → /srv/meca/api + base meca_api
│ Odoo + base │ → │ capbe-odoo :8069 → base meca (tenant)
└──────────────────────────┘ └──────────────────────────────────────┘
(INTACT — jamais touché)
2. Les 3 phases de la migration (détail réel)
Phase 1 — Frontend statique → capbe-meca
- Création du conteneur
capbe-meca(profilfront, réseaucapbe-br0). - Provisionnement nginx :80 (
root /var/www/html,try_files ... /index.html). - Détection de la racine web de l'héritage :
/var/www/html(nginx) ou/usr/share/caddy(racine par défaut de Caddy — piège rencontré avec agroworoba : le script ne cherchait que/var/www/html→ front 403). - Copie du build via
tar | incus exec tar(ne dépend pas de scp/rsync).
Phase 2 — API FastAPI + base → capbe-pgis
- Détection du code API :
/opt/<legacy>/api(layout historique) ou/app(layout supervisor/venv — piège : étape silencieusement ignorée sinon). - Base :
pg_dump --no-owner --no-privilegesde la base source (nom lu dansalembic.ini→sqlalchemy.url), restore sur pgis en base<id>_api. - Port : un service uvicorn par port sur pgis — meca a pris le 8000
(1re API migrée), agro le 8001. Routage Caddy même-origine :
meca.app.ci.capbe.ovh/api/* → pgis:8000. .env: recopié vers/srv/meca/api/.env(chmod 600) avecDATABASE_URL/DB_PASSWORDréécrits vers pgis (rôleapp, mot de passeAPP_DB_PASS). Les clés de paiement (Wave/Orange/MTN/Moov) sont conservées. Si aucun.envhérité : généré avec les connexions capbe-pgis.alembic.ini(tous, racine + sous-dossiers) :sqlalchemy.urlrepointé vers capbe-pgis (sinon une copie orpheline garde une URL morte).- Service systemd
meca-apisur pgis :uvicorn app.main:app --port 8000. - Redis + Celery déployés automatiquement si l'app déclare un
celery_app.
Phase 3 — Odoo → tenant de capbe-odoo
- Base Odoo source détectée via
db_namede la config (/etc/odoo/odoo.conf,/etc/odoo.conf, …) — repli convention<legacy>_odoo. pg_dump --no-owner→ base<id>sur pgis (rôleodoo), servie par capbe-odoo avecdbfilter ^%d$→https://meca.ci.capbe.ovh.- Filestore (pièces jointes) : copié depuis l'héritage
(
/var/lib/odoo/filestore,/opt/odoo*/.local/share/Odoo/filestore, …) verscapbe-odoo:/var/lib/odoo/filestore/<tenant>. - Tenant vierge (
odoo -i base) si la base source n'avait jamais été initialisée (pas deir_module_module) — aucune donnée ERP à attendre.
3. Pièges rencontrés en réel (corrections incluses dans le script)
- Propriété PostgreSQL (PG15+) : les objets restaurés avec
--no-ownerappartiennent àpostgres.REASSIGN OWNEDéchoue sur PG15+ (postgres possède des objets système). →fix_owner+sweep_owner(DO blocks) réattribuent schémas utilisateur, tables, vues, séquences (hors séquences de colonne liées), fonctions. - Tables
orm_signaling_*(Odoo 19) : restaurées possédées par postgres, elles deviennent invisibles au rôleodoodansinformation_schema→ Odoo tente de les recréer (DuplicateTable) → registre KO (KeyError: 'meca', login 500). Le sweep de propriété est indispensable (constaté en réel sur meca). /tmp/et non/root/: le restore tourne viasu postgres, qui ne peut pas lire/root/(700) → « Permission denied » sinon.- Dump sensible nettoyé : le
.sqlintermédiaire est supprimé dès son push vers pgis (même si le restore échoue ensuite). - Rewrite API
/api/* → /api/v2/*: la convention d'URL de l'API meca est/api/v2/<ressource>mais le front appelle/api/<ressource>. Caddy réécrit uniquement les ressources listées dans la regex du blocmeca.app.ci.capbe.ovh(health, dashboard, clients, parcelles, engins, conducteurs, interventions, factures, prestations, paiements, geographie, echeancier, sites, transferts, photos, notifications, global-search, stock). ⚠️ Tout nouvel endpoint doit y être ajouté (voir EVOLUTIONS.md). - Héritage jamais touché : agrimeca n'est ni arrêté ni supprimé — son sort est décidé manuellement après validation (playbook/POST-DEPLOY.md).
4. État réel constaté (validation du 03/08/2026)
| Vérification | Résultat |
|---|---|
https://meca.app.ci.capbe.ovh |
✅ 200 (front Flutter) |
https://meca.app.ci.capbe.ovh/api/health |
✅ 200 (même-origine → pgis:8000) |
https://meca.ci.capbe.ovh/web/login?db=meca |
✅ 200 (tenant Odoo) |
meca-api sur pgis |
✅ actif, uvicorn :8000 |
Base meca_api |
✅ 247 tables |
Tenant meca |
✅ 676 modules Odoo (ir_module_module) |
Héritage agrimeca |
⏳ RUNNING (sort différé) |
5. Rejouer la migration
sudo ./deploy/migrate-meca.sh # wrapper : playbook 12-migrate-projects.sh agrimeca=meca
sudo ./playbook/09-caddy-config.sh # (re)déploie le Caddyfile si besoin
sudo ./ops/smoke-meca.sh # validation
Idempotent : les composants déjà migrés sont détectés et ignorés (warn) ; les manquants sont complétés.