Ses machines n existaient plus depuis le 2026-09-06 (D-83), mais son plan restait sur disque : la federation lui reservait l index 29 et quatre machines du site lui ouvraient SSH, apt, DNS et HTTPS. Les commentaires et documents vivants gardent leur lecon sans le nommer ; les archives restent telles quelles. Pas encore sur le reseau : les regles regenerees attendent le runner du site. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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 d'un autre ecosysteme, 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
|