--- - name: Exiger les secrets de sauvegarde (Vault) ansible.builtin.assert: that: - client_backup_password | length > 0 - client_backup_ssh_privkey | length > 0 fail_msg: "vault_restic_password et vault_backup_ssh_privkey requis." # Un noeud peut legitimement ne rien detenir de non regenerable (nginx, PowerDNS, Icinga : # leur configuration se REDEPLOIE). Le defaut mesure le 2026-08-11 n'etait pas la, il etait # dans l'unite qui MENT : 11 hotes deployaient un timer qui echouait chaque nuit sur # « nothing to backup », invisible depuis le 2026-07-03. Une sauvegarde qui ne sauvegarde # rien est pire que pas de sauvegarde — elle rassure. # # On ne refuse donc pas le deploiement : on refuse d'installer une sauvegarde vide, et on # RETIRE celle qui existerait. Que tout detenteur d'etat porte bien `client_backup` est # lisible dans le plan, donc prouve statiquement (D-75, P36) — pas ici. - name: Refuser une porte d'entree du catalogue devenue fausse ansible.builtin.assert: that: - (client_backup_catalogue.keys() | list | sort) == (client_backup_groupes_etat | sort) fail_msg: >- `client_backup_groupes_etat` ne decrit plus `client_backup_catalogue` : catalogue={{ client_backup_catalogue.keys() | list | sort }}, liste={{ client_backup_groupes_etat | sort }}. La supervision et P36 lisent la liste — la laisser diverger, c'est sauvegarder sans surveiller, ou surveiller ce qui n'existe pas. - name: Etat de la sauvegarde pour ce noeud ansible.builtin.debug: msg: >- {{ (client_backup_jobs | length > 0) | ternary(client_backup_jobs | length | string + ' jeu(x) : ' + (client_backup_jobs | map(attribute='nom') | join(', ')), 'aucun etat non regenerable — aucune sauvegarde installee') }} # L'ACCES AU DEPOT vit a part : les roles proprietaires l'incluent aussi, pour RESTAURER # avant meme que ce role-ci ait tourne (l'AC se restaure dans la couche `pki_racine`). - name: Accès au dépôt (restic, clé, mot de passe, SSH) et outil de restauration ansible.builtin.include_tasks: acces.yml - name: Assurer le répertoire de préparation des dumps ansible.builtin.file: path: "{{ client_backup_staging }}" state: directory owner: root group: root mode: "0700" - name: Déployer le script de sauvegarde when: client_backup_jobs | length > 0 ansible.builtin.template: src: sauvegarder.sh.j2 dest: /usr/local/sbin/setops-sauvegarder.sh owner: root group: root mode: "0700" - name: Déployer l'unité et le timer systemd when: client_backup_jobs | length > 0 ansible.builtin.template: src: "{{ item.s }}" dest: "/etc/systemd/system/{{ item.d }}" owner: root group: root mode: "0644" loop: - { s: setops-sauvegarde.service.j2, d: setops-sauvegarde.service } - { s: setops-sauvegarde.timer.j2, d: setops-sauvegarde.timer } - { s: setops-sauvegarde-avant-raser.service.j2, d: setops-sauvegarde-avant-raser.service } - name: Activer le timer de sauvegarde when: - client_backup_jobs | length > 0 - not ansible_check_mode ansible.builtin.systemd: name: setops-sauvegarde.timer enabled: true state: started daemon_reload: true # --- Retrait d'une sauvegarde vide heritee (le noeud ne detient plus rien) --- - name: Arreter le timer d'une sauvegarde devenue vide when: - client_backup_jobs | length == 0 - not ansible_check_mode ansible.builtin.systemd: name: setops-sauvegarde.timer enabled: false state: stopped failed_when: false # LES VERIFICATIONS PARTENT AVEC LA SAUVEGARDE (2026-09-30). Retirees seules, les unites de # sauvegarde laissaient derriere elles celles qui VERIFIENT le depot et la restauration : # elles continuaient de rapporter a des services qu'Icinga ne declare plus (le noeud n'est # plus detenteur d'etat) — un 404 toutes les quatre heures, et une unite rouge. Vu en # retirant le web frontal du catalogue. - name: Arreter les verifications d'une sauvegarde devenue vide when: - client_backup_jobs | length == 0 - not ansible_check_mode ansible.builtin.systemd: name: "{{ item }}" enabled: false state: stopped loop: - setops-verification-depot.timer - setops-verification-restauration.timer failed_when: false - name: Retirer le script et les unites d'une sauvegarde vide when: client_backup_jobs | length == 0 ansible.builtin.file: path: "{{ item }}" state: absent loop: - /usr/local/sbin/setops-sauvegarder.sh - /etc/systemd/system/setops-sauvegarde.service - /etc/systemd/system/setops-sauvegarde.timer - /etc/systemd/system/setops-sauvegarde-avant-raser.service - /usr/local/sbin/setops-verifier-mon-depot.sh - /usr/local/sbin/setops-verifier-restauration.sh - /etc/systemd/system/setops-verification-depot.service - /etc/systemd/system/setops-verification-depot.timer - /etc/systemd/system/setops-verification-restauration.service - /etc/systemd/system/setops-verification-restauration.timer notify: Recharger systemd # --- LE NOEUD VERIFIE SON PROPRE DEPOT ---------------------------------------- # # Conditionne a l'existence d'un `serveur_icinga` : sans destinataire, le rapport # n'irait nulle part, et un timer qui echoue chaque nuit apprend a ignorer le rouge. # # Et conditionne a `client_backup_jobs` : un noeud qui n'emporte rien n'a pas de depot # a verifier — c'est la meme liste qui decide des deux, pour qu'elles ne divergent pas. - name: Vérifier mon dépôt distant, et le dire à Icinga ansible.builtin.include_tasks: verifier.yml when: - client_backup_jobs | length > 0 - client_backup_icinga_hote | length > 0 # DIRE CE QU'ON NE FAIT PAS. Sans supervision dans l'ecosysteme, la sauvegarde tourne et # personne ne constate jamais qu'elle a abouti. Ce n'est pas une erreur — c'est une dette # qui doit se voir au deploiement plutot que le jour de la restauration. - name: Dire que personne ne constatera cette sauvegarde ansible.builtin.debug: msg: >- Aucun `serveur_icinga` dans cet écosystème : la sauvegarde de {{ inventory_hostname }} tournera sans que personne ne constate qu'elle aboutit. Une unité verte sur un dépôt vide est exactement ce qui a menti pendant un mois. when: - client_backup_jobs | length > 0 - client_backup_icinga_hote | length == 0