Document technique qui explique comment et pourquoi l'application agroworoba (pile autonome : Caddy local + FastAPI + Postgres + Odoo dans un seul conteneur) a été transformée en capbe-agro 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-agro.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 agro) |
| Odoo 19 multi-tenant | capbe-odoo |
ERP (un tenant = une base, dbfilter ^%d$) |
| Messagerie Stalwart | capbe-mail |
SMTP/IMAP/JMAP + webmail |
Le projet agro y est découpé en 3 morceaux :
agroworoba (1 conteneur autonome) capbe-agro (architecture CAPBE)
┌──────────────────────────┐ ┌──────────────────────────────────────┐
│ Caddy local (TLS) │ → │ capbe-caddy : https://agro.app.ci.capbe.ovh
│ Front Flutter │ → │ capbe-agro : nginx :80 (/var/www/html)
│ FastAPI + base Postgres │ → │ capbe-pgis :8001 → /srv/agro/api + base agro_api
│ Odoo + base │ → │ capbe-odoo :8069 → base agro (tenant)
└──────────────────────────┘ └──────────────────────────────────────┘
(INTACT — jamais touché)
2. Les 3 phases de la migration (détail réel)
Phase 1 — Frontend statique → capbe-agro
- Création du conteneur
capbe-agro(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— piège constaté en réel pour agroworoba : le front Flutter vivait dans la racine par défaut de Caddy (/usr/share/caddy), le script ne cherchait que/var/www/html→ front 403 avant correction. - 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 agroworoba : l'API tournait enuvicorn app.main:appdepuis/app; sans cette détection, l'étape API était silencieusement ignorée. - 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 — agro a pris le 8001
(meca occupant le 8000). Routage Caddy même-origine :
agro.app.ci.capbe.ovh/api/* → pgis:8001. .env: recopié vers/srv/agro/api/.env(chmod 600) avecDATABASE_URL/DB_PASSWORDréécrits vers pgis (rôleapp). Si aucun.envhérité : généré avecDATABASE_URL(+DATABASE_URL_SYNCpour SQLAlchemy async) etREDIS_URL.alembic.ini(tous) :sqlalchemy.urlrepointé vers capbe-pgis.- Service systemd
agro-apisur pgis :uvicorn app.main:app --port 8001. - Redis + Celery déployés 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 — repli convention<legacy>_odoo. Piège agroworoba constaté en réel : la base « Odoo » de l'héritage ne contenait en fait que les tablesorm_signaling_*de la FastAPI (jamais initialisée comme ERP) → le script détecte l'absence deir_module_moduleet initialise un tenant VIERGE (odoo -i base) pour une URL fonctionnelle (aucune donnée ERP à attendre). pg_dump --no-owner→ base<id>sur pgis (rôleodoo), servie par capbe-odoo avecdbfilter ^%d$→https://agro.ci.capbe.ovh.- Filestore : copié si un chemin standard existe, sinon avertissement.
3. Pièges rencontrés en réel (corrections incluses dans le script)
- Racine web Caddy (
/usr/share/caddy) : le front agroworoba n'était pas dans/var/www/html→ 403. Le script détecte les deux. - Layout API
/app: l'API agroworoba tournait depuis/app(pas/opt/<legacy>/api) → étape ignorée en silence avant la correction. - Tenant Odoo non initialisé : base source sans
ir_module_module→ tenant vierge créé (odoo -i base). - Propriété PostgreSQL (PG15+) :
REASSIGN OWNEDéchoue →fix_owner+sweep_owner(DO blocks) réattribuent les objets utilisateur. Le sweep est indispensable pour les tablesorm_signaling_*(invisibles au rôle odoo sinon →DuplicateTable, registre KO, login 500 — constaté sur meca). /tmp/et non/root/: le restore tourne viasu postgres(ne peut pas lire/root/, 700).- Héritage jamais touché : agroworoba 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://agro.app.ci.capbe.ovh |
✅ 200 (front Flutter) |
https://agro.app.ci.capbe.ovh/api/v1/auth/me |
✅ 401 sans token (relay → pgis:8001 prouvé) |
https://agro.ci.capbe.ovh/web/login?db=agro |
✅ 200 (tenant Odoo) |
agro-api sur pgis |
✅ actif, uvicorn :8001 |
Base agro_api |
✅ présente |
Tenant agro |
✅ présent (676 modules Odoo) |
Héritage agroworoba |
⏳ RUNNING (sort différé) |
5. Rejouer la migration
sudo ./deploy/migrate-agro.sh # wrapper : playbook 12-migrate-projects.sh agroworoba=agro
sudo ./playbook/09-caddy-config.sh # (re)déploie le Caddyfile si besoin
sudo ./ops/smoke-agro.sh # validation
Idempotent : les composants déjà migrés sont détectés et ignorés (warn) ; les manquants sont complétés.