--- # UN POSTE QUI NE SAIT PAS SUR QUOI IL TRAVAILLE N'A RIEN À PILOTER. # # On exige explicitement ce qu'il pilote, plutôt que de retomber sur un défaut : le # défaut nommait patient 0, et un écosystème distrait aurait cloné le génome d'un autre # sans qu'aucune erreur ne le signale. # # DEUX FORMES DE RUNNER, ET UNE SEULE ÉTAIT PRÉVUE (2026-08-25). # # Un runner de TENANT pilote un écosystème : il monte son plan par le lien `instance`, # détient sa voûte, et configure ses services. Il lui faut donc un dépôt `role: instance` # et `serveur_ops_instance`. # # Un runner de SITE ne pilote aucun écosystème — il MATÉRIALISE le terrain que d'autres # occuperont : VNets, VM, routes, flux. Son propre inventaire est DYNAMIQUE # (`site_inventaire.py`), il n'a pas de lien `instance` à monter, et les dépôts de # tenants qu'il porte sont marqués `role: tenant` précisément pour dire qu'il ne les # pilote pas. Ce qu'il pilote, c'est la CARTE de l'hébergeur : `role: hebergeur` et # `serveur_ops_underlay`. # # L'assertion n'admettait que la première forme. Le plan du site déclarait pourtant tout # ce qu'il fallait, correctement et avec ses raisons écrites — et le rôle le refusait au # nom d'une exigence qui ne le concernait pas. # # On exige donc que le runner pilote QUELQUE CHOSE, sans prescrire quoi. - name: Exiger les intrants du poste d'exploitation ansible.builtin.assert: that: - domaine_interne | default('') | length > 0 - (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | list | length) == 1 - >- ((serveur_ops_depots | selectattr('role', 'eq', 'instance') | list | length) >= 1 and serveur_ops_instance | default('') | length > 0) or ((serveur_ops_depots | selectattr('role', 'eq', 'hebergeur') | list | length) >= 1 and serveur_ops_underlay | default('') | length > 0) - not (serveur_ops_forge_externe | bool) or (serveur_ops_forge_amont | length > 0) fail_msg: >- serveur_ops exige `domaine_interne`, exactement un dépôt `role: moteur`, et de savoir sur quoi il travaille — SOIT un dépôt `role: instance` avec `serveur_ops_instance` (runner de TENANT : il pilote un écosystème), SOIT un dépôt `role: hebergeur` avec `serveur_ops_underlay` (runner de SITE : il matérialise le terrain, et son inventaire est dynamique). Et si `serveur_ops_forge_externe` est levé — écosystème au premier âge, qui lit son génome chez son hôte — `serveur_ops_forge_amont` doit nommer cette forge. - name: Installer l'outillage du poste ansible.builtin.apt: name: "{{ serveur_ops_paquets }}" state: present update_cache: true when: not ansible_check_mode - name: Créer l'utilisateur d'exploitation ansible.builtin.user: name: "{{ serveur_ops_utilisateur }}" home: "{{ serveur_ops_racine }}" shell: /bin/bash create_home: true system: false - name: Poser les droits de la racine d'exploitation ansible.builtin.file: path: "{{ serveur_ops_racine }}" state: directory owner: "{{ serveur_ops_utilisateur }}" group: "{{ serveur_ops_utilisateur }}" mode: "0750" # L'ENVIRONNEMENT PYTHON EST ISOLÉ, PAS SYSTÈME. Debian gère ansible en paquet, mais la # version qu'il propose suit son propre calendrier : reconstruire un écosystème avec un # ansible plus récent que celui qui l'a construit, c'est changer la recette sans le dire. # Le venv permet d'épingler exactement la famille utilisée par le mainteneur. - name: Créer l'environnement Python isolé ansible.builtin.command: cmd: "python3 -m venv {{ serveur_ops_venv }}" creates: "{{ serveur_ops_venv }}/bin/python" become: true become_user: "{{ serveur_ops_utilisateur }}" # LES BIBLIOTHEQUES DU CONTROLEUR SE DECLARENT, ELLES AUSSI (2026-08-27). Cette liste # etait ecrite en dur — `ansible-core` et `pyyaml`, rien de plus — alors que les modules # Proxmox du moteur exigent `proxmoxer` sur la machine qui les execute. Le runner du SITE # a donc echoue a sa premiere materialisation de VM, sur une bibliotheque que le poste du # mainteneur possedait par son paquet systeme sans que personne ne l'ait declaree. # `requirements-python.txt` est desormais la source, au meme titre que `requirements.yml` # pour les collections. Voir la preuve P51. - name: Deposer la liste des bibliotheques Python du controleur ansible.builtin.copy: src: "{{ playbook_dir }}/../../requirements-python.txt" dest: "{{ serveur_ops_racine }}/requirements-python.txt" owner: "{{ serveur_ops_utilisateur }}" group: "{{ serveur_ops_utilisateur }}" mode: "0644" # --- LA ROUE HORS-LIGNE ------------------------------------------------------ # # MEME IDIOME QUE LES COLLECTIONS, quelques lignes plus bas : le CONTROLEUR telecharge # une fois dans son cache, puis pousse par SSH. La cible n'ouvre aucun flux, et # l'artefact devient deployable hors ligne. - name: Preparer le cache des roues Python sur le controleur ansible.builtin.file: path: "{{ serveur_ops_cache_roues }}" state: directory mode: "0755" delegate_to: localhost become: false run_once: true # LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas. # # `pip download` rend les roues du systeme qui telecharge. `ansible-core`, `proxmoxer` et # `requests` sont universelles (`py3-none-any`), mais `pyyaml` porte du C compile : une # roue `cp313` posee sur un python 3.12 ne s'installe pas. L'echec serait « No matching # distribution found » a l'installation — un message qui accuse le depot local alors que # le coupable est l'ecart entre deux machines. On le mesure ici, ou on peut encore le dire. - name: Lire le Python du controleur ansible.builtin.command: argv: ["python3", "-c", "import sys; print('%d.%d' % sys.version_info[:2])"] delegate_to: localhost become: false run_once: true changed_when: false # LIRE UNE VERSION N'EST PAS CHANGER, ET `--check` RENDAIT LA GARDE MUETTE (2026-09-14). # # Sans `check_mode: false`, cette commande est sautee en mode idempotent : la garde qui # suit compare alors contre du VIDE et refuse. # # Le controleur tourne Python et site-ops-01 tourne Python 3.13 # # L'espace apres « Python » est tout le diagnostic — il n'y avait rien a comparer. Un # essai a blanc rendait donc un echec sur le runner, alors que le deploiement reel passe. check_mode: false register: serveur_ops_python_controleur - name: Exiger que le controleur et la cible partagent leur version de Python ansible.builtin.assert: that: - serveur_ops_python_controleur.stdout | trim == ansible_python_version.split('.')[0:2] | join('.') fail_msg: >- Le controleur tourne Python {{ serveur_ops_python_controleur.stdout | trim }} et {{ inventory_hostname }} tourne Python {{ ansible_python_version.split('.')[0:2] | join('.') }}. Les roues telechargees ne s'installeraient pas sur la cible, et l'echec accuserait le depot local au lieu de cet ecart. Materialiser depuis un controleur de meme famille — pendant une insemination, c'est le runner du SITE, qui porte le meme Debian que la machine qu'il amorce. # `argv` ET NON `cmd` : le chemin du controleur peut contenir une ESPACE (« Espace # Chezlepro/… » chez le mainteneur). Meme piege que pour ansible-galaxy le 2026-08-23. - name: Telecharger les roues sur le controleur (une seule fois par empreinte) ansible.builtin.command: argv: - "python3" - "-m" - "pip" - "download" - "--dest" - "{{ serveur_ops_cache_roues }}" - "--requirement" - "{{ serveur_ops_roues_source }}" - "{{ serveur_ops_ansible }}" - "pyyaml" delegate_to: localhost become: false run_once: true register: serveur_ops_roues_telechargees changed_when: "'Saved' in serveur_ops_roues_telechargees.stdout" # LE GARDE-FOU SUIT LE RESULTAT, PAS LE GESTE (lecon du 2026-08-27) : on saute quand le # cache PORTE deja des roues, pas quand une commande a deja tourne un jour. when: not ansible_check_mode - name: Deposer les roues sur le runner ansible.builtin.copy: src: "{{ serveur_ops_cache_roues }}/" dest: "{{ serveur_ops_roues_depot }}/" owner: "{{ serveur_ops_utilisateur }}" group: "{{ serveur_ops_utilisateur }}" mode: "0644" directory_mode: "0755" when: not ansible_check_mode # `--no-index` EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de # sortir vers PyPI le jour ou le depot local serait incomplet — et la reussite dependrait # alors d'un flux que ce tenant n'a pas le droit d'avoir. Un repli silencieux vers # l'Internet transformerait une lignee autonome en lignee qui a l'air autonome. - name: Installer Ansible dans l'environnement isolé (hors ligne) ansible.builtin.pip: name: - "{{ serveur_ops_ansible }}" - "pyyaml" virtualenv: "{{ serveur_ops_venv }}" state: present extra_args: "--no-index --find-links {{ serveur_ops_roues_depot }}" become: true become_user: "{{ serveur_ops_utilisateur }}" when: not ansible_check_mode register: serveur_ops_pip retries: 3 delay: 6 until: serveur_ops_pip is succeeded # TACHE SEPAREE : dans `ansible.builtin.pip`, `name` et `requirements` sont MUTUELLEMENT # EXCLUSIFS. Les fusionner ne produit pas une erreur parlante — le module ignore l'un des # deux, et la bibliotheque manquante ne se revele qu'au premier module qui en depend. - name: Installer les bibliotheques Python du controleur (source declaree, hors ligne) ansible.builtin.pip: requirements: "{{ serveur_ops_racine }}/requirements-python.txt" virtualenv: "{{ serveur_ops_venv }}" state: present extra_args: "--no-index --find-links {{ serveur_ops_roues_depot }}" become: true become_user: "{{ serveur_ops_utilisateur }}" when: not ansible_check_mode register: serveur_ops_pip_controleur retries: 3 delay: 6 until: serveur_ops_pip_controleur is succeeded # L'OUTILLAGE DOIT ETRE SUR LE CHEMIN DE CELUI QUI L'EXPLOITE (2026-08-26). # # Ansible vit dans un venv isole — c'est voulu, il est epingle. Mais rien ne le mettait sur # le PATH du compte d'exploitation : `make instancier` y echouait sur # « [Errno 2] No such file or directory: 'ansible-inventory' », un message qui accuse un # fichier manquant alors que le fichier est la, deux dossiers plus loin. # # Mesure du 2026-08-26, en eprouvant la sequence depuis le runner du site. Un exploitant y # serait tombe exactement de la meme facon — et c'est precisement ce qu'un outil « utilisable # sans IA » ne doit pas faire. # # Un fragment `profile.d` plutot qu'un `.bashrc` : il vaut pour toute session du compte, y # compris `sudo -iu setops`, et se retire d'un seul geste. - name: Mettre l'outillage du poste sur le chemin du compte d'exploitation ansible.builtin.copy: dest: /etc/profile.d/50-setops-venv.sh content: | # GÉNÉRÉ par Set-OPS (rôle serveur_ops). NE PAS éditer à la main. # Ansible vit dans un venv épinglé ; sans cette ligne, `make` ne le trouve pas. if [ "$(id -un)" = "{{ serveur_ops_utilisateur }}" ]; then PATH="{{ serveur_ops_venv }}/bin:$PATH" export PATH fi owner: root group: root mode: "0644" # LA CONFIANCE, AVANT LE CLONE — et limitee a une seule url. # # Voir defaults/main.yml : on ne pose pas cette racine dans le magasin systeme. `git # config http..sslCAInfo` ne l'applique qu'aux echanges avec CETTE forge. - name: Deposer la racine de la forge amont ansible.builtin.copy: src: "{{ serveur_ops_forge_amont_ac }}" dest: "{{ serveur_ops_forge_amont_ac_depot }}" owner: "{{ serveur_ops_utilisateur }}" group: "{{ serveur_ops_utilisateur }}" mode: "0644" when: - serveur_ops_forge_amont_ac | length > 0 - not ansible_check_mode - name: Limiter la confiance a la forge amont, et a elle seule community.general.git_config: name: "http.{{ serveur_ops_forge_url }}/.sslCAInfo" scope: global value: "{{ serveur_ops_forge_amont_ac_depot }}" become: true become_user: "{{ serveur_ops_utilisateur }}" when: - serveur_ops_forge_amont_ac | length > 0 - not ansible_check_mode # --- LE GÉNOME, DEPUIS SA PROPRE FORGE --------------------------------------- # # Cloné en anonyme : les dépôts du génome sont lisibles sur la forge de l'écosystème, et # le poste n'a donc AUCUN justificatif à détenir pour se reconstruire. C'est délibéré — # un secret de moins sur la machine qui en concentre déjà beaucoup. # # La confiance TLS vient de `client_pki` quand la forge est celle de CET écosystème # (racine step-ca dans le magasin système). Quand elle est celle d'un voisin, elle vient # de la tâche ci-dessus — limitée à cette seule url. Sans l'une ou l'autre, `git clone` # refuse le certificat, et c'est bien qu'il refuse. - name: Cloner le génome depuis la forge de l'écosystème ansible.builtin.git: repo: "{{ serveur_ops_forge_url }}/{{ serveur_ops_forge_organisation }}/{{ item.depot }}.git" dest: "{{ serveur_ops_racine }}/{{ item.dest }}" version: "{{ item.branche | default('main') }}" update: true force: false loop: "{{ serveur_ops_depots }}" loop_control: label: "{{ item.depot }} → {{ item.dest }}" become: true become_user: "{{ serveur_ops_utilisateur }}" when: not ansible_check_mode register: serveur_ops_clone retries: 3 delay: 6 until: serveur_ops_clone is succeeded # --- LES COLLECTIONS, SANS DEPENDRE DE GALAXY --------------------------------- # # UN POSTE D'EXPLOITATION QUI APPELLE galaxy.ansible.com POUR SE CONSTRUIRE N'EST PAS # SOUVERAIN. Mesure du 2026-08-23, premier deploiement : `ansible-galaxy collection # install` a echoue depuis l'overlay de patient 0 — # `SSL: UNEXPECTED_EOF_WHILE_READING` — parce que la frontiere ne laisse pas passer ce # flux, et c'est tres bien ainsi. Ouvrir une regle vers un serveur americain pour que # l'ecosysteme sache se reconstruire aurait ete la mauvaise reponse. # # MEME IDIOME QUE serveur_forgejo DEVANT SON PROPRE TIERS : le CONTROLEUR telecharge une # fois, dans son cache, puis pousse par SSH. Aucun flux nouveau depuis la cible, et # l'artefact devient deployable hors ligne — un poste peut se reconstruire sans internet # du tout, ce qui est le sens meme d'une lignee autonome. - name: Preparer le cache des collections sur le controleur ansible.builtin.file: path: "{{ serveur_ops_cache_collections }}" state: directory mode: "0755" delegate_to: localhost become: false run_once: true # `argv` ET NON `cmd` : le chemin du controleur peut contenir une ESPACE (« Espace # Chezlepro/… » chez le mainteneur), et `cmd` la lit comme un separateur d'arguments. # Constate le 2026-08-23 : ansible-galaxy recevait « /home/…/Espace » puis # « Chezlepro/… » comme deux arguments, et refusait — « positional collection_name arg # and --requirements-file are mutually exclusive ». Le message ne parlait pas du tout du # vrai probleme. - name: Telecharger les collections dans le cache du controleur (une seule fois) ansible.builtin.command: argv: - "ansible-galaxy" - "collection" - "download" - "-r" - "{{ serveur_ops_requirements_controleur }}" - "-p" - "{{ serveur_ops_cache_collections }}" creates: "{{ serveur_ops_cache_collections }}/requirements.yml" delegate_to: localhost become: false run_once: true when: not ansible_check_mode - name: Deposer les collections sur le poste register: serveur_ops_depot_collections ansible.builtin.copy: src: "{{ serveur_ops_cache_collections }}/" dest: "{{ serveur_ops_racine }}/.collections-hors-ligne/" owner: "{{ serveur_ops_utilisateur }}" group: "{{ serveur_ops_utilisateur }}" mode: "0644" directory_mode: "0755" when: not ansible_check_mode # `chdir` OBLIGATOIRE : le `requirements.yml` produit par `collection download` nomme les # archives en RELATIF (`community-postgresql-4.2.0.tar.gz`), et ansible-galaxy les cherche # depuis le repertoire COURANT, pas depuis celui du fichier. Sans chdir : « Could not find # community-postgresql-4.2.0.tar.gz » alors que l'archive est bien la, a cote. # LE GARDE-FOU DOIT SUIVRE LE RESULTAT, PAS LE GESTE (2026-08-27). La condition # d'installation ne regardait que le DEPOT des archives. Le jour ou l'on a corrige le # CHEMIN d'installation, les archives etaient inchangees : le role a donc conclu qu'il # n'y avait rien a faire, et les collections sont restees a l'ancien endroit. Le meme # piege qu'en 2026-08-24, deplace d'un cran. On mesure desormais la DESTINATION. - name: Voir si les collections sont deja la ou le moteur les cherche ansible.builtin.stat: path: >- {{ serveur_ops_racine }}/{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | first).dest }}/.ansible/collections/ansible_collections/community/general/MANIFEST.json register: serveur_ops_collections_en_place become: true become_user: "{{ serveur_ops_utilisateur }}" - name: Installer les collections depuis le depot local (hors ligne) ansible.builtin.command: cmd: >- {{ serveur_ops_venv }}/bin/ansible-galaxy collection install -r requirements.yml --force -p {{ serveur_ops_racine }}/{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | first).dest }}/.ansible/collections chdir: "{{ serveur_ops_racine }}/.collections-hors-ligne" # INSTALLER LA OU LE MOTEUR REGARDE (2026-08-27). Le `Makefile` pose # `export ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous `make`, Ansible ne cherche donc ses # collections QUE dans `/.ansible/collections`. Ce role les deposait dans # `/.ansible/collections` — a cote, jamais lu. Le runner du SITE echouait ainsi # sur `couldn't resolve module/action community.general.proxmox_pool` alors que la # collection etait bel et bien installee, complete, et visible par `ansible-doc`. # # POURQUOI LE MAINTENEUR NE VOYAIT RIEN : sur son poste, les collections viennent du # paquet systeme (`/usr/lib/python3/dist-packages/ansible_collections`), toujours dans # le chemin de recherche quel que soit `ANSIBLE_HOME`. Le reglage du Makefile n'y a # jamais eu de consequence. Sur un runner, ou rien n'est installe par la distribution, # il decide de tout. Encore un ecart qu'on ne voit qu'en portant le moteur ailleurs. # # `--force` PARCE QU'UN POSTE PEUT ETRE EN AVANCE, PAS SEULEMENT EN RETARD (2026-08-27). # Sans lui, `ansible-galaxy` laisse en place une collection deja installee dans une # version SUPERIEURE a celle demandee : il ne retrograde pas. Le runner du SITE, monte # avant que `requirements.yml` n'epingle ses versions, portait `community.general` 13.3.0 # — une version d'ou les modules Proxmox ont ete RETIRES. La premiere materialisation de # VM depuis le runner a echoue la-dessus. Le depot doit faire autorite dans les DEUX # sens : ce que `requirements.yml` declare est ce qui est installe, ni plus haut ni plus # bas. Voir la preuve P51. # # LE GARDE-FOU SUIVAIT LA MAUVAISE CHOSE (2026-08-24). Il etait `creates: …/community/general` : # une collection AJOUTEE a requirements.yml n'aurait jamais ete installee, puisque le # repertoire temoin existait deja. Le runner serait reste en retard sur le moteur, en # silence. On suit desormais le DEPOT des archives : si les collections deposees # changent, on reinstalle. changed_when: true become: true become_user: "{{ serveur_ops_utilisateur }}" # DEUX `when` SUR UNE MEME TACHE, C'EST UN SEUL — LE DERNIER (2026-08-24). En ajoutant # la condition sur le depot, j'ai laisse celle du check-mode juste en dessous : YAML # garde la derniere cle et jette l'autre, en silence. `ansible-lint` l'a vu ; personne # d'autre ne l'aurait vu. Les conditions vont donc dans UNE liste. when: - serveur_ops_depot_collections is changed or not serveur_ops_collections_en_place.stat.exists - not ansible_check_mode # LES DEUX SYMLINKS (D-80). `instance` dit QUEL tenant on pilote. Le poste en pose un par # défaut ; l'exploitant le bascule comme sur le poste du mainteneur. # # UN RUNNER DE SITE N'EN A PAS (2026-08-26). Il ne pilote aucun écosystème — il matérialise # le terrain — et son propre inventaire est DYNAMIQUE (`site_inventaire.py`). Son plan # déclare donc `serveur_ops_instance: ""`, et c'est une réponse, pas un oubli. # # Sans la garde ci-dessous, la chaîne rendait `{{ racine }}/` + `""` : le lien `instance` # pointait sur `/opt/setops/` lui-même — un répertoire qui n'est l'écosystème de personne. # Mesuré le 2026-08-26 : `instance -> /opt/setops/`. Un lien qui existe et ne désigne rien # est pire qu'un lien absent : tout ce qui le suit croit avoir trouvé une instance. - name: Désigner l'instance pilotée par défaut ansible.builtin.file: src: "{{ serveur_ops_racine }}/{{ serveur_ops_instance }}" dest: >- {{ serveur_ops_racine }}/{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | first).dest }}/instance state: link owner: "{{ serveur_ops_utilisateur }}" group: "{{ serveur_ops_utilisateur }}" force: true when: - serveur_ops_instance | default('') | trim | length > 0 - not ansible_check_mode # Et s'il n'y en a pas, le lien ne doit pas SURVIVRE d'un passage precedent : un runner # qu'on reconfigure en runner de site garderait sinon un lien perime vers un tenant. - name: Retirer le lien `instance` quand le poste ne pilote aucun écosystème ansible.builtin.file: path: >- {{ serveur_ops_racine }}/{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | first).dest }}/instance state: absent when: - serveur_ops_instance | default('') | trim | length == 0 - not ansible_check_mode # LE SECOND SYMLINK. Sans lui, le poste sait configurer des machines qui existent ; il ne # sait pas en creer, faute de savoir sur quelle fabric les poser. C'est la difference # entre exploiter et engendrer. - name: Désigner la fabric sur laquelle l'écosystème repose ansible.builtin.file: src: "{{ serveur_ops_racine }}/{{ serveur_ops_underlay }}/underlay.yml" dest: >- {{ serveur_ops_racine }}/{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | first).dest }}/underlay.yml state: link owner: "{{ serveur_ops_utilisateur }}" group: "{{ serveur_ops_utilisateur }}" force: true when: - serveur_ops_underlay | length > 0 - not ansible_check_mode # --- LA CLÉ DU POSTE --------------------------------------------------------- # # Le poste se fabrique sa propre paire, distincte de celle du mainteneur. Deux # exploitants, deux clés : on peut révoquer l'un sans couper l'autre — et l'accès du # poste se lit dans les `authorized_keys` de la flotte, au lieu de se confondre avec # celui d'un humain. # # `ssh-keygen` plutôt que `community.crypto.openssh_keypair` : le dépôt ne requiert que # `community.postgresql` et `community.general`, et fabriquer une paire de clés ne # justifie pas d'imposer une collection de plus à tout écosystème qui déploie un poste. - name: Assurer le répertoire SSH du poste ansible.builtin.file: path: "{{ serveur_ops_racine }}/.ssh" state: directory owner: "{{ serveur_ops_utilisateur }}" group: "{{ serveur_ops_utilisateur }}" mode: "0700" - name: Fabriquer la clé SSH du poste ansible.builtin.command: cmd: >- ssh-keygen -t {{ serveur_ops_cle_type }} -N '' -C "setops@{{ inventory_hostname }}.{{ domaine_interne }}" -f {{ serveur_ops_racine }}/.ssh/id_{{ serveur_ops_cle_type }} creates: "{{ serveur_ops_racine }}/.ssh/id_{{ serveur_ops_cle_type }}" become: true become_user: "{{ serveur_ops_utilisateur }}" when: - serveur_ops_cle_generer | bool - not ansible_check_mode register: serveur_ops_cle - name: Relire la clé publique du poste ansible.builtin.slurp: src: "{{ serveur_ops_racine }}/.ssh/id_{{ serveur_ops_cle_type }}.pub" register: serveur_ops_pub when: - serveur_ops_cle_generer | bool - not ansible_check_mode # ÉCRIRE, PUIS RELIRE (D-68) — et rendre le résultat EXPLOITABLE. Cette clé ne sert à # rien tant qu'elle n'est pas autorisée sur la flotte : on l'affiche pour que l'exploitant # la reprenne dans les intrants, au lieu de la laisser dormir sur le disque. - name: La clé publique à autoriser sur la flotte ansible.builtin.debug: msg: - "Clé publique du poste d'exploitation — à ajouter aux intrants SSH du plan :" - "{{ serveur_ops_pub.content | b64decode | trim }}" when: - serveur_ops_cle_generer | bool - not ansible_check_mode - name: Déposer le mode d'emploi sur le poste ansible.builtin.template: src: LISEZ-MOI.md.j2 dest: "{{ serveur_ops_racine }}/LISEZ-MOI.md" owner: "{{ serveur_ops_utilisateur }}" group: "{{ serveur_ops_utilisateur }}" mode: "0644" # --- Sonde de supervision (docs/supervision-conception.md) -------------------------- # Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la # fait tourner et pousse le verdict, sans savoir ce qu'elle mesure. - name: Assurer le repertoire des sondes de supervision ansible.builtin.file: path: /usr/local/lib/setops/sondes state: directory owner: root group: root mode: "0755" - name: Deposer la sonde « runner » ansible.builtin.template: src: sonde-runner.sh.j2 dest: /usr/local/lib/setops/sondes/runner.sh owner: root group: root mode: "0750"