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 <noreply@anthropic.com>
This commit is contained in:
parent
7a4c55bfc9
commit
c7378ed7f7
9 changed files with 306 additions and 22 deletions
68
CHANGELOG.md
68
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
|
||||
|
|
|
|||
1
Makefile
1
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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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') }}"
|
||||
|
|
|
|||
125
roles/serveur_nextcloud/tasks/app_tierce.yml
Normal file
125
roles/serveur_nextcloud/tasks/app_tierce.yml
Normal file
|
|
@ -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('<version>([^<]+)</version>', '\\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"
|
||||
|
|
@ -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 }}"]
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
54
scripts/tests/test_apps_nextcloud.py
Normal file
54
scripts/tests/test_apps_nextcloud.py
Normal file
|
|
@ -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())
|
||||
Loading…
Reference in a new issue