Chaque role proprietaire (AC, bases, annuaire, Nextcloud, rspamd/DKIM, courriel, forge, web) remet son etat au moment ou il le creerait neuf, depuis le dernier instantane anterieur a la naissance de la machine. La sauvegarde refuse de deposer tant qu'un etat d'avant attend. Outil de noeud setops-restaurer ; cibles sauvegarder-maintenant, restauration-etat, restauration-renoncer ; test_restauration. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
624 lines
27 KiB
YAML
624 lines
27 KiB
YAML
---
|
|
|
|
# L'URL PUBLIQUE de l'IdP se derive de l'exposition declaree au plan, elle ne se
|
|
# fabrique pas : `keycloak.<domaine>` n'est publie nulle part (voir resoudre_idp).
|
|
#
|
|
# ON NE RESOUT PAS UN FOURNISSEUR QU'ON N'UTILISERA PAS (2026-08-25).
|
|
#
|
|
# Cette resolution partait INCONDITIONNELLEMENT, y compris quand OIDC est desactive.
|
|
# `resoudre_idp` lit les registres du plan (`applications.yml`, `domaines.yml`) et exige
|
|
# que l'IdP y declare une exposition. Sur une forge SANS Keycloak — celle du site, qui
|
|
# porte le genome et n'a pas d'annuaire — il n'y a ni Keycloak ni plan : le role echouait
|
|
# sur `'setops_plan_dir' is undefined`, un message qui ne dit rien de la vraie cause.
|
|
#
|
|
# `serveur_forgejo_oidc_actif` derive deja de la presence du groupe `serveur_keycloak`.
|
|
# Il suffisait de s'en servir ici aussi : la resolution suit l'usage.
|
|
- name: Resoudre le fournisseur d'identite (role partage)
|
|
ansible.builtin.include_role:
|
|
name: resoudre_idp
|
|
vars:
|
|
resoudre_idp_realm: "{{ serveur_forgejo_oidc_realm }}"
|
|
when: serveur_forgejo_oidc_actif | bool
|
|
- name: Refuser de fermer la connexion locale sur un Forgejo anterieur a la v10
|
|
# `ENABLE_INTERNAL_SIGNIN` a ete ajoute en v10 (suivi amont, issue 7476). Sur une
|
|
# version anterieure il est ignore EN SILENCE : le role croirait avoir ferme la porte
|
|
# alors qu'elle reste ouverte. On refuse plutot que de laisser croire.
|
|
ansible.builtin.assert:
|
|
that:
|
|
- serveur_forgejo_version is version('10.0.0', '>=')
|
|
fail_msg: >-
|
|
serveur_forgejo_connexion_locale=false exige Forgejo >= 10.0.0
|
|
(ici {{ serveur_forgejo_version }}) : le reglage serait ignore sans erreur.
|
|
Mettre a jour, ou assumer la connexion locale en la passant a true.
|
|
when: not (serveur_forgejo_connexion_locale | bool)
|
|
|
|
- name: Exiger les secrets Forgejo (Vault)
|
|
ansible.builtin.assert:
|
|
that:
|
|
- serveur_forgejo_secret_key | length > 0
|
|
- serveur_forgejo_internal_token | length > 0
|
|
- 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:
|
|
serveur_forgejo_entree: "{{ resoudre_base_entree }}"
|
|
serveur_forgejo_db_password: "{{ resoudre_base_db_password }}"
|
|
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:
|
|
name: "{{ serveur_forgejo_utilisateur }}"
|
|
system: true
|
|
shell: /bin/bash
|
|
home: "/home/{{ serveur_forgejo_utilisateur }}"
|
|
create_home: true
|
|
comment: "Git Version Control"
|
|
|
|
# Le chemin de destination PORTE la version : `forgejo-10.0.0` ne peut pas designer un
|
|
# autre contenu demain. Le binaire est donc immuable une fois pose, et le retelecharger
|
|
# n'a aucun sens — or `get_url` sans garde recontacte le serveur distant a CHAQUE
|
|
# deploiement, pour 106 Mo deja presents.
|
|
#
|
|
# Constate le 2026-08-09 pendant le passage d'idempotence : « Connection failure: The
|
|
# read operation timed out ». Un deploiement de flotte echouait parce qu'un serveur tiers
|
|
# etait lent — sur une plateforme qui se veut souveraine, c'est une dependance de trop
|
|
# sur le chemin critique.
|
|
# LE ROLE QUI CREE LE GROUPE REVENDIQUE LA CLE (2026-09-12).
|
|
#
|
|
# `client_pki` (couche `pki_client`) pose les droits de la cle d'hote pour le groupe qui
|
|
# doit la lire — ici `git`. Mais ce groupe est cree JUSTE AU-DESSUS, dans une couche
|
|
# POSTERIEURE. Au premier jour d'un site, l'ordre se mord la queue :
|
|
#
|
|
# client_pki a besoin du groupe git <- cree par ce role
|
|
# forgejo a besoin de la cle lisible <- posee par client_pki
|
|
#
|
|
# Mesure sur deux reconstructions completes : le passage 1 laisse la cle en `root:root`,
|
|
# Forgejo ne demarre pas, et l'attente du premier demarrage coute 300 SECONDES pour rien.
|
|
# Il fallait un passage entier de plus.
|
|
#
|
|
# AUCUN ORDRE DE COUCHES NE DENOUE CE CYCLE — les certificats doivent venir tot, tout le
|
|
# monde en depend. Ce qui se deplace, c'est la PROPRIETE DU GESTE : le role qui cree le
|
|
# groupe est celui qui peut, a cet instant, donner l'acces.
|
|
#
|
|
# `client_pki` garde la sienne et la REPOSE a chaque passage — `step` reecrit la cle a
|
|
# chaque renouvellement, et ne regler les droits qu'ici les perdrait des le premier.
|
|
# Les deux taches se recouvrent, et c'est voulu : l'une amorce, l'autre entretient.
|
|
- name: Donner acces a la cle d'hote au groupe git (amorcage)
|
|
ansible.builtin.file:
|
|
path: "{{ client_pki_cle | default('/etc/step/certs/' ~ (ansible_fqdn | default(ansible_hostname)) ~ '.key') }}"
|
|
owner: root
|
|
group: "{{ serveur_forgejo_utilisateur }}"
|
|
mode: "{{ client_pki_cle_mode | default('0640') }}"
|
|
failed_when: false # pas encore de certificat : `client_pki` passera apres
|
|
|
|
- name: Le binaire de cette version est-il deja pose ?
|
|
ansible.builtin.stat:
|
|
path: "/usr/local/bin/forgejo-{{ serveur_forgejo_version }}"
|
|
register: serveur_forgejo_binaire_present
|
|
|
|
# Le controleur telecharge, puis pousse par SSH — la cible ne tire jamais d'Internet.
|
|
# Motif explique en detail dans `roles/serveur_nextcloud/tasks/installer.yml`. C'est
|
|
# precisement ce binaire qui a fait tomber un deploiement de flotte le 2026-08-09, sur un
|
|
# « read operation timed out » venu d'un serveur tiers.
|
|
- name: Cache d artefacts du contrôleur
|
|
ansible.builtin.file:
|
|
path: "{{ serveur_forgejo_cache_local }}"
|
|
state: directory
|
|
mode: "0700"
|
|
delegate_to: localhost
|
|
become: false
|
|
when: not serveur_forgejo_binaire_present.stat.exists
|
|
|
|
# --- LE DEPOT DU SITE D'ABORD, L'INTERNET ENSUITE (2026-09-12) ----------------
|
|
#
|
|
# Le site tient ces artefacts dans son cache (`serveur_artefacts`, LocalDirs). Un runner
|
|
# qui les y trouve ne sort pas du tout : c'est le meme principe que le cache apt, applique
|
|
# aux publications qui ne vivent dans aucun depot apt.
|
|
#
|
|
# PLACEE AVANT LE `stat` DU CACHE DU CONTROLEUR, ET C'EST TOUT LE MONTAGE : si le depot
|
|
# sert le fichier, le `stat` qui suit le voit, et la tache de telechargement amont se
|
|
# saute d'elle-meme. Aucune tache existante n'a change.
|
|
#
|
|
# `failed_when: false` — le depot est une COMMODITE. Site sans depot, depot qui n'a pas
|
|
# encore cette version, cache du site tout neuf : on retombe sur l'amont, comme avant.
|
|
- name: Prendre le binaire Forgejo au dépôt du site, avant de sortir sur Internet
|
|
ansible.builtin.get_url:
|
|
url: "{{ setops_depot_binaires }}/forgejo-{{ serveur_forgejo_version }}"
|
|
dest: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}"
|
|
mode: "0644"
|
|
timeout: 30
|
|
delegate_to: localhost
|
|
become: false
|
|
failed_when: false
|
|
when:
|
|
- setops_depot_binaires | default('') | length > 0
|
|
- not serveur_forgejo_binaire_present.stat.exists
|
|
|
|
- name: L artefact est-il déjà dans le cache du contrôleur ?
|
|
ansible.builtin.stat:
|
|
path: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}"
|
|
register: serveur_forgejo_cache_present
|
|
delegate_to: localhost
|
|
become: false
|
|
when: not serveur_forgejo_binaire_present.stat.exists
|
|
|
|
# Un serveur tiers intermittent ne doit pas arreter un deploiement de quarante minutes
|
|
# (mesure du 2026-08-23 : la meme URL pend, puis rend 200 en 0,48 s au second essai).
|
|
# Defaut de `get_url` : 10 s et aucune reprise.
|
|
- name: Télécharger le binaire Forgejo dans le cache du contrôleur (une seule fois)
|
|
ansible.builtin.get_url:
|
|
url: "{{ serveur_forgejo_url }}"
|
|
dest: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}"
|
|
mode: "0644"
|
|
timeout: 30
|
|
delegate_to: localhost
|
|
become: false
|
|
when:
|
|
- not serveur_forgejo_binaire_present.stat.exists
|
|
- not (serveur_forgejo_cache_present.stat.exists | default(false))
|
|
register: serveur_forgejo_bin
|
|
retries: 5
|
|
delay: 6
|
|
until: serveur_forgejo_bin is succeeded
|
|
|
|
# VERIFIER LA SIGNATURE AVANT DE POSER UN BINAIRE EXECUTABLE SUR UN HOTE.
|
|
#
|
|
# C'est un binaire, pas une archive de donnees : ce qu'on depose ici s'execute en tant que
|
|
# service. La verification tourne sur le CONTROLEUR, contre la cle versionnee du depot et
|
|
# l'empreinte PRIMAIRE epinglee — voir `defaults/main.yml` pour la raison de la primaire.
|
|
|
|
# Un serveur tiers intermittent ne doit pas arreter un deploiement de quarante minutes
|
|
# (mesure du 2026-08-23 : la meme URL pend, puis rend 200 en 0,48 s au second essai).
|
|
# Defaut de `get_url` : 10 s et aucune reprise.
|
|
# LA SIGNATURE AUSSI VIENT DU DEPOT QUAND IL L'A (2026-09-12). Elle pese 228 octets,
|
|
# mais elle est demandee a CHAQUE passage ou la cible n'a pas encore le binaire : sans
|
|
# elle ici, un ecosysteme entierement servi par le site sortirait quand meme sur Internet
|
|
# pour un quart de kilo-octet. Une sortie reste une sortie.
|
|
- name: Prendre la signature au dépôt du site, avant de sortir sur Internet
|
|
ansible.builtin.get_url:
|
|
url: "{{ setops_depot_binaires }}/forgejo-{{ serveur_forgejo_version }}.asc"
|
|
dest: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}.asc"
|
|
mode: "0644"
|
|
timeout: 30
|
|
force: true
|
|
delegate_to: localhost
|
|
become: false
|
|
failed_when: false
|
|
when:
|
|
- setops_depot_binaires | default('') | length > 0
|
|
- not serveur_forgejo_binaire_present.stat.exists
|
|
|
|
# ON MESURE LE FICHIER, PAS LE VERDICT DE LA TACHE. `failed_when: false` REECRIT le
|
|
# verdict : la tache n'est plus jamais `failed`, donc une garde posee dessus est toujours
|
|
# vraie et ne garde rien. Le depot connait cette marche — elle a deja declare « joignable »
|
|
# un amont mesure muet la seconde d'avant. Ce qui compte ici, c'est qu'un fichier soit la.
|
|
- name: La signature est-elle arrivée dans le cache du contrôleur ?
|
|
ansible.builtin.stat:
|
|
path: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}.asc"
|
|
register: serveur_forgejo_asc_present
|
|
delegate_to: localhost
|
|
become: false
|
|
|
|
- name: Récupérer la signature détachée du binaire
|
|
ansible.builtin.get_url:
|
|
url: "{{ serveur_forgejo_signature_url }}"
|
|
dest: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}.asc"
|
|
mode: "0644"
|
|
timeout: 30
|
|
force: true
|
|
delegate_to: localhost
|
|
become: false
|
|
when:
|
|
- not serveur_forgejo_binaire_present.stat.exists
|
|
# Le depot a servi : ne pas ressortir pour ecraser ce qu'on a deja.
|
|
- not (serveur_forgejo_asc_present.stat.exists and (serveur_forgejo_asc_present.stat.size | default(0)) > 0)
|
|
register: serveur_forgejo_sig
|
|
retries: 5
|
|
delay: 6
|
|
until: serveur_forgejo_sig is succeeded
|
|
|
|
- name: Vérifier la signature du binaire contre l empreinte épinglée
|
|
ansible.builtin.command:
|
|
argv:
|
|
- python3
|
|
- "{{ role_path }}/../../scripts/verifier_signature.py"
|
|
- --artefact
|
|
- "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}"
|
|
- --signature
|
|
- "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}.asc"
|
|
- --cle
|
|
- "{{ role_path }}/files/forgejo-release.asc"
|
|
- --empreinte
|
|
- "{{ serveur_forgejo_signature_empreinte }}"
|
|
delegate_to: localhost
|
|
become: false
|
|
changed_when: false
|
|
when: not serveur_forgejo_binaire_present.stat.exists
|
|
|
|
- name: Déposer le binaire Forgejo sur l hôte depuis le cache
|
|
ansible.builtin.copy:
|
|
src: "{{ serveur_forgejo_cache_local }}/forgejo-{{ serveur_forgejo_version }}"
|
|
dest: "/usr/local/bin/forgejo-{{ serveur_forgejo_version }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
when: not serveur_forgejo_binaire_present.stat.exists
|
|
|
|
- name: Activer la version courante du binaire
|
|
ansible.builtin.file:
|
|
src: "/usr/local/bin/forgejo-{{ serveur_forgejo_version }}"
|
|
dest: "{{ serveur_forgejo_binaire }}"
|
|
state: link
|
|
notify: Redemarrer forgejo
|
|
|
|
# LES DEPOTS D'UNE INCARNATION PRECEDENTE (2026-09-30). Avant que le role ne cree
|
|
# l'arborescence : ensuite, le repertoire ne serait plus vierge. La base, elle, a deja
|
|
# ete remise par `serveur_postgresql` (ou vit dans ces fichiers, en SQLite). La
|
|
# configuration revient avec le secret JWT que le role relit plus bas.
|
|
- name: Restauration — les donnees de Forgejo sont-elles vierges ?
|
|
ansible.builtin.find:
|
|
paths: "{{ serveur_forgejo_data }}"
|
|
file_type: any
|
|
register: serveur_forgejo_data_initial
|
|
|
|
- name: Restauration — serveur_forgejo d'une incarnation precedente ?
|
|
ansible.builtin.include_role:
|
|
name: client_backup
|
|
tasks_from: restaurer.yml
|
|
vars:
|
|
client_backup_restaurer_jeu: serveur_forgejo
|
|
client_backup_restaurer_vierge: "{{ serveur_forgejo_data_initial.matched | default(1) == 0 }}"
|
|
|
|
- name: Restauration — remettre serveur_forgejo
|
|
changed_when: true
|
|
ansible.builtin.command:
|
|
argv: >-
|
|
{{ ['/usr/local/sbin/setops-restaurer', 'fichiers',
|
|
'--instantane', client_backup_restauration.instantane]
|
|
+ client_backup_restauration.chemins }}
|
|
when: client_backup_restauration.etat == 'a_restaurer'
|
|
|
|
# restic rend les proprietaires NUMERIQUES ; `git` n'a pas forcement le meme uid que sur
|
|
# la machine d'avant.
|
|
- name: Restauration — rendre les donnees au service
|
|
ansible.builtin.file:
|
|
path: "{{ item.chemin }}"
|
|
owner: "{{ item.proprietaire }}"
|
|
group: "{{ serveur_forgejo_utilisateur }}"
|
|
recurse: true
|
|
loop:
|
|
- { chemin: "{{ serveur_forgejo_data }}", proprietaire: "{{ serveur_forgejo_utilisateur }}" }
|
|
- { chemin: "{{ serveur_forgejo_config_dir }}", proprietaire: root }
|
|
when: client_backup_restauration.etat == 'a_restaurer'
|
|
|
|
- name: Restauration — acter serveur_forgejo
|
|
ansible.builtin.include_role:
|
|
name: client_backup
|
|
tasks_from: acter.yml
|
|
vars:
|
|
client_backup_acter_etat: restaure
|
|
when: client_backup_restauration.etat == 'a_restaurer'
|
|
|
|
- name: Creer les repertoires de donnees
|
|
ansible.builtin.file:
|
|
path: "{{ serveur_forgejo_data }}/{{ item }}"
|
|
state: directory
|
|
owner: "{{ serveur_forgejo_utilisateur }}"
|
|
group: "{{ serveur_forgejo_utilisateur }}"
|
|
mode: "0750"
|
|
loop:
|
|
- ""
|
|
- custom
|
|
- data
|
|
- log
|
|
|
|
- name: Creer le repertoire de configuration
|
|
ansible.builtin.file:
|
|
path: "{{ serveur_forgejo_config_dir }}"
|
|
state: directory
|
|
owner: root
|
|
group: "{{ serveur_forgejo_utilisateur }}"
|
|
mode: "0770"
|
|
|
|
# JWT_SECRET appartient au SERVICE, pas au depot : Forgejo le genere au premier
|
|
# demarrage et le persiste dans `app.ini`. Le gabarit ne le portait pas — donc chaque
|
|
# rendu l'EFFACAIT, Forgejo en generait un nouveau au redemarrage, et le passage suivant
|
|
# recommencait. Autrement dit : chaque deploiement faisait tourner le secret JWT de la
|
|
# forge, invalidant les jetons qu'elle avait emis. Trouve par le passage d'idempotence du
|
|
# 2026-08-09 — seule ligne differente entre deux rendus.
|
|
#
|
|
# On le RELIT donc avant de rendre, et on le repose tel quel. Le depot cesse de disputer
|
|
# au service une valeur qui lui appartient.
|
|
- name: Relire le secret JWT genere par Forgejo (lui appartient)
|
|
ansible.builtin.slurp:
|
|
src: "{{ serveur_forgejo_config }}"
|
|
register: serveur_forgejo_app_ini
|
|
failed_when: false
|
|
no_log: true
|
|
|
|
- name: Retenir le secret JWT (vide au premier deploiement)
|
|
ansible.builtin.set_fact:
|
|
# Sans groupe de capture : on selectionne la ligne, puis on retire le prefixe. La
|
|
# forme avec `regex_search(..., '\\1', multiline=True)` fonctionnait pourtant en
|
|
# isolation — mais elle echouait sur l'hote, et `no_log` (indispensable ici, c'est un
|
|
# secret) empechait de voir pourquoi. Une expression qu'on ne peut pas diagnostiquer
|
|
# en place doit ceder a une expression plus simple.
|
|
serveur_forgejo_jwt_secret: >-
|
|
{{ (serveur_forgejo_app_ini.content | default('') | b64decode).splitlines()
|
|
| select('match', '^JWT_SECRET *=')
|
|
| map('regex_replace', '^JWT_SECRET *= *', '')
|
|
| first | default('', true) | trim }}
|
|
no_log: true
|
|
|
|
- name: Deployer app.ini
|
|
ansible.builtin.template:
|
|
src: app.ini.j2
|
|
dest: "{{ serveur_forgejo_config }}"
|
|
owner: "{{ serveur_forgejo_utilisateur }}" # Forgejo persiste des secrets générés (oauth2 JWT) → doit pouvoir écrire
|
|
group: "{{ serveur_forgejo_utilisateur }}"
|
|
# 0600, et non 0640 : Forgejo REECRIT ce fichier lui-meme (il y persiste des secrets
|
|
# generes) et le repose systematiquement en 0600. Le role remettait 0640 a chaque
|
|
# passage, Forgejo le ramenait a 0600 — deux proprietaires pour un fichier, en
|
|
# desaccord, et un `changed` perpetuel qui redemarrait le service pour rien.
|
|
# Trouve par le passage d'idempotence du 2026-08-09 : contenu identique, diff VIDE,
|
|
# seul le mode differait — c'est ce qui rendait la cause invisible.
|
|
#
|
|
# Le desaccord n'avait aucun effet utile : le groupe est `git`, c'est-a-dire le
|
|
# service lui-meme. On s'aligne sur le plus strict, qui est aussi le sien.
|
|
mode: "0600"
|
|
no_log: true
|
|
notify: Redemarrer forgejo
|
|
|
|
- name: Deployer l'identite visuelle (logo, favicon, accent aurore, accueil)
|
|
ansible.builtin.copy:
|
|
src: custom/
|
|
dest: "{{ serveur_forgejo_data }}/custom/"
|
|
owner: "{{ serveur_forgejo_utilisateur }}"
|
|
group: "{{ serveur_forgejo_utilisateur }}"
|
|
mode: "0644"
|
|
directory_mode: "0755"
|
|
when: serveur_forgejo_branding | bool
|
|
notify: Redemarrer forgejo
|
|
|
|
- name: Deployer l'unite systemd forgejo
|
|
ansible.builtin.template:
|
|
src: forgejo.service.j2
|
|
dest: /etc/systemd/system/forgejo.service
|
|
owner: root
|
|
group: root
|
|
mode: "0644"
|
|
notify: Redemarrer forgejo
|
|
|
|
- name: Activer et demarrer Forgejo
|
|
when: not ansible_check_mode
|
|
ansible.builtin.systemd:
|
|
name: "{{ serveur_forgejo_service }}"
|
|
enabled: true
|
|
state: started
|
|
daemon_reload: true
|
|
|
|
# LAISSER LE PREMIER DEMARRAGE FINIR AVANT DE L'INTERROMPRE (2026-09-02).
|
|
#
|
|
# Au TOUT PREMIER demarrage, Forgejo cree son schema et deroule ses migrations — plusieurs
|
|
# dizaines de secondes. Le `flush_handlers` ci-dessous le REDEMARRE parce qu'`app.ini`
|
|
# vient de changer. Redemarre au milieu, il laisse une base a moitie faite : la version
|
|
# cible est deja inscrite dans `version`, le schema ne l'est pas.
|
|
#
|
|
# Il ne s'en releve jamais seul. La migration suivante cherche une table qui n'a jamais
|
|
# ete creee et echoue en boucle :
|
|
#
|
|
# migration[v14a_add-foreign-keys-collaboration] ... failure to delete inconsistent
|
|
# records before foreign key sync: la relation « collaboration » n'existe pas
|
|
#
|
|
# ORM engine initialization attempt #1/10, #2/10... indefiniment. Le service reste
|
|
# `active` — il n'a pas plante, il reessaie — et n'ecoute jamais son port.
|
|
#
|
|
# CE QUI RENDAIT CE DEFAUT INVISIBLE : aux deploiements SUIVANTS la base est deja migree,
|
|
# le premier demarrage est instantane, et le redemarrage ne tombe au milieu de rien. Il
|
|
# ne se manifeste que sur une base VIERGE — donc a la reconstruction, et pas a chaque
|
|
# fois : c'est une course. Il a fallu deux reconstructions completes pour le voir.
|
|
- name: Attendre la fin du PREMIER demarrage avant tout redemarrage
|
|
ansible.builtin.wait_for:
|
|
host: 127.0.0.1
|
|
port: "{{ serveur_forgejo_http_port }}"
|
|
timeout: 300
|
|
when: not ansible_check_mode
|
|
|
|
- name: Appliquer les redemarrages Forgejo (config a jour) avant admin/OAuth2
|
|
ansible.builtin.meta: flush_handlers
|
|
|
|
- name: Attendre que Forgejo reponde (apres redemarrage)
|
|
ansible.builtin.wait_for:
|
|
host: 127.0.0.1
|
|
port: "{{ serveur_forgejo_http_port }}"
|
|
timeout: 120
|
|
when: not ansible_check_mode
|
|
|
|
- name: Creer le compte administrateur (une fois)
|
|
# `--must-change-password=false` : SANS LUI, LE COMPTE EST INUTILISABLE (2026-08-23,
|
|
# premiere forge d'un ecosysteme). Forgejo exige par defaut un changement de mot de passe
|
|
# au premier acces, et refuse TOUTE requete d'API tant qu'il n'a pas eu lieu :
|
|
# « You must change your password ». Or ce role desactive la connexion locale
|
|
# (`serveur_forgejo_connexion_locale: false`, SSO d'abord) — il n'existait donc aucun
|
|
# chemin pour effectuer ce changement. Le compte de secours ne secourait rien.
|
|
#
|
|
# Le mot de passe vient de la voute et tourne par empreinte (voir plus bas) : exiger un
|
|
# changement manuel en plus ferait diverger la voute de la realite, ce que ce role
|
|
# s'emploie justement a empecher.
|
|
ansible.builtin.shell:
|
|
cmd: >-
|
|
{{ serveur_forgejo_binaire }} admin user create
|
|
--admin --username {{ serveur_forgejo_admin_user }}
|
|
--email {{ serveur_forgejo_admin_email }}
|
|
--password '{{ serveur_forgejo_admin_password }}'
|
|
--must-change-password=false
|
|
--config {{ serveur_forgejo_config }}
|
|
&& touch {{ serveur_forgejo_data }}/.admin-created
|
|
creates: "{{ serveur_forgejo_data }}/.admin-created"
|
|
become: true
|
|
become_user: "{{ serveur_forgejo_utilisateur }}"
|
|
environment:
|
|
GITEA_WORK_DIR: "{{ serveur_forgejo_data }}"
|
|
no_log: true
|
|
|
|
# ON VERIFIE L'ETAT, ON NE FAIT PAS CONFIANCE AU DRAPEAU (2026-08-25).
|
|
#
|
|
# La creation passe deja `--must-change-password=false` — corrige le 2026-08-23, sur la
|
|
# premiere forge d'un ecosysteme. **Forgejo 16 l'a ignore** : le compte de la forge du SITE
|
|
# est ne avec le drapeau POSE malgre l'option.
|
|
#
|
|
# La panne qui en resulte n'accuse rien de juste : toute requete d'API rend **403**, pas
|
|
# 401. Le jeton est valide, l'identite reconnue, la requete rejetee quand meme — on
|
|
# cherche un scope, un droit, une politique, et la cause est un drapeau sur le compte.
|
|
#
|
|
# D'ou cette tache : elle ne demande pas a la creation de bien se comporter, elle CONSTATE
|
|
# l'etat voulu et le retablit. Idempotente, et sans effet quand la creation a tenu parole.
|
|
- name: Le compte d'administration ne doit exiger aucun changement de mot de passe
|
|
ansible.builtin.command:
|
|
cmd: >-
|
|
{{ serveur_forgejo_binaire }} admin user must-change-password --unset
|
|
{{ serveur_forgejo_admin_user }}
|
|
--config {{ serveur_forgejo_config }}
|
|
become: true
|
|
become_user: "{{ serveur_forgejo_utilisateur }}"
|
|
environment:
|
|
FORGEJO_WORK_DIR: "{{ serveur_forgejo_data }}"
|
|
register: forgejo_mcp
|
|
changed_when: "'Updated 1 users' in (forgejo_mcp.stdout | default(''))"
|
|
failed_when:
|
|
- forgejo_mcp.rc != 0
|
|
- "'not found' not in (forgejo_mcp.stderr | default(''))"
|
|
|
|
- name: Configurer la source OAuth2 SSO (Keycloak, idempotent)
|
|
ansible.builtin.shell:
|
|
executable: /bin/bash
|
|
cmd: |
|
|
set -euo pipefail
|
|
FJ="{{ serveur_forgejo_binaire }} --config {{ serveur_forgejo_config }}"
|
|
# Les reglages de GROUPE sont RECONCILIES, pas seulement poses a la creation :
|
|
# `add-oauth` une fois puis plus rien laissait un cablage perime vivre
|
|
# indefiniment — c'est ce qui est arrive a la federation LDAP de Keycloak, qui
|
|
# a pointe un hote inexistant pendant des semaines (2026-08-07).
|
|
OPTS_GROUPE=""
|
|
{% if serveur_forgejo_oidc_groupe_admin | length > 0 %}
|
|
OPTS_GROUPE="--group-claim-name {{ serveur_forgejo_oidc_groupe_claim }} --admin-group {{ serveur_forgejo_oidc_groupe_admin }}"
|
|
{% endif %}
|
|
ID=$($FJ admin auth list 2>/dev/null \
|
|
| awk -v n="{{ serveur_forgejo_oidc_nom }}" '$2 == n {print $1}' | head -1)
|
|
if [ -z "$ID" ]; then
|
|
$FJ admin auth add-oauth \
|
|
--name "{{ serveur_forgejo_oidc_nom }}" \
|
|
--provider openidConnect \
|
|
--key "{{ serveur_forgejo_oidc_client_id }}" \
|
|
--secret "$OIDC_SECRET" \
|
|
--auto-discover-url "{{ serveur_forgejo_oidc_discovery }}" \
|
|
$OPTS_GROUPE >/dev/null
|
|
echo SETOPS_CHANGED
|
|
else
|
|
# `update-oauth` est idempotent cote Forgejo : il reecrit les memes valeurs
|
|
# sans effet de bord. On ne peut pas comparer avant/apres (la CLI n'expose
|
|
# pas le detail d'une source), donc on ne signale PAS `changed`.
|
|
# `--secret` fait partie de la reconciliation : sans lui, faire tourner
|
|
# `vault_forgejo_oidc` laissait Forgejo sur l'ancienne valeur.
|
|
$FJ admin auth update-oauth --id "$ID" \
|
|
--auto-discover-url "{{ serveur_forgejo_oidc_discovery }}" \
|
|
--secret "$OIDC_SECRET" \
|
|
$OPTS_GROUPE >/dev/null
|
|
echo SETOPS_OK
|
|
fi
|
|
environment:
|
|
OIDC_SECRET: "{{ serveur_forgejo_oidc_client_secret }}"
|
|
GITEA_WORK_DIR: "{{ serveur_forgejo_data }}"
|
|
become: true
|
|
become_user: "{{ serveur_forgejo_utilisateur }}"
|
|
register: serveur_forgejo_oauth
|
|
changed_when: "'SETOPS_CHANGED' in serveur_forgejo_oauth.stdout"
|
|
no_log: true
|
|
when:
|
|
- serveur_forgejo_oidc_actif | bool
|
|
- not ansible_check_mode
|
|
|
|
# --- ROTATION DU COMPTE DE SECOURS -------------------------------------------
|
|
# `admin user create` porte `creates: .admin-created` : il ne tourne qu'UNE fois.
|
|
# Changer la voute ne changeait donc rien, et la voute se mettait a mentir. Meme
|
|
# raisonnement que pour Grafana — idempotence par EMPREINTE du secret applique.
|
|
- name: Empreinte du mot de passe admin en place
|
|
ansible.builtin.slurp:
|
|
src: "{{ serveur_forgejo_marqueur_admin }}"
|
|
register: serveur_forgejo_marque
|
|
failed_when: false
|
|
changed_when: false
|
|
no_log: true
|
|
|
|
- name: Appliquer le mot de passe du compte de secours (rotation)
|
|
ansible.builtin.command:
|
|
argv:
|
|
- "{{ serveur_forgejo_binaire }}"
|
|
- "--config"
|
|
- "{{ serveur_forgejo_config }}"
|
|
- admin
|
|
- user
|
|
- change-password
|
|
- "--username"
|
|
- "{{ serveur_forgejo_admin_user }}"
|
|
- "--password"
|
|
- "{{ serveur_forgejo_admin_password }}"
|
|
environment:
|
|
GITEA_WORK_DIR: "{{ serveur_forgejo_data }}"
|
|
become: true
|
|
become_user: "{{ serveur_forgejo_utilisateur }}"
|
|
changed_when: true
|
|
no_log: true
|
|
when: >-
|
|
(serveur_forgejo_marque.content | default('') | b64decode | trim)
|
|
!= (serveur_forgejo_admin_password | hash('sha256'))
|
|
|
|
- name: Retenir l'empreinte appliquee
|
|
ansible.builtin.copy:
|
|
content: "{{ serveur_forgejo_admin_password | hash('sha256') }}\n"
|
|
dest: "{{ serveur_forgejo_marqueur_admin }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0600"
|
|
no_log: true
|
|
|
|
# --- Sonde de supervision (docs/supervision-conception.md) --------------------------
|
|
# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la
|
|
# fait tourner et pousse le verdict, sans savoir ce qu'elle mesure.
|
|
- name: Assurer le repertoire des sondes de supervision
|
|
ansible.builtin.file:
|
|
path: /usr/local/lib/setops/sondes
|
|
state: directory
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- name: Deposer la sonde « forge »
|
|
ansible.builtin.template:
|
|
src: sonde-forge.sh.j2
|
|
dest: /usr/local/lib/setops/sondes/forge.sh
|
|
owner: root
|
|
group: root
|
|
mode: "0750"
|