--- - name: Installer PostgreSQL ansible.builtin.apt: name: "{{ serveur_postgresql_packages }}" state: present update_cache: true cache_valid_time: 3600 - name: Lister les clusters PostgreSQL installes ansible.builtin.find: paths: /etc/postgresql file_type: directory recurse: false register: serveur_postgresql_clusters - name: Determiner la version du cluster ansible.builtin.set_fact: serveur_postgresql_version_effective: >- {{ serveur_postgresql_version or (serveur_postgresql_clusters.files | map(attribute='path') | map('basename') | select('match', '^[0-9]+$') | sort | last | default('')) }} - name: Refuser si aucun cluster PostgreSQL n'est present ansible.builtin.assert: that: - serveur_postgresql_version_effective | length > 0 fail_msg: "Aucun cluster PostgreSQL trouve sous /etc/postgresql." - name: Definir le repertoire de configuration du cluster ansible.builtin.set_fact: serveur_postgresql_conf_dir: "/etc/postgresql/{{ serveur_postgresql_version_effective }}/main" - name: Assurer le repertoire conf.d ansible.builtin.file: path: "{{ serveur_postgresql_conf_dir }}/conf.d" state: directory owner: postgres group: postgres mode: "0755" # --- TLS : synchroniser le cert step_ca AVANT d'activer ssl (ordre critique) --- - name: TLS — assurer le repertoire des certificats (postgres) ansible.builtin.file: path: "{{ serveur_postgresql_tls_dir }}" state: directory owner: postgres group: postgres mode: "0700" when: serveur_postgresql_tls_actif | bool - name: TLS — deployer le script de synchronisation du certificat ansible.builtin.template: src: setops-postgresql-cert-sync.sh.j2 dest: /usr/local/sbin/setops-postgresql-cert-sync owner: root group: root mode: "0755" when: serveur_postgresql_tls_actif | bool - name: TLS — deployer les unites de synchronisation (service + path) ansible.builtin.template: src: "{{ item.s }}" dest: "{{ item.d }}" owner: root group: root mode: "0644" loop: - { s: "setops-postgresql-cert-sync.service.j2", d: "/etc/systemd/system/setops-postgresql-cert-sync.service" } - { s: "setops-postgresql-cert-sync.path.j2", d: "/etc/systemd/system/setops-postgresql-cert-sync.path" } loop_control: label: "{{ item.d | basename }}" when: serveur_postgresql_tls_actif | bool - name: TLS — activer la surveillance du certificat (path) ansible.builtin.systemd: name: setops-postgresql-cert-sync.path enabled: true state: started daemon_reload: true when: - serveur_postgresql_tls_actif | bool - not ansible_check_mode - name: TLS — synchroniser le certificat maintenant ansible.builtin.command: /usr/local/sbin/setops-postgresql-cert-sync changed_when: false when: - serveur_postgresql_tls_actif | bool - not ansible_check_mode - name: TLS — verifier la presence du certificat synchronise ansible.builtin.stat: path: "{{ serveur_postgresql_tls_dir }}/{{ item }}" register: serveur_postgresql_tls_stat loop: - server.crt - server.key when: - serveur_postgresql_tls_actif | bool - not ansible_check_mode - name: TLS — garde-fou, refuser d'activer ssl sans certificat en place ansible.builtin.assert: that: - item.stat.exists fail_msg: "Certificat PG absent ({{ item.item }}) — ssl non active pour ne rien casser." loop: "{{ serveur_postgresql_tls_stat.results | default([]) }}" loop_control: label: "{{ item.item }}" when: - serveur_postgresql_tls_actif | bool - not ansible_check_mode - name: Deployer la configuration Set-OPS ansible.builtin.template: src: 99-setops.conf.j2 dest: "{{ serveur_postgresql_conf_dir }}/conf.d/99-setops.conf" owner: postgres group: postgres mode: "0640" notify: Redemarrer PostgreSQL - name: Deployer pg_hba.conf ansible.builtin.template: src: pg_hba.conf.j2 dest: "{{ serveur_postgresql_conf_dir }}/pg_hba.conf" owner: postgres group: postgres mode: "0640" notify: Recharger PostgreSQL - name: Activer et demarrer PostgreSQL when: not ansible_check_mode ansible.builtin.systemd: name: "{{ serveur_postgresql_service_name }}" enabled: true state: started - name: Appliquer la configuration en attente avant le provisioning ansible.builtin.meta: flush_handlers - name: Creer les comptes applicatifs PostgreSQL community.postgresql.postgresql_user: name: "{{ item.nom }}" password: "{{ item.mot_de_passe }}" role_attr_flags: "{{ item.attributs | default('LOGIN') }}" state: present become: true become_user: postgres loop: "{{ serveur_postgresql_comptes }}" loop_control: label: "{{ item.nom }}" no_log: true when: serveur_postgresql_comptes | length > 0 - name: Creer les bases applicatives PostgreSQL community.postgresql.postgresql_db: name: "{{ item.nom }}" owner: "{{ item.proprietaire | default(item.nom) }}" encoding: "{{ item.encodage | default('UTF8') }}" state: present become: true become_user: postgres loop: "{{ serveur_postgresql_bases }}" loop_control: label: "{{ item.nom }}" when: serveur_postgresql_bases | length > 0 # --- Provisioning depuis le registre partage instance/plan/bases-donnees.yml --- - name: Charger le registre des bases applicatives ansible.builtin.include_vars: file: "{{ setops_plan_dir }}/bases-donnees.yml" failed_when: false - name: Determiner les serveurs BD nommes heberges par ce groupe ansible.builtin.set_fact: serveur_postgresql_noms_bd: >- {{ (serveurs_bd | default({})) | dict2items | selectattr('value.groupe', 'defined') | selectattr('value.groupe', 'equalto', serveur_postgresql_groupe) | map(attribute='key') | list }} - name: Selectionner les bases hebergees par ce serveur ansible.builtin.set_fact: serveur_postgresql_registre: >- {{ (bases_donnees | default({})) | dict2items | selectattr('value.serveur', 'defined') | selectattr('value.serveur', 'in', serveur_postgresql_noms_bd) | list }} - name: Creer les comptes proprietaires (registre) community.postgresql.postgresql_user: name: "{{ item.value.proprietaire }}" password: "{{ lookup('vars', item.value.secret) }}" role_attr_flags: "LOGIN" state: present become: true become_user: postgres loop: "{{ serveur_postgresql_registre }}" loop_control: label: "{{ item.value.proprietaire }}" no_log: true when: serveur_postgresql_registre | length > 0 - name: Creer les bases applicatives (registre) community.postgresql.postgresql_db: name: "{{ item.value.base }}" owner: "{{ item.value.proprietaire }}" encoding: UTF8 state: present become: true become_user: postgres loop: "{{ serveur_postgresql_registre }}" loop_control: label: "{{ item.value.base }}" when: serveur_postgresql_registre | length > 0 # --- LES BASES D'UNE INCARNATION PRECEDENTE (2026-09-30) ---------------------------- # # ICI, et pas plus tard : les bases viennent d'etre creees, leurs consommateurs (Keycloak, # Forgejo...) ne les ont pas encore touchees. Chaque base VIDE recoit sa section du # `pg_dumpall` d'avant, en une transaction ; une base qui a deja des tables n'est jamais # ecrasee d'office. Les roles et leurs mots de passe restent ceux de la voute : la section # d'une base ne les porte pas. - name: Restauration — quelles bases sont vierges ? ansible.builtin.command: argv: - psql - -tAX - -d - "{{ item }}" - -c - >- select count(*) from information_schema.tables where table_schema not in ('pg_catalog','information_schema') become: true become_user: postgres loop: "{{ serveur_postgresql_bases_restaurables }}" register: serveur_postgresql_tables_initiales changed_when: false check_mode: false failed_when: false - name: Restauration — retenir les bases vierges ansible.builtin.set_fact: serveur_postgresql_bases_vierges: >- {{ serveur_postgresql_tables_initiales.results | selectattr('rc', 'equalto', 0) | selectattr('stdout', 'equalto', '0') | map(attribute='item') | list }} - name: Restauration — les bases d'une incarnation precedente ? ansible.builtin.include_role: name: client_backup tasks_from: restaurer.yml vars: client_backup_restaurer_jeu: serveur_postgresql client_backup_restaurer_vierge: "{{ serveur_postgresql_bases_vierges | length > 0 }}" - name: Restauration — rejouer chaque base vierge changed_when: true ansible.builtin.command: argv: - /usr/local/sbin/setops-restaurer - base - --instantane - "{{ client_backup_restauration.instantane }}" - "{{ item }}" loop: "{{ serveur_postgresql_bases_vierges }}" when: client_backup_restauration.etat == 'a_restaurer' - name: Restauration — acter les bases remises ansible.builtin.include_role: name: client_backup tasks_from: acter.yml vars: client_backup_acter_etat: restaure when: client_backup_restauration.etat == 'a_restaurer' # --- 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 « base » ansible.builtin.template: src: sonde-base.sh.j2 dest: /usr/local/lib/setops/sondes/base.sh owner: root group: root mode: "0750" # --- L'EXPORTATEUR DE METRIQUES ------------------------------------------------------- # # CE QU'IL AJOUTE A LA SONDE : la sonde repond « est-ce casse ? » et ne garde aucune # memoire. L'exportateur publie des SERIES — ce qui derive lentement n'a que le graphe # pour se faire voir. Voir `meta/metriques.yml` pour ce que le moteur en derive. - name: Creer le compte de metriques (lecture seule, pg_monitor) community.postgresql.postgresql_user: name: "{{ serveur_postgresql_exportateur_utilisateur }}" password: "{{ serveur_postgresql_exportateur_mot_de_passe }}" role_attr_flags: "LOGIN" state: present become: true become_user: postgres no_log: true when: serveur_postgresql_exportateur_actif | bool # `pg_monitor` DONNE LES VUES DE STATISTIQUES, ET RIEN D'AUTRE. Pas une ligne de donnee # applicative. C'est la difference entre superviser une base et l'ouvrir. - name: Donner a ce compte le role de supervision, et lui seul community.postgresql.postgresql_membership: group: pg_monitor target_role: "{{ serveur_postgresql_exportateur_utilisateur }}" state: present become: true become_user: postgres when: serveur_postgresql_exportateur_actif | bool - name: Installer l'exportateur de metriques ansible.builtin.apt: name: "{{ serveur_postgresql_exportateur_paquet }}" state: present when: serveur_postgresql_exportateur_actif | bool # LA CHAINE DE CONNEXION NE PASSE PAS PAR LA LIGNE DE COMMANDE. Un `DATA_SOURCE_NAME` en # argument serait lisible dans `ps` par tout le monde sur la machine ; dans un fichier a # 0600, il ne l'est que par root et le service. - name: Poser la chaine de connexion de l'exportateur ansible.builtin.copy: content: | # Gere par Set-OPS (role serveur_postgresql). Ne pas editer a la main. DATA_SOURCE_NAME="postgresql://{{ serveur_postgresql_exportateur_utilisateur }}:{{ serveur_postgresql_exportateur_mot_de_passe }}@127.0.0.1:5432/postgres?sslmode=disable" ARGS="--web.listen-address=:{{ serveur_postgresql_exportateur_port }}" dest: /etc/default/prometheus-postgres-exporter owner: root group: root mode: "0600" no_log: true notify: Redemarrer l exportateur de metriques when: serveur_postgresql_exportateur_actif | bool - name: Activer et demarrer l'exportateur de metriques ansible.builtin.systemd: name: "{{ serveur_postgresql_exportateur_service }}" enabled: true state: started when: serveur_postgresql_exportateur_actif | bool