paquets tiers : passer par le cache du controleur, plus par Internet
Some checks are pending
verifier / verifier (push) Waiting to run
Some checks are pending
verifier / verifier (push) Waiting to run
Trois depots tiers etaient en HTTPS, et client_artefacts pose Acquire::https::Proxy DIRECT — sans quoi le cache du site refuse les tunnels et aucun depot tiers n est joignable. La ligne est juste ; sa consequence ne l avait pas ete vue : ces trois depots CONTOURNENT le cache, et chaque VM neuve allait les chercher sur Internet a sa naissance. step-cli et alloy sont poses par des integrations UNIVERSELLES. Sans lien, une machine neuve n obtenait ni son client d autorite ni ses metriques — elle n entrait dans aucun flux chiffre. Dernier obstacle a une reconstruction hors ligne, et il tenait dans un mot. make cacher-paquets tire les 21 deb aux versions epinglees, empreinte SHA256 verifiee depuis l index du depot. Le role partage paquets_tiers les depose et les installe EN UN SEUL appel a apt — il sait resoudre un ensemble de fichiers locaux qui se dependent, la ou paquet par paquet echouerait sur l ordre (Icinga en apporte dix-sept). Six roles branches ; chacun retombe sur le depot distant pour ce que le cache n a PAS fourni, et rien d autre. Eprouve pour de vrai : packages.smallstep.com renvoye vers 127.0.0.1, step-cli desinstalle, cache local efface. Le role rejoue installe 0.30.6-1 depuis le cache et la machine obtient ses certificats. Zero echec. Deux defauts de mon propre outil, trouves en le construisant : Smallstep sert son index NON COMPRESSE (404 sur .gz) donc step-cli n etait jamais mis en cache ; et un depot injoignable faisait continue AVANT d incrementer le total, si bien que le script rapportait 20 sur 20 alors qu il en manquait un. Une garde qui ne peut pas echouer ne garde rien — troisieme fois cette semaine. Corrige puis eprouve en le faisant echouer. Reste hors ligne : NTP externe, et l expedition des alertes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
parent
872b5e91aa
commit
42becd0c02
16 changed files with 700 additions and 10 deletions
66
CHANGELOG.md
66
CHANGELOG.md
|
|
@ -1,5 +1,71 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-09-03 — Les paquets non Debian passent par le cache : le dernier obstacle au hors-ligne
|
||||||
|
|
||||||
|
**56 preuves.** Trois depots tiers etaient configures sur la flotte — Smallstep, Icinga,
|
||||||
|
Grafana — **tous en HTTPS**. Or `client_artefacts` pose `Acquire::https::Proxy "DIRECT"`,
|
||||||
|
sans quoi le cache du site refuse les tunnels HTTPS (« 403 CONNECT denied ») et aucun
|
||||||
|
depot tiers n'est joignable.
|
||||||
|
|
||||||
|
Cette ligne est juste, et elle a ete posee pour une bonne raison. Sa CONSEQUENCE n'avait
|
||||||
|
pas ete vue : **ces trois depots contournent le cache par conception**, et chaque VM neuve
|
||||||
|
allait les chercher sur Internet a sa naissance.
|
||||||
|
|
||||||
|
`step-cli` (Smallstep) et `alloy` (Grafana) sont poses par des integrations
|
||||||
|
**universelles** : sans lien, une machine neuve n'obtenait ni son client d'autorite ni ses
|
||||||
|
metriques — elle n'entrait dans aucun flux chiffre. C'etait le dernier obstacle a une
|
||||||
|
reconstruction hors ligne, et il tenait dans un mot : `DIRECT`.
|
||||||
|
|
||||||
|
### Ce qui change
|
||||||
|
|
||||||
|
`make cacher-paquets` lit `paquets-tiers.yml`, va chercher l'index de chaque depot, tire
|
||||||
|
les 21 `.deb` aux **versions epinglees** et verifie l'empreinte SHA256 que l'index annonce.
|
||||||
|
Tout atterrit dans `~/.cache/setops/paquets/`, a cote de Forgejo, Keycloak et Nextcloud —
|
||||||
|
meme patron depuis toujours : telecharger une fois, verifier, deposer ensuite.
|
||||||
|
|
||||||
|
Le role partage `paquets_tiers` les depose et les installe **en un seul appel a apt** : apt
|
||||||
|
sait resoudre un ensemble de fichiers locaux qui se dependent mutuellement, la ou une
|
||||||
|
installation paquet par paquet echouerait sur l'ordre. Icinga en apporte dix-sept dans ce
|
||||||
|
cas.
|
||||||
|
|
||||||
|
Six roles y sont branches — `client_pki`, `client_journal`, `serveur_icinga`,
|
||||||
|
`serveur_icingaweb2`, `serveur_grafana`, `serveur_loki`. Chacun retombe sur le depot
|
||||||
|
distant pour **ce que le cache n'a pas fourni, et rien d'autre** : reinstaller ce que le
|
||||||
|
cache vient de poser ferait un `update_cache` inutile qui, hors ligne, echouerait APRES un
|
||||||
|
travail deja fait.
|
||||||
|
|
||||||
|
### La coupure, eprouvee pour de vrai
|
||||||
|
|
||||||
|
Sur `web-dorsal-01` : `packages.smallstep.com` renvoye vers `127.0.0.1`, `step-cli`
|
||||||
|
desinstalle, cache local efface. Le role rejoue :
|
||||||
|
|
||||||
|
step-cli : 0.30.6-1
|
||||||
|
installe depuis : step-cli_0.30.6-1_amd64_...deb
|
||||||
|
certificat present : 2
|
||||||
|
|
||||||
|
Zero echec. Le depot etait injoignable et la machine a obtenu son certificat.
|
||||||
|
|
||||||
|
### Deux defauts de mon propre outil, trouves en le construisant
|
||||||
|
|
||||||
|
**Smallstep sert son index NON COMPRESSE.** Icinga et Grafana servent `Packages.gz` ;
|
||||||
|
Smallstep sert `Packages` en clair et rend 404 sur le reste. La premiere version ne
|
||||||
|
demandait que `.gz` : le depot echouait, et `step-cli` — le paquet de CHAQUE machine — n'a
|
||||||
|
jamais ete mis en cache.
|
||||||
|
|
||||||
|
**Et le script rapportait « 20 tires, 20 declares ».** Un depot injoignable faisait
|
||||||
|
`continue` AVANT d'incrementer le total : il disparaissait du decompte, et l'echec se
|
||||||
|
lisait comme un succes. Une garde qui ne peut pas echouer ne garde rien — troisieme
|
||||||
|
occurrence cette semaine, cette fois dans un outil ecrit le jour meme.
|
||||||
|
|
||||||
|
Corrige, puis **eprouve en le faisant echouer** : depot rendu injoignable, le script rend
|
||||||
|
`INCOMPLET` et sort en 1 ; tout present, il sort en 0.
|
||||||
|
|
||||||
|
### Ce qui reste hors ligne
|
||||||
|
|
||||||
|
Deux dependances mineures, nommees pour qu'on sache : le temps (NTP externe, la derive est
|
||||||
|
lente) et l'expedition des alertes (le relais livre directement aux MX). La supervision
|
||||||
|
continuerait de VOIR sans pouvoir le DIRE.
|
||||||
|
|
||||||
## 2026-09-02 (6) — Deuxieme reconstruction de validation : une course que la premiere n'avait pas montree
|
## 2026-09-02 (6) — Deuxieme reconstruction de validation : une course que la premiere n'avait pas montree
|
||||||
|
|
||||||
**56 preuves. Reconstruction : 14/14 hotes, 0 echec. `make valider` : 0 echec sur 13
|
**56 preuves. Reconstruction : 14/14 hotes, 0 echec. `make valider` : 0 echec sur 13
|
||||||
|
|
|
||||||
9
Makefile
9
Makefile
|
|
@ -562,6 +562,15 @@ site-verifier: ## Verifie que playbooks/site.yml correspond aux couches declaree
|
||||||
python3 scripts/orchestrer.py verifier
|
python3 scripts/orchestrer.py verifier
|
||||||
|
|
||||||
.PHONY: flux flux-verifier
|
.PHONY: flux flux-verifier
|
||||||
|
cacher-paquets: ## Tire les paquets NON DEBIAN dans le cache du controleur (a faire en ligne)
|
||||||
|
@# CE QUI REND LA RECONSTRUCTION HORS LIGNE POSSIBLE. Trois depots tiers sont en
|
||||||
|
@# HTTPS et contournent le cache du site (`Acquire::https::Proxy "DIRECT"`). Sans
|
||||||
|
@# cette cible, chaque VM neuve va chercher `step-cli` et `alloy` sur Internet — deux
|
||||||
|
@# integrations UNIVERSELLES. A lancer depuis un poste connecte ; le deploiement, lui,
|
||||||
|
@# n'en aura plus besoin.
|
||||||
|
python3 scripts/cacher_paquets.py
|
||||||
|
|
||||||
|
.PHONY: flux
|
||||||
flux: ansible-runtime ## Regenere le registre des flux et les regles nftables depuis les meta/flux.yml
|
flux: ansible-runtime ## Regenere le registre des flux et les regles nftables depuis les meta/flux.yml
|
||||||
python3 scripts/resoudre_flux.py registre
|
python3 scripts/resoudre_flux.py registre
|
||||||
python3 scripts/resoudre_flux.py nftables
|
python3 scripts/resoudre_flux.py nftables
|
||||||
|
|
|
||||||
93
docs/audit/preuve-2026-09-03.md
Normal file
93
docs/audit/preuve-2026-09-03.md
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
# Preuve de conformite — Set-OPS — 2026-09-03
|
||||||
|
|
||||||
|
> Genere par `make prouver` (`scripts/prouver.py`). **Rejouable** : relancer
|
||||||
|
> reproduit ce rapport. Chaque preuve rejoue l'outillage existant du depot ;
|
||||||
|
> aucune validation n'est reimplementee ici. Voir le mode d'emploi :
|
||||||
|
> [`docs/audit/README.md`](README.md), et le registre trace :
|
||||||
|
> [`docs/audit/affirmations.md`](affirmations.md).
|
||||||
|
|
||||||
|
- **Instance** : `instance` — inventaire `instance/inventories/principal/hosts.yml`
|
||||||
|
- **Verdict** : ❌ NON CONFORME (55 OK · 1 echec · 0 saute)
|
||||||
|
|
||||||
|
## Preuves
|
||||||
|
|
||||||
|
| # | Preuve | Affirmations | Statut | Detail |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| P01 | Lint (ansible-lint) | AFF-006 | ✅ OK | [0m[0m |
|
||||||
|
| P02 | Tests unitaires (inventory_host) | — | ✅ OK | >>> le verrou tient : aucune VM n'aurait ete touchee |
|
||||||
|
| P03 | Diff-vide du plan — TOUTES les instances | AFF-001, AFF-004, AFF-030, AFF-031, AFF-032 | ✅ OK | 4 instance(s) verifiee(s) — OPS-Chezlepro-lab, OPS-Chezlepro, OPS-Technolibre, OPS-Patient0 : plan et inventaire applique coincident. |
|
||||||
|
| P04 | Groupes <-> playbooks homonymes | AFF-008 | ✅ OK | |
|
||||||
|
| 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 : 40 groupes classes, aucun cycle, aucune arete en arriere. |
|
||||||
|
| P09 | Flux reseau (schema + matrice) | AFF-071 | ✅ OK | Flux coherents : 38 rôles, 99 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 | serveur_resolveur_site |
|
||||||
|
| P12 | Existence des runbooks cites | AFF-010, AFF-011, AFF-012, AFF-083 | ✅ OK | 17/17 runbooks/registres cites presents. |
|
||||||
|
| P13 | Invariants structurels/doctrinaux | AFF-015, AFF-022, AFF-037, AFF-038, AFF-062 | ✅ OK | LICENSE, socle dossier, pas de couches paralleles, SSH clef-only, nftables off : OK. |
|
||||||
|
| P14 | Pas de chemin lab/ code en dur | AFF-097 | ✅ OK | Aucun chemin instance/inventories/lab/group_vars code en dur. |
|
||||||
|
| P15 | Modele public socle valide | AFF-022, AFF-099 | ✅ OK | Modele public socle : domaines/serveurs/applications/bases valides. |
|
||||||
|
| P16 | Inventaire Ansible complet (--list) | AFF-030 | ✅ OK | 14 hotes, 35 groupes (inventaire dechiffre et parse). |
|
||||||
|
| P17 | Tous les modeles valident (registres + underlay) | AFF-022, AFF-099 | ✅ OK | Les 1 modele(s) decouvert(s) valident. |
|
||||||
|
| P18 | Gabarit de voute complet | AFF-026 | ✅ OK | Gabarit de voute complet : 23 secret(s) exige(s), tous presents. (Voute reelle non lisible ici : verification sautee.) |
|
||||||
|
| P19 | Le GUI couvre le schema du plan | AFF-002, AFF-095 | ✅ OK | GUI : les 28 champ(s) des plans reels sont editables (2 plan(s) inspecte(s)), registres toleres : nomenclature. |
|
||||||
|
| P20 | Adressage 100% derive du seed (aucun stocke) | AFF-001, AFF-003 | ✅ OK | 2 nomenclature(s) : adressage 100% derive du seed index. |
|
||||||
|
| 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 : 13 reseau(x), aucune collision avec la plage tenant. |
|
||||||
|
| P24 | Frontiere nord/sud : acces d'administration declare | AFF-104 | ✅ OK | note : serveur_powerdns declare un port `derive` que le plan du site ne resout pas — aucune regle emise. |
|
||||||
|
| P25 | Pare-feu Proxmox : est-ouest intra-tenant derive | AFF-107 | ✅ OK | CONFORME : pare-feu Proxmox, 3 tenant(s), 50 groupe(s), 80 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 (0 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, 34 VM placee(s), aucun nom ni VMID en collision. |
|
||||||
|
| P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 32 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 21, 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 | 55 scripts expliques et atteignables, 109 cibles make documentees, 65 roles avec README. |
|
||||||
|
| P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 37 exigence(s) de role, toutes satisfaites (131 cle(s) declaree(s) par l'instance). |
|
||||||
|
| P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 33 revendication(s) de port, aucune collision entre roles co-localises (37 groupes). |
|
||||||
|
| P34 | Chaque document declare son lecteur | — | ✅ OK | 42 document(s) declarent leur lecteur (31 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) de l'ecosysteme et 3 du site 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 : 39 role(s) serveur/client tous nommes, 40 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 : 51 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). |
|
||||||
|
| P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 4 edge(s) emettent un certificat portant les noms publies (OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre/principal, OPS-Patient0/product |
|
||||||
|
| P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 104 regle(s) du site. |
|
||||||
|
| P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 4 integration(s) appliquent leur serveur avant leurs clients. |
|
||||||
|
| P45 | Pare-feu Proxmox : arme sur les VNet SDN, jamais ailleurs | — | ✅ OK | Le pare-feu Proxmox ne s'arme que sur un VNet SDN (4 cas evalues, dont un qui doit rendre VRAI). |
|
||||||
|
| P46 | Plancher /etc/hosts : un seul role en decide | — | ✅ OK | Un seul maitre du plancher — roles/hosts_statiques/tasks/main.yml : manage_etc_hosts: false ; et le gabarit maitre est pose (roles/hosts_statiques/templates/hos |
|
||||||
|
| P47 | Zones inverses : couvrir l'occupe, et rien de plus | — | ✅ OK | Les zones inverses couvrent l'occupe et rien de plus (5 cas evalues, dont un site a quatre zones et un tenant a une). |
|
||||||
|
| P48 | La carte d'orientation designe ce qui existe, et compte juste | — | ❌ ECHEC | La carte d'orientation ne dit plus vrai :
|
||||||
|
- « pieces d'audit » : la carte annonce 33, le depot en compte 34 |
|
||||||
|
| P49 | Registre des flux : la matrice d'audit est a jour | — | ✅ OK | Le registre des flux reproduit exactement ce que les `meta/flux.yml` declarent (117 lignes). |
|
||||||
|
| P50 | Silences : un refus muet est declare, place en dernier, et motive | — | ✅ OK | 2 silence(s) declare(s), tous en sequence > 1 (la plus haute des 169 regles `pass`), tous non consignes et tous motives. |
|
||||||
|
| P51 | Collections : toutes declarees, toutes epinglees | — | ✅ OK | 3 collection(s) et 2 bibliotheque(s) Python declarees et epinglees : ansible.posix==1.6.2, community.general==10.3.0, community.postgresql==3.10.2 |
|
||||||
|
| P52 | Materialiser n'exige pas d'entrer dans le tenant | — | ✅ OK | `creer-vm` confirme par l'agent invite (API des hyperviseurs, deja utilisee pour creer), sans exiger d'entrer dans le tenant. |
|
||||||
|
| P53 | L'interne refuse a voix haute, la bordure se tait | — | ✅ OK | L'interne parle, la bordure se tait — 15 ruleset(s) nftables refusent a voix haute ; pare-feu est-ouest en REJECT, source unique ; frontiere muette (actions : b |
|
||||||
|
| P54 | L'insemination ne reclame aucun secret du tenant | — | ✅ OK | 2 couche(s) d'insemination (serveur_debian, serveur_ops), 10 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. |
|
||||||
|
|
||||||
|
## Couverture des affirmations ✅ du registre
|
||||||
|
|
||||||
|
Chaque affirmation ✅ automatisable est couverte par la preuve indiquee ci-dessus.
|
||||||
|
Les ✅ **structurelles/doctrinales** non rejouables par une commande (ex. AFF-005
|
||||||
|
`make`=aide, AFF-014 ciblage groupe, AFF-024 `instancier-appliquer`, AFF-051 autorite
|
||||||
|
d'AGENTS.md, AFF-073/075 gardes `make`, AFF-090 wiki) ont ete verifiees a l'audit ;
|
||||||
|
elles restent hors du harnais recurrent (rien d'executable a rejouer).
|
||||||
|
|
||||||
|
## Declarations d'intention (⚪ invérifiables localement — assumees)
|
||||||
|
|
||||||
|
Ces affirmations ne sont pas rejouables hors production ; elles sont **assumees**
|
||||||
|
comme declarations d'intention, non comme preuves :
|
||||||
|
|
||||||
|
- **AFF-036** — « testables avec `--check` autant que possible » : verifiable seulement
|
||||||
|
contre une flotte vivante.
|
||||||
|
- **AFF-091** — contenu pedagogique du wiki : affirmations conceptuelles.
|
||||||
|
- **AFF-096** — « GUI 100 % francais » : revue exhaustive des libelles rendus, non automatisee.
|
||||||
|
- **AFF-007** — hote d'exemple `web-frontal-01` : placeholder assume.
|
||||||
|
|
||||||
|
_Rapport genere le 2026-09-03._
|
||||||
|
|
@ -23,10 +23,10 @@ README de rôles). Cette page comble ces deux trous.
|
||||||
|
|
||||||
| Ce qu'on compte | Combien | Comment on le mesure |
|
| Ce qu'on compte | Combien | Comment on le mesure |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| rôles | 64 | `roles/*/` |
|
| rôles | 65 | `roles/*/` |
|
||||||
| README de rôles | 64 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette |
|
| README de rôles | 65 | `roles/*/README.md` — l'écart avec la ligne au-dessus est la dette |
|
||||||
| documents | 38 | `docs/*.md` |
|
| documents | 38 | `docs/*.md` |
|
||||||
| pièces d'audit | 33 | `docs/audit/*` |
|
| pièces d'audit | 34 | `docs/audit/*` |
|
||||||
| unités de wiki | 27 | `wiki/*.md` |
|
| unités de wiki | 27 | `wiki/*.md` |
|
||||||
| décisions en vigueur | 79 | lignes `\| **D-nn** \|` de `decisions-architecture.md` |
|
| décisions en vigueur | 79 | lignes `\| **D-nn** \|` de `decisions-architecture.md` |
|
||||||
| décisions renversées | 3 | lignes `\| **D-nn** —` du même document |
|
| décisions renversées | 3 | lignes `\| **D-nn** —` du même document |
|
||||||
|
|
|
||||||
59
paquets-tiers.yml
Normal file
59
paquets-tiers.yml
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
---
|
||||||
|
# LES PAQUETS QUI NE VIENNENT PAS DE DEBIAN — et pourquoi ils sont ici.
|
||||||
|
#
|
||||||
|
# Trois dépôts tiers sont configurés sur la flotte, tous en HTTPS. Or `client_artefacts`
|
||||||
|
# pose `Acquire::https::Proxy "DIRECT"` — obligatoire, parce que le cache refuse les
|
||||||
|
# tunnels HTTPS (« 403 CONNECT denied ») et que sans cette ligne aucun dépôt tiers
|
||||||
|
# n'était joignable. La conséquence n'avait pas été vue : **ces trois dépôts contournent
|
||||||
|
# le cache par conception**, et chaque construction de VM va les chercher sur Internet.
|
||||||
|
#
|
||||||
|
# CE QUE ÇA COÛTAIT. `client_pki` est une intégration UNIVERSELLE : chaque machine de
|
||||||
|
# chaque écosystème installe `step-cli` depuis Smallstep au moment de sa naissance. Sans
|
||||||
|
# Internet, une VM neuve n'obtient pas son client d'AC, donc pas de certificat, donc
|
||||||
|
# n'entre dans aucun flux chiffré. `alloy` (métriques) est dans le même cas.
|
||||||
|
#
|
||||||
|
# Les versions sont ÉPINGLÉES, comme les collections Ansible. Une mise à niveau devient
|
||||||
|
# alors un geste délibéré — `make cacher-paquets` — au lieu d'arriver toute seule le jour
|
||||||
|
# où l'amont publie.
|
||||||
|
depots:
|
||||||
|
smallstep:
|
||||||
|
uri: https://packages.smallstep.com/stable/debian
|
||||||
|
suite: debs
|
||||||
|
composant: main
|
||||||
|
# Sur CHAQUE machine, via `client_pki`. Le paquet le plus critique de la liste.
|
||||||
|
paquets:
|
||||||
|
step-cli: 0.30.6-1
|
||||||
|
|
||||||
|
icinga:
|
||||||
|
uri: https://packages.icinga.com/debian
|
||||||
|
suite: icinga-trixie
|
||||||
|
composant: main
|
||||||
|
# La supervision et sa vue web, avec leurs dépendances PHP propres au dépôt.
|
||||||
|
paquets:
|
||||||
|
icinga2: 2.16.5-1+debian13
|
||||||
|
icinga2-bin: 2.16.5-1+debian13
|
||||||
|
icinga2-common: 2.16.5-1+debian13
|
||||||
|
icinga2-doc: 2.16.5-1+debian13
|
||||||
|
icinga-archive-keyring: 2.0.0-1+debian13
|
||||||
|
icingacli: 2.14.0-2+debian13
|
||||||
|
icingadb: 1.5.1-8+debian13
|
||||||
|
icingadb-redis: 8.2.9-1+debian13
|
||||||
|
icingadb-web: 1.4.0-1+debian13
|
||||||
|
icinga-l10n: 1.4.0-1+debian13
|
||||||
|
icinga-php-legacy: 1.1.0-1+debian13
|
||||||
|
icinga-php-library: 1.0.0-2+debian13
|
||||||
|
icinga-php-thirdparty: 1.0.0-2+debian13
|
||||||
|
icingaweb2: 2.14.0-2+debian13
|
||||||
|
icingaweb2-common: 2.14.0-2+debian13
|
||||||
|
icingaweb2-module-monitoring: 2.12.6-1+debian13
|
||||||
|
php-icinga: 2.14.0-2+debian13
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
uri: https://apt.grafana.com
|
||||||
|
suite: stable
|
||||||
|
composant: main
|
||||||
|
# `alloy` est universel comme `step-cli` : `client_metrique` le pose partout.
|
||||||
|
paquets:
|
||||||
|
alloy: 1.19.2-1
|
||||||
|
grafana: 13.2.0
|
||||||
|
loki: 3.7.7
|
||||||
|
|
@ -36,12 +36,30 @@
|
||||||
filename: grafana
|
filename: grafana
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT POUR LE RESTE (2026-09-03).
|
||||||
|
#
|
||||||
|
# Les depots tiers sont en HTTPS, et `client_artefacts` pose
|
||||||
|
# `Acquire::https::Proxy "DIRECT"` — ils CONTOURNENT donc le cache du site et sortent sur
|
||||||
|
# Internet a chaque construction de VM. `make cacher-paquets` les tire une fois, versions
|
||||||
|
# epinglees et empreintes verifiees ; ce role les depose depuis ce cache.
|
||||||
|
- name: Poser les paquets tiers depuis le cache du controleur
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: paquets_tiers
|
||||||
|
vars:
|
||||||
|
paquets_tiers_noms: "{{ client_journal_paquets }}"
|
||||||
|
|
||||||
- name: Installer Grafana Alloy
|
- name: Installer Grafana Alloy
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: "{{ client_journal_paquets }}"
|
# CE QUE LE CACHE N'A PAS FOURNI, ET RIEN D'AUTRE. Les dependances Debian passent
|
||||||
|
# par le cache du site en HTTP ; seuls les paquets tiers non caches exigent Internet.
|
||||||
|
# Reinstaller ce que le cache vient de poser ferait un `update_cache` inutile — et
|
||||||
|
# hors ligne, il echouerait APRES un travail deja fait.
|
||||||
|
name: "{{ client_journal_paquets | difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
|
||||||
|
when: (client_journal_paquets
|
||||||
|
| difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0
|
||||||
- name: Autoriser Alloy a lire le journal systeme
|
- name: Autoriser Alloy a lire le journal systeme
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
name: "{{ client_journal_utilisateur }}"
|
name: "{{ client_journal_utilisateur }}"
|
||||||
|
|
|
||||||
|
|
@ -88,11 +88,39 @@
|
||||||
group: root
|
group: root
|
||||||
mode: "0644"
|
mode: "0644"
|
||||||
|
|
||||||
- name: Installer step-cli
|
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT ENSUITE (2026-09-03).
|
||||||
|
#
|
||||||
|
# `client_pki` est une integration UNIVERSELLE : chaque machine de chaque ecosysteme
|
||||||
|
# installe `step-cli` ici, a sa naissance. Le depot Smallstep est en HTTPS, et
|
||||||
|
# `client_artefacts` pose `Acquire::https::Proxy "DIRECT"` — il CONTOURNE donc le cache
|
||||||
|
# du site et sort sur Internet. Sans lien, une VM neuve n'obtenait pas son client
|
||||||
|
# d'autorite, donc pas de certificat, donc n'entrait dans aucun flux chiffre.
|
||||||
|
#
|
||||||
|
# C'etait le dernier obstacle a une reconstruction hors ligne, et il tenait dans un mot :
|
||||||
|
# `DIRECT`, pose a juste titre pour une autre raison.
|
||||||
|
- name: Poser step-cli depuis le cache du controleur, s'il y est
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: paquets_tiers
|
||||||
|
vars:
|
||||||
|
paquets_tiers_noms: "{{ client_pki_paquets }}"
|
||||||
|
|
||||||
|
# FILET, ET SEULEMENT SI LE CACHE N'A RIEN DONNE.
|
||||||
|
#
|
||||||
|
# `update_cache: true` interroge TOUS les depots configures, Smallstep compris. Hors
|
||||||
|
# ligne, cette tache echouerait donc APRES que le cache ait deja pose le paquet — le
|
||||||
|
# deploiement tomberait sur un travail deja fait. Le repli ne doit exister que quand il
|
||||||
|
# y a quelque chose a rattraper.
|
||||||
|
#
|
||||||
|
# Cache vide — poste jamais connecte, paquet retire de la declaration — on retombe sur le
|
||||||
|
# depot distant comme avant. Degrader, jamais deviner.
|
||||||
|
- name: Installer depuis le depot distant ce que le cache n'a pas fourni
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: "{{ client_pki_paquets }}"
|
name: "{{ client_pki_paquets
|
||||||
|
| difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
when: (client_pki_paquets
|
||||||
|
| difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0
|
||||||
|
|
||||||
- name: Creer le repertoire STEPPATH des certificats
|
- name: Creer le repertoire STEPPATH des certificats
|
||||||
ansible.builtin.file:
|
ansible.builtin.file:
|
||||||
|
|
|
||||||
69
roles/paquets_tiers/README.md
Normal file
69
roles/paquets_tiers/README.md
Normal file
|
|
@ -0,0 +1,69 @@
|
||||||
|
# `paquets_tiers` — poser ce que Debian ne fournit pas, sans Internet
|
||||||
|
|
||||||
|
> **Pour qui :** celui qui prépare une reconstruction hors ligne, ou qui se demande
|
||||||
|
> pourquoi une VM neuve va chercher un paquet sur Internet.
|
||||||
|
|
||||||
|
## Le problème qu'il ferme
|
||||||
|
|
||||||
|
Trois dépôts tiers sont configurés sur la flotte — Smallstep, Icinga, Grafana — **tous en
|
||||||
|
HTTPS**. Or `client_artefacts` pose `Acquire::https::Proxy "DIRECT"`, sans quoi le cache
|
||||||
|
du site refuse les tunnels HTTPS (« 403 CONNECT denied ») et aucun dépôt tiers n'est
|
||||||
|
joignable.
|
||||||
|
|
||||||
|
Cette ligne est juste. Sa conséquence n'avait pas été vue : **ces trois dépôts contournent
|
||||||
|
le cache par conception**, et chaque VM allait les chercher sur Internet à sa naissance.
|
||||||
|
|
||||||
|
`step-cli` (Smallstep) et `alloy` (Grafana) sont posés par des intégrations
|
||||||
|
**universelles**. Sans lien, une machine neuve n'obtenait ni son client d'autorité ni ses
|
||||||
|
métriques — elle n'entrait dans aucun flux chiffré. C'était le dernier obstacle à une
|
||||||
|
reconstruction hors ligne.
|
||||||
|
|
||||||
|
## Comment il s'en sort
|
||||||
|
|
||||||
|
`make cacher-paquets` lit `paquets-tiers.yml`, va chercher l'index de chaque dépôt, tire
|
||||||
|
les `.deb` aux **versions épinglées** et vérifie leur empreinte SHA256 — celle que l'index
|
||||||
|
annonce. Tout atterrit dans `~/.cache/setops/paquets/`, à côté de Forgejo, Keycloak et
|
||||||
|
Nextcloud, qui suivent le même patron depuis toujours.
|
||||||
|
|
||||||
|
Ce rôle dépose ensuite ces fichiers sur l'hôte et les installe **en un seul appel à
|
||||||
|
`apt`** — c'est le point : apt sait résoudre un ensemble de fichiers locaux qui se
|
||||||
|
dépendent mutuellement, alors qu'une installation paquet par paquet échouerait sur
|
||||||
|
l'ordre. Icinga en apporte dix-sept dans ce cas.
|
||||||
|
|
||||||
|
Les **dépendances Debian** restent l'affaire d'apt et du cache du site, en HTTP. Ce rôle
|
||||||
|
ne s'occupe que de ce que Debian ne fournit pas.
|
||||||
|
|
||||||
|
## Ce qu'il fait quand le cache est vide
|
||||||
|
|
||||||
|
Rien, et il le **dit**. L'appelant retombe alors sur le dépôt distant, comme avant. Un
|
||||||
|
déploiement connecté ne casse pas parce qu'un cache manque — mais on sait, à la lecture du
|
||||||
|
journal, qu'une reconstruction hors ligne échouerait là.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
- name: Poser les paquets tiers depuis le cache du contrôleur
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: paquets_tiers
|
||||||
|
vars:
|
||||||
|
paquets_tiers_noms: "{{ mon_role_paquets }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Puis, pour le reste seulement :
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
name: "{{ mon_role_paquets
|
||||||
|
| difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Éprouver la coupure
|
||||||
|
|
||||||
|
Rendre le dépôt injoignable sur un hôte, retirer le paquet, rejouer le rôle :
|
||||||
|
|
||||||
|
```bash
|
||||||
|
echo "127.0.0.1 packages.smallstep.com" >> /etc/hosts
|
||||||
|
apt-get remove -y step-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
Mesuré le 2026-09-03 : `step-cli 0.30.6-1` s'installe depuis le cache, la machine obtient
|
||||||
|
ses certificats, zéro échec.
|
||||||
22
roles/paquets_tiers/defaults/main.yml
Normal file
22
roles/paquets_tiers/defaults/main.yml
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
---
|
||||||
|
# LES PAQUETS QUE DEBIAN NE FOURNIT PAS, SERVIS DEPUIS LE CACHE DU CONTRÔLEUR.
|
||||||
|
#
|
||||||
|
# Trois dépôts tiers sont configurés sur la flotte — Smallstep, Icinga, Grafana — tous en
|
||||||
|
# HTTPS. Or `client_artefacts` pose `Acquire::https::Proxy "DIRECT"`, sans quoi le cache
|
||||||
|
# du site refuse les tunnels HTTPS et aucun dépôt tiers n'est joignable. La conséquence
|
||||||
|
# n'avait pas été vue : **ces trois dépôts contournent le cache par conception**, et
|
||||||
|
# chaque VM neuve allait les chercher sur Internet à sa naissance.
|
||||||
|
#
|
||||||
|
# `step-cli` (Smallstep) et `alloy` (Grafana) sont posés par des intégrations
|
||||||
|
# UNIVERSELLES : sans Internet, une machine neuve n'obtenait ni son client d'autorité ni
|
||||||
|
# ses métriques. C'était le dernier obstacle à une reconstruction hors ligne.
|
||||||
|
#
|
||||||
|
# `make cacher-paquets` les tire UNE FOIS, versions épinglées et empreintes vérifiées.
|
||||||
|
# Ce rôle les dépose depuis ce cache. Les DÉPENDANCES restent l'affaire d'apt : ce sont
|
||||||
|
# des paquets Debian, qui passent déjà par le cache du site en HTTP.
|
||||||
|
paquets_tiers_noms: []
|
||||||
|
|
||||||
|
# Le cache vit chez le CONTRÔLEUR, comme celui de Forgejo, Keycloak et Nextcloud : on
|
||||||
|
# télécharge une fois depuis un poste qui a Internet, on déploie ensuite sans en avoir.
|
||||||
|
paquets_tiers_cache: "{{ lookup('env', 'HOME') }}/.cache/setops/paquets"
|
||||||
|
paquets_tiers_dest: "/var/cache/setops/paquets"
|
||||||
3
roles/paquets_tiers/meta/main.yml
Normal file
3
roles/paquets_tiers/meta/main.yml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
# Aucune dépendance : ce rôle est appelé PAR ceux qui installent, jamais l'inverse.
|
||||||
|
dependencies: []
|
||||||
85
roles/paquets_tiers/tasks/main.yml
Normal file
85
roles/paquets_tiers/tasks/main.yml
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
---
|
||||||
|
# DÉGRADER, JAMAIS DEVINER. Sans cache sur le contrôleur, ce rôle ne fait rien et le DIT :
|
||||||
|
# l'appelant installera depuis le dépôt distant, comme avant. On ne casse pas un
|
||||||
|
# déploiement connecté parce qu'un cache manque.
|
||||||
|
- name: Lire le manifeste du cache du contrôleur
|
||||||
|
ansible.builtin.slurp:
|
||||||
|
src: "{{ paquets_tiers_cache }}/MANIFESTE"
|
||||||
|
delegate_to: localhost
|
||||||
|
become: false
|
||||||
|
register: paquets_tiers_manifeste
|
||||||
|
failed_when: false
|
||||||
|
changed_when: false
|
||||||
|
|
||||||
|
# REMISE A ZERO AVANT DE REMPLIR. Un `set_fact` survit au role : appele deux fois dans
|
||||||
|
# le meme play, ce dictionnaire garderait les paquets du premier appelant et les
|
||||||
|
# reinstallerait chez le second. Une variable qui traine est une action qu'on ne demande
|
||||||
|
# plus mais qui a toujours lieu.
|
||||||
|
- name: Repartir d'un inventaire vide
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
paquets_tiers_disponibles: {}
|
||||||
|
|
||||||
|
# Le manifeste porte une ligne `nom=version fichier.deb` par paquet mis en cache. On n'en
|
||||||
|
# retient que les noms que l'appelant a DEMANDES : un role n'installe pas ce qu'il n'a
|
||||||
|
# pas declare vouloir, meme si le cache le contient.
|
||||||
|
- name: Établir ce que le cache peut fournir
|
||||||
|
ansible.builtin.set_fact:
|
||||||
|
paquets_tiers_disponibles: >-
|
||||||
|
{{ paquets_tiers_disponibles
|
||||||
|
| combine({ (item.split(' ')[0].split('=')[0]): item.split(' ')[1] }) }}
|
||||||
|
loop: "{{ (paquets_tiers_manifeste.content | default('') | b64decode).splitlines() }}"
|
||||||
|
when:
|
||||||
|
- item is search('=')
|
||||||
|
- item.split(' ') | length > 1
|
||||||
|
- item.split(' ')[0].split('=')[0] in paquets_tiers_noms
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.split(' ')[0] }}"
|
||||||
|
|
||||||
|
- name: Dire que le cache ne couvre pas ces paquets
|
||||||
|
ansible.builtin.debug:
|
||||||
|
msg: >-
|
||||||
|
Aucun paquet en cache pour {{ paquets_tiers_noms | join(', ') }} — ils seront tirés
|
||||||
|
du dépôt distant. Une reconstruction SANS INTERNET échouera ici.
|
||||||
|
Lancer `make cacher-paquets` depuis un poste connecté.
|
||||||
|
when:
|
||||||
|
- paquets_tiers_noms | length > 0
|
||||||
|
- paquets_tiers_disponibles | length == 0
|
||||||
|
|
||||||
|
- name: Assurer le répertoire des paquets déposés
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ paquets_tiers_dest }}"
|
||||||
|
state: directory
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0755"
|
||||||
|
when: paquets_tiers_disponibles | length > 0
|
||||||
|
|
||||||
|
- name: Déposer les paquets depuis le cache du contrôleur
|
||||||
|
ansible.builtin.copy:
|
||||||
|
src: "{{ paquets_tiers_cache }}/{{ item.value }}"
|
||||||
|
dest: "{{ paquets_tiers_dest }}/{{ item.value }}"
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
loop: "{{ paquets_tiers_disponibles | dict2items }}"
|
||||||
|
loop_control:
|
||||||
|
label: "{{ item.key }}"
|
||||||
|
|
||||||
|
# EN UN SEUL APPEL, ET C'EST LE POINT. `apt` sait résoudre un ENSEMBLE de fichiers locaux
|
||||||
|
# — l'un dépendant de l'autre — alors qu'une installation paquet par paquet échouerait sur
|
||||||
|
# l'ordre. Icinga en apporte dix-sept qui se dépendent mutuellement : les poser un à un
|
||||||
|
# demanderait de connaître leur graphe, que seul apt connaît.
|
||||||
|
#
|
||||||
|
# Les dépendances Debian, elles, viennent du cache du site en HTTP, comme d'habitude.
|
||||||
|
- name: Installer les paquets tiers depuis le cache
|
||||||
|
ansible.builtin.command:
|
||||||
|
argv: >-
|
||||||
|
{{ ["apt-get", "install", "-y", "--no-install-recommends"]
|
||||||
|
+ (paquets_tiers_disponibles.values()
|
||||||
|
| map('regex_replace', '^', paquets_tiers_dest ~ '/') | list) }}
|
||||||
|
environment:
|
||||||
|
DEBIAN_FRONTEND: noninteractive
|
||||||
|
register: paquets_tiers_pose
|
||||||
|
changed_when: "'0 nouvellement installés' not in paquets_tiers_pose.stdout
|
||||||
|
and '0 newly installed' not in paquets_tiers_pose.stdout"
|
||||||
|
when: paquets_tiers_disponibles | length > 0
|
||||||
|
|
@ -50,12 +50,30 @@
|
||||||
filename: grafana
|
filename: grafana
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT POUR LE RESTE (2026-09-03).
|
||||||
|
#
|
||||||
|
# Les depots tiers sont en HTTPS, et `client_artefacts` pose
|
||||||
|
# `Acquire::https::Proxy "DIRECT"` — ils CONTOURNENT donc le cache du site et sortent sur
|
||||||
|
# Internet a chaque construction de VM. `make cacher-paquets` les tire une fois, versions
|
||||||
|
# epinglees et empreintes verifiees ; ce role les depose depuis ce cache.
|
||||||
|
- name: Poser les paquets tiers depuis le cache du controleur
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: paquets_tiers
|
||||||
|
vars:
|
||||||
|
paquets_tiers_noms: "{{ serveur_grafana_paquets }}"
|
||||||
|
|
||||||
- name: Installer Grafana
|
- name: Installer Grafana
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: "{{ serveur_grafana_paquets }}"
|
# CE QUE LE CACHE N'A PAS FOURNI, ET RIEN D'AUTRE. Les dependances Debian passent
|
||||||
|
# par le cache du site en HTTP ; seuls les paquets tiers non caches exigent Internet.
|
||||||
|
# Reinstaller ce que le cache vient de poser ferait un `update_cache` inutile — et
|
||||||
|
# hors ligne, il echouerait APRES un travail deja fait.
|
||||||
|
name: "{{ serveur_grafana_paquets | difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
|
||||||
|
when: (serveur_grafana_paquets
|
||||||
|
| difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0
|
||||||
- name: Provisionner les datasources (Prometheus + Loki)
|
- name: Provisionner les datasources (Prometheus + Loki)
|
||||||
ansible.builtin.template:
|
ansible.builtin.template:
|
||||||
src: datasources.yaml.j2
|
src: datasources.yaml.j2
|
||||||
|
|
|
||||||
|
|
@ -31,12 +31,30 @@
|
||||||
filename: icinga
|
filename: icinga
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT POUR LE RESTE (2026-09-03).
|
||||||
|
#
|
||||||
|
# Les depots tiers sont en HTTPS, et `client_artefacts` pose
|
||||||
|
# `Acquire::https::Proxy "DIRECT"` — ils CONTOURNENT donc le cache du site et sortent sur
|
||||||
|
# Internet a chaque construction de VM. `make cacher-paquets` les tire une fois, versions
|
||||||
|
# epinglees et empreintes verifiees ; ce role les depose depuis ce cache.
|
||||||
|
- name: Poser les paquets tiers depuis le cache du controleur
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: paquets_tiers
|
||||||
|
vars:
|
||||||
|
paquets_tiers_noms: "{{ serveur_icinga_paquets }}"
|
||||||
|
|
||||||
- name: Installer Icinga 2, Icinga DB et Redis dedie
|
- name: Installer Icinga 2, Icinga DB et Redis dedie
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: "{{ serveur_icinga_paquets }}"
|
# CE QUE LE CACHE N'A PAS FOURNI, ET RIEN D'AUTRE. Les dependances Debian passent
|
||||||
|
# par le cache du site en HTTP ; seuls les paquets tiers non caches exigent Internet.
|
||||||
|
# Reinstaller ce que le cache vient de poser ferait un `update_cache` inutile — et
|
||||||
|
# hors ligne, il echouerait APRES un travail deja fait.
|
||||||
|
name: "{{ serveur_icinga_paquets | difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
|
||||||
|
when: (serveur_icinga_paquets
|
||||||
|
| difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0
|
||||||
- name: Resoudre la base de donnees depuis le registre (role partage)
|
- name: Resoudre la base de donnees depuis le registre (role partage)
|
||||||
ansible.builtin.include_role:
|
ansible.builtin.include_role:
|
||||||
name: resoudre_base
|
name: resoudre_base
|
||||||
|
|
|
||||||
|
|
@ -19,11 +19,30 @@
|
||||||
serveur_icingaweb2_ldap_bind_password: "{{ resoudre_annuaire_bind_password }}"
|
serveur_icingaweb2_ldap_bind_password: "{{ resoudre_annuaire_bind_password }}"
|
||||||
no_log: true
|
no_log: true
|
||||||
|
|
||||||
|
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT POUR LE RESTE (2026-09-03).
|
||||||
|
#
|
||||||
|
# Les depots tiers sont en HTTPS, et `client_artefacts` pose
|
||||||
|
# `Acquire::https::Proxy "DIRECT"` — ils CONTOURNENT donc le cache du site et sortent sur
|
||||||
|
# Internet a chaque construction de VM. `make cacher-paquets` les tire une fois, versions
|
||||||
|
# epinglees et empreintes verifiees ; ce role les depose depuis ce cache.
|
||||||
|
- name: Poser les paquets tiers depuis le cache du controleur
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: paquets_tiers
|
||||||
|
vars:
|
||||||
|
paquets_tiers_noms: "{{ serveur_icingaweb2_paquets }}"
|
||||||
|
|
||||||
- name: Installer Icinga Web 2 + module IcingaDB + PHP-FPM + nginx
|
- name: Installer Icinga Web 2 + module IcingaDB + PHP-FPM + nginx
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: "{{ serveur_icingaweb2_paquets }}"
|
# CE QUE LE CACHE N'A PAS FOURNI, ET RIEN D'AUTRE. Les dependances Debian passent
|
||||||
|
# par le cache du site en HTTP ; seuls les paquets tiers non caches exigent Internet.
|
||||||
|
# Reinstaller ce que le cache vient de poser ferait un `update_cache` inutile — et
|
||||||
|
# hors ligne, il echouerait APRES un travail deja fait.
|
||||||
|
name: "{{ serveur_icingaweb2_paquets
|
||||||
|
| difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
when: (serveur_icingaweb2_paquets
|
||||||
|
| difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0
|
||||||
|
|
||||||
- name: Ajouter www-data au groupe icingaweb2 (lecture de la config)
|
- name: Ajouter www-data au groupe icingaweb2 (lecture de la config)
|
||||||
ansible.builtin.user:
|
ansible.builtin.user:
|
||||||
|
|
|
||||||
|
|
@ -36,12 +36,30 @@
|
||||||
filename: grafana
|
filename: grafana
|
||||||
state: present
|
state: present
|
||||||
|
|
||||||
|
# LE CACHE DU CONTROLEUR D'ABORD, LE DEPOT DISTANT POUR LE RESTE (2026-09-03).
|
||||||
|
#
|
||||||
|
# Les depots tiers sont en HTTPS, et `client_artefacts` pose
|
||||||
|
# `Acquire::https::Proxy "DIRECT"` — ils CONTOURNENT donc le cache du site et sortent sur
|
||||||
|
# Internet a chaque construction de VM. `make cacher-paquets` les tire une fois, versions
|
||||||
|
# epinglees et empreintes verifiees ; ce role les depose depuis ce cache.
|
||||||
|
- name: Poser les paquets tiers depuis le cache du controleur
|
||||||
|
ansible.builtin.include_role:
|
||||||
|
name: paquets_tiers
|
||||||
|
vars:
|
||||||
|
paquets_tiers_noms: "{{ serveur_loki_paquets }}"
|
||||||
|
|
||||||
- name: Installer Loki
|
- name: Installer Loki
|
||||||
ansible.builtin.apt:
|
ansible.builtin.apt:
|
||||||
name: "{{ serveur_loki_paquets }}"
|
# CE QUE LE CACHE N'A PAS FOURNI, ET RIEN D'AUTRE. Les dependances Debian passent
|
||||||
|
# par le cache du site en HTTP ; seuls les paquets tiers non caches exigent Internet.
|
||||||
|
# Reinstaller ce que le cache vient de poser ferait un `update_cache` inutile — et
|
||||||
|
# hors ligne, il echouerait APRES un travail deja fait.
|
||||||
|
name: "{{ serveur_loki_paquets | difference((paquets_tiers_disponibles | default({})).keys() | list) }}"
|
||||||
state: present
|
state: present
|
||||||
update_cache: true
|
update_cache: true
|
||||||
|
|
||||||
|
when: (serveur_loki_paquets
|
||||||
|
| difference((paquets_tiers_disponibles | default({})).keys() | list)) | length > 0
|
||||||
- name: Assurer le groupe loki (le paquet cree l'utilisateur en nogroup)
|
- name: Assurer le groupe loki (le paquet cree l'utilisateur en nogroup)
|
||||||
ansible.builtin.group:
|
ansible.builtin.group:
|
||||||
name: loki
|
name: loki
|
||||||
|
|
|
||||||
165
scripts/cacher_paquets.py
Normal file
165
scripts/cacher_paquets.py
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""Tire les paquets NON DEBIAN dans le cache du controleur, une fois, verifies.
|
||||||
|
|
||||||
|
POURQUOI CE SCRIPT EXISTE (mesure du 2026-09-03).
|
||||||
|
|
||||||
|
Trois depots tiers sont configures sur la flotte — Smallstep, Icinga, Grafana — tous en
|
||||||
|
HTTPS. Or `client_artefacts` pose `Acquire::https::Proxy "DIRECT"`, sans quoi le cache
|
||||||
|
refuse les tunnels HTTPS et aucun depot tiers n'est joignable. La consequence n'avait pas
|
||||||
|
ete vue : ces trois depots CONTOURNENT LE CACHE, et chaque VM neuve va les chercher sur
|
||||||
|
Internet a sa naissance.
|
||||||
|
|
||||||
|
`step-cli` (Smallstep) et `alloy` (Grafana) sont poses par des integrations UNIVERSELLES.
|
||||||
|
Sans Internet, une machine neuve n'obtient donc ni son client d'autorite ni ses metriques
|
||||||
|
— elle ne peut entrer dans aucun flux chiffre. C'etait le dernier obstacle a une
|
||||||
|
reconstruction hors ligne.
|
||||||
|
|
||||||
|
MEME PATRON QUE LE RESTE DU CACHE DU CONTROLEUR (Forgejo, Keycloak, Nextcloud,
|
||||||
|
oauth2-proxy) : on telecharge UNE FOIS, on verifie, on depose ensuite depuis le cache.
|
||||||
|
|
||||||
|
CE QU'IL NE FAIT PAS, ET POURQUOI. Il ne resout aucune dependance. Les dependances de ces
|
||||||
|
paquets sont des paquets DEBIAN, qui passent deja par le cache du site en HTTP — c'est
|
||||||
|
justement la difference qu'on corrige ici. Ce script ne s'occupe que de ce que Debian ne
|
||||||
|
fournit pas.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import gzip
|
||||||
|
import hashlib
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
RACINE = Path(__file__).resolve().parent.parent
|
||||||
|
DECLARATION = RACINE / "paquets-tiers.yml"
|
||||||
|
CACHE = Path.home() / ".cache" / "setops" / "paquets"
|
||||||
|
|
||||||
|
|
||||||
|
def _index(uri: str, suite: str, composant: str, arch: str = "amd64") -> dict[str, dict]:
|
||||||
|
"""Les paquets d'un depot, lus dans SON index — pas devines depuis une URL.
|
||||||
|
|
||||||
|
Le chemin d'un `.deb` dans un depot n'est pas derivable de son nom : il vit dans le
|
||||||
|
champ `Filename` de l'index. Le construire a la main marcherait pour un depot et
|
||||||
|
casserait au suivant. On lit donc l'index, qui porte aussi le `SHA256` — la
|
||||||
|
verification vient avec, sans effort supplementaire.
|
||||||
|
"""
|
||||||
|
# TOUS LES DEPOTS NE COMPRESSENT PAS LEUR INDEX (mesure du 2026-09-03).
|
||||||
|
#
|
||||||
|
# Icinga et Grafana servent `Packages.gz` ; Smallstep sert `Packages` en clair, et
|
||||||
|
# rend 404 sur les autres formes. La premiere version de ce script ne demandait que
|
||||||
|
# `.gz` : le depot Smallstep echouait en silence, et `step-cli` — le paquet de CHAQUE
|
||||||
|
# machine — n'a jamais ete mis en cache. On essaie donc les formes courantes, dans
|
||||||
|
# l'ordre du moins couteux au plus.
|
||||||
|
base = f"{uri.rstrip('/')}/dists/{suite}/{composant}/binary-{arch}"
|
||||||
|
brut = None
|
||||||
|
derniere = None
|
||||||
|
for suffixe in (".gz", "", ".xz"):
|
||||||
|
try:
|
||||||
|
with urllib.request.urlopen(base + "/Packages" + suffixe, timeout=60) as rep:
|
||||||
|
donnees = rep.read()
|
||||||
|
except Exception as e: # noqa: BLE001 — on essaie la forme suivante
|
||||||
|
derniere = e
|
||||||
|
continue
|
||||||
|
if suffixe == ".gz":
|
||||||
|
donnees = gzip.decompress(donnees)
|
||||||
|
elif suffixe == ".xz":
|
||||||
|
import lzma
|
||||||
|
donnees = lzma.decompress(donnees)
|
||||||
|
brut = donnees.decode("utf-8", "replace")
|
||||||
|
break
|
||||||
|
if brut is None:
|
||||||
|
raise derniere or RuntimeError(f"aucun index lisible sous {base}")
|
||||||
|
paquets: dict[str, dict] = {}
|
||||||
|
for bloc in brut.split("\n\n"):
|
||||||
|
champs: dict[str, str] = {}
|
||||||
|
cle = None
|
||||||
|
for ligne in bloc.splitlines():
|
||||||
|
if ligne.startswith((" ", "\t")) and cle:
|
||||||
|
continue
|
||||||
|
if ":" in ligne:
|
||||||
|
cle, _, val = ligne.partition(":")
|
||||||
|
champs[cle.strip()] = val.strip()
|
||||||
|
nom, ver = champs.get("Package"), champs.get("Version")
|
||||||
|
if nom and ver:
|
||||||
|
paquets[f"{nom}={ver}"] = champs
|
||||||
|
return paquets
|
||||||
|
|
||||||
|
|
||||||
|
def _telecharger(uri: str, champs: dict, dest: Path) -> str:
|
||||||
|
url = f"{uri.rstrip('/')}/{champs['Filename'].lstrip('/')}"
|
||||||
|
with urllib.request.urlopen(url, timeout=300) as rep:
|
||||||
|
contenu = rep.read()
|
||||||
|
somme = hashlib.sha256(contenu).hexdigest()
|
||||||
|
attendue = champs.get("SHA256", "")
|
||||||
|
if attendue and somme != attendue:
|
||||||
|
raise SystemExit(
|
||||||
|
f"ERREUR: empreinte refusee pour {dest.name}\n"
|
||||||
|
f" attendue {attendue}\n obtenue {somme}\n"
|
||||||
|
" Le fichier n'est pas celui que l'index annonce — on ne le garde pas.")
|
||||||
|
dest.write_bytes(contenu)
|
||||||
|
return somme
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
if not DECLARATION.is_file():
|
||||||
|
print(f"ERREUR: {DECLARATION.name} absent — rien de declare a mettre en cache.",
|
||||||
|
file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
decl = yaml.safe_load(DECLARATION.read_text(encoding="utf-8")) or {}
|
||||||
|
CACHE.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
total = tires = deja = 0
|
||||||
|
manifeste: list[str] = []
|
||||||
|
for nom_depot, d in sorted((decl.get("depots") or {}).items()):
|
||||||
|
voulus = d.get("paquets") or {}
|
||||||
|
if not voulus:
|
||||||
|
continue
|
||||||
|
print(f" {nom_depot} : lecture de l'index…")
|
||||||
|
try:
|
||||||
|
index = _index(d["uri"], d["suite"], d["composant"])
|
||||||
|
except Exception as e:
|
||||||
|
# ON COMPTE QUAND MEME CE QU'IL DEVAIT FOURNIR (2026-09-03).
|
||||||
|
#
|
||||||
|
# La premiere version faisait `continue` AVANT d'incrementer `total` : un
|
||||||
|
# depot injoignable disparaissait donc du decompte, et le script concluait
|
||||||
|
# « 20 tires, 20 declares » alors qu'il en manquait un — le plus important.
|
||||||
|
# Une garde qui ne peut pas echouer ne garde rien.
|
||||||
|
total += len(voulus)
|
||||||
|
print(f" ! {nom_depot} injoignable ({type(e).__name__}: {str(e)[:60]}) — "
|
||||||
|
f"{len(voulus)} paquet(s) NON mis en cache.", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
for paquet, version in sorted(voulus.items()):
|
||||||
|
total += 1
|
||||||
|
cle = f"{paquet}={version}"
|
||||||
|
champs = index.get(cle)
|
||||||
|
if champs is None:
|
||||||
|
# DIRE CE QU'ON NE TROUVE PAS. Un amont qui retire une version epinglee
|
||||||
|
# est un fait a connaitre AVANT la prochaine reconstruction, pas pendant.
|
||||||
|
print(f" ! {cle} absent de l'index de {nom_depot} — version retiree en "
|
||||||
|
"amont ? Ajuster `paquets-tiers.yml`.", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
fichier = CACHE / Path(champs["Filename"]).name
|
||||||
|
if fichier.is_file() and champs.get("SHA256") == hashlib.sha256(
|
||||||
|
fichier.read_bytes()).hexdigest():
|
||||||
|
deja += 1
|
||||||
|
manifeste.append(f"{cle} {fichier.name}")
|
||||||
|
continue
|
||||||
|
somme = _telecharger(d["uri"], champs, fichier)
|
||||||
|
tires += 1
|
||||||
|
manifeste.append(f"{cle} {fichier.name}")
|
||||||
|
print(f" + {fichier.name} ({len(fichier.read_bytes()) // 1024} Ko, "
|
||||||
|
f"sha256 {somme[:12]}…)")
|
||||||
|
|
||||||
|
(CACHE / "MANIFESTE").write_text("\n".join(sorted(manifeste)) + "\n", encoding="utf-8")
|
||||||
|
print(f"\n {tires} tire(s), {deja} deja en cache, {total} declare(s).")
|
||||||
|
print(f" Cache : {CACHE}")
|
||||||
|
if tires + deja < total:
|
||||||
|
print("\n INCOMPLET — la reconstruction hors ligne echouera sur ce qui manque.")
|
||||||
|
return 1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Loading…
Reference in a new issue