squelette du site de Technolibre, et la liste de ce qu il faut relever

Un seul noeud Proxmox en version 9, une frontiere OPNsense. Trois
consequences ecrites dans le README : le SPOF est total, les clones sont
complets (un clone lie n achete rien sans second noeud), et rien n a jamais
tourne contre un PVE 9.

Le chiffre a verifier avant de cabler : 57 Go de RAM pour le site et son
locataire sur la meme machine. La RAM est la contrainte dure.

Adressage au statu quo : gestion en 10.23.0.0/24, zones du site en
10.0.31-36 comme chez Chezlepro. Consequence connue et ecrite : les deux
sites ne pourront pas etre relies tant qu aucun n est renumerote.

COLLECTE.md liste dans l ordre ce qui doit etre releve sur place, dont la
question de forme : ce qu il y a entre le noeud et l OPNsense decide du mode
de routage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
Daniel Allaire 2026-09-12 14:16:58 -04:00
commit 912a84483f
9 changed files with 716 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*.vault.yml.bak
flux-genere/

98
COLLECTE.md Normal file
View file

@ -0,0 +1,98 @@
# À relever chez Technolibre — avant de lancer quoi que ce soit
> Une ligne non remplie ici est une ligne qui bloquera le déploiement plus tard, à un
> endroit où le message d'erreur ne dira pas qu'elle manquait.
> Référence : `docs/preparer-un-site-hebergeur.md` §7, adapté au cas **un seul nœud**.
## 1. Le chiffre qui décide de tout
```
SITE 7 VM 20 Go RAM 776 Go provisionnés
tenant 14 VM 37 Go RAM 460 Go provisionnés
─────────────────────────────────────────
TOTAL 21 VM 57 Go RAM ~1,2 To
```
Sur trois nœuds ça se répartit. Sur **un seul**, la même machine porte tout — et la RAM est
la contrainte dure (le disque se dégonfle en provisionnement fin, pas la mémoire).
- [ ] **RAM physique du nœud** : ______ Go → si < 64, décider de réduire AVANT de câbler
- [ ] **Espace libre du stockage `images`** : ______ Go (viser 1,2 To ; 800 Go à la rigueur)
## 2. L'hyperviseur
- [ ] **Nom exact du nœud** tel qu'il apparaît dans l'interface : ______
- [ ] **Version** : `pveversion | head -1` → ______ (attendu : `pve-manager/9.x`)
- [ ] **Noms exacts des stockages** acceptant le contenu `images` : `pvesm status`
- [ ] **Ponts** présents : `ip -br link | grep vmbr`
- [ ] **Adresse de gestion** du nœud : ______
- [ ] **SDN disponible ?** `pvesh get /cluster/sdn` répond sans erreur : oui / non
### Compte d'API
```
pveum user add ansible@pve
pveum aclmod / -user ansible@pve -role Administrator
pveum user token add ansible@pve set-ops --privsep 0
```
- [ ] **Token ID** + **secret** notés (le secret ne s'affiche **qu'une fois**)
## 3. La frontière (OPNsense)
- [ ] **IP publique (WAN)** : ______
- [ ] **Version** : ______
- [ ] **Interfaces libres** pour les zones du site : ______
- [ ] **Clé + secret d'API** créés et notés
- [ ] **Compte `ansible`** en SSH par clé, `sudo` vérifié : `sudo -n -l`
- [ ] **Une patte face au nœud Proxmox** (le lien de transit) : interface ______
## 4. Entre les deux — LA question de forme
Ce qu'il y a entre le nœud Proxmox et l'OPNsense décide du mode de routage :
- [ ] **Un commutateur capable de SVI et d'ACL** → mode `switch`, modèle : ______
- [ ] **Un commutateur simple (VLAN seulement)** → l'OPNsense route tout, mode `switch`
avec l'OPNsense comme routeur
- [ ] **Un lien direct nœud ↔ OPNsense** → trunk 802.1Q, l'OPNsense route tout
- [ ] **SDN EVPN sur le nœud** → indépendant du matériel, mais jamais éprouvé sur un
nœud unique
## 5. Cohabitation d'adressage
- [ ] `ip route` sur le poste de Mathieu — quelles plages `10.x` sont déjà prises ?
- [ ] Son réseau bureautique / son VPN utilisent-ils `10.0.3x` ou `10.23.x` ? ______
- [ ] Docker présent sur ses machines d'administration ? (`172.17`+ déjà pris)
## 6. Le gabarit
- [ ] Gabarit Debian 13 `modeleSetOPS-minimal` : **fait** / **à faire**
(procédure : `docs/procedure-template-debian13-proxmox.md`, ~30 min)
- [ ] Si fait : **VMID** ______ , **nom** ______ , **stockage** ______
- [ ] Vérifier `machine: q35` et `bios: ovmf` — **ne jamais convertir** un i440fx
## 7. Les accès réseau nécessaires depuis le poste
| Cible | Port | Pour quoi | OK |
|---|---|---|---|
| Hyperviseur | 8006 | créer et détruire les VM | [ ] |
| Hyperviseur | 22 | `pvesh`, `qm`, SDN | [ ] |
| Frontière | 443 | poser les règles par API | [ ] |
| Frontière | 22 | SSH, compte `ansible` | [ ] |
| Zones du site | 22 | SSH vers les VM une fois créées | [ ] |
## 8. Les deux paires de secrets
Par un canal chiffré, **séparément du reste**, et jamais dans git :
- [ ] Token ID + secret de l'hyperviseur → voûte de l'instance
- [ ] Clé + secret de l'API de la frontière → voûte de l'instance
## 9. Ce qui n'a jamais été éprouvé, et qu'il faut regarder de près
- [ ] **Proxmox 9** — le moteur n'analyse aucune version et n'appelle que des chemins
d'API stables, mais rien n'a jamais tourné contre un PVE 9. Surveiller le pare-feu
(`proxmox-firewall` nftables en 9) et le SDN (passé GA).
- [ ] **Nœud unique** — pas de Ceph, pas de migration, le gabarit et les clones sont sur
la même machine. Le SPOF est total et assumé.
- [ ] **Deuxième site** — `10.0.31–36` est déjà l'adressage du site de Chezlepro. Les
réutiliser ici ferme la possibilité de relier les deux sites (§8 du guide) tant
qu'aucun n'est renuméroté.

67
README.md Normal file
View file

@ -0,0 +1,67 @@
# SITE-Technolibre
Le dépôt de l'**hébergeur** Technolibre : son matériel, ses zones, ses sept machines de
site. Il n'est pas le plan d'un locataire — celui-là vit dans `OPS-Technolibre` (index 23).
> **État : SQUELETTE.** Tout ce qui est marqué `<<…>>` est à relever sur place.
> La liste complète, dans l'ordre où la poser : **`COLLECTE.md`**.
## Ce qui le distingue du site de Chezlepro
| | Chezlepro | Technolibre |
|---|---|---|
| Nœuds Proxmox | 3 (asgard, gandalf, vishnu) | **1** |
| Version | PVE 8.4 | **PVE 9** |
| Stockage partagé | Ceph NVMe + TrueNAS iSCSI | **local au nœud** |
| Clones | liés, sur stockage partagé | **complets** — pas d'autre nœud pour porter le gabarit |
| Frontière | OPNsense | OPNsense |
Trois conséquences directes :
1. **Le SPOF est total et assumé.** Le gabarit, ses clones et le site entier sont sur la
même machine. Perdre le nœud, c'est tout perdre — d'où l'importance du dépôt de
sauvegarde hors nœud dès le premier jour.
2. **`clone_complet: true`.** Un clone lié dépend à vie de son gabarit ; sans second nœud,
ce lien n'achète rien et coûte une dépendance.
3. **Proxmox 9 n'a jamais été éprouvé.** Le moteur n'analyse aucune version et n'appelle
que des chemins d'API stables, mais c'est la première fois. Surveiller le pare-feu
(`proxmox-firewall` nftables depuis la 9) et le SDN (passé GA).
## Le chiffre à vérifier en premier
```
SITE 7 VM 20 Go RAM 776 Go provisionnés
tenant 14 VM 37 Go RAM 460 Go provisionnés
─────────────────────────────────────────
TOTAL 21 VM 57 Go RAM ~1,2 To
```
Sur un seul nœud, la même machine porte tout, et **la RAM est la contrainte dure**. En
dessous de 64 Go, il faut décider de réduire avant de câbler, pas pendant le déploiement.
## L'adressage
- **Gestion** : `10.23.0.0/24` — la bande basse du supernet de son locataire, que la
dérivation des zones n'alloue jamais (elles commencent à `.16`).
- **Zones du site** : `10.0.31` à `10.0.36` — **identiques à celles de Chezlepro**.
Statu quo décidé le 2026-09-12 : un index se redéploie assez facilement pour qu'une
collision ne justifie pas de revoir la méthode.
> Conséquence connue : les deux sites ne peuvent pas être reliés (§8 du guide de
> préparation) tant qu'aucun des deux n'est renuméroté.
- **Chemins** (transit) : `10.0.4.0/24`, comme chez Chezlepro.
## L'ordre des gestes, le jour J
1. Remplir `COLLECTE.md` **en entier** — une ligne vide bloque plus tard, ailleurs.
2. Reporter les valeurs dans `underlay.yml`, `plan/10-intrants.yml`, `opnsense.yml`.
3. Trancher le mode de routage devant le matériel (`COLLECTE.md` §4).
4. Gabarit : vérifier ou fabriquer (`docs/procedure-template-debian13-proxmox.md`).
5. Voûte : y déposer les deux paires de secrets, jamais dans git.
6. `make underlay` — le devis lit le site réel et refuse ce qui ne concorde pas.
7. `make site-creer CONFIRMER=true`, puis le socle.
## Voir aussi
- `docs/preparer-un-site-hebergeur.md` — ce qu'un hébergeur doit fournir
- `docs/implanter-un-tenant-sur-un-site.md` — y poser `OPS-Technolibre` ensuite
- `docs/hebergeur-exploitation.md` — l'exploitation courante

25
opnsense.yml Normal file
View file

@ -0,0 +1,25 @@
# Intrants de la frontière de Technolibre (OPNsense).
# Les SECRETS (clé et secret d'API) ne vivent PAS ici : voûte chiffrée de l'instance.
---
opnsense_api_url: https://<<IP_FRONTIERE_ADMIN>>
opnsense_wan_ip: <<IP_PUBLIQUE>>
# L'interface d'arrivée de chaque zone. D-61 : le devis raisonne en ARRIVÉE, donc une
# règle posée sur la mauvaise patte ne correspond JAMAIS — et rien ne le signale.
# Relever les noms exacts (`optN`) dans Interfaces > Assignments.
opnsense_if_zones:
site-pilotage: <<optN>>
site-autorite: <<optN>>
site-genome: <<optN>>
site-service: <<optN>>
site-sauvegarde: <<optN>>
site-supervision: <<optN>>
# La patte face au nœud Proxmox — par où arrive TOUT le trafic des locataires.
# Elle manquait chez Chezlepro et les règles entrantes des tenants ne correspondaient
# à rien : la poser dès le premier jour.
transit-frontiere: <<optN>>
opnsense_if_transit: <<optN>>
opnsense_if_wan: wan
opnsense_if_gestion: <<lan|optN>>
opnsense_if_site: <<optN>>

36
plan/10-intrants.yml Normal file
View file

@ -0,0 +1,36 @@
# Intrants de base du SITE-Technolibre — les valeurs-racines dont tout le reste dérive.
# ⚠ Ce qui est marqué <<…>> est à relever sur place. Voir COLLECTE.md.
---
# LE DOMAINE INTERNE DU SITE, pas celui de son locataire. Chezlepro utilise
# `genese.internal` ; deux sites qui porteraient le même nom de zone ne pourraient pas
# coexister dans un même résolveur. Proposition : `technolibre.site` ou `atelier.internal`.
domaine_interne: <<DOMAINE_SITE>>
organisation: <<ORGANISATION>>
# Par où l'administration entre. Chez Chezlepro c'est la patte LAN de la frontière ;
# ici ce sera la patte de gestion de l'OPNsense (10.23.0.1) ou un accès direct.
rebond: ansible@<<IP_REBOND>>
cle_ssh: ~/.ssh/<<CLE_SSH>>
integrations_exemptes: {}
# La clé publique du runner du site — GÉNÉRÉE au premier déploiement de `site-ops-01`,
# puis recopiée ici. Laisser vide au départ : c'est normal.
runner_cle_publique: ""
# LE GABARIT. Sur un nœud unique il n'y a pas d'autre machine pour le porter :
# ne jamais le personnaliser, et ne jamais convertir une VM i440fx en q35.
gabarit:
vmid: <<VMID_GABARIT>>
nom: modeleSetOPS-minimal
noeud: <<NOEUD>>
machine: q35
bios: ovmf
stockage: <<STOCKAGE>>
# Clé publique du compte de dépôt des sauvegardes du site lui-même — générée au
# déploiement de `site-backup-01`, recopiée ensuite.
backup_pubkey: ""
nftables_admin_ssh: []
supervision_courriel: <<COURRIEL_SUPERVISION>>

173
plan/applications.yml Normal file
View file

@ -0,0 +1,173 @@
---
# Les services du SITE et leur placement — même forme que chez un tenant.
#
# `groupe` est le rôle Ansible, `hote` la machine de `plan/serveurs.yml` qui le porte.
# C'est de ce registre que l'inventaire tire ses groupes : une application déclarée ici
# range son hôte dans le groupe correspondant, et `playbooks/groupes/<rôle>.yml` le
# trouve sans qu'on câble quoi que ce soit.
#
# `expose:` NOMME LE SERVICE, indépendamment de la machine qui le rend.
#
# C'est la pièce qui manquait, et son absence s'est vue au pire endroit : le certificat
# de la forge portait `forge.<<DOMAINE_SITE>>` dans ses SAN, mais **aucune zone ne
# résolvait ce nom**. On avait donc du TLS correct sur un nom que personne ne pouvait
# appeler — et l'usage retombait sur `site-forge-01`, c'est-à-dire sur la machine.
#
# Un nom de service survit au déménagement du service ; un nom de machine, non.
applications:
# LE RUNNER DU SITE — DEUX RÔLES, ET DANS CET ORDRE.
#
# `serveur_ops` INSTALLE : ansible, le cache de collections, les dépôts du génome clonés
# depuis la forge du site, les symlinks du moteur. `serveur_ops_site` n'installe rien —
# il dépose la voûte de l'underlay (chiffrée) et ajoute le pouvoir de MATÉRIALISER.
#
# C'est le même patron que `serveur_artefacts` + `serveur_cache_site` : un installateur
# et un marqueur. J'avais déclaré le marqueur seul, et son assertion l'a dit sans
# détour : « exige `serveur_ops_site_depot`, cloné par `serveur_ops` ».
ops:
groupe: serveur_ops
hote: site-ops-01
ops_site:
groupe: serveur_ops_site
hote: site-ops-01
# LA SOURCE D'ARTEFACTS, et son marquage comme racine de chaîne. Les deux vont
# ensemble : `serveur_artefacts` INSTALLE apt-cacher-ng, `serveur_cache_site` MARQUE ce
# cache comme racine et vérifie qu'il n'a pas d'amont — il n'installe rien.
artefacts:
groupe: serveur_artefacts
hote: site-cache-01
cache_site:
groupe: serveur_cache_site
hote: site-cache-01
# LA FORGE DU GÉNOME. `expose` porte son nom de service : c'est lui que le certificat
# doit couvrir, et lui que la zone souveraine doit résoudre.
forgejo:
groupe: serveur_forgejo
hote: site-forge-01
# 443, ET NON 3000. Le `3000` est le port que Forgejo ecoute DERRIERE un edge, en
# clair. Ici il sert lui-meme son TLS : garder 3000 aurait grave `:3000` dans
# `ROOT_URL`, donc dans l'`origin` de chaque ecosysteme descendant, pour toujours.
port: 443
expose:
- forge.<<DOMAINE_SITE>>
# LE MARQUEUR DE LA FORGE DU GÉNOME — même patron que `artefacts` + `cache_site`.
#
# Le site rend deux services à ses locataires pendant leur jeunesse : les PAQUETS et le
# GÉNOME. Le premier était déclaré, le second ne l'était pas — alors que D-81 fait de
# cette forge l'autorité dont tout écosystème se reproduit.
#
# Mesuré le 2026-08-28 sur `ops-01`, première machine de la reconstruction de
# Chezlepro : le cache du site répondait, la forge non, et le clonage du génome
# attendait sans fin. Le tenant déclarait pourtant lire son génome à cette adresse.
forge_site:
groupe: serveur_forge_site
hote: site-forge-01
# L'AUTORITÉ. Elle sert le 443 à ses voisins du même segment, en L2 — rien ne traverse
# la frontière, donc aucune règle ne l'accompagne au devis. `expose` lui donne quand
# même son nom de service : les clients s'y enrôlent par un nom, jamais par une adresse.
step_ca:
groupe: serveur_step_ca
hote: site-pki-01
expose:
- pki.<<DOMAINE_SITE>>
# LE DNS — autoritatif et résolveur sur la même machine, à dessein.
powerdns:
groupe: serveur_powerdns
hote: site-dns-01
resolveur:
groupe: serveur_resolveur
hote: site-dns-01
expose:
- dns.<<DOMAINE_SITE>>
# LE MARQUEUR DU RÉSOLVEUR DU SITE — troisième service prêté aux locataires, après les
# paquets (`cache_site`) et le génome (`forge_site`). Même patron : un installateur, un
# marqueur.
#
# Un écosystème neuf n'a ni cache, ni forge, ni résolveur — et c'est lui qui doit les
# construire. Sans celui-ci, ses machines sortent en 443 par adresse et restent
# AVEUGLES AUX NOMS : `apt` s'en sort par le mandataire du cache, qui résout à sa place,
# mais un `get_url` ou un dépôt tiers en HTTPS échoue. Mesuré le 2026-08-30 sur
# `infra-pki-01`, première machine à porter l'autorité de Chezlepro.
#
# `client_resolveur` bascule chaque tenant chez lui dès que son propre résolveur existe.
# Retirer cet emprunt ce jour-là est une émancipation — un geste, pas un effet.
resolveur_site:
groupe: serveur_resolveur_site
hote: site-dns-01
# LE SERVEUR DE SAUVEGARDE, ET SON MARQUEUR — meme patron que les trois autres
# services pretes : `serveur_backup` INSTALLE le depot, `serveur_backup_site` dit qu'il
# sert les locataires et declare le flux qui le permet.
backup:
groupe: serveur_backup
hote: site-backup-01
backup_site:
groupe: serveur_backup_site
hote: site-backup-01
# LE NOM QUE LES LOCATAIRES ÉCRIVENT DANS LEUR CONFIGURATION.
#
# Un locataire grave l'adresse de son dépôt dans `client_backup_repo`, donc dans le
# chemin de CHACUN de ses instantanés. Y graver `10.0.35.11` rendrait le dépôt
# indéplaçable : le jour où il change de machine, tous les locataires cessent de
# déposer, et chacun doit être modifié un par un — pendant que plus rien n'est
# sauvegardé.
#
# `sauvegarde`, pas `site-backup-01` : c'est le SERVICE qu'on nomme. La machine qui
# le rend a le droit de changer sans que personne ne réécrive quoi que ce soit.
expose:
- sauvegarde.<<DOMAINE_SITE>>
# LA SUPERVISION DU SITE. `serveur_backup` rapporte PASSIVEMENT ici, avec un `ttl` :
# c'est l'expiration de ce `ttl` qui fait qu'un SILENCE alerte. Une sauvegarde qui
# cesse ne produit aucune erreur, seulement une absence — sans destinataire pour ce
# rapport, il n'y a pas de supervision, il y a un script qui tourne.
postgresql:
groupe: serveur_postgresql
hote: site-mon-01
icinga:
groupe: serveur_icinga
hote: site-mon-01
# L'OBSERVABILITE DU SITE — LA SIENNE, ET RIEN QUE LA SIENNE (D-87, 2026-09-10).
#
# Elle manquait, et son absence etait le REVERS de la frontiere : en refusant de voir
# les journaux de ses locataires — parce qu'ils contiennent du CONTENU, et qu'un
# hebergeur qui les lit en sait plus que s'il detenait leur annuaire — le site s'etait
# prive des siens. Ses sept machines n'expediaient rien nulle part.
#
# Le remede n'est pas d'assouplir la regle : c'est de lui donner SA pile. Aucun lien
# avec celle d'un tenant, dans aucun sens. Un locataire n'y envoie rien, et le site n'y
# lit que ses propres machines.
#
# POSEE SUR `site-mon-01`, la zone de supervision : c'est deja la qu'Icinga juge l'etat
# du site, et le zonage veut qu'une preoccupation vive dans une seule zone. La machine a
# la place (3,3 Go disponibles, charge 0,10 au moment du choix).
prometheus:
groupe: serveur_prometheus
hote: site-mon-01
loki:
groupe: serveur_loki
hote: site-mon-01
# GRAFANA SANS SSO, ET C'EST VOULU. Chez un tenant il vit derriere `oauth2_proxy`, donc
# derriere Keycloak. Le site n'a pas d'annuaire — et ne doit pas en avoir un pour ca.
# Faire monter l'identite pour offrir un tableau de bord serait payer tres cher une
# commodite : l'acces se fait depuis le plan d'administration, qui est deja la barriere.
#
# LE NOM DIT LA FONCTION, PAS LE PRODUIT (2026-09-10). C'etait `tableaux.<<DOMAINE_SITE>>`,
# un mot que rien d'autre n'employait. `observatoire` est le meme nom que chez le locataire
# (`observatoire.chezlepro.internal`) : un seul vocabulaire des deux cotes, et un nom qui
# ne ment pas le jour ou Grafana est remplace.
grafana:
groupe: serveur_grafana
hote: site-mon-01
expose:
- observatoire.<<DOMAINE_SITE>>
# LE RELAIS DE COURRIEL DU SITE — souverain, et c'est tout l'interet : emprunter le MTA
# d'un locataire ferait dependre le site d'un ecosysteme qu'il peut outvivre.
postfix:
groupe: serveur_postfix
hote: site-mon-01

26
plan/bases-donnees.yml Normal file
View file

@ -0,0 +1,26 @@
---
# LE REGISTRE DES BASES DU SITE — la garde D-72 : sans entree ici, `resoudre_base`
# REFUSE plutot que de deviner un hote, un nom de base ou un secret.
#
# Le site n'en a qu'une, et c'est voulu. Chez un locataire, PostgreSQL vit sur une
# machine dediee parce que plusieurs services la partagent ; ici un seul en a besoin.
# La base est donc sur la machine qui la consomme — une seconde VM n'aurait fait
# qu'ajouter une machine a surveiller a celle qui surveille.
serveurs_bd:
pg-site:
type: postgres
hote: site-mon-01
port: 5432
groupe: serveur_postgresql
bases_donnees:
# LA BASE D'ICINGA. Elle porte l'historique de la supervision — pas irremplacable,
# mais une base l'est par nature, et `site-mon-01` porte donc `client_backup`.
icingadb:
serveur: pg-site
base: icingadb
proprietaire: icingadb
secret: vault_bd_icingadb
consommateur: icinga
portee: application
usage: principale

205
plan/serveurs.yml Normal file
View file

@ -0,0 +1,205 @@
---
# Les machines du SITE — l'équivalent de `plan/serveurs.yml` chez un tenant.
#
# UNE SEULE DIFFÉRENCE AVEC CELUI D'UN TENANT, mais elle est structurante : ici
# l'adressage est **déclaré**, là-bas il est **dérivé**. Un tenant écrit `fonction:` et
# tout descend de son `index` ; un site écrit `ip`, `vmid`, `noeud`, parce qu'il n'y a
# aucun seed dont les faire descendre — le site *est* le terrain.
#
# `reseau:` nomme un réseau de `underlay.yml`, qui fournit le pont, l'étiquette VLAN, le
# masque et la passerelle. La fabric reste la source de ces valeurs-là : on la nomme, on
# ne la recopie pas.
#
# UNE ZONE PAR NATURE D'AUTORITÉ (2026-08-25). Ces machines ont vécu quelques heures dans
# un `/24` plat — la plus autoritaire du système avec moins d'isolation que n'importe
# quelle machine de tenant. Chacune vit désormais dans la zone de ce qu'elle détient, et
# tout ce qui va d'une zone à l'autre est routé, donc policé par la frontière.
serveurs:
# LE RUNNER DU SITE. Il MATÉRIALISE : VNets, VM, routes, flux — il prépare le terrain
# qu'un runner de tenant occupera ensuite. Il détient la voûte de l'underlay, jamais
# celle d'un tenant. Voir roles/serveur_ops_site/README.md pour la ligne de partage.
site-ops-01:
etat: actif
reseau: site-pilotage
ip: 10.0.31.11
noeud: <<NOEUD>>
gabarit: { vcpu: 2, memoire: 4096, disque: 40G }
vmid: 9001
variables:
# CE QUE LE RUNNER DU SITE DOIT DÉTENIR POUR TRAVAILLER.
#
# Le moteur, la carte de l'hébergeur, les modèles — et les PLANS qu'il matérialise.
# `tenant` et non `instance` : il ne PILOTE pas ces écosystèmes, il leur prépare le
# terrain. Configurer leurs services exige leurs voûtes, qu'il n'aura jamais.
serveur_ops_depots:
- { depot: "set-ops-public", dest: "Set-OPS-public", role: "moteur" }
- { depot: "site-technolibre", dest: "SITE-Technolibre", role: "hebergeur" }
- { depot: "set-ops-modeles", dest: "Set-OPS-Modeles", role: "modeles", branche: "master" }
- { depot: "ops-technolibre", dest: "OPS-Technolibre", role: "tenant", branche: "master" }
# AUCUNE INSTANCE ACTIVE : un SITE n'est pas un tenant, il n'a pas de plan monté par
# le symlink `instance`. Son propre inventaire est dynamique (`site_inventaire.py`).
serveur_ops_instance: ""
serveur_ops_underlay: "SITE-Technolibre"
# Le dépôt que `serveur_ops_site` habille de la voûte, une fois cloné ci-dessus.
serveur_ops_site_depot: "SITE-Technolibre"
serveur_ops_site_voute_source: >-
{{ (playbook_dir ~ '/../../underlay.yml') | realpath | dirname }}/underlay.vault.yml
# ARMER CE RUNNER — il est maître de sa voûte et en détient la clé (2026-08-28).
#
# Sans elle, il porte le chiffré et ne peut rien en faire : il calcule son
# inventaire, et chaque geste qui demande un secret échoue sur une valeur vide.
#
# LA CLÉ DE CE SITE, ET ELLE SEULE. Depuis la séparation des voûtes, le mot de passe
# de l'underlay n'ouvre plus aucun tenant : ce runner ne peut donc ouvrir que la
# fabric, ce qui est exactement son pouvoir. C'était faux jusqu'au 2026-08-28, où
# un mot de passe unique ouvrait les six voûtes de la flotte.
#
# Le chemin est un POINTEUR — le fichier vit sur le poste de l'exploitant et
# n'entre dans aucun dépôt.
serveur_ops_site_cle_deposer: true
serveur_ops_site_cle_source: "~/.config/setops-vault-site-technolibre"
# LA RACINE DE LA CHAÎNE DE CACHES :
# VM du tenant -> cache du tenant -> cache du SITE -> Debian
# Debian n'est ainsi téléchargé qu'une fois pour tout le site, et ce cache ne voit que
# des requêtes agrégées — jamais quelle machine de quel tenant installe quoi.
site-cache-01:
etat: actif
reseau: site-genome
ip: 10.0.33.21
noeud: <<NOEUD>>
gabarit: { vcpu: 2, memoire: 2048, disque: 120G }
vmid: 9002
# LA FORGE DU GÉNOME. Elle vivait chez patient 0 — c'est-à-dire chez un écosystème —
# et tout l'univers en dépendait pour se reproduire.
site-forge-01:
etat: actif
reseau: site-genome
ip: 10.0.33.11
noeud: <<NOEUD>>
gabarit: { vcpu: 2, memoire: 4096, disque: 80G }
vmid: 9003
# CETTE MACHINE DÉTIENT DE L'ÉTAT — elle dépose donc sur le dépôt du site.
#
# `client_backup` est une intégration FACULTATIVE : elle se déclare là où il y a
# quelque chose à perdre, pas partout. Ici, le génome — les quatre dépôts dont tout descend. Ils vivent
# aussi sur les postes et les runners, mais la forge est le seul endroit où ils se
# rejoignent.
integrations: [client_backup]
variables:
# Elle tourne seule : ni PostgreSQL ni Keycloak sur le site. C'est délibéré —
# cette forge sert LE GÉNOME, pas une équipe. SQLite est un choix de conception,
# pas un repli : la base tient dans un fichier, que la sauvegarde emporte déjà.
serveur_forgejo_bd: sqlite
# Elle sert son propre TLS : il n'y a pas d'edge devant elle sur le site.
serveur_forgejo_tls: true
# Le port du schema, pas celui d'un service derriere un edge. Forgejo tournant en
# `git`, l'unite lui accorde CAP_NET_BIND_SERVICE pour s'y lier.
serveur_forgejo_http_port: 443
# Forgejo tourne en `git` dès le départ, contrairement à nginx ou Postfix qui
# lisent la clé en root avant de déprivilégier. Accès GROUPE à la clé privée —
# pas un élargissement général.
client_pki_cle_groupe: git
client_pki_cle_mode: "0640"
# Un cert renouvelé sur disque reste servi périmé tant que le consommateur n'est
# pas rechargé. La cicatrice est déjà dans le dépôt ; on ne la refait pas.
client_pki_reload_services:
- forgejo
# L'AUTORITÉ DE CERTIFICATION DU SITE. Sans elle, la forge servait le génome en clair
# et aucune machine n'avait de certificat — donc aucun chiffrement est-ouest.
site-pki-01:
etat: actif
reseau: site-autorite
ip: 10.0.32.11
noeud: <<NOEUD>>
gabarit: { vcpu: 2, memoire: 2048, disque: 32G }
vmid: 9004
# CETTE MACHINE DÉTIENT DE L'ÉTAT — elle dépose donc sur le dépôt du site.
#
# `client_backup` est une intégration FACULTATIVE : elle se déclare là où il y a
# quelque chose à perdre, pas partout. Ici, la racine de l'autorité de certification. Compromise, elle forge
# tout nom ; perdue, il faut redistribuer la confiance à chaque machine de chaque
# écosystème.
integrations: [client_backup]
# LE DNS DU SITE — autoritatif ET résolveur, colocalisés.
#
# Ils partagent la zone souveraine, et le repli de port de PowerDNS (53 -> 5300) DÉRIVE
# de cette colocalisation : le résolveur prend le 53, l'autoritatif se range derrière.
# Les séparer casserait cette dérivation.
site-dns-01:
etat: actif
reseau: site-service
ip: 10.0.34.11
noeud: <<NOEUD>>
gabarit: { vcpu: 2, memoire: 2048, disque: 40G }
vmid: 9005
variables:
# CORRIGÉ LE 2026-08-25 — c'était `false`, avec pour motif « le site n'a pas d'edge,
# aucun FQDN public à faire pointer quelque part ». Le raisonnement confondait
# PUBLIC et EXPOSÉ.
#
# Le site n'a effectivement aucun domaine public. Mais `plan/applications.yml`
# déclare bien trois expositions — `forge.genese.internal`, `pki.genese.internal`,
# `dns.genese.internal` — et ce sont des noms de SERVICE, que les certificats
# portent et que les clients appellent. Ils n'avaient aucun enregistrement dans la
# zone : seul le plancher `/etc/hosts` savait les résoudre.
#
# Un service ne doit pas dépendre d'un plancher pour être joignable. Le plancher est
# un filet — il rattrape le DNS éteint — pas le sol.
serveur_powerdns_publier_expositions: true
# LE DEPOT DE SAUVEGARDE DU SITE — le quatrieme service prete aux locataires.
#
# Apres les paquets, le genome et les noms : l'ETAT. Un ecosysteme neuf n'a pas de
# depot ou sauvegarder, et c'est justement quand il commence a detenir quelque chose
# d'irremplacable qu'il en a besoin.
#
# CE QUE CA CORRIGE, ET QUI ETAIT DANGEREUX (mesure le 2026-09-01). Chezlepro
# sauvegardait sur `backup-01`, UNE VM DE SA PROPRE FLOTTE. Raser l'ecosysteme aurait
# detruit les donnees ET leur seul filet dans le meme geste. La doctrine affirmait
# pourtant que `client_backup_cible` « pointe deja hors de l'ecosysteme » — vrai pour
# patient 0, qui sauvegarde chez eregion ; faux pour Chezlepro. Un document qui decrit
# une propriete que le reel n'a pas est exactement ce que ce depot traque.
#
# SA ZONE EST A LUI SEUL. Il detient l'etat de CHAQUE tenant : cles d'autorite,
# annuaires, bases, courriel. C'est la machine dont la compromission coute le plus cher
# du site — plus que la forge, dont le contenu est du code, et plus que l'AC, qui forge
# des noms sans detenir de donnees.
#
# SON DISQUE EST LE PLUS GROS DU SITE : il accueille l'etat de tous les locataires, et
# restic garde des instantanes. On le dimensionne large plutot que de decouvrir la
# limite le jour ou une sauvegarde echoue en silence.
site-backup-01:
etat: actif
reseau: site-sauvegarde
ip: 10.0.35.11
noeud: <<NOEUD>>
gabarit: { vcpu: 2, memoire: 2048, disque: 400G }
vmid: 9010
# LE TEMOIN DU SITE — celui qui sait quand les autres se taisent.
#
# Il porte SA propre base : chez un locataire, PostgreSQL vit sur une machine dediee
# parce que plusieurs services la partagent. Ici un seul en a besoin, et une seconde VM
# ne ferait qu'ajouter une machine a surveiller a celle qui surveille.
#
# Il porte aussi un relais de courriel. Sans lui, la seule facon d'alerter serait
# d'emprunter le MTA d'un locataire — le site dependrait alors d'un ecosysteme qu'il
# peut outvivre, et une emancipation emporterait son alerte avec elle.
site-mon-01:
etat: actif
reseau: site-supervision
ip: 10.0.36.11
noeud: <<NOEUD>>
gabarit: { vcpu: 2, memoire: 4096, disque: 64G }
vmid: 9011
# ELLE PORTE UNE BASE, DONC DE L'ETAT — P36 l'a exige des la declaration.
#
# L'historique d'Icinga n'est pas irremplacable ; la base l'est par nature, et le
# catalogue ne connait pas la nuance. C'est tant mieux : une exception ici serait
# exactement ce que cette preuve existe pour empecher. Le dump coute quelques
# megaoctets et vit deja sur le depot du site.
integrations: [client_backup]

84
underlay.yml Normal file
View file

@ -0,0 +1,84 @@
# Underlay de SITE-Technolibre — l'infrastructure PHYSIQUE de l'hébergeur.
#
# UN SEUL NŒUD PROXMOX, en version 9. C'est la différence de fond avec le site de
# Chezlepro : pas de Ceph, pas d'iSCSI, pas de migration, pas de fabric de stockage.
# Le gabarit et ses clones vivent sur la même machine — le SPOF est total, et assumé.
#
# Dérivé du modèle `exemples/modeles/socle` (« un seul commutateur, pas de fabric de
# stockage séparée — le point de départ honnête d'un petit hébergeur »), adapté.
#
# ⚠ TOUT CE QUI EST MARQUÉ <<…>> EST À RELEVER SUR PLACE. Voir COLLECTE.md.
---
underlay:
# Le locataire que ce site hébergera. Son index est déjà fédéré (23) et il ne
# change pas : c'est lui qui porte l'adressage d'OPS-Technolibre, ses VLAN et ses VMID.
tenants:
OPS-Technolibre: 23
# CE QUI ROUTE ENTRE LES ZONES D'UN LOCATAIRE — à trancher devant le matériel.
# `switch` : un commutateur porte les SVI et les ACL (mode par défaut du moteur)
# `sdn` : EVPN sur le nœud, un VRF par locataire (éprouvé à 3 nœuds, jamais à 1)
# Voir COLLECTE.md §4 : la réponse dépend de ce qu'il y a entre le nœud et l'OPNsense.
routage_tenants: <<SWITCH_OU_SDN>>
routeur: <<NOM_DU_ROUTEUR>> # le commutateur, ou la frontière si elle route tout
dialecte: <<cisco|binardat>> # dicte la forme des ACL et des routes du devis
mtu_overlay: 1450
# À `false` quand le matériel ne sait pas lier une ACL à une interface de routage.
acl_inter_tenant: <<true|false>>
stp:
mode: rstp
topologie: etoile
reseaux:
# PLAN DE GESTION — la seule chose qui doit être unique entre deux sites.
# `10.<index>.0.0/24`, index 23 : la bande basse du supernet du locataire, que la
# dérivation des zones n'alloue jamais (elles commencent à .16).
- nom: management
description: Gestion du nœud, OOB/IPMI, poste de l'exploitant
vlan: null
sous_reseau: 10.23.0.0/24
mtu: 1500
# LIEN VERS LA FRONTIÈRE. Sans lui, la flotte n'a ni sortie ni chemin de retour :
# elle serait routée jusqu'à la bordure puis muette — très difficile à diagnostiquer.
- nom: transit-frontiere
description: Lien nœud Proxmox <-> OPNsense, et sortie par défaut des locataires
vlan: 40
sous_reseau: 10.0.4.0/24
passerelle_sortie: 10.0.4.1
mtu: 1500
# LES SIX ZONES DU SITE. Une préoccupation par zone.
# ⚠ Identiques à celles du site de Chezlepro (statu quo décidé le 2026-09-12).
# Conséquence connue : les deux sites ne pourront pas être reliés (§8 du guide)
# tant qu'aucun des deux n'est renuméroté.
- { nom: site-pilotage, description: Runner et pilotage, vlan: 31, sous_reseau: 10.0.31.0/24, pont: <<PONT>>, mtu: 1500 }
- { nom: site-autorite, description: Autorité de certification, vlan: 32, sous_reseau: 10.0.32.0/24, pont: <<PONT>>, mtu: 1500 }
- { nom: site-genome, description: Forge et cache de paquets, vlan: 33, sous_reseau: 10.0.33.0/24, pont: <<PONT>>, mtu: 1500 }
- { nom: site-service, description: Noms (DNS autoritaire), vlan: 34, sous_reseau: 10.0.34.0/24, pont: <<PONT>>, mtu: 1500 }
- { nom: site-sauvegarde, description: Dépôt de sauvegarde mutualisé, vlan: 35, sous_reseau: 10.0.35.0/24, pont: <<PONT>>, mtu: 1500 }
- { nom: site-supervision, description: Supervision et observabilité, vlan: 36, sous_reseau: 10.0.36.0/24, pont: <<PONT>>, mtu: 1500 }
hotes:
# L'UNIQUE NŒUD. `via` = l'interface physique qui porte le trunk vers la frontière.
- { nom: <<NOEUD>>, reseau: management, ip: <<IP_NOEUD>>, role: hyperviseur }
- { nom: <<NOEUD>>, reseau: transit-frontiere, ip: 10.0.4.41, role: hyperviseur, via: <<IFACE>> }
# LA FRONTIÈRE, patte par patte. Une ligne par zone qu'elle sert : c'est de là que
# le devis dérive les interfaces d'arrivée des règles (D-61 raisonne en ARRIVÉE).
- { nom: <<FRONTIERE>>, reseau: management, ip: 10.23.0.1, role: frontiere }
- { nom: <<FRONTIERE>>, reseau: transit-frontiere, ip: 10.0.4.1, role: frontiere }
- { nom: <<FRONTIERE>>, reseau: site-pilotage, ip: 10.0.31.1, role: frontiere }
- { nom: <<FRONTIERE>>, reseau: site-autorite, ip: 10.0.32.1, role: frontiere }
- { nom: <<FRONTIERE>>, reseau: site-genome, ip: 10.0.33.1, role: frontiere }
- { nom: <<FRONTIERE>>, reseau: site-service, ip: 10.0.34.1, role: frontiere }
- { nom: <<FRONTIERE>>, reseau: site-sauvegarde, ip: 10.0.35.1, role: frontiere }
- { nom: <<FRONTIERE>>, reseau: site-supervision, ip: 10.0.36.1, role: frontiere }
# LE GABARIT ET SON STOCKAGE. Sur un nœud unique, `clone_complet` est le choix sûr :
# un clone lié dépend à vie du gabarit, et il n'y a pas d'autre nœud pour le porter.
materialisation:
vmid_modele: <<VMID_GABARIT>>
stockage: <<STOCKAGE>>
clone_complet: true