--- - name: Installer PowerDNS Authoritative ansible.builtin.apt: name: "{{ serveur_powerdns_packages }}" state: present update_cache: true cache_valid_time: 3600 - name: Créer le répertoire des zones PowerDNS ansible.builtin.file: path: "{{ serveur_powerdns_zone_directory }}" state: directory owner: root group: root mode: "0755" - name: Déployer la configuration du backend BIND ansible.builtin.template: src: setops-bind.conf.j2 dest: /etc/powerdns/pdns.d/setops-bind.conf owner: root group: root mode: "0644" notify: Validate and reload PowerDNS - name: Déployer l'index des zones PowerDNS ansible.builtin.template: src: bindbackend.conf.j2 dest: "{{ serveur_powerdns_bind_config }}" owner: root group: root mode: "0644" notify: Validate and reload PowerDNS # CHAQUE REGISTRE EST CHARGE POUR LUI-MEME (2026-08-25). # # Ce `include_vars` listait les deux fichiers en dur et echouait si l'un manquait. # `domaines.yml` declare les domaines PUBLICS, et un ecosysteme peut legitimement n'en # avoir aucun — c'est le cas du SITE, dont tous les services sont internes. # # Le defaut est reste invisible tant que le site gardait `publier_expositions: false` ; # il est apparu a la seconde ou on a voulu publier ses noms de service. C'est le MEME # defaut que `hosts_statiques` a corrige le meme jour, dans les memes termes : un registre # absent n'est pas une faute, il est simplement absent. - name: Vérifier la présence des registres du plan (sur le nœud de contrôle) ansible.builtin.stat: path: "{{ item }}" register: serveur_powerdns_plan delegate_to: localhost # les registres du plan vivent sur le nœud de contrôle become: false # lire un fichier ne nécessite pas de privilèges loop: - "{{ setops_plan_dir }}/applications.yml" - "{{ setops_plan_dir }}/domaines.yml" loop_control: label: "{{ item | basename }}" when: serveur_powerdns_publier_expositions | bool - name: Charger les registres applications et domaines (si présents) ansible.builtin.include_vars: file: "{{ item.item }}" loop: "{{ serveur_powerdns_plan.results | default([]) }}" loop_control: label: "{{ item.item | basename }}" when: - serveur_powerdns_publier_expositions | bool - item.stat.exists | default(false) - name: Dériver les A d'exposition (FQDN publics -> edge qui les sert) ansible.builtin.set_fact: serveur_powerdns_expositions: >- {{ {'applications': applications | default({})} | expositions_des_applications({'domaines_publics': domaines_publics | default({})}) }} when: serveur_powerdns_publier_expositions | bool - name: Générer la zone DNS interne depuis l'inventaire ansible.builtin.template: src: zone.db.j2 dest: "{{ serveur_powerdns_zone_directory }}/{{ serveur_powerdns_zone }}.zone" owner: root group: root mode: "0644" notify: Validate and reload PowerDNS # Meme source que la zone directe : `hotes_actifs` et son `ansible_host`. Deux zones # alimentees par une seule verite — l'endroit ou elles divergeraient n'existe pas. - name: Générer les zones DNS inverses depuis l'inventaire ansible.builtin.template: src: zone-inverse.db.j2 dest: "{{ serveur_powerdns_zone_directory }}/{{ zone_inverse }}.zone" owner: root group: root mode: "0644" loop: "{{ serveur_powerdns_zones_inverses | default([]) }}" loop_control: loop_var: zone_inverse notify: Validate and reload PowerDNS # UNE ZONE INVERSE QUI N'EST PLUS REVENDIQUEE DOIT DISPARAITRE. Sans ce retrait, un # ecosysteme qui abandonne un reseau laisserait derriere lui un fichier que le backend ne # declare plus mais que le repertoire garde — et le prochain lecteur croirait la zone # servie. - name: Relever les zones inverses présentes sur la machine ansible.builtin.find: paths: "{{ serveur_powerdns_zone_directory }}" patterns: "*.in-addr.arpa.zone" register: serveur_powerdns_inverses_presentes - name: Retirer les zones inverses que l'écosystème ne revendique plus ansible.builtin.file: path: "{{ item.path }}" state: absent loop: "{{ serveur_powerdns_inverses_presentes.files | default([]) }}" loop_control: label: "{{ item.path | basename }}" when: >- (item.path | basename | regex_replace('\.zone$', '')) not in (serveur_powerdns_zones_inverses | default([])) notify: Validate and reload PowerDNS # LES ZONES PUBLIQUES, DANS LEUR PROPRE INSTANCE — seulement si le site a un serveur # public pour les tirer. Sans lui, publier une zone n'aurait personne pour la servir. - name: Publier les zones primaire-cache ansible.builtin.include_tasks: zones-publiques.yml when: serveur_powerdns_dns_public_site | length > 0 - name: Activer et démarrer PowerDNS ansible.builtin.systemd: name: "{{ serveur_powerdns_service_name }}" enabled: true state: started # Dry-run (--check) : le paquet n'est pas reellement installe, donc le service # n'existe pas encore et systemd echouerait. On saute en mode check seulement ; # en vrai deploiement, la tache s'execute normalement. when: not ansible_check_mode # LE FICHIER SUR DISQUE N'EST PAS LA ZONE SERVIE (2026-09-29). Le rechargement passait # seulement par un handler. Or un handler en attente est ABANDONNE si une tache echoue plus # loin dans le meme passage — et aux passages suivants le fichier ne change plus, donc plus # rien ne le redemande. Constate chez les deux locataires : zone reecrite le 16 septembre # (ajout de `console`), PowerDNS la servait telle que chargee le 13 ou le 14. Treize jours # de NXDOMAIN sur un nom que le fichier portait, et aucune sonde ne le voyait. # # On compare donc a chaque passage, zone par zone, la date du fichier a celle ou PowerDNS # l'a chargee, et on recharge ce qui est en retard. Ce qui est juste n'est pas touche. - name: Recharger toute zone dont le fichier est plus recent que la version servie ansible.builtin.shell: executable: /bin/bash cmd: | set -uo pipefail verifier() { # $1 = option d'instance ("" ou --config-name=public), $2 = repertoire local f z charge for f in "$2"/*.zone; do [[ -e "${f}" ]] || continue z=$(basename "${f}" .zone) charge=$(pdns_control $1 bind-domain-status "${z}" 2>/dev/null \ | grep -oP 'parsed into memory at \K.*') if [[ -z "${charge}" ]] || (( $(date -r "${f}" +%s) > $(date -d "${charge}" +%s) )); then pdns_control $1 bind-reload-now "${z}" >/dev/null && echo "RECHARGEE ${z}" fi done } verifier "" "{{ serveur_powerdns_zone_directory }}" {% if serveur_powerdns_dns_public_site | length > 0 %} verifier "--config-name=public" "{{ serveur_powerdns_public_zones_dir }}" {% endif %} register: serveur_powerdns_reconciliation changed_when: "'RECHARGEE' in serveur_powerdns_reconciliation.stdout" when: not ansible_check_mode # --- 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 « zones » ansible.builtin.template: src: sonde-zones.sh.j2 dest: /usr/local/lib/setops/sondes/zones.sh owner: root group: root mode: "0750"