--- - 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: path: /var/lib/setops state: directory owner: root group: root mode: "0750" - 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"