artefacts : la forge sert le code, il manquait qui sert les binaires
Some checks failed
verifier / verifier (push) Has been cancelled
Some checks failed
verifier / verifier (push) Has been cancelled
serveur_artefacts (apt-cacher-ng) + client_artefacts, integration universelle qui s'eteint quand aucun hote ne porte le service et RETIRE la direction posee. Chez patient 0 : colocalise sur forge-01 -- la forge est la source, du code et des binaires. La preuve est le mode hors ligne : paquet en cache servi en 0 o/s, paquet absent refuse par un 503. Tant qu'internet repond, un apt update qui reussit ne dit pas d'ou vient l'octet. Trois lecons : apt fait heriter Acquire::https::Proxy de la valeur HTTP (d'ou 403 CONNECT denied sur les depots tiers, et smallstep injoignable) ; un service ne doit pas dependre de lui-meme pour se reparer (l'apt update du role passait par le cache hors ligne) ; et rediriger 2>/dev/null, c'est choisir de ne pas voir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
a3f496a496
commit
32e1f6fbcd
19 changed files with 503 additions and 9 deletions
66
CHANGELOG.md
66
CHANGELOG.md
|
|
@ -1,5 +1,71 @@
|
|||
# CHANGELOG — Set-OPS
|
||||
|
||||
## 2026-08-23 — La source d'artefacts : la forge sert le code, il manquait qui sert les binaires
|
||||
|
||||
Pour poser une seule machine, un écosystème allait chercher chez **six serveurs
|
||||
étrangers** : `deb.debian.org`, `security.debian.org`, `packages.smallstep.com`,
|
||||
`apt.grafana.com`, `packages.icinga.com`, `codeberg.org`. La forge héberge le code ; rien
|
||||
n'hébergeait les binaires.
|
||||
|
||||
Deux rôles neufs — `serveur_artefacts` (apt-cacher-ng) et `client_artefacts`, intégration
|
||||
**universelle** qui s'éteint d'elle-même quand aucun hôte ne porte le service, et qui
|
||||
**retire** la direction posée auparavant : une intégration qui ne sait pas se retirer est
|
||||
un piège différé.
|
||||
|
||||
Chez patient 0, le service est colocalisé sur `forge-01`. C'était l'intuition de départ,
|
||||
prise au mot : *la forge est la source* — du code **et** des binaires.
|
||||
|
||||
### Ce qui était déjà couvert, et ce qui ne l'était pas
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| téléchargements **directs** (Forgejo, Keycloak, Nextcloud, oauth2-proxy, collections) | déjà couverts — le contrôleur télécharge une fois et pousse par SSH |
|
||||
| **dépôts apt** | c'est ce qui manquait |
|
||||
|
||||
Deux mécanismes, parce que ce sont deux problèmes : on ne sert pas un dépôt apt par `scp`.
|
||||
|
||||
### La preuve : couper l'amont
|
||||
|
||||
Tant qu'internet répond, un `apt update` qui réussit ne dit pas d'où vient l'octet. D'où
|
||||
le mode hors ligne, qui est autant une fonction qu'un instrument :
|
||||
|
||||
```
|
||||
paquet DÉJÀ en cache 235 ko réceptionnés en 0s (0 o/s) ← servi localement
|
||||
paquet ABSENT du cache 503 Unable to download in offline mode ← refusé
|
||||
```
|
||||
|
||||
Le `0 o/s` est le témoin : rien n'a traversé le réseau. Contrôle positif et négatif dans
|
||||
la même minute.
|
||||
|
||||
### Trois choses apprises en le construisant
|
||||
|
||||
**`apt` fait hériter `Acquire::https::Proxy` de la valeur HTTP.** Poser le seul proxy HTTP
|
||||
envoyait donc aussi les dépôts tiers en HTTPS dans le cache, qui refuse les tunnels — à
|
||||
juste titre : `403 CONNECT denied`. `packages.smallstep.com` devenait injoignable pour
|
||||
toute la flotte. Il faut écrire `DIRECT` explicitement.
|
||||
|
||||
**Un service ne doit pas dépendre de lui-même pour se réparer.** La première version
|
||||
faisait `apt update` à chaque passage. Sur l'hôte qui *porte* le cache, cet `apt update`
|
||||
passe par le cache — et en mode hors ligne, il est refusé. Le rôle qui devait remettre le
|
||||
service en ligne ne pouvait plus s'exécuter, et il a fallu réparer la machine à la main.
|
||||
L'index n'est désormais rafraîchi qu'à la **première** installation.
|
||||
|
||||
**Mes sondes ont menti deux fois de plus.** `apt-get update >/dev/null 2>&1 && echo ok` a
|
||||
rendu « ok » sur cinq hôtes où le proxy était injoignable : rediriger la sortie d'erreur,
|
||||
c'est choisir de ne pas voir. Et `grep -c` rend un code de sortie **1** quand il compte
|
||||
zéro — un instrument qui crie à l'échec en constatant le succès attendu.
|
||||
|
||||
### Ce que ça ne règle pas encore
|
||||
|
||||
Les dépôts tiers en **HTTPS** vont toujours en direct. Les faire passer par le cache
|
||||
demande de réécrire leurs sources en `http://cache/<remap>/…` — propre, faisable, pas dans
|
||||
cet incrément.
|
||||
|
||||
Et un cache ne sert **jamais** la machine qui le construit : `client_artefacts` est
|
||||
déployé en dernière couche, donc lors d'une construction *from-zero* les premières
|
||||
machines vont encore à l'amont. Il sert dès le deuxième passage, et à chaque
|
||||
reconstruction — c'est-à-dire exactement le scénario pour lequel il existe.
|
||||
|
||||
## 2026-08-23 — Le résolveur : un service qui répondait à des questions que personne ne posait
|
||||
|
||||
Les hôtes de patient 0 interrogeaient **Quad9**, alors que `infra-dns-01` fait tourner un
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@
|
|||
| P05 | Dependances causales de groupes | AFF-009, AFF-084 | ✅ OK | |
|
||||
| P06 | Validateurs de registres (serveurs/apps/bases/domaines) | AFF-003 | ✅ OK | Registre des domaines valide. |
|
||||
| P07 | GUI (node --check) | AFF-033 | ✅ OK | JS du GUI : syntaxe valide (node --check). |
|
||||
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 31 groupes classes, aucun cycle, aucune arete en arriere. |
|
||||
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 30 rôles, 80 flux, schéma + matrice OK. |
|
||||
| P08 | Orchestration (couches + graphe) | AFF-070 | ✅ OK | Orchestration coherente : 33 groupes classes, aucun cycle, aucune arete en arriere. |
|
||||
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 31 rôles, 82 flux, schéma + matrice OK. |
|
||||
| P10 | Handlers <-> notify | AFF-034, AFF-035 | ✅ OK | Tout notify pointe vers un handler du meme role (49 roles). |
|
||||
| P11 | Syntaxe des playbooks (--syntax-check) | AFF-083 | ✅ OK | playbook: playbooks/proxmox/cloner_vm_debian.yml |
|
||||
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
|
||||
|
|
@ -36,21 +36,21 @@
|
|||
| P21 | Federation : aucun index en collision | AFF-102 | ✅ OK | Federation coherente : 3 instance(s) federee(s), aucun index en collision. |
|
||||
| P22 | Plan de recette a jour (genere du wiki) | AFF-002 | ✅ OK | Plan de recette à jour (22 sections). |
|
||||
| P23 | Underlay sans collision avec la plage tenant | AFF-103 | ✅ OK | Underlay conforme : 7 reseau(x), aucune collision avec la plage tenant. |
|
||||
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 51 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,192.168.254.2/32,192.168.255.2/32. |
|
||||
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 47 groupe(s), 76 regle(s). |
|
||||
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 4 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). |
|
||||
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | CONFORME : frontiere nord/sud, 52 regles, 15 routes, admin=10.0.0.0/24,10.17.0.0/24,192.168.254.2/32,192.168.255.2/32. |
|
||||
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 48 groupe(s), 77 regle(s). |
|
||||
| P26 | Integrations universelles : aucun hote laisse de cote | AFF-108 | ✅ OK | 14 hote(s) x 5 integration(s) universelle(s) : aucune lacune, aucune recopie (1 exemption(s) derivee(s) du service rendu). |
|
||||
| P27 | Propriete des intrants : hebergeur et tenant separes | AFF-109 | ✅ OK | 8 cle(s) de cluster chez l'hebergeur, aucune recopiee dans les group_vars du tenant. |
|
||||
| P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 3 pool(s) Proxmox, 33 VM placee(s), aucun nom ni VMID en collision. |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 24 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 13, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv |
|
||||
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 25 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 14, 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 | 47 scripts expliques et atteignables, 98 cibles make documentees, 55 roles avec README. |
|
||||
| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 47 scripts expliques et atteignables, 98 cibles make documentees, 57 roles avec README. |
|
||||
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 34 exigence(s) de role, toutes satisfaites (118 cle(s) declaree(s) par l'instance). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 32 revendication(s) de port, aucune collision entre roles co-localises (33 groupes). |
|
||||
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (33 groupes). |
|
||||
| P34 | Chaque document declare son lecteur | — | ✅ OK | 41 document(s) declarent leur lecteur (21 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). |
|
||||
| P36 | Tout detenteur d'etat porte une sauvegarde | — | ✅ OK | 9 hote(s) detiennent de l'etat, tous porteurs de `client_backup` (9 groupe(s) au catalogue). |
|
||||
| P37 | Le placement du tenant existe chez son hebergeur | — | ✅ OK | placement confronte a l'hebergeur monte (SITE-Chezlepro) : noeud, stockage, pont — tous offerts. |
|
||||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 30 role(s) serveur/client tous nommes, 31 groupe(s) cite(s) en table existent tous. |
|
||||
| P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 32 role(s) serveur/client tous nommes, 33 groupe(s) cite(s) en table existent tous. |
|
||||
| P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 27 page(s) de wiki toutes atteignables. |
|
||||
| P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. |
|
||||
| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 43 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||
|
|
|
|||
|
|
@ -50,6 +50,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 |
|
||||
| Agents de flotte | `client_metrique`, `client_journal`, `client_smtp` | collecte et relais sur toute la flotte |
|
||||
|
||||
*Rôles retirés (2026-07-04, supersédés ou hors conception)* : `serveur_sendmail`
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ couches:
|
|||
- serveur_dovecot
|
||||
- serveur_postfix
|
||||
- serveur_backup
|
||||
# La source d'artefacts vient AVANT ceux qui installent des paquets — c'est tout
|
||||
# son objet. Placee plus tard, elle serait remplie apres avoir servi.
|
||||
- serveur_artefacts
|
||||
|
||||
- nom: apps
|
||||
raison: "Les applications métier, qui consomment les services (base, SSO, courriel, edge)."
|
||||
|
|
@ -73,3 +76,4 @@ couches:
|
|||
- client_smtp
|
||||
- client_backup
|
||||
- client_unbound
|
||||
- client_artefacts
|
||||
|
|
|
|||
|
|
@ -100,6 +100,24 @@ groupes:
|
|||
raison: "Le poste d'exploitation clone le genome depuis la forge de l'ecosysteme ; sans elle, il n'a pas de source."
|
||||
surveillance: "Verifier que les depots clones suivent leur amont et qu'ansible repond dans le venv."
|
||||
|
||||
client_artefacts:
|
||||
requiert_groupes_actifs:
|
||||
- serveur_artefacts
|
||||
# L'INTEGRATION SUIT L'EXISTENCE DU SERVICE. Un ecosysteme sans source d'artefacts
|
||||
# prend ses paquets a l'amont : c'est un choix valide, pas une panne. Le role se
|
||||
# desactive alors seul (`client_artefacts_actif` derive de l'inventaire) et RETIRE la
|
||||
# direction posee auparavant -- sans quoi les hotes resteraient braques sur une
|
||||
# machine disparue.
|
||||
sauf_si:
|
||||
serveur_artefacts: { variable: client_artefacts_actif, vaut: false }
|
||||
raison: "Un hote ne peut prendre ses paquets chez lui que si l'ecosysteme heberge une source."
|
||||
surveillance: "Verifier que le cache repond sur 3142 et que les hotes le designent bien."
|
||||
|
||||
serveur_artefacts:
|
||||
requiert_groupes_actifs: []
|
||||
raison: "Un cache apt ne depend d'aucun service de l'ecosysteme : il ne fait que relayer et retenir."
|
||||
surveillance: "Verifier l'ecoute sur 3142, le taux de service depuis le journal, et l'espace du cache."
|
||||
|
||||
serveur_nextcloud:
|
||||
requiert_groupes_actifs:
|
||||
- serveur_postgresql
|
||||
|
|
|
|||
19
playbooks/groupes/client_artefacts.yml
Normal file
19
playbooks/groupes/client_artefacts.yml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
- name: Appliquer le groupe client_artefacts
|
||||
hosts: client_artefacts
|
||||
become: true
|
||||
module_defaults:
|
||||
ansible.builtin.apt:
|
||||
lock_timeout: 300
|
||||
|
||||
gather_facts: true
|
||||
|
||||
pre_tasks:
|
||||
- name: Vérifier que la cible est Debian
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts.distribution == "Debian"
|
||||
fail_msg: "Ce playbook est prévu pour Debian."
|
||||
|
||||
roles:
|
||||
- client_artefacts
|
||||
19
playbooks/groupes/serveur_artefacts.yml
Normal file
19
playbooks/groupes/serveur_artefacts.yml
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
---
|
||||
- name: Appliquer le groupe serveur_artefacts
|
||||
hosts: serveur_artefacts
|
||||
become: true
|
||||
module_defaults:
|
||||
ansible.builtin.apt:
|
||||
lock_timeout: 300
|
||||
|
||||
gather_facts: true
|
||||
|
||||
pre_tasks:
|
||||
- name: Vérifier que la cible est Debian
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- ansible_facts.distribution == "Debian"
|
||||
fail_msg: "Ce playbook est prévu pour Debian."
|
||||
|
||||
roles:
|
||||
- serveur_artefacts
|
||||
39
roles/client_artefacts/README.md
Normal file
39
roles/client_artefacts/README.md
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
# client_artefacts
|
||||
|
||||
**Intégration cliente** : l'hôte prend ses paquets à la source de l'écosystème
|
||||
(`serveur_artefacts`) plutôt que chez Debian.
|
||||
|
||||
## Principe
|
||||
|
||||
Une ligne dans `/etc/apt/apt.conf.d/`. C'est tout ce que ce rôle fait, et c'est
|
||||
délibérément tout ce qu'il fait.
|
||||
|
||||
```
|
||||
Acquire::http::Proxy "http://<hôte-source>.<domaine>:3142";
|
||||
```
|
||||
|
||||
## Elle suit l'existence du service — elle ne se déclare pas
|
||||
|
||||
`client_artefacts_actif` est **dérivé de l'inventaire** : un groupe `serveur_artefacts`
|
||||
sans hôte, c'est un écosystème qui prend ses paquets à l'amont, et c'est un choix valide.
|
||||
Le rôle ne pose alors rien — et **retire** la direction posée auparavant, sans quoi les
|
||||
hôtes resteraient braqués sur une machine disparue et n'installeraient plus rien. Une
|
||||
intégration qui ne sait pas se retirer est un piège différé.
|
||||
|
||||
C'est la même leçon que pour le SSO, les bases et les dépendances causales : le moteur ne
|
||||
doit pas supposer l'écosystème complet.
|
||||
|
||||
## Un cache ne sert jamais la machine qui le construit
|
||||
|
||||
`client_artefacts` est déployé dans la couche **agents**, en dernier — quand sa cible est
|
||||
debout. Conséquence à connaître : lors d'une construction *from-zero*, les premières
|
||||
machines prennent encore leurs paquets à l'amont, puisque le cache n'existe pas encore.
|
||||
|
||||
Il sert dès le **deuxième passage**, et à chaque reconstruction — c'est-à-dire exactement
|
||||
le scénario pour lequel il existe.
|
||||
|
||||
## Seul le HTTP passe par le cache
|
||||
|
||||
Les dépôts Debian sont servis en HTTP et leur intégrité vient de leurs signatures. Les
|
||||
dépôts tiers en HTTPS continuent d'aller en direct : `client_artefacts_https` existe mais
|
||||
reste à `false` tant que le cache ne sait pas les remapper.
|
||||
33
roles/client_artefacts/defaults/main.yml
Normal file
33
roles/client_artefacts/defaults/main.yml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
---
|
||||
# INTÉGRATION CLIENTE : passer par la source d'artefacts de l'écosystème.
|
||||
#
|
||||
# Une ligne dans `/etc/apt/apt.conf.d/` suffit à ce qu'un hôte prenne ses paquets chez lui
|
||||
# plutôt que chez Debian. C'est tout ce que ce rôle fait — et c'est délibérément tout ce
|
||||
# qu'il fait.
|
||||
|
||||
# DÉRIVÉ de l'inventaire : l'hôte qui porte `serveur_artefacts`. Un écosystème sans source
|
||||
# d'artefacts rend une valeur vide, et le rôle ne pose rien (voir `client_artefacts_actif`).
|
||||
client_artefacts_hote: >-
|
||||
{{ (groups['serveur_artefacts'] | default([]) | first | default('')) }}
|
||||
client_artefacts_port: 3142
|
||||
client_artefacts_fichier: "/etc/apt/apt.conf.d/00-setops-artefacts"
|
||||
|
||||
# L'INTÉGRATION SUIT L'EXISTENCE DU SERVICE — elle ne se déclare pas.
|
||||
#
|
||||
# C'est la quatrième fois que la même leçon se paie dans ce dépôt : le moteur supposait
|
||||
# l'écosystème COMPLET. Un groupe `serveur_artefacts` sans hôte, c'est un écosystème qui
|
||||
# prend ses paquets à la source d'origine, et c'est un choix valide — pas une panne.
|
||||
client_artefacts_actif: "{{ (groups['serveur_artefacts'] | default([])) | length > 0 }}"
|
||||
|
||||
# SEUL LE HTTP PASSE PAR LE CACHE, et c'est voulu.
|
||||
#
|
||||
# Les dépôts Debian (`deb.debian.org`, `security.debian.org`) sont servis en HTTP : leur
|
||||
# intégrité ne vient pas du transport mais des SIGNATURES du dépôt, qu'apt vérifie de
|
||||
# toute façon. Un cache intermédiaire ne peut donc pas les altérer sans se faire prendre.
|
||||
#
|
||||
# Les dépôts tiers en HTTPS (Smallstep, Grafana, Icinga) continuent d'aller en direct :
|
||||
# les faire passer par le cache exigerait soit de casser le TLS de bout en bout, soit de
|
||||
# réécrire leurs sources en `http://cache/<remap>/…`. La seconde voie est propre et
|
||||
# reste à faire — elle n'est pas dans cet incrément, et le dire vaut mieux que le laisser
|
||||
# croire.
|
||||
client_artefacts_https: false
|
||||
12
roles/client_artefacts/meta/integration.yml
Normal file
12
roles/client_artefacts/meta/integration.yml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
---
|
||||
# Politique d'integration. Voir roles/client_metrique/meta/integration.yml pour le
|
||||
# raisonnement, et docs/decisions-architecture.md (D-33).
|
||||
integration:
|
||||
universelle: true
|
||||
raison: >-
|
||||
Tout hote installe des paquets, et doit les prendre chez lui quand l'ecosysteme a
|
||||
une source. Une machine restee braquee sur deb.debian.org est une dependance
|
||||
exterieure de plus, invisible tant que le lien tient.
|
||||
# AUCUNE exemption, pas meme l'hote qui PORTE le cache : lui aussi installe des
|
||||
# paquets, et se servir soi-meme est le cas le plus simple. L'exempter reviendrait a
|
||||
# dire que le cache ne se fait pas confiance.
|
||||
42
roles/client_artefacts/tasks/main.yml
Normal file
42
roles/client_artefacts/tasks/main.yml
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
---
|
||||
- name: Exiger un hôte de source d'artefacts quand l'intégration est active
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- client_artefacts_hote | length > 0
|
||||
fail_msg: >-
|
||||
client_artefacts est actif mais aucun hôte ne porte `serveur_artefacts`.
|
||||
Déclarer le service au plan, ou forcer client_artefacts_actif=false.
|
||||
when: client_artefacts_actif | bool
|
||||
|
||||
- name: Diriger apt vers la source de l'écosystème
|
||||
ansible.builtin.copy:
|
||||
dest: "{{ client_artefacts_fichier }}"
|
||||
content: |
|
||||
// GÉNÉRÉ par Set-OPS (rôle client_artefacts). NE PAS éditer à la main.
|
||||
// Les paquets viennent de l'écosystème, pas de six serveurs étrangers.
|
||||
Acquire::http::Proxy "http://{{ client_artefacts_hote }}.{{ domaine_interne }}:{{ client_artefacts_port }}";
|
||||
{% if client_artefacts_https | bool %}
|
||||
Acquire::https::Proxy "http://{{ client_artefacts_hote }}.{{ domaine_interne }}:{{ client_artefacts_port }}";
|
||||
{% else %}
|
||||
// « DIRECT » EST OBLIGATOIRE, PAS DECORATIF (mesure du 2026-08-23).
|
||||
//
|
||||
// apt fait HERITER `Acquire::https::Proxy` de la valeur HTTP quand elle n'est pas
|
||||
// definie. Poser le seul proxy HTTP envoyait donc AUSSI les depots tiers en HTTPS
|
||||
// dans le cache, qui refuse les tunnels — a juste titre :
|
||||
// « Invalid response from proxy: HTTP/1.0 403 CONNECT denied »
|
||||
// et packages.smallstep.com devenait injoignable pour toute la flotte.
|
||||
Acquire::https::Proxy "DIRECT";
|
||||
{% endif %}
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
when: client_artefacts_actif | bool
|
||||
|
||||
# L'écosystème qui RETIRE sa source d'artefacts doit voir ses hôtes revenir à l'amont,
|
||||
# sans quoi ils resteraient braqués sur une machine disparue et n'installeraient plus
|
||||
# rien. Une intégration qui ne sait pas se retirer est un piège différé.
|
||||
- name: Retirer la direction quand l'écosystème n'a pas de source
|
||||
ansible.builtin.file:
|
||||
path: "{{ client_artefacts_fichier }}"
|
||||
state: absent
|
||||
when: not (client_artefacts_actif | bool)
|
||||
63
roles/serveur_artefacts/README.md
Normal file
63
roles/serveur_artefacts/README.md
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# serveur_artefacts
|
||||
|
||||
**La source d'artefacts de l'écosystème** : un cache apt (apt-cacher-ng) qui sert à toute
|
||||
la flotte les paquets qu'elle installe, au lieu de les faire chercher chez Debian.
|
||||
|
||||
## Principe
|
||||
|
||||
La forge héberge le **code**. Il manquait qui héberge les **binaires**. Pour poser une
|
||||
seule machine, un écosystème allait chercher chez six serveurs étrangers :
|
||||
`deb.debian.org`, `security.debian.org`, `packages.smallstep.com`, `apt.grafana.com`,
|
||||
`packages.icinga.com`, `codeberg.org`.
|
||||
|
||||
Ce n'est pas théorique. `packages.smallstep.com` est intermittent — la même URL pend au
|
||||
premier essai et répond en 0,48 s au second — et a fait échouer un déploiement de
|
||||
quarante minutes sur la **première** machine.
|
||||
|
||||
## Ce que ce rôle couvre, et ce qui est couvert ailleurs
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **dépôts apt** | ce rôle — un cache, servi à la flotte |
|
||||
| **téléchargements directs** (Forgejo, Keycloak, Nextcloud, oauth2-proxy, collections) | le **cache du contrôleur** : il télécharge une fois et pousse par SSH |
|
||||
|
||||
Deux mécanismes parce que ce sont deux problèmes : on ne sert pas un dépôt apt par `scp`.
|
||||
|
||||
## Ce qu'il ne couvre pas encore
|
||||
|
||||
**Les dépôts tiers en HTTPS** (Smallstep, Grafana, Icinga) continuent d'aller en direct.
|
||||
Les faire passer par le cache exigerait soit de casser le TLS de bout en bout, soit de
|
||||
réécrire leurs sources en `http://cache/<remap>/…`. La seconde voie est propre et reste à
|
||||
faire. Le dire vaut mieux que le laisser croire.
|
||||
|
||||
## Le mode hors ligne, qui est aussi l'épreuve
|
||||
|
||||
```yaml
|
||||
serveur_artefacts_hors_ligne: true
|
||||
```
|
||||
|
||||
Le cache cesse d'aller en amont : il sert ce qu'il détient et refuse le reste. C'est
|
||||
l'état d'un écosystème coupé du monde — et **le seul moyen de prouver que le cache sert
|
||||
vraiment**. Tant qu'internet répond, un `apt update` qui réussit ne dit pas d'où vient
|
||||
l'octet.
|
||||
|
||||
## Pourquoi le trafic est en clair, et pourquoi c'est correct
|
||||
|
||||
L'intégrité d'un dépôt apt ne vient pas du transport mais des **signatures**, qu'`apt`
|
||||
vérifie de toute façon. Un intermédiaire ne peut pas altérer un paquet sans se faire
|
||||
prendre. Restreindre *qui* interroge le cache est le travail du pare-feu est-ouest,
|
||||
dérivé de `meta/flux.yml` — pas d'une authentification.
|
||||
|
||||
## Variables principales
|
||||
|
||||
| Variable | Défaut | Rôle |
|
||||
|---|---|---|
|
||||
| `serveur_artefacts_port` | `3142` | port d'écoute |
|
||||
| `serveur_artefacts_cache` | `/var/cache/apt-cacher-ng` | où les paquets sont retenus |
|
||||
| `serveur_artefacts_expiration_jours` | `30` | rétention sans redemande |
|
||||
| `serveur_artefacts_hors_ligne` | `false` | couper l'amont (et éprouver) |
|
||||
|
||||
## Ce que ce rôle ne fait pas
|
||||
|
||||
Il ne sauvegarde rien : un cache se reconstitue en le re-remplissant. Le sauvegarder
|
||||
reviendrait à sauvegarder les binaires de Debian.
|
||||
47
roles/serveur_artefacts/defaults/main.yml
Normal file
47
roles/serveur_artefacts/defaults/main.yml
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
# LA SOURCE D'ARTEFACTS DE L'ÉCOSYSTÈME — apt-cacher-ng.
|
||||
#
|
||||
# Un écosystème qui prétend se reproduire depuis son propre génome allait chercher, pour
|
||||
# poser une seule machine, les binaires de **six serveurs étrangers** : deb.debian.org,
|
||||
# security.debian.org, packages.smallstep.com, apt.grafana.com, packages.icinga.com,
|
||||
# codeberg.org. La forge héberge le CODE ; il manquait qui héberge les BINAIRES.
|
||||
#
|
||||
# CE N'EST PAS THÉORIQUE. Deux cicatrices dans ce dépôt :
|
||||
# - `packages.smallstep.com` est intermittent — la même URL pend au premier essai et
|
||||
# répond en 0,48 s au second. Ça a fait échouer un déploiement de quarante minutes
|
||||
# sur la PREMIÈRE machine (2026-08-23) ;
|
||||
# - `galaxy.ansible.com` est carrément injoignable depuis l'overlay, et c'est très bien
|
||||
# ainsi — on l'a réglé par le cache du contrôleur, pas en ouvrant la frontière.
|
||||
#
|
||||
# CE RÔLE COUVRE LES DÉPÔTS APT. Les téléchargements DIRECTS (binaire Forgejo, Keycloak,
|
||||
# Nextcloud, oauth2-proxy, collections Ansible) sont déjà couverts, autrement : le
|
||||
# contrôleur télécharge une fois dans son cache et pousse par SSH. Deux mécanismes,
|
||||
# parce que ce sont deux problèmes — on ne sert pas un dépôt apt par `scp`.
|
||||
|
||||
serveur_artefacts_paquets:
|
||||
- apt-cacher-ng
|
||||
serveur_artefacts_service: "apt-cacher-ng"
|
||||
serveur_artefacts_port: 3142
|
||||
# L'écoute est large : les clients sont sur d'autres hôtes de la flotte. C'est le pare-feu
|
||||
# est-ouest, dérivé de meta/flux.yml, qui restreint QUI peut s'y adresser — pas l'adresse
|
||||
# d'écoute, qui ne saurait distinguer un voisin légitime d'un autre.
|
||||
serveur_artefacts_ecoute: "0.0.0.0"
|
||||
serveur_artefacts_cache: "/var/cache/apt-cacher-ng"
|
||||
|
||||
# Combien de temps un paquet reste en cache sans être redemandé. 30 jours : assez pour
|
||||
# qu'une reconstruction complète retrouve tout ce que la précédente a téléchargé.
|
||||
serveur_artefacts_expiration_jours: 30
|
||||
|
||||
# --- LE MODE HORS LIGNE, QUI EST AUSSI L'ÉPREUVE -----------------------------
|
||||
#
|
||||
# `true` : le cache ne va PLUS rien chercher en amont — il sert ce qu'il détient, et
|
||||
# refuse le reste. C'est le mode d'un écosystème coupé du monde, et c'est surtout le seul
|
||||
# moyen de PROUVER que le cache sert vraiment : tant qu'internet répond, un `apt update`
|
||||
# qui réussit ne dit pas d'où vient l'octet.
|
||||
#
|
||||
# Laisser à `false` en exploitation normale.
|
||||
serveur_artefacts_hors_ligne: false
|
||||
|
||||
# Journalisation : apt-cacher-ng tient un journal des requêtes servies. C'est lui qui
|
||||
# permet de répondre à « ce paquet, l'avons-nous servi ou est-il venu du dehors ? ».
|
||||
serveur_artefacts_journal: "/var/log/apt-cacher-ng"
|
||||
6
roles/serveur_artefacts/handlers/main.yml
Normal file
6
roles/serveur_artefacts/handlers/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- name: Redémarrer le cache d'artefacts
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ serveur_artefacts_service }}"
|
||||
state: restarted
|
||||
when: not ansible_check_mode
|
||||
14
roles/serveur_artefacts/meta/authentification.yml
Normal file
14
roles/serveur_artefacts/meta/authentification.yml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
---
|
||||
# Position de ce role dans la directive d'authentification (D-38..D-41).
|
||||
# Voir docs/authentification.md. Gardee par la preuve P29.
|
||||
authentification:
|
||||
portee: sans-auth-humaine
|
||||
mecanisme: aucun
|
||||
formulaire_local: sans-objet
|
||||
secours: "Acces SSH a l'hote"
|
||||
raison: >-
|
||||
Un cache de paquets ne sert que des machines, jamais des humains : il n'expose
|
||||
aucune interface a federer. Ce qu'il sert est PUBLIC par nature -- les depots
|
||||
Debian le sont -- et son integrite ne repose pas sur qui demande, mais sur les
|
||||
signatures que le client verifie. Restreindre QUI peut l'interroger est le travail
|
||||
du pare-feu est-ouest, pas d'une authentification.
|
||||
8
roles/serveur_artefacts/meta/empreinte.yml
Normal file
8
roles/serveur_artefacts/meta/empreinte.yml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
# Empreinte ressources — un cache apt ne calcule rien, il stocke. Le disque est la seule
|
||||
# dimension qui compte : un socle Debian complet plus les paquets des rôles tiennent
|
||||
# largement sous 15 Go, et l'expiration recycle le reste.
|
||||
setops_empreinte:
|
||||
coeurs: 1
|
||||
memoire_mo: 512
|
||||
disque_go: 15
|
||||
18
roles/serveur_artefacts/meta/flux.yml
Normal file
18
roles/serveur_artefacts/meta/flux.yml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
---
|
||||
# Flux réseau de la source d'artefacts. Voir docs/flux-conception.md.
|
||||
flux:
|
||||
- sens: ingress
|
||||
port: 3142
|
||||
protocole: tcp
|
||||
pair: flotte
|
||||
chiffrement: clair
|
||||
raison: >-
|
||||
Toute la flotte prend ses paquets ici. En clair, et c'est correct : l'intégrité
|
||||
d'un dépôt apt vient de ses signatures, qu'apt vérifie de toute façon — un
|
||||
intermédiaire ne peut pas altérer un paquet sans se faire prendre.
|
||||
- sens: egress
|
||||
port: 80
|
||||
protocole: tcp
|
||||
pair: externe
|
||||
chiffrement: clair
|
||||
raison: "Remplir le cache depuis les dépôts Debian amont (deb.debian.org, security)."
|
||||
66
roles/serveur_artefacts/tasks/main.yml
Normal file
66
roles/serveur_artefacts/tasks/main.yml
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
---
|
||||
# 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.
|
||||
- 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
|
||||
19
roles/serveur_artefacts/templates/zzz-setops.conf.j2
Normal file
19
roles/serveur_artefacts/templates/zzz-setops.conf.j2
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# GÉNÉRÉ par Set-OPS (rôle serveur_artefacts). NE PAS éditer à la main.
|
||||
#
|
||||
# Lu APRÈS acng.conf et zz_debconf.conf : ces valeurs l'emportent.
|
||||
|
||||
Port: {{ serveur_artefacts_port }}
|
||||
BindAddress: {{ serveur_artefacts_ecoute }}
|
||||
CacheDir: {{ serveur_artefacts_cache }}
|
||||
LogDir: {{ serveur_artefacts_journal }}
|
||||
|
||||
ExThreshold: {{ serveur_artefacts_expiration_jours }}
|
||||
|
||||
{% if serveur_artefacts_hors_ligne | bool %}
|
||||
# MODE HORS LIGNE : le cache ne va plus rien chercher en amont. Il sert ce qu'il détient
|
||||
# et refuse le reste — c'est l'état d'un écosystème coupé du monde, et l'épreuve qui
|
||||
# montre ce que le cache détient VRAIMENT.
|
||||
Offlinemode: 1
|
||||
{% else %}
|
||||
Offlinemode: 0
|
||||
{% endif %}
|
||||
Loading…
Reference in a new issue