diff --git a/CHANGELOG.md b/CHANGELOG.md index d51ab25..3d67811 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,61 @@ # CHANGELOG — Set-OPS +## 2026-08-22 — Une forge n'a pas besoin d'un serveur de bases pour trois personnes + +Doute de l'exploitant en relisant patient 0 : *« je doute de la pertinence de pgsql. »* +Mesuré plutôt que discuté, et le résultat est allé plus loin que la question. + +**Redis ne servait à rien.** Le rôle `serveur_forgejo` ne le mentionne ni dans son +`app.ini`, ni dans ses défauts, et ne déclare aucun lien vers lui. Il était au plan par +héritage du modèle `forge`. Retiré. + +**PostgreSQL, lui, était exigé par le rôle** : `DB_TYPE = postgres` écrit en dur, et +`resoudre_base` appelé sans condition. Le doute était donc fondé mais le moteur ne savait +pas faire autrement. + +### L'interrupteur + +```yaml +serveur_forgejo_bd: sqlite # ou postgres (défaut) +``` + +En `sqlite`, la base devient **un fichier** sous `serveur_forgejo_data`. Ce que ça change +ailleurs : **rien**. Le job de sauvegarde `serveur_forgejo` emporte déjà ce dossier ; la +variable `PGSSLROOTCERT` de l'unité systemd était déjà conditionnée au mode TLS ; et P35 +lit désormais l'interrupteur, donc n'attend aucune entrée de registre. + +Une valeur inconnue est **refusée** au début du rôle. Retomber en silence sur PostgreSQL +déploierait le contraire de ce qu'on croyait choisir, et l'écart se lirait au premier +démarrage. + +### Ce que ça donne + +``` +patient 0 : 6 machines → 4 (Dovecot, Redis, PostgreSQL et sa VM) +``` + +Sur la machine dont tout le reste descend, chaque service en moins est une chose de moins +à défendre, à sauvegarder et à rebâtir un soir de reconstruction. Et l'effet dépasse +patient 0 : **une offre `forge` pour un petit organisme cesse d'exiger une VM PostgreSQL.** + +### La neuvième + +En vérifiant P35 sur patient 0, elle a rendu **un verdict juste sur le mauvais +écosystème** : `plan = RACINE / "instance" / "plan"`, le symlink en dur. C'est la +**neuvième** résolution d'instance codée en dur trouvée en cinq jours — après le Makefile, +`verifier_ports`, `verifier_intrants`, `_frontiere_absente`, `devis_placement`… + +À ce stade la conclusion s'impose : ce n'est pas une série de bogues, c'est **une pièce +manquante**. Une résolution unique et partagée, que chaque preuve et chaque devis +appellerait au lieu d'en écrire une copie. À faire de tête reposée, en une fois. + +`make verifier` : 40 OK, 0 échec, 0 sauté. Lint et syntaxe du rôle : verts. + +> **Le doute d'un exploitant vaut une mesure.** Celui-ci a retiré trois services, allégé +> une offre commerciale et révélé une neuvième occurrence d'un défaut de fond — parce +> qu'on est allé regarder au lieu d'argumenter. + + ## 2026-08-21 — L'intuition disait « blockchain » ; la réponse était déjà dans git L'exploitant, en regardant la lignée s'ouvrir : *« j'ai une intuition : blockchain. »* diff --git a/roles/serveur_forgejo/README.md b/roles/serveur_forgejo/README.md index 4b36bef..cbfc635 100644 --- a/roles/serveur_forgejo/README.md +++ b/roles/serveur_forgejo/README.md @@ -62,3 +62,21 @@ des mêmes fichiers est décrite dans [`docs/theme-forgejo-hors-flotte.md`](../. ## Prérequis - Dépendances `serveur_postgresql`, `serveur_nginx`, `serveur_postfix` (cf. `docs/dependances-groupes.yml`). + +## Quelle base de données — `serveur_forgejo_bd` + +| Valeur | Ce que ça donne | +|---|---| +| `postgres` *(défaut)* | la base vit dans le registre du plan, sur un serveur dédié : une VM, une zone, un secret, une sauvegarde | +| `sqlite` | un **fichier** sous `serveur_forgejo_data` — aucun serveur, aucun secret de base, aucune entrée au registre | + +`sqlite` est le défaut documenté de Forgejo pour une petite instance. Il a été ajouté le +2026-08-22 parce qu'une offre `forge` destinée à un petit organisme exigeait une VM +PostgreSQL entière pour une base que trois personnes sollicitent. + +**Ce que ça change ailleurs : rien.** Le job de sauvegarde `serveur_forgejo` emporte déjà +`serveur_forgejo_data`, où le fichier se trouve. Et P35 lit l'interrupteur : elle +n'attend aucune entrée de registre quand la base est un fichier. + +Une valeur inconnue est **refusée** au début du rôle, plutôt que de retomber en silence +sur PostgreSQL — qui déploierait le contraire de ce qu'on croyait choisir. diff --git a/roles/serveur_forgejo/defaults/main.yml b/roles/serveur_forgejo/defaults/main.yml index 3bd2ba4..f642906 100644 --- a/roles/serveur_forgejo/defaults/main.yml +++ b/roles/serveur_forgejo/defaults/main.yml @@ -67,8 +67,28 @@ serveur_forgejo_oidc_repli: >- serveur_forgejo_oidc_discovery: >- {{ resoudre_idp_discovery | default(serveur_forgejo_oidc_repli) }} +# --- QUELLE BASE DE DONNEES --------------------------------------------------- +# +# `postgres` (defaut) : la base vit dans le registre du plan, sur un serveur dedie. +# `sqlite` : un FICHIER dans les donnees de Forgejo, aucun serveur, aucun +# secret de base, aucune VM. C'est le defaut documente de Forgejo +# pour une petite instance, et il tient tres bien quelques dizaines +# d'utilisateurs et des miroirs. +# +# POURQUOI CE CHOIX EXISTE (2026-08-22). Une offre `forge` pour un petit organisme +# exigeait une VM PostgreSQL entiere — un serveur, une zone, un secret, une sauvegarde — +# pour une base que trois personnes sollicitent. Le premier ecosysteme a en avoir profite +# est patient 0, qui porte le genome : moins de surface sur la machine dont tout descend. +# +# CE QUE `sqlite` CHANGE AILLEURS : rien a la sauvegarde — le job `serveur_forgejo` de +# `client_backup` emporte deja `serveur_forgejo_data`, ou le fichier se trouve. Et rien +# au registre : aucune entree de base n'est attendue (P35 le sait). +serveur_forgejo_bd: "postgres" +serveur_forgejo_db_chemin: "{{ serveur_forgejo_data }}/forgejo.db" + # TLS vers PostgreSQL (zero-confiance). 'disable' = compat ; 'verify-full' = chiffre + verifie # le cert serveur contre le root_ca step-ca (via PGSSLROOTCERT dans l'unite systemd). +# Sans objet en `sqlite` : il n'y a pas de connexion reseau a chiffrer. serveur_forgejo_db_sslmode: "disable" serveur_forgejo_db_sslrootcert: "/etc/step/certs/root_ca.crt" diff --git a/roles/serveur_forgejo/tasks/main.yml b/roles/serveur_forgejo/tasks/main.yml index 2fca1d3..9703e35 100644 --- a/roles/serveur_forgejo/tasks/main.yml +++ b/roles/serveur_forgejo/tasks/main.yml @@ -28,11 +28,23 @@ - serveur_forgejo_admin_password | length > 0 fail_msg: "secret_key, internal_token et admin_password sont requis (Ansible Vault)." +- name: Refuser une base de donnees inconnue + # Une valeur mal orthographiee ne doit pas retomber en silence sur le defaut : elle + # ferait deployer PostgreSQL a qui croyait choisir SQLite, et l'ecart se lirait au + # premier demarrage, pas ici. + ansible.builtin.assert: + that: + - serveur_forgejo_bd in ['postgres', 'sqlite'] + fail_msg: >- + serveur_forgejo_bd={{ serveur_forgejo_bd }} : valeurs acceptees `postgres` ou + `sqlite`. + - name: Resoudre la base de donnees depuis le registre (role partage) ansible.builtin.include_role: name: resoudre_base vars: resoudre_base_groupe: "{{ serveur_forgejo_groupe }}" + when: serveur_forgejo_bd == 'postgres' - name: Adopter les facts de base pour Forgejo ansible.builtin.set_fact: @@ -41,6 +53,7 @@ serveur_forgejo_db_host: "{{ resoudre_base_db_host }}" serveur_forgejo_db_port: "{{ resoudre_base_db_port }}" no_log: true + when: serveur_forgejo_bd == 'postgres' - name: Creer l'utilisateur systeme git ansible.builtin.user: diff --git a/roles/serveur_forgejo/templates/app.ini.j2 b/roles/serveur_forgejo/templates/app.ini.j2 index 0585299..45a5cb8 100644 --- a/roles/serveur_forgejo/templates/app.ini.j2 +++ b/roles/serveur_forgejo/templates/app.ini.j2 @@ -12,12 +12,19 @@ HTTP_PORT = {{ serveur_forgejo_http_port }} SSH_DOMAIN = {{ serveur_forgejo_hostname }} [database] +{% if serveur_forgejo_bd == 'sqlite' %} +; Un FICHIER, pas un serveur. Il vit dans les donnees de Forgejo, donc la sauvegarde +; existante l'emporte deja — rien de special a prevoir. +DB_TYPE = sqlite3 +PATH = {{ serveur_forgejo_db_chemin }} +{% else %} DB_TYPE = postgres HOST = {{ serveur_forgejo_db_host }}:{{ serveur_forgejo_db_port | default(5432) }} NAME = {{ serveur_forgejo_entree.base }} USER = {{ serveur_forgejo_entree.proprietaire }} PASSWD = {{ serveur_forgejo_db_password }} SSL_MODE = {{ serveur_forgejo_db_sslmode | default('disable') }} +{% endif %} [security] INSTALL_LOCK = true diff --git a/scripts/prouver.py b/scripts/prouver.py index 16625ec..e368633 100644 --- a/scripts/prouver.py +++ b/scripts/prouver.py @@ -405,6 +405,30 @@ def preuve_inventaire_ansible() -> tuple[bool, str]: return True, f"{n_hotes} hotes, {n_groupes} groupes (inventaire dechiffre et parse)." +def _valeur_effective(cle: str, role: str) -> str | None: + """La valeur d'une variable pour l'instance montee : group_vars d'abord, defaut du role. + + Sert aux preuves qui doivent tenir compte d'un CHOIX de l'instance et non seulement de + ce que le role sait faire. Lecture textuelle des group_vars — aucune voute n'est + dechiffree, aucun Ansible n'est lance : ces preuves doivent rester instantanees. + """ + inv = INSTANCE / "inventories" + if inv.is_dir(): + for f in sorted(inv.glob("*/group_vars/**/*.yml")): + try: + d = yaml.safe_load(f.read_text(encoding="utf-8")) or {} + except (OSError, yaml.YAMLError): + continue + if isinstance(d, dict) and cle in d: + return str(d[cle]) + defauts = RACINE / "roles" / role / "defaults" / "main.yml" + if defauts.is_file(): + d = yaml.safe_load(defauts.read_text(encoding="utf-8")) or {} + if isinstance(d, dict) and cle in d: + return str(d[cle]) + return None + + def preuve_base_par_consommateur() -> tuple[bool, str]: """Toute application deployee dont le role exige une base en a une au plan. @@ -428,7 +452,10 @@ def preuve_base_par_consommateur() -> tuple[bool, str]: CE QU'ELLE NE TESTE PAS : que le secret nomme existe — c'est P18, sur le gabarit de voute. Ni que la base reponde : c'est `make postgresql-plan`, qui interroge le reel. """ - plan = RACINE / "instance" / "plan" + # INSTANCE, pas le symlink en dur : sans cela cette preuve mesurait toujours + # l'instance montee, quelle que soit celle qu'on visait (2026-08-22, en verifiant + # patient 0 — elle a rendu un verdict juste sur le mauvais ecosysteme). + plan = INSTANCE / "plan" if not (plan / "bases-donnees.yml").is_file(): return True, "Aucun registre de bases : rien a verifier." bases = (yaml.safe_load((plan / "bases-donnees.yml").read_text(encoding="utf-8")) @@ -452,6 +479,17 @@ def preuve_base_par_consommateur() -> tuple[bool, str]: exigences[role] = str(val or role) consommateurs = {str((b or {}).get("consommateur")) for b in bases.values()} + # UN ROLE PEUT SAVOIR SE PASSER DE SERVEUR (2026-08-22). `serveur_forgejo` accepte + # `serveur_forgejo_bd: sqlite` : la base devient un fichier, aucune entree de registre + # n'est attendue. La convention est `_bd`, lue dans les group_vars de l'instance + # et, a defaut, dans le defaut du role. Sans cette lecture, la preuve reclamerait une + # base a une instance qui a justement choisi de ne pas en avoir — et on aurait appris + # a l'ignorer. + for role in list(exigences): + val = _valeur_effective(f"{role}_bd", role) + if val == "sqlite": + del exigences[role] + manques: list[str] = [] for nom_app, app in sorted(apps.items()): groupe = str((app or {}).get("groupe") or "") @@ -733,7 +771,7 @@ JARGON_A_ENSEIGNER = [ "LDAP", "SSO", "OIDC", "SAML", "realm", "RBAC", "SMTP", "IMAP", "LMTP", "DKIM", "MTA", # etat et methode - "restic", "devis", "preuve", "harnais", "GUI", "index", "plan", "tenant", + "restic", "devis", "preuve", "harnais", "GUI", "index", "plan", "tenant", "SQLite", # filiation — le vocabulaire arrive le 2026-08-21 avec les signatures "genome", "parente", "commit", "empreinte", "Merkle", "etiquette", "signature", "allowed-signers", "temoin", "journal de transparence", "horodatage", diff --git a/wiki/Glossaire.md b/wiki/Glossaire.md index 5ebed9c..cd76cee 100644 --- a/wiki/Glossaire.md +++ b/wiki/Glossaire.md @@ -283,6 +283,10 @@ règles. Un par écosystème. ## L'état, et sa preuve +**SQLite** — Une base de données qui tient dans **un fichier**, sans serveur ni compte. +Suffisante pour une petite forge ; c'est ce que porte patient 0. À l'inverse de +*PostgreSQL*, qui est un service à part entière — un serveur, une zone, un secret. + **restic** — L'outil de sauvegarde chiffrée et dédupliquée. Cf. [Sauvegardes](Sauvegardes). **Sauvegarde vs snapshot** — L'infrastructure se **reconstruit** depuis le code ; seul