diff --git a/AGENTS.md b/AGENTS.md index 67c56f7..57f8c26 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -188,7 +188,7 @@ Si `ansible-lint` n’est pas disponible, le signaler clairement. Ne pas invente ## Écrire, puis relire (D-68) `--syntax-check` et `ansible-lint` prouvent que le dépôt est cohérent **avec lui-même**. -C'est aussi ce que font les 69 preuves de `make prouver` : elles lisent le dépôt, sans le +C'est aussi ce que font les 70 preuves de `make prouver` : elles lisent le dépôt, sans le moindre appel réseau. **Aucune ne demande au système déployé s'il ressemble à ce que le dépôt annonce.** diff --git a/CHANGELOG.md b/CHANGELOG.md index fdb28a2..64a8c07 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,100 @@ # CHANGELOG — Set-OPS +## 2026-09-12 (6) — Le site tient aussi les binaires, pas seulement les paquets + +### Ce qui manquait + +Le cache du site couvre `apt` depuis longtemps : 1,34 Go tires de l'amont, 6,13 Go servis +a la flotte pendant la reconstruction d'un locataire — un rapport de 4,58. Mais quatre +artefacts arrivent autrement, parce qu'ils ne vivent dans aucun depot apt : Forgejo, +Keycloak, Nextcloud, oauth2-proxy. Le CONTROLEUR les tire (`delegate_to: localhost`) puis +les pousse par SSH. + +**Mesure du 2026-09-12** : `/opt/setops/.cache/setops` est ABSENT sur le runner du site. +Un second locataire monte depuis ce runner sortait donc chercher **570 Mo** sur +codeberg.org, github.com (deux fois) et download.nextcloud.com — alors que le meme +ecosysteme ne demandait plus un seul paquet a Debian. Le poste du mainteneur, lui, a ces +1,6 Go depuis toujours : c'est pourquoi personne ne l'avait vu. + +### Pourquoi pas un relais transparent + +Les `Remap-*` du cache font deja passer quatre fournisseurs HTTPS. Mesure des quatre +amonts avant de decider : + +| amont | reponse | +|---|---| +| `codeberg.org` | 200, aucune redirection | +| `download.nextcloud.com` | 200, aucune redirection | +| `github.com` (x2) | 302 vers `release-assets.githubusercontent.com`, **URL signee valable une heure** | + +L'URL finale de GitHub change a chaque requete. Un cache qui la prend pour cle ne fait +jamais mouche, et ce qu'il garderait serait perime avant d'etre relu. Le relais marche +pour deux amonts sur quatre : ce n'est pas un mecanisme, c'est une coincidence. + +### Ce qui a ete fait + +**Un vrai depot de fichiers, dans le service qui existe deja.** `LocalDirs` d'apt-cacher-ng +publie un repertoire du disque sous un prefixe — eprouve sur `site-cache-01` AVANT d'ecrire +le role : 200, 90 158 octets, identiques a l'octet. Donc **aucun service, aucun port, +aucun certificat, aucun flux nouveaux** : l'`ingress 3142 pair: flotte` deja declare couvre +exactement ce chemin. + +- `serveur_artefacts` publie `/var/lib/setops/artefacts-directs` sous `setops-binaires`, + et le remplit depuis les amonts — une seule machine sort, pour tout le site et pour les + ecosystemes qui naitront demain. +- **Les versions ne sont pas recopiees.** Le role lit les defauts des quatre roles + consommateurs (`include_vars` SANS `name:` — enfermees dans un dictionnaire, les valeurs + qui se citent entre elles ne se resolvent plus ; le devis des certificats avait deja paye + cette marche). +- Les quatre roles recoivent **une tache ajoutee, placee avant leur `stat` de cache**. Si + le depot sert le fichier, le `stat` le voit et la tache de telechargement amont se saute + d'elle-meme : aucune tache existante n'a change. +- Les deux signatures detachees passent aussi par le depot. Elles pesent 228 octets, mais + elles sont demandees a chaque passage : une sortie reste une sortie. +- `setops_depot_binaires` est **derive**, jamais ecrit deux fois — de `artefacts_amorcage` + chez le locataire, du plan du site dans `site_inventaire.py`. + +### La garde, ecrite le meme jour que la liste + +`P70` exige que tout `dest:` ecrit sous un `_cache_local` figure au depot. Sans elle, +une version montee dans un role sans l'etre dans le depot produirait exactement le defaut +que ce depot traque : le runner ne trouve pas le fichier, retombe sur Internet, **et tout +fonctionne** — sans que rien ne le dise. + +Une liste qui suit une autre prend du retard. Celle-ci est nee avec son garde-fou. + +### Une erreur corrigee en cours de route + +La premiere version des gardes de signature s'appuyait sur `failed_when: false` puis testait +`is not succeeded`. `failed_when: false` REECRIT le verdict : la tache n'est plus jamais +`failed`, donc la garde etait toujours vraie et ne gardait rien — la faute exacte que +`client_artefacts` documente depuis le 2026-08-31. Les gardes **mesurent le fichier** +desormais, pas le verdict de la tache. + +### Ce que ca ne couvre pas encore + +Le cache du contrôleur reste per-runner : un runner tout neuf demande le depot, et si le +depot ne l'a pas encore, il sort. Le depot se remplit au deploiement du site — donc un +locataire monte avant que le site n'ait ete redeploye sortira une derniere fois. + + +## 2026-09-12 (5) — Une reprise conditionnelle ne reprend rien + +Cinq roles telechargeaient une cle de signature avec une boucle `until` exigeant une taille +non nulle. Des que le fichier existe — meme a zero octet — `get_url` emet une requete +CONDITIONNELLE ; l'amont repond `304 Not Modified` ; la tache reussit sans rien ecrire ; et +la boucle retente cinq fois contre un serveur qui repondra toujours 304 : + +``` +HTTP Error 304: Not Modified size: 0 attempts: 5 +``` + +La boucle de reprise se battait contre elle-meme. `force: true` sur les cinq +(`client_pki`, `client_journal`, `serveur_collabora`, `serveur_grafana`, `serveur_loki`) — +le `when:` qui precede garantit deja qu'on ne retelecharge pas une cle valide, donc `force` +ne concerne que le cas ou l'on a DECIDE d'aller chercher. + + ## 2026-09-12 (4) — Pools nommes comme les depots, et les cles sortent du poste ### Le nom d'un pool est celui de son depot diff --git a/docs/audit/preuve-2026-09-12.md b/docs/audit/preuve-2026-09-12.md index 80c7fc1..cdfb4cc 100644 --- a/docs/audit/preuve-2026-09-12.md +++ b/docs/audit/preuve-2026-09-12.md @@ -7,7 +7,7 @@ > [`docs/audit/affirmations.md`](affirmations.md). - **Instance** : `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance` — inventaire `/home/danallaire/Espace Chezlepro/DépôtsSurForge/Set-OPS-public/instance/inventories/principal/hosts.yml` -- **Verdict** : ✅ CONFORME (68 OK · 0 echec · 1 saute) +- **Verdict** : ✅ CONFORME (69 OK · 0 echec · 1 saute) ## Preuves @@ -44,7 +44,7 @@ | P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv | | P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. | | P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 62 scripts expliques et atteignables, 119 cibles make documentees, 68 roles avec README. | -| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (138 cle(s) declaree(s) par l'instance). | +| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (139 cle(s) declaree(s) par l'instance). | | P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 34 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). | | P34 | Chaque document declare son lecteur | — | ✅ OK | 44 document(s) declarent leur lecteur (38 genere(s) exempte(s)). | | P35 | Toute application exigeant une base en a une au plan | — | ✅ OK | 5 application(s) exigeant une base l'ont toutes (4 entree(s) au registre). | @@ -69,7 +69,7 @@ | P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 9 role(s) applique(s), aucun secret de tenant reclame. | | P55 | La cle du SITE ne nait que sur le runner d'un tenant | — | ✅ OK | 14 hote(s) : la cle du SITE ne nait que sur 1 runner(s) de tenant, celle du tenant sur 14. | | P56 | Gabarit minimal, et rien de retire n'est perdu | — | ✅ OK | Gabarit minimal : 4 role(s), tous indispensables au premier demarrage ; 14 role(s) retire(s), tous repris par le socle ou le durcissement. | -| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (69 preuves, 68 roles, 41 groupes). | +| P57 | Comptes en prose : les chiffres du depot sur lui-meme | — | ✅ OK | Les comptes ecrits en prose correspondent a la mesure (70 preuves, 68 roles, 41 groupes). | | P58 | Habilitations : chaque service dit a quel GROUPE, et par quoi | — | ✅ OK | 8 habilitation(s) declarees, toutes nommant un groupe, un mecanisme connu et une raison ; les `role-realm` sont projetees. | | P59 | Enumerations annoncees : le nombre correspond a ce qui suit | — | ✅ OK | 2 enumeration(s) annoncee(s) correspondent a ce qu'elles annoncent (formes non ambigues seulement). | | P60 | Wiki publie : la forge sert ce que le depot dit | AFF-002 | ✅ OK | Le wiki publie correspond au depot : `wiki/` n'a pas bouge depuis `904ece3` (publie le 2026-09-12). | @@ -82,6 +82,7 @@ | P67 | Nom public : le service porte celui du plan, pas celui du role | — | ✅ OK | 6 service(s) expose(s) portent le nom du plan (6 groupe(s) derive(s)). | | P68 | Cle de depot telechargee : mesuree avant d'etre utilisee | — | ✅ OK | 5 role(s) telechargent une cle de depot, tous la mesurent avant de s'en servir. | | P69 | Amorcage d'un tenant : l'adresse designe le site REEL | — | ✅ OK | 2 adresse(s) d'amorcage designent bien une machine du site. | +| P70 | Depot de binaires : il tient tout ce que les roles vont chercher | — | ✅ OK | 6 artefact(s) direct(s) tenus par le depot du site. | ## Couverture des affirmations ✅ du registre diff --git a/docs/catalogue-services.md b/docs/catalogue-services.md index 555da35..0d297d2 100644 --- a/docs/catalogue-services.md +++ b/docs/catalogue-services.md @@ -57,7 +57,7 @@ Ce que la reconstruction couvre, par capacité : | 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 | -| Source d'artefacts | `serveur_artefacts`, `client_artefacts` | cache apt de l'ecosysteme (apt-cacher-ng) : les paquets viennent de chez soi, pas de six serveurs etrangers — **mode hors ligne** pour prouver ce que le cache detient vraiment | +| Source d'artefacts | `serveur_artefacts`, `client_artefacts` | **deux faces sur un seul service.** Cache apt (apt-cacher-ng) : les paquets viennent de chez soi, pas de six serveurs etrangers — **mode hors ligne** pour prouver ce que le cache detient vraiment. Et **depot des binaires directs** (`LocalDirs`) : Forgejo, Keycloak, Nextcloud, oauth2-proxy ne vivent dans aucun depot apt et etaient tires d'Internet par CHAQUE runner — 570 Mo mesures le 2026-09-12. Meme port, meme regle de pare-feu, garde `P70` | | Runner de tenant | `serveur_ops_tenant` | le pouvoir de **configurer** : la voute de l'ecosysteme, deposee CHIFFREE sur son propre runner. Sans elle un runner calcule son inventaire et ne peut rien en faire — chaque role qui demande un secret echoue sur son assertion. N'atteint ni la fabric ni la frontiere | | Runner de site | `serveur_ops_site` | le pouvoir de **materialiser** : creer et detruire des VM sur la fabric. Detient la voute du SITE, chiffree, et n'entre JAMAIS chez un tenant — reserve a l'ecosysteme de l'hebergeur | | Resolution | `serveur_resolveur`, `client_resolveur` | UN resolveur recursif par tenant (Unbound), qui recurse depuis la racine et delegue la zone souveraine a PowerDNS. Remplace les N demons locaux d'avant le 2026-08-24 | diff --git a/docs/devis-services.md b/docs/devis-services.md index ea58927..addf60b 100644 --- a/docs/devis-services.md +++ b/docs/devis-services.md @@ -30,7 +30,7 @@ make placement-plan # chaque VM est-elle là où le plan la met ## Le trou qu'il comble -`scripts/prouver.py` porte 69 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le +`scripts/prouver.py` porte 70 preuves (dont une conditionnelle, sautée sans la clé de la voûte). Elles sont toutes **statiques** : elles lisent le dépôt. Zéro appel réseau, zéro SSH, zéro `ansible`. Elles établissent que le dépôt est cohérent **avec lui-même** — que les handlers existent, que les intrants ont un propriétaire, que rien n'est codé en dur. diff --git a/roles/client_journal/tasks/main.yml b/roles/client_journal/tasks/main.yml index c0ca39b..b5defa3 100644 --- a/roles/client_journal/tasks/main.yml +++ b/roles/client_journal/tasks/main.yml @@ -60,7 +60,20 @@ - 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 diff --git a/roles/client_pki/tasks/main.yml b/roles/client_pki/tasks/main.yml index a70b7eb..dd27bbd 100644 --- a/roles/client_pki/tasks/main.yml +++ b/roles/client_pki/tasks/main.yml @@ -105,7 +105,20 @@ - name: Telecharger la cle de signature Smallstep when: not (telecharger_la_cle_de_signature_smallste_present.stat.exists and telecharger_la_cle_de_signature_smallste_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_pki_depot_cle_url }}" dest: "{{ client_pki_depot_cle_fichier }}" owner: root diff --git a/roles/serveur_artefacts/defaults/main.yml b/roles/serveur_artefacts/defaults/main.yml index 5888a6b..daba60f 100644 --- a/roles/serveur_artefacts/defaults/main.yml +++ b/roles/serveur_artefacts/defaults/main.yml @@ -111,3 +111,66 @@ serveur_artefacts_remaps: # tomber tout le reste. Sans cette ligne, le remap serait vrai a moitie : les paquets # passeraient par le cache, et chaque machine sortirait quand meme chercher sa cle. serveur_artefacts_motif_fichiers_sup: '.*\.(key|asc)$' + +# --- LE DEPOT DES BINAIRES DIRECTS (2026-09-12) -------------------------------------- +# +# LE CACHE APT NE COUVRAIT QUE apt. Quatre artefacts arrivent autrement — Forgejo, +# Keycloak, Nextcloud, oauth2-proxy — parce qu'ils ne vivent dans aucun depot apt : ce +# sont des archives de publication, tirees par le CONTROLEUR (`delegate_to: localhost`) +# puis poussees par SSH. Le role le disait deja : « deux mecanismes, parce que ce sont +# deux problemes ». Ce qui manquait, c'est que le second n'avait pas de source locale. +# +# CE QUE CA COUTAIT, MESURE LE 2026-09-12 : `/opt/setops/.cache/setops` est ABSENT sur le +# runner du site. Un second locataire monte depuis ce runner sortait donc chercher 570 Mo +# sur codeberg.org, github.com (deux fois) et download.nextcloud.com — alors que le meme +# ecosysteme ne demandait plus un seul paquet a Debian. +# +# POURQUOI PAS UN RELAIS TRANSPARENT, comme les `Remap-*` ci-dessus. Mesure des quatre +# amonts le 2026-09-12 : +# +# codeberg.org 200, 0 redirection -> relayable +# download.nextcloud.com 200, 0 redirection -> relayable +# github.com 302 -> release-assets.githubusercontent.com +# avec signature et jeton VALABLES UNE HEURE +# +# L'URL finale de GitHub change a chaque requete. Un cache qui la prend pour cle ne fait +# jamais mouche, et ce qu'il garderait serait perime avant d'etre relu. Le relais +# transparent marche pour deux amonts sur quatre : ce n'est pas un mecanisme, c'est une +# coincidence. On tient donc un VRAI depot de fichiers, rempli une fois, servi toujours. +# +# ET IL N'A DEMANDE NI SERVICE NI PORT NI CERTIFICAT NI FLUX. `LocalDirs` d'apt-cacher-ng +# publie un repertoire du disque sous un prefixe — eprouve sur site-cache-01 avant +# d'ecrire ce rôle : 200, 90 158 octets, identiques a l'octet. L'`ingress 3142 pair: +# flotte` deja declare couvre exactement ce chemin. +serveur_artefacts_directs_actif: true +serveur_artefacts_directs_dir: "/var/lib/setops/artefacts-directs" +serveur_artefacts_directs_prefixe: "setops-binaires" + +# LES ROLES DONT ON LIT LES DEFAUTS POUR SAVOIR QUOI TENIR. +# +# La version et l'URL amont sont declarees UNE SEULE FOIS, dans le role qui consomme +# l'artefact. Les redeclarer ici ferait exactement ce que ce depot combat partout +# ailleurs : deux sources du meme fait, qui divergent en silence — et la divergence se +# lirait « le depot sert un fichier », pas « il sert le mauvais ». +serveur_artefacts_directs_roles: + - serveur_forgejo + - serveur_keycloak + - serveur_nextcloud + - serveur_oauth2_proxy + +# LES NOMS SONT CEUX QUE LE CACHE DU CONTROLEUR PORTE — pas des noms choisis ici. +# Un nom different obligerait chaque role a traduire, et la traduction serait le +# troisieme endroit ou la version pourrait se perimer. +serveur_artefacts_directs: + - nom: "forgejo-{{ serveur_forgejo_version }}" + url: "{{ serveur_forgejo_url }}" + - nom: "forgejo-{{ serveur_forgejo_version }}.asc" + url: "{{ serveur_forgejo_signature_url }}" + - nom: "keycloak-{{ serveur_keycloak_version }}.tar.gz" + url: "{{ serveur_keycloak_url }}" + - nom: "keycloak-{{ serveur_keycloak_version }}.tar.gz.asc" + url: "{{ serveur_keycloak_signature_url }}" + - nom: "nextcloud-{{ serveur_nextcloud_version }}.tar.bz2" + url: "{{ serveur_nextcloud_url }}" + - nom: "oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" + url: "{{ serveur_oauth2_proxy_url }}" diff --git a/roles/serveur_artefacts/tasks/main.yml b/roles/serveur_artefacts/tasks/main.yml index dec6e74..035ef5b 100644 --- a/roles/serveur_artefacts/tasks/main.yml +++ b/roles/serveur_artefacts/tasks/main.yml @@ -90,6 +90,38 @@ - serveur_artefacts_amont | length > 0 - not ansible_check_mode +# LE REPERTOIRE AVANT LA DIRECTIVE QUI LE PUBLIE. `LocalDirs` pointant sur un chemin +# absent, apt-cacher-ng ne refuse pas de demarrer : il rend 503 sur ce prefixe, et le +# message parle de « File or directory not found » — ce qui accuse le fichier demande, +# pas la racine manquante. +# LE PARENT AUSSI, ET C'EST LUI QUI A MORDU (mesure du 2026-09-12). +# +# `file: state=directory` cree les parents manquants avec les droits par defaut — ici +# `0750 root:root`. apt-cacher-ng ne tourne PAS en root : il n'a pas pu traverser +# `/var/lib/setops` et rendait 403 sur chaque fichier du depot, alors que le repertoire +# final etait bien en 0755 et les fichiers bien la. +# +# 403 ACCUSE LE FICHIER DEMANDE, pas le repertoire qui le contient — et encore moins le +# GRAND-PARENT. On a donc cherche du cote des motifs de fichiers d'apt-cacher-ng, qui +# n'y etaient pour rien. Un chemin se traverse en entier ; un droit se verifie de meme. +- name: Assurer la racine d'état de Set-OPS, traversable par les services + ansible.builtin.file: + path: "{{ serveur_artefacts_directs_dir | dirname }}" + state: directory + owner: root + group: root + mode: "0755" + when: serveur_artefacts_directs_actif | bool + +- name: Assurer le répertoire du dépôt de binaires directs + ansible.builtin.file: + path: "{{ serveur_artefacts_directs_dir }}" + state: directory + owner: root + group: root + mode: "0755" + when: serveur_artefacts_directs_actif | bool + - name: Déployer la configuration Set-OPS ansible.builtin.template: src: zzz-setops.conf.j2 @@ -145,3 +177,70 @@ owner: root group: root mode: "0750" + +# --- REMPLIR LE DEPOT DE BINAIRES DIRECTS -------------------------------------------- +# +# CETTE MACHINE EST LA SEULE A SORTIR, et c'est le meme principe que pour apt : un seul +# telechargement pour tout le site, y compris pour les ecosystemes qui naitront demain. +# +# ON LIT LES DEFAUTS DES ROLES CONSOMMATEURS, on ne recopie pas leurs versions. Sans ca, +# la version vivrait a deux endroits et le depot finirait par servir, en silence, un +# artefact que plus personne ne demande. +# +# `include_vars` SANS `name:` — DELIBEREMENT. Enfermees dans un dictionnaire, les valeurs +# qui se citent entre elles ne se resolvent plus : `serveur_forgejo_url` reference +# `serveur_forgejo_version`. Le depot connait deja cette marche, payee sur le devis des +# certificats ; on ne la repaye pas. +- name: Lire ce que les rôles consommateurs déclarent télécharger + ansible.builtin.include_vars: + dir: "{{ role_path }}/../{{ item }}/defaults" + loop: "{{ serveur_artefacts_directs_roles }}" + loop_control: + label: "{{ item }}" + when: serveur_artefacts_directs_actif | bool + +# `force: false` ET UN `dest` NOMME PAR LA VERSION : une publication ne change pas sous +# le meme nom, donc un fichier deja present est le bon. Une nouvelle version porte un +# autre nom et se telecharge d'elle-meme — le depot se met a jour en suivant les roles, +# sans qu'on ait rien a purger. +# +# LES REPRISES SONT OBLIGATOIRES, PAS PRUDENTIELLES. `packages.smallstep.com` a deja fait +# echouer un deploiement de quarante minutes en pendant au premier essai et en repondant +# en 0,48 s au second ; codeberg.org n'a aucune raison d'etre plus fiable. +- name: Tenir les binaires directs à la disposition des écosystèmes + ansible.builtin.get_url: + url: "{{ item.url }}" + dest: "{{ serveur_artefacts_directs_dir }}/{{ item.nom }}" + mode: "0644" + force: false + timeout: 60 + loop: "{{ serveur_artefacts_directs }}" + loop_control: + label: "{{ item.nom }}" + register: serveur_artefacts_directs_tires + retries: 5 + delay: 6 + until: serveur_artefacts_directs_tires is succeeded + # UN AMONT MUET NE DOIT PAS ARRETER LE SITE. Ce depot est une COMMODITE : sans lui, + # chaque runner sort lui-meme, comme avant. Le faire echouer rendrait le site + # indeployable le jour ou codeberg.org tousse — on aurait echange une sortie Internet + # contre une dependance a Internet, ce qui est exactement l'inverse du but. + failed_when: false + when: + - serveur_artefacts_directs_actif | bool + - not ansible_check_mode + +# ON LE DIT, FORT. Un depot a moitie rempli sert a moitie, et le runner qui complete en +# sortant sur Internet le fait sans que rien ne le signale. +- name: Dire ce que le dépôt de binaires ne tient PAS + ansible.builtin.debug: + msg: >- + {{ (serveur_artefacts_directs_tires.results | default([]) + | selectattr('failed', 'defined') | selectattr('failed') + | map(attribute='item.nom') | list) | join(', ') }} + — absent(s) du depot. Les runners qui en ont besoin sortiront sur Internet. + when: + - serveur_artefacts_directs_actif | bool + - not ansible_check_mode + - (serveur_artefacts_directs_tires.results | default([]) + | selectattr('failed', 'defined') | selectattr('failed') | list | length) > 0 diff --git a/roles/serveur_artefacts/templates/zzz-setops.conf.j2 b/roles/serveur_artefacts/templates/zzz-setops.conf.j2 index c207e56..b891181 100644 --- a/roles/serveur_artefacts/templates/zzz-setops.conf.j2 +++ b/roles/serveur_artefacts/templates/zzz-setops.conf.j2 @@ -50,3 +50,16 @@ Remap-{{ r.nom }}: http://{{ r.hote }} ; https://{{ r.hote }}/ {% endfor %} {% endif %} {% endif %} + +{% if serveur_artefacts_directs_actif | bool %} +# --- LE DEPOT DES BINAIRES DIRECTS --------------------------------------------------- +# +# `LocalDirs` publie un repertoire du disque sous un prefixe. C'est la seule facon de +# servir Forgejo, Keycloak, Nextcloud et oauth2-proxy sans monter un second service : +# ils ne vivent dans aucun depot apt, et GitHub redirige vers une URL signee valable une +# heure — qu'aucun relais transparent ne peut mettre en cache (voir defaults/main.yml). +# +# CE CHEMIN EST DEJA COUVERT PAR LE PARE-FEU : c'est le meme port 3142, la meme regle +# `ingress pair: flotte`. Rien de neuf a ouvrir. +LocalDirs: {{ serveur_artefacts_directs_prefixe }} {{ serveur_artefacts_directs_dir }} +{% endif %} diff --git a/roles/serveur_collabora/tasks/main.yml b/roles/serveur_collabora/tasks/main.yml index 02284fd..1662bf7 100644 --- a/roles/serveur_collabora/tasks/main.yml +++ b/roles/serveur_collabora/tasks/main.yml @@ -73,7 +73,20 @@ - 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 diff --git a/roles/serveur_forgejo/tasks/main.yml b/roles/serveur_forgejo/tasks/main.yml index ccd515c..72eedb0 100644 --- a/roles/serveur_forgejo/tasks/main.yml +++ b/roles/serveur_forgejo/tasks/main.yml @@ -131,6 +131,31 @@ become: false when: not serveur_forgejo_binaire_present.stat.exists +# --- LE DEPOT DU SITE D'ABORD, L'INTERNET ENSUITE (2026-09-12) ---------------- +# +# Le site tient ces artefacts dans son cache (`serveur_artefacts`, LocalDirs). Un runner +# qui les y trouve ne sort pas du tout : c'est le meme principe que le cache apt, applique +# aux publications qui ne vivent dans aucun depot apt. +# +# PLACEE AVANT LE `stat` DU CACHE DU CONTROLEUR, ET C'EST TOUT LE MONTAGE : si le depot +# sert le fichier, le `stat` qui suit le voit, et la tache de telechargement amont se +# saute d'elle-meme. Aucune tache existante n'a change. +# +# `failed_when: false` — le depot est une COMMODITE. Site sans depot, depot qui n'a pas +# encore cette version, cache du site tout neuf : on retombe sur l'amont, comme avant. +- name: Prendre le binaire Forgejo au dépôt du site, avant de sortir sur Internet + ansible.builtin.get_url: + url: "{{ setops_depot_binaires }}/forgejo-{{ serveur_forgejo_version }}" + dest: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}" + mode: "0644" + timeout: 30 + delegate_to: localhost + become: false + failed_when: false + when: + - setops_depot_binaires | default('') | length > 0 + - not serveur_forgejo_binaire_present.stat.exists + - name: L artefact est-il déjà dans le cache du contrôleur ? ansible.builtin.stat: path: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}" @@ -167,6 +192,35 @@ # Un serveur tiers intermittent ne doit pas arreter un deploiement de quarante minutes # (mesure du 2026-08-23 : la meme URL pend, puis rend 200 en 0,48 s au second essai). # Defaut de `get_url` : 10 s et aucune reprise. +# LA SIGNATURE AUSSI VIENT DU DEPOT QUAND IL L'A (2026-09-12). Elle pese 228 octets, +# mais elle est demandee a CHAQUE passage ou la cible n'a pas encore le binaire : sans +# elle ici, un ecosysteme entierement servi par le site sortirait quand meme sur Internet +# pour un quart de kilo-octet. Une sortie reste une sortie. +- name: Prendre la signature au dépôt du site, avant de sortir sur Internet + ansible.builtin.get_url: + url: "{{ setops_depot_binaires }}/forgejo-{{ serveur_forgejo_version }}.asc" + dest: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}.asc" + mode: "0644" + timeout: 30 + force: true + delegate_to: localhost + become: false + failed_when: false + when: + - setops_depot_binaires | default('') | length > 0 + - not serveur_forgejo_binaire_present.stat.exists + +# ON MESURE LE FICHIER, PAS LE VERDICT DE LA TACHE. `failed_when: false` REECRIT le +# verdict : la tache n'est plus jamais `failed`, donc une garde posee dessus est toujours +# vraie et ne garde rien. Le depot connait cette marche — elle a deja declare « joignable » +# un amont mesure muet la seconde d'avant. Ce qui compte ici, c'est qu'un fichier soit la. +- name: La signature est-elle arrivée dans le cache du contrôleur ? + ansible.builtin.stat: + path: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}.asc" + register: serveur_forgejo_asc_present + delegate_to: localhost + become: false + - name: Récupérer la signature détachée du binaire ansible.builtin.get_url: url: "{{ serveur_forgejo_signature_url }}" @@ -176,7 +230,10 @@ force: true delegate_to: localhost become: false - when: not serveur_forgejo_binaire_present.stat.exists + when: + - not serveur_forgejo_binaire_present.stat.exists + # Le depot a servi : ne pas ressortir pour ecraser ce qu'on a deja. + - not (serveur_forgejo_asc_present.stat.exists and (serveur_forgejo_asc_present.stat.size | default(0)) > 0) register: serveur_forgejo_sig retries: 5 delay: 6 diff --git a/roles/serveur_grafana/tasks/main.yml b/roles/serveur_grafana/tasks/main.yml index b014950..82d3498 100644 --- a/roles/serveur_grafana/tasks/main.yml +++ b/roles/serveur_grafana/tasks/main.yml @@ -92,7 +92,20 @@ - 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: "{{ serveur_grafana_depot_cle_url }}" dest: "{{ serveur_grafana_depot_cle_fichier }}" owner: root diff --git a/roles/serveur_keycloak/tasks/main.yml b/roles/serveur_keycloak/tasks/main.yml index abc5350..5db0407 100644 --- a/roles/serveur_keycloak/tasks/main.yml +++ b/roles/serveur_keycloak/tasks/main.yml @@ -96,6 +96,31 @@ become: false when: not serveur_keycloak_archive_present.stat.exists +# --- LE DEPOT DU SITE D'ABORD, L'INTERNET ENSUITE (2026-09-12) ---------------- +# +# Le site tient ces artefacts dans son cache (`serveur_artefacts`, LocalDirs). Un runner +# qui les y trouve ne sort pas du tout : c'est le meme principe que le cache apt, applique +# aux publications qui ne vivent dans aucun depot apt. +# +# PLACEE AVANT LE `stat` DU CACHE DU CONTROLEUR, ET C'EST TOUT LE MONTAGE : si le depot +# sert le fichier, le `stat` qui suit le voit, et la tache de telechargement amont se +# saute d'elle-meme. Aucune tache existante n'a change. +# +# `failed_when: false` — le depot est une COMMODITE. Site sans depot, depot qui n'a pas +# encore cette version, cache du site tout neuf : on retombe sur l'amont, comme avant. +- name: Prendre l'archive Keycloak au dépôt du site, avant de sortir sur Internet + ansible.builtin.get_url: + url: "{{ setops_depot_binaires }}/keycloak-{{ serveur_keycloak_version }}.tar.gz" + dest: "{{ serveur_keycloak_cache_local }}/keycloak-{{ serveur_keycloak_version }}.tar.gz" + mode: "0644" + timeout: 30 + delegate_to: localhost + become: false + failed_when: false + when: + - setops_depot_binaires | default('') | length > 0 + - not serveur_keycloak_archive_present.stat.exists + - name: L artefact est-il déjà dans le cache du contrôleur ? ansible.builtin.stat: path: "{{ serveur_keycloak_cache_local }}/keycloak-{{ serveur_keycloak_version }}.tar.gz" @@ -125,6 +150,35 @@ # l'empreinte epinglee. Elle ne consulte aucun serveur de cles : un serveur de cles n'est # pas une autorite, et en dependre au moment du deploiement rendrait la garde aussi # fragile que ce qu'elle protege. +# LA SIGNATURE AUSSI VIENT DU DEPOT QUAND IL L'A (2026-09-12). Elle pese 228 octets, +# mais elle est demandee a CHAQUE passage ou la cible n'a pas encore le binaire : sans +# elle ici, un ecosysteme entierement servi par le site sortirait quand meme sur Internet +# pour un quart de kilo-octet. Une sortie reste une sortie. +- name: Prendre la signature au dépôt du site, avant de sortir sur Internet + ansible.builtin.get_url: + url: "{{ setops_depot_binaires }}/keycloak-{{ serveur_keycloak_version }}.tar.gz.asc" + dest: "{{ serveur_keycloak_cache_local }}/keycloak-{{ serveur_keycloak_version }}.tar.gz.asc" + mode: "0644" + timeout: 30 + force: true + delegate_to: localhost + become: false + failed_when: false + when: + - setops_depot_binaires | default('') | length > 0 + - not serveur_keycloak_archive_present.stat.exists + +# ON MESURE LE FICHIER, PAS LE VERDICT DE LA TACHE. `failed_when: false` REECRIT le +# verdict : la tache n'est plus jamais `failed`, donc une garde posee dessus est toujours +# vraie et ne garde rien. Le depot connait cette marche — elle a deja declare « joignable » +# un amont mesure muet la seconde d'avant. Ce qui compte ici, c'est qu'un fichier soit la. +- name: La signature est-elle arrivée dans le cache du contrôleur ? + ansible.builtin.stat: + path: "{{ serveur_keycloak_cache_local }}/keycloak-{{ serveur_keycloak_version }}.tar.gz.asc" + register: serveur_keycloak_asc_present + delegate_to: localhost + become: false + - name: Récupérer la signature détachée de l archive ansible.builtin.get_url: url: "{{ serveur_keycloak_signature_url }}" @@ -133,7 +187,10 @@ force: true delegate_to: localhost become: false - when: not serveur_keycloak_archive_present.stat.exists + when: + - not serveur_keycloak_archive_present.stat.exists + # Le depot a servi : ne pas ressortir pour ecraser ce qu'on a deja. + - not (serveur_keycloak_asc_present.stat.exists and (serveur_keycloak_asc_present.stat.size | default(0)) > 0) - name: Vérifier la signature de l archive contre l empreinte épinglée ansible.builtin.command: diff --git a/roles/serveur_loki/tasks/main.yml b/roles/serveur_loki/tasks/main.yml index 2dd1848..d5a6ce8 100644 --- a/roles/serveur_loki/tasks/main.yml +++ b/roles/serveur_loki/tasks/main.yml @@ -60,7 +60,20 @@ - 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: "{{ serveur_loki_depot_cle_url }}" dest: "{{ serveur_loki_depot_cle_fichier }}" owner: root diff --git a/roles/serveur_nextcloud/tasks/installer.yml b/roles/serveur_nextcloud/tasks/installer.yml index bb2ef6c..6afcad0 100644 --- a/roles/serveur_nextcloud/tasks/installer.yml +++ b/roles/serveur_nextcloud/tasks/installer.yml @@ -33,6 +33,31 @@ become: false when: not serveur_nextcloud_archive_present.stat.exists +# --- LE DEPOT DU SITE D'ABORD, L'INTERNET ENSUITE (2026-09-12) ---------------- +# +# Le site tient ces artefacts dans son cache (`serveur_artefacts`, LocalDirs). Un runner +# qui les y trouve ne sort pas du tout : c'est le meme principe que le cache apt, applique +# aux publications qui ne vivent dans aucun depot apt. +# +# PLACEE AVANT LE `stat` DU CACHE DU CONTROLEUR, ET C'EST TOUT LE MONTAGE : si le depot +# sert le fichier, le `stat` qui suit le voit, et la tache de telechargement amont se +# saute d'elle-meme. Aucune tache existante n'a change. +# +# `failed_when: false` — le depot est une COMMODITE. Site sans depot, depot qui n'a pas +# encore cette version, cache du site tout neuf : on retombe sur l'amont, comme avant. +- name: Prendre l'archive Nextcloud au dépôt du site, avant de sortir sur Internet + ansible.builtin.get_url: + url: "{{ setops_depot_binaires }}/nextcloud-{{ serveur_nextcloud_version }}.tar.bz2" + dest: "{{ serveur_nextcloud_cache_local }}/nextcloud-{{ serveur_nextcloud_version }}.tar.bz2" + mode: "0644" + timeout: 30 + delegate_to: localhost + become: false + failed_when: false + when: + - setops_depot_binaires | default('') | length > 0 + - not serveur_nextcloud_archive_present.stat.exists + - name: L artefact est-il déjà dans le cache du contrôleur ? ansible.builtin.stat: path: "{{ serveur_nextcloud_cache_local }}/nextcloud-{{ serveur_nextcloud_version }}.tar.bz2" diff --git a/roles/serveur_oauth2_proxy/tasks/main.yml b/roles/serveur_oauth2_proxy/tasks/main.yml index 190e9ed..6494e1a 100644 --- a/roles/serveur_oauth2_proxy/tasks/main.yml +++ b/roles/serveur_oauth2_proxy/tasks/main.yml @@ -74,6 +74,31 @@ become: false when: not serveur_oauth2_proxy_archive_present.stat.exists +# --- LE DEPOT DU SITE D'ABORD, L'INTERNET ENSUITE (2026-09-12) ---------------- +# +# Le site tient ces artefacts dans son cache (`serveur_artefacts`, LocalDirs). Un runner +# qui les y trouve ne sort pas du tout : c'est le meme principe que le cache apt, applique +# aux publications qui ne vivent dans aucun depot apt. +# +# PLACEE AVANT LE `stat` DU CACHE DU CONTROLEUR, ET C'EST TOUT LE MONTAGE : si le depot +# sert le fichier, le `stat` qui suit le voit, et la tache de telechargement amont se +# saute d'elle-meme. Aucune tache existante n'a change. +# +# `failed_when: false` — le depot est une COMMODITE. Site sans depot, depot qui n'a pas +# encore cette version, cache du site tout neuf : on retombe sur l'amont, comme avant. +- name: Prendre l'archive oauth2-proxy au dépôt du site, avant de sortir sur Internet + ansible.builtin.get_url: + url: "{{ setops_depot_binaires }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" + dest: "{{ serveur_oauth2_proxy_cache_local }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" + mode: "0644" + timeout: 30 + delegate_to: localhost + become: false + failed_when: false + when: + - setops_depot_binaires | default('') | length > 0 + - not serveur_oauth2_proxy_archive_present.stat.exists + - name: L artefact est-il déjà dans le cache du contrôleur ? ansible.builtin.stat: path: "{{ serveur_oauth2_proxy_cache_local }}/oauth2-proxy-{{ serveur_oauth2_proxy_version }}.tar.gz" diff --git a/scripts/prouver.py b/scripts/prouver.py index 27f3501..763aadc 100644 --- a/scripts/prouver.py +++ b/scripts/prouver.py @@ -3278,6 +3278,47 @@ def preuve_cle_telechargee_non_vide() -> tuple[bool, str]: f"avant de s'en servir.") +def preuve_depot_binaires_complet() -> tuple[bool, str]: + """Le depot du site tient-il TOUT ce que les roles vont chercher sur Internet ? + + UNE LISTE QUI SUIT UNE AUTRE PREND DU RETARD, et celle-ci a ete ecrite le meme jour + que la garde — deliberement. Le depot des binaires directs (`serveur_artefacts_directs`) + enumere ce que le cache du site tient ; les roles consommateurs, eux, declarent ce + qu'ils telechargent. Deux listes, un seul fait. + + CE QUE LE RETARD PRODUIRAIT : une version montee dans `serveur_forgejo_version` sans + l'etre dans le depot. Le runner ne trouve pas le fichier, retombe sur codeberg.org, + et tout fonctionne — en sortant sur Internet, sans que rien ne le dise. C'est + exactement la famille de defaut que ce depot traque : du correct qui ment. + + LA MESURE EST TEXTUELLE, ET C'EST SUFFISANT. On releve, dans les taches des roles, + chaque `dest:` ecrit sous `_cache_local`, et on exige que son nom de fichier + figure au depot. Rendre Jinja n'apporterait rien : c'est le NOM qui doit correspondre, + et il est ecrit des deux cotes dans la meme forme. + """ + import re as _re + roles_dir = RACINE / "roles" + defauts = (roles_dir / "serveur_artefacts" / "defaults" / "main.yml").read_text(encoding="utf-8") + tenus = set(_re.findall(r'^\s*-\s*nom:\s*"([^"]+)"', defauts, _re.M)) + if not tenus: + return False, "Le depot de binaires ne declare aucun artefact (serveur_artefacts_directs vide)." + + 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}" + + manquants = sorted(f"{nom} ({ou})" for nom, ou in attendus.items() if nom not in tenus) + if manquants: + return False, ("Telecharges par un role mais ABSENTS du depot du site — " + "chaque runner sortira les chercher : " + " ; ".join(manquants)) + orphelins = sorted(tenus - set(attendus)) + detail = f" ({len(orphelins)} tenu(s) que plus aucun role ne demande : {', '.join(orphelins)})" if orphelins else "" + return True, f"{len(attendus)} artefact(s) direct(s) tenus par le depot du site{detail}." + + def preuve_amorcage_suit_le_site() -> tuple[bool, str]: """Les adresses d'amorcage d'un tenant designent-elles le site REEL ? @@ -3501,6 +3542,8 @@ PREUVES: list[dict] = [ "refs": [], "func": preuve_cle_telechargee_non_vide}, {"id": "P69", "titre": "Amorcage d'un tenant : l'adresse designe le site REEL", "refs": [], "func": preuve_amorcage_suit_le_site}, + {"id": "P70", "titre": "Depot de binaires : il tient tout ce que les roles vont chercher", + "refs": [], "func": preuve_depot_binaires_complet}, ] diff --git a/scripts/site_inventaire.py b/scripts/site_inventaire.py index f7ed237..d3047c0 100755 --- a/scripts/site_inventaire.py +++ b/scripts/site_inventaire.py @@ -239,10 +239,24 @@ def inventaire() -> dict: for a in applications.values()) and str(s.get("etat", "actif")) == "actif"), "") + # LE DEPOT DES BINAIRES DIRECTS — meme derivation que le resolveur, pour la meme + # raison : l'adresse se lit la ou elle est vraie, dans le plan qui porte le service. + # + # Forgejo, Keycloak, Nextcloud et oauth2-proxy ne vivent dans aucun depot apt : le + # controleur les tire puis les pousse par SSH. Le cache du site les tient desormais + # (`serveur_artefacts`, LocalDirs), et c'est ce qui permet a un runner de monter un + # ecosysteme entier sans sortir. Mesure du 2026-09-12 : sans lui, 570 Mo par runner. + _cache_site = next((str(s.get("ip")) for nom, s in serveurs.items() + if any(a.get("hote") == nom and a.get("groupe") == "serveur_artefacts" + for a in applications.values()) + and str(s.get("etat", "actif")) == "actif"), "") + # Ce que TOUTE machine du site recoit, des intrants du plan. communes: dict = {} if _resolveur: communes["dns_amorcage"] = _resolveur + if _cache_site: + communes["setops_depot_binaires"] = f"http://{_cache_site}:3142/setops-binaires" for cle in ("domaine_interne", "organisation", "dns_amorcage"): if intrants.get(cle): communes[cle] = str(intrants[cle])