Set-OPS — moteur d'ecosystemes numeriques souverains (Alliance Boreale)

This commit is contained in:
Alliance Boreale 2026-06-24 20:17:46 -04:00
commit 3dd3f43ad8
308 changed files with 13649 additions and 0 deletions

7
.ansible-lint Normal file
View file

@ -0,0 +1,7 @@
# Les fichiers sous docs/ sont des REGISTRES DE DONNEES (consommes par Python),
# pas du contenu Ansible : nomenclature alignee pour la lisibilite, serveurs.yml
# genere par safe_dump. Les regles yaml d'ansible-lint ne s'y appliquent pas.
exclude_paths:
- docs/
- instance/
- exemples/

30
.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
*.retry
# Instance (plan + inventaire) : depot separe, monte ici par symlink (modele A).
/instance
.vault-pass
facts_cache/
.ansible/
*.vault
*.vault.yml
*.secret
*.pem
*.key
*.p12
*.pfx
.env
.env.*
id_rsa
id_ed25519
__pycache__/
*.pyc
*.swp
*.swo
*~
.vscode/
.idea/
tmp/
dist/
*.tar
*.tar.gz
*.zip
exemples/modeles/*/inventories/*/hosts*.yml

690
AGENTS.md Normal file
View file

@ -0,0 +1,690 @@
# AGENTS.md — Set-OPS
## Rôle du dépôt
Ce dépôt contient les playbooks, rôles, inventaires et templates Ansible servant à construire, configurer, maintenir et documenter les serveurs dun écosystème numérique souverain.
`Set-OPS` est le moteur global dexploitation décosystèmes numériques souverains.
Le template Debian 13 Proxmox est seulement un sous-ensemble du dépôt. Le dépôt ne doit jamais être restructuré autour dun seul besoin ponctuel.
---
## Mission et identité
Au-delà de lexploitation, `Set-OPS` **définit et construit un écosystème numérique souverain** : une infrastructure interne auto-suffisante, sans dépendance SaaS, dont tous les piliers sont décrits en code et reliés entre eux.
Piliers de lécosystème :
- identité — machines (PKI / certificats) et utilisateurs (annuaire + SSO) ;
- confiance — autorité de certification interne (ACME) ;
- nommage et adressage — DNS interne et nomenclature dérivable ;
- données — bases relationnelles et cache, avec registre des connexions ;
- communication — relais courriel interne ;
- observabilité et supervision — métriques, journaux, tableaux de bord, supervision active ;
- applicatif — services internes (forge, etc.) et couche web.
Propriétés visées, avec leurs nuances honnêtes :
- **Souverain / auto-suffisant** : cest lobjectif. Aucune dépendance à un service externe pour la confiance, lidentité, le nom ou la communication. Cela justifie le choix de construire plutôt quassembler des SaaS.
- **Déclaratif et convergent** : létat voulu est décrit dans des registres machine-lisibles (`instance/plan/nomenclature.yml`, `docs/dependances-groupes.yml`, `instance/plan/bases-donnees.yml`) et appliqué par les groupes Ansible. Ces registres sont la **source unique de vérité**.
- **Pas (encore) auto-réparé** : la convergence est pilotée par lopérateur (GUI / CLI / `make`), pas une boucle fermée dauto-remédiation.
- **Définition avant déploiement** : une grande partie est planifiée et validée (`--syntax-check`, `ansible-lint`) mais pas encore exécutée contre des VM réelles. Ne jamais présenter un rôle non déployé comme « en production ».
Conséquence pour le travail : préserver la discipline qui tient lensemble — registres comme source unique, dépendances explicites, validation de chaque pièce, secrets hors dépôt. Cest ce qui empêche lécosystème de devenir un objet ingérable. Le template Debian 13 reste la fondation (le moule des VM), pas la finalité.
---
## Le plan et la génération de linventaire (méta-classe)
`Set-OPS` se pilote par un **plan**, pas par lédition directe de linventaire.
Linventaire Ansible est **généré** depuis le plan.
**RÈGLE DOR : `instance/inventories/production/hosts.yml` est un artefact GÉNÉRÉ. Ne jamais léditer à la main.** On édite le *plan*, puis on régénère.
- L**application** est lentité pivot ; le **groupe** Ansible nest quune capacité (le rôle appliqué), plus une cible de liaison.
- Le plan vit dans des registres machine-lisibles : `instance/plan/serveurs.yml` (les VM), `instance/plan/applications.yml` (les services et leurs liens `requiert`/`utilise`/`expose`), `instance/plan/bases-donnees.yml`, `instance/plan/domaines.yml`, `instance/plan/nomenclature.yml`.
- Génération : `make instancier` (génère + diff sémantique), `make instancier-appliquer` (régénère `hosts.yml`, refuse si le diff nest pas vide — `FORCE=1` pour un changement intentionnel).
- Flux : **éditer le plan → `make instancier` (revoir le diff) → `make instancier-appliquer` → déployer**. Via le GUI : vues **Serveurs** et **Applications**, puis « Appliquer le plan » (la vue Inventaire est en lecture seule).
- VMID / IP / VLAN / passerelle sont **dérivés** de la `fonction` via la nomenclature ; les groupes dune VM sont dérivés (socle + services des applications + intégrations + état).
- Garde-fous : validateurs de registres, diff-vide, `node --check` du JS du GUI (`scripts/verifier_gui.py`, dans `make inventaire-verifier`), git comme filet.
Référence complète : **`docs/plan-et-generation.md`**.
**Plan de contrôle gelé en périmètre** : le GUI, le générateur, l'IPAM et la modélisation sont volontairement *maison* et **souverains**, mais leur périmètre est gelé. Ne pas y ajouter de fonctionnalités de type NetBox/AWX (RBAC, historique d'audit, détection de conflits IPAM, API riche) : le besoin réel d'une de ces fonctions est le **signal d'adopter l'outil mûr correspondant** (NetBox pour la source de vérité, AWX pour l'exécution), pas de le réimplémenter. Décision et seuils : **`docs/positionnement.md`**.
---
## Règle dor IA
Un seul agent IA travaille dans ce dépôt à la fois.
- Soit Codex.
- Soit Claude Code.
- Jamais les deux simultanément.
Tout changement significatif doit être consigné dans `CHANGELOG.md`.
---
## Principes
1. Toujours lire lexistant avant de modifier.
2. Ne jamais introduire de secret en clair.
3. Ne jamais casser lidempotence Ansible.
4. Ne jamais exécuter daction destructive sans confirmation explicite.
5. Préférer la simplicité à lingénierie excessive.
6. Documenter ce qui est utile à lexploitation réelle.
7. Préférer les correctifs ciblés aux régénérations massives.
8. Ne jamais supposer quun rôle est complet sans lavoir inspecté.
9. Ne jamais déclarer un playbook prêt si la validation minimale échoue.
10. **Impératif : Set-OPS doit rester pleinement exploitable par un humain SANS IA.** La doc, `make` et le GUI sont linterface primaire et complète. Ne jamais introduire de fonctionnalité qui *exige* une IA pour sen servir. LIA nassiste que le mainteneur pour faire évoluer loutil — jamais lutilisateur final. (Le bon étalon : un sysadmin humain réussit depuis la doc. `AGENTS.md`/`CLAUDE.md` ne font pas partie de loutil livré.)
---
## Sécurité
Ne jamais commiter :
- mots de passe ;
- clés privées SSH ;
- tokens API ;
- secrets non chiffrés ;
- fichiers `.env` sensibles ;
- certificats privés ;
- backups réels ;
- exports de production non anonymisés.
Les secrets doivent être gérés hors dépôt ou avec un mécanisme explicitement prévu, par exemple :
- Ansible Vault ;
- fichier local non versionné ;
- secret injecté hors dépôt ;
- gestionnaire de secrets approuvé.
---
## Actions destructives
Toute action destructive doit exiger une variable explicite, par exemple :
```yaml
confirm_destructive_action: true
```
Sont considérées comme destructives ou risquées :
- modification bloquante de SSH ;
- activation ou modification dun pare-feu ;
- suppression dutilisateurs ;
- suppression de paquets critiques ;
- formatage disque ;
- modification de partitions ;
- redémarrage massif ;
- purge de données ;
- changement réseau pouvant couper laccès ;
- modification dun hyperviseur Proxmox ;
- opération sur stockage, iSCSI, ZFS ou Ceph.
Sans confirmation explicite, refuser lexécution.
---
## Règle de modification du dépôt
Avant toute modification, exécuter ou demander léquivalent de :
```bash
git status --short
find . -maxdepth 3 -type f | sort
```
Ne pas remplacer massivement larborescence sans demande explicite.
Avant de modifier un fichier, lire son contenu actuel.
Après modification, indiquer clairement :
- les fichiers créés ;
- les fichiers modifiés ;
- les commandes de validation exécutées ;
- les tests non exécutés ;
- les limites connues.
---
## Validation Ansible obligatoire
Avant de proposer un changement comme terminé, vérifier au minimum la syntaxe du playbook touché.
Exemple pour le template Debian 13 Proxmox :
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_preparer.yml --syntax-check
```
Pour un autre playbook, remplacer le chemin par le playbook concerné.
Ne jamais déclarer un playbook prêt si `--syntax-check` échoue.
Si `ansible-lint` est disponible, lutiliser :
```bash
ansible-lint
```
Si `ansible-lint` nest pas disponible, le signaler clairement. Ne pas inventer un résultat.
---
## Règle pour les handlers Ansible
Chaque rôle qui utilise `notify` doit contenir son handler dans le rôle lui-même.
Exemple :
```text
roles/ssh_baseline/tasks/main.yml
roles/ssh_baseline/handlers/main.yml
```
Ne pas dépendre dun handler défini dans un autre rôle, sauf justification explicite.
Tout `notify` doit pointer vers un handler existant.
Commandes de vérification utiles :
```bash
find roles -path '*/tasks/*.yml' -exec grep -H "notify:" {} \;
find roles -path '*/handlers/main.yml' -print
```
Avant de livrer un rôle, vérifier que chaque handler référencé existe réellement.
---
## Style Ansible attendu
Les playbooks doivent être :
- idempotents ;
- lisibles ;
- sobres ;
- compatibles Debian 13 sauf exception documentée ;
- testables avec `--check` autant que possible ;
- sécuritaires par défaut ;
- sans dépendances SaaS ou cloud inutiles.
Préférer les modules Ansible standards :
- `ansible.builtin.apt`
- `ansible.builtin.template`
- `ansible.builtin.copy`
- `ansible.builtin.service`
- `ansible.builtin.systemd`
- `ansible.builtin.lineinfile`
- `ansible.builtin.file`
- `ansible.builtin.user`
- `ansible.builtin.group`
Éviter `shell` et `command` sauf nécessité réelle.
Lorsquune commande shell est nécessaire, elle doit être encadrée avec les paramètres appropriés selon le cas :
- `changed_when`
- `failed_when`
- `creates`
- `removes`
---
## Structure générale du dépôt
Le dépôt peut contenir progressivement :
```text
inventories/
playbooks/
roles/
templates/
files/
scripts/
docs/
```
Les playbooks peuvent être classés par domaine :
```text
playbooks/
├── groupes/
├── maintenance/
├── monitoring/
├── networking/
├── proxmox/
├── modeles_vm/
├── web/
├── database/
├── identity/
├── backup/
└── applications/
```
Les rôles peuvent être ajoutés progressivement selon les besoins.
Ne pas créer de structure inutile uniquement pour donner une impression de complétude.
La conformité normale des VM déployées doit passer par `playbooks/groupes/`.
Ne pas maintenir en parallèle des playbooks de couches génériques comme `playbooks/socle/` ou `playbooks/durcissement/` lorsqu'un groupe opérationnel exprime déjà cet état voulu.
---
## Interface opérateur Makefile
Le `Makefile` est l'interface opérateur privilégiée pour les gestes courants.
Les commandes `make` doivent simplifier l'exploitation Ansible sans masquer les playbooks réellement exécutés.
Préférer quelques cibles claires et utiles :
- validation du dépôt ;
- inspection des inventaires ;
- ajout ou mise à jour d'un hôte dans un inventaire ;
- association d'un hôte à ses groupes ;
- déploiement ou remise en conformité d'un hôte ;
- déploiement ou remise en conformité d'un groupe ;
- préparation, vérification et nettoyage protégé d'un modèle de VM.
Ne pas multiplier les cibles `make` secondaires si elles ne correspondent pas à un geste réel d'exploitation.
Les cibles d'exploitation des VM doivent privilégier les groupes :
```text
make deployer HOTE=web-01
make deployer-groupe GROUPE=serveur_debian
make hote-planifier HOTE=obs-01 VMID=94101 GROUPES="serveur_debian serveur_durci serveur_prometheus"
```
Éviter les cibles parallèles qui réappliquent les mêmes rôles par couche, par exemple `make socle`, `make durcissement`, `make converger` ou `make deployer-vm`.
Toute cible `make` qui lance une action destructive ou risquée doit exiger une confirmation explicite.
---
## Convention groupes et playbooks
Chaque groupe opérationnel Ansible doit avoir un playbook homonyme dans `playbooks/groupes/`.
La convention attendue est :
```text
groupe Ansible : serveur_debian
playbook : playbooks/groupes/serveur_debian.yml
```
L'appartenance aux groupes détermine les services, intégrations et politiques appliqués à une VM.
`playbooks/groupes/` est la source officielle de conformité pour les VM déployées.
Un playbook de groupe doit cibler son groupe homonyme, pas `all`, sauf justification explicite.
Les concepts comme socle Debian ou durcissement commun doivent être représentés par des groupes explicites :
```text
serveur_debian -> socle commun Debian
serveur_durci -> durcissement commun
client_dns -> intégration cliente DNS
client_pki -> intégration cliente PKI / ACME
```
Ne pas dupliquer ces mêmes rôles dans des playbooks de couches séparés.
Quand un nouveau groupe opérationnel est ajouté :
- créer le playbook homonyme dans `playbooks/groupes/` ;
- documenter son intention opérationnelle ;
- déclarer ses dépendances causales dans `docs/dependances-groupes.yml` si son exécution requiert un autre service actif ;
- prévoir les rôles nécessaires ;
- valider au minimum sa syntaxe ;
- l'ajouter aux facilités d'exploitation si l'opérateur doit l'utiliser directement.
---
## Cycle de vie et conformité des VM
Set-OPS doit gérer le cycle de vie complet des VM de linstance :
```text
VM Debian minimale
→ goldenisation du template
→ clonage
→ identité initiale cloud-init
→ conformité par groupes Ansible
→ intégrations transversales
→ conformité continue
```
Le template est seulement la fondation. Les VM existantes et futures doivent converger vers l'état voulu par les playbooks de groupes.
Les rôles et playbooks doivent donc être conçus pour être relancés régulièrement, sans effet secondaire inutile.
Le groupe d'inventaire attendu pour les VM Debian gérées est :
```text
serveur_debian
```
Les groupes spécialisés doivent s'ajouter selon les besoins réels, par exemple :
```text
client_dns
client_pki
client_ldap
client_supervision
client_metrique
serveur_web_frontal
serveur_postgresql
serveur_prometheus
```
Ne pas cibler `all` par défaut pour un playbook qui ne s'applique pas réellement à tous les hôtes.
---
## Services centraux et intégrations clientes
Pour chaque service d'infrastructure, distinguer deux responsabilités :
```text
service serveur : installe et configure le service central
intégration cliente : raccorde les VM au service central
```
Exemples :
```text
PowerDNS serveur → clients DNS / resolver / enregistrements
step-ca serveur → confiance CA / ACME client
LDAP serveur → SSSD / NSS / PAM client
Keycloak serveur → intégrations OIDC applicatives
Prometheus → node_exporter sur les VM
Icinga2 → agent ou checks distants
Grafana → datasources et dashboards côté plateforme
```
Quand un nouveau service central est ajouté, prévoir aussi le ou les rôles clients nécessaires pour intégrer les VM existantes et futures.
Les dépendances entre groupes doivent être déclarées dans :
```text
docs/dependances-groupes.yml
```
Ces dépendances servent à refuser un déploiement lorsque les prérequis actifs sont absents et à préparer les futurs checks de supervision.
Les intégrations clientes doivent être :
- idempotentes ;
- activables par inventaire ou variables ;
- désactivées par défaut si leur dépendance centrale n'existe pas ;
- documentées avec leurs prérequis ;
- testables avec `--syntax-check` et, lorsque possible, `--check`.
Ne pas mettre les intégrations applicatives ou les agents spécialisés dans le golden template, sauf justification opérationnelle explicite.
---
## Variables et inventaires
Les variables de rôle doivent utiliser un préfixe correspondant au rôle.
Exemples :
```yaml
ssh_durcissement_port: 22
nftables_socle_enabled: false
fail2ban_ssh_enabled: true
```
Séparer clairement :
```text
variables de template : construction du golden template
variables de conformité : état voulu des VM déployées
variables applicatives : services et rôles spécialisés
```
Les variables de conformité ne doivent pas couper l'accès SSH, DNS ou réseau sans validation explicite.
Toute variable susceptible de provoquer une action destructive, bloquante ou irréversible doit exiger une confirmation explicite.
---
## Langue et nommage
La surface destinée à l'opérateur doit être en français.
À franciser :
- noms de groupes d'inventaire ;
- cibles `make` ;
- variables de commande maison ;
- libellés et messages des scripts maison ;
- noms des playbooks maison quand ils sont exposés à l'opérateur ;
- documentation d'exploitation.
À ne pas franciser automatiquement :
- mots-clés Ansible (`hosts`, `roles`, `tasks`, `handlers`, `vars`, `become`) ;
- noms de modules Ansible (`ansible.builtin.apt`, `ansible.builtin.template`, etc.) ;
- conventions techniques imposées par un outil ;
- noms de rôles existants, sauf demande explicite ou migration ciblée justifiée.
Les variables de rôle peuvent rester alignées sur le nom technique du rôle, mais elles doivent conserver un préfixe clair et stable.
Ne pas faire de renommage massif uniquement pour franciser si cela augmente le risque sans bénéfice opérationnel immédiat.
---
## Template Debian 13 Proxmox
Le template Debian 13 Proxmox doit rester un socle commun.
Il peut contenir :
- Debian minimal ;
- SSH ;
- sudo ;
- compte technique `ansible` ;
- sudo NOPASSWD pour `ansible` lorsque requis ;
- `qemu-guest-agent` ;
- `cloud-init` ;
- `cloud-guest-utils` ;
- chrony ;
- outils de diagnostic de base ;
- durcissement raisonnable ;
- AppArmor ;
- auditd ;
- fail2ban SSH ;
- unattended-upgrades ;
- configuration journald ;
- sysctl de sécurité ;
- nftables installé et préparé, mais pas forcément activé.
Il ne doit pas contenir par défaut :
- NGINX ;
- PostgreSQL ;
- MariaDB ;
- Docker ;
- Podman ;
- Redis ;
- GitLab ;
- Nextcloud ;
- monitoring complet ;
- agents applicatifs spécialisés ;
- données propres à un clone ;
- secrets ;
- clés privées.
Les services spécialisés doivent être installés ensuite par des playbooks dédiés sur les clones.
---
## SSH
Le template Debian 13 Proxmox doit être construit avec un accès SSH par clé dès le départ.
Cloud-init doit injecter le `ciuser` et sa clé publique avant l'exécution d'Ansible.
État attendu :
```text
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
AuthenticationMethods publickey
```
Ne pas lancer le playbook de préparation tant que l'accès SSH par clé au compte technique n'est pas confirmé.
Toute modification SSH doit valider la configuration avant rechargement :
```bash
sshd -t
```
---
## Pare-feu
Ne pas activer un pare-feu générique dans un template sans confirmation explicite.
Pour le template Debian 13, `nftables` peut être installé et préparé, mais rester désactivé par défaut.
Lactivation du pare-feu doit être faite sur un clone ou sur un serveur final, avec des règles adaptées à son rôle.
---
## Cloud-init
Cloud-init sert à donner lidentité initiale dun clone :
- hostname ;
- utilisateur initial ;
- clé SSH ;
- adresse IP ;
- passerelle ;
- DNS ;
- agrandissement de la partition racine.
Cloud-init ne doit pas remplacer Ansible pour la configuration applicative.
Séparation attendue :
```text
Proxmox + cloud-init : identité initiale de la VM
Set-OPS + Ansible : configuration réelle du serveur
```
---
## Nettoyage avant template
Le nettoyage final avant conversion en template doit être protégé par une confirmation explicite.
Exemple :
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_nettoyer.yml -e template_cleanup_confirm=true
```
Le nettoyage peut inclure :
- `cloud-init clean --logs` ;
- nettoyage du cache APT ;
- rotation ou purge contrôlée des journaux ;
- vidage de `/etc/machine-id` ;
- remise en place du lien `/var/lib/dbus/machine-id` ;
- suppression des historiques shell.
Ne jamais lancer ce nettoyage sur un serveur de production sans confirmation explicite.
---
## CHANGELOG
Chaque modification significative doit être inscrite dans `CHANGELOG.md`.
Format recommandé :
```markdown
## YYYY-MM-DD
### Ajouté
- ...
### Modifié
- ...
### Corrigé
- ...
```
Les corrections de rôles, handlers, playbooks et templates doivent être consignées.
---
## Stabilisation des changements
Après une série cohérente de changements validés, recommander un commit propre avant de poursuivre vers un nouveau chantier.
Avant de recommander un commit :
- vérifier l'état Git ;
- résumer les fichiers créés, modifiés, déplacés ou supprimés ;
- indiquer les validations exécutées ;
- signaler les validations non exécutées ;
- mentionner les risques ou limites connus.
Ne pas créer de commit sans demande explicite.
---
## Comportement attendu de Codex
Avant de modifier :
1. Lire `AGENTS.md`.
2. Lire `README.md`, `CHANGELOG.md` et `ansible.cfg` sils existent.
3. Vérifier létat Git.
4. Inspecter les fichiers concernés.
5. Identifier la portée exacte de la demande.
6. Proposer le plus petit changement utile.
7. Préserver les conventions existantes.
Après modification :
1. Résumer les fichiers créés ou modifiés.
2. Indiquer les commandes de validation exécutées.
3. Signaler clairement ce qui na pas été testé.
4. Mettre à jour `CHANGELOG.md` si pertinent.
5. Ne pas affirmer que cest prêt si la validation a échoué.
---
## Philosophie
Set-OPS est un outil dexploitation réelle, pas une démonstration technique.
Priorités :
- reproductibilité ;
- sobriété ;
- clarté ;
- sécurité ;
- maintenance ;
- autonomie ;
- résilience.
La complexité doit toujours être justifiée par un bénéfice opérationnel clair.

21
CHANGELOG.md Normal file
View file

@ -0,0 +1,21 @@
# CHANGELOG — Set-OPS
## 2026-06-24 — Première publication publique
Première mise à disposition publique de **Set-OPS**, moteur Ansible d'écosystèmes
numériques souverains sur Proxmox — offert à la communauté québécoise par
l'**Alliance Boréale**, à la Saint-Jean-Baptiste 2026.
- **Moteur générique, piloté par un plan déclaratif** : on édite le plan
(`instance/plan/*.yml`), l'inventaire Ansible se génère, les VM se clonent depuis
un golden template Debian 13, les rôles s'appliquent par groupes. Tout passe par
`make`, le GUI local et la documentation.
- **Piliers d'un écosystème souverain** : socle Debian durci, AC/PKI interne, DNS
interne, identité (LDAP + SSO), relais courriel, bases de données, observabilité,
forge.
- **Catalogue de modèles prêts à déployer** (`exemples/modeles/`) : un hébergeur
copie un modèle, le renseigne à ses couleurs, et instancie.
- **Souveraineté jusqu'au bout** : Set-OPS s'exploite entièrement à la main, sans
aucune IA.
Pour démarrer : **`QUICKSTART.md`**.

244
CLAUDE.md Normal file
View file

@ -0,0 +1,244 @@
# CLAUDE.md — Set-OPS
## Instruction principale
Claude Code doit lire et respecter `AGENTS.md` avant toute modification.
`AGENTS.md` est la source dautorité principale du dépôt.
En cas de contradiction entre `CLAUDE.md` et `AGENTS.md`, suivre `AGENTS.md`.
---
## Rôle du dépôt
`Set-OPS` est le moteur Ansible global dexploitation décosystèmes numériques souverains.
Le template Debian 13 Proxmox est seulement un sous-ensemble du dépôt. Ne pas restructurer tout le dépôt autour de ce seul chantier.
---
## Règle dor IA
Un seul agent IA travaille dans ce dépôt à la fois.
- Soit Codex.
- Soit Claude Code.
- Jamais les deux simultanément.
---
## Avant toute modification
Exécuter ou demander léquivalent de :
```bash
git status --short
find . -maxdepth 3 -type f | sort
```
Lire au minimum :
```text
AGENTS.md
README.md
CHANGELOG.md
ansible.cfg
```
Lire aussi les fichiers directement concernés avant de les modifier.
Ne pas remplacer massivement larborescence sans demande explicite.
Préférer un correctif minimal ciblé.
---
## Validation Ansible obligatoire
Avant de dire quun changement est prêt, exécuter au minimum le `--syntax-check` du playbook touché.
Pour le template Debian 13 Proxmox :
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_preparer.yml --syntax-check
```
Si `ansible-lint` est disponible :
```bash
ansible-lint
```
Si `ansible-lint` nest pas disponible, le signaler clairement.
Ne jamais déclarer un playbook prêt si la validation échoue.
---
## Handlers Ansible
Chaque rôle qui utilise `notify` doit contenir son handler dans le rôle lui-même.
Exemple :
```text
roles/ssh_baseline/tasks/main.yml
roles/ssh_baseline/handlers/main.yml
```
Commandes utiles :
```bash
find roles -path '*/tasks/*.yml' -exec grep -H "notify:" {} \;
find roles -path '*/handlers/main.yml' -print
```
Avant de livrer un rôle, vérifier que chaque handler référencé existe réellement.
---
## Actions destructives
Toute action destructrice ou risquée doit exiger une confirmation explicite.
Exemple :
```yaml
confirm_destructive_action: true
```
Sont considérées risquées :
- modification bloquante de SSH ;
- activation ou modification dun pare-feu ;
- suppression dutilisateurs ;
- suppression de paquets critiques ;
- formatage disque ;
- modification de partitions ;
- redémarrage massif ;
- purge de données ;
- changement réseau pouvant couper laccès ;
- modification dun hyperviseur Proxmox ;
- opération sur stockage, iSCSI, ZFS ou Ceph.
Sans confirmation explicite, refuser lexécution.
---
## Template Debian 13 Proxmox
Le template doit rester un socle commun.
Il peut contenir :
- Debian minimal ;
- SSH ;
- sudo ;
- compte technique `ansible` ;
- sudo NOPASSWD pour `ansible` lorsque requis ;
- `qemu-guest-agent` ;
- `cloud-init` ;
- `cloud-guest-utils` ;
- chrony ;
- outils de diagnostic ;
- hardening raisonnable ;
- AppArmor ;
- auditd ;
- fail2ban SSH ;
- unattended-upgrades ;
- journald ;
- sysctl de sécurité ;
- nftables installé et préparé, mais non activé par défaut.
Il ne doit pas contenir par défaut :
- NGINX ;
- PostgreSQL ;
- MariaDB ;
- Docker ;
- Podman ;
- Redis ;
- GitLab ;
- Nextcloud ;
- monitoring complet ;
- données propres à un clone ;
- secrets ;
- clés privées.
---
## SSH
Pendant la construction du template :
```text
PasswordAuthentication yes
PermitRootLogin no
PubkeyAuthentication yes
```
État cible après validation des clés SSH :
```text
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
```
Ne jamais désactiver lauthentification par mot de passe avant davoir confirmé que laccès par clé fonctionne.
Toute modification SSH doit valider la configuration avant rechargement :
```bash
sshd -t
```
---
## Pare-feu
Ne pas activer un pare-feu générique dans le template sans confirmation explicite.
`nftables` peut être installé et préparé, mais rester désactivé dans le template.
Lactivation doit être faite sur un clone ou un serveur final, avec des règles adaptées au rôle du serveur.
---
## Cloud-init
Cloud-init donne lidentité initiale dun clone :
- hostname ;
- utilisateur initial ;
- clé SSH ;
- IP ;
- passerelle ;
- DNS ;
- agrandissement de la partition racine.
Cloud-init ne remplace pas Ansible.
Séparation attendue :
```text
Proxmox + cloud-init : identité initiale de la VM
Set-OPS + Ansible : configuration réelle du serveur
```
---
## Après modification
Répondre avec :
1. fichiers créés ;
2. fichiers modifiés ;
3. commandes de validation exécutées ;
4. résultat des validations ;
5. tests non exécutés ;
6. limites connues ;
7. entrée `CHANGELOG.md` ajoutée ou raison de labsence dentrée.
Ne pas dire que cest prêt si ce nest pas validé.

235
LICENSE Normal file
View file

@ -0,0 +1,235 @@
GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.
Preamble
The GNU Affero General Public License is a free, copyleft license for software and other kinds of works, specifically designed to ensure cooperation with the community in the case of network server software.
The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, our General Public Licenses are intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users.
When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things.
Developers that use our General Public Licenses protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License which gives you legal permission to copy, distribute and/or modify the software.
A secondary benefit of defending all users' freedom is that improvements made in alternate versions of the program, if they receive widespread use, become available for other developers to incorporate. Many developers of free software are heartened and encouraged by the resulting cooperation. However, in the case of software used on network servers, this result may fail to come about. The GNU General Public License permits making a modified version and letting the public access it on a server without ever releasing its source code to the public.
The GNU Affero General Public License is designed specifically to ensure that, in such cases, the modified source code becomes available to the community. It requires the operator of a network server to provide the source code of the modified version running there to the users of that server. Therefore, public use of a modified version, on a publicly accessible server, gives the public access to the source code of the modified version.
An older license, called the Affero General Public License and published by Affero, was designed to accomplish similar goals. This is a different license, not a version of the Affero GPL, but Affero has released a new version of the Affero GPL which permits relicensing under this license.
The precise terms and conditions for copying, distribution and modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU Affero General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based on the Program.
To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work.
A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source.
The Corresponding Source for a work in source code form is that same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures.
When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified it, and giving a relevant date.
b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices".
c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so.
A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways:
a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b.
d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d.
A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product.
"Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made.
If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM).
The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or authors of the material; or
e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors.
All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11).
However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.
Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party.
If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it.
A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program.
13. Remote Network Interaction; Use with the GNU General Public License.
Notwithstanding any other provision of this License, if you modify the Program, your modified version must prominently offer all users interacting with it remotely through a computer network (if your version supports such interaction) an opportunity to receive the Corresponding Source of your version by providing access to the Corresponding Source from a network server at no charge, through some standard or customary means of facilitating copying of software. This Corresponding Source shall include the Corresponding Source for any work covered by version 3 of the GNU General Public License that is incorporated pursuant to the following paragraph.
Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the work with which it is combined will remain governed by version 3 of the GNU General Public License.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of the GNU Affero General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU Affero General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU Affero General Public License, you may choose any version ever published by the Free Software Foundation.
If the Program specifies that a proxy can decide which future versions of the GNU Affero General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program.
Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found.
Set-OPS
Copyright (C) 2026 Alliance Boréale
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If your software can interact with users remotely through a computer network, you should also make sure that it provides a way for users to get its source. For example, if your program is a web application, its interface could display a "Source" link that leads users to an archive of the code. There are many ways you could offer source, and different solutions will be better for different programs; see section 13 for the specific requirements.
You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU AGPL, see <http://www.gnu.org/licenses/>.

478
Makefile Normal file
View file

@ -0,0 +1,478 @@
SHELL := /usr/bin/env bash
export ANSIBLE_HOME ?= $(CURDIR)/.ansible
export ANSIBLE_LOCAL_TEMP ?= $(CURDIR)/.ansible/tmp
export ANSIBLE_SSH_CONTROL_PATH_DIR ?= $(CURDIR)/.ansible/cp
export ANSIBLE_SSH_ARGS ?= -F /dev/null -o ControlMaster=no
export SETOPS_INSTANCE ?= instance
INVENTAIRE_LAB ?= $(SETOPS_INSTANCE)/inventories/lab/hosts.yml
INVENTAIRE_PRODUCTION ?= $(SETOPS_INSTANCE)/inventories/production/hosts.yml
FICHIER_INVENTAIRE ?= $(INVENTAIRE_PRODUCTION)
FICHIER_DEPENDANCES ?= docs/dependances-groupes.yml
GROUPE_MODELE ?= modeles_vm
GROUPE_DEBIAN ?= serveur_debian
GROUPE_HOTES_ACTIFS ?= hotes_actifs
LIMITE ?= $(GROUPE_DEBIAN)
HOTE ?=
ADRESSE_IP ?=
GROUPES ?= $(GROUPE_DEBIAN)
GROUPE ?= $(GROUPE_DEBIAN)
UTILISATEUR_ANSIBLE ?= ansible
VMID_MODELE ?=
VMID ?=
NOEUD_PROXMOX ?=
STOCKAGE_PROXMOX ?=
FORMAT_DISQUE ?=
TAILLE_DISQUE ?=
DISQUE_PROXMOX ?=
CIDR ?= 24
PASSERELLE ?=
DNS ?=
DHCP ?= false
CIUSER ?=
CLE_SSH_PUBLIQUE ?=
PONT_PROXMOX ?=
VLAN ?=
DEMARRER ?=
CLONE_COMPLET ?=
CONFIRMER ?= false
VERIFICATION ?= false
DIFF ?= false
ETIQUETTES ?=
SAUTER_ETIQUETTES ?=
VARIABLES ?=
OPTIONS_PLAYBOOK :=
ifneq ($(LIMITE),)
OPTIONS_PLAYBOOK += --limit $(LIMITE)
endif
ifeq ($(VERIFICATION),true)
OPTIONS_PLAYBOOK += --check
endif
ifeq ($(DIFF),true)
OPTIONS_PLAYBOOK += --diff
endif
ifneq ($(ETIQUETTES),)
OPTIONS_PLAYBOOK += --tags $(ETIQUETTES)
endif
ifneq ($(SAUTER_ETIQUETTES),)
OPTIONS_PLAYBOOK += --skip-tags $(SAUTER_ETIQUETTES)
endif
ifneq ($(VARIABLES),)
OPTIONS_PLAYBOOK += -e $(VARIABLES)
endif
PLAYBOOK_PREPARER_MODELE := playbooks/modeles_vm/debian13_proxmox_preparer.yml
PLAYBOOK_VERIFIER_MODELE := playbooks/modeles_vm/debian13_proxmox_verifier.yml
PLAYBOOK_NETTOYER_MODELE := playbooks/modeles_vm/debian13_proxmox_nettoyer.yml
PLAYBOOK_VERIFIER_HOTE := playbooks/maintenance/verifier_hote_debian.yml
PLAYBOOK_PROXMOX_CLONER_VM := playbooks/proxmox/cloner_vm_debian.yml
DOSSIER_PLAYBOOKS_GROUPES := playbooks/groupes
.DEFAULT_GOAL := aide
.PHONY: ansible-runtime
ansible-runtime:
@mkdir -p "$(ANSIBLE_LOCAL_TEMP)"
@mkdir -p "$(ANSIBLE_SSH_CONTROL_PATH_DIR)"
.PHONY: _instance-requise
_instance-requise:
@if [[ ! -f "$(SETOPS_INSTANCE)/plan/serveurs.yml" ]]; then \
printf '%s\n' "Aucune instance configuree : '$(SETOPS_INSTANCE)/plan' introuvable."; \
printf '%s\n' "Demarre avec QUICKSTART.md. En bref :"; \
printf '%s\n' " cp -r exemples/modeles/<modele> ../mon-instance && ln -s ../mon-instance instance"; \
printf '%s\n' " modeles disponibles : $$(ls exemples/modeles 2>/dev/null | grep -v '\.md' | tr '\n' ' ')"; \
exit 2; \
fi
.PHONY: aide
aide:
@printf '%s\n' 'Set-OPS — moteur d ecosystemes numeriques souverains'
@printf '%s\n' ''
@printf '%s\n' 'Nouveau ? -> QUICKSTART.md (de zero a ton ecosysteme sur Proxmox)'
@printf '%s\n' 'Flux: editer le plan -> make instancier -> make instancier-appliquer -> make deployer'
@printf '%s\n' ''
@printf '%s\n' 'VM'
@printf '%s\n' ' Creer une VM (VMID/IP/VLAN/passerelle lus dans le plan):'
@printf '%s\n' ' make creer-vm HOTE=web-frontal-01'
@printf '%s\n' ' Cloner seulement, sans passer par le plan:'
@printf '%s\n' ' make cloner-vm HOTE=web-frontal-01 VMID=95301 VLAN=15 ADRESSE_IP=10.0.2.31 PASSERELLE=10.0.2.1'
@printf '%s\n' ' Configurer Proxmox et le Vault API:'
@printf '%s\n' ' make config'
@printf '%s\n' ''
@printf '%s\n' 'Hotes'
@printf '%s\n' ' Planifier/modifier un hote: editer le plan, puis regenerer:'
@printf '%s\n' ' editer instance/plan/serveurs.yml (ou la vue Serveurs du GUI)'
@printf '%s\n' ' make instancier-appliquer'
@printf '%s\n' ' Verifier un deploiement a blanc (dry-run):'
@printf '%s\n' ' make verifier-deploiement HOTE=web-frontal-01'
@printf '%s\n' ' Remettre un hote en conformite selon ses groupes:'
@printf '%s\n' ' make deployer HOTE=web-frontal-01'
@printf '%s\n' ' Afficher un hote:'
@printf '%s\n' ' make hote-afficher HOTE=web-frontal-01'
@printf '%s\n' ' Diagnostiquer:'
@printf '%s\n' ' make verifier-hote LIMITE=web-frontal-01'
@printf '%s\n' ''
@printf '%s\n' 'Groupes'
@printf '%s\n' ' Appliquer un groupe complet:'
@printf '%s\n' ' make deployer-groupe GROUPE=serveur_debian'
@printf '%s\n' ' Convention:'
@printf '%s\n' ' groupe serveur_debian -> playbooks/groupes/serveur_debian.yml'
@printf '%s\n' ''
@printf '%s\n' 'Inventaires'
@printf '%s\n' ' Graphe de production:'
@printf '%s\n' ' make inventaire'
@printf '%s\n' ' Graphe explicite:'
@printf '%s\n' ' make inventaire-graphe FICHIER_INVENTAIRE=$(SETOPS_INSTANCE)/inventories/production/hosts.yml'
@printf '%s\n' ' Verifier les inventaires:'
@printf '%s\n' ' make inventaire-verifier'
@printf '%s\n' ' Lister les donnees brutes:'
@printf '%s\n' ' make inventaire-lister'
@printf '%s\n' ' Interface locale de gestion:'
@printf '%s\n' ' make inventaire-ui'
@printf '%s\n' ''
@printf '%s\n' 'Modele Debian 13 Proxmox'
@printf '%s\n' ' Construire et verifier:'
@printf '%s\n' ' make preparer-modele'
@printf '%s\n' ' make verifier-modele'
@printf '%s\n' ' Nettoyage final protege:'
@printf '%s\n' ' make nettoyer-modele CONFIRMER=true'
@printf '%s\n' ''
@printf '%s\n' 'Validation'
@printf '%s\n' ' make syntaxe'
@printf '%s\n' ' make lint'
@printf '%s\n' ' make verifier'
@printf '%s\n' ''
@printf '%s\n' 'Variables frequentes'
@printf '%s\n' ' HOTE=web-frontal-01 GROUPE=serveur_debian GROUPES="serveur_debian serveur_durci"'
@printf '%s\n' ' VMID=95301 VLAN=15 ADRESSE_IP=10.0.2.31 PASSERELLE=10.0.2.1'
@printf '%s\n' ' FICHIER_INVENTAIRE=$(SETOPS_INSTANCE)/inventories/production/hosts.yml FICHIER_DEPENDANCES=docs/dependances-groupes.yml CONFIRMER=true'
.PHONY: lint
lint: ansible-runtime
ansible-lint
.PHONY: syntaxe syntaxe-modele syntaxe-nettoyage syntaxe-verification-modele syntaxe-verification-hote syntaxe-groupes syntaxe-proxmox
syntaxe: syntaxe-modele syntaxe-verification-modele syntaxe-nettoyage syntaxe-verification-hote syntaxe-groupes syntaxe-proxmox
syntaxe-modele: ansible-runtime
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_PREPARER_MODELE) --syntax-check
syntaxe-verification-modele: ansible-runtime
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_VERIFIER_MODELE) --syntax-check
syntaxe-nettoyage: ansible-runtime
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_NETTOYER_MODELE) --syntax-check
syntaxe-verification-hote: ansible-runtime
ansible-playbook -i $(INVENTAIRE_PRODUCTION) $(PLAYBOOK_VERIFIER_HOTE) --syntax-check
syntaxe-groupes: ansible-runtime
@for playbook in $(DOSSIER_PLAYBOOKS_GROUPES)/*.yml; do \
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --syntax-check; \
done
syntaxe-proxmox: ansible-runtime
ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) --syntax-check
.PHONY: test
test:
python3 scripts/tests/test_inventory_host.py
.PHONY: verifier
verifier: lint test inventaire-verifier syntaxe
.PHONY: inventaire hote-planifier hote-ajouter hote-groupes hote-afficher appliquer deployer deployer-groupe cloner-vm creer-vm config inventaire-ui inventaire-verifier inventaire-lister inventaire-graphe inventaire-hote inventaire-lab inventaire-production
inventaire: inventaire-production
config:
python3 scripts/config_proxmox.py
inventaire-ui: _instance-requise
python3 scripts/inventory_gui.py --inventaire $(FICHIER_INVENTAIRE)
hote-ajouter hote-planifier hote-groupes:
@printf '%s\n' 'Cible depreciee: l inventaire est GENERE depuis le plan, il ne s edite plus a la main.'
@printf '%s\n' 'Declare ou modifie l hote dans instance/plan/serveurs.yml (ou la vue Serveurs du GUI), puis :'
@printf '%s\n' ' make instancier-appliquer'
@printf '%s\n' '(creer-vm lit desormais VMID/IP/VLAN/passerelle directement dans l inventaire genere.)'
@exit 2
hote-afficher: ansible-runtime
@if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
exit 2; \
fi
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) afficher --hote $(HOTE)
appliquer: ansible-runtime
@if [[ -z "$(GROUPE)" ]]; then \
printf '%s\n' 'Refus: relancer avec GROUPE=nom_groupe.'; \
exit 2; \
fi
@if [[ ! -f "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" ]]; then \
printf '%s\n' 'Refus: aucun playbook pour ce groupe: $(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml'; \
exit 2; \
fi
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-groupe --groupe $(GROUPE)
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$(DOSSIER_PLAYBOOKS_GROUPES)/$(GROUPE).yml" --limit '$(GROUPE):&$(GROUPE_HOTES_ACTIFS)'
deployer: _instance-requise
@set -e; \
if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
exit 2; \
fi; \
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) verifier-actif --hote $(HOTE); \
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-hote --hote $(HOTE); \
playbooks="$$(python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) playbooks --hote $(HOTE) --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES))"; \
if [[ -z "$$playbooks" ]]; then \
printf '%s\n' 'Refus: aucun playbook applicable pour HOTE=$(HOTE).'; \
exit 2; \
fi; \
vault_chiffre="$$(grep -rlsIF '$$ANSIBLE_VAULT' $(SETOPS_INSTANCE)/inventories/production/group_vars 2>/dev/null | head -1 || true)"; \
if [[ -n "$$vault_chiffre" && -z "$${ANSIBLE_VAULT_PASSWORD_FILE:-}" ]]; then \
if [[ -t 0 ]]; then \
read -r -s -p 'Mot de passe du vault Ansible: ' mdp; echo; \
vf="$$(mktemp)"; printf '%s' "$$mdp" > "$$vf"; chmod 600 "$$vf"; \
export ANSIBLE_VAULT_PASSWORD_FILE="$$vf"; \
trap 'rm -f "$$vf"' EXIT; \
else \
printf '%s\n' 'Refus: vault chiffre detecte mais aucun mot de passe (entree non interactive). Fournir ANSIBLE_VAULT_PASSWORD_FILE ou le champ vault de la GUI.'; \
exit 2; \
fi; \
fi; \
$(MAKE) _verifier-acces-hote LIMITE="$(HOTE)"; \
$(MAKE) _verifier-privileges-hote LIMITE="$(HOTE)"; \
for playbook in $$playbooks; do \
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --limit "$(HOTE)"; \
done; \
$(MAKE) verifier-hote LIMITE="$(HOTE)"
deployer-groupe:
@if [[ -z "$(GROUPE)" ]]; then \
printf '%s\n' 'Refus: relancer avec GROUPE=nom_groupe.'; \
exit 2; \
fi
$(MAKE) appliquer GROUPE="$(GROUPE)"
.PHONY: verifier-deploiement
verifier-deploiement: ansible-runtime
@set -e; \
if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
exit 2; \
fi; \
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) verifier-actif --hote $(HOTE); \
python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) --dependances $(FICHIER_DEPENDANCES) verifier-dependances-hote --hote $(HOTE); \
playbooks="$$(python3 scripts/inventory_host.py --inventaire $(FICHIER_INVENTAIRE) playbooks --hote $(HOTE) --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES))"; \
if [[ -z "$$playbooks" ]]; then \
printf '%s\n' 'Refus: aucun playbook applicable pour HOTE=$(HOTE).'; \
exit 2; \
fi; \
vault_chiffre="$$(grep -rlsIF '$$ANSIBLE_VAULT' $(SETOPS_INSTANCE)/inventories/production/group_vars 2>/dev/null | head -1 || true)"; \
if [[ -n "$$vault_chiffre" && -z "$${ANSIBLE_VAULT_PASSWORD_FILE:-}" ]]; then \
if [[ -t 0 ]]; then \
read -r -s -p 'Mot de passe du vault Ansible: ' mdp; echo; \
vf="$$(mktemp)"; printf '%s' "$$mdp" > "$$vf"; chmod 600 "$$vf"; \
export ANSIBLE_VAULT_PASSWORD_FILE="$$vf"; \
trap 'rm -f "$$vf"' EXIT; \
else \
printf '%s\n' 'Refus: vault chiffre detecte mais aucun mot de passe (entree non interactive). Fournir ANSIBLE_VAULT_PASSWORD_FILE ou le champ vault de la GUI.'; \
exit 2; \
fi; \
fi; \
for playbook in $$playbooks; do \
ansible-playbook -i $(INVENTAIRE_PRODUCTION) "$$playbook" --limit "$(HOTE)" --check --diff; \
done
cloner-vm: ansible-runtime
@if [[ -z "$(HOTE)" || -z "$(VMID)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom VMID=id_clone.'; \
exit 2; \
fi
@if [[ -z "$(VLAN)" ]]; then \
printf '%s\n' 'Refus: relancer avec VLAN=id_vlan.'; \
exit 2; \
fi
@if ! [[ "$(VLAN)" =~ ^[0-9]+$$ ]] || (( 10#$(VLAN) < 1 || 10#$(VLAN) > 4094 )); then \
printf '%s\n' 'Refus: VLAN doit etre un nombre entre 1 et 4094.'; \
exit 2; \
fi
@if [[ -n "$(CLE_SSH_PUBLIQUE)" && ! -f "$(CLE_SSH_PUBLIQUE)" ]]; then \
printf '%s\n' 'Refus: cle publique SSH introuvable: $(CLE_SSH_PUBLIQUE)'; \
exit 2; \
fi
@if [[ "$(DHCP)" != "true" && ( -z "$(ADRESSE_IP)" || -z "$(CIDR)" || -z "$(PASSERELLE)" ) ]]; then \
printf '%s\n' 'Refus: fournir ADRESSE_IP, CIDR et PASSERELLE, ou utiliser DHCP=true.'; \
exit 2; \
fi
@ipconfig='ip=dhcp'; \
if [[ "$(DHCP)" != "true" ]]; then \
ipconfig='ip=$(ADRESSE_IP)/$(CIDR),gw=$(PASSERELLE)'; \
fi; \
extra_vars=( \
-e proxmox_clone_nom="$(HOTE)" \
-e proxmox_clone_vmid="$(VMID)" \
-e proxmox_clone_ipconfig0="$$ipconfig" \
); \
[[ -n "$(VMID_MODELE)" ]] && extra_vars+=( -e proxmox_clone_vmid_modele="$(VMID_MODELE)" ); \
[[ -n "$(NOEUD_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_noeud="$(NOEUD_PROXMOX)" ); \
[[ -n "$(STOCKAGE_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_stockage="$(STOCKAGE_PROXMOX)" ); \
[[ -n "$(FORMAT_DISQUE)" ]] && extra_vars+=( -e proxmox_clone_format="$(FORMAT_DISQUE)" ); \
[[ -n "$(CLONE_COMPLET)" ]] && extra_vars+=( -e proxmox_clone_complet="$(CLONE_COMPLET)" ); \
[[ -n "$(TAILLE_DISQUE)" ]] && extra_vars+=( -e proxmox_clone_taille_disque="$(TAILLE_DISQUE)" ); \
[[ -n "$(DISQUE_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_disque="$(DISQUE_PROXMOX)" ); \
[[ -n "$(DNS)" ]] && extra_vars+=( -e proxmox_clone_dns="$(DNS)" ); \
[[ -n "$(CIUSER)" ]] && extra_vars+=( -e proxmox_clone_ciuser="$(CIUSER)" ); \
[[ -n "$(CLE_SSH_PUBLIQUE)" ]] && extra_vars+=( -e proxmox_clone_cle_publique_fichier="$(CLE_SSH_PUBLIQUE)" ); \
[[ -n "$(PONT_PROXMOX)" ]] && extra_vars+=( -e proxmox_clone_pont="$(PONT_PROXMOX)" ); \
extra_vars+=( -e proxmox_clone_vlan="$(VLAN)" ); \
[[ -n "$(DEMARRER)" ]] && extra_vars+=( -e proxmox_clone_demarrer="$(DEMARRER)" ); \
vault_args=(); \
vault_file="$(SETOPS_INSTANCE)/inventories/lab/group_vars/proxmox.vault.yml"; \
if [[ -f "$$vault_file" ]]; then \
read -r premiere_ligne < "$$vault_file" || true; \
case "$$premiere_ligne" in \
'$$ANSIBLE_VAULT'*) \
if [[ -z "$${ANSIBLE_VAULT_PASSWORD_FILE:-}" ]]; then \
vault_args+=( --ask-vault-pass ); \
fi; \
;; \
esac; \
fi; \
ansible-playbook -i localhost, $(PLAYBOOK_PROXMOX_CLONER_VM) "$${vault_args[@]}" "$${extra_vars[@]}"
creer-vm: _instance-requise
@set -e; \
if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote (declare dans le plan).'; \
exit 2; \
fi; \
params="$$(python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) parametres-proxmox --hote $(HOTE))"; \
eval "$$params"; \
$(MAKE) cloner-vm \
HOTE="$(HOTE)" \
VMID="$$SETOPS_VMID" \
ADRESSE_IP="$$SETOPS_IP" \
CIDR="$$SETOPS_CIDR" \
PASSERELLE="$$SETOPS_PASSERELLE" \
VLAN="$$SETOPS_VLAN" \
STOCKAGE_PROXMOX="$${SETOPS_STOCKAGE:-$(STOCKAGE_PROXMOX)}" \
TAILLE_DISQUE="$${SETOPS_DISQUE:-$(TAILLE_DISQUE)}" \
NOEUD_PROXMOX="$${SETOPS_NOEUD:-$(NOEUD_PROXMOX)}" \
VMID_MODELE="$(VMID_MODELE)" \
FORMAT_DISQUE="$(FORMAT_DISQUE)" \
DISQUE_PROXMOX="$(DISQUE_PROXMOX)" \
DNS="$(DNS)" \
DHCP="$(DHCP)" \
CIUSER="$(CIUSER)" \
CLE_SSH_PUBLIQUE="$(CLE_SSH_PUBLIQUE)" \
PONT_PROXMOX="$(PONT_PROXMOX)" \
DEMARRER="$(DEMARRER)" \
CLONE_COMPLET="$(CLONE_COMPLET)"
inventaire-verifier: ansible-runtime _instance-requise
ansible-inventory -i $(INVENTAIRE_LAB) --list > /dev/null
ansible-inventory -i $(INVENTAIRE_PRODUCTION) --list > /dev/null
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) verifier-playbooks --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
python3 scripts/inventory_host.py --inventaire $(INVENTAIRE_PRODUCTION) --dependances $(FICHIER_DEPENDANCES) verifier-dependances --dossier-playbooks $(DOSSIER_PLAYBOOKS_GROUPES)
python3 scripts/verifier_gui.py
python3 scripts/serveurs.py verifier
python3 scripts/applications.py verifier
python3 scripts/bases_donnees.py verifier
python3 scripts/domaines.py verifier
.PHONY: bases bases-verifier domaines domaines-verifier applications applications-verifier applications-bootstrap serveurs serveurs-verifier serveurs-bootstrap
serveurs:
python3 scripts/serveurs.py lister
serveurs-verifier:
python3 scripts/serveurs.py verifier
serveurs-bootstrap:
python3 scripts/serveurs.py bootstrap
.PHONY: instancier instancier-appliquer
instancier: _instance-requise
python3 scripts/instancier.py generer
python3 scripts/instancier.py comparer
instancier-appliquer: _instance-requise
python3 scripts/instancier.py appliquer
bases:
python3 scripts/bases_donnees.py lister
bases-verifier:
python3 scripts/bases_donnees.py verifier
domaines:
python3 scripts/domaines.py lister
domaines-verifier:
python3 scripts/domaines.py verifier
applications:
python3 scripts/applications.py lister
applications-verifier:
python3 scripts/applications.py verifier
applications-bootstrap:
python3 scripts/applications.py bootstrap
inventaire-lister: ansible-runtime
ansible-inventory -i $(FICHIER_INVENTAIRE) --list
inventaire-graphe: ansible-runtime
ansible-inventory -i $(FICHIER_INVENTAIRE) --graph
inventaire-hote: ansible-runtime
@if [[ -z "$(HOTE)" ]]; then \
printf '%s\n' 'Refus: relancer avec HOTE=nom_hote.'; \
exit 2; \
fi
ansible-inventory -i $(FICHIER_INVENTAIRE) --host $(HOTE)
inventaire-lab:
$(MAKE) inventaire-graphe FICHIER_INVENTAIRE="$(INVENTAIRE_LAB)"
inventaire-production:
$(MAKE) inventaire-graphe FICHIER_INVENTAIRE="$(INVENTAIRE_PRODUCTION)"
.PHONY: _verifier-acces-modele _verifier-privileges-modele preparer-modele verifier-modele nettoyer-modele
_verifier-acces-modele: ansible-runtime
ansible -i $(INVENTAIRE_LAB) $(GROUPE_MODELE) -m ping -e ansible_become=false
_verifier-privileges-modele: ansible-runtime
ansible -i $(INVENTAIRE_LAB) $(GROUPE_MODELE) -b -m command -a "whoami"
preparer-modele: ansible-runtime _verifier-acces-modele _verifier-privileges-modele
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_PREPARER_MODELE)
verifier-modele: ansible-runtime
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_VERIFIER_MODELE)
nettoyer-modele: ansible-runtime
@if [[ "$(CONFIRMER)" != "true" ]]; then \
printf '%s\n' 'Refus: relancer avec CONFIRMER=true pour le nettoyage final du modele.'; \
exit 2; \
fi
ansible-playbook -i $(INVENTAIRE_LAB) $(PLAYBOOK_NETTOYER_MODELE) -e template_cleanup_confirm=true
.PHONY: _verifier-acces-hote _verifier-privileges-hote faits verifier-hote
_verifier-acces-hote: ansible-runtime
ansible -i $(INVENTAIRE_PRODUCTION) $(LIMITE) -m ping -e ansible_become=false
_verifier-privileges-hote: ansible-runtime
ansible -i $(INVENTAIRE_PRODUCTION) $(LIMITE) -b -m command -a "whoami"
faits: ansible-runtime
ansible -i $(INVENTAIRE_PRODUCTION) $(LIMITE) -m setup -a "filter=ansible_distribution*"
verifier-hote: ansible-runtime
ansible-playbook -i $(INVENTAIRE_PRODUCTION) $(PLAYBOOK_VERIFIER_HOTE) $(OPTIONS_PLAYBOOK)

104
QUICKSTART.md Normal file
View file

@ -0,0 +1,104 @@
# Démarrage rapide — de zéro à ton écosystème souverain
Tu débarques avec une **grappe Proxmox vierge** et tu veux monter ton écosystème
numérique. Voici le chemin, de bout en bout. Set-OPS est le **moteur** ; tu vas
créer **ton instance** à partir d'un modèle, puis déployer.
> Concepts en 10 s : tu **décris un plan** (quelles VM, quels services), le moteur
> **génère l'inventaire**, et Ansible **déploie**. Tu n'édites jamais l'inventaire
> à la main. Détails : `docs/plan-et-generation.md`.
## 0. Prérequis (à toi de fournir)
- une **grappe Proxmox** où tu es admin (API activée) ;
- une machine de pilotage Linux avec **Ansible**, **Python 3**, **make**, `git`
(et `node` pour le garde-fou JS du GUI, optionnel) ;
- un **token API Proxmox** ;
- une **paire de clés SSH** (la publique ira dans les VM via cloud-init) ;
- un **mot de passe Ansible Vault** (pour chiffrer tes secrets).
## 1. Cloner le moteur
```bash
git clone <url-de-Set-OPS> Set-OPS && cd Set-OPS
```
## 2. Choisir un modèle et créer ton instance
Les modèles sont dans `exemples/modeles/` (voir leur README). Choisis selon ton offre :
`socle`, `presence-web`, `forge`, `identite`, `observabilite`, `integral`.
```bash
cp -r exemples/modeles/presence-web ../mon-instance # ton instance, ailleurs
ln -s ../mon-instance instance # le moteur la trouve via ce lien
```
*(Alternative au symlink : `export SETOPS_INSTANCE=../mon-instance`.)*
## 3. Renseigner ton instance (« tes couleurs »)
- `instance/inventories/production/group_vars/all.yml`**`domaine_interne`** (ex. `monorg.internal`) ;
- `instance/plan/nomenclature.yml` → ton **supernet** (ex. `10.20.0.0/16`) ;
- `instance/plan/domaines.yml` → ton **domaine public** ;
- `instance/plan/serveurs.yml`**placement Proxmox** (nœud, stockage, disque, mémoire, cœurs).
Tu peux aussi le faire dans le GUI plus tard (`make inventaire-ui`).
## 4. Configurer Proxmox et tes secrets
```bash
make config # renseigne API host/user/port, nœud, stockage, VMID du template...
```
Place ton **token API** et tes secrets dans le Vault de l'instance
(`instance/inventories/lab/group_vars/proxmox.vault.yml`, à partir du `.example`,
chiffré avec `ansible-vault`). Exporte ton mot de passe Vault, ex. :
```bash
export ANSIBLE_VAULT_PASSWORD_FILE=~/.config/setops-vault-pass
```
## 5. Construire le golden template Debian 13 (UNE seule fois)
Une grappe vierge n'a aucun template. Crée une VM **Debian 13 vanille**, rends-la
joignable par Ansible, puis :
```bash
make preparer-modele # socle + durcissement + cloud-init + qemu-guest-agent...
make verifier-modele
make nettoyer-modele CONFIRMER=true
```
Convertis ensuite la VM en **template Proxmox** nommé `modele-debian13` (le nom de
clone source par défaut). Détails et procédure : `docs/vm-lifecycle.md` et
`docs/procedure-template-debian13-proxmox.md`.
## 6. Générer ton inventaire depuis le plan
```bash
make instancier # montre ce que le plan produit (diff)
make instancier-appliquer FORCE=1 # 1re génération : écrit instance/inventories/production/hosts.yml
```
Tes hôtes sont là, en état `planifie`. `make serveurs` te montre leurs VMID/IP dérivés.
## 7. Créer les VM (clone du template + cloud-init)
Pour chaque hôte — `creer-vm` lit ses VMID/IP/VLAN/passerelle directement dans
l'inventaire généré (`make serveurs` te les montre) :
```bash
make creer-vm HOTE=infra-dns-01
```
*(Les valeurs de nœud/stockage/template viennent de `make config`. La création
suit désormais le plan de bout en bout : un seul argument, `HOTE`.)*
## 8. Activer puis déployer
Passe l'hôte en `etat: actif` dans `instance/plan/serveurs.yml`, régénère, déploie :
```bash
make instancier-appliquer
make deployer HOTE=infra-dns-01 # configure l'hôte selon ses groupes
# ou, par couche :
make deployer-groupe GROUPE=serveur_postgresql
```
Les déploiements de groupe ne ciblent **que** les hôtes actifs.
## 9. Ensuite : tu vis dans le plan
Édite le plan (GUI `make inventaire-ui`, ou les registres `instance/plan/`),
`make instancier-appliquer`, `make deployer`. Tu ne touches jamais `hosts.yml`.
## Valider à tout moment
```bash
make inventaire-verifier # registres + inventaire + garde-fous
make verifier # + ansible-lint + --syntax-check
```
---
Ordre de mise en place des services : `docs/catalogue-services.md`. Modèle,
registres et règle d'or : `docs/plan-et-generation.md` et `AGENTS.md`.

171
README.md Normal file
View file

@ -0,0 +1,171 @@
# Set-OPS — moteur d'écosystèmes numériques souverains
**Set-OPS est un moteur Ansible générique** qui permet à un hébergeur de **construire et exploiter un écosystème numérique souverain** — DNS interne, AC/PKI, identité (LDAP + SSO), relais courriel, bases de données, observabilité, applications — sur sa propre grappe **Proxmox**, à partir d'un **plan déclaratif**.
Le dépôt est le **moteur** (générique, partageable). Chaque déploiement réel est une **instance** (le plan + l'inventaire d'un hébergeur, dans son propre dépôt). Tu crées la tienne à partir d'un **modèle prêt à déployer** (`exemples/modeles/`).
> 👉 **Tu débarques avec une grappe Proxmox et tu veux monter ton écosystème ? Commence par [`QUICKSTART.md`](QUICKSTART.md).**
**Souveraineté jusqu'au bout : Set-OPS s'exploite entièrement à la main** — la doc, `make` et le GUI suffisent, **sans aucune IA**. L'outil libère de la dépendance aux géants ; il ne la remplace pas par une dépendance à une IA.
---
## Mission (la vision derrière l'outil)
`Set-OPS` ne se limite plus à l'exploitation : il **définit et construit un écosystème numérique souverain** — une infrastructure interne auto-suffisante, sans dépendance SaaS, décrite en code et pilotée par des registres machine-lisibles qui font office de **source unique de vérité** (`instance/plan/serveurs.yml`, `instance/plan/applications.yml`, `instance/plan/bases-donnees.yml`, `instance/plan/domaines.yml`, `instance/plan/nomenclature.yml`, `docs/dependances-groupes.yml`).
Piliers de l'écosystème :
- **identité** — machines (PKI / certificats) et utilisateurs (annuaire + SSO) ;
- **confiance** — autorité de certification interne (ACME) ;
- **nommage et adressage** — DNS interne et nomenclature dérivable ;
- **données** — bases relationnelles et cache, avec registre des connexions ;
- **communication** — relais courriel interne ;
- **observabilité et supervision** — métriques, journaux, tableaux de bord, supervision active ;
- **applicatif** — services internes (forge, etc.) et couche web.
L'état voulu est **déclaratif et convergent** (appliqué par les groupes Ansible), **souverain** par conception, mais **piloté par l'opérateur** (pas d'auto-remédiation : la boucle n'est pas fermée). Une grande partie est aujourd'hui *définie et validée* avant d'être déployée sur des VM réelles ; le template Debian 13 reste la fondation, pas la finalité.
Cadre et règles d'autorité : voir `AGENTS.md` (section « Mission et identité »).
## Le plan : on édite, l'inventaire se génère
`Set-OPS` se pilote par un **plan**, pas par l'édition directe de l'inventaire.
`instance/inventories/production/hosts.yml` est **généré** depuis le plan — **ne pas l'éditer à la main**.
```
éditer le PLAN → make instancier (revoir le diff) → make instancier-appliquer → make deployer
```
- **Plan** : `instance/plan/serveurs.yml` (les VM), `instance/plan/applications.yml` (les services et leurs liens), `instance/plan/bases-donnees.yml`, `instance/plan/domaines.yml`, dérivés via `instance/plan/nomenclature.yml`.
- **GUI** (`make inventaire-ui`) : vues **Serveurs** et **Applications** pour éditer, puis « Appliquer le plan » ; la vue **Inventaire** est en lecture seule.
- VMID / IP / VLAN sont **dérivés** de la `fonction` ; les groupes d'une VM sont dérivés des applications qui y tournent.
Guide complet : **`docs/plan-et-generation.md`**.
## Portée transverse
Au-delà des piliers ci-dessus, le dépôt couvre des préoccupations transverses, communes à toutes les VM :
- templates de VM Proxmox (la fondation) ;
- groupes de conformité et durcissement ;
- maintenance ;
- sauvegardes.
## Premier chantier
Template Debian 13 Proxmox :
```text
playbooks/modeles_vm/debian13_proxmox_preparer.yml
playbooks/modeles_vm/debian13_proxmox_verifier.yml
playbooks/modeles_vm/debian13_proxmox_nettoyer.yml
```
## Principe
Le template contient seulement le socle commun.
Les services spécialisés seront installés ensuite sur les clones :
- NGINX ;
- PostgreSQL ;
- MariaDB ;
- Docker/Podman ;
- monitoring complet ;
- applications métier.
## Exploitation courante
Un `Makefile` fournit les commandes d'exploitation principales.
Afficher l'aide :
```bash
make
```
Valider le dépôt :
```bash
make verifier
```
Inspecter les inventaires :
```bash
make inventaire
make hote-afficher HOTE=web-frontal-01
```
Ouvrir l'interface locale de gestion d'inventaire :
```bash
make inventaire-ui
```
Planifier une VM passe désormais par le **plan**, pas par l'édition de l'inventaire :
1. déclarer la VM dans `instance/plan/serveurs.yml` (vue **Serveurs** du GUI, ou `make serveurs`) — `fonction`, `etat`, placement ; VMID/IP/VLAN sont dérivés ;
2. déclarer les services qui y tournent dans `instance/plan/applications.yml` (vue **Applications**) ;
3. régénérer l'inventaire :
```bash
make instancier # génère + diff sémantique (que va-t-il changer ?)
make instancier-appliquer # régénère instance/inventories/production/hosts.yml
```
Les anciennes commandes `make hote-planifier` / `hote-ajouter` / `hote-groupes` éditaient l'inventaire **directement** ; elles sont **supplantées** par le plan (l'inventaire est généré, ne pas l'éditer à la main).
Chaque groupe opérationnel doit avoir son playbook homonyme dans `playbooks/groupes/`.
La conformité normale des VM passe par ces playbooks de groupes. Les rôles de socle et de durcissement sont appliqués par `serveur_debian` et `serveur_durci`, pas par des playbooks de couches séparés.
Les groupes opérationnels avec des hôtes, comme `serveur_web_frontal` ou `client_dns`, sont validés contre `playbooks/groupes/` par les commandes d'inventaire.
Le catalogue des services et intégrations prévus est dans `docs/catalogue-services.md`.
Les dépendances causales entre groupes sont dans `docs/dependances-groupes.yml`.
Le runbook du DNS interne initial est dans `docs/dns-interne.md`.
La nomenclature des noms de VM et des VMID est dans `docs/nomenclature-vm.md`.
Créer un clone depuis le modèle Debian 13 via l'API Proxmox :
```bash
make config
make creer-vm HOTE=web-frontal-01 # VMID/IP/VLAN/passerelle lus dans le plan
make deployer HOTE=web-frontal-01
```
L'hôte doit d'abord être déclaré dans `instance/plan/serveurs.yml` et l'inventaire régénéré (`make instancier-appliquer`) : `creer-vm` ne prend que `HOTE`, le reste est dérivé.
Les valeurs Cloud-Init communes déjà présentes dans le modèle Proxmox sont héritées par les clones.
Préparer le golden template Debian 13 Proxmox :
```bash
make preparer-modele
make verifier-modele
```
Le nettoyage final du template est protégé :
```bash
make nettoyer-modele CONFIRMER=true
```
Déployer ou remettre en conformité une VM Debian clonée depuis le template :
```bash
make deployer HOTE=web-frontal-01
```
Déployer ou remettre en conformité un groupe :
```bash
make deployer-groupe GROUPE=serveur_debian
```
Les déploiements de groupes ciblent automatiquement les hôtes actifs seulement.

62
SOLUTION.md Normal file
View file

@ -0,0 +1,62 @@
# La solution Set-OPS
## Décision
`Set-OPS` est le moteur Ansible global dexploitation décosystèmes numériques souverains.
Le template Debian 13 Proxmox est seulement un chantier dans ce dépôt.
## Ce qu'on fait maintenant
On garde une seule structure simple :
```text
inventories/
playbooks/
roles/
docs/
```
Les rôles sont nommés clairement, sans sous-arborescence complexe.
## Commande principale pour construire le template
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_preparer.yml --ask-pass --ask-become-pass
```
Après que sudo sans mot de passe fonctionne :
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_preparer.yml
```
## Vérification
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_verifier.yml
```
## Nettoyage final avant conversion
```bash
ansible-playbook -i instance/inventories/lab/hosts.yml playbooks/modeles_vm/debian13_proxmox_nettoyer.yml -e template_cleanup_confirm=true
```
Ensuite :
```bash
sudo shutdown -h now
```
Puis côté Proxmox :
```bash
qm template VMID
```
## Important
Le pare-feu `nftables` est installé et préparé, mais il n'est pas activé par défaut dans le template.
C'est volontaire pour éviter de couper SSH pendant la construction.

13
ansible.cfg Normal file
View file

@ -0,0 +1,13 @@
[defaults]
inventory = instance/inventories/lab/hosts.yml
roles_path = roles
filter_plugins = filter_plugins
interpreter_python = auto_silent
host_key_checking = False
retry_files_enabled = False
stdout_callback = default
[privilege_escalation]
become = True
become_method = sudo
become_user = root

View file

@ -0,0 +1,197 @@
# Mise à jour pour Codex et Claude Code — Set-OPS
## État du dépôt
`Set-OPS` est le moteur Ansible global dexploitation décosystèmes numériques souverains.
Il ne sert pas seulement à créer un template Proxmox. Il doit contenir progressivement les playbooks, rôles, inventaires et templates nécessaires à tous les systèmes dune instance.
Le chantier en cours est le template Debian 13 Proxmox.
---
## Fichiers de gouvernance
Les fichiers suivants doivent être lus avant toute modification :
```text
AGENTS.md
CLAUDE.md
README.md
CHANGELOG.md
ansible.cfg
```
`AGENTS.md` est la source dautorité principale.
---
## État fonctionnel visé pour le template Debian 13
Le template Debian 13 Proxmox doit contenir :
- Debian minimal ;
- SSH ;
- sudo ;
- compte `ansible` ;
- sudo NOPASSWD pour `ansible` ;
- `qemu-guest-agent` ;
- `cloud-init` ;
- `cloud-guest-utils` ;
- chrony ;
- outils de diagnostic ;
- AppArmor ;
- auditd ;
- fail2ban SSH ;
- unattended-upgrades ;
- journald ;
- sysctl de sécurité ;
- nftables installé et préparé, mais désactivé par défaut.
Il ne doit pas contenir :
- NGINX ;
- PostgreSQL ;
- MariaDB ;
- Docker ;
- Podman ;
- Redis ;
- GitLab ;
- Nextcloud ;
- monitoring complet ;
- secrets ;
- clés privées ;
- données propres à un clone.
---
## Incident récent à corriger
Le playbook :
```text
playbooks/modeles_vm/debian13_proxmox_preparer.yml
```
a échoué sur un handler manquant :
```text
ERROR! The requested handler 'Validate and reload ssh' was not found
```
Le problème est relié à des rôles contenant :
```yaml
notify: Validate and reload ssh
```
sans handler local correspondant.
Rôles à vérifier en priorité :
```text
roles/ssh_baseline/
roles/ssh_durcissement/
```
Chaque rôle utilisant `notify` doit avoir son propre fichier :
```text
roles/<role>/handlers/main.yml
```
---
## Correctif minimal attendu
Ne pas régénérer tout le dépôt.
Créer ou corriger :
```text
roles/ssh_baseline/handlers/main.yml
roles/ssh_durcissement/handlers/main.yml
```
Contenu attendu :
```yaml
---
- name: Valider la configuration SSH
ansible.builtin.command: sshd -t
changed_when: false
listen: Validate and reload ssh
- name: Recharger SSH
ansible.builtin.systemd:
name: ssh
state: reloaded
listen: Validate and reload ssh
```
Puis vérifier tous les `notify` :
```bash
find roles -path '*/tasks/*.yml' -exec grep -H "notify:" {} \;
find roles -path '*/handlers/main.yml' -print
```
---
## Validation obligatoire
Après correction :
```bash
make syntax-template
```
Puis relancer :
```bash
make preparer-modele
```
Puis vérifier :
```bash
make verifier-modele
```
---
## Nettoyage final
Ne lancer le nettoyage final que lorsque la VM est validée :
```bash
make nettoyer-modele CONFIRMER=true
```
Ensuite seulement :
```bash
sudo shutdown -h now
```
Puis côté Proxmox :
```bash
qm template VMID
```
---
## Règle de conduite
Ne pas faire de régénération massive.
Lire lexistant.
Corriger petit.
Valider.
Mettre à jour `CHANGELOG.md`.
Résumer clairement ce qui a été fait et ce qui na pas été testé.

View file

@ -0,0 +1,66 @@
# Architecture Set-OPS
Set-OPS définit et construit l'écosystème numérique souverain. Il se
pilote par un **plan** : l'inventaire Ansible (`instance/inventories/production/hosts.yml`)
est **généré** depuis le plan, pas édité à la main.
- Modèle, registres, commandes et flux de travail : **`docs/plan-et-generation.md`**.
- Règles d'autorité : **`AGENTS.md`** (sections « Mission et identité » et « Le plan et la génération de l'inventaire »).
## Entités du plan
```text
serveur (VM) instance/plan/serveurs.yml fonction, état, placement, intégrations
application instance/plan/applications.yml groupe (rôle), hôte, port, requiert, expose
base instance/plan/bases-donnees.yml serveur de BD, base, propriétaire, secret (Vault), portée
domaine (DNS) instance/plan/domaines.yml zone publique, autorité, edge
nomenclature instance/plan/nomenclature.yml fonctions -> VMID / VLAN / IP (dérivés)
```
L'**application** est l'entité pivot ; le **groupe** Ansible n'est qu'une capacité
(le rôle appliqué). VMID / IP / VLAN et les appartenances de groupes sont **dérivés**.
## Rôles Ansible
Les rôles actifs sont conservés directement sous `roles/`.
Les sous-arborescences catégorielles de rôles ne doivent pas dupliquer un rôle actif. Un nouveau rôle doit être ajouté seulement lorsqu'un besoin opérationnel réel existe.
Les valeurs communes aux rôles doivent rester dans les defaults des rôles quand elles sont valables pour tous les environnements. Les inventaires ne doivent contenir que les politiques propres à leur contexte.
## Groupes et playbooks
Les groupes opérationnels de l'inventaire doivent correspondre à un playbook homonyme :
```text
instance/inventories/production/hosts.yml
serveur_debian
playbooks/groupes/serveur_debian.yml
```
Cette relation rend l'exploitation lisible :
```text
appartenance à un groupe
→ playbook correspondant
→ état voulu appliqué par Ansible
```
Les groupes structurels ou vides peuvent exister, mais un groupe assigné à une VM de production doit avoir un playbook s'il exprime une intention de configuration.
## Cycle de vie VM
Le cycle de vie des VM est documenté dans :
```text
docs/vm-lifecycle.md
```
## Intégrations futures
Les intégrations transversales des VM sont documentées dans :
```text
docs/integrations-vm.md
```

222
docs/catalogue-services.md Normal file
View file

@ -0,0 +1,222 @@
# Catalogue des services
Ce document fixe les noms de groupes et de playbooks pour les prochains services.
La règle reste :
```text
groupe opérationnel -> playbooks/groupes/<groupe>.yml
```
Les playbooks ajoutés maintenant sont des points d'ancrage. Ils ne doivent pas installer un service tant que le rôle correspondant n'existe pas et que ses variables, secrets et prérequis sont documentés.
La nomenclature des VM et des VMID est documentée dans `docs/nomenclature-vm.md`.
Un service central peut partager un hôte avec d'autres services de la même fonction opérationnelle (plusieurs applications par VM). La relation stricte est entre groupe et playbook, pas entre groupe et VM.
> Modèle à jour : chaque service est une **application** (`instance/plan/applications.yml`). Voir `docs/plan-et-generation.md`.
## État d'implémentation des rôles
> **Mise à jour (2026-06-24).** La plupart des rôles ci-dessous **existent désormais**
> écrits et validés *en tant que code*, **pas encore éprouvés sur des VM réelles**. Le rôle
> effectif porte le nom du **groupe** (`serveur_keycloak`, `client_pki`…), pas le nom court
> de la colonne « Rôle » des tables.
**Implémentés** (`tasks` + `templates` + `handlers`, validés) : `serveur_step_ca`,
`client_pki`, `serveur_powerdns`, `client_dns`, `serveur_openldap`, `client_ldap`,
`serveur_keycloak`, `serveur_postgresql`, `serveur_redis`, `serveur_nginx`,
`serveur_sendmail`, `client_smtp`, `serveur_prometheus`, `client_metrique`, `serveur_loki`,
`client_journal`, `serveur_grafana`, `serveur_icinga`, `serveur_forgejo`. Plus le socle et
le durcissement, appliqués par `serveur_debian` / `serveur_durci`.
**Échafaudages** (playbook-ancre `debug` « reste à définir », **sans rôle**) :
`serveur_nextcloud`, `serveur_collabora`, `client_supervision`, `serveur_web_frontal`,
`serveur_web_dorsal`. Les capacités *collaboration* et *couche web applicative* sont câblées
par convention mais pas encore implémentées.
**Rôles-catégories inertes** (dossiers `roles/<x>/` réduits à un `README.md`, jamais câblés) :
`applications`, `backup`, `database`, `identity`, `monitoring`, `proxmox`, `storage`, `web`.
L'architecture réelle est **plate** (`serveur_*` / `client_*`) ; ces dossiers sont des
vestiges d'un regroupement par catégorie abandonné — à supprimer ou réactiver lors d'une
consolidation ultérieure.
## Services centraux
| Service | Groupe | Playbook | Rôle |
| --- | --- | --- | --- |
| Keycloak | `serveur_keycloak` | `playbooks/groupes/serveur_keycloak.yml` | `keycloak` |
| OpenLDAP | `serveur_openldap` | `playbooks/groupes/serveur_openldap.yml` | `openldap` |
| PostgreSQL | `serveur_postgresql` | `playbooks/groupes/serveur_postgresql.yml` | `postgresql` |
| Loki | `serveur_loki` | `playbooks/groupes/serveur_loki.yml` | `loki` |
| Plateforme Icinga | `serveur_icinga` | `playbooks/groupes/serveur_icinga.yml` | `icinga` |
| Prometheus | `serveur_prometheus` | `playbooks/groupes/serveur_prometheus.yml` | `prometheus` |
| Grafana | `serveur_grafana` | `playbooks/groupes/serveur_grafana.yml` | `grafana` |
| Forgejo | `serveur_forgejo` | `playbooks/groupes/serveur_forgejo.yml` | `forgejo` |
| Sendmail MTA | `serveur_sendmail` | `playbooks/groupes/serveur_sendmail.yml` | `sendmail` |
| step-ca | `serveur_step_ca` | `playbooks/groupes/serveur_step_ca.yml` | `step_ca` |
| PowerDNS | `serveur_powerdns` | `playbooks/groupes/serveur_powerdns.yml` | `serveur_powerdns` |
| Redis | `serveur_redis` | `playbooks/groupes/serveur_redis.yml` | `redis` |
| NGINX WAF et reverse proxy | `serveur_nginx` | `playbooks/groupes/serveur_nginx.yml` | `nginx` |
| Nextcloud | `serveur_nextcloud` | `playbooks/groupes/serveur_nextcloud.yml` | `nextcloud` |
| Collabora | `serveur_collabora` | `playbooks/groupes/serveur_collabora.yml` | `collabora` |
## Couche applicative web
La couche applicative web est distincte de l'edge `serveur_nginx` :
- `serveur_nginx` est l'edge : mandataire inverse, terminaison TLS, WAF ; il reçoit le trafic externe et le route.
- `serveur_web_frontal` est la couche présentation web (UI, rendu, assets), servie *derrière* l'edge.
- `serveur_web_dorsal` est la couche application web (API, traitement), consommée par les frontaux.
Le « web » de ces deux groupes est implicite par leur position derrière `serveur_nginx`. Le jour où un service dorsal n'est pas web (worker batch, file, démon), créer un groupe dédié plutôt que d'élargir `serveur_web_dorsal`.
| Couche | Groupe | Playbook | Rôle |
| --- | --- | --- | --- |
| Présentation web (frontends) | `serveur_web_frontal` | `playbooks/groupes/serveur_web_frontal.yml` | `web_frontaux` |
| Application web (backends) | `serveur_web_dorsal` | `playbooks/groupes/serveur_web_dorsal.yml` | `web_dorsaux` |
Hôtes planifiés : `web-frontal-01` et `web-frontal-02` dans `serveur_web_frontal` ; `web-dorsal-01` dans `serveur_web_dorsal`. Aucun n'est encore actif (à créer puis activer).
Dépendance d'intégration prévue : `serveur_web_frontal` devra publier via `serveur_nginx` (règle « tout service exposé en HTTP(S) passe par l'edge »). Cette dépendance n'est pas encore déclarée dans `docs/dependances-groupes.yml` (les deux groupes sont vides) ; elle sera ajoutée avec le rôle `web_frontaux`, lorsque des frontaux actifs devront publier via l'edge.
## Hôtes planifiés
| Hôte | Services |
| --- | --- |
| `infra-pki-01` | step-ca |
| `infra-edge-01` | NGINX WAF et reverse proxy |
| `infra-mail-01` | Sendmail MTA |
| `infra-dns-01` | PowerDNS |
| `idm-01` | OpenLDAP, Keycloak |
| `data-01` | PostgreSQL, Redis |
| `obs-01` | Prometheus, Loki, Grafana |
| `mon-01` | Plateforme Icinga : Icinga 2, Icinga Web 2, Icinga BPM |
| `forge-01` | Forgejo |
| `collab-01` | Nextcloud, Collabora |
## Intégrations clientes
| Intégration | Groupe | Playbook | Rôle |
| --- | --- | --- | --- |
| Résolution DNS interne | `client_dns` | `playbooks/groupes/client_dns.yml` | `client_dns` |
| Confiance PKI / ACME | `client_pki` | `playbooks/groupes/client_pki.yml` | `client_pki` |
| Authentification LDAP | `client_ldap` | `playbooks/groupes/client_ldap.yml` | `client_ldap` |
| Supervision Icinga | `client_supervision` | `playbooks/groupes/client_supervision.yml` | `client_supervision` |
| Métriques Prometheus | `client_metrique` | `playbooks/groupes/client_metrique.yml` | `client_metriques` |
| Journaux vers Loki | `client_journal` | `playbooks/groupes/client_journal.yml` | `client_journaux` |
| Relais SMTP | `client_smtp` | `playbooks/groupes/client_smtp.yml` | `client_smtp` |
## Ordre d'implémentation recommandé
L'ordre ci-dessous privilégie les dépendances structurantes avant les applications.
### Phase 1 - Fondations transversales
1. `serveur_powerdns`
- Service central : DNS interne autoritaire et/ou résolution interne selon le design retenu.
- Intégration à prévoir : `client_dns`.
- Raison : les autres intégrations auront besoin de noms stables plutôt que d'adresses IP.
2. `serveur_step_ca`
- Service central : autorité de certification interne et ACME.
- Intégration à prévoir : `client_pki`.
- Raison : les autres services auront besoin de certificats fiables avant d'être exposés proprement.
3. `serveur_nginx`
- Service central : reverse proxy, terminaison TLS, publication HTTP(S), WAF.
- Intégration à prévoir : publication des services HTTP derrière le proxy.
- Raison : plusieurs services seront consommés par navigateur ou API et doivent passer par un point d'entrée cohérent.
4. `serveur_sendmail`
- Service central : relais SMTP sortant.
- Intégration à prévoir : `client_smtp`.
- Raison : les notifications, réinitialisations de mot de passe et alertes doivent fonctionner tôt.
### Phase 2 - Données et identité
5. `serveur_postgresql`
- Service central : base de données relationnelle partagée.
- Intégration à prévoir : bases dédiées par application, comptes applicatifs et sauvegardes.
- Raison : Keycloak, Grafana, Icinga Web 2, Forgejo et Nextcloud peuvent dépendre de PostgreSQL.
6. `serveur_openldap`
- Service central : annuaire interne.
- Intégration à prévoir : `client_ldap`.
- Raison : l'identité Unix et l'annuaire doivent exister avant les intégrations d'authentification avancées.
7. `serveur_keycloak`
- Service central : SSO/OIDC/SAML.
- Intégration à prévoir : applications web derrière `serveur_nginx`.
- Raison : les applications devraient être branchées au SSO dès leur arrivée plutôt qu'après coup.
### Phase 3 - Observabilité minimale
8. `serveur_prometheus`
- Service central : métriques.
- Intégration à prévoir : `client_metrique`.
- Raison : les prochains services doivent être mesurables dès leur déploiement.
9. `serveur_loki`
- Service central : journaux centralisés.
- Intégration à prévoir : `client_journal`.
- Raison : les journaux centralisés accélèrent le diagnostic des services suivants.
10. `serveur_grafana`
- Service central : tableaux de bord.
- Intégration à prévoir : datasources Prometheus et Loki, authentification Keycloak.
- Raison : Grafana consolide les métriques et journaux après leur mise en place.
### Phase 4 - Supervision active
11. `serveur_icinga`
- Service central : supervision active, interface web et vues métiers Icinga.
- Composants prévus : Icinga 2, Icinga Web 2, Icinga BPM.
- Intégrations à prévoir : `client_supervision`, PostgreSQL, NGINX, Keycloak si retenu.
- Raison : ces composants forment une même capacité de supervision et gagnent à cohabiter sur `mon-01` au départ.
### Phase 5 - Services applicatifs internes
12. `serveur_redis`
- Service central : cache et files internes.
- Intégration à prévoir : Nextcloud et autres applications qui en ont besoin.
- Raison : Redis est une dépendance applicative, pas une fondation globale.
13. `serveur_forgejo`
- Service central : forge Git.
- Intégration à prévoir : PostgreSQL, NGINX, Keycloak, SMTP, sauvegardes.
- Raison : la forge devient plus utile après SSO, TLS, SMTP et observabilité.
14. `serveur_nextcloud`
- Service central : collaboration fichiers.
- Intégration à prévoir : PostgreSQL, Redis, NGINX, Keycloak, SMTP, sauvegardes.
- Raison : Nextcloud dépend de plusieurs fondations et doit arriver après elles.
15. `serveur_collabora`
- Service central : édition documentaire en ligne.
- Intégration à prévoir : Nextcloud, NGINX, certificats.
- Raison : Collabora est une extension de Nextcloud et doit venir après lui.
## Règles d'intégration
- Tout service exposé en HTTP(S) doit prévoir son intégration avec `serveur_nginx`.
- Tout service avec authentification humaine doit prévoir son intégration avec `serveur_keycloak`, sauf justification contraire.
- Tout service générant des alertes ou notifications doit prévoir `client_smtp`.
- Toute VM de service doit rejoindre `client_metrique`, `client_journal` et `client_supervision` quand les services centraux correspondants existent.
- Tout service utilisant un certificat interne doit dépendre de `client_pki`.
- Tout rôle serveur doit documenter ses ports, secrets, sauvegardes, dépendances et groupes clients associés.
Les groupes clients peuvent être ajoutés aux VM existantes quand le service central correspondant est réellement disponible.
## Dépendances causales
Les dépendances exécutables sont déclarées dans `docs/dependances-groupes.yml`.
Le runbook DNS initial est dans `docs/dns-interne.md`.
Ce fichier sert à deux usages :
- bloquer le déploiement d'un groupe tant que ses prérequis actifs ne sont pas présents ;
- fournir une source exploitable pour les futurs modèles de supervision.
Un hôte peut porter un groupe client à l'état planifié. Le déploiement est refusé tant que le groupe serveur requis n'a pas au moins un hôte actif.

View file

@ -0,0 +1,86 @@
---
groupes:
client_dns:
requiert_groupes_actifs:
- serveur_powerdns
raison: "Les resolvers clients ne doivent pas pointer vers un service DNS absent."
surveillance: "Verifier resolution interne et disponibilite du service DNS."
client_pki:
requiert_groupes_actifs:
- serveur_step_ca
raison: "La confiance CA et ACME client dependent de l'autorite interne."
surveillance: "Verifier validite CA, emission ACME et expiration des certificats."
client_ldap:
requiert_groupes_actifs:
- serveur_openldap
raison: "La configuration NSS/PAM/SSSD depend de l'annuaire LDAP."
surveillance: "Verifier bind LDAP, latence et expiration des certificats LDAP."
client_supervision:
requiert_groupes_actifs:
- serveur_icinga
raison: "Les agents ou checks clients doivent se rattacher a une plateforme Icinga active."
surveillance: "Verifier enregistrement des hotes, fraicheur des checks et notifications."
client_metrique:
requiert_groupes_actifs:
- serveur_prometheus
raison: "Les exporters clients doivent etre collectes par Prometheus."
surveillance: "Verifier targets Prometheus, scrape duration et erreurs de collecte."
client_journal:
requiert_groupes_actifs:
- serveur_loki
raison: "L'expedition des journaux depend du collecteur central Loki."
surveillance: "Verifier ingestion Loki, retard et volume de journaux."
client_smtp:
requiert_groupes_actifs:
- serveur_sendmail
raison: "Les notifications locales doivent relayer vers un MTA actif."
surveillance: "Verifier file d'attente, relais SMTP et echecs de livraison."
serveur_keycloak:
requiert_groupes_actifs:
- serveur_postgresql
raison: "Keycloak doit utiliser une base PostgreSQL geree."
surveillance: "Verifier connexion base, etat realm et disponibilite OIDC."
serveur_grafana:
requiert_groupes_actifs:
- serveur_prometheus
- serveur_loki
raison: "Grafana est utile comme interface aux metriques et journaux centraux."
surveillance: "Verifier datasources Prometheus/Loki et authentification."
serveur_icinga:
requiert_groupes_actifs:
- serveur_postgresql
raison: "La plateforme Icinga Web/BPM depend d'une base relationnelle."
surveillance: "Verifier moteur Icinga, base, interface web et notifications."
serveur_forgejo:
requiert_groupes_actifs:
- serveur_postgresql
- serveur_nginx
- serveur_sendmail
raison: "Forgejo depend d'une base, d'une publication HTTP(S) et du courriel."
surveillance: "Verifier HTTP(S), base, files Git et envoi courriel."
serveur_nextcloud:
requiert_groupes_actifs:
- serveur_postgresql
- serveur_redis
- serveur_nginx
- serveur_sendmail
raison: "Nextcloud depend d'une base, d'un cache, d'un frontal web et du courriel."
surveillance: "Verifier jobs, base, Redis, HTTP(S), stockage et courriel."
serveur_collabora:
requiert_groupes_actifs:
- serveur_nextcloud
- serveur_nginx
raison: "Collabora est expose via le frontal web et integre a Nextcloud."
surveillance: "Verifier connectivite Nextcloud, websocket et disponibilite HTTP(S)."

90
docs/dns-interne.md Normal file
View file

@ -0,0 +1,90 @@
# DNS interne
Le service DNS interne est la premiere capacite de plateforme.
## Groupes
```text
serveur_powerdns -> service DNS central PowerDNS Authoritative
client_dns -> integration cliente DNS
```
`client_dns` depend de `serveur_powerdns` dans `docs/dependances-groupes.yml`.
## Zone initiale
La zone initiale est :
```text
exemple.internal
```
Elle est definie dans :
```text
instance/inventories/production/group_vars/serveur_powerdns.yml
```
## Enregistrement automatique
Le role `serveur_powerdns` genere la zone a partir de l'inventaire.
Les hotes qui remplissent ces conditions sont ajoutes automatiquement :
```text
hote dans hotes_actifs
ansible_host defini
```
Exemple :
```text
web-frontal-01 ansible_host=10.0.2.31
-> web-frontal-01.exemple.internal A 10.0.2.31
```
Les enregistrements additionnels explicites sont dans `serveur_powerdns_records`.
## Backend PowerDNS
Le premier jalon utilise le backend BIND de PowerDNS.
Raison :
- pas de dependance prematuree a PostgreSQL ;
- zone lisible et generee par Ansible ;
- idempotence simple ;
- bon point de depart pour la supervision.
Quand `serveur_postgresql` sera stable, il sera possible de migrer vers un backend SQL si le besoin operationnel le justifie.
## Clients DNS
Le role `client_dns` valide :
- qu'un serveur `serveur_powerdns` actif existe ;
- que la zone repond a une requete SOA ;
- que le nom de l'hote existe dans la zone.
La modification du resolver local est protegee :
```yaml
client_dns_apply: true
client_dns_confirm: true
```
Sans ces deux variables, le role valide les prerequis mais ne modifie pas `/etc/resolv.conf`.
Cette protection est volontaire : une mauvaise configuration DNS peut couper la resolution de noms.
## Surveillance a prevoir
Les premiers checks utiles :
- service `pdns` actif ;
- port UDP/TCP 53 disponible ;
- SOA de `exemple.internal` resoluble ;
- enregistrement `ns1.exemple.internal` resoluble ;
- enregistrements des hotes actifs presents ;
- serial de zone attendu ;
- latence de resolution.

43
docs/integrations-vm.md Normal file
View file

@ -0,0 +1,43 @@
# Intégrations futures des VM
Les serveurs centraux ne sont pas encore en place. Les rôles d'intégration doivent donc rester optionnels et désactivés par défaut tant que leurs dépendances n'existent pas.
## Séparation
```text
Template Debian 13 Proxmox : socle commun minimal
Cloud-init : identité initiale du clone
Set-OPS + Ansible : configuration réelle du serveur
```
Le template ne doit pas être modifié pour chaque nouveau serveur. Les intégrations sont appliquées après clonage selon les groupes d'inventaire et les playbooks dédiés.
## Domaines prévus
```text
DNS interne : PowerDNS, résolveurs, enregistrements applicatifs
PKI interne : step-ca, ACME, confiance CA locale
Identité : LDAP, SSSD, Keycloak/OIDC selon le besoin
Supervision : Icinga2 agent ou checks distants
Métriques : Prometheus node_exporter
Visualisation : Grafana côté plateforme centrale
```
## Principes Ansible
- Chaque rôle d'intégration doit être désactivé par défaut.
- Chaque variable de rôle doit utiliser le préfixe du rôle.
- Les secrets ne doivent jamais être stockés en clair dans le dépôt.
- Un rôle ne doit pas échouer si son intégration est désactivée.
- Les playbooks applicatifs doivent être appliqués sur les clones, pas sur le template.
- Les opérations pouvant couper l'accès réseau ou SSH doivent exiger une confirmation explicite.
## Ordre logique de construction
1. DNS interne.
2. PKI interne et confiance CA.
3. Identité système et applicative.
4. Supervision et métriques.
5. Visualisation et alerting.
Cet ordre reste indicatif. Les rôles doivent rester découplés pour permettre une adoption progressive.

View file

@ -0,0 +1,312 @@
# Golden template Debian 13 Proxmox
## Objet
Ce document justifie les ajouts appliqués par Set-OPS à une installation Debian 13 minimale pour obtenir le golden template Proxmox.
Le template doit rester un socle commun, clonable et sécuritaire. Il ne doit pas devenir un serveur applicatif ni intégrer des dépendances propres à un rôle final.
## État de départ
L'état de départ attendu est décrit dans :
```text
docs/procedure-template-debian13-proxmox.md
```
Résumé :
```text
Debian 13 minimal
aucun environnement graphique
SSH installé
partition EFI
partition racine ext4
pas de LVM
pas de swap
disque agrandissable
CloudInit Drive Proxmox présent
ciuser et clé SSH injectés par Cloud-Init
aucun secret
aucune donnée propre à un clone final
```
## Runbook du playbook prepare
### Objectif
Le playbook `debian13_proxmox_preparer.yml` transforme une VM Debian 13 minimale en socle golden template.
Il installe les composants communs, configure l'accès Ansible, applique le durcissement template-safe et prépare la VM pour cloud-init.
Il ne fait pas le nettoyage final avant conversion en template.
### Prérequis côté VM
Avant de lancer le playbook, la VM doit respecter ces conditions :
```text
Debian installé et démarré
réseau fonctionnel
SSH joignable depuis le poste Ansible
CloudInit Drive présent côté Proxmox
utilisateur Ansible initial injecté par Cloud-Init
clé publique SSH injectée par Cloud-Init
sudo ou accès root possible
APT fonctionnel
aucun rôle applicatif déjà installé
VM destinée à devenir un template, pas un serveur de production
```
Vérifications utiles depuis le poste Ansible :
```bash
ssh ansible@10.0.2.99
ansible -i instance/inventories/lab/hosts.yml modeles_vm -m ping
ansible -i instance/inventories/lab/hosts.yml modeles_vm -m setup
```
Adapter l'utilisateur et l'adresse IP selon `instance/inventories/lab/hosts.yml`.
### Prérequis côté dépôt
Avant l'exécution :
```bash
git status --short
make syntax-template
```
L'inventaire doit contenir la VM dans le groupe :
```text
modeles_vm
```
Les variables du template sont dans :
```text
instance/inventories/lab/group_vars/modeles_vm.yml
```
### Commande de préparation
Lancement lorsque l'accès par clé et sudo sont fonctionnels :
```bash
make preparer-modele
```
Le flux `make` suppose que SSH par clé et sudo NOPASSWD sont déjà fonctionnels pour `ansible`.
### Déroulement attendu
Le playbook :
1. vérifie que la cible est Debian ;
2. avertit si la version majeure n'est pas Debian 13 ;
3. installe les paquets communs ;
4. active `qemu-guest-agent` ;
5. installe et configure `cloud-init` ;
6. configure le compte technique Ansible ;
7. active la synchronisation du temps ;
8. configure SSH ;
9. applique le durcissement template-safe ;
10. prépare nftables sans l'activer ;
11. affiche le statut cloud-init.
### Résultat attendu
Une première exécution peut changer la VM.
Une relance sur une VM déjà préparée doit idéalement finir avec :
```text
failed=0
changed=0
```
Un résultat `changed=0` à la relance est le signal que le playbook est idempotent dans l'état actuel.
### Erreurs fréquentes
| Symptôme | Cause probable | Action |
| --- | --- | --- |
| `UNREACHABLE` | IP, SSH, utilisateur ou clé SSH incorrecte. | Vérifier `instance/inventories/lab/hosts.yml`, cloud-init et tester `ssh`. |
| échec `become` | Sudo NOPASSWD absent ou utilisateur non autorisé. | Corriger l'accès sudo initial, puis relancer `make preparer-modele`. |
| échec APT | DNS, passerelle, miroir Debian ou verrou APT. | Vérifier réseau, DNS et processus APT en cours. |
| erreur handler SSH | Handler manquant ou nom `notify` incohérent. | Vérifier les handlers du rôle SSH avant de relancer. |
| avertissement Debian non 13 | La cible n'est pas Debian 13. | Ne pas convertir en template Debian 13 sans justification. |
### Point d'arrêt
Après `prepare`, ne pas convertir immédiatement la VM en template.
Exécuter d'abord :
```bash
make verifier-modele
```
Le playbook de vérification contrôle notamment Debian 13, les paquets requis, l'absence de swap actif, l'absence d'environnement graphique, les services de base, `nftables` désactivé, sudo NOPASSWD et le durcissement SSH effectif.
Le nettoyage final se lance seulement après validation complète :
```bash
make nettoyer-modele CONFIRMER=true
```
Ne pas lancer le cleanup final sur une VM encore en diagnostic ou sur un serveur final.
## Préparation Ansible
Le playbook applique les rôles suivants :
```text
common_packages
qemu_guest_agent
cloud_init
sudo_ansible
chrony
ssh_baseline
systemd_ssh_auto
motd
hardening_packages
sysctl_durcissement
core_dumps
unattended_upgrades
apparmor
auditd
fail2ban_ssh
journald
ssh_durcissement
nftables_socle
```
## Justification des ajouts
| Rôle | Ajout ou changement | Justification | Limite volontaire |
| --- | --- | --- | --- |
| `common_packages` | Installe les outils système et diagnostic de base. | Permet l'administration locale, le dépannage réseau, l'exécution fiable d'Ansible et les opérations de maintenance courantes. | Aucun service applicatif spécialisé n'est installé. |
| `qemu_guest_agent` | Installe et active `qemu-guest-agent`. | Donne à Proxmox une visibilité propre sur l'état de la VM et facilite les opérations d'arrêt, IP reporting et gestion depuis l'hyperviseur. | Ne remplace pas la supervision applicative. |
| `cloud_init` | Installe `cloud-init` et `cloud-guest-utils`, puis active les services cloud-init. | Donne au clone son identité initiale : hostname, utilisateur, clé SSH, IP, DNS et agrandissement de disque. | Cloud-init ne sert pas à configurer les applications. |
| `sudo_ansible` | Crée ou maintient le compte technique et son sudo NOPASSWD. | Permet l'administration automatisée par Ansible sans stocker de mot de passe dans le dépôt. | Ce compte doit rester technique et contrôlé. |
| `chrony` | Installe et active la synchronisation du temps. | Un temps correct est nécessaire pour TLS, journaux, audit, Kerberos/LDAP futur, monitoring et corrélation d'incidents. | La source NTP définitive peut être ajustée plus tard par inventaire. |
| `ssh_baseline` | Pose la base SSH : root interdit, clé publique obligatoire, mot de passe désactivé. | Force un accès administrateur par clé dès la construction du template. | Cloud-init doit avoir injecté le `ciuser` et sa clé avant Ansible. |
| `systemd_ssh_auto` | Désactive le comportement `systemd.ssh_auto` si demandé. | Évite une exposition SSH automatique via mécanismes transitoires non désirés dans un template serveur. | Ne remplace pas la politique SSH principale. |
| `motd` | Déploie un message d'accueil sobre. | Identifie le contexte de l_instance et rappelle que la machine est gérée par Set-OPS. | Ne doit pas contenir d'information sensible. |
| `hardening_packages` | Installe les paquets de sécurité communs. | Fournit les briques nécessaires au durcissement sans configurer de service applicatif. | Les politiques strictes par rôle restent appliquées sur les clones. |
| `sysctl_durcissement` | Applique des paramètres noyau de sécurité. | Réduit des comportements réseau et noyau risqués avec des réglages standards pour serveur Linux. | Les réglages spécifiques à une charge de travail peuvent être ajustés par rôle. |
| `core_dumps` | Désactive les core dumps. | Limite le risque de fuite de secrets ou données sensibles dans des dumps mémoire. | Un serveur de debug peut réactiver un comportement adapté hors template. |
| `unattended_upgrades` | Configure les mises à jour automatiques de sécurité. | Réduit l'exposition aux vulnérabilités connues sur les clones qui restent proches du socle. | Les redémarrages automatiques restent désactivés par défaut. |
| `apparmor` | Installe et active AppArmor. | Ajoute une couche de confinement standard Debian avec faible coût opérationnel. | Les profils applicatifs spécifiques seront gérés par les rôles applicatifs. |
| `auditd` | Installe auditd et des règles de base. | Fournit une trace système utile pour exploitation, investigation et conformité minimale. | Les règles lourdes ou propres à une application ne vont pas dans le template. |
| `fail2ban_ssh` | Active une protection SSH simple contre les essais répétés. | Réduit le bruit et les attaques opportunistes sur SSH sans dépendre d'une plateforme centrale. | Ne remplace pas un pare-feu ni une politique d'accès réseau. |
| `journald` | Limite l'usage disque et la rétention des journaux. | Évite qu'une VM issue du template remplisse son disque à cause des logs système. | La centralisation des logs viendra plus tard si nécessaire. |
| `ssh_durcissement` | Ajoute les paramètres SSH plus stricts : port, délais, tentatives, keepalive, forwarding et tunnels désactivés. | Réduit la surface d'exposition SSH sans dépendre d'un rôle applicatif. | Les exceptions, comme un bastion ou du forwarding contrôlé, doivent être portées par un rôle dédié. |
| `nftables_socle` | Installe nftables et prépare une configuration, mais garde le service désactivé par défaut. | Prépare le standard pare-feu sans risquer de couper l'accès au template ou aux clones. | L'activation se fait par rôle serveur avec règles adaptées. |
## Paquets communs
Les paquets communs sont choisis pour l'exploitation réelle :
```text
administration : sudo, acl, bash-completion, tmux, vim, nano
transport : curl, wget, ca-certificates, gnupg
ansible : python3, python3-apt, python3-pip
diagnostic : htop, iotop, iftop, sysstat, lsof, ncdu, tree, file, less
réseau : dnsutils, iproute2, iputils-ping, net-tools, traceroute, mtr-tiny, tcpdump, netcat-openbsd, socat
maintenance : rsync, unzip, zip, tar, jq, logrotate, unattended-upgrades, apt-listchanges, needrestart
virtualisation : qemu-guest-agent, cloud-init, cloud-guest-utils
temps : chrony
```
Ces paquets sont acceptés dans le template parce qu'ils sont utiles sur presque tous les serveurs et ne transforment pas la VM en serveur applicatif.
## Choix de sécurité importants
### SSH
État attendu dès la construction :
```text
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
AuthenticationMethods publickey
```
Le playbook `prepare` ne doit être lancé qu'après validation de l'accès SSH par clé au compte technique.
### Pare-feu
`nftables` est installé et préparé, mais désactivé par défaut.
Ce choix est volontaire : un pare-feu générique dans un template peut couper l'accès SSH ou bloquer un futur rôle serveur. L'activation doit être faite sur un clone avec des règles correspondant à son usage.
### Nettoyage final
Le nettoyage final est séparé et protégé par confirmation explicite. Il ne doit être lancé qu'au moment où la VM est validée et prête à être convertie en template.
## Vérification
```bash
make verifier-modele
```
## Validation minimale
Avant de déclarer le template prêt :
```bash
make verifier
make preparer-modele
make verifier-modele
```
La relance du playbook de préparation doit idéalement finir avec :
```text
failed=0
changed=0
```
## Nettoyage
```bash
make nettoyer-modele CONFIRMER=true
```
Ne pas lancer le nettoyage final sur un serveur de production ou sur une VM qui n'est pas destinée à devenir un template.
## Hors périmètre du template
Les éléments suivants ne doivent pas être installés dans le golden template :
```text
NGINX
PostgreSQL
MariaDB
Docker
Podman
Redis
GitLab
Nextcloud
monitoring complet
agents applicatifs spécialisés
données propres à un clone
secrets
clés privées
```
Les intégrations futures sont documentées dans :
```text
docs/integrations-vm.md
```
Le cycle complet VM vanille, golden template, clone et conformité continue est documenté dans :
```text
docs/vm-lifecycle.md
```

138
docs/nomenclature-vm.md Normal file
View file

@ -0,0 +1,138 @@
# Nomenclature des VM
La nomenclature doit rendre lisible la fonction opérationnelle d'une VM sans l'enfermer dans un seul service. (« fonction » est le terme du plan ; « domaine » est réservé au DNS.)
Un hôte peut porter plusieurs groupes Ansible et plusieurs applications. Le nom de VM représente donc une capacité ou une fonction, pas forcément un produit unique.
## Noms de VM
Format recommandé :
```text
<fonction>-<NN>
```
Exemples :
```text
infra-pki-01
infra-edge-01
infra-mail-01
infra-dns-01
idm-01
data-01
obs-01
mon-01
forge-01
collab-01
web-frontal-01
web-dorsal-01
```
La couche applicative web suit le même format. Le tier est porté par la fonction, au singulier puisqu'il nomme une instance :
```text
web-frontal-01 présentation web (UI, rendu, assets), groupe serveur_web_frontal
web-frontal-02
web-dorsal-01 application web (API, traitement), groupe serveur_web_dorsal
```
Les anciens noms de test `web-01` et `web-02` sont retirés. Ils ne doivent pas être réutilisés comme noms de production : préférer `web-frontal-NN` et `web-dorsal-NN`.
## Regroupements recommandés
| Hôte | Groupes prévus |
| --- | --- |
| `infra-pki-01` | `serveur_step_ca` |
| `infra-edge-01` | `serveur_nginx` |
| `infra-mail-01` | `serveur_sendmail` |
| `infra-dns-01` | `serveur_powerdns` |
| `idm-01` | `serveur_openldap`, `serveur_keycloak` |
| `data-01` | `serveur_postgresql`, `serveur_redis` |
| `obs-01` | `serveur_prometheus`, `serveur_loki`, `serveur_grafana` |
| `mon-01` | `serveur_icinga` |
| `forge-01` | `serveur_forgejo` |
| `collab-01` | `serveur_nextcloud`, `serveur_collabora` |
| `web-frontal-01`, `web-frontal-02` | `serveur_web_frontal` |
| `web-dorsal-01` | `serveur_web_dorsal` |
Les groupes restent fins et composables. La cohabitation se fait en associant plusieurs groupes au même hôte.
## Plages VMID
| Plage | Usage |
| --- | --- |
| `91xxx` | fondations transversales : PKI, reverse proxy, SMTP |
| `92xxx` | identité : LDAP, SSO |
| `93xxx` | données et cache : PostgreSQL, Redis |
| `94xxx` | observabilité et supervision |
| `95xxx` | applications internes |
| `99xxx` | modèles, essais initiaux ou exceptions documentées |
## Plan d'adressage interne
Réseau interne unique : `10.0.0.0/16`. Segmentation par fonction, un `/24` et un VLAN par catégorie, **3ᵉ octet = VLAN** (L2 alignée sur L3).
| Catégorie | VLAN | Sous-réseau | Passerelle |
| --- | --- | --- | --- |
| 1 — Fondations / infra | 11 | `10.0.11.0/24` | `10.0.11.1` |
| 2 — Identité | 12 | `10.0.12.0/24` | `10.0.12.1` |
| 3 — Données | 13 | `10.0.13.0/24` | `10.0.13.1` |
| 4 — Observabilité | 14 | `10.0.14.0/24` | `10.0.14.1` |
| 5 — Applications | 15 | `10.0.15.0/24` | `10.0.15.1` |
Adresse d'hôte (4ᵉ octet) : **`service × 10 + NN`**. `.1` = passerelle ; `.2``.9` réservés. Exemple : `web-dorsal-01` (catégorie 5, service 4, NN 01) → `10.0.15.41`.
Tout se dérive de la fonction de l'hôte, et la source unique machine-lisible est **`instance/plan/nomenclature.yml`** :
```text
hostname = <fonction>-<NN>
VMID = 9 · catégorie · service · NN
VLAN = catégorie.vlan
IP = 10.0.<vlan>.(service × 10 + NN)
```
`make inventaire-ui` lit ce registre et **propose** automatiquement VMID, VLAN, IP et passerelle quand on nomme un hôte. La sécurité entre zones se fera par règles inter-zones (nftables / edge), pas par l'adressage.
Contrainte : `NN` de 01 à 09 par fonction (l'octet hôte reste dans le bloc du service). Au-delà, ouvrir une nouvelle fonction/service dans `instance/plan/nomenclature.yml`.
La segmentation `10.0.0.0/16` remplace l'ancienne plage d'essais `192.168.12.x`.
## Variables de provisioning d'hôte
Le **plan** est la source de vérité du provisioning. Le placement et le dimensionnement d'une VM vivent dans `instance/plan/serveurs.yml` (vue **Serveurs** du GUI) ; VMID / IP / VLAN / passerelle sont **dérivés** de la fonction. Ces variables d'hôte sont alors **générées** dans l'inventaire (ne pas les éditer à la main) :
| Variable | Sens | Type |
| --- | --- | --- |
| `ansible_host` | adresse IP | str |
| `proxmox_cidr` | masque réseau en bits (0-32) | int |
| `proxmox_passerelle` | passerelle | str |
| `proxmox_vlan` | tag VLAN (1-4094) | int |
| `proxmox_pont` | pont réseau Proxmox (ex. `vmbr0`) | str |
| `proxmox_dns` | serveurs DNS Cloud-Init (séparés par virgule) | str |
| `proxmox_vmid` | identifiant VM Proxmox | int |
| `proxmox_noeud` | nœud Proxmox cible | str |
| `proxmox_stockage` | stockage du disque | str |
| `proxmox_disque_taille` | taille disque (ex. `32G`) | str |
| `proxmox_memoire` | mémoire en Mo | int |
| `proxmox_coeurs` | nombre de cœurs | int |
Ces valeurs sont **générées** dans l'inventaire depuis `instance/plan/serveurs.yml` + la nomenclature (`make instancier-appliquer`). Une valeur vide n'est pas écrite, pour garder l'inventaire propre.
## État d'un hôte (planifié / actif)
Chaque VM porte un `etat` dans `instance/plan/serveurs.yml` :
- `planifie` — prévue mais non encore déployée → groupe `hotes_planifies` à la génération ;
- `actif` — réellement joignable par Ansible → groupe `hotes_actifs`.
L'état se change dans la vue **Serveurs** du GUI (ou `instance/plan/serveurs.yml`), puis on
régénère l'inventaire :
```bash
make instancier-appliquer
```
Les déploiements groupés limitent automatiquement l'exécution à `<groupe demandé> & hotes_actifs`, ce qui permet de décrire l'écosystème complet (hôtes planifiés inclus) sans tenter de configurer une VM qui n'existe pas encore.
> Les anciennes commandes `make hote-planifier` / `hote-ajouter` éditaient `hosts.yml` directement ; l'inventaire étant désormais **généré**, elles sont supplantées par le plan (`instance/plan/serveurs.yml`).

179
docs/plan-et-generation.md Normal file
View file

@ -0,0 +1,179 @@
# Le plan et la génération d'inventaire (méta-classe)
Set-OPS ne s'édite plus comme un inventaire à la main : on **décrit un plan**, et
l'inventaire Ansible en est **généré**. Le dépôt est la définition ; chaque VM en
est une instance. Ce document décrit le modèle, les registres, les commandes et
le flux de travail.
> Règle d'or : **`instance/inventories/production/hosts.yml` est GÉNÉRÉ. Ne jamais l'éditer
> à la main.** On édite le *plan* puis on régénère (`make instancier-appliquer`).
---
## 1. Le modèle : deux ancres, cinq liens
L'écosystème = des **serveurs** (VM) et des **applications**, reliés à leurs
**bases**, leurs **domaines** et leurs dépendances :
```
serveur (VM) ──fournit──▶ capacités (= groupes/rôles Ansible)
application ──tourne_sur──▶ serveur (une VM peut porter N applications)
application ──requiert──▶ application(s) (DNS, PKI, BD…)
application ──utilise──▶ base(s) (via le DSN)
application ──expose──▶ domaine(s) public(s) (le FQDN, derrière l'edge)
base ──hébergée_sur──▶ serveur de BD
```
L'**application** est l'entité pivot : tout ce qui décrit « ce qui tourne et à
quoi c'est connecté » pend d'elle. Le **groupe** Ansible n'est plus une cible de
liaison — seulement une **capacité** qu'une VM fournit (le rôle appliqué).
---
## 2. Les registres (source unique de vérité)
Tous sous `docs/`, machine-lisibles, validés, consommés par le GUI, le CLI et Ansible.
| Registre | Décrit | Champs clés |
| --- | --- | --- |
| `nomenclature.yml` | nommage & adressage | `fonctions` (catégorie/service), `categories` (VLAN/sous-réseau/passerelle), `supernet` |
| `serveurs.yml` | les VM du plan | `fonction`, `etat` (actif/planifie), placement Proxmox (`noeud`/`stockage`/`disque`/`memoire`/`coeurs`), `integrations` (les `clients_*`) |
| `applications.yml` | les applications | `groupe` (capacité/rôle), `hote` (VM), `port`, `requiert`, `expose`, (+ bases via consommateur) |
| `bases-donnees.yml` | serveurs de BD + bases | `serveurs_bd` ; `bases_donnees` : `serveur`/`base`/`proprietaire`/`secret`(Vault), `consommateur` + `portee` (`application`/`groupe`/`hote`), `usage` |
| `domaines.yml` | zones DNS publiques | `domaines_publics` : `autorite`, `edge`, `secondaires`, `dnssec`, `mail` |
| `dependances-groupes.yml` | prérequis entre groupes | `requiert_groupes_actifs` |
### Dérivations clés
- **Nommage/adressage** : tout part de la `fonction` de l'hôte (`web-frontal-03`).
`VMID = 9·catégorie·service·NN`, `VLAN = catégorie.vlan`,
`IP = 10.0.<vlan>.(service×10 + NN)`. Voir `docs/nomenclature-vm.md`.
- **DSN** (lien application↔base) : `<type>://<proprietaire>:<secret>@<hôte>:<port>/<base>`.
Une application reçoit les bases où `(portee=application ET consommateur=elle)`
OU `(portee=groupe ET consommateur=son groupe)` OU `(portee=hote ET consommateur=son hôte)`.
- **Exposition DNS** : `application.expose: [fqdn]` + l'`edge` du domaine parent →
`serveur_nginx` génère le vhost (`application → hôte → IP:port`).
---
## 3. La génération (méta-classe)
`make instancier` produit `hosts.yml` **depuis le plan** :
- **host vars** : `ansible_host`/`ansible_user` + `proxmox_*` (IP/VMID/VLAN/passerelle
**dérivés** de la nomenclature ; placement/taille depuis `serveurs.yml`).
- **groupes** d'une VM = socle (`serveur_debian` + `serveur_durci`)
+ **services** (les `groupe` des applications de l'hôte)
+ **intégrations** (`serveurs.yml: integrations`)
+ **état** (`hotes_actifs` / `hotes_planifies`).
La comparaison est **sémantique** (via `ansible-inventory --list`, formatage
ignoré). « **Diff vide** » = le plan reproduit exactement l'inventaire courant ;
c'est le feu vert pour appliquer.
---
## 4. Le flux de travail
```
éditer le PLAN ──▶ make instancier (revoir le diff) ──▶ make instancier-appliquer ──▶ déployer
(GUI ou CLI) (que va-t-il changer ?) (régénère hosts.yml) (make deployer)
```
### Via le GUI — `make inventaire-ui`
Cinq vues :
| Vue | Rôle |
| --- | --- |
| **Inventaire** | **lecture seule** (inventaire généré) — vue d'ensemble des hôtes |
| **Serveurs** | éditer les VM du plan : fonction/état/placement/intégrations (VMID·IP·VLAN dérivés en direct) ; bouton **« Appliquer le plan »** |
| **Chaîne** | vue holistique par hôte : groupes → rôles, et par application ses `expose` / `requiert` / bases (DSN) |
| **Applications** | éditer les applications : groupe/hôte/port/requiert/expose |
| **Bases** | éditer serveurs de BD et bases (portée + consommateur), DSN affiché |
Édition → **Sauvegarder** (écrit le registre) → **Appliquer le plan** (régénère
`hosts.yml`). L'écriture directe de l'inventaire est refusée (409).
### Via le CLI / `make`
Chaque registre a son script miroir et ses cibles `make` :
| Domaine | Lister | Vérifier | Bootstrap (depuis l'inventaire) |
| --- | --- | --- | --- |
| Serveurs | `make serveurs` | `make serveurs-verifier` | `make serveurs-bootstrap` |
| Applications | `make applications` | `make applications-verifier` | `make applications-bootstrap` |
| Bases | `make bases` | `make bases-verifier` | — |
| Domaines | `make domaines` | `make domaines-verifier` | — |
Génération :
```bash
make instancier # génère hosts.genere.yml (gitignoré) + diff sémantique
make instancier-appliquer # régénère hosts.yml (refuse si diff non vide ; FORCE=1 pour forcer)
```
Validation globale : `make inventaire-verifier` (ansible-inventory + tous les
registres + **`node --check` du JS du GUI**).
---
## 5. Garde-fous
- **Validateurs** : chaque registre est validé (références connues, énumérations,
unicité). Un `expose` sans domaine parent, un `requiert` fantôme, une `portee`
inconnue, une `fonction` absente → rejet.
- **Diff vide** : `instancier-appliquer` refuse d'écraser l'inventaire si le plan
ne le reproduit pas (sauf `FORCE=1` pour un changement intentionnel).
- **`node --check`** : le JS embarqué du GUI est vérifié (`scripts/verifier_gui.py`,
intégré à `make inventaire-verifier`) — une erreur de syntaxe JS casse toute la page.
- **git** : `hosts.yml` est versionné ; `git diff` / `git checkout` est le filet.
- **Secrets** : jamais en clair ; `secret:` nomme une variable Ansible Vault.
---
## 6. Réutilisation de la règle (Ansible)
La règle de résolution vit **une seule fois**, en Python (`scripts/inventory_rules.py`),
et est exposée à Ansible par un *filter plugin* (`filter_plugins/registres.py`) :
`bases_de_application`, `applications_de_hote`, `expositions_des_applications`,
`chaine_connexion`. Les playbooks par application (`serveur_web_dorsal`/`_frontaux`)
itèrent ainsi sur les applications de l'hôte et résolvent leurs DSN.
---
## 7. Amorçage / reconstruction
Pour (re)construire le plan depuis un inventaire existant :
```bash
make serveurs-bootstrap # VM -> instance/plan/serveurs.yml (fonction/état/placement/integrations)
make applications-bootstrap # services serveurs_* (hors socle) -> instance/plan/applications.yml
make instancier # vérifier le diff vide
```
C'est ainsi que le plan a été initialisé sans perte, avec diff vide vérifié.
---
## 8. Moteur et instance : deux dépôts
Le **moteur** (ce dépôt, `Set-OPS`) est générique et partageable ; il ne contient
aucune donnée d'instance. Une **instance** (le plan + l'inventaire d'un loup) vit
dans son **propre dépôt** (ex. `OPS-monatelier`).
Le moteur localise l'instance via **`SETOPS_INSTANCE`** (défaut : `instance`). Deux
modèles :
- **Modèle A — dépôts frères** (en cours) : moteur et instance côte à côte ; un
symlink `instance -> ../OPS-monatelier` (gitignoré) fait que le défaut résout
l'instance sans configuration. Idéal quand on développe le moteur *et* l'instance.
- **Modèle B — moteur en sous-module** (futur, pour la meute) : l'instance épingle
une version du moteur ; `SETOPS_INSTANCE` pointe la racine de l'instance.
Pour brancher une instance (modèle A) :
```bash
cd Set-OPS
ln -s ../OPS-monatelier instance # ou : export SETOPS_INSTANCE=/chemin/instance
make inventaire-verifier # lit l'instance via le symlink
```
Le moteur écrit `hosts.yml` (généré) **dans le dépôt d'instance**, jamais dans le sien.

95
docs/positionnement.md Normal file
View file

@ -0,0 +1,95 @@
# Positionnement : ce que Set-OPS fait maison, et quand adopter l'existant
Ce document **acte une décision** pour ne plus la redébattre à chaque évolution :
quelles parties de Set-OPS sont volontairement *maison*, pourquoi, et à partir de
quel **seuil** il vaudra mieux adopter un outil du marché plutôt que continuer à
le réimplémenter.
> En une phrase : le cœur de Set-OPS (un plan déclaratif qui génère l'inventaire
> Ansible) **existe déjà sous forme mûre** — c'est NetBox. Set-OPS en est une
> version **souveraine, légère et sur-mesure**, assumée comme telle. Le risque à
> surveiller est de réimplémenter NetBox/AWX brique par brique.
---
## 1. La carte : chaque facette a un équivalent du marché
| Facette de Set-OPS | Équivalent mûr | Remarque |
| --- | --- | --- |
| Source de vérité déclarative → **génère l'inventaire Ansible** | **NetBox** / **Nautobot** + plugin `netbox.netbox.nb_inventory` | Le plus proche du **cœur** de Set-OPS (le « plan »). |
| GUI qui **exécute** Ansible (déployer, RBAC, planification) | **AWX** / Ansible Automation Platform ; **Semaphore** | Set-OPS a un GUI plus modeste (lancer `make`). |
| « La **définition instancie** toute la flotte » | **NixOS + Colmena/morph** ; **Terraform** (provider Proxmox) pour créer les VM | L'idée « méta-classe » au sens littéral. |
| **Application = entité pivot** avec ses dépendances et ressources | **Backstage** (catalogue de composants + graphe) | Le modèle `requiert`/`utilise`/`expose` y ressemble fortement. |
| IPAM / dérivation d'adressage | **NetBox** (IPAM natif) | Set-OPS dérive depuis la nomenclature. |
**Aucun** produit ne fait l'**assemblage exact** : Proxmox + Ansible + le modèle
bespoke (DSN, exposition DNS, dérivation par fonction) en **un seul outil
souverain et léger**. Cet assemblage-là est propre à Set-OPS.
---
## 2. Ce que Set-OPS fait sciemment maison (et pourquoi)
- **Le plan déclaratif** (`instance/plan/serveurs.yml`, `applications.yml`, `bases-donnees.yml`,
`domaines.yml`, `nomenclature.yml`) et le **générateur d'inventaire** (`make instancier`).
- Le **modèle application-hub** : bindings DSN (portée application/groupe/hôte) et
exposition DNS, taillés exactement pour nos concepts.
- Le **GUI** (Python stdlib, fichier unique) et les **validateurs / garde-fous**
(diff-vide, `node --check`, vérificateurs de registres).
Raisons assumées :
1. **Souveraineté** — mission explicite du dépôt. NetBox + AWX + Backstage = trois
applications lourdes à héberger et maintenir (Django + PostgreSQL, etc.). Set-OPS
reste possédé en entier, sans dépendance.
2. **Bon dimensionnement** — ~13 VM. NetBox est de la machinerie d'échelle entreprise.
3. **Modèle sur-mesure** — NetBox exprimerait nos DSN / expositions à coups de
*custom fields* et de plugins, moins naturellement que nos registres.
4. **Maîtrise** — chaque ligne est comprise et auditable.
---
## 3. Ce que le maison NE fait PAS (et que les outils mûrs ont)
À garder en tête honnêtement — ce sont des fonctions qu'on n'a pas, pas des bugs :
- historique/audit des changements (au-delà de `git`) ;
- RBAC multi-utilisateurs ;
- détection de conflits IPAM, réservations, gestion d'adresses à grande échelle ;
- webhooks / intégrations tierces, API riche (REST/GraphQL) ;
- écosystème de plugins, communauté, correctifs de sécurité maintenus par d'autres.
---
## 4. La règle : plan de contrôle **gelé en fonctionnalités**
Set-OPS continue d'évoluer pour **décrire et déployer l'écosystème** (rôles,
services, registres applicatifs). En revanche, le **plan de contrôle** (le GUI,
le générateur, l'IPAM, la modélisation) est considéré **gelé en périmètre** : on
ne lui ajoute pas de fonctionnalités de type NetBox/AWX.
### Seuils d'adoption (les signaux de bascule)
Adopter l'outil du marché — **sans réécrire**, en branchant Ansible/notre GUI
par-dessus — dès qu'un de ces besoins devient réel :
| Besoin qui apparaît | Adopter |
| --- | --- |
| RBAC, plusieurs opérateurs, historique d'audit, secrets/credentials centralisés | **AWX** (exécution) |
| IPAM sérieux, détection de conflits, source de vérité partagée, API/webhooks | **NetBox** (et `nb_inventory` remplace notre générateur) |
| Catalogue de services / portail développeur, ownership, scaffolding | **Backstage** |
| Provisionnement de VM déclaratif et reproductible à plus grande échelle | **Terraform** (Proxmox) ou **NixOS + Colmena** |
**Test simple** : si on se surprend à vouloir réimplémenter une de ces fonctions
dans Set-OPS, c'est le signal d'adopter l'outil correspondant plutôt que de
prolonger le maison.
---
## 5. Décision en vigueur
- On **garde** le plan de contrôle maison : il fonctionne, il est souverain et
bien dimensionné pour aujourd'hui. Pas de réécriture sur NetBox « par principe ».
- On **gèle** son périmètre fonctionnel (voir §4).
- On **réévalue** à l'échéance d'un seuil ci-dessus, ou si la charge de
maintenance du maison dépasse le coût d'héberger l'outil mûr.

View file

@ -0,0 +1,817 @@
# Procédure manuelle — VM Debian 13 vanille pour Proxmox
## Objet du document
Ce document décrit le travail manuel à effectuer pour créer une VM Debian 13 vanille dans Proxmox, jusquau moment où elle peut être prise en charge par Set-OPS.
Le résultat attendu est une VM minimale, sobre, joignable en SSH, capable d'installer des paquets depuis les dépôts Debian, compatible avec cloud-init et prête à recevoir le playbook de préparation du golden template.
Ce document sarrête au point de bascule vers le `Makefile`.
Les ajouts appliqués ensuite par Ansible, leur validation et leur justification sont documentés dans :
```text
docs/modeles_vm/debian13-proxmox.md
```
---
## 1. Objectif du modèle
Le modèle Debian 13 doit servir de base commune pour les futurs serveurs de l_instance
Objectifs :
- système Debian 13 minimal ;
- aucun environnement graphique ;
- SSH actif ;
- `qemu-guest-agent` installé ;
- `cloud-init` installé ;
- disque CloudInit Proxmox attaché ;
- compte technique `ansible` injecté par cloud-init ;
- clé publique SSH injectée par cloud-init ;
- `ansible` autorisé à utiliser sudo ;
- partitionnement simple et agrandissable ;
- aucune partition swap bloquant la croissance du disque ;
- aucune donnée propre à une VM finale ;
- aucune clé privée ;
- aucun secret ;
- prêt à être converti en template Proxmox.
---
## 2. Création de la VM dans Proxmox
### Paramètres généraux
Créer une nouvelle VM dans Proxmox avec des paramètres sobres.
Exemple :
```text
Nom de la VM : debian13-template
VMID : 9000 ou autre ID réservé aux modèles
OS : Debian 13
BIOS : OVMF / UEFI
Machine : q35
```
### Disque EFI Proxmox
Avec OVMF/UEFI, Proxmox crée un petit disque EFI, par exemple :
```text
efidisk0 : 4 Mo
```
Ce disque ne remplace pas la partition EFI de Debian.
Il sert à conserver les variables du firmware UEFI virtuel :
- ordre de démarrage ;
- entrées de boot ;
- variables UEFI ;
- paramètres liés à Secure Boot, si utilisé.
Il faut le garder.
---
## 3. Disque virtuel principal
### Type de disque
Pour une VM Linux moderne :
```text
Bus/Device : SCSI
SCSI Controller : VirtIO SCSI single
IO thread : activé si disponible
Cache : Default / No cache
Discard : selon stockage, seulement si pertinent
SSD emulation : oui si le stockage est réellement SSD/NVMe
```
Même si le stockage réel est sur un stockage réseau iSCSI, le disque présenté à la VM doit rester :
```text
SCSI via VirtIO SCSI single
```
Le choix SCSI ici concerne le bus virtuel vu par la VM, pas le protocole réel entre Proxmox et le stockage.
### Taille du disque
Pour le modèle de base :
```text
Disque : 16 Go
```
Cest suffisant pour une base Debian minimale.
Des clones pourront ensuite être agrandis avant le premier démarrage cloud-init.
---
## 4. Processeur
Pour un modèle portable entre plusieurs hôtes Proxmox :
```text
CPU Type : x86-64-v2-AES
Sockets : 1
Cores : 2
```
Éviter `host` pour un modèle générique, sauf si tous les nœuds Proxmox sont strictement homogènes et que la portabilité nest pas une priorité.
---
## 5. Mémoire
Pour le modèle Debian 13 minimal :
```text
Mémoire : 2048 MiB
```
Le ballooning peut être activé, mais si la VM na pas de swap, éviter de descendre trop bas.
Réglage prudent :
```text
RAM max : 2048 MiB
RAM minimum : 1536 MiB ou 2048 MiB
```
Pour un modèle simple, il est acceptable de laisser 2048 MiB fixe.
---
## 6. Carte graphique virtuelle
Pour un serveur Debian minimal :
```text
Display : Default / Standard VGA
```
Ne pas installer denvironnement graphique.
SPICE et VirtIO-GPU ne sont pas nécessaires pour un modèle serveur administré par SSH et Ansible.
---
## 7. Installation Debian 13
Démarrer la VM sur lISO Debian 13.
Loption `Graphical install` est acceptable : elle ne signifie pas quun environnement graphique sera installé. Elle ne concerne que linterface de linstallateur.
---
## 8. Partitionnement
### Objectif
Le disque doit rester facile à agrandir après clonage.
Ne pas créer de partition swap à la fin du disque, car cela bloquerait lagrandissement direct de la partition racine avec `growpart`.
### Partitionnement recommandé
Utiliser un partitionnement manuel :
```text
/dev/sda1 EFI System Partition 512 Mo FAT32 /boot/efi
/dev/sda2 Linux root reste ext4 /
```
Ne pas créer :
```text
partition swap
LVM
/home séparé
/var séparé
```
### Pourquoi éviter LVM dans le modèle
Le stockage réel contient déjà plusieurs couches :
```text
stockage ZFS
→ zvol iSCSI
→ Proxmox
→ disque virtuel
→ Debian
```
Ajouter LVM dans la VM complique le modèle sans bénéfice clair pour une base minimale.
### Pourquoi éviter la partition swap
Un exemple à éviter :
```text
/dev/sda1 EFI
/dev/sda2 /
/dev/sda3 swap
```
Si le disque est agrandi plus tard, lespace libre sera après la swap. La partition `/` ne sera donc plus la dernière partition, ce qui complique ou empêche lusage direct de :
```bash
growpart /dev/sda 2
resize2fs /dev/sda2
```
### Swap
Pour le modèle :
```text
Swap : aucun
```
Lavertissement de Debian au sujet de labsence de swap peut être ignoré.
Si un clone a besoin de swap plus tard, utiliser plutôt :
- un swapfile ;
- ou un deuxième disque virtuel dédié au swap.
Exemple de swapfile futur :
```bash
sudo fallocate -l 1G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab
```
---
## 9. Choix du noyau Debian
Si linstallateur demande quel noyau installer, choisir :
```text
linux-image-amd64
```
Ne pas choisir une version fixe du noyau sauf besoin très particulier.
Le méta-paquet `linux-image-amd64` suivra les mises à jour normales de Debian.
---
## 10. Image initrd
Si linstallateur demande le type dimage initrd, choisir :
```text
image générique : comporte tous les pilotes disponibles
```
Cest le meilleur choix pour un modèle Proxmox, car la VM doit rester portable même si certains paramètres virtuels changent plus tard.
---
## 11. Sélection des logiciels Debian
Dans lécran de sélection des logiciels, cocher seulement :
```text
[x] serveur SSH
[x] utilitaires usuels du système
```
Laisser décoché :
```text
[ ] environnement de bureau Debian
[ ] GNOME
[ ] KDE Plasma
[ ] XFCE
[ ] LXDE
[ ] LXQt
[ ] MATE
[ ] serveur web
[ ] serveur dimpression
```
Le serveur web, les rôles applicatifs et les services métier seront installés plus tard par Ansible.
---
## 12. Premier démarrage après installation
Après linstallation, démarrer la VM et se connecter en console ou par SSH.
Passer root si le compte root est disponible :
```bash
su -
```
ou, si sudo est déjà disponible :
```bash
sudo -i
```
Toutes les commandes de bootstrap suivantes sont exécutées dans la VM.
---
## 13. Réparer APT après installation DVD
Si Debian a été installé depuis l'ISO/DVD complet, APT peut garder une source `cdrom:`. Dans ce cas, `apt update` ou `apt install` échoue parce que le système cherche les paquets sur le média d'installation plutôt que sur les miroirs Debian.
Vérifier les sources :
```bash
grep -R "^[[:space:]]*deb cdrom:" /etc/apt/sources.list /etc/apt/sources.list.d 2>/dev/null || true
```
Si une ligne `deb cdrom:` est présente, la commenter ou la retirer.
Configuration simple attendue pour Debian 13 :
```bash
cat > /etc/apt/sources.list <<'EOF'
deb http://deb.debian.org/debian trixie main contrib non-free-firmware
deb http://security.debian.org/debian-security trixie-security main contrib non-free-firmware
deb http://deb.debian.org/debian trixie-updates main contrib non-free-firmware
EOF
```
Si l'installation a créé des fichiers `.sources` sous `/etc/apt/sources.list.d/`, vérifier qu'ils ne contredisent pas cette configuration et qu'ils ne pointent pas vers `cdrom:`.
Vérifier le réseau avant de continuer :
```bash
ip route
cat /etc/resolv.conf
ping -c 3 1.1.1.1
ping -c 3 deb.debian.org
```
Résultat attendu :
```text
une route par défaut existe
un serveur DNS est configuré
le ping IP fonctionne
la résolution DNS fonctionne
```
---
## 14. Mise à jour du système
Exécuter :
```bash
apt update
apt full-upgrade -y
```
---
## 15. Paquets de bootstrap
Installer seulement les paquets nécessaires pour que Set-OPS puisse prendre le relais :
```bash
apt install -y \
sudo \
openssh-server \
qemu-guest-agent \
cloud-init \
cloud-guest-utils \
ca-certificates \
curl \
vim \
nano
```
Rôle des paquets principaux :
```text
sudo : délégation administrative
openssh-server : accès distant initial
qemu-guest-agent : communication Proxmox ↔ VM
cloud-init : personnalisation au premier boot du clone
cloud-guest-utils : fournit notamment growpart
```
Les autres paquets communs, outils de diagnostic, chrony et composants de durcissement sont installés ensuite par `make preparer-modele`.
---
## 16. Activation du QEMU Guest Agent
Activer le service :
```bash
systemctl enable --now qemu-guest-agent
```
Vérifier :
```bash
systemctl status qemu-guest-agent --no-pager
```
Dans Proxmox, activer aussi :
```text
VM → Options → QEMU Guest Agent → Enabled
```
---
## 17. Identité initiale par Cloud-Init
À partir d'ici, ne pas créer manuellement le compte `ansible` ni son fichier `authorized_keys`, sauf dépannage.
L'identité initiale doit venir de Proxmox + Cloud-Init :
```text
Proxmox : ciuser, clé SSH, réseau, DNS
Debian : cloud-init applique ces paramètres au démarrage
Ansible : configure ensuite le vrai socle du serveur
```
Cette séparation évite de maintenir deux méthodes concurrentes pour créer le même accès.
### Ajouter le CloudInit Drive
Éteindre la VM :
```bash
shutdown -h now
```
Dans Proxmox :
```text
VM → Hardware → Add → CloudInit Drive
```
Ou en ligne de commande Proxmox :
```bash
qm set VMID --ide2 STORAGE:cloudinit
```
Exemple :
```bash
qm set 9000 --ide2 local-lvm:cloudinit
```
Le nom exact du stockage dépend de la configuration Proxmox.
### Définir les paramètres Cloud-Init du modèle
Pour la VM qui deviendra le modèle, utiliser une identité technique minimale, pas une identité de serveur final.
Exemple DHCP :
```bash
qm set 9000 --ciuser ansible
qm set 9000 --sshkeys ~/.ssh/id_ed25519.pub
qm set 9000 --ipconfig0 ip=dhcp
```
Exemple IP statique temporaire pour joindre la VM de modèle :
```bash
qm set 9000 --ciuser ansible
qm set 9000 --sshkeys ~/.ssh/id_ed25519.pub
qm set 9000 --ipconfig0 ip=10.0.99.99/24,gw=10.0.99.1
qm set 9000 --nameserver 10.0.99.1
```
Pour les clones, ces valeurs seront remplacées par l'identité réelle du serveur.
### Réinitialiser l'état Cloud-Init avant le test
Comme `cloud-init` vient d'être installé après le premier démarrage Debian, nettoyer son état avant de tester l'injection Proxmox :
```bash
cloud-init clean --logs
reboot
```
Au redémarrage, cloud-init doit appliquer le `ciuser`, la clé SSH et les paramètres réseau.
---
## 18. SSH et sudo après Cloud-Init
### Tester la connexion par clé
Depuis le poste d'administration :
```bash
ssh -o PreferredAuthentications=publickey ansible@IP_DE_LA_VM
```
Résultat attendu : la connexion fonctionne sans saisir le mot de passe du compte `ansible`.
### Vérifier sudo
Tester :
```bash
sudo -v
```
```bash
sudo -n true && echo OK
```
Le résultat attendu est `OK`. Le flux `make` ne demande pas de mot de passe interactif.
### Correction minimale si sudo manque
Si cloud-init a créé `ansible` sans privilège sudo, corriger seulement ce point depuis la console Proxmox ou un compte administrateur existant :
```bash
usermod -aG sudo ansible
```
Puis retester `sudo -v` avec le compte `ansible`.
Ne pas créer manuellement `/home/ansible/.ssh/authorized_keys` dans le flux normal. Si la clé ne fonctionne pas, corriger les paramètres Cloud-Init dans Proxmox, puis relancer :
```bash
cloud-init clean --logs
reboot
```
---
## 19. SSH durci attendu
Le modèle suppose que Cloud-Init injecte le `ciuser` et sa clé publique avant la prise en charge par Ansible.
L'exigence immédiate est que l'accès par clé fonctionne pour `ansible`.
L'état final sera appliqué et validé par Set-OPS :
```text
PermitRootLogin no
PubkeyAuthentication yes
PasswordAuthentication no
AuthenticationMethods publickey
```
Ne pas lancer le playbook de préparation tant que l'accès SSH par clé ne fonctionne pas.
---
## 20. Vérifications avant passage à Set-OPS
Redémarrer la VM une dernière fois si nécessaire, puis vérifier :
```bash
lsblk
df -h
swapon --show
ip a
hostnamectl
timedatectl
systemctl status ssh --no-pager
systemctl status qemu-guest-agent --no-pager
cloud-init --version
```
État attendu :
```text
/boot/efi présent
/ sur ext4
aucun swap actif
qemu-guest-agent actif
ssh actif
cloud-init installé
compte ansible fonctionnel
connexion SSH par clé fonctionnelle
sudo fonctionnel pour ansible
CloudInit Drive présent côté Proxmox
```
---
## 21. Point de bascule vers Set-OPS
À partir de ce point, ne pas continuer manuellement la configuration du socle. Utiliser le `Makefile` depuis le dépôt Set-OPS.
Depuis le poste Ansible :
```bash
make preparer-modele
make verifier-modele
```
Le cleanup final est séparé et protégé :
```bash
make nettoyer-modele CONFIRMER=true
```
Ne pas lancer le cleanup final tant que `make verifier-modele` n'a pas réussi.
---
## 22. Dépannage du bootstrap initial
| Symptôme | Cause probable | Correction |
| --- | --- | --- |
| `apt install` demande le DVD | Source APT `cdrom:` encore active. | Refaire la section 13 et relancer `apt update`. |
| `Temporary failure resolving deb.debian.org` | DNS absent ou incorrect. | Vérifier `/etc/resolv.conf`, Proxmox cloud-init DNS, passerelle et réseau. |
| `Network is unreachable` | Pas de route par défaut. | Vérifier l'IP, la passerelle et `ip route`. |
| SSH refuse la clé | Clé Cloud-Init absente, mauvais `ciuser`, ou cloud-init déjà initialisé avant l'ajout des paramètres. | Corriger les paramètres Proxmox, puis `cloud-init clean --logs` et redémarrer. |
| `sudo: a password is required` | Sudo NOPASSWD pas encore posé. | Corriger sudo NOPASSWD pour `ansible`, puis relancer `make preparer-modele`. |
| `ansible is not in the sudoers file` | Le compte a été créé par Cloud-Init sans privilège sudo. | Ajouter seulement `ansible` au groupe `sudo`, puis laisser Set-OPS gérer NOPASSWD. |
| Cloud-init sans effet | `cloud-init` absent au premier boot concerné, disque CloudInit absent, ou VM déjà initialisée. | Installer `cloud-init`, ajouter le CloudInit Drive, corriger les paramètres Proxmox, puis `cloud-init clean --logs` et redémarrer. |
---
## 23. Nettoyage avant conversion en template
Le nettoyage est fait par Set-OPS, pas à la main, sauf diagnostic exceptionnel.
Commande attendue :
```bash
make nettoyer-modele CONFIRMER=true
```
Ce nettoyage peut inclure :
```text
cloud-init clean --logs
nettoyage du cache APT
rotation ou purge contrôlée des journaux
remise à zéro de /etc/machine-id
suppression des historiques shell
```
---
## 24. Point darrêt : VM prête à convertir
À ce stade, la VM doit être arrêtée.
Elle est prête à être convertie en template Proxmox.
État attendu côté Proxmox :
```text
VM arrêtée
BIOS OVMF / UEFI
Machine q35
EFI Disk présent
Disque principal SCSI / VirtIO SCSI single
CloudInit Drive présent
QEMU Guest Agent activé dans les options
CPU x86-64-v2-AES
RAM 2048 MiB
Aucun environnement graphique
```
État attendu côté Debian :
```text
Debian 13 minimal
SSH installé
qemu-guest-agent installé
cloud-init installé
cloud-guest-utils installé
sudo installé
compte ansible injecté par cloud-init
clé publique SSH injectée par cloud-init
sudo NOPASSWD pour ansible après préparation Set-OPS
partition EFI 512 Mo
partition / ext4
aucune partition swap
aucun LVM
machine-id nettoyé
cloud-init nettoyé
apt cache nettoyé
VM éteinte
```
---
## 25. Conversion en template Proxmox
Cette commande nest exécutée quaprès validation finale :
```bash
qm template VMID
```
Exemple :
```bash
qm template 9000
```
À partir de là, le modèle peut être cloné.
---
## 26. Flux normal après conversion
Après conversion du modèle, le flux normal passe par `make` et l'API Proxmox.
Les paramètres communs Proxmox sont dans :
```text
instance/inventories/lab/group_vars/proxmox.yml
```
Les secrets d'API doivent être dans un fichier Vault non versionné :
```text
instance/inventories/lab/group_vars/proxmox.vault.yml
```
Créer le clone et l'ajouter à l'inventaire :
```text
1. Cloner le template via l'API Proxmox.
2. Agrandir le disque si demandé.
3. Définir l'identité Cloud-Init.
4. Démarrer la VM.
5. Ajouter l'hôte à l'inventaire Set-OPS.
```
Exemple :
```bash
make creer-vm HOTE=web-frontal-01
```
Les paramètres par VM (VMID, IP, VLAN, passerelle) ne sont plus saisis à la main : `creer-vm` les lit dans l'inventaire généré depuis le plan. L'hôte doit donc être déclaré dans `instance/plan/serveurs.yml` et l'inventaire régénéré (`make instancier-appliquer`) au préalable.
Après le premier démarrage, tester l'accès :
```bash
ssh ansible@10.0.15.31
```
Ensuite appliquer la conformité Ansible :
```bash
make deployer HOTE=web-frontal-01
```
---
## 27. Résumé court
La VM vanille idéale est simple :
```text
Debian 13 minimal
UEFI / OVMF
q35
SCSI / VirtIO SCSI single
16 Go
2 Go RAM
2 cores
partition EFI 512 Mo
partition / ext4
pas de LVM
pas de swap
SSH
sudo
qemu-guest-agent
cloud-init
cloud-guest-utils
CloudInit Drive Proxmox
ciuser ansible défini dans Proxmox
clé publique SSH définie dans Proxmox
sudo fonctionnel pour ansible
APT réseau fonctionnel
```
La philosophie :
```text
Proxmox crée la VM.
Cloud-init donne son identité au clone.
Ansible configure le vrai serveur.
Set-OPS documente et automatise lensemble.
```

201
docs/vm-lifecycle.md Normal file
View file

@ -0,0 +1,201 @@
# Cycle de vie des VM
Ce document décrit le cycle normal d'une VM Debian, depuis l'installation minimale jusqu'à la conformité continue par Ansible.
## Vue d'ensemble
```text
Debian 13 minimale
→ goldenisation du template
→ cleanup final
→ conversion Proxmox en template
→ clonage
→ identité initiale par cloud-init
→ conformité par groupes Ansible
→ intégrations par rôle
→ conformité continue
```
## 1. VM vanille
La VM vanille est une Debian 13 minimale installée manuellement ou par procédure Proxmox.
Elle contient seulement le nécessaire pour être joignable et prise en charge :
```text
Debian 13
SSH
réseau fonctionnel
cloud-init installé
CloudInit Drive Proxmox présent
utilisateur initial injecté par Cloud-Init
clé publique SSH injectée par Cloud-Init
sudo ou accès root possible
aucun rôle applicatif
aucun secret
```
Cette VM n'est pas encore un golden template.
## 2. Goldenisation
La goldenisation est faite par :
```bash
make preparer-modele
```
Ce playbook ajoute le socle commun au template :
```text
paquets communs
qemu-guest-agent
cloud-init
compte technique Ansible
chrony
SSH socle
durcissement template-safe
nftables préparé mais désactivé
```
Les détails et justifications sont dans :
```text
docs/modeles_vm/debian13-proxmox.md
```
## 3. Validation et cleanup
Après la préparation :
```bash
make verifier-modele
```
Le cleanup final est séparé et protégé :
```bash
make nettoyer-modele CONFIRMER=true
```
Le cleanup ne doit jamais être lancé sur une VM de production.
## 4. Clonage
Le clone reçoit son identité initiale par Proxmox et cloud-init :
```text
hostname
utilisateur initial
clé SSH
IP ou DHCP
passerelle
DNS
agrandissement disque
```
Cloud-init ne remplace pas Ansible pour la configuration réelle du serveur.
## 5. Groupes opérationnels
Une VM déployée doit être placée dans les groupes d'inventaire qui décrivent l'état voulu.
Les hôtes prévus mais non créés restent dans `hotes_planifies`.
Les hôtes joignables par Ansible sont dans `hotes_actifs`.
Chaque groupe opérationnel doit avoir un playbook homonyme :
```text
serveur_debian -> playbooks/groupes/serveur_debian.yml
serveur_durci -> playbooks/groupes/serveur_durci.yml
```
L'appartenance aux groupes devient la déclaration d'intention :
```text
web-frontal-01 dans serveur_debian -> applique le socle Debian
web-frontal-01 dans serveur_durci -> applique le durcissement commun
```
Un groupe sans playbook ne doit pas être assigné à une VM de production.
Un playbook de groupe peut être un contrat temporaire sans rôle tant que le service n'est pas encore implémenté. Il doit rester idempotent et explicite.
## 6. Socle Debian
Le groupe `serveur_debian` maintient les composants de base :
```bash
make deployer-groupe GROUPE=serveur_debian
```
## 7. Hardening commun
Le groupe `serveur_durci` maintient les couches de sécurité communes :
```bash
make deployer-groupe GROUPE=serveur_durci
```
L'accès SSH par mot de passe est désactivé dès le socle. L'activation de nftables doit rester dépendante des règles réseau propres au rôle du serveur.
## 8. Déploiement par Makefile
L'hôte est d'abord déclaré dans le **plan** (`instance/plan/serveurs.yml`) et l'inventaire régénéré (`make instancier-appliquer`) — `hosts.yml` est généré, il ne s'édite plus à la main. Pour une VM déjà clonée et démarrée :
```bash
make deployer HOTE=web-frontal-01
```
La cible `deployer` lit les groupes de l'hôte, cherche les playbooks correspondants dans `playbooks/groupes/`, puis les applique dans l'ordre de l'inventaire.
Ensuite, elle lance la vérification post-déploiement :
```text
playbooks/groupes/serveur_debian.yml
playbooks/groupes/serveur_durci.yml
verifier-hote
```
Pour converger un groupe complet :
```bash
make deployer-groupe GROUPE=serveur_debian
```
Cette commande limite le playbook au croisement entre le groupe demandé et `hotes_actifs`.
## 9. Variables template et conformité
Les variables du template servent à construire le golden template dans `instance/inventories/lab/group_vars/modeles_vm.yml`.
Les variables de conformité servent aux VM déployées dans `instance/inventories/production/group_vars/serveur_debian.yml`.
Différence attendue :
```text
template : SSH par clé seulement, pare-feu non activé
conformité VM : même base SSH, règles réseau adaptées aux serveurs finaux
```
Ne pas durcir une variable de conformité si son application peut couper l'accès sans validation préalable.
## 10. Intégrations futures
Les intégrations transversales sont ajoutées après socle et durcissement :
```text
DNS interne
PKI interne
identité
supervision
métriques
visualisation
```
Elles sont documentées dans :
```text
docs/integrations-vm.md
```

View file

@ -0,0 +1,16 @@
---
# Identite d'une INSTANCE Set-OPS (un loup / un client).
#
# Ces variables distinguent une instance d'une autre. Le MOTEUR (roles, scripts,
# generateur) est generique ; c'est ici qu'on dit "qui on est". A placer dans
# inventories/<env>/group_vars/all.yml de l'instance.
#
# Voir docs/plan-et-generation.md et docs/positionnement.md.
# Domaine DNS interne de l'instance. Les roles en derivent zones, FQDN, base DN
# LDAP, expediteurs courriel, etc. (ex. dc=acme,dc=local pour acme.local).
domaine_interne: "acme.internal"
# Le reste de l'identite vit dans le PLAN de l'instance :
# - instance/plan/nomenclature.yml : supernet (ex. 10.0.0.0/16) + liste de fonctions
# - instance/plan/serveurs.yml / applications.yml / domaines.yml : le plan rempli

View file

@ -0,0 +1,27 @@
# Modèles d'écosystèmes numériques
Des **instances prêtes à déployer** : un hébergeur copie le modèle qui colle à son
offre, le renseigne à ses couleurs (ou celles de son client), et instancie.
## Utilisation
```bash
cp -r exemples/modeles/<modele> ../mon-instance
# éditer mon-instance : domaine_interne, domaines publics, dimensionnement
cd <moteur Set-OPS>
ln -s ../mon-instance instance # ou export SETOPS_INSTANCE=../mon-instance
make instancier-appliquer FORCE=1 # première génération de l'inventaire
```
## Structure : socle + modules
Tout modèle part d'un **socle souverain** (DNS interne, AC/PKI, edge TLS, relais
courriel) et ajoute des **modules** selon l'offre.
| Modèle | = socle + | État |
| --- | --- | --- |
| `socle` | (rien) | ✅ |
| **`presence-web`** | web (frontal+dorsal) + données (PostgreSQL/Redis) | ✅ |
| `forge` | Forgejo + données | ✅ |
| `identite` | LDAP + Keycloak (SSO) | ✅ |
| `observabilite` | Prometheus/Loki/Grafana + Icinga | ✅ |
| `collaboration` | Nextcloud + Collabora | rôles à construire |
| `integral` | tout | ✅ |

View file

@ -0,0 +1,5 @@
# Modele : forge
Forge Git souveraine : socle + Forgejo + PostgreSQL.
6 VM. Renseignez `domaine_interne`, `plan/domaines.yml` et `plan/nomenclature.yml` (supernet), puis generez (voir `exemples/modeles/README.md`).

View file

@ -0,0 +1,3 @@
---
domaine_interne: "exemple.internal" # <- VOTRE domaine DNS interne
setops_plan_dir: "{{ playbook_dir }}/../../instance/plan"

View file

@ -0,0 +1,24 @@
---
applications:
step_ca:
groupe: serveur_step_ca
hote: infra-pki-01
nginx:
groupe: serveur_nginx
hote: infra-edge-01
sendmail:
groupe: serveur_sendmail
hote: infra-mail-01
powerdns:
groupe: serveur_powerdns
hote: infra-dns-01
postgresql:
groupe: serveur_postgresql
hote: data-01
port: 5432
forgejo:
groupe: serveur_forgejo
hote: forge-01
port: 3000
expose:
- forge.exemple.ca

View file

@ -0,0 +1,16 @@
---
serveurs_bd:
pg-principal:
type: postgres
hote: 10.10.13.11
port: 5432
groupe: serveur_postgresql
bases_donnees:
forgejo:
serveur: pg-principal
base: forgejo
proprietaire: forgejo
secret: vault_bd_forgejo
consommateur: serveur_forgejo
portee: groupe
usage: principale

View file

@ -0,0 +1,7 @@
---
domaines_publics:
exemple.ca:
autorite: primaire-cache
edge: serveur_nginx
secondaires: []
dnssec: false

View file

@ -0,0 +1,42 @@
---
supernet: 10.10.0.0/16
cidr_hote: 24
reservations:
passerelle: 1
reserve_min: 2
reserve_max: 9
categories:
1:
libelle: Fondations
vlan: 11
sous_reseau: 10.10.11.0/24
passerelle: 10.10.11.1
3:
libelle: Donnees
vlan: 13
sous_reseau: 10.10.13.0/24
passerelle: 10.10.13.1
5:
libelle: Applications
vlan: 15
sous_reseau: 10.10.15.0/24
passerelle: 10.10.15.1
fonctions:
data:
categorie: 3
service: 1
forge:
categorie: 5
service: 1
infra-dns:
categorie: 1
service: 4
infra-edge:
categorie: 1
service: 2
infra-mail:
categorie: 1
service: 3
infra-pki:
categorie: 1
service: 1

View file

@ -0,0 +1,20 @@
---
serveurs:
infra-pki-01:
fonction: infra-pki
etat: planifie
infra-edge-01:
fonction: infra-edge
etat: planifie
infra-mail-01:
fonction: infra-mail
etat: planifie
infra-dns-01:
fonction: infra-dns
etat: planifie
data-01:
fonction: data
etat: planifie
forge-01:
fonction: forge
etat: planifie

View file

@ -0,0 +1,5 @@
# Modele : identite
Identite centralisee : socle + LDAP + Keycloak (SSO) + PostgreSQL.
6 VM. Renseignez `domaine_interne`, `plan/domaines.yml` et `plan/nomenclature.yml` (supernet), puis generez (voir `exemples/modeles/README.md`).

View file

@ -0,0 +1,3 @@
---
domaine_interne: "exemple.internal" # <- VOTRE domaine DNS interne
setops_plan_dir: "{{ playbook_dir }}/../../instance/plan"

View file

@ -0,0 +1,27 @@
---
applications:
step_ca:
groupe: serveur_step_ca
hote: infra-pki-01
nginx:
groupe: serveur_nginx
hote: infra-edge-01
sendmail:
groupe: serveur_sendmail
hote: infra-mail-01
powerdns:
groupe: serveur_powerdns
hote: infra-dns-01
postgresql:
groupe: serveur_postgresql
hote: data-01
port: 5432
openldap:
groupe: serveur_openldap
hote: idm-01
keycloak:
groupe: serveur_keycloak
hote: idm-01
port: 8080
expose:
- auth.exemple.ca

View file

@ -0,0 +1,16 @@
---
serveurs_bd:
pg-principal:
type: postgres
hote: 10.10.13.11
port: 5432
groupe: serveur_postgresql
bases_donnees:
keycloak:
serveur: pg-principal
base: keycloak
proprietaire: keycloak
secret: vault_bd_keycloak
consommateur: serveur_keycloak
portee: groupe
usage: principale

View file

@ -0,0 +1,7 @@
---
domaines_publics:
exemple.ca:
autorite: primaire-cache
edge: serveur_nginx
secondaires: []
dnssec: false

View file

@ -0,0 +1,42 @@
---
supernet: 10.10.0.0/16
cidr_hote: 24
reservations:
passerelle: 1
reserve_min: 2
reserve_max: 9
categories:
1:
libelle: Fondations
vlan: 11
sous_reseau: 10.10.11.0/24
passerelle: 10.10.11.1
2:
libelle: Identite
vlan: 12
sous_reseau: 10.10.12.0/24
passerelle: 10.10.12.1
3:
libelle: Donnees
vlan: 13
sous_reseau: 10.10.13.0/24
passerelle: 10.10.13.1
fonctions:
data:
categorie: 3
service: 1
idm:
categorie: 2
service: 1
infra-dns:
categorie: 1
service: 4
infra-edge:
categorie: 1
service: 2
infra-mail:
categorie: 1
service: 3
infra-pki:
categorie: 1
service: 1

View file

@ -0,0 +1,20 @@
---
serveurs:
infra-pki-01:
fonction: infra-pki
etat: planifie
infra-edge-01:
fonction: infra-edge
etat: planifie
infra-mail-01:
fonction: infra-mail
etat: planifie
infra-dns-01:
fonction: infra-dns
etat: planifie
data-01:
fonction: data
etat: planifie
idm-01:
fonction: idm
etat: planifie

View file

@ -0,0 +1,5 @@
# Modele : integral
Ecosysteme integral (services implementes) : socle + donnees + identite + forge + observabilite + web.
11 VM. Renseignez `domaine_interne`, `plan/domaines.yml` et `plan/nomenclature.yml` (supernet), puis generez (voir `exemples/modeles/README.md`).

View file

@ -0,0 +1,3 @@
---
domaine_interne: "exemple.internal" # <- VOTRE domaine DNS interne
setops_plan_dir: "{{ playbook_dir }}/../../instance/plan"

View file

@ -0,0 +1,57 @@
---
applications:
step_ca:
groupe: serveur_step_ca
hote: infra-pki-01
nginx:
groupe: serveur_nginx
hote: infra-edge-01
sendmail:
groupe: serveur_sendmail
hote: infra-mail-01
powerdns:
groupe: serveur_powerdns
hote: infra-dns-01
postgresql:
groupe: serveur_postgresql
hote: data-01
port: 5432
redis:
groupe: serveur_redis
hote: data-01
openldap:
groupe: serveur_openldap
hote: idm-01
keycloak:
groupe: serveur_keycloak
hote: idm-01
port: 8080
expose:
- auth.exemple.ca
forgejo:
groupe: serveur_forgejo
hote: forge-01
port: 3000
expose:
- forge.exemple.ca
prometheus:
groupe: serveur_prometheus
hote: obs-01
loki:
groupe: serveur_loki
hote: obs-01
grafana:
groupe: serveur_grafana
hote: obs-01
port: 3000
expose:
- grafana.exemple.ca
icinga:
groupe: serveur_icinga
hote: mon-01
web_frontal:
groupe: serveur_web_frontal
hote: web-frontal-01
web_dorsal:
groupe: serveur_web_dorsal
hote: web-dorsal-01

View file

@ -0,0 +1,32 @@
---
serveurs_bd:
pg-principal:
type: postgres
hote: 10.10.13.11
port: 5432
groupe: serveur_postgresql
bases_donnees:
keycloak:
serveur: pg-principal
base: keycloak
proprietaire: keycloak
secret: vault_bd_keycloak
consommateur: serveur_keycloak
portee: groupe
usage: principale
forgejo:
serveur: pg-principal
base: forgejo
proprietaire: forgejo
secret: vault_bd_forgejo
consommateur: serveur_forgejo
portee: groupe
usage: principale
icingadb:
serveur: pg-principal
base: icingadb
proprietaire: icingadb
secret: vault_bd_icingadb
consommateur: serveur_icinga
portee: groupe
usage: principale

View file

@ -0,0 +1,7 @@
---
domaines_publics:
exemple.ca:
autorite: primaire-cache
edge: serveur_nginx
secondaires: []
dnssec: false

View file

@ -0,0 +1,67 @@
---
supernet: 10.10.0.0/16
cidr_hote: 24
reservations:
passerelle: 1
reserve_min: 2
reserve_max: 9
categories:
1:
libelle: Fondations
vlan: 11
sous_reseau: 10.10.11.0/24
passerelle: 10.10.11.1
2:
libelle: Identite
vlan: 12
sous_reseau: 10.10.12.0/24
passerelle: 10.10.12.1
3:
libelle: Donnees
vlan: 13
sous_reseau: 10.10.13.0/24
passerelle: 10.10.13.1
4:
libelle: Observabilite
vlan: 14
sous_reseau: 10.10.14.0/24
passerelle: 10.10.14.1
5:
libelle: Applications
vlan: 15
sous_reseau: 10.10.15.0/24
passerelle: 10.10.15.1
fonctions:
data:
categorie: 3
service: 1
forge:
categorie: 5
service: 1
idm:
categorie: 2
service: 1
infra-dns:
categorie: 1
service: 4
infra-edge:
categorie: 1
service: 2
infra-mail:
categorie: 1
service: 3
infra-pki:
categorie: 1
service: 1
mon:
categorie: 4
service: 4
obs:
categorie: 4
service: 1
web-dorsal:
categorie: 5
service: 4
web-frontal:
categorie: 5
service: 3

View file

@ -0,0 +1,35 @@
---
serveurs:
infra-pki-01:
fonction: infra-pki
etat: planifie
infra-edge-01:
fonction: infra-edge
etat: planifie
infra-mail-01:
fonction: infra-mail
etat: planifie
infra-dns-01:
fonction: infra-dns
etat: planifie
data-01:
fonction: data
etat: planifie
idm-01:
fonction: idm
etat: planifie
forge-01:
fonction: forge
etat: planifie
obs-01:
fonction: obs
etat: planifie
mon-01:
fonction: mon
etat: planifie
web-frontal-01:
fonction: web-frontal
etat: planifie
web-dorsal-01:
fonction: web-dorsal
etat: planifie

View file

@ -0,0 +1,5 @@
# Modele : observabilite
Observabilite & supervision : socle + Prometheus/Loki/Grafana + Icinga.
7 VM. Renseignez `domaine_interne`, `plan/domaines.yml` et `plan/nomenclature.yml` (supernet), puis generez (voir `exemples/modeles/README.md`).

View file

@ -0,0 +1,3 @@
---
domaine_interne: "exemple.internal" # <- VOTRE domaine DNS interne
setops_plan_dir: "{{ playbook_dir }}/../../instance/plan"

View file

@ -0,0 +1,36 @@
---
applications:
step_ca:
groupe: serveur_step_ca
hote: infra-pki-01
nginx:
groupe: serveur_nginx
hote: infra-edge-01
sendmail:
groupe: serveur_sendmail
hote: infra-mail-01
powerdns:
groupe: serveur_powerdns
hote: infra-dns-01
postgresql:
groupe: serveur_postgresql
hote: data-01
port: 5432
redis:
groupe: serveur_redis
hote: data-01
prometheus:
groupe: serveur_prometheus
hote: obs-01
loki:
groupe: serveur_loki
hote: obs-01
grafana:
groupe: serveur_grafana
hote: obs-01
port: 3000
expose:
- grafana.exemple.ca
icinga:
groupe: serveur_icinga
hote: mon-01

View file

@ -0,0 +1,16 @@
---
serveurs_bd:
pg-principal:
type: postgres
hote: 10.10.13.11
port: 5432
groupe: serveur_postgresql
bases_donnees:
icingadb:
serveur: pg-principal
base: icingadb
proprietaire: icingadb
secret: vault_bd_icingadb
consommateur: serveur_icinga
portee: groupe
usage: principale

View file

@ -0,0 +1,7 @@
---
domaines_publics:
exemple.ca:
autorite: primaire-cache
edge: serveur_nginx
secondaires: []
dnssec: false

View file

@ -0,0 +1,45 @@
---
supernet: 10.10.0.0/16
cidr_hote: 24
reservations:
passerelle: 1
reserve_min: 2
reserve_max: 9
categories:
1:
libelle: Fondations
vlan: 11
sous_reseau: 10.10.11.0/24
passerelle: 10.10.11.1
3:
libelle: Donnees
vlan: 13
sous_reseau: 10.10.13.0/24
passerelle: 10.10.13.1
4:
libelle: Observabilite
vlan: 14
sous_reseau: 10.10.14.0/24
passerelle: 10.10.14.1
fonctions:
data:
categorie: 3
service: 1
infra-dns:
categorie: 1
service: 4
infra-edge:
categorie: 1
service: 2
infra-mail:
categorie: 1
service: 3
infra-pki:
categorie: 1
service: 1
mon:
categorie: 4
service: 4
obs:
categorie: 4
service: 1

View file

@ -0,0 +1,23 @@
---
serveurs:
infra-pki-01:
fonction: infra-pki
etat: planifie
infra-edge-01:
fonction: infra-edge
etat: planifie
infra-mail-01:
fonction: infra-mail
etat: planifie
infra-dns-01:
fonction: infra-dns
etat: planifie
data-01:
fonction: data
etat: planifie
obs-01:
fonction: obs
etat: planifie
mon-01:
fonction: mon
etat: planifie

View file

@ -0,0 +1,9 @@
# Modèle : Présence Web
Écosystème pour héberger **sites et applications web** : socle souverain (DNS,
PKI, edge TLS, relais courriel) + couche web (frontal + dorsal) + données
(PostgreSQL + Redis). 7 VM.
Renseignez : `inventories/production/group_vars/all.yml` (`domaine_interne`),
`plan/domaines.yml` (votre domaine public), `plan/nomenclature.yml` (supernet),
puis générez l'inventaire (voir `exemples/modeles/README.md`).

View file

@ -0,0 +1,3 @@
---
domaine_interne: "exemple.internal" # <- VOTRE domaine DNS interne
setops_plan_dir: "{{ playbook_dir }}/../../instance/plan"

View file

@ -0,0 +1,10 @@
---
applications:
pki: { groupe: serveur_step_ca, hote: infra-pki-01 }
edge: { groupe: serveur_nginx, hote: infra-edge-01 }
mail: { groupe: serveur_sendmail, hote: infra-mail-01 }
dns: { groupe: serveur_powerdns, hote: infra-dns-01 }
bdd: { groupe: serveur_postgresql, hote: data-01, port: 5432 }
cache: { groupe: serveur_redis, hote: data-01 }
frontal: { groupe: serveur_web_frontal, hote: web-frontal-01 }
dorsal: { groupe: serveur_web_dorsal, hote: web-dorsal-01 }

View file

@ -0,0 +1,4 @@
---
serveurs_bd:
pg-principal: { type: postgres, hote: 10.10.13.11, port: 5432, groupe: serveur_postgresql }
bases_donnees: {}

View file

@ -0,0 +1,7 @@
---
domaines_publics:
exemple.ca: # <- VOTRE domaine public
autorite: primaire-cache
edge: serveur_nginx
secondaires: []
dnssec: false

View file

@ -0,0 +1,17 @@
---
# Adaptez le supernet à VOTRE réseau interne.
supernet: "10.10.0.0/16"
cidr_hote: 24
reservations: { passerelle: 1, reserve_min: 2, reserve_max: 9 }
categories:
1: { libelle: "Fondations", vlan: 11, sous_reseau: "10.10.11.0/24", passerelle: "10.10.11.1" }
3: { libelle: "Donnees", vlan: 13, sous_reseau: "10.10.13.0/24", passerelle: "10.10.13.1" }
5: { libelle: "Applications", vlan: 15, sous_reseau: "10.10.15.0/24", passerelle: "10.10.15.1" }
fonctions:
infra-pki: { categorie: 1, service: 1 }
infra-edge: { categorie: 1, service: 2 }
infra-mail: { categorie: 1, service: 3 }
infra-dns: { categorie: 1, service: 4 }
data: { categorie: 3, service: 1 }
web-frontal: { categorie: 5, service: 3 }
web-dorsal: { categorie: 5, service: 4 }

View file

@ -0,0 +1,9 @@
---
serveurs:
infra-pki-01: { fonction: infra-pki, etat: planifie }
infra-edge-01: { fonction: infra-edge, etat: planifie }
infra-mail-01: { fonction: infra-mail, etat: planifie }
infra-dns-01: { fonction: infra-dns, etat: planifie }
data-01: { fonction: data, etat: planifie }
web-frontal-01: { fonction: web-frontal, etat: planifie }
web-dorsal-01: { fonction: web-dorsal, etat: planifie }

View file

@ -0,0 +1,5 @@
# Modele : socle
Socle souverain : DNS interne, AC/PKI, edge TLS, relais courriel.
4 VM. Renseignez `domaine_interne`, `plan/domaines.yml` et `plan/nomenclature.yml` (supernet), puis generez (voir `exemples/modeles/README.md`).

View file

@ -0,0 +1,3 @@
---
domaine_interne: "exemple.internal" # <- VOTRE domaine DNS interne
setops_plan_dir: "{{ playbook_dir }}/../../instance/plan"

View file

@ -0,0 +1,14 @@
---
applications:
step_ca:
groupe: serveur_step_ca
hote: infra-pki-01
nginx:
groupe: serveur_nginx
hote: infra-edge-01
sendmail:
groupe: serveur_sendmail
hote: infra-mail-01
powerdns:
groupe: serveur_powerdns
hote: infra-dns-01

View file

@ -0,0 +1,3 @@
---
serveurs_bd: {}
bases_donnees: {}

View file

@ -0,0 +1,7 @@
---
domaines_publics:
exemple.ca:
autorite: primaire-cache
edge: serveur_nginx
secondaires: []
dnssec: false

View file

@ -0,0 +1,26 @@
---
supernet: 10.10.0.0/16
cidr_hote: 24
reservations:
passerelle: 1
reserve_min: 2
reserve_max: 9
categories:
1:
libelle: Fondations
vlan: 11
sous_reseau: 10.10.11.0/24
passerelle: 10.10.11.1
fonctions:
infra-dns:
categorie: 1
service: 4
infra-edge:
categorie: 1
service: 2
infra-mail:
categorie: 1
service: 3
infra-pki:
categorie: 1
service: 1

View file

@ -0,0 +1,14 @@
---
serveurs:
infra-pki-01:
fonction: infra-pki
etat: planifie
infra-edge-01:
fonction: infra-edge
etat: planifie
infra-mail-01:
fonction: infra-mail
etat: planifie
infra-dns-01:
fonction: infra-dns
etat: planifie

View file

@ -0,0 +1,34 @@
"""Filtres Ansible exposant les regles de resolution des registres Set-OPS.
Reutilise scripts/inventory_rules.py : la regle de liaison application <-> base
(et la derivation du DSN) vit une SEULE fois, en Python. Le CLI, le GUI (qui en
est le miroir) et Ansible passent tous par la meme logique.
"""
from __future__ import annotations
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
from inventory_rules import ( # noqa: E402
applications_de_hote,
bases_de_application,
bases_du_groupe,
chaine_connexion,
expositions_des_applications,
)
class FilterModule:
"""Filtres Set-OPS pour les playbooks par application."""
def filters(self) -> dict:
return {
"bases_de_application": bases_de_application,
"applications_de_hote": applications_de_hote,
"bases_du_groupe": bases_du_groupe,
"chaine_connexion": chaine_connexion,
"expositions_des_applications": expositions_des_applications,
}

View file

@ -0,0 +1,3 @@
# Playbooks applications
Espace réservé aux futurs playbooks.

View file

@ -0,0 +1,3 @@
# Playbooks backup
Espace réservé aux futurs playbooks.

View file

@ -0,0 +1,3 @@
# Playbooks database
Espace réservé aux futurs playbooks.

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe client_dns
hosts: client_dns
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_dns

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe client_journal
hosts: client_journal
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_journal

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe client_ldap
hosts: client_ldap
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_ldap

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe client_metrique
hosts: client_metrique
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_metrique

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe client_pki
hosts: client_pki
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_pki

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe client_smtp
hosts: client_smtp
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- client_smtp

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe client_supervision
hosts: client_supervision
become: true
gather_facts: true
tasks:
- name: Indiquer que l'intégration cliente supervision reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe client_supervision."

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveur_collabora
hosts: serveur_collabora
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Collabora reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveur_collabora."

View file

@ -0,0 +1,22 @@
---
- name: Appliquer le groupe serveur_debian
hosts: serveur_debian
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- common_packages
- qemu_guest_agent
- cloud_init
- sudo_ansible
- chrony
- ssh_baseline
- systemd_ssh_auto
- motd

View file

@ -0,0 +1,24 @@
---
- name: Appliquer le groupe serveur_durci
hosts: serveur_durci
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- hardening_packages
- sysctl_hardening
- core_dumps
- unattended_upgrades
- apparmor
- auditd
- fail2ban_ssh
- journald
- ssh_hardening
- nftables_baseline

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_forgejo
hosts: serveur_forgejo
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_forgejo

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_grafana
hosts: serveur_grafana
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_grafana

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_icinga
hosts: serveur_icinga
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_icinga

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_keycloak
hosts: serveur_keycloak
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_keycloak

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_loki
hosts: serveur_loki
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_loki

View file

@ -0,0 +1,10 @@
---
- name: Appliquer le groupe serveur_nextcloud
hosts: serveur_nextcloud
become: true
gather_facts: true
tasks:
- name: Indiquer que le service Nextcloud reste à définir
ansible.builtin.debug:
msg: "Aucun rôle n'est encore associé au groupe serveur_nextcloud."

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_nginx
hosts: serveur_nginx
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_nginx

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_openldap
hosts: serveur_openldap
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_openldap

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_postgresql
hosts: serveur_postgresql
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_postgresql

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_powerdns
hosts: serveur_powerdns
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_powerdns

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_prometheus
hosts: serveur_prometheus
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_prometheus

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_redis
hosts: serveur_redis
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_redis

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_sendmail
hosts: serveur_sendmail
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_sendmail

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe serveur_step_ca
hosts: serveur_step_ca
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- serveur_step_ca

View file

@ -0,0 +1,43 @@
---
- name: Appliquer le groupe serveur_web_dorsal
hosts: serveur_web_dorsal
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
tasks:
# Couche application : une VM dorsale peut porter PLUSIEURS applications.
# On itere donc sur les applications mappees a cet hote (instance/plan/applications.yml),
# chacune resolvant ses bases via son DSN (regle partagee, filter plugin).
- name: Charger les registres applications et bases
ansible.builtin.include_vars:
file: "{{ item }}"
loop:
- "{{ setops_plan_dir }}/applications.yml"
- "{{ setops_plan_dir }}/bases-donnees.yml"
- name: Determiner les applications instanciees sur cet hote
ansible.builtin.set_fact:
applications_hote: "{{ {'applications': applications | default({})} | applications_de_hote(inventory_hostname) }}"
registre_bd: "{{ {'serveurs_bd': serveurs_bd | default({}), 'bases_donnees': bases_donnees | default({})} }}"
- name: Afficher la chaine application -> bases (scaffold ; le deploiement reel s'insere ici)
ansible.builtin.debug:
msg: >-
{{ item.id }} [{{ item.groupe }}] ->
{{ (registre_bd | bases_de_application(item.id, item.groupe, inventory_hostname))
| map(attribute='cle') | list }}
loop: "{{ applications_hote }}"
loop_control:
label: "{{ item.id }}"
- name: Avertir si l'hote ne porte aucune application
ansible.builtin.debug:
msg: "Aucune application declaree sur cet hote (instance/plan/applications.yml). Couche dorsale : API, traitement."
when: applications_hote | length == 0

View file

@ -0,0 +1,43 @@
---
- name: Appliquer le groupe serveur_web_frontal
hosts: serveur_web_frontal
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
tasks:
# Couche presentation : une VM frontale peut porter PLUSIEURS applications.
# On itere sur les applications mappees a cet hote (instance/plan/applications.yml),
# chacune resolvant ses bases via son DSN (regle partagee, filter plugin).
- name: Charger les registres applications et bases
ansible.builtin.include_vars:
file: "{{ item }}"
loop:
- "{{ setops_plan_dir }}/applications.yml"
- "{{ setops_plan_dir }}/bases-donnees.yml"
- name: Determiner les applications instanciees sur cet hote
ansible.builtin.set_fact:
applications_hote: "{{ {'applications': applications | default({})} | applications_de_hote(inventory_hostname) }}"
registre_bd: "{{ {'serveurs_bd': serveurs_bd | default({}), 'bases_donnees': bases_donnees | default({})} }}"
- name: Afficher la chaine application -> bases (scaffold ; le deploiement reel s'insere ici)
ansible.builtin.debug:
msg: >-
{{ item.id }} [{{ item.groupe }}] ->
{{ (registre_bd | bases_de_application(item.id, item.groupe, inventory_hostname))
| map(attribute='cle') | list }}
loop: "{{ applications_hote }}"
loop_control:
label: "{{ item.id }}"
- name: Avertir si l'hote ne porte aucune application
ansible.builtin.debug:
msg: "Aucune application declaree sur cet hote (instance/plan/applications.yml). Couche presentation, derriere serveur_nginx."
when: applications_hote | length == 0

View file

@ -0,0 +1,19 @@
---
- name: Mettre à jour les serveurs Debian
hosts: all
become: true
gather_facts: true
tasks:
- name: Mettre à jour le cache APT
ansible.builtin.apt:
update_cache: true
cache_valid_time: 3600
- name: Appliquer les mises à jour
ansible.builtin.apt:
upgrade: full
- name: Supprimer les paquets inutiles
ansible.builtin.apt:
autoremove: true

View file

@ -0,0 +1,38 @@
---
- name: Vérifier une VM Debian gérée par Set-OPS
hosts: serveur_debian
become: true
gather_facts: true
tasks:
- name: Vérifier que la cible est Debian 13
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
- ansible_facts.distribution_major_version == "13"
fail_msg: "Cette vérification attend une VM Debian 13."
- name: Collecter les services
ansible.builtin.service_facts:
- name: Vérifier les services du socle
ansible.builtin.assert:
that:
- '"ssh.service" in ansible_facts.services'
- '"qemu-guest-agent.service" in ansible_facts.services'
- '"chrony.service" in ansible_facts.services'
- ansible_facts.services["ssh.service"].state == "running"
- ansible_facts.services["qemu-guest-agent.service"].state == "running"
- ansible_facts.services["chrony.service"].state == "running"
fail_msg: "Un service du socle Debian n'est pas actif."
- name: Vérifier sudo non interactif via become
ansible.builtin.command: whoami
changed_when: false
register: verifier_hote_debian_whoami
- name: Valider l'élévation root
ansible.builtin.assert:
that:
- verifier_hote_debian_whoami.stdout == "root"
fail_msg: "L'élévation sudo ne retourne pas root."

View file

@ -0,0 +1,8 @@
---
- name: Nettoyage final avant conversion en template Proxmox
hosts: modeles_vm
become: true
gather_facts: true
roles:
- template_cleanup

Some files were not shown because too many files have changed in this diff Show more