--- # LA CONDITION EST ECRITE D'UN SEUL TENANT, ET C'EST DELIBERE. # # `scripts/verifier_intrants.py` lit les `assert` de la forme `var | length > 0` comme un # CONTRAT : l'instance doit fournir cette valeur. Et il ignore volontairement les `when:` # — un intrant exige sous condition reste un intrant exige. # # Ecrite en deux morceaux (`that: var | length > 0` + `when: actif`), cette garde # promettait donc un secret pour une console FERMEE, et P32 le refusait a juste titre. # Ecrite en une implication, elle dit ce qu'elle veut vraiment dire : SI la console est # ouverte, ALORS un mot de passe. Ne pas la « simplifier » en la recoupant en deux. - name: Exiger un secret si — et seulement si — la console d'administration est ouverte ansible.builtin.assert: that: - not (serveur_collabora_admin_actif | bool) or (serveur_collabora_admin_mot_de_passe | length > 0) fail_msg: >- La console d'administration est ouverte mais son mot de passe est vide. Le renseigner depuis la voûte de l'instance, ou refermer la console (`serveur_collabora_admin_actif: false`). - name: Assurer le repertoire des trousseaux apt ansible.builtin.file: path: /etc/apt/keyrings state: directory owner: root group: root mode: "0755" # Récupérée UNE FOIS, comme la clé Smallstep : une plateforme souveraine ne recontacte pas # un serveur étranger à chaque déploiement pour une clé déjà installée. Une rotation amont # ne passe pas inaperçue pour autant — `apt` refuse alors le dépôt, bruyamment. # 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 ? (cle Collabora) ansible.builtin.stat: path: "{{ serveur_collabora_depot_cle_fichier }}" register: serveur_collabora_cle_presente # ... 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: "{{ serveur_collabora_depot_cle_fichier }}" state: absent when: - serveur_collabora_cle_presente.stat.exists - (serveur_collabora_cle_presente.stat.size | default(0)) == 0 - name: Telecharger la cle de signature Collabora when: not (serveur_collabora_cle_presente.stat.exists and serveur_collabora_cle_presente.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: "{{ serveur_collabora_depot_cle_url }}" dest: "{{ serveur_collabora_depot_cle_fichier }}" owner: root group: root mode: "0644" timeout: 30 register: serveur_collabora_cle retries: 5 delay: 6 until: serveur_collabora_cle is succeeded and (serveur_collabora_cle.size | default(0)) > 0 # 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: "" # DEPOT PLAT : `Suites: ./`, aucun composant. Une source classique ne le trouverait pas. - name: Mesurer la cle de signature Collabora qui vient d'etre posee ansible.builtin.stat: path: "{{ serveur_collabora_depot_cle_fichier }}" register: cle_collabora_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_collabora_posee.stat.size | default(0)) > 0 fail_msg: >- La cle de signature Collabora ({{ serveur_collabora_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: Ajouter le depot apt Collabora ansible.builtin.template: src: collabora.sources.j2 dest: /etc/apt/sources.list.d/collabora.sources owner: root group: root mode: "0644" - name: Installer Collabora Online when: not ansible_check_mode # dry-run : le depot n'est pas reellement ajoute ansible.builtin.apt: name: "{{ serveur_collabora_paquets }}" state: present update_cache: true - name: Assurer le repertoire du fragment systemd ansible.builtin.file: path: "{{ serveur_collabora_fragment | dirname }}" state: directory owner: root group: root mode: "0755" # LA CONFIGURATION DE SET-OPS TIENT EN UN FICHIER, et le coolwsd.xml du paquet reste # intact. Voir defaults/main.yml pour le raisonnement. - name: Deployer la surcharge Set-OPS ansible.builtin.template: src: setops.conf.j2 dest: "{{ serveur_collabora_fragment }}" owner: root group: root mode: "0640" notify: Redemarrer Collabora - name: Appliquer les redemarrages avant de verifier ansible.builtin.meta: flush_handlers - name: Activer et demarrer Collabora when: not ansible_check_mode ansible.builtin.systemd: name: "{{ serveur_collabora_service }}" enabled: true state: started daemon_reload: true # ECRIRE, PUIS RELIRE (D-68). Un service « active » qui ne repond pas a `capabilities` # est un vert sur un perimetre vide : Nextcloud le decouvrirait a la premiere ouverture # de document, c'est-a-dire devant un usager. - name: Collabora repond-il vraiment ? when: not ansible_check_mode ansible.builtin.uri: url: "http://127.0.0.1:{{ serveur_collabora_port }}/hosting/capabilities" status_code: [200] timeout: 20 register: serveur_collabora_capacites retries: 12 delay: 5 until: serveur_collabora_capacites is succeeded # LE ROLE QUI POSSEDE LA VERITE DEPOSE SA PROPRE SONDE. `client_sante` la fait tourner et # pousse le resultat ; les greffons standard, eux, sont poses par le porteur. - 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 « edition » ansible.builtin.template: src: sonde-edition.sh.j2 dest: /usr/local/lib/setops/sondes/edition.sh owner: root group: root mode: "0750"