Le cache du site couvrait apt ; quatre artefacts arrivaient autrement, parce qu ils ne vivent dans aucun depot apt. Le controleur les tire puis les pousse par SSH. Mesure du 2026-09-12 : le cache du runner du site est ABSENT. Un second locataire monte depuis lui sortait chercher 570 Mo sur codeberg.org, github.com et download.nextcloud.com, alors que le meme ecosysteme ne demandait plus un seul paquet a Debian. Le poste du mainteneur les a depuis toujours : personne ne l avait vu. Pas de relais transparent, et la mesure tranche : github.com redirige vers une URL signee valable une heure, differente a chaque requete. Un cache qui la prend pour cle ne fait jamais mouche. Le relais marcherait pour deux amonts sur quatre. Donc un vrai depot, dans le service qui existe deja. LocalDirs d apt-cacher-ng publie un repertoire du disque sous un prefixe, eprouve AVANT d ecrire le role. Aucun service, aucun port, aucun certificat, aucun flux nouveaux : l ingress 3142 pair flotte couvre exactement ce chemin. Les versions ne sont pas recopiees : le role lit les defauts des quatre consommateurs. Les quatre roles recoivent une tache AJOUTEE, placee avant leur stat de cache — si le depot sert, le stat le voit et la tache amont se saute d elle-meme. Aucune tache existante n a change. P70 exige que tout dest ecrit sous un cache_local figure au depot. Une liste qui suit une autre prend du retard ; celle-ci est nee avec sa garde. Deux marches payees en chemin : - failed_when: false REECRIT le verdict, donc la premiere garde de signature ne gardait rien. Elles mesurent le fichier desormais. - file: state=directory cree les parents en 0750 : apt-cacher-ng, qui ne tourne pas en root, rendait 403 sur chaque fichier. Un chemin se traverse en entier. Verifie sur l infrastructure : 6/6 artefacts servis (200/206) depuis le runner du site ET depuis une machine du locataire a travers la frontiere ; les 6 empreintes SHA-256 sont identiques a celles qui ont construit Chezlepro ; second passage changed=0. make prouver : 69 OK, 0 echec, 1 saute. ansible-lint : 0 failure, profil production. Inclut aussi force: true sur cinq telechargements de cles : une reprise conditionnelle ne reprend rien (304 Not Modified, size 0, attempts 5). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
246 lines
11 KiB
YAML
246 lines
11 KiB
YAML
---
|
|
# LE CACHE NE DOIT PAS DÉPENDRE DE LUI-MÊME POUR SE RÉPARER.
|
|
#
|
|
# La première version faisait `update_cache: true` à chaque passage. Sur l'hôte qui PORTE
|
|
# le cache, cet `apt update` passe par le cache lui-même — et en mode hors ligne, il est
|
|
# refusé. Le rôle qui devait remettre le service en ligne ne pouvait donc plus s'exécuter :
|
|
#
|
|
# « Failed to update apt cache after 5 retries » (mesuré le 2026-08-23, après
|
|
# l'épreuve hors ligne : il a fallu réparer la machine à la main)
|
|
#
|
|
# Un service dont la remise en route exige qu'il fonctionne déjà est un piège. On ne
|
|
# rafraîchit donc l'index qu'à la PREMIÈRE installation, quand aucun cache n'existe encore
|
|
# et qu'`apt` va forcément à l'amont.
|
|
- name: Le cache est-il déjà installé ?
|
|
ansible.builtin.stat:
|
|
path: /usr/sbin/apt-cacher-ng
|
|
register: serveur_artefacts_present
|
|
|
|
- name: Installer le cache d'artefacts
|
|
ansible.builtin.apt:
|
|
name: "{{ serveur_artefacts_paquets }}"
|
|
state: present
|
|
update_cache: "{{ not serveur_artefacts_present.stat.exists }}"
|
|
when: not ansible_check_mode
|
|
|
|
- name: Assurer le répertoire de cache
|
|
ansible.builtin.file:
|
|
path: "{{ serveur_artefacts_cache }}"
|
|
state: directory
|
|
owner: apt-cacher-ng
|
|
group: apt-cacher-ng
|
|
mode: "0755"
|
|
when: not ansible_check_mode
|
|
|
|
# apt-cacher-ng lit TOUS les `*.conf` de son répertoire, par ordre alphabétique, et le
|
|
# dernier lu gagne. Le préfixe `zzz-` place donc notre configuration après celle du
|
|
# paquet Debian et celle de debconf, sans avoir à les éditer — on ajoute, on ne remplace
|
|
# pas. Une valeur posée ici l'emporte, et `apt purge` rend la machine à son état d'origine.
|
|
# --- LE FILET : NE PAS POSER UN AMONT INJOIGNABLE ----------------------------
|
|
#
|
|
# CE CACHE EST UN MAILLON DONT DEPEND SA PROPRE RECONSTRUCTION (mesure du 2026-08-31).
|
|
#
|
|
# Toute la flotte du tenant prend ses paquets ici. Si ce cache pointe vers un amont mort,
|
|
# `apt` echoue sur les QUINZE machines — donc le socle echoue, donc le deploiement
|
|
# n'atteint jamais la couche qui poserait le bon amont. Le correctif se retrouve dans la
|
|
# couche que la panne empeche d'atteindre, et il faut une main pour en sortir.
|
|
#
|
|
# C'est exactement ce qui est arrive : l'amont pointait sur le cache de patient 0, eteint
|
|
# la veille pour liberer de la RAM. `apt` rendait `503 Connection timeout` EN CITANT
|
|
# L'ADRESSE DU CACHE LOCAL, jamais celle de l'amont manquant — la panne accusait le
|
|
# maillon visible.
|
|
#
|
|
# On mesure donc AVANT d'ecrire. Un amont injoignable est refuse : mieux vaut un cache
|
|
# qui garde sa configuration precedente et un message clair, qu'un cache qu'on vient de
|
|
# rendre inutilisable pour toute la flotte.
|
|
- name: L'amont declare repond-il ?
|
|
ansible.builtin.wait_for:
|
|
host: "{{ serveur_artefacts_amont | urlsplit('hostname') }}"
|
|
port: "{{ serveur_artefacts_amont | urlsplit('port') | default(3142, true) | int }}"
|
|
timeout: 10
|
|
register: serveur_artefacts_amont_sonde
|
|
changed_when: false
|
|
# `ignore_errors` ET NON `failed_when: false` — LA DIFFERENCE EST TOUTE LA GARDE.
|
|
#
|
|
# `failed_when: false` REECRIT le verdict : la tache n'est plus jamais `failed`, donc
|
|
# `is not failed` est toujours vrai, donc l'assertion qui suit ne peut PAS tirer. Ecrite
|
|
# ainsi, elle a declare « joignable » un amont mesure MUET la seconde d'avant
|
|
# (2026-08-31). Une garde qui ne peut pas echouer ne garde rien.
|
|
#
|
|
# `ignore_errors` laisse le verdict intact et se contente de ne pas arreter le play :
|
|
# c'est ce qu'il faut pour SONDER puis DECIDER.
|
|
ignore_errors: true
|
|
when:
|
|
- serveur_artefacts_amont | length > 0
|
|
- not ansible_check_mode
|
|
|
|
- name: Refuser un amont injoignable plutot que de rendre ce cache inutilisable
|
|
ansible.builtin.assert:
|
|
that:
|
|
- serveur_artefacts_amont_sonde is not failed
|
|
fail_msg: >-
|
|
L'amont declare ({{ serveur_artefacts_amont }}) ne repond pas. Le poser rendrait ce
|
|
cache — et donc `apt` sur TOUTE la flotte de cet ecosysteme — inutilisable, et le
|
|
deploiement ne pourrait plus atteindre la couche qui le corrigerait.
|
|
Verifier que la machine d'amont tourne, ou vider `serveur_artefacts_amont` pour
|
|
aller directement chez Debian (c'est une emancipation : voir
|
|
docs/filiation-emancipation.md).
|
|
success_msg: "Amont {{ serveur_artefacts_amont }} joignable."
|
|
when:
|
|
- 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
|
|
dest: /etc/apt-cacher-ng/zzz-setops.conf
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
notify: Redémarrer le cache d'artefacts
|
|
|
|
- name: Activer et démarrer le cache d'artefacts
|
|
ansible.builtin.systemd:
|
|
name: "{{ serveur_artefacts_service }}"
|
|
enabled: true
|
|
state: started
|
|
when: not ansible_check_mode
|
|
|
|
- name: Appliquer les redémarrages avant de vérifier
|
|
ansible.builtin.meta: flush_handlers
|
|
|
|
# ÉCRIRE, PUIS RELIRE (D-68). Un service « active » qui n'écoute pas sur le port attendu
|
|
# est un vert sur un périmètre vide : les clients le découvriraient à leur premier
|
|
# `apt update`, c'est-à-dire au pire moment.
|
|
- name: Le cache écoute-t-il vraiment ?
|
|
ansible.builtin.wait_for:
|
|
host: "127.0.0.1"
|
|
port: "{{ serveur_artefacts_port }}"
|
|
timeout: 30
|
|
when: not ansible_check_mode
|
|
|
|
# --- Sonde de supervision (docs/supervision-conception.md) --------------------------
|
|
# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la
|
|
# fait tourner et pousse le verdict, sans savoir ce qu'elle mesure.
|
|
- name: Assurer le repertoire des sondes de supervision
|
|
ansible.builtin.file:
|
|
path: /usr/local/lib/setops/sondes
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Deposer la sonde « cache-apt »
|
|
ansible.builtin.template:
|
|
src: sonde-cache-apt.sh.j2
|
|
dest: /usr/local/lib/setops/sondes/cache-apt.sh
|
|
owner: root
|
|
group: root
|
|
mode: "0750"
|
|
|
|
- name: Deposer la sonde « cache-apt-volume »
|
|
ansible.builtin.template:
|
|
src: sonde-cache-apt-volume.sh.j2
|
|
dest: /usr/local/lib/setops/sondes/cache-apt-volume.sh
|
|
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
|