Set-OPS-Public/roles/serveur_ops/tasks/main.yml
Daniel Allaire d94fca490f
Some checks are pending
verifier / verifier (push) Waiting to run
poste : le second symlink, celui qui separe exploiter d'engendrer
Le poste ne clonait que le moteur et son plan : il savait configurer des machines
existantes, pas en creer. Placer une VM demande de savoir sur quelle fabric la
poser.

Patient 0 n'a pas d'underlay a lui -- il est TENANT de SITE-Chezlepro. Son poste
porte donc les deux symlinks de D-80, et quatre depots : le moteur, son plan, la
fabric qui le porte, les modeles.

underlay.vault.yml reste hors du genome : le poste lit la CARTE du monde
physique, jamais ses cles. Mesure depuis ops-01 : `make instancier` rend un diff
vide sans aucun secret, `make underlay-plan` refuse faute de voute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:24:28 -04:00

243 lines
9.7 KiB
YAML

---
- name: Exiger les intrants du poste d'exploitation
ansible.builtin.assert:
that:
- domaine_interne | default('') | length > 0
- serveur_ops_depots | length > 0
fail_msg: >-
serveur_ops exige `domaine_interne` et au moins un dépôt dans
`serveur_ops_depots` (le moteur au minimum).
- 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 }}"
- name: Installer Ansible dans l'environnement isolé
ansible.builtin.pip:
name:
- "{{ serveur_ops_ansible }}"
- "pyyaml"
virtualenv: "{{ serveur_ops_venv }}"
state: present
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
# --- 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` (racine step-ca dans le magasin système). Sans
# lui, `git clone` refuse le certificat de la forge — 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
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.
- 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
-p {{ serveur_ops_racine }}/.ansible/collections
chdir: "{{ serveur_ops_racine }}/.collections-hors-ligne"
creates: "{{ serveur_ops_racine }}/.ansible/collections/ansible_collections/community/general"
become: true
become_user: "{{ serveur_ops_utilisateur }}"
when: 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.
- 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: 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"