Prometheus : intersect rend un ENSEMBLE, dont l'ordre d'iteration n'est pas stable d'un processus a l'autre. Le fichier se rendait differemment a chaque passage — memes cibles, ordre different — et le service redemarrait pour rien. Trie sur les hotes. Forgejo : JWT_SECRET est genere par le service et ajoute par lui a la fin d'app.ini. Le gabarit ne le portait pas, donc chaque rendu l'EFFACAIT et Forgejo en generait un nouveau. Ce n'etait pas du bruit : chaque deploiement invalidait les jetons OAuth2 emis par la forge. Le role le relit et le repose ; meme empreinte avant/apres, changed=0 aux 2e et 3e passages. Trois erreurs de methode de ma part dans cette enquete : - conclu « diff vide donc contenu identique » alors que no_log masquait le diff - applique un replace sur le gabarit SANS verifier qu'il avait pris (la section [oauth2] n'existait pas), affiche un succes, et interprete trois passages sur cette base - garde une expression Jinja indiagnosticable en place a cause de no_log Verifier l'effet, pas l'intention — un replace qui ne trouve rien reussit silencieusement, exactement comme kcadm -s sur une map. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
299 lines
12 KiB
YAML
299 lines
12 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).
|
|
- name: Resoudre le fournisseur d'identite (role partage)
|
|
ansible.builtin.include_role:
|
|
name: resoudre_idp
|
|
vars:
|
|
resoudre_idp_realm: "{{ serveur_forgejo_oidc_realm }}"
|
|
- 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: Resoudre la base de donnees depuis le registre (role partage)
|
|
ansible.builtin.include_role:
|
|
name: resoudre_base
|
|
vars:
|
|
resoudre_base_groupe: "{{ serveur_forgejo_groupe }}"
|
|
|
|
- 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
|
|
|
|
- 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.
|
|
- 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
|
|
|
|
- name: Telecharger le binaire Forgejo
|
|
when: not serveur_forgejo_binaire_present.stat.exists
|
|
ansible.builtin.get_url:
|
|
url: "{{ serveur_forgejo_url }}"
|
|
dest: "/usr/local/bin/forgejo-{{ serveur_forgejo_version }}"
|
|
owner: root
|
|
group: root
|
|
mode: "0755"
|
|
|
|
- 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
|
|
|
|
- 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
|
|
|
|
- name: Appliquer les redemarrages Forgejo (config a jour) avant admin/OAuth2
|
|
ansible.builtin.meta: flush_handlers
|
|
|
|
- name: Attendre que Forgejo reponde (migrations terminees)
|
|
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)
|
|
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 }}'
|
|
--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
|
|
|
|
- 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
|