forgejo : apprendre SQLite, et retirer ce qui ne servait pas
Some checks are pending
verifier / verifier (push) Waiting to run
Some checks are pending
verifier / verifier (push) Waiting to run
Doute de l'exploitant sur patient 0 : « je doute de la pertinence de pgsql ». Mesure plutot que discussion. REDIS NE SERVAIT A RIEN : le role serveur_forgejo ne le mentionne ni dans son app.ini, ni dans ses defauts, et ne declare aucun lien. Heritage du modele `forge`. Retire du plan. POSTGRESQL ETAIT EXIGE PAR LE ROLE : DB_TYPE = postgres en dur, resoudre_base sans condition. Le doute etait fonde, le moteur ne savait pas faire autrement. INTERRUPTEUR `serveur_forgejo_bd: postgres|sqlite`. En sqlite la base devient un FICHIER sous serveur_forgejo_data. Ce que ca change ailleurs : rien. Le job de sauvegarde `serveur_forgejo` emporte deja ce dossier ; PGSSLROOTCERT etait deja conditionne au mode TLS ; et P35 lit desormais l'interrupteur (convention `<role>_bd`, group_vars de l'instance puis defaut du role), donc n'attend aucune entree de registre. Une valeur inconnue est REFUSEE au debut du role plutot que de retomber en silence sur PostgreSQL. PATIENT 0 PASSE DE SIX A QUATRE MACHINES (Dovecot, Redis, PostgreSQL et sa VM). Sur la machine dont tout descend, chaque service en moins est une chose de moins a defendre, a sauvegarder et a rebatir. Et l'effet depasse patient 0 : une offre `forge` pour un petit organisme cesse d'exiger une VM PostgreSQL. LA NEUVIEME. En verifiant P35 sur patient 0, elle a rendu un verdict JUSTE SUR LE MAUVAIS ECOSYSTEME : `plan = RACINE / "instance" / "plan"`, le symlink en dur. Neuvieme resolution d'instance codee en dur en cinq jours. Ce n'est plus une serie de bogues, c'est une piece manquante : une resolution unique et partagee, a faire en une fois et de tete reposee. Enseigne : SQLite au glossaire (P39 l'exige desormais), et le README du role documente l'interrupteur et ce qu'il ne change pas. make verifier 40/40 ; make ci 40/40 ; lint et syntaxe du role verts. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
79461fdc38
commit
bd1b897815
7 changed files with 158 additions and 2 deletions
56
CHANGELOG.md
56
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. »*
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 `<role>_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",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue