From c7378ed7f7af9a97d0a2c2ce5d497fb8add2c223 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Thu, 8 Oct 2026 22:47:33 -0400 Subject: [PATCH] nextcloud : les applications viennent du depot du site, plus de l'app store user_oidc et richdocuments epinglees (version, sha256), tenues par site-cache-01 dans le depot de binaires, installees par app_tierce.yml : site d'abord, amont en repli dit, somme puis signature (occ integrity:check-app), activation. P70 comprend les listes d'artefacts. L'app store a 77 ko/s avait arrete la reconstruction de Technolibre. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 68 ++++++++++ Makefile | 1 + roles/serveur_artefacts/tasks/main.yml | 4 +- roles/serveur_nextcloud/defaults/main.yml | 25 ++++ roles/serveur_nextcloud/tasks/app_tierce.yml | 125 +++++++++++++++++++ roles/serveur_nextcloud/tasks/collabora.yml | 16 +-- roles/serveur_nextcloud/tasks/oidc.yml | 16 +-- scripts/prouver.py | 19 ++- scripts/tests/test_apps_nextcloud.py | 54 ++++++++ 9 files changed, 306 insertions(+), 22 deletions(-) create mode 100644 roles/serveur_nextcloud/tasks/app_tierce.yml create mode 100644 scripts/tests/test_apps_nextcloud.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 065c84f..84ba109 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,73 @@ # CHANGELOG — Set-OPS +## 2026-10-08 (117) — Les applications de Nextcloud ne viennent plus de l'app store : le site les tient + +**94 preuves.** La reconstruction de Technolibre sur `7a4c55b` (règle stricte, entrée (116)) s'est +arrêtée sur `collab-01` : « Could not download app user_oidc, it was not found on the +appstore », après deux minutes. Journal de Nextcloud, à la seconde : « Response from appstore +is invalid ». Mesuré depuis `collab-01` : le catalogue de l'app store (11,7 Mo) arrivait à +**77 ko/s**, un autre fournisseur à **18,8 Mo/s** ; Nextcloud abandonne et prend la réponse +tronquée pour invalide. + +### Ce n'était pas une régression, c'était une dépendance jamais fermée + +Mesurée le 2026-10-05 (reconstruction de Chezlepro observée à la frontière) et jamais écrite +ici : apt est local (6,2 Go servis par le cache du site, aucun `.deb` amont), mais trois +dépendances sortent encore sur Internet à chaque reconstruction — `pip` (PyPI) pour les +runners, `ansible-galaxy` sur un runner neuf, et `occ app:install user_oidc / richdocuments` +(app store de Nextcloud). C'est la troisième qui a mordu. + +### Le mécanisme existait : le dépôt de binaires du site + +Depuis le 2026-09-12, `site-cache-01` tient dans `/var/lib/setops/artefacts-directs`, et sert +sous `/setops-binaires` (port 3142, flux existant), les archives que les rôles téléchargent : +Forgejo, Keycloak, Nextcloud, oauth2-proxy — déclarées une seule fois, dans le rôle qui les +consomme. Les applications de Nextcloud n'y avaient jamais été ajoutées. (Une forge-registre +avait d'abord été proposée : le mécanisme existant la rend inutile.) + +**Essai d'abord** : versions relevées sur Chezlepro (`user_oidc` 8.11.0, `richdocuments` 11.1.2, +sur Nextcloud 34.0.4) ; archives de publication GitHub en 0,7 et 1,2 s ; `occ +integrity:check-app --path` valide leur code hors ligne contre l'autorité de signature de +Nextcloud ; témoin : une ligne ajoutée à un fichier donne `INVALID_HASH`. + +**Fait** : +- `serveur_nextcloud_apps` : chaque application, version, `nom`, `url`, SHA-256. +- `serveur_artefacts` : le site tient ces archives (il parcourt la liste, ne la recopie pas). +- `serveur_nextcloud/tasks/app_tierce.yml` : dépôt du site d'abord, amont en repli **dit à + voix haute** ; somme vérifiée dans les deux cas ; dépôt dans `apps/` ; `occ + integrity:check-app` ; activation. Une version installée qui diffère de l'épinglée + **arrête** (une mise à niveau se décide). Plus aucun `app:install`. +- **P70** comprend désormais les listes d'artefacts : un `dest` modèle attend chacun de ses + noms, tenus seulement si le site parcourt la liste. + +**Éprouvé** : `test_apps_nextcloud.py` (dans `make test`) ; témoin sur l'ancien `oidc.yml`, deux +échecs. P70 : témoin, le site ne parcourant plus la liste, échec. Lint propre. `make verifier` +conforme, **94/94**. + +**Reste** : PyPI et Galaxy, même chemin. Puis la preuve : reconstruire avec l'amont du cache du +site coupé (`serveur_artefacts_hors_ligne`), et constater que rien ne sort. + +## 2026-10-08 (116) — Chezlepro reconstruite sur `7a4c55b`, d'une traite + +**94 preuves.** `make reconstruire-locataire TENANT=OPS-Chezlepro` (46 min, journal +`OPS-Chezlepro/logs/reconstruction-20261008-182930.log`), sans reprise : le runner tire +`7a4c55b` ; 13/13 VM ; `valider` du premier coup (15 cibles) ; pare-feu 13/13, 0 critique. + +**9 jeux** restaurés depuis l'instantané `avant-raser` de 18:29, tous encore au dépôt. +Témoins : aucun écart ; Nextcloud, annuaire, AC d'Icinga, web dorsal identiques ; Loki, aucun +morceau retiré ; historique d'Icinga, 777 lignes d'avant, 0 retirée. Demandé aux bases, sur +17:30–18:29 : Prometheus, 15 cibles toutes les 10 min ; Loki, 1 000 à 2 000 lignes par tranche +de 10 min jusqu'au rasage. Sondes de sauvegarde : OK, sauf `web-dorsal-01` (aucune +application déployée, rien à sauvegarder). + +**La recette corrigée n'a pas eu à attendre** : le premier dépôt d'`obs-01` a relancé +Prometheus à 19:13:02, la recette l'a interrogé à 19:13:29 (27 s, plus qu'un intervalle de +relevé). Son attente reste prête pour un écart plus court, comme les 18 s de Technolibre. + +**Release** : l'exploitant applique la règle stricte. Technolibre, reconstruite sur `2dce6ea` +(recette et bilan rejoués sur `9ca9a5f`), est reconstruite à nouveau sur `7a4c55b`, pour que +les deux locataires portent le même commit avant d'étiqueter. + ## 2026-10-08 (115) — La sonde du dépôt jugeait le premier groupe d'instantanés, pas le plus récent **94 preuves.** Question de l'exploitant : « on a aussi les sondes pour vérifier que les diff --git a/Makefile b/Makefile index 947691f..0c894a3 100644 --- a/Makefile +++ b/Makefile @@ -320,6 +320,7 @@ test: ## Lance les tests unitaires (derivation de nomenclature et d'inventaire) python3 scripts/tests/test_temoins.py python3 scripts/tests/test_genome_colis.py python3 scripts/tests/test_verifier_depot.py + python3 scripts/tests/test_apps_nextcloud.py python3 scripts/tests/test_adressage_derive.py python3 scripts/tests/test_gui_intrants.py python3 scripts/tests/test_runbooks.py diff --git a/roles/serveur_artefacts/tasks/main.yml b/roles/serveur_artefacts/tasks/main.yml index 5864b2d..b595830 100644 --- a/roles/serveur_artefacts/tasks/main.yml +++ b/roles/serveur_artefacts/tasks/main.yml @@ -214,7 +214,9 @@ mode: "0644" force: false timeout: 60 - loop: "{{ serveur_artefacts_directs }}" + # Les applications de Nextcloud (2026-10-08) : declarees par `serveur_nextcloud`, lues + # telles quelles — meme `nom`, meme `url`, rien de recopie ici. + loop: "{{ serveur_artefacts_directs + (serveur_nextcloud_apps | default([])) }}" loop_control: label: "{{ item.nom }}" register: serveur_artefacts_directs_tires diff --git a/roles/serveur_nextcloud/defaults/main.yml b/roles/serveur_nextcloud/defaults/main.yml index a959fa4..9231b2e 100644 --- a/roles/serveur_nextcloud/defaults/main.yml +++ b/roles/serveur_nextcloud/defaults/main.yml @@ -74,6 +74,31 @@ serveur_nextcloud_http_port: 80 # IP de l'edge → trusted_proxies (pour voir la vraie IP client, pas throttler l'edge). serveur_nextcloud_edge_ip: "{{ hostvars[(groups['serveur_nginx'] | default([]) | first) | default('')].ansible_host | default('') }}" +# --- LES APPLICATIONS NE VIENNENT PLUS DE L'APP STORE (2026-10-08) ------------------- +# +# `occ app:install` les tirait d'apps.nextcloud.com a chaque construction. Le 2026-10-08, +# l'app store servait son catalogue (11,7 Mo) a 77 ko/s — 18,8 Mo/s ailleurs depuis la meme +# machine : Nextcloud a abandonne, et la reconstruction de Technolibre s'est arretee. Le +# site tient desormais ces archives dans son depot de binaires, comme celle de Nextcloud +# (`serveur_artefacts`), et le role les installe sans l'app store. +# +# EPINGLEES, VERIFIEES DEUX FOIS : la somme de l'archive (ci-dessous), puis la signature de +# son code (`occ integrity:check-app`, contre l'autorite de signature de Nextcloud, hors +# ligne ; eprouve : une ligne ajoutee a un fichier donne INVALID_HASH). Versions relevees +# sur Chezlepro, ou elles tournent sur Nextcloud 34.0.4. `nom` et `url` portent la version +# en toutes lettres : `test_apps_nextcloud.py` verifie qu'elles disent la meme. +serveur_nextcloud_apps: + - id: user_oidc + version: "8.11.0" + nom: "nextcloud-app-user_oidc-8.11.0.tar.gz" + url: "https://github.com/nextcloud-releases/user_oidc/releases/download/v8.11.0/user_oidc-v8.11.0.tar.gz" + sha256: "887920504c2f0e0d055abd66225ee7b66d756f50fe5a3739779d6b2ce108633f" + - id: richdocuments + version: "11.1.2" + nom: "nextcloud-app-richdocuments-11.1.2.tar.gz" + url: "https://github.com/nextcloud-releases/richdocuments/releases/download/v11.1.2/richdocuments-v11.1.2.tar.gz" + sha256: "96766a94cf417a716e47ed89c6ff2411c54954caa916afcdddc4d9668dffb557" + # --- SSO OIDC (Keycloak) — app user_oidc --- serveur_nextcloud_oidc_actif: true serveur_nextcloud_oidc_nom: "{{ organisation | default('Organisation') }}" diff --git a/roles/serveur_nextcloud/tasks/app_tierce.yml b/roles/serveur_nextcloud/tasks/app_tierce.yml new file mode 100644 index 0000000..b376a03 --- /dev/null +++ b/roles/serveur_nextcloud/tasks/app_tierce.yml @@ -0,0 +1,125 @@ +--- +# UNE APPLICATION NEXTCLOUD, SANS L'APP STORE (2026-10-08) — voir `serveur_nextcloud_apps`. +# +# entree : serveur_nextcloud_app_id (user_oidc, richdocuments) +# +# Le depot du SITE d'abord, l'amont ensuite (meme patron que l'archive de Nextcloud, +# `installer.yml`) ; la somme verifiee dans les deux cas ; puis la signature du code ; puis +# l'activation. Une version installee qui differe de l'epinglee ARRETE : une mise a niveau +# d'application est une decision (et un `occ upgrade`), pas un effet de bord. +- name: "Application Nextcloud — l'application epinglee ({{ serveur_nextcloud_app_id }})" + ansible.builtin.set_fact: + serveur_nextcloud_app: >- + {{ serveur_nextcloud_apps | selectattr('id', 'equalto', serveur_nextcloud_app_id) | first }} + +- name: "Application Nextcloud — deja presente ? ({{ serveur_nextcloud_app_id }})" + ansible.builtin.slurp: + src: "{{ serveur_nextcloud_racine }}/apps/{{ serveur_nextcloud_app.id }}/appinfo/info.xml" + register: serveur_nextcloud_app_info + failed_when: false + +- name: "Application Nextcloud — la version presente est-elle l'epinglee ? ({{ serveur_nextcloud_app_id }})" + vars: + presente: >- + {{ (serveur_nextcloud_app_info.content | b64decode + | regex_search('([^<]+)', '\\1') | default([''], true) | first) + if serveur_nextcloud_app_info.content is defined else '' }} + ansible.builtin.assert: + that: + - presente | length == 0 or presente == serveur_nextcloud_app.version + fail_msg: >- + {{ serveur_nextcloud_app.id }} {{ presente }} est installee, {{ serveur_nextcloud_app.version }} + est epinglee. Une mise a niveau d'application se decide (et passe par `occ upgrade`) : + ajuster `serveur_nextcloud_apps`, ou mettre a niveau a la main. + quiet: true + +- name: "Application Nextcloud — l'obtenir et la deposer ({{ serveur_nextcloud_app_id }})" + when: serveur_nextcloud_app_info.content is not defined + block: + - name: "Application Nextcloud — le cache du controleur ({{ serveur_nextcloud_app_id }})" + ansible.builtin.file: + path: "{{ serveur_nextcloud_cache_local }}" + state: directory + mode: "0700" + delegate_to: localhost + become: false + + # `failed_when: false` : le depot est une COMMODITE ; sans lui, on retombe sur l'amont. + # La somme est verifiee ici aussi : un fichier altere chez le site ne passe pas. + - name: "Application Nextcloud — au depot du site, avant de sortir sur Internet ({{ serveur_nextcloud_app_id }})" + ansible.builtin.get_url: + url: "{{ setops_depot_binaires }}/{{ serveur_nextcloud_app.nom }}" + dest: "{{ serveur_nextcloud_cache_local }}/{{ serveur_nextcloud_app.nom }}" + checksum: "sha256:{{ serveur_nextcloud_app.sha256 }}" + mode: "0644" + timeout: 30 + delegate_to: localhost + become: false + register: serveur_nextcloud_app_site + failed_when: false + when: setops_depot_binaires | default('') | length > 0 + + - name: "Application Nextcloud — dans le cache du controleur ? ({{ serveur_nextcloud_app_id }})" + ansible.builtin.stat: + path: "{{ serveur_nextcloud_cache_local }}/{{ serveur_nextcloud_app.nom }}" + checksum_algorithm: sha256 + register: serveur_nextcloud_app_cache + delegate_to: localhost + become: false + + # ON LE DIT, FORT : une reconstruction qui sort sur Internet doit se voir. + - name: "Application Nextcloud — absente du depot du site : prise en amont ({{ serveur_nextcloud_app_id }})" + ansible.builtin.debug: + msg: >- + {{ serveur_nextcloud_app.nom }} n'est pas au depot du site + ({{ setops_depot_binaires | default('aucun depot declare') }}) : prise sur Internet. + when: not (serveur_nextcloud_app_cache.stat.exists + and serveur_nextcloud_app_cache.stat.checksum == serveur_nextcloud_app.sha256) + + - name: "Application Nextcloud — en amont (repli) ({{ serveur_nextcloud_app_id }})" + ansible.builtin.get_url: + url: "{{ serveur_nextcloud_app.url }}" + dest: "{{ serveur_nextcloud_cache_local }}/{{ serveur_nextcloud_app.nom }}" + checksum: "sha256:{{ serveur_nextcloud_app.sha256 }}" + mode: "0644" + timeout: 60 + delegate_to: localhost + become: false + when: not (serveur_nextcloud_app_cache.stat.exists + and serveur_nextcloud_app_cache.stat.checksum == serveur_nextcloud_app.sha256) + + - name: "Application Nextcloud — deposer le code dans apps/ ({{ serveur_nextcloud_app_id }})" + ansible.builtin.unarchive: + src: "{{ serveur_nextcloud_cache_local }}/{{ serveur_nextcloud_app.nom }}" + dest: "{{ serveur_nextcloud_racine }}/apps" + owner: "{{ serveur_nextcloud_utilisateur }}" + group: "{{ serveur_nextcloud_utilisateur }}" + creates: "{{ serveur_nextcloud_racine }}/apps/{{ serveur_nextcloud_app.id }}/appinfo/info.xml" + +- name: "Application Nextcloud — signature du code, puis activation ({{ serveur_nextcloud_app_id }})" + become: true + become_user: "{{ serveur_nextcloud_utilisateur }}" + environment: + PGSSLMODE: "{{ serveur_nextcloud_pg_sslmode }}" + PGSSLROOTCERT: "{{ serveur_nextcloud_ca_racine }}" + when: not ansible_check_mode + block: + # Contre l'autorite de signature de Nextcloud, sans reseau. Rien a dire = intact. + - name: "Application Nextcloud — le code est-il celui que Nextcloud a signe ? ({{ serveur_nextcloud_app_id }})" + ansible.builtin.command: + argv: ["php", "{{ serveur_nextcloud_racine }}/occ", "integrity:check-app", "{{ serveur_nextcloud_app.id }}"] + register: serveur_nextcloud_app_integrite + changed_when: false + failed_when: serveur_nextcloud_app_integrite.rc != 0 or serveur_nextcloud_app_integrite.stdout | trim | length > 0 + + - name: "Application Nextcloud — activer ({{ serveur_nextcloud_app_id }})" + ansible.builtin.shell: + executable: /bin/bash + cmd: | + set -eo pipefail + OCC="php {{ serveur_nextcloud_racine }}/occ" + APP="{{ serveur_nextcloud_app.id }}" + if $OCC app:list --enabled | grep -qw "$APP"; then echo SETOPS_OK + else $OCC app:enable "$APP" && echo SETOPS_CHANGED; fi + register: serveur_nextcloud_app_active + changed_when: "'SETOPS_CHANGED' in serveur_nextcloud_app_active.stdout" diff --git a/roles/serveur_nextcloud/tasks/collabora.yml b/roles/serveur_nextcloud/tasks/collabora.yml index 522f146..4ebe56e 100644 --- a/roles/serveur_nextcloud/tasks/collabora.yml +++ b/roles/serveur_nextcloud/tasks/collabora.yml @@ -1,4 +1,10 @@ --- +# L'APPLICATION VIENT DU DEPOT DU SITE, PLUS DE L'APP STORE (2026-10-08) : `app_tierce.yml`. +- name: Installer l'app richdocuments (depot du site, somme, signature, activation) + ansible.builtin.include_tasks: app_tierce.yml + vars: + serveur_nextcloud_app_id: richdocuments + - name: Collabora / Office (occ, en www-data avec TLS PG) become: true become_user: "{{ serveur_nextcloud_utilisateur }}" @@ -7,16 +13,6 @@ PGSSLROOTCERT: "{{ serveur_nextcloud_ca_racine }}" when: not ansible_check_mode block: - - name: Installer + activer l'app richdocuments (idempotent) - ansible.builtin.shell: - executable: /bin/bash - cmd: | - set -eo pipefail - OCC="php {{ serveur_nextcloud_racine }}/occ" - if $OCC app:list | grep -qw richdocuments; then echo SETOPS_OK; else $OCC app:install richdocuments && echo SETOPS_CHANGED; fi - register: serveur_nextcloud_office_app - changed_when: "'SETOPS_CHANGED' in serveur_nextcloud_office_app.stdout" - - name: URL serveur -> Collabora (locale, rapide) ansible.builtin.command: argv: ["php", "{{ serveur_nextcloud_racine }}/occ", "config:app:set", "richdocuments", "wopi_url", "--value={{ serveur_nextcloud_wopi_url }}"] diff --git a/roles/serveur_nextcloud/tasks/oidc.yml b/roles/serveur_nextcloud/tasks/oidc.yml index 5f6615c..d0f58ae 100644 --- a/roles/serveur_nextcloud/tasks/oidc.yml +++ b/roles/serveur_nextcloud/tasks/oidc.yml @@ -1,4 +1,10 @@ --- +# L'APPLICATION VIENT DU DEPOT DU SITE, PLUS DE L'APP STORE (2026-10-08) : `app_tierce.yml`. +- name: Installer l'app user_oidc (depot du site, somme, signature, activation) + ansible.builtin.include_tasks: app_tierce.yml + vars: + serveur_nextcloud_app_id: user_oidc + - name: SSO OIDC (occ, en www-data avec TLS PG) become: true become_user: "{{ serveur_nextcloud_utilisateur }}" @@ -7,16 +13,6 @@ PGSSLROOTCERT: "{{ serveur_nextcloud_ca_racine }}" when: not ansible_check_mode block: - - name: Installer + activer l'app user_oidc (idempotent) - ansible.builtin.shell: - executable: /bin/bash - cmd: | - set -eo pipefail - OCC="php {{ serveur_nextcloud_racine }}/occ" - if $OCC app:list | grep -qw user_oidc; then echo SETOPS_OK; else $OCC app:install user_oidc && echo SETOPS_CHANGED; fi - register: serveur_nextcloud_oidc_app - changed_when: "'SETOPS_CHANGED' in serveur_nextcloud_oidc_app.stdout" - - name: Ne plus annoncer le formulaire de connexion locale # Masquage, pas fermeture : `?direct=1` reste le chemin de secours documente par # l'amont. Voir serveur_nextcloud_connexion_locale dans defaults/main.yml. diff --git a/scripts/prouver.py b/scripts/prouver.py index f81a74c..0fa71a3 100644 --- a/scripts/prouver.py +++ b/scripts/prouver.py @@ -3882,12 +3882,29 @@ def preuve_depot_binaires_complet() -> tuple[bool, str]: if not tenus: return False, "Le depot de binaires ne declare aucun artefact (serveur_artefacts_directs vide)." + # LES LISTES D'ARTEFACTS (2026-10-08). Les applications de Nextcloud sont declarees en + # LISTE par leur role (`serveur_nextcloud_apps`) ; leur `dest` est donc un modele + # (`{{ serveur_nextcloud_app.nom }}`), et le site les tient en parcourant cette liste. + # Un modele attend CHACUN des noms de sa liste ; ces noms ne sont tenus que si la tache + # du site parcourt bien la liste — sans quoi le depot ne les tiendrait pas. + import yaml as _yaml + listes = {"serveur_nextcloud_app.nom": ("serveur_nextcloud", "serveur_nextcloud_apps")} + taches_site = (roles_dir / "serveur_artefacts" / "tasks" / "main.yml").read_text(encoding="utf-8") + noms_de: dict[str, list[str]] = {} + for modele, (role_liste, liste) in listes.items(): + d = _yaml.safe_load((roles_dir / role_liste / "defaults" / "main.yml").read_text(encoding="utf-8")) or {} + noms_de[modele] = [str(a["nom"]) for a in d.get(liste) or []] + if liste in taches_site: + tenus |= set(noms_de[modele]) + attendus: dict[str, str] = {} for role in sorted(p.name for p in roles_dir.iterdir() if p.is_dir()): for tf in sorted((roles_dir / role / "tasks").glob("*.yml")) if (roles_dir / role / "tasks").is_dir() else []: texte = tf.read_text(encoding="utf-8") for dest in _re.findall(r'^\s*dest:\s*"\{\{\s*\w+_cache_local\s*\}\}/([^"]+)"', texte, _re.M): - attendus[dest] = f"{role}/tasks/{tf.name}" + m = _re.fullmatch(r'\{\{\s*([\w.]+)\s*\}\}', dest) + for nom in (noms_de.get(m.group(1), [dest]) if m else [dest]): + attendus[nom] = f"{role}/tasks/{tf.name}" manquants = sorted(f"{nom} ({ou})" for nom, ou in attendus.items() if nom not in tenus) if manquants: diff --git a/scripts/tests/test_apps_nextcloud.py b/scripts/tests/test_apps_nextcloud.py new file mode 100644 index 0000000..08886f0 --- /dev/null +++ b/scripts/tests/test_apps_nextcloud.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python3 +"""Les applications Nextcloud viennent du depot du SITE, epinglees — plus de l'app store. + +POURQUOI (2026-10-08). `occ app:install` tirait `user_oidc` et `richdocuments` +d'apps.nextcloud.com a chaque construction ; l'app store servant son catalogue a 77 ko/s, +la reconstruction de Technolibre s'est arretee. Ce test garde la declaration coherente : une +version qui differerait entre `nom` et `url` ferait tenir au site un fichier que personne ne +demande, et la divergence se lirait « le depot sert un fichier », pas « le mauvais ». +""" +from __future__ import annotations + +import re +import sys +from pathlib import Path + +import yaml + +RACINE = Path(__file__).resolve().parents[2] +ECHECS: list[str] = [] + + +def verifier(cond: bool, msg: str) -> None: + print(("OK " if cond else "ECHEC ") + msg) + if not cond: + ECHECS.append(msg) + + +def main() -> int: + apps = yaml.safe_load((RACINE / "roles/serveur_nextcloud/defaults/main.yml").read_text())["serveur_nextcloud_apps"] + verifier(len({a["id"] for a in apps}) == len(apps) and len(apps) > 0, f"{len(apps)} application(s), identifiants uniques") + for a in apps: + v, i = a["version"], a["id"] + verifier(f"-{i}-{v}." in a["nom"] and a["nom"].startswith("nextcloud-app-"), f"{i} : le nom porte l'id et la version ({a['nom']})") + verifier(f"/{i}/" in a["url"] and f"v{v}" in a["url"], f"{i} : l'url porte l'id et la version") + verifier(re.fullmatch(r"[0-9a-f]{64}", a["sha256"]) is not None, f"{i} : sha256 bien formee") + taches = (RACINE / "roles/serveur_artefacts/tasks/main.yml").read_text() + verifier("serveur_nextcloud_apps" in taches, "le site tient les applications declarees par serveur_nextcloud") + # Les commandes, pas les commentaires : `restaurer.yml` raconte encore pourquoi + # `occ app:install` refusait une base restauree trop tot. + roles = "".join(l for p in (RACINE / "roles/serveur_nextcloud/tasks").glob("*.yml") + for l in p.read_text().splitlines(keepends=True) if not l.lstrip().startswith("#")) + verifier("app:install" not in roles, "plus aucune tache ne tire une application de l'app store (`app:install`)") + for f, app in (("oidc.yml", "user_oidc"), ("collabora.yml", "richdocuments")): + t = (RACINE / "roles/serveur_nextcloud/tasks" / f).read_text() + verifier(f"serveur_nextcloud_app_id: {app}" in t, f"{f} installe {app} par app_tierce.yml") + if ECHECS: + print(f"\n{len(ECHECS)} echec(s).") + return 1 + print("\nLes applications Nextcloud sont epinglees, tenues par le site, installees sans l'app store.") + return 0 + + +if __name__ == "__main__": + sys.exit(main())