From 70e57076e8ea71ba0ce9a9c89437e3adc07679df Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Mon, 24 Aug 2026 16:23:35 -0400 Subject: [PATCH] collabora : en natif, la derniere exception conteneurisee tombe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le role lancait collabora/code dans Docker -- seule exception de la flotte au principe « logiciel libre en natif ». Desormais : paquet coolwsd du depot amont, systemd, derriere l'edge nginx. community.docker est retiree de requirements : plus aucun role n'a besoin de Docker. Eprouve avant d'ecrire, sur une Debian 13.6 reelle et sans rien installer : apt-get install --simulate coolwsd resout jusqu'a « Conf coolwsd (26.04.3.1-1) », libgcc1 est fourni par libgcc-s1, et le depot CODE-deb est PLAT. La configuration passe par un fragment systemd (--o:) : le coolwsd.xml livre, 439 lignes commentees, reste intact. Trois outils du harnais ont pese. voute.py lisait les COMMENTAIRES : documenter le nom d'une clef suffisait a l'exiger -- corrige, controle negatif fait. Et verifier_intrants avait raison : une garde ecrite en deux morceaux promettait un secret pour une console fermee ; reecrite en implication, elle dit le vrai contrat. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 59 +++++++++ docs/audit/preuve-2026-08-24.md | 2 +- docs/catalogue-services.md | 2 +- requirements.yml | 11 +- roles/serveur_collabora/README.md | 84 +++++++++--- roles/serveur_collabora/defaults/main.yml | 97 +++++++++++--- roles/serveur_collabora/handlers/main.yml | 7 + roles/serveur_collabora/tasks/main.yml | 120 ++++++++++++++---- .../templates/collabora.sources.j2 | 8 ++ .../templates/setops.conf.j2 | 17 +++ scripts/voute.py | 22 +++- 11 files changed, 356 insertions(+), 73 deletions(-) create mode 100644 roles/serveur_collabora/handlers/main.yml create mode 100644 roles/serveur_collabora/templates/collabora.sources.j2 create mode 100644 roles/serveur_collabora/templates/setops.conf.j2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 8da2abe..26da73c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,64 @@ # CHANGELOG — Set-OPS +## 2026-08-24 — Collabora en natif : la dernière exception conteneurisée tombe + +`serveur_collabora` lançait `collabora/code` dans Docker — **seule exception** de la +flotte au principe fondateur : logiciel libre en natif, systemd + paquets + nginx. Elle +traînait une dépendance entière, `community.docker`, qui n'était même pas déclarée dans +`requirements.yml` et donc absente de tout runner. + +**Plus aucun rôle du dépôt n'a besoin de Docker.** La collection est retirée. + +### Éprouver l'outil avant le rôle + +Sur une Debian 13.6 réelle, sans rien installer : + +``` +apt-get install --simulate coolwsd -> résout jusqu'à « Conf coolwsd (26.04.3.1-1) » +libgcc1 (exigé par le paquet) -> fourni par libgcc-s1 sur trixie +le dépôt CODE-deb -> PLAT : Packages et Release à la racine, pas de dists/ +``` + +Le paquet livre aussi `/etc/nginx/snippets/coolwsd.conf` et un profil AppArmor — deux +choses que cette flotte sait exploiter, contrairement à une image opaque. + +### La configuration par surcharge + +Le `coolwsd.xml` livré fait **439 lignes richement commentées**. Le remplacer par un +gabarit maison obligerait à suivre son évolution amont et ferait perdre ses explications. +`coolwsd` acceptant des surcharges `--o:=`, le rôle pose un **fragment +systemd** : la configuration de Set-OPS tient en un fichier, celle du paquet reste intacte. + +### La console d'administration est fermée + +Elle expose un formulaire hors de tout SSO, avec un secret à créer, faire tourner et +surveiller — pour une fonction dont personne n'a besoin. Même raisonnement que +`serveur_forgejo_connexion_locale`. + +### Trois outils du harnais ont eu raison, et l'un avait tort + +**`voute.py` lisait les commentaires.** Expliquer en commentaire « pour ouvrir cette +option, fournir `vault_x` » suffisait à rendre `vault_x` obligatoire dans le gabarit ET +dans la voûte réelle. Documenter le nom d'une clef ne doit pas l'exiger : le scanner +ignore désormais ce qui suit un `#`. Contrôle négatif fait — une référence réelle, dans du +code, reste exigée. + +**Le message d'erreur d'un `assert` comptait comme une référence.** Il nommait la clef de +voûte ; il nomme maintenant la variable que l'exploitant règle, ce qui est plus utile de +toute façon. + +**Et `verifier_intrants.py` avait raison.** Un `assert` de la forme `var | length > 0` +déclare un **contrat** — et l'outil ignore délibérément les `when:`, parce qu'un intrant +exigé sous condition reste un intrant exigé. Écrite en deux morceaux, ma garde promettait +un secret pour une console fermée. Réécrite en **implication** — *si ouverte, alors un mot +de passe* — elle dit ce qu'elle veut vraiment dire. + +### Ce qui n'est pas prouvé + +L'**installabilité** est établie. Le **fonctionnement** ne l'est pas : aucun écosystème de +la flotte ne fait tourner Collabora aujourd'hui. La première mise en service devra +vérifier l'édition d'un document de bout en bout, depuis Nextcloud. + ## 2026-08-24 — La clé du runner, et trois silences fermés en chemin Le poste d'exploitation fabriquait sa propre paire SSH depuis son premier déploiement, et diff --git a/docs/audit/preuve-2026-08-24.md b/docs/audit/preuve-2026-08-24.md index 05caed1..91fdbb3 100644 --- a/docs/audit/preuve-2026-08-24.md +++ b/docs/audit/preuve-2026-08-24.md @@ -30,7 +30,7 @@ | P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. | | P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 15 hotes, 32 groupes (inventaire dechiffre et parse). | | P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. | -| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 24 secret(s) exige(s), tous presents. Voute reelle : 25 cle(s), aucun manque. | +| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. Voute reelle : 25 cle(s), aucun manque. | | P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. | | P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. | | P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. | diff --git a/docs/catalogue-services.md b/docs/catalogue-services.md index 8cd4bbb..88fb49c 100644 --- a/docs/catalogue-services.md +++ b/docs/catalogue-services.md @@ -46,7 +46,7 @@ Ce que la reconstruction couvre, par capacité : | Observabilité | `serveur_prometheus`, `serveur_loki`, `serveur_grafana` | métriques, journaux, tableaux sous SSO | | Supervision | `serveur_icinga`, `serveur_icingaweb2` | Icinga 2 + IcingaDB + Web 2 + BPM, au SSO | | Forge | `serveur_forgejo` | Git + PostgreSQL + SSO OIDC | -| Collaboration | `serveur_nextcloud`, `serveur_collabora` | déployés par la reconstruction, base et client OIDC dérivés du plan — **usage** (dépôt de fichier, édition partagée) non consigné comme preuve | +| Collaboration | `serveur_nextcloud`, `serveur_collabora` (**natif**, plus de conteneur) | déployés par la reconstruction, base et client OIDC dérivés du plan — **usage** (dépôt de fichier, édition partagée) non consigné comme preuve | | Plateforme webapp | `serveur_web_frontal`, `serveur_web_dorsal` | sites statiques et webapps natives (venv + systemd + nginx), **zéro conteneur** | | Sauvegardes | `serveur_backup`, `client_backup` | restic hors-nœud, **restauration éprouvée** (2026-08-12 : la donnée revient) | | Exploitation | `serveur_ops` | le poste depuis lequel l'ecosysteme se reconstruit : Ansible epingle, genome clone depuis **sa propre forge**, cle SSH propre — **sans** la voute ni son mot de passe | diff --git a/requirements.yml b/requirements.yml index 740c21c..c47b682 100644 --- a/requirements.yml +++ b/requirements.yml @@ -13,11 +13,6 @@ collections: # `serveur_backup` : ansible.posix.authorized_key. Utilisee depuis longtemps, jamais # declaree — donc absente du runner, qui n'aurait pas pu deployer les sauvegardes. - name: ansible.posix - # `serveur_collabora` : community.docker.docker_container. - # - # A TRANCHER. La doctrine du depot dit « zero conteneur » pour la plateforme webapp, et - # le principe general est le logiciel libre en natif. Collabora fait exception, en - # conteneur Docker. On declare la dependance parce qu'elle EXISTE — mieux vaut une - # contradiction visible qu'un role qui echoue en silence sur un runner — mais la - # question reste ouverte : Collabora en natif, ou l'exception assumee et documentee ? - - name: community.docker + # `community.docker` a ete RETIREE le 2026-08-24 : `serveur_collabora` etait la seule + # exception conteneurisee de la flotte, et il tourne desormais en natif (paquet coolwsd + # du depot amont Collabora). Plus aucun role n'a besoin de Docker. diff --git a/roles/serveur_collabora/README.md b/roles/serveur_collabora/README.md index c941364..949cebb 100644 --- a/roles/serveur_collabora/README.md +++ b/roles/serveur_collabora/README.md @@ -1,27 +1,69 @@ # serveur_collabora -Serveur d'édition de documents **Collabora Online (CODE)**, protocole WOPI, consommé -par Nextcloud (`richdocuments`). **Codifié depuis le spike du 2026-07-05** (Docker, prouvé). +**Collabora Online, en natif** — paquet `coolwsd`, systemd, derrière l'edge nginx. Aucun +conteneur. -## Ce que le rôle fait -- Installe Docker + lance le conteneur `collabora/code` (`restart: always`) ; -- publie sur `127.0.0.1:9980` (Nextcloud co-localisé) **ET** l'IP LAN `:9980` (l'edge) — gotcha spike : bind localhost seul ⇒ **502** depuis l'edge ; -- `aliasgroup1` = le domaine Nextcloud (`cloud.`) — Collabora n'accepte le WOPI que de cet hôte ; -- `ssl.enable=false` + `ssl.termination=true` (TLS terminé à l'edge) ; -- attend la santé (`/hosting/capabilities` = 200). +## Pourquoi ce rôle a été réécrit -## Dépendances / exposition (à fournir) -- **Vhost edge WebSocket-aware** : `bureau.` → ce nœud:9980 avec `map $http_upgrade` - + en-têtes `Upgrade`/`Connection` + `proxy_read_timeout` long. **Le vhost générique ne suffit pas** - (l'édition live échoue sans WebSocket) ; -- FQDN `bureau.` dans les SAN du cert edge ; -- port 9980 à firewaller (voir `meta/flux.yml` : ingress depuis l'edge seulement). +Il lançait `collabora/code` dans Docker. C'était la **seule exception conteneurisée** de +la flotte, et elle contredisait le principe fondateur : logiciel libre en natif, systemd + +paquets + nginx. Elle traînait en plus une dépendance entière — `community.docker` — qui +n'était même pas déclarée dans `requirements.yml`, donc absente de tout runner. -## Docker vs natif -Le spike a prouvé **Docker** → on codifie Docker. Le **CODE natif** (paquets Collabora, -`loolwsd`) serait plus « Set-OPS » (natif, pas de moteur Docker) mais exigerait **son propre -spike**. Décision à trancher. +Depuis la réécriture, **plus aucun rôle du dépôt n'a besoin de Docker**. -## Robustesse -- **Épingler `serveur_collabora_tag`** en prod (pas de `:latest` surprise) ; -- surveillance Icinga sur `/hosting/capabilities`. +## Ce qui a été éprouvé avant d'écrire + +Doctrine du dépôt : *éprouver l'outil avant le rôle*. Sur une Debian 13.6 réelle, sans +rien installer : + +``` +apt-get install --simulate coolwsd -> résout jusqu'à « Conf coolwsd (26.04.3.1-1) » +libgcc1 (exigé par le paquet) -> fourni par libgcc-s1 sur trixie +le dépôt CODE-deb -> PLAT : Packages et Release à la racine, pas de dists/ +``` + +Le paquet livre aussi `/etc/nginx/snippets/coolwsd.conf` et un profil AppArmor — deux +choses que cette flotte sait exploiter, contrairement à une image opaque. + +## La configuration : par surcharge, pas par réécriture + +Le `coolwsd.xml` livré fait **439 lignes richement commentées**. Le remplacer par un +gabarit maison obligerait à suivre son évolution à chaque version amont, et ferait perdre +les commentaires qui expliquent chaque réglage. + +`coolwsd` accepte des surcharges `--o:=`. Le rôle pose donc un **fragment +systemd** : la configuration de Set-OPS tient en un fichier lisible, celle du paquet reste +intacte, et `apt purge` rend la machine à son état d'origine. + +## La console d'administration est FERMÉE + +Elle expose un formulaire identifiant/mot de passe hors de tout SSO, avec un secret à +créer, faire tourner et surveiller — pour une fonction dont personne n'a besoin au +quotidien. Même raisonnement que `serveur_forgejo_connexion_locale`. + +L'ouvrir (`serveur_collabora_admin_actif: true`) exige alors `vault_collabora_admin` en +voûte, et de l'ajouter au gabarit de l'instance. + +## Variables principales + +| Variable | Défaut | Rôle | +|---|---|---| +| `serveur_collabora_port` | `9980` | écoute, derrière l'edge | +| `serveur_collabora_server_name` | `bureau.` | nom externe — vide, coolwsd le déduit mal derrière un mandataire | +| `serveur_collabora_domaine_nextcloud` | `cloud.` | l'hôte WOPI autorisé | +| `serveur_collabora_ssl_termination` | `true` | TLS terminé à l'edge, comme le reste de la flotte | +| `serveur_collabora_admin_actif` | `false` | console d'administration | + +## Ce que ce rôle vérifie + +Il interroge `/hosting/capabilities` après démarrage. Un service « active » qui ne répond +pas là serait découvert par Nextcloud **devant un usager**, à la première ouverture de +document. + +## Ce qui n'est pas encore prouvé + +L'**installabilité** est établie par simulation sur Debian 13.6. Le **fonctionnement** ne +l'est pas : aucun écosystème de la flotte ne fait tourner Collabora aujourd'hui. La +première mise en service devra vérifier l'édition d'un document de bout en bout, depuis +Nextcloud. diff --git a/roles/serveur_collabora/defaults/main.yml b/roles/serveur_collabora/defaults/main.yml index c96bb95..fc7b112 100644 --- a/roles/serveur_collabora/defaults/main.yml +++ b/roles/serveur_collabora/defaults/main.yml @@ -1,22 +1,87 @@ --- -# ========================================================================= -# serveur_collabora — serveur d'édition de documents (Collabora Online / CODE), -# protocole WOPI, consommé par Nextcloud (richdocuments). Codifié depuis le spike -# du 2026-07-05 (Docker, prouvé). Le CODE natif (paquets Collabora) reste une -# option future — nécessiterait son propre spike. -# ========================================================================= -serveur_collabora_conteneur: "collabora" -serveur_collabora_image: "collabora/code" -serveur_collabora_tag: "latest" # À ÉPINGLER en prod (robustesse / pas d'upgrade surprise) +# COLLABORA ONLINE, EN NATIF — plus de conteneur. +# +# Ce rôle lançait `collabora/code` dans Docker, ce qui contredisait le principe fondateur +# du dépôt : logiciel libre **en natif**, systemd + paquets + nginx, zéro conteneur. C'était +# la seule exception de la flotte, et elle traînait une dépendance entière +# (`community.docker`) qui n'était même pas déclarée dans `requirements.yml`. +# +# CE QUI A ÉTÉ ÉPROUVÉ AVANT D'ÉCRIRE CE RÔLE (2026-08-24), sur une Debian 13.6 réelle, +# sans rien installer : +# +# apt-get install --simulate coolwsd -> résout jusqu'à « Conf coolwsd (26.04.3.1-1) » +# libgcc1 (exigé) -> fourni par libgcc-s1 sur trixie +# le dépôt CODE-deb est PLAT -> Packages et Release à la racine, pas de dists/ +# +# Le paquet livre aussi `/etc/nginx/snippets/coolwsd.conf` et un profil AppArmor — deux +# choses que cette flotte sait déjà exploiter, contrairement à une image opaque. +serveur_collabora_paquets: + - coolwsd +serveur_collabora_service: "coolwsd" serveur_collabora_port: 9980 -# L'edge (autre hôte) doit joindre le conteneur → publier sur localhost ET l'IP LAN -# (gotcha spike : bind 127.0.0.1 seul => 502 depuis l'edge). Firewaller via le registre des flux. -serveur_collabora_lan_ip: "{{ ansible_default_ipv4.address | default(ansible_host) }}" -# Domaine Nextcloud autorisé (aliasgroup) — Collabora n'accepte le WOPI que de cet hôte. +# --- LE DÉPÔT AMONT ---------------------------------------------------------- +# +# Dépôt PLAT : `Suites: ./` et pas de composant. Une source classique (`deb … stable +# main`) ne le trouverait pas. +serveur_collabora_depot_uri: "https://www.collaboraoffice.com/repos/CollaboraOnline/CODE-deb" +serveur_collabora_depot_cle_url: "https://www.collaboraoffice.com/downloads/gpg/collaboraonline-release-keyring.gpg" +serveur_collabora_depot_cle_fichier: "/etc/apt/keyrings/collaboraonline.gpg" + +# --- CONFIGURATION : PAR SURCHARGE, PAS PAR RÉÉCRITURE ----------------------- +# +# Le `coolwsd.xml` livré fait 439 lignes richement commentées. Le remplacer par un gabarit +# maison, ce serait s'engager à suivre son évolution à chaque version amont — et perdre +# les commentaires qui expliquent chaque réglage. +# +# `coolwsd` accepte des surcharges `--o:=` sur sa ligne de commande. On pose +# donc un fragment systemd : la configuration de Set-OPS tient en un seul fichier lisible, +# le fichier du paquet reste intact, et `apt purge` rend la machine à son état d'origine. +serveur_collabora_fragment: "/etc/systemd/system/coolwsd.service.d/setops.conf" + +# TLS TERMINÉ À L'EDGE, comme tout le reste de la flotte : `serveur_nginx` présente le +# certificat step-ca, Collabora écoute en clair derrière lui. +serveur_collabora_ssl_enable: false +serveur_collabora_ssl_termination: true + +# Nom externe, tel que le navigateur l'atteint. Vide = déduit de la requête, ce qui échoue +# derrière un mandataire inverse — d'où la valeur explicite. +serveur_collabora_server_name: "bureau.{{ domaine_interne }}" + +# Hôtes WOPI autorisés — qui a le droit de faire éditer un document. +# +# Les défauts du paquet couvrent déjà les plages privées (10/8, 172.16/12, 192.168/16, +# loopback), donc l'adressage dérivé de Set-OPS. On ajoute le FQDN de Nextcloud pour que +# l'autorisation soit LISIBLE dans le plan plutôt que déduite d'un masque. serveur_collabora_domaine_nextcloud: "cloud.{{ domaine_interne }}" -serveur_collabora_aliasgroup: "https://{{ serveur_collabora_domaine_nextcloud | regex_escape }}:443" -# TLS terminé à l'edge : Collabora sert en HTTP derrière le proxy. -serveur_collabora_extra_params: "--o:ssl.enable=false --o:ssl.termination=true" +# --- CONSOLE D'ADMINISTRATION : FERMÉE PAR DÉFAUT ---------------------------- +# +# Elle sert à surveiller les sessions et les documents ouverts. Personne n'en a besoin au +# quotidien, et elle expose un formulaire identifiant/mot de passe de plus — hors de tout +# SSO, avec un secret à créer, à faire tourner et à surveiller. +# +# Même raisonnement que `serveur_forgejo_connexion_locale` : une porte ouverte en +# permanence contourne la politique centrale et ne se révoque nulle part. On la ferme, et +# l'on garde l'accès par SSH à l'hôte — le mécanisme de secours retenu par ce dépôt. +# +# L'ouvrir exige alors un secret en voûte, et de l'ajouter au gabarit de l'instance. +# LE MOT DE PASSE N'EST PAS TIRE D'UNE CLE DE VOUTE PAR DEFAUT, ET C'EST VOULU. +# +# `scripts/voute.py` exige de la voute TOUTE reference `vault_*` trouvee dans un role +# actif — c'est ce qui garantit qu'aucun secret n'est oublie. Mais une porte FERMEE ne +# doit pas reclamer de clef : nommer ici `vault_collabora_admin` obligerait chaque +# ecosysteme installant Collabora a inventer, chiffrer et faire tourner un secret pour +# une console que personne n'ouvre. +# +# Qui veut la console la declare chez lui : +# +# # group_vars/serveur_collabora.yml de l'instance +# serveur_collabora_admin_actif: true +# serveur_collabora_admin_mot_de_passe: "{{ vault_collabora_admin }}" +# +# et ajoute `vault_collabora_admin` a son gabarit de voute — ou P18 le lui rappellera. +serveur_collabora_admin_actif: false +serveur_collabora_admin_utilisateur: "collabora-admin" +serveur_collabora_admin_mot_de_passe: "" diff --git a/roles/serveur_collabora/handlers/main.yml b/roles/serveur_collabora/handlers/main.yml new file mode 100644 index 0000000..5fe6690 --- /dev/null +++ b/roles/serveur_collabora/handlers/main.yml @@ -0,0 +1,7 @@ +--- +- name: Redemarrer Collabora + when: not ansible_check_mode + ansible.builtin.systemd: + name: "{{ serveur_collabora_service }}" + state: restarted + daemon_reload: true diff --git a/roles/serveur_collabora/tasks/main.yml b/roles/serveur_collabora/tasks/main.yml index b2ab6ce..767c3e7 100644 --- a/roles/serveur_collabora/tasks/main.yml +++ b/roles/serveur_collabora/tasks/main.yml @@ -1,39 +1,109 @@ --- -- name: Installer Docker (moteur du conteneur Collabora) +# 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. +- name: Cette ressource est-elle deja recuperee ? (cle Collabora) + ansible.builtin.stat: + path: "{{ serveur_collabora_depot_cle_fichier }}" + register: serveur_collabora_cle_presente + +- name: Telecharger la cle de signature Collabora + when: not serveur_collabora_cle_presente.stat.exists + ansible.builtin.get_url: + 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 + +# DEPOT PLAT : `Suites: ./`, aucun composant. Une source classique ne le trouverait pas. +- 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: docker.io + name: "{{ serveur_collabora_paquets }}" state: present update_cache: true - cache_valid_time: 3600 -- name: Activer et demarrer Docker +- 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: docker + name: "{{ serveur_collabora_service }}" enabled: true state: started - when: not ansible_check_mode + daemon_reload: true -- name: Lancer le conteneur Collabora CODE (WOPI, publie sur localhost + IP LAN) - community.docker.docker_container: - name: "{{ serveur_collabora_conteneur }}" - image: "{{ serveur_collabora_image }}:{{ serveur_collabora_tag }}" - state: started - pull: true - restart_policy: always - ports: - - "127.0.0.1:{{ serveur_collabora_port }}:9980" - - "{{ serveur_collabora_lan_ip }}:{{ serveur_collabora_port }}:9980" - env: - aliasgroup1: "{{ serveur_collabora_aliasgroup }}" - extra_params: "{{ serveur_collabora_extra_params }}" +# 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 - -- name: Attendre que Collabora reponde (capabilities) ansible.builtin.uri: url: "http://127.0.0.1:{{ serveur_collabora_port }}/hosting/capabilities" - status_code: 200 - register: serveur_collabora_sante - until: serveur_collabora_sante.status == 200 + status_code: [200] + timeout: 20 + register: serveur_collabora_capacites retries: 12 delay: 5 - when: not ansible_check_mode + until: serveur_collabora_capacites is succeeded diff --git a/roles/serveur_collabora/templates/collabora.sources.j2 b/roles/serveur_collabora/templates/collabora.sources.j2 new file mode 100644 index 0000000..5f175cb --- /dev/null +++ b/roles/serveur_collabora/templates/collabora.sources.j2 @@ -0,0 +1,8 @@ +# GÉNÉRÉ par Set-OPS (rôle serveur_collabora). NE PAS éditer à la main. +# +# Dépôt PLAT : `Suites: ./` et aucun composant. Constaté le 2026-08-24 — `Packages` et +# `Release` vivent à la racine, il n'y a pas de `dists/`. +Types: deb +URIs: {{ serveur_collabora_depot_uri }} +Suites: ./ +Signed-By: {{ serveur_collabora_depot_cle_fichier }} diff --git a/roles/serveur_collabora/templates/setops.conf.j2 b/roles/serveur_collabora/templates/setops.conf.j2 new file mode 100644 index 0000000..bf1a93a --- /dev/null +++ b/roles/serveur_collabora/templates/setops.conf.j2 @@ -0,0 +1,17 @@ +# GÉNÉRÉ par Set-OPS (rôle serveur_collabora). NE PAS éditer à la main. +# +# Surcharge de l'unité du paquet. Le `coolwsd.xml` livré (439 lignes commentées) reste +# intact : le remplacer par un gabarit maison obligerait à suivre son évolution à chaque +# version amont, et ferait perdre les commentaires qui expliquent chaque réglage. +[Service] +ExecStart= +ExecStart=/usr/bin/coolwsd --version --o:sys_template_path=/opt/cool/systemplate \ + --o:child_root_path=/opt/cool/child-roots \ + --o:file_server_root_path=/usr/share/coolwsd \ + --o:ssl.enable={{ serveur_collabora_ssl_enable | bool | lower }} \ + --o:ssl.termination={{ serveur_collabora_ssl_termination | bool | lower }} \ + --o:server_name={{ serveur_collabora_server_name }} \ + --o:storage.wopi.alias_group[0].host=https://{{ serveur_collabora_domaine_nextcloud }}:443 \ + --o:admin_console.enable={{ serveur_collabora_admin_actif | bool | lower }}{% if serveur_collabora_admin_actif | bool %} \ + --o:admin_console.username={{ serveur_collabora_admin_utilisateur }} \ + --o:admin_console.password={{ serveur_collabora_admin_mot_de_passe }}{% endif %} diff --git a/scripts/voute.py b/scripts/voute.py index bf20606..204f94c 100644 --- a/scripts/voute.py +++ b/scripts/voute.py @@ -65,10 +65,30 @@ def gabarit() -> Path | None: def _refs_vault(chemin: Path) -> set[str]: + """Les `vault_*` qu'un fichier UTILISE — pas ceux dont il PARLE. + + ON IGNORE LES COMMENTAIRES (2026-08-24). Le scanner lisait le fichier entier, si bien + qu'expliquer en commentaire « pour ouvrir cette option, fournir vault_x » SUFFISAIT a + rendre `vault_x` obligatoire dans le gabarit et dans la voute reelle. + + Constate sur `serveur_collabora` : sa console d'administration est FERMEE par defaut, + et le role se contentait de documenter comment l'ouvrir. Trois phrases de commentaire + imposaient donc a tout ecosysteme installant Collabora d'inventer, chiffrer et faire + tourner un secret pour une porte que personne n'ouvre. + + Documenter le nom d'une clef ne doit pas l'exiger. Une clef reellement utilisee + apparait dans du code — c'est ce qu'on lit ici. + """ try: - return set(MOTIF_VAULT.findall(chemin.read_text(encoding="utf-8", errors="ignore"))) + texte = chemin.read_text(encoding="utf-8", errors="ignore") except OSError: return set() + # Suffisant pour YAML, Jinja (`{# … #}` compris) et Python : ce qui suit un `#` hors + # guillemets est de la prose. On ne cherche pas a analyser finement — un `#` dans une + # chaine rendrait au pire une reference ignoree, et une reference reellement utilisee + # apparait ailleurs dans du code. + utile = "\n".join(ligne.split("#", 1)[0] for ligne in texte.splitlines()) + return set(MOTIF_VAULT.findall(utile)) def groupes_du_plan() -> set[str]: