--- # LE POSTE D'EXPLOITATION — la machine depuis laquelle l'écosystème se reconstruit. # # Jusqu'ici, Set-OPS ne s'exécutait que depuis le poste de son mainteneur. L'écosystème # détenait donc la recette (le génome, sur sa forge) sans que personne, chez lui, ne # sache la lire à voix haute. `serveur_ops` est la cuisine : ansible, le génome cloné # depuis SA PROPRE forge, et de quoi rejouer le moteur. # # CE QU'IL N'EMPORTE PAS, ET NE DOIT PAS EMPORTER : le mot de passe de la voûte. Il est # saisi à l'exécution. Une machine qui détient à la fois le plan, l'accès SSH à la flotte # et la clé des secrets n'a plus aucune profondeur : sa compromission est celle de tout # l'écosystème. Voir README.md du rôle. serveur_ops_utilisateur: "setops" serveur_ops_racine: "/opt/setops" serveur_ops_venv: "{{ serveur_ops_racine }}/venv" # Paquets du poste. `make` parce que l'interface opérateur de Set-OPS est un Makefile ; # `git` parce que le génome vit dans des dépôts ; `rsync` pour les transferts de fichiers # d'Ansible sur les gros arbres. serveur_ops_paquets: - git - make - python3-venv - python3-pip - openssh-client - rsync - ca-certificates # Ansible est ÉPINGLÉ sur la même famille que le poste du mainteneur (core 2.18) : un # écosystème qui se reconstruit avec une version différente de celle qui l'a construit ne # reproduit pas la même chose, et l'écart ne se voit qu'au premier échec. serveur_ops_ansible: "ansible-core>=2.18,<2.19" # --- D'OÙ VIENT LE GÉNOME ---------------------------------------------------- # # DE SA PROPRE FORGE, pas de l'amont. C'est tout l'objet des miroirs : la forge de # l'écosystème détient une copie vivante du génome, resynchronisée toutes les huit # heures. Le poste d'exploitation lit CETTE copie — l'écosystème se reconstruit donc # depuis lui-même, et non depuis son parent. # # Dérivé du groupe `serveur_forgejo` de l'inventaire. Surchargeable pour une forge # externe au plan. serveur_ops_forge_hote: >- {{ (groups['serveur_forgejo'] | default([]) | first | default('')) }} serveur_ops_forge_url: >- {{ serveur_ops_forge_amont if (serveur_ops_forge_externe | bool) else 'https://forge.' ~ domaine_interne }} serveur_ops_forge_organisation: "genome" # --- FILIATION, MUTUALISATION, ÉMANCIPATION ---------------------------------- # # Un écosystème naît FILS : il n'héberge pas encore tout ce dont il a besoin, et emprunte # à son hôte. Il peut rester ainsi — la MUTUALISATION est un état durable et choisi, pas # une infirmité — ou devenir AUTOPORTANT le jour où il le décide. # # La forge est le premier service concerné. `false` : le génome est lu sur la forge de # CET écosystème (état émancipé). `true` : il est lu chez l'hôte (état natal, ou # mutualisation assumée) — et l'écosystème n'a alors pas besoin d'héberger de forge. # # CE N'EST PAS UN RABAIS. Un modèle sans forge n'est pas un modèle amputé : c'est un # écosystème au premier âge, dont le runner lit le génome chez son parent. L'émancipation # consiste à lever ce drapeau, déployer sa propre forge, et PROUVER que le lien est coupé. # # Cette variable était citée par `docs/dependances-groupes.yml` et par le README sans # exister nulle part (constaté le 2026-08-24) : une exemption qui ne pouvait jamais # s'appliquer, donc un écosystème sans forge que le registre refusait quand même. serveur_ops_forge_externe: false serveur_ops_forge_amont: "" # --- LA CONFIANCE ENVERS UNE FORGE D'UN AUTRE TENANT ------------------------- # # Lire le genome chez un voisin suppose de faire confiance a SON autorite — or chaque # ecosysteme a la sienne, et `client_pki` n'etablit la confiance que dans celle du tenant. # # ON NE POSE PAS CETTE RACINE DANS LE MAGASIN SYSTEME. L'y mettre ferait confiance a cette # AC pour TOUT ce que la machine contacte, alors qu'on ne lui demande qu'une chose : servir # un depot git. `git config http..sslCAInfo` limite la confiance a CETTE url — une # porte, pas un trousseau. # # Chemin d'un certificat racine PUBLIC, sur le controleur. Vide = rien a poser (forge du # meme ecosysteme, ou AC publiquement reconnue). serveur_ops_forge_amont_ac: "" serveur_ops_forge_amont_ac_depot: "{{ serveur_ops_racine }}/.ac-amont.crt" # Les dépôts du génome, et où ils atterrissent. Les noms sont ceux que la forge porte # (minuscules) ; les destinations reprennent la disposition du poste du mainteneur, où # le moteur et les instances sont des dossiers frères — c'est cette fraternité que # `scripts/instances.py` découvre. # # AUCUN DÉFAUT NE NOMME UN ÉCOSYSTÈME PARTICULIER (2026-08-24). # # La première version listait ici `ops-patient0`. Tout écosystème qui aurait déployé un # poste sans déclarer ses propres dépôts aurait donc cloné le génome de **patient 0** — # silencieusement, et en croyant piloter le sien. C'est la faute que ce dépôt combat sous # tous ses déguisements : un défaut plausible qui rend un résultat faux sans rien dire. # # Le moteur est le seul défaut légitime : il est le même pour tous. Le dépôt d'instance, # lui, est propre à l'écosystème et DOIT être déclaré — la tâche d'exigence refuse de # poursuivre sans lui. serveur_ops_depots: - { depot: "set-ops-public", dest: "Set-OPS-public", role: "moteur" } # --- LES DEUX SYMLINKS (D-80) ------------------------------------------------ # # `instance` dit QUEL tenant on pilote. # `underlay.yml` dit SUR QUELLE FABRIC il repose. # # Les deux sont independants, et le second manquait. Un poste qui ne connaît que son # plan sait CONFIGURER des machines existantes ; il ne sait pas en CREER. Placer une VM # demande de savoir sur quel nœud, quel stockage, quel pont — c'est-à-dire l'underlay. # Sans lui, le poste est un exploitant, pas un géniteur. # # CE QUE LE POSTE N'AURA PAS POUR AUTANT : `underlay.vault.yml`, qui porte les secrets du # monde physique. Il est hors dépôt (gitignore), donc absent du génome. Le poste lit la # CARTE de la fabric, pas ses clés. # Le dossier de l'instance pilotée, cible du lien `instance`. Sans valeur, le rôle # refuse : un poste qui ne sait pas quel tenant il pilote n'a rien à piloter. serveur_ops_instance: "" # Vide = le poste ne pose pas de lien `underlay.yml` (il configure, il n'engendre pas). serveur_ops_underlay: "" # --- CLÉ SSH DU POSTE -------------------------------------------------------- # # Le poste a besoin d'atteindre toute la flotte en SSH. Il se fabrique donc sa PROPRE # paire, distincte de celle du mainteneur : deux exploitants, deux clés, deux révocations # possibles. La clé publique produite doit être autorisée sur la flotte — le rôle # l'affiche et l'écrit dans un fichier prévu pour être repris au plan (voir README). serveur_ops_cle_type: "ed25519" serveur_ops_cle_generer: true # --- CACHE DES COLLECTIONS (côté CONTRÔLEUR) --------------------------------- # # Le poste ne va PAS chercher ses collections sur galaxy.ansible.com : la frontière ne # laisse pas passer ce flux, et ouvrir une règle vers un serveur étranger pour qu'un # écosystème sache se reconstruire serait la mauvaise réponse. Le contrôleur télécharge # une fois dans son cache, puis pousse par SSH — même idiome que `serveur_forgejo` # devant son propre tiers. # # Effet de bord recherché : le poste devient déployable **hors ligne**. # LE CACHE SUIT LE CONTENU DE `requirements.yml`, PAS SON EXISTENCE (2026-08-24). # # La premiere version gardait le telechargement par `creates: /requirements.yml` : # une collection AJOUTEE au fichier n'aurait jamais ete recuperee, puisque le temoin # existait deja. Le runner serait reste en retard sur le moteur, en silence -- exactement # le defaut qui a fait qu'`ansible.posix` manquait sans que rien ne le dise. # # L'empreinte du fichier entre dans le chemin : changer une ligne de `requirements.yml` # cree un cache neuf, donc un telechargement. Les anciens caches restent (ils ne genent # pas) et documentent ce que les versions precedentes exigeaient. serveur_ops_cache_collections: >- {{ (setops_cache_artefacts | default(lookup('env', 'HOME') + '/.cache/setops')) }}/collections-{{ lookup('file', serveur_ops_requirements_controleur) | hash('sha1') | truncate(12, true, '') }} serveur_ops_requirements_controleur: "{{ playbook_dir }}/../../requirements.yml" # --- LA ROUE HORS-LIGNE : PYTHON PAR LE MEME CHEMIN QUE LES COLLECTIONS ------- # # LA DERNIERE DEPENDANCE QUI SORTAIT PAR ELLE-MEME (2026-08-28). Les collections # Ansible suivent depuis longtemps l'idiome de ce depot — « le CONTROLEUR telecharge une # fois, puis pousse par SSH ; aucun flux nouveau depuis la cible, et l'artefact devient # deployable hors ligne, ce qui est le sens meme d'une lignee autonome ». `pip`, lui, # sortait encore vers PyPI, en HTTPS et par nom de domaine. # # Un tenant n'a NI DNS SORTANT NI 443 vers l'Internet, et c'est voulu. Mesure sur ops-01, # premiere machine de la reconstruction de Chezlepro : # # ERROR: Could not find a version that satisfies the requirement ansible-core<2.19 # Echec temporaire dans la resolution du nom # # POURQUOI PAS LE PAQUET DEBIAN : trixie propose `ansible-core 2.19.4`, hors de la plage # epinglee. Y aller demanderait de deplacer l'epinglage vers une version majeure aux # changements de gabarits connus, trois jours apres l'avoir pose pour cause de # portabilite. Ce n'est pas le moment, et ce n'est pas ce probleme-ci. # # L'EMPREINTE DES SOURCES ENTRE DANS LE CHEMIN, comme pour les collections : changer une # ligne de `requirements-python.txt` ou l'epinglage d'ansible-core cree un cache neuf, # donc un telechargement. Les anciens restent et documentent ce qu'exigeaient les # versions precedentes. serveur_ops_roues_source: "{{ playbook_dir }}/../../requirements-python.txt" serveur_ops_cache_roues: >- {{ (setops_cache_artefacts | default(lookup('env', 'HOME') + '/.cache/setops')) }}/roues-{{ (lookup('file', serveur_ops_roues_source) ~ serveur_ops_ansible) | hash('sha1') | truncate(12, true, '') }} serveur_ops_roues_depot: "{{ serveur_ops_racine }}/.roues-hors-ligne"