Chapitres

DOC-05 / Référence technique · Chapitre 09

Déploiement & infrastructure

Décrit le pipeline de mise en ligne Synedre OS : asymétrie entre ./deploy (IA, preprod) et ./ship (Alex, production), le dispatcher YAML, le pattern build-host sans build VPS, et les règles associées (secrets, commit-avant-deploy, drift, smoke).

Asymétrie ./ship vs ./deploy

Deux points d'entrée existent à la racine du dépôt. Ils ne font pas la même chose et n'ont pas le même propriétaire.

./deploy ./ship
Cible Pré-production (ou runtime vaisseau-mère / PROD pour les sites fondateurs sans preprod) Production
Propriétaire IA / worker / cascade, de façon systématique Alex uniquement, geste manuel
Git Auto-commit du dirty + reste sur la branche preprod git checkout maingit pullgit merge preprod -X theirsgit push origin main, puis retour preprod (trap EXIT)
Garde-fou dirty Auto-commit silencieux, sauf si sur main (refus dur) Bloquant : refus si working tree sale, sauf --allow-dirty
Vérification de dérive de schéma Une seule cible preprod câblée ; tout autre tenant → skip explicite. Bypass --skip-drift disponible. Vérification du schéma du hub (bloquante) + DB prod tenant si cible connue. Aucun équivalent --skip-drift : la dérive hub est strictement bloquante.
Application automatique de la dérive Le moteur de correction de dérive génère et applique en une seule transaction les DDL idempotentes manquantes (CREATE TABLE IF NOT EXISTS, ALTER TABLE ADD COLUMN IF NOT EXISTS). Actif par défaut. Désactivable ponctuellement. Appliqué avant tout build ou rechargement. — (dérive appliquée manuellement avant le ship)
Smoke post-déploiement Vérification HTTP des tenants, enchaînée avec un smoke visuel non bloquant. Désactivable. Vérification de l'environnement de processus (anti-faux-positif) puis smoke HTTP + contrôles de contenu JSON.
Migrations Affiche les fichiers SQL en attente, non bloquant, non auto-appliqué — aucun runner de base de données branché.
Clôture Propose la fermeture des chantiers de type test (post-ship).

Doctrine :

  • ./ship <tenant> = Production, jamais en autonome, exclusif Alex. L'IA ne déclenche jamais ./ship.
  • ./deploy <tenant> = toujours l'IA, systématiquement et sans demander — y compris sur le vaisseau-mère où ./deploy équivaut à un rebuild live du runtime (pas de preprod dédiée).
        ┌─────────────┐   ./deploy <tenant>   ┌──────────────┐
  IA →  │  branche     │ ──────────────────→   │  preprod /    │
        │  preprod     │   (auto, N fois)        │  runtime live │
        └─────────────┘                         └──────────────┘
                │
                │  Alex review preprod, valide
                ▼
        ┌─────────────┐   ./ship <tenant>      ┌──────────────┐
 Alex → │ merge preprod│ ──────────────────→   │  PRODUCTION   │
        │  → main      │   (manuel uniquement)   │              │
        └─────────────┘                         └──────────────┘

Exception site public sans preprod : ./deploy est refusé pour ce tenant — le site public n'a pas de preprod. On le modifie uniquement via ./ship.

Exception tenants fondateurs sans preprod : pour certains sites fondateurs, ./deploy pointe directement la production. La frontière deploy = preprod ne tient pas pour ces cas. La seule opération qui reste manuelle est ./ship (fusion preprod→main + cérémonie complète).

Détail du pipeline ./ship (étapes numérotées)

./ship <tenant> enchaîne, dans l'ordre :

  1. Parsing des flags : --allow-dirty (bypass garde-fou dirty), --no-close (skip post-ship), le reste est transmis au dispatcher.
  2. Garde-fou dirty bloquant : vérification de la propreté du dépôt ; exit si sale, sauf --allow-dirty.
  3. Trap EXIT cleanup : retour sur la branche preprod garanti même en cas d'erreur ; tout échec de checkout est signalé bruyamment (pour éviter de rester coincé sur main).
  4. Nettoyage des fichiers root-owned : suppression des artefacts laissés par d'anciens déploiements, qui sinon bloquent l'écriture.
  5. Vérification de dérive du hub (bloquante) : aucun bypass. Suivie de la vérification de dérive DB prod du tenant si la cible est connue.
  6. Fusion preprod → main : checkout main, pull, merge preprod -X theirs, push origin main.
  7. Migrations : affichage des fichiers SQL en attente (voir section suivante). Non bloquant, non auto-appliqué.
  8. Audit des agents : vérification de l'état des agents actifs (non bloquant).
  9. Déploiement prod : appel du dispatcher de déploiement avec les paramètres du tenant.
  10. Smoke prod : vérification de l'environnement de processus avant le smoke HTTP + contrôles de contenu JSON.
  11. Post-ship close : propose la fermeture des chantiers de type test (ignoré via --no-close).

Asymétrie --skip-drift : ./deploy accepte --skip-drift pour bypasser la vérification de dérive en preprod (cas de hotfix urgent). ./ship n'a aucun équivalent — la dérive du hub y est strictement bloquante. On ne livre jamais en production sur un schéma divergent.

Migrations de base de données lors du ship

Le ship affiche les fichiers SQL en attente mais ne les applique jamais : aucun runner de base de données n'est branché. Le bloc est purement informatif et non bloquant — il imprime les commandes à exécuter manuellement.

Un mapping tenant → scope de migrations évite de mélanger les migrations du vaisseau-mère et celles des tenants :

Tenant Dossier de migrations ciblé
Vaisseau-mère / runtime OS Scope mothership
Un tenant e-commerce (ex. tenant A) Scope propre au tenant A
Un tenant vape (ex. tenant B) Scope propre au tenant B
Sites fondateurs (client, alexandrecarette, codemyshop) Scope propre à chaque site
all Cas spécial : agrège toutes les cibles
autre Fallback : scope = nom du tenant

Cas spécial all : agrège l'ensemble des fichiers SQL de tous les scopes, en excluant les sous-dossiers _applied/ et applied/ (migrations déjà passées). Les autres scopes listent simplement les fichiers SQL du dossier correspondant.

Routage de ./deploy et ./ship par tenant

Chaque tenant est routé vers un script dédié ou vers le dispatcher générique de déploiement.

Tenant (argument) ./deploy ./ship
Vaisseau-mère (défaut) Vérification de santé du verrou de concurrence, puis script de déploiement dédié au runtime OS (--prod --all) Idem script dédié runtime OS (--prod --all)
Site public sans preprod Refusé — pas de preprod Dispatcher générique (--all)
Site fondateur synedre.com Dispatcher générique → PROD direct Dispatcher générique ⚠️ vérification d'environnement de processus peut bloquer si le tenant n'est pas déclaré dans le vérificateur
Site fondateur CodeMyShop Dispatcher générique → PROD direct (plus d'ansible/preprod) ; smoke activé Dispatcher générique
Demo CodeMyShop Dispatcher générique → staging démo
Tenant e-commerce A (preprod) Dispatcher générique (--target=preprod --all) ; smoke désactivé (preprod derrière basic auth) Dispatcher générique sans --target → prod
Tenant vape B Dispatcher générique ; smoke activé ⚠️ Tombe dans le cas générique fallback — probablement en erreur si aucun VPS prod n'est défini
Site fondateur client Dispatcher générique → PROD ; smoke activé Dispatcher générique
all 5 jobs en parallèle en arrière-plan : vaisseau-mère, CodeMyShop (PROD), tenant e-commerce A (preprod), tenant vape B, client. Smoke post-jobs sur l'ensemble. ⚠️ Le smoke all teste la démo CodeMyShop mais le job déploie la prod vitrine — la vitrine prod n'est pas vérifiée dans ce chemin. 3 jobs en parallèle : runtime OS, site public sans preprod, CodeMyShop. Smoke sur les trois.
--list / -l Affiche l'inventaire des VPS clients (voir section dédiée)
autre (inconnu) Fallback vers le script d'ansible legacy (--preprod) Fallback vers le script d'ansible legacy (--prod)

Convergence vers le dispatcher unique : depuis la standardisation du pattern de déploiement et la purge des scripts legacy, tous les tenants distants sauf le vaisseau-mère passent par le dispatcher générique, aussi bien pour ./deploy que pour ./ship. Le vaisseau-mère conserve son script dédié car le self-déploiement du runtime est architecturalement distinct : build docker local, sans transfert réseau vers un VPS externe.

⚠️ Angle mort smoke all : lors d'un ./deploy all, le déploiement cible la vitrine CodeMyShop prod, mais le smoke suivant vérifie la démo staging. La vitrine de production n'est pas contrôlée dans ce chemin — point à corriger dans la boucle de smoke.

Le dispatcher piloté par YAML

Le pipeline de déploiement standard — utilisé pour tous les tenants distants, à l'exception du déploiement du vaisseau-mère lui-même — est piloté par un fichier de configuration YAML propre à chaque tenant. Un script de déploiement principal orchestre l'ensemble de la séquence en lisant ce fichier, en le validant, puis en enchaînant les étapes dans un ordre déterministe.

Résolution du fichier de configuration

À l'invocation, le dispatcher localise le fichier YAML à appliquer selon deux règles :

  • Lorsque la cible désigne le vaisseau-mère, le dispatcher tente de charger un fichier de configuration dédié. Ce fichier est documenté dans le schéma officiel, mais n'existe pas sur disque : le vaisseau-mère suit un pipeline de self-déploiement distinct, activé en amont. Le branchement correspondant dans le dispatcher est donc inerte en pratique.
  • Pour tout autre tenant, le dispatcher charge le fichier deploy.yaml situé dans le répertoire du tenant. Si une cible nommée est précisée (option --target=<X>), le fichier deploy.<X>.yaml est utilisé à la place.

Note : le nom synedre (sans suffixe) identifie le tenant fondateur du site synedre.com ; il suit le chemin tenant standard et n'active pas le branchement vaisseau-mère.

Étapes du dispatcher

Le dispatcher exécute les étapes suivantes dans l'ordre :

  1. Analyse des arguments : l'option --target=<X> (le signe = est obligatoire), le flag --all (obligatoire) et le flag optionnel --clean sont extraits et validés.
  2. Résolution et validation YAML : un validateur Python lit le fichier YAML, vérifie sa conformité au schéma, et émet les variables de configuration sous forme de paires clé-valeur évaluées par le shell. Toute erreur de validation provoque l'arrêt immédiat du pipeline.
  3. Vérification des arguments standard : les flags CLEAN_CACHE et HAS_ALL sont consolidés.
  4. Bannière de démarrage : affichage du contexte et initialisation du chronomètre de déploiement.
  5. Vérification de dérive de schéma (drift check) : si la section drift est présente dans le YAML, le moteur compare l'état réel de la base de données au schéma attendu et signale les écarts.
  6. Application automatique de la dérive (drift auto-apply) : activé par défaut, ce mécanisme génère et applique en une seule transaction les instructions DDL idempotentes manquantes (CREATE TABLE IF NOT EXISTS, ALTER TABLE ADD COLUMN IF NOT EXISTS). Le schéma est mis à jour avant tout build ou rechargement. Cette étape peut être désactivée ponctuellement via une variable d'environnement dédiée.
  7. Lancement des hooks en arrière-plan : l'auditeur d'agents est démarré en tâche de fond.
  8. Génération des routes i18n (prébuild) : si la section seed_i18n est présente, le dispatcher interroge les tables de catégories et de métadonnées localisées du tenant (via SSH ou conteneur) et écrit le fichier JSON des segments de route localisés consommé par le module de routing international au moment du build. Cette étape est non bloquante : si la base de données est inaccessible, le build se replie sur les valeurs codées en dur.
  9. Build Nuxt : compilation de l'application front-end.
  10. Seed i18n : si la section est présente, injection des données de traduction.
  11. Source maps : si le hook correspondant est activé, envoi des source maps.
  12. Packaging : création d'une archive compressée du build dans le répertoire temporaire de la machine locale.
  13. Push Git en arrière-plan : si le hook est activé, poussée du dépôt en tâche de fond.
  14. Upload de l'archive : transfert sécurisé de l'archive vers le VPS client.
  15. Rechargement distant : selon la variante déclarée dans le YAML, le dispatcher exécute soit un rechargement gracieux via le gestionnaire de processus (pm2), soit un redémarrage du conteneur applicatif (docker compose restart).
  16. Installation des automates récurrents (cron) : si la section cron est présente, le dispatcher installe les entrées planifiées dans le crontab de l'utilisateur SSH sur le VPS client (jamais sur le vaisseau-mère). Chaque bloc est marqué et idempotent : l'ancien bloc est retiré avant réécriture. Une liste vide déclenche la suppression du bloc. Doctrine : les automates récurrents s'exécutent au plus près de la base de données du tenant.
  17. Attente des hooks en arrière-plan : synchronisation avec les tâches Git push et audit d'agents.
  18. Health check : boucle de vérification de disponibilité de l'URL cible jusqu'à expiration du délai configuré.
  19. Bannière de fin : affichage du bilan et du temps total écoulé.

Schéma du fichier de configuration YAML

Chaque fichier de configuration est validé par le validateur Python avant tout déploiement. Les sections reconnues sont les suivantes :

Section Obligatoire Description
name Oui Identifiant du déploiement (minuscules, chiffres, tirets, underscores).
build Oui Chemin du client local (local_client requis) et environnement Node (node_env, défaut production).
ssh Oui Hôte cible requis ; utilisateur SSH (défaut ubuntu) et chemin de clé optionnels.
remote Oui Variante de déploiement : pm2 ou docker. Voir garde-fous ci-dessous.
health Oui URL de vérification requise ; délai maximal d'attente (défaut 45 secondes).
drift Non Déclaration des écarts de schéma à détecter et appliquer automatiquement.
seed_i18n Non Active la génération prébuild des routes i18n et l'injection des traductions.
hooks Non Hooks optionnels : audit d'agents, upload de source maps, push Git.
cron Non Liste d'automates récurrents à installer sur le VPS client.

Garde-fous de cohérence par variante

Le validateur applique des règles de cohérence strictes selon la variante choisie :

  • Variante pm2 : le nom de l'application de processus (pm2_app) est requis ; les champs propres à Docker (container, subdir) sont interdits.
  • Variante docker : le nom du conteneur cible (container) est requis ; les champs propres à PM2 sont interdits.

Validation de la section cron

Chaque entrée de la section cron est validée individuellement :

  • key : identifiant en kebab-case, unique dans le fichier.
  • schedule : expression crontab en exactement 5 champs.
  • command : commande non vide à exécuter.
  • comment : description libre (optionnelle).

Une section cron déclarée vide ([]) est transmise à l'installeur comme signal de nettoyage : le bloc correspondant est retiré du crontab du tenant.

Exemple de fichier de configuration

Le fichier ci-dessous illustre une configuration type pour un tenant déployé via le gestionnaire de processus PM2, avec push Git activé :

name: mon-tenant
build:
  local_client: un composant interne
  node_env: production
ssh:
  host: <adresse du VPS client>
remote:
  variant: pm2
  dir: /var/www/un composant interne
  pm2_app: mon-tenant-nuxt
  pm2_remote_user: codemyshop
health:
  url: https://mon-tenant.example.com
hooks:
  git_push: mon-tenant

Rappel : l'hôte SSH (host) doit être renseigné avec l'adresse ou le nom d'hôte réel du VPS client. Aucune adresse IP réelle ne doit être versionnée dans un fichier de configuration partagé publiquement.

Pattern : compilation sur le vaisseau-mère, déploiement vers le VPS

L'invariant central pour les déploiements distants est le suivant : le VPS client ne compile jamais. Le bundle Nuxt est entièrement produit sur le vaisseau-mère, compressé, transféré, extrait sur place, puis le processus applicatif est rechargé. La bannière de départ du pipeline affiche explicitement ce mode de fonctionnement.

Déroulement en huit étapes

  ┌─────────────────── VAISSEAU-MÈRE (compilation) ───────────────────┐
  │ 1. Installation des dépendances (delta, mode hors-ligne préféré)  │
  │ 2. Exécution des tests smoke d'invariants d'URL                   │
  │ 3. Compilation Nuxt du client cible  →  .output/                 │
  │ 4. Archivage compressé (pigz si disponible, sinon gzip)           │
  └───────────────────────────┬───────────────────────────────────────┘
                               │  transfert sécurisé (SCP)
                               ▼
  ┌─────────────────── VPS CLIENT ────────────────────────────────────┐
  │ 5. Rotation  .output → .output_old        (rollback prêt)        │
  │ 6. Extraction de l'archive compressée                             │
  │ 7. Rechargement du processus (graceful reload ou restart)         │
  │ 8. Si en ligne → suppression de .output_old                       │
  │    Sinon       → affichage de la commande de restauration + stop  │
  └───────────────────────────┬───────────────────────────────────────┘
                               │
                               ▼  sondage HTTP 200 (délai max configurable)
                         vérification de santé

Détail des fonctions du pipeline

  • Compilation Nuxt : purge du répertoire de sortie (et du cache incrémental si l'option --clean est passée), installation des dépendances en mode hors-ligne, puis test smoke bloquant sur les invariants de forme des URL produit avant tout nuxi build. Si le build incrémental échoue, une tentative avec cache purgé est automatiquement relancée.
  • Archivage : le répertoire .output du client est empaqueté dans une archive compressée. pigz est utilisé si disponible sur le vaisseau-mère, avec repli automatique sur gzip.
  • Transfert : l'archive est copiée sur le VPS via SCP, puis supprimée localement.
  • Redémarrage Docker : connexion SSH au VPS, rotation .output → .output_old, extraction de l'archive dans le répertoire cible, suppression d'un module CommonJS superflu identifié lors d'une cicatrice antérieure (tree-shake CJS), redémarrage du conteneur applicatif, vérification que le conteneur est bien en état running — sinon rollback.
  • Rechargement PM2 graceful : pm2 reload avec mise à jour des variables d'environnement — préserve les connexions en cours. Gère optionnellement un utilisateur PM2 dédié sur le VPS. Un lien symbolique optionnel vers le dossier des uploads du back-office peut être (re)créé pour éviter les erreurs de fichiers statiques introuvables.
  • Redémarrage PM2 hard : suppression puis recréation complète du processus PM2 (premier déploiement ou configuration explicite via paramètre pm2_start), suivi d'une sauvegarde de la liste des processus.
  • Boucle de vérification de santé : sondage HTTP répété jusqu'à obtenir un code 200 ou expiration du délai maximum (45 secondes par défaut).
  • Bannières de départ et de fin : encadrement visuel du pipeline avec décomposition des durées par phase.
  • Installateur de tâche planifiée : installe la tâche cron par client sur le VPS distant (voir §3).

Comportement du rollback

.output_old est conservé pendant toute la durée du rechargement. Il est purgé uniquement si le processus redémarre avec succès. En cas d'échec, le script affiche uniquement la commande de restauration suggérée (mv .output_old .output) et se termine avec un code d'erreur — la restauration reste manuelle, le déplacement n'est jamais exécuté automatiquement. Pour forcer la conservation de .output_old même en cas de succès, la variable SHIP_KEEP_ROLLBACK=1 peut être positionnée.

Smoke post-déploiement : trois couches de validation

Le script de vérification post-déploiement comprend deux sous-niveaux de vérification formels, complétés d'un troisième niveau visuel non-bloquant.

  1. Vérifications HTTP : sondage de chaque URL déclarée pour le client ; tout code 5xx est bloquant, un 4xx génère un avertissement non-bloquant. Les clients couverts incluent le cockpit principal, la vitrine démo, plusieurs tenants de production et le site institutionnel. Une boucle interne couvre six clients en mode all ; deux autres sont vérifiés individuellement depuis le pipeline de livraison.
  2. Vérifications de contenu / JSON : valide la forme du JSON retourné par l'API de navigation principale. Cette couche a été introduite après un incident où une page renvoyait HTTP 200 pendant deux jours avec un JSON portant { error: true }, rendant la navigation entièrement vide et le pied de page affiché en clés brutes. Sans ce niveau, les rendus SSR pilotés par la base de données pouvaient échouer silencieusement. Les clients disposant d'une navigation en base de données sont couverts ; les clients sans navigation globale (cockpit headless, vitrine sans navigation configurée) sont volontairement exclus.
  3. Vérification visuelle automatisée (non-bloquante) : capture d'écran Playwright suivie d'un verdict multimodal. Le module est ignoré proprement si aucun fichier de configuration de vérifications visuelles n'existe pour le client (zéro latence ajoutée). Actif par défaut, désactivable via variable d'environnement.

Anti-faux-positif : vérification des variables d'environnement PM2

Un code HTTP 200 ne suffit pas à valider un déploiement PM2. Un incident a démontré qu'un client pouvait être redéployé sans aucune variable de connexion à la base de données : toutes les routes SSR pilotées par les données renvoyaient 500, mais la page servait un squelette vide syntaxiquement valide — le smoke HTTP retournait donc 200 pendant deux jours.

C'est pourquoi le script de livraison lance un vérificateur d'environnement PM2 avant la vérification smoke : il se connecte en SSH au VPS distant et inspecte la présence des variables critiques dans l'environnement du processus PM2 actif.

Code retour Signification Comportement
0 Toutes les variables critiques sont présentes Pipeline continue
1 Variables manquantes Bloquant — arrêt du pipeline
2 Connexion SSH ou processus PM2 inaccessible Bloquant — arrêt du pipeline

Les cibles non-PM2 (vaisseau-mère headless, environnements Docker purs) retournent automatiquement 0 et ne sont pas inspectées.

Vérification de cohérence du lockfile avant compilation

Avant tout déploiement du cockpit principal, le pipeline exécute un contrôle de cohérence du lockfile détectant deux décalages bloquants :

  1. Version de minifieur CSS inférieure au seuil requis dans le lockfile (dépendances PostCSS transitives manquantes → build cassé silencieusement).
  2. Version de Nuxt inférieure au seuil requis dans le manifeste de dépendances (risque de décalage avec l'outil de bundling).

Un avertissement non-bloquant est émis si des surcharges de version de Vite sont détectées dans la configuration racine. En cas d'erreur bloquante, le script se termine avec un code 1 et un message correctif explicite — le déploiement est annulé. La correction suggérée consiste à régénérer le lockfile depuis zéro.

Déploiement du cockpit principal : auto-déploiement local

Le cockpit principal ne passe pas par le pipeline de déploiement distant décrit ci-dessus. C'est un auto-déploiement local : le vaisseau-mère compile à l'intérieur de son propre conteneur applicatif. Un script dédié orchestre l'ensemble en mode --prod --all.

Spécificités par rapport aux déploiements distants

  • Absence d'environnement de pré-production depuis mi-2026 : ./deploy sur le cockpit principal est un push direct en production live, sans la cérémonie de livraison de ./ship.
  • Transfert local par copie Docker : une archive source minimale (code applicatif + manifestes de dépendances, sans .nuxt, .output ni node_modules) est copiée dans le conteneur via docker cp — aucun transfert réseau.
  • Préservation du cache de build incrémental : avant l'extraction de l'archive, le cache .nuxt existant dans le conteneur est mis de côté puis restauré après extraction. Ce mécanisme est le principal levier de performance : le build reste incrémental et non un cold build complet.
  • Compilation à l'intérieur du conteneur : le build Nuxt s'exécute dans le conteneur actif via docker exec. L'ancien serveur reste en ligne pendant toute la durée de la compilation. Aucun gestionnaire de processus PM2 n'est utilisé dans ce pipeline : le conteneur démarre directement le serveur Node.
  • Séquence de rotation : arrêt gracieux du conteneur → copie du nouveau .output vers l'hôte → recréation forcée du conteneur (relit les fichiers d'environnement). Cette étape a été introduite après un incident où les variables d'environnement n'étaient pas rechargées lors d'un simple restart.
  • Base de code autonome : le cockpit principal n'étend plus le socle commun — l'archive transférée est allégée d'environ 16 MB par rapport à l'ancienne configuration.
  • Optimisation npm par empreinte : une empreinte SHA-256 du fichier de verrou des dépendances est conservée dans le conteneur. Si le lockfile n'a pas changé depuis le dernier déploiement, l'étape npm install est intégralement sautée.
  • Vérification de santé par UUID de build : la vérification ne se limite pas à un HTTP 200. L'UUID du build attendu (lu depuis le manifeste de build côté hôte) est comparé à celui retourné par le point de terminaison /api/health du conteneur. Cela garantit que c'est bien le nouveau build qui sert les requêtes, et non l'ancien maintenu par un rechargement partiel.
  • Variante accélérée : une option opt-in permet de déporter la compilation sur l'hôte plutôt que dans le conteneur, avec un swap atomique du répertoire de sortie (~35 % de gain de temps).
  • Cérémonie de livraison ./ship : ajoute la fusion de branche, le push, les audits d'agents, le smoke de production et la clôture de chantier, mais appelle en final le même script de déploiement local.

Note : contrairement aux VPS clients, le conteneur du cockpit principal ne passe jamais par la phase de transfert SCP ni par PM2. La compilation et le service applicatif sont entièrement conteneurisés sur le même hôte physique.

Inventaire de la topologie — source unique de vérité

La topologie complète de l'infrastructure (liste des environnements, accès SSH, bases de données associées) est stockée dans une seule table de référence, hébergée dans la base de données centrale du vaisseau-mère. Cette table est la source de vérité unique : aucun fichier d'architecture, aucun script, aucun agent ne doit dupliquer ou coder en dur ces informations — tout autre document peut être obsolète.

La commande de listage de l'inventaire interroge cette table et affiche un tableau aligné en console. La requête filtre les entrées actives, ordonne les résultats par criticité décroissante, et formate les colonnes pour une lecture rapide.

-- Exemple illustratif (colonnes réelles, valeurs anonymisées)
SELECT
  rpad(identifiant_tenant, 14)        AS tenant,
  rpad(environnement, 11)             AS env,
  rpad(coalesce(domaine, '-'), 40)    AS domaine,
  CASE WHEN stack_nuxt THEN 'nuxt' ELSE 'ps' END AS stack
FROM inventaire_topologie
WHERE actif = 1
ORDER BY criticite DESC, identifiant_tenant, environnement;

Colonnes notables de la table d'inventaire

Colonne Rôle
Identifiant tenant Clé logique primaire identifiant l'environnement client
Environnement production | staging | infra | legacy | audit
Accès SSH Adresse du VPS client, utilisateur et chemin de clé SSH
Base de données Références vers le container DB, le nom de base et l'identifiant de la variable d'environnement contenant le mot de passe (jamais la valeur)
Runtime web Container applicatif, présence PrestaShop, présence Nuxt
Criticité business Niveau de criticité, revenu mensuel récurrent, offre souscrite
Ship automatique autorisé Booléen autorisant un déploiement automatique — à confirmer avant usage
Codename de déploiement Identifiant utilisé pour composer la commande exacte de déploiement
Codename client Clé étrangère vers la table de fédération client (référentiel centralisé)

État de l'inventaire actif

L'inventaire comprend plusieurs catégories d'entrées actives :

  • Tenants en production active : environnements critiques hébergeant des boutiques ou sites en service, sur un ou plusieurs VPS clients.
  • Environnements staging : copies de recette associées aux tenants principaux.
  • Infra vaisseau-mère : l'entrée désignant le VPS central du vaisseau-mère lui-même.
  • Productions orphelines : certains tenants portent purpose='production' tout en étant en sommeil — il s'agit de projets secondaires sans maintenance active. Ils ne doivent pas être agrégés sous l'étiquette « legacy ».
  • Double entrée hétérogène : un tenant peut apparaître en deux lignes distinctes (ex. une ligne production Nuxt et une ligne legacy sans Nuxt) reflétant deux générations techniques coexistantes. Ne pas les fusionner.
  • Entrée décommissionnée : un tenant ayant fait l'objet d'une mission ponctuelle terminée a été passé actif=0 ; son dossier opérationnel a été retiré, mais son historique est conservé dans le référentiel client centralisé.

Doctrine opérationnelle : avant chaque déploiement (./deploy) ou mise en production (./ship), consulter le cadran de gestion des tenants pour y copier la commande exacte associée au codename cible. Ne jamais recomposer la commande de mémoire.

Doctrine de gestion des secrets — cinq niveaux

Chaque secret possède un seul fichier canonique. La présence d'une même clé dans deux fichiers distincts est traitée comme un bug, non comme une redondance de sécurité.

Les cinq niveaux de fichiers de secrets

# Fichier Périmètre Suivi git
1 Fichier d'environnement du tenant Un seul tenant — lu par son container applicatif et la configuration de déploiement Non
2 Fichier d'environnement du cœur vaisseau-mère Services centraux du vaisseau-mère Non
3 Fichier d'environnement hôte Scripts hôte : tâches planifiées, automates, accès SSH aux VPS clients Non
4 Fichier d'environnement partagé Cross-projet : clés API des fournisseurs IA, clé de chiffrement API, configuration SMTP maître Non (hors dépôt)
5 Fichiers d'exemple (*.env.example) Templates publics open-source — ne contiennent aucune valeur réelle Oui

Ordre de chargement et règle de précédence

Au démarrage du service central, les fichiers d'environnement sont chargés dans l'ordre suivant : fichier partagé → fichier cœur → fichier hôte. En cas de clé présente dans le fichier cœur et dans le fichier hôte, le fichier hôte prend la priorité. Il est donc interdit de déposer la même clé dans ces deux niveaux.

Règle anti-fuite P0

Aucun secret en clair ne doit apparaître dans un fichier suivi par git. Lorsqu'un fichier versionné doit référencer un secret, il cite uniquement le nom de la variable d'environnement et le fichier .env* qui la porte — jamais sa valeur. Par exemple, la configuration de déploiement d'un tenant référence le nom de la variable contenant le mot de passe de base de données ; la table d'inventaire topologie stocke de même le nom de la variable, pas le mot de passe lui-même.

Note : la vue résumée en trois niveaux (infra / cœur / tenants) constitue l'entrée rapide dans la documentation principale. La doctrine complète à cinq niveaux — qui ajoute le fichier partagé cross-projet et les templates publics — est la référence faisant foi en cas de divergence.

Règle de commit avant déploiement

Une modification appliquée localement mais jamais commitée ne survit pas à un déploiement : le mécanisme de synchronisation du dépôt restaure la version commitée et écrase tout changement flottant. Un incident passé (correction d'URL en production restée non commitée, écrasée lors d'un déploiement suivant) a causé plusieurs heures de production cassée. Cette règle est donc classée P0.

Comportement du garde-fou de propreté

Un script de vérification de l'état du dépôt est exécuté avant chaque opération de déploiement. Il retourne trois états : propre, modifié (dirty), ou hors-dépôt. La vérification est scopée par cible : un fichier modifié côté vaisseau-mère ne bloque pas un déploiement tenant, et inversement.

  • Lors d'un déploiement preprod (./deploy) : si l'état est dirty, un commit automatique silencieux est généré (chore(auto): pre-deploy snapshot <horodatage ISO>) et poussé en arrière-plan. Exception : si la branche courante est main, le déploiement est refusé — aucun commit automatique sur la branche principale.
  • Lors d'une mise en production (./ship) : si l'état est dirty, la commande est bloquante et retourne une erreur explicite invitant à commiter d'abord. Un paramètre de contournement existe pour les corrections d'urgence volontaires uniquement.

Un hook de fin de session bloque également la clôture de session si le dépôt n'est pas propre. La règle de workflow est la suivante : aucun travail terminé ne reste uncommitted — l'agent commente et commite, l'opérateur humain ne manipule jamais directement les commandes git de base.

Annexe — fichiers de référence

Composant Rôle
Entrypoint de déploiement preprod Point d'entrée racine pour les déploiements de recette et le routage vers les tenants
Entrypoint de mise en production Point d'entrée racine pour les mises en production — opérateur humain uniquement — avec cérémonie complète de validation
Dispatcher de déploiement Lit la configuration de déploiement au format YAML et orchestre les déploiements distants pour tous les tenants (hors vaisseau-mère)
Bibliothèque de helpers de déploiement Fonctions réutilisables : build, packaging, upload, rechargement, vérification de santé, bannière, tâches planifiées
Parseur de configuration de déploiement Analyse et valide le fichier de configuration YAML et expose les variables de déploiement (sections : build, SSH, dérive, initialisation i18n, remote, santé, hooks, tâches planifiées)
Script de self-déploiement vaisseau-mère Déploie le service central du vaisseau-mère via build dans le container suivi d'un swap atomique stop/copie/start
Variante de déploiement accélérée Effectue le build sur l'hôte directement puis réalise le swap atomique — plus rapide pour les itérations courtes
Garde-fou de propreté git Vérifie l'état du dépôt avant déploiement ou mise en production (propre / modifié / hors-dépôt)
Garde-fou de cohérence des dépendances Vérifie la cohérence des versions de lockfile avant déploiement du vaisseau-mère (versions minimales requises pour les outils CSS et le framework)
Script de smoke test HTTP Vérifie la disponibilité et le contenu des endpoints après déploiement — huit branches de cas couverts ; mode all couvre l'ensemble des tenants actifs
Vérificateur d'environnement de processus Contrôle la configuration de l'environnement du gestionnaire de processus avant les smoke tests en production — plusieurs tenants couverts ; certains environnements sans gestionnaire de processus sont exclus par conception
Schéma de configuration de déploiement Spécification du format YAML de déploiement avec quatre exemples canoniques
Runbook d'exploitation VPS Procédures opérationnelles SSH, gestionnaire de processus, Docker, renouvellement de certificats
Doctrine des secrets Référence complète des cinq niveaux de gestion des secrets
Table d'inventaire topologie Source unique de vérité pour la topologie de l'infrastructure (voir §6)

Avertissement : le document de workflow historique est partiellement obsolète. Il décrit encore un environnement de preprod mothership (port dédié, sous-domaine de recette) et un script de ship séparé comme pipeline actif, alors que la preprod vaisseau-mère a été démantelée et que le déploiement passe désormais par le script de self-déploiement décrit ci-dessus. En cas de conflit, le code fait foi, pas la documentation narrative.