Synchroniser une doc git vers un index RAG — webhook + wrapper
Référence de structure pour un pipeline qui maintient un index RAG à jour à partir d'un dépôt git de documentation. Le principe : le RAG indexe un clone git local de la doc, hébergé sur le serveur applicatif. Sans mécanisme de rafraîchissement, ce clone devient obsolète dès qu'un commit est poussé. Deux déclencheurs complémentaires alimentent un pipeline unique de checkout + ré-indexation.
Vue d'ensemble
| Déclencheur | Origine | Ref ciblé |
|---|---|---|
| Webhook | Nouveau tag poussé sur la branche de release | Le tag poussé |
| Interface d'administration | Action manuelle d'un admin | Branche + ref au choix |
Les deux passent par le mĂŞme pipeline :
Déclencheur (webhook / admin)
→ Lanceur (process CLI détaché)
→ Commande CLI : checkout --ref=<ref>
→ git fetch origin --tags --prune
→ git checkout <ref>
→ git pull --ff-only (seulement si branche ; pas si tag/SHA détaché)
→ Service d'ingestion : drop + rebuild complet de l'index
→ Persistance d'état : running=false, last_at=now, current_ref=<ref>
Déclencheur 1 — Webhook
Endpoint
Un endpoint HTTP POST dédié, par exemple POST /api/webhook/<fournisseur>/docs.
Sécurité
- Signature HMAC-SHA256 du corps de la requête, transmise dans un en-tête dédié (les fournisseurs git le nomment typiquement
X-Hub-Signature-256). - Le secret est stocké dans une variable d'environnement côté serveur (jamais en dur dans le code, jamais dans le dépôt).
- Secret absent OU signature invalide →
401.
Filtre d'événements
- Ne traiter que l'événement de création de tag. Les push de branche, PRs et autres événements →
200« ignoré ». - Ce filtre garantit qu'un simple push de développement ne relance pas une ré-indexation complète.
Anti-chevauchement
Si un checkout est déjà en cours (verrou d'état à true), le webhook répond 202 sans relancer.
Configuration côté dépôt
| Champ | Valeur |
|---|---|
| URL | l'endpoint webhook exposé par l'application |
| Content-Type | application/json |
| Événements | création de branche/tag uniquement |
| Secret | mĂŞme valeur que la variable d'environnement serveur |
Le secret webhook doit être géré comme un secret d'infrastructure (coffre de configuration / gestionnaire de secrets), puis injecté dans l'environnement du serveur — jamais commité.
Déclencheur 2 — Interface d'administration
Une section d'administration dédiée offre un déclenchement manuel avec choix libre de la ref.
Flux d'utilisation
- L'admin choisit une branche (liste issue des branches remote-tracking locales).
- (Optionnel) L'admin déclenche un rafraîchissement : un
git fetchdistant, puis rechargement de la liste des refs. - Le sélecteur de ref se peuple via AJAX à partir des refs locales (pas de fetch réseau) :
- tags fusionnés dans la branche (le plus récent mis en avant) ;
- N derniers commits (
SHA court — date — message).
- L'admin sélectionne la ref et soumet.
- Checkout + ré-indexation se lancent en arrière-plan (process CLI détaché).
- La section affiche un verrou « en cours » avec auto-refresh.
Endpoints AJAX
| Méthode | Rôle |
|---|---|
GET refs?branch=<b> |
Liste des refs locales (tags + commits) — aucun accès réseau |
POST fetch |
Déclenche un git fetch distant |
Ségrégation d'identité côté serveur
C'est le cœur du modèle de sécurité. Deux utilisateurs système distincts :
- L'utilisateur du serveur web (celui qui exécute PHP-FPM ou équivalent) n'a aucune clé SSH ni identité externe. Il ne peut pas parler à l'hébergeur du dépôt.
- Un utilisateur applicatif dédié détient la clé SSH de déploiement et donc l'accès au dépôt distant.
Le serveur web n'agit jamais lui-même sur le dépôt : il délègue à l'utilisateur applicatif via des règles sudo étroitement cadrées. Aucune commande arbitraire n'est autorisée.
git safe.directory
Le clone appartient à l'utilisateur applicatif ; le serveur web ne fait que le lire. Sans configuration, git renvoie fatal: detected dubious ownership. Il faut déclarer le chemin du clone comme safe.directory (portée système) :
git config --system --add safe.directory <chemin-du-clone>
Wrapper de commande
Les noms de commandes de nombreux frameworks (Symfony, etc.) contiennent des :, caractère réservé dans la syntaxe sudoers. On contourne cela avec un wrapper shell minimal, à chemin fixe, qui encapsule la commande réelle :
#!/bin/bash exec <interpréteur> <chemin-console> <commande:checkout> "$@"
Le wrapper ne fait que relayer ses arguments : aucune logique, aucune interpolation.
Règles sudoers étroitement cadrées
Deux règles seulement, correspondant exactement aux deux actions autorisées (le fetch du bouton de rafraîchissement, et le checkout en arrière-plan) :
<user-web> ALL=(<user-app>) NOPASSWD: /usr/bin/git -C <chemin-du-clone> fetch origin --tags --prune <user-web> ALL=(<user-app>) NOPASSWD: <chemin-du-wrapper> *
Principe : on énumère précisément les commandes permises, chemins absolus compris. Rien d'autre n'est délégable.
État persisté
L'application conserve un petit état applicatif pour piloter le verrou et l'affichage :
| Clé | Type | Rôle |
|---|---|---|
checkout_running |
bool | verrou anti-chevauchement |
last_checkout_at |
datetime ISO | horodatage du dernier checkout |
current_ref |
string | ref actuellement indexée |
Périmètre d'indexation — mode default-allow
Le module de lecture du corpus (et le RAG) opèrent en default-allow : tout fichier de doc est indexé sauf exclusions permanentes codées dans le composant. Il n'y a pas d'allowlist à maintenir dans la configuration.
Exemples de segments exclus en dur (à adapter au dépôt) :
| Exclusion | Raison |
|---|---|
| dossier de données d'entrée | données personnelles / sources brutes |
| dossier de scripts | outillage, pas de la documentation |
dossier legacy/ |
archivé, hors corpus courant |
| brouillons non publiés | contenu non validé |
| dotfiles / dotdirs | métadonnées de dépôt (.git, etc.) |
Pour exclure un dossier : ajouter son segment Ă la liste des segments toujours exclus. Pour rendre un dossier visible : il l'est d'office, sans intervention.
Opérations de référence
Provisionner le clone initial
git clone <url-ssh-du-depot> <chemin-du-clone> git -C <chemin-du-clone> checkout <branche-de-release>
Le webhook prend ensuite le relais pour les mises Ă jour automatiques.
Vérifier l'état courant
git -C <chemin-du-clone> log --oneline -3 git -C <chemin-du-clone> describe --tags
Forcer un refresh manuel
sudo -u <user-app> <chemin-du-wrapper> --ref=<sha-ou-tag>
Bonne pratique : la commande de checkout n'accepte que des SHA ou des tags (validés par une regex du type
^[0-9a-f]{7,40}$). Un nom de branche est rejeté — on passe un SHA explicite ou un tag, afin d'indexer une ref immuable.
Débloquer un verrou resté à true
Si le process a échoué en laissant checkout_running à true, remettre la clé d'état à false dans le stockage applicatif (base ou fichier de config selon l'implémentation).
Consulter les logs
tail -f <chemin-du-log-de-checkout>
Workflow de release recommandé
1. Pousser les commits sur la branche de développement 2. Merger vers la branche de release 3. Tagger la release, puis pousser le tag → Le webhook déclenche automatiquement le checkout du tag + la ré-indexation → Le RAG reflète la doc de release en quelques minutes
Garde-fous Ă retenir
- Le serveur web n'a jamais d'accès direct au dépôt distant ni au disque du clone en écriture.
- Toute délégation passe par sudoers énuméré commande par commande, chemins absolus.
- Le webhook est authentifié par HMAC, filtré sur le seul événement de création de tag.
- On indexe des refs immuables (SHA/tag), pas des branches mouvantes.
- Le secret webhook et la clé de déploiement vivent dans un coffre / l'environnement, jamais dans le dépôt.
Note de rédaction : cette référence est adaptée d'une spec opérationnelle interne. Les valeurs concrètes (domaines, chemins serveur, ports, noms d'utilisateurs système, hébergeur, noms de bundles/commandes réels) ont été volontairement généralisées. Remplacer les
<placeholders>par les valeurs de votre propre infrastructure.