Set-OPS-Public/roles/serveur_ops/defaults/main.yml
Daniel Allaire 1363ebff59 acces : le runner peut enfin entrer, et trois silences fermes
ssh_baseline gere les cles d'administration declarees au plan, avec un `etat`
par entree : revoquer devient un changement de plan, pas une visite sur chaque
machine. Pas d'exclusive -- il effacerait la cle de cloud-init et fermerait la
flotte a tout le monde.

cloud-init reecrivait /etc/hosts a chaque demarrage et effacait le plancher de
resolution. Constate sur infra-dns-01 apres un redemarrage : six entrees
perdues, revelees deux jours plus tard par un apt update qui ne resolvait plus.

requirements.yml ne declarait pas ansible.posix ni community.docker, pourtant
utilisees. Ca marchait chez le mainteneur, pas sur un runner. Et le cache des
collections suivait l'existence du fichier au lieu de son contenu.

Enfin : deux `when` sur une meme tache, c'est un seul -- le dernier. Le
check-mode avait disparu en silence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 15:50:37 -04:00

140 lines
7.4 KiB
YAML

---
# 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: ""
# 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: <cache>/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"