5.2 KiB
Architecture
Vue d'ensemble
tunnel Cloudflare
│
Traefik (https)
│
┌───────────────────┴───────────────────┐
│ priorité 20 : /api priorité 10 : / │
▼ ▼
limier-api limier-web
(FastAPI) (nginx + React compilé)
│
├──── PostgreSQL ──── recherches, résultats, comptes, quotas
│
└──── Redis ────┬──── file de travaux (arq)
└──── progression en direct (SSE)
▲
│
limier-worker
│
┌─────────────────┼─────────────────┐
▼ ▼ ▼
moteur pseudonyme moteur e-mail moteur domaine
(Maigret) │
│ │
limier-holehe RDAP, DNS, CT
(optionnel, GPL)
Pourquoi cette découpe
API et worker dans la même image, deux commandes. Même code, même base de sites, aucune divergence possible à la mise à jour. Deux images finiraient par se désynchroniser à la première publication oubliée.
Interface servie par un conteneur nginx distinct. L'API ne sert aucun
fichier statique. Reconstruire l'interface ne touche pas au backend, et
inversement. Traefik arbitre par priorité de routeur : /api (priorité 20)
l'emporte sur / (priorité 10).
Une recherche est un travail, pas une requête. Un balayage de 500 sites prend 30 à 120 secondes. Le faire dans une requête HTTP produirait des délais dépassés côté proxy et bloquerait un worker uvicorn. L'API accepte, met en file et rend un identifiant ; le worker exécute.
arq plutôt que Celery. Maigret est asynchrone de bout en bout, arq aussi. Celery aurait imposé un pont entre son modèle de travailleurs synchrones et une boucle asyncio, sans aucun gain.
SSE plutôt que WebSocket. Le flux est unidirectionnel, SSE se reconnecte seul, passe partout en HTTP/1.1 et ne demande aucune configuration Traefik particulière.
Le contrat des moteurs
Tout part de engines/base.py. Un moteur reçoit une EngineRequest et émet
un flux d'évènements au lieu de rendre un résultat final :
| Évènement | Rôle |
|---|---|
ProgressEvent |
avancement, alimente la barre de progression |
FindingEvent |
un résultat, déjà scoré |
NoticeEvent |
information non bloquante (module désactivé, source KO) |
C'est ce qui permet l'affichage en direct, et c'est aussi ce qui rend
l'ajout d'un moteur trivial : créer un module dans engines/<nom>/engine.py,
l'enregistrer dans engines/registry.py. Rien d'autre ne change.
Les résultats sont écrits pendant la recherche, pas à la fin : une recherche interrompue à 80 % conserve ses 80 %.
Chemin d'un évènement
moteur ──► worker ──► Redis ──┬──► liste (historique rejouable)
└──► canal (réveil immédiat)
│
▼
API /flux (SSE) ──► navigateur
La liste permet à un navigateur qui se connecte en retard, ou qui se reconnecte,
de ne rien perdre. EventSource renvoie Last-Event-ID, le backend ne rejoue
que le manquant.
Où chercher quoi
| Question | Fichier |
|---|---|
| Une variable d'environnement | backend/src/limier/config.py |
| Le schéma de la base | backend/src/limier/db/models.py |
| Comment un résultat est jugé fiable | backend/src/limier/engines/scoring.py |
| L'appel à Maigret | engines/username/engine.py |
| Les variantes « prénom nom » | engines/username/permutations.py |
| Ce que fait le worker | backend/src/limier/jobs/tasks.py |
| Les règles d'acceptation d'une recherche | api/routes/searches.py |
| L'authentification Authentik | backend/src/limier/auth/oidc.py |
| Les quotas et la limitation de débit | backend/src/limier/quotas/service.py |
| La conservation et l'effacement | backend/src/limier/privacy/retention.py |
| La sortie réseau et les proxies | backend/src/limier/net/proxy_pool.py |