Vue d'ensemble des services qui composent une instance Ekylibre, de leurs interactions, et des choix structurants.
Le schéma ci-dessous est rendu en PixiJS : survolez un service pour voir son rôle, observez les flux de requêtes animés. Le contenu est purement décoratif — toute l'information est aussi présente dans les sections texte ci-dessous.
┌─────────────┐
│ Navigateur │
└──────┬──────┘
│ HTTPS
▼
┌─────────────┐
│ Caddy │ ← TLS, Let's Encrypt auto, on-demand
│ (reverse- │ (prod) / cert local (dev)
│ proxy) │
└──┬───────┬──┘
│ │
┌──────────▼─┐ ┌─▼──────────┐
│ Rails │ │ duke-api │ (optionnel)
│ (Puma) │ │ chatbot │
└──┬─────┬───┘ └─────┬──────┘
│ │ │
│ │ ▼
│ │ ┌──────────────┐
│ │ │ postgres-duke│
│ │ └──────────────┘
│ │
┌───▼─┐ ┌─▼────┐
│ DB │ │ Redis│
│ + │ │ │
│POST-│ └──┬───┘
│ GIS │ │
└──┬──┘ │
│ │ enqueue / fetch
│ │
│ ┌────▼─────┐
└──┤ Sidekiq │ ← jobs asynchrones
│ (worker) │ (imports, exports, intégrations)
└──────────┘
Rôle : terminaison TLS, routage des sous-domaines vers Rails ou Duke.
on_demand_tls. Un endpoint /health de Rails filtre les hostnames inconnus pour éviter le rate-limit ACME.docker/dev/trust-ca.sh l'installe dans le store système et NSS pour Firefox/Chrome. Domaine *.ekylibre.localhost (résolu nativement RFC 6761).Stack : Ruby 2.6.6 + Rails 6.x + Puma. Port interne 3000.
Multi-tenancy : chaque "ferme" est un tenant isolé via la gem apartment (schémas PostgreSQL séparés). Routage par sous-domaine : acme.example.com → Apartment::Tenant.switch!('acme').
Plugins : déclarés dans Gemfile.local (dev) ou docker/prod/Gemfile.prod (prod). 13+ plugins publics couvrant les intégrations bancaires, le suivi GPS, la météo, la traçabilité, etc.
Rôle : déporter les opérations longues hors du cycle requête HTTP.
Exemples : imports de catalogues, exports de bilans comptables, synchronisation Saisigo / Baqio, calculs d'interventions sur grandes parcelles.
Lit la file dans Redis, écrit les résultats dans PostgreSQL. Le même container peut donc être app ou sidekiq — c'est la commande de démarrage qui change.
Version : 13 + PostGIS 2.5. Port interne 5432 (exposé sur 5431 en dev pour psql direct depuis l'hôte ; non exposé en prod).
Volumétrie typique : - ~300 modèles ActiveRecord - ~3 GB de lexicon (nomenclatures) par tenant - ~10-100 MB de données métier par exploitation moyenne
Schémas séparés :
- public : tables système, lexicon partagé
- <tenant_name> : tables métier propres à chaque ferme
Version : 7-alpine. Port interne 6379, non exposé.
Usages : - File Sidekiq (jobs en attente, en cours, échoués) - Cache Rails (fragments de vue, queries) - Sessions utilisateur (selon config)
Optimisation : sur l'hôte,
sysctl vm.overcommit_memory=1évite l'échec deBGSAVEsous pression mémoire.
Stack : API Python (FastAPI) + PostgreSQL dédié + LLM (Anthropic / Mistral / Ollama local).
Architecture :
WebSocket ─► duke-api ─► PostgreSQL duke (historique)
│
├─► PostgreSQL Ekylibre (lecture seule, rôle duke_reader)
│
└─► LLM (Claude / Mistral / Ollama local)
duke_reader avec GRANT SELECT uniquement sur public, postgis, lexicon
HASH_SECRET (toutes les requêtes utilisateur sont hashées avant log)--profile duke)Browser → Caddy (TLS) → Rails (Puma)
→ Devise (auth)
→ Apartment.switch(tenant)
→ Render dashboard
Browser → Rails (formulaire)
→ PostgreSQL (insert intervention + working_periods + cost_amount)
→ Sidekiq.perform_async (recalcul agrégats parcelle)
→ Redis (job enqueue)
Sidekiq → Redis (job dequeue)
→ PostgreSQL (recalculs)
→ ActionCable / WebSocket → Browser (notification temps réel)
Browser → Rails (upload fichier)
→ ActiveStorage (S3 ou disque)
→ Sidekiq.perform_async(CatalogImportJob)
→ Reply 202 Accepted
Sidekiq → Lecture fichier
→ Parsing
→ Insert batch par batch
→ PostgreSQL
→ Update progress (broadcast WebSocket)
Décision historique. La migration vers Ruby 3.x + Rails 7 est dans la roadmap v6 — voir docs/planning/v6-brainstorm.md dans le repo Ekylibre.
Les données géospatiales (parcelles, observations) sont au cœur des requêtes métier (« quelles interventions sur cette zone ? »). Une couche séparée imposerait des jointures cross-store coûteuses. PostGIS gère 99 % des cas avec ST_Intersects / ST_Area natifs.
search_path PostgreSQL garantit qu'un tenant ne voit pas les autrescertbot renew)on_demand_tls : provisionne à la volée pour les sous-domaines tenant, sans recharger la configGemfile.local (dev) et docker/prod/Gemfile.prod (prod)Cette documentation est open-source. Aidez-nous à l'améliorer en ouvrant une issue ou une pull request.