# Architecture
> **Pour qui :** quiconque doit comprendre comment Set-OPS est construit — avant de
> l'installer, de l'exploiter ou de le modifier. Le détail de chaque mécanisme est dans
> `docs/conception/` ; l'index complet dans [`README.md`](README.md).
Set-OPS est un **moteur** Ansible et Python. Il lit un **plan** déclaratif, en dérive un
inventaire et des politiques, et les applique à des VM Debian 13 clonées sur une grappe
Proxmox. Le moteur est générique ; chaque écosystème réel vit dans **son propre dépôt**.
## Composants
```mermaid
flowchart LR
subgraph POSTE["Poste de l'exploitant (ou runner)"]
MOT["Set-OPS
moteur : roles/, playbooks/, scripts/"]
INST["instance/ → ../OPS-X
le locataire actif"]
UND["underlay.yml → ../SITE-Y/underlay.yml
le site actif"]
CLE["~/.config/setops-vault-*
une clé par voûte"]
end
subgraph SITE["Site d'hébergement"]
PVE["Grappe Proxmox
SDN EVPN, pare-feu est-ouest"]
OPN["Frontière OPNsense
nord/sud"]
RUN["site-ops-01
runner du site"]
FORGE["Forge du site
le génome"]
VMS["VM des locataires
et du site"]
end
MOT -- "lit" --> INST & UND
MOT -- "API Proxmox : clone, SDN, pare-feu" --> PVE
MOT -- "API OPNsense : règles, routes" --> OPN
MOT -- "SSH + Ansible" --> VMS
POSTE -- "make publier / genome-pousser" --> FORGE
RUN -- "tire" --> FORGE
RUN -- "SSH + Ansible" --> VMS
PVE --- VMS
```
| Composant | Ce que c'est | Où |
|---|---|---|
| Moteur | rôles, playbooks, scripts de dérivation, preuves | ce dépôt |
| Locataire (tenant) | un écosystème : son plan, son inventaire généré, sa voûte | dépôt `OPS-`, monté par `instance/` |
| Site | l'hébergeur : matériel, underlay, frontière, services prêtés aux locataires | dépôt `SITE-`, désigné par `underlay.yml` |
| Voûtes | secrets chiffrés `ansible-vault`, une par dépôt, une clé par voûte | `group_vars/all/vault.yml` ; clés hors dépôt |
| Runner du site | machine du site qui déploie à la place du poste | `site-ops-01`, compte `setops`, `/opt/setops` |
| Forge du site | Forgejo qui porte le génome (moteur + plans) que les runners tirent | VM du site |
## Moteur, site, locataire
```mermaid
flowchart TB
M["Set-OPS (moteur)"]
S["SITE-Y
underlay.yml · plan/ · opnsense.yml"]
T1["OPS-A
plan/ · inventories/"]
T2["OPS-B
plan/ · inventories/"]
M -. "monte un site" .-> S
M -. "monte un locataire" .-> T1 & T2
S -- "contrat du site → fiche-site.yml" --> T1 & T2
T1 & T2 -- "face réseau publiée" --> S
```
- **Deux classes d'écosystème, un tronc commun** : `scripts/contexte.py` est le seul endroit
qui lit les indices (`instance/`, `underlay.yml`, `SETOPS_*`, dossiers frères) et rend un
`Site` ou un `Locataire`. Conception : `docs/conception/conception-contextes.md`.
- **Le site lit la face réseau des locataires, le locataire lit la fiche du site.** Le
contrat (`make site-intrants`) dérive ce que le site expose : résolveur, cache de paquets,
forge, adresses publiques, serveurs DNS publics. `make fiches-site-deposer` le dépose chez
chaque locataire (`fiche-site.yml`) ; `make site-intrants-verifier` juge l'écart.
- **Multi-instance par convention** : un dépôt par écosystème, découverts comme dossiers
frères. Tout l'adressage dérive d'un seed `index` par écosystème ; P21 refuse une
collision. Détail : `docs/conception/multi-instances.md`.
- **Le site prête trois services** à un locataire jeune — paquets (`serveur_cache_site`),
génome (`serveur_forge_site`), noms (`serveur_resolveur_site`) — jusqu'à ce qu'il ait les
siens. Filiation et émancipation : `docs/conception/filiation-emancipation.md`.
## Du plan au déploiement
```mermaid
flowchart LR
PLAN["plan/
serveurs · applications · bases-donnees
domaines · nomenclature"] --> INS["make instancier
scripts/instancier.py
inventory_rules.py"]
INS -- "diff sémantique" --> APP["make instancier-appliquer"]
APP --> HOSTS["inventories/INVENTAIRE/hosts.yml
GÉNÉRÉ"]
COUCHES["docs/couches-deploiement.yml
docs/dependances-groupes.yml"] --> ORCH["make site
scripts/orchestrer.py"]
ORCH --> SITEYML["playbooks/site.yml
GÉNÉRÉ"]
HOSTS & SITEYML --> ANS["make deployer · deployer-tout
ansible-playbook"]
VAULT["vault.yml
ANSIBLE_VAULT_IDENTITY_LIST"] --> ANS
ANS --> VM["VM : groupes → playbooks/groupes/GROUPE.yml → rôles"]
```
1. **On édite le plan, jamais l'inventaire.** VMID, IP, VLAN, FQDN et appartenances aux
groupes sont dérivés de la `fonction` d'une VM et des applications qui y tournent.
2. **L'inventaire est généré** et comparé à l'existant avant d'être écrit.
3. **L'ordre de déploiement est généré** depuis les couches et le graphe des dépendances
entre groupes.
4. **Un groupe = un playbook homonyme** (`playbooks/groupes/.yml`) qui applique ses
rôles. Le socle (`serveur_debian`) et le durcissement (`serveur_durci`) passent en premier.
Les registres du plan :
| Registre | Entité | Champs clés |
|---|---|---|
| `plan/serveurs.yml` | VM | `fonction`, `etat` (`planifie` → `actif`), placement |
| `plan/applications.yml` | service (entité pivot) | `groupe` (rôle), `hote`, `port`, `liens`, `expose` |
| `plan/bases-donnees.yml` | base | serveur, propriétaire, secret en voûte, `consommateur`, `portee` |
| `plan/domaines.yml` | zone DNS | `autorite`, `edge`, `dnssec`, `enregistrements`, `resolution_interne` |
| `plan/nomenclature.yml` | dérivation | `index` (seed), fonctions → VMID / VLAN / IP |
Détail : `docs/conception/plan-et-generation.md`, `docs/conception/meta-classe.md`.
## Ce qu'un rôle déclare, ce que le moteur dérive
Chaque rôle porte ses déclarations dans `roles//meta/`. Le moteur les agrège ; aucune
liste n'est tenue à la main ailleurs.
| Déclaration | Ce que le moteur en dérive | Doc |
|---|---|---|
| `meta/flux.yml` | nftables de l'hôte, pare-feu Proxmox, frontière OPNsense, registre des flux | `conception/flux-conception.md` |
| `meta/liens.yml` | variables de connexion service → service | `conception/bindings-conception.md` |
| `meta/empreinte.yml` | RAM, CPU, disque de la VM | `conception/dimensionnement-ressources.md` |
| `meta/supervision.yml`, `meta/metriques.yml` | services Icinga, cibles Prometheus, tableaux | `conception/supervision-conception.md` |
| `meta/authentification.yml` | portée et mécanisme d'authentification du rôle, gardés par P29 | `conception/authentification.md` |
| `meta/acces.yml` | rôles d'accès projetés dans Keycloak | `exploitation/autorisation.md` |
| `meta/integration.yml` | intégrations universelles (`client_*`) posées sur chaque VM | `conception/integrations-vm.md` |
## Réseau
```mermaid
flowchart TB
NET(("Internet")) --> OPN["Frontière OPNsense
nord/sud · seul équipement L3 de bordure"]
OPN --> SDN["SDN EVPN sur les hyperviseurs
une zone (VRF) par écosystème · passerelles anycast"]
SDN --> PFW["Pare-feu Proxmox
est-ouest, par VM"]
PFW --> NFT["nftables de l'hôte"]
NFT --> SVC["service"]
FLUX["roles/*/meta/flux.yml"] -. "dérive" .-> OPN & PFW & NFT
```
- **Underlay** (`underlay.yml` du site) : la fabric physique — gestion, iSCSI, Ceph, ponts.
`make underlay-plan` le confronte au réel.
- **Overlay** : un écosystème = une zone EVPN et ses VNets, nommés depuis le seed
(`make sdn-plan`). Détail : `docs/conception/sdn-evpn.md`.
- **Quatre couches de filtrage, une seule source** : les flux déclarés par les rôles.
`pair: externe` devient la politique de bordure. Détail :
`docs/conception/frontiere-opnsense.md`, `docs/conception/registre-flux.md`.
- **Deux sites reliés** par un tunnel WireGuard (`opnsense_liens_sites`) : réplication du DNS
public, administration du site pair. DNS : `docs/conception/dns-interne.md`.
## Déclaré, dérivé, mesuré
```mermaid
flowchart LR
D["Déclaré
plan · meta/ · cartes"] --> V["Dérivé
devis : scripts/devis_*.py"]
V --> P["make SUJET-plan
écart, aucune écriture"]
P --> A["make SUJET-appliquer CONFIRMER=true
réconcilie"]
R["Réel
API Proxmox · OPNsense · VM"] --> P
D --> PR["make prouver / verifier
preuves statiques : lisent le dépôt"]
```
- **Les preuves** (`make verifier`, `make prouver`) lisent le dépôt seulement : cohérence du
plan, des rôles, de la doc. Registre : `docs/audit/affirmations.md`.
- **Les devis** lisent le système en marche et le comparent à ce que le plan dérive. Ils
n'écrivent rien ; la cible `-appliquer` correspondante réconcilie, sous `CONFIRMER=true`.
Détail : `docs/exploitation/devis-services.md`.
## Sécurité
| Sujet | Mécanisme | Doc |
|---|---|---|
| Secrets | une voûte par dépôt, une clé par voûte, `scripts/voutes.py` construit `ANSIBLE_VAULT_IDENTITY_LIST` | `exploitation/sortir-les-cles-du-poste.md` |
| Confiance machine | AC interne step-ca (ACME), certificats par `client_pki` | `conception/integrations-vm.md` |
| Identité | OpenLDAP source unique, Keycloak fédéré, `oauth2-proxy` devant les services sans OIDC | `conception/identite-sso.md`, `conception/authentification.md` |
| Habilitations | amorcées par `amorcage_acces`, jamais réconciliées | `exploitation/autorisation.md` |
| Administration | tunnel WireGuard nominatif `admins`, rebond par la frontière | `exploitation/acces-administration.md` |
| Transit | chaque flux déclare son `chiffrement` ; le registre en fait la synthèse | `conception/flux-conception.md`, `conception/registre-flux.md` |
## Invariants
- Logiciel libre exclusivement ; ni Docker, ni Podman.
- L'inventaire et `playbooks/site.yml` sont générés : ne pas les éditer.
- Aucun nom d'inventaire codé en dur : `` est résolu par `ORDRE_INVENTAIRE`.
- Le moteur s'exploite sans IA : la doc, `make` et le GUI suffisent.
- L'autorité est `AGENTS.md`.