/var/lib/setops etait tenu par quatre roles (0750 contre 0755) : apt-cacher-ng ne le traversait plus, le depot rendait 403 et chaque runner reconstruit allait chercher 500 Mo sur Internet. /etc/setops (0700 contre 0755) et /srv/restic au site portaient le meme desaccord. test_repertoires_partages.py le refuse. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
245 lines
11 KiB
YAML
245 lines
11 KiB
YAML
---
|
|
- name: Assurer le repertoire des trousseaux apt
|
|
ansible.builtin.file:
|
|
path: /etc/apt/keyrings
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
# Recuperee UNE FOIS. Sans garde, chaque deploiement recontactait le serveur du
|
|
# fournisseur : cinq cles x quatorze hotes = soixante-dix allers-retours externes pour
|
|
# des cles deja installees, et autant d'occasions qu'un tiers lent fasse tomber le
|
|
# deploiement. Arbitrage rendu le 2026-08-09 : une plateforme souveraine ne depend pas
|
|
# de six serveurs etrangers pour redeployer ce qu'elle possede deja.
|
|
#
|
|
# CONSEQUENCE ASSUMEE : une rotation de cle amont n'est plus recuperee toute seule. Elle
|
|
# ne passe pas inapercue pour autant — `apt` refuse alors le depot, bruyamment. Pour
|
|
# forcer le rafraichissement : supprimer le fichier et rejouer le role.
|
|
# UN FICHIER VIDE EXISTE (mesure du 2026-09-10).
|
|
#
|
|
# Cette garde demandait « ce fichier est-il la ? ». Un telechargement interrompu — un 503,
|
|
# un delai depasse, une coupure — laisse un fichier de ZERO octet, qui existe. La tache
|
|
# est donc sautee a tous les passages suivants, et la machine garde une ressource morte
|
|
# pour toujours, en silence.
|
|
#
|
|
# CE QUE CA A COUTE : `infra-mail-01` a garde une cle smallstep de 0 octet apres une
|
|
# epreuve hors ligne. Treize machines portaient 1022 octets, elle portait le vide :
|
|
#
|
|
# E: Le depot http://packages.smallstep.com/... n'est pas signe.
|
|
#
|
|
# Le meme jour, l'index `dists/trixie` du cache du site etait tombe de 140 416 a 4 332
|
|
# octets — meme famille, autre endroit. Un telechargement partiel ne se signale pas : il
|
|
# se fait passer pour un succes.
|
|
#
|
|
# On demande donc « est-il ENTIER ? » — a defaut de pouvoir demander « est-il JUSTE ? »,
|
|
# ce qui exigerait une empreinte de reference que l'amont ne publie pas toujours.
|
|
- name: Cette ressource est-elle deja recuperee ? (Telecharger la cle de signature Gr)
|
|
ansible.builtin.stat:
|
|
path: "{{ client_journal_depot_cle_fichier }}"
|
|
register: telecharger_la_cle_de_signature_grafana_present
|
|
|
|
# ... ET IL FAUT LE RETIRER, PAS SEULEMENT LE REDEMANDER (mesure du 2026-09-10).
|
|
#
|
|
# Laisser la garde autoriser la tache ne suffit PAS. `get_url` sur une destination qui
|
|
# existe emet une requete CONDITIONNELLE : l'amont repond « non modifie », le module rend
|
|
# `ok`, et le fichier corrompu reste en place. Mesure : la tache s'executait bien
|
|
# (`ok: [infra-mail-01]`), et la cle faisait toujours 0 octet au passage suivant. Un
|
|
# `force: yes` manuel l'a reparee en une seconde.
|
|
#
|
|
# On efface donc la ruine avant de reconstruire. `state: absent` ne mord que sur un
|
|
# fichier vide : une cle valide n'est jamais retiree, et la doctrine « on ne retelecharge
|
|
# pas ce qu'on possede deja » reste intacte.
|
|
- name: Retirer une ressource VIDE avant de la redemander
|
|
ansible.builtin.file:
|
|
path: "{{ client_journal_depot_cle_fichier }}"
|
|
state: absent
|
|
when:
|
|
- telecharger_la_cle_de_signature_grafana_present.stat.exists
|
|
- (telecharger_la_cle_de_signature_grafana_present.stat.size | default(0)) == 0
|
|
|
|
- name: Telecharger la cle de signature Grafana
|
|
when: not (telecharger_la_cle_de_signature_grafana_present.stat.exists and telecharger_la_cle_de_signature_grafana_present.stat.size | default(0) > 0)
|
|
# `force` PARCE QU'UNE REPRISE CONDITIONNELLE NE REPREND RIEN (2026-09-12).
|
|
#
|
|
# Sans lui, `get_url` envoie un `If-Modified-Since` des que le fichier existe. Le cache
|
|
# repond `304 Not Modified`, la tache reussit sans rien ecrire, et la boucle `until`
|
|
# — qui exige une taille non nulle — retente cinq fois contre un serveur qui repondra
|
|
# toujours 304 :
|
|
#
|
|
# HTTP Error 304: Not Modified size: 0 attempts: 5
|
|
#
|
|
# C'est la boucle de reprise qui se bat contre elle-meme. Le `when:` ci-dessus garantit
|
|
# deja qu'on ne retelecharge pas une cle valide ; `force` ne concerne donc que le cas
|
|
# ou l'on a DECIDE d'aller chercher.
|
|
ansible.builtin.get_url:
|
|
force: true
|
|
url: "{{ client_journal_depot_cle_url }}"
|
|
dest: "{{ client_journal_depot_cle_fichier }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
# LA CLE PASSE PAR LE CACHE, ELLE AUSSI (mesure du 2026-09-10).
|
|
#
|
|
# `get_url` ignore la configuration d'apt : le mandataire pose dans
|
|
# `/etc/apt/apt.conf.d/` ne vaut que pour apt. Les cles de signature sortaient donc
|
|
# TOUJOURS en direct, meme apres que tous les depots soient passes par le cache — un
|
|
# trou reste ouvert derriere une porte qu'on croyait fermee.
|
|
#
|
|
# Et ce n'est pas theorique : depuis `collab-01`, la route directe vers
|
|
# `www.collaboraoffice.com` EXPIRE, quand le cache l'atteint sans peine.
|
|
#
|
|
# en direct grafana 200 collabora TIMEOUT
|
|
# via le cache collabora 200
|
|
#
|
|
# Le meme geste corrige donc les deux : plus rien ne sort, et la machine qui n'avait
|
|
# pas de route en trouve une.
|
|
#
|
|
# DEGRADE, JAMAIS DEVINE : sans cache d'amorcage declare, la valeur est VIDE et
|
|
# `get_url` sort en direct comme avant. On n'invente pas un mandataire.
|
|
environment:
|
|
http_proxy: >-
|
|
{{ ('http://' ~ artefacts_amorcage) if (artefacts_amorcage | default('') | string | length > 0) else '' }}
|
|
https_proxy: ""
|
|
register: client_journal_cle_depot
|
|
retries: 5
|
|
delay: 6
|
|
until: client_journal_cle_depot is succeeded and (client_journal_cle_depot.size | default(0)) > 0
|
|
|
|
# `apt_repository` AJOUTE, IL NE REMPLACE PAS (mesure du 2026-09-10).
|
|
#
|
|
# Passer une source de `https://` a `http://` y ecrit une SECONDE ligne au lieu de
|
|
# corriger la premiere. apt interroge alors les deux — et l'ancienne continue de sortir
|
|
# en direct sur Internet, ce que le passage au cache visait justement a supprimer. Le
|
|
# symptome le disait, sur les quatorze machines :
|
|
#
|
|
# W: La cible Packages est specifiee plusieurs fois dans grafana.list:1 et :2
|
|
#
|
|
# On retire donc explicitement la forme precedente. `state: absent` ne mord que si la
|
|
# ligne existe : sur une machine neuve, cette tache ne fait rien.
|
|
- name: Mesurer la cle de signature Grafana qui vient d'etre posee
|
|
ansible.builtin.stat:
|
|
path: "{{ client_journal_depot_cle_fichier }}"
|
|
register: cle_grafana_posee
|
|
|
|
- name: Exiger une cle NON VIDE avant de s'en servir
|
|
# UNE REUSSITE N'EST PAS UN CONTENU (mesure du 2026-09-11). Sur une reconstruction a
|
|
# froid, `get_url` a rendu 0 octet sans erreur sur UNE machine des quatorze : le cache
|
|
# a servi un 200 au corps vide. La garde « retirer une ressource VIDE » ne mord qu'au
|
|
# passage SUIVANT, quand le fichier existe deja — au premier, elle n'a rien a retirer.
|
|
#
|
|
# Le defaut ne se lisait pas ici. Il se lisait deux cents lignes plus loin, dans un
|
|
# `apt` qui accusait la SIGNATURE : « Missing key ..., which is needed to verify
|
|
# signature ». Un message qui envoie chercher une cle revoquee chez le fournisseur,
|
|
# alors que le fichier local fait zero octet.
|
|
ansible.builtin.assert:
|
|
that:
|
|
- (cle_grafana_posee.stat.size | default(0)) > 0
|
|
fail_msg: >-
|
|
La cle de signature Grafana ({{ client_journal_depot_cle_fichier }}) fait 0 octet.
|
|
Le depot a repondu sans corps — cache vide, ou amont injoignable. apt echouera
|
|
plus loin en accusant la signature : c'est ICI qu'il faut regarder.
|
|
|
|
- name: Retirer la forme HTTPS du depot (elle contournait le cache)
|
|
ansible.builtin.apt_repository:
|
|
repo: "{{ client_journal_depot_source | replace('http://', 'https://') }}"
|
|
filename: grafana
|
|
state: absent
|
|
when: client_journal_depot_schema == 'http'
|
|
|
|
- name: Ajouter le depot apt Grafana
|
|
ansible.builtin.apt_repository:
|
|
repo: "{{ client_journal_depot_source }}"
|
|
filename: grafana
|
|
state: present
|
|
|
|
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT POUR LE RESTE (2026-09-03).
|
|
#
|
|
# Les depots tiers sont en HTTPS, et `client_artefacts` pose
|
|
# `Acquire::https::Proxy "DIRECT"` — ils CONTOURNENT donc le cache du site et sortent sur
|
|
# Internet a chaque construction de VM. `make cacher-paquets` les tire une fois, versions
|
|
# epinglees et empreintes verifiees ; ce role les depose depuis ce cache.
|
|
- name: Poser les paquets tiers depuis le cache du controleur
|
|
ansible.builtin.include_role:
|
|
name: paquets_tiers
|
|
vars:
|
|
paquets_tiers_noms: "{{ client_journal_paquets }}"
|
|
|
|
- name: Installer Grafana Alloy
|
|
ansible.builtin.apt:
|
|
# CE QUE LE CACHE N'A PAS FOURNI, ET RIEN D'AUTRE. Les dependances Debian passent
|
|
# par le cache du site en HTTP ; seuls les paquets tiers non caches exigent Internet.
|
|
# Reinstaller ce que le cache vient de poser ferait un `update_cache` inutile — et
|
|
# hors ligne, il echouerait APRES un travail deja fait.
|
|
name: "{{ client_journal_paquets | difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
|
|
state: present
|
|
update_cache: true
|
|
|
|
when: (client_journal_paquets
|
|
| difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0
|
|
- name: Autoriser Alloy a lire le journal systeme
|
|
ansible.builtin.user:
|
|
name: "{{ client_journal_utilisateur }}"
|
|
groups: systemd-journal
|
|
append: true
|
|
notify: Redemarrer alloy
|
|
|
|
- name: Deployer la configuration Alloy
|
|
ansible.builtin.template:
|
|
src: config.alloy.j2
|
|
dest: "{{ client_journal_config }}"
|
|
owner: root
|
|
group: "{{ client_journal_utilisateur }}"
|
|
mode: "0640"
|
|
notify: Redemarrer alloy
|
|
|
|
# Le port d'ecoute est IMPOSE ici plutot que subi : voir defaults/main.yml.
|
|
- name: Imposer le port de l interface locale d Alloy
|
|
ansible.builtin.lineinfile:
|
|
path: /etc/default/alloy
|
|
regexp: "^CUSTOM_ARGS="
|
|
line: >-
|
|
CUSTOM_ARGS="--server.http.listen-addr={{ client_journal_http_adresse }}:{{ client_journal_http_port }}"
|
|
create: false
|
|
notify: Redemarrer alloy
|
|
|
|
- name: Activer et demarrer Alloy
|
|
ansible.builtin.systemd:
|
|
name: "{{ client_journal_service }}"
|
|
enabled: true
|
|
state: started
|
|
|
|
# LA SONDE VIT AVEC LE ROLE QU'ELLE MESURE (docs/supervision-conception.md). Le porteur
|
|
# `client_sante` fait tourner tout ce qui vit dans ce repertoire ; il n'a pas a savoir ce
|
|
# que celle-ci 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"
|
|
|
|
# La sonde compare le compteur de lignes jetees a celui du passage precedent : il lui
|
|
# faut un endroit ou s'en souvenir. `/var/lib`, parce que ce n'est pas un cache — le
|
|
# perdre ferait passer une cicatrice ancienne pour une perte en cours.
|
|
- name: Assurer le repertoire d etat des sondes
|
|
ansible.builtin.file:
|
|
# 0755, COMME PARTOUT AILLEURS (2026-10-01) : quatre roles tenaient ce repertoire, trois
|
|
# en 0750 et `serveur_artefacts` en 0755 — apt-cacher-ng doit le traverser pour servir
|
|
# le depot de binaires du site. Le dernier deploye gagnait : au site, le 2026-10-01, il
|
|
# rendait 403, et chaque runner allait chercher 500 Mo sur Internet. Le repertoire ne
|
|
# tient que des etats de sondes, deja lisibles. Garde : test_repertoires_partages.py.
|
|
path: /var/lib/setops
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Deposer la sonde d expedition des journaux
|
|
ansible.builtin.template:
|
|
src: sonde-journaux.sh.j2
|
|
dest: /usr/local/lib/setops/sondes/journaux.sh
|
|
owner: root
|
|
group: root
|
|
mode: "0750"
|