Set-OPS-Public/roles/client_backup/tasks/main.yml

153 lines
4.8 KiB
YAML
Raw Normal View History

---
- 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.
supervision : c'est le DEPOT qui dit ou en sont les sauvegardes Icinga ne surveillait rien : aucun objet Host ni Service de Set-OPS, seulement la config Debian d'origine sur localhost. Superviser setops-sauvegarde.service aurait reproduit le defaut du jour meme : l'unite etait VERTE sur onze noeuds pendant qu'elle n'emportait rien. Le noeud sait qu'il a LANCE sa sauvegarde, pas qu'elle est ARRIVEE. backup-01 evalue donc ses depots et pousse un resultat passif par noeud vers l'API Icinga. Trois criteres, parce qu'un seul suffit a mentir : l'instantane existe, il est recent (26 h / 50 h), il contient au moins un fichier. Le sens du flux est delibere : le depot parle a la supervision, jamais l'inverse — compromettre mon-01 ne donne aucun acces aux sauvegardes. Le ttl de 6 h fait la fraicheur : si le rapporteur se tait, Icinga perime les services tout seul. C'est le silence qui a laisse le defaut vivre un mois. Deux erreurs corrigees par la mesure : - --data-urlencode refuse en Bad Request (l'API veut du JSON) ; le flux, le TLS et l'auth marchaient, seule la charge etait perdue. - le seuil « vide » en octets signalait a tort idm-01 (2363 o) : un export LDIF d'un annuaire a un compte pese cela. « Vide » se mesure en FICHIERS. Et le verdict est un AVERTISSEMENT : la machine ne distingue pas « les donnees ont disparu » de « il n'y en a pas encore ». Reserve assumee : curl -k — l'API presente le cert de sa propre AC, pas step-ca. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 21:39:05 -04:00
- 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') }}
- name: Installer restic
ansible.builtin.apt:
name: "{{ client_backup_paquets }}"
state: present
update_cache: true
- name: Créer le répertoire des secrets Set-OPS
ansible.builtin.file:
path: /etc/setops
state: directory
owner: root
group: root
mode: "0700"
- name: Déposer la clé SSH privée de sauvegarde
ansible.builtin.copy:
content: "{{ client_backup_ssh_privkey }}"
dest: "{{ client_backup_ssh_key }}"
owner: root
group: root
mode: "0600"
no_log: true
- name: Déposer le mot de passe restic
ansible.builtin.copy:
content: "{{ client_backup_password }}\n"
dest: /etc/setops/restic.pass
owner: root
group: root
mode: "0600"
no_log: true
- name: Assurer /root/.ssh
ansible.builtin.file:
path: /root/.ssh
state: directory
owner: root
group: root
mode: "0700"
- name: Configurer l'accès SSH à la cible de sauvegarde
ansible.builtin.blockinfile:
path: /root/.ssh/config
create: true
owner: root
group: root
mode: "0600"
marker: "# {mark} SETOPS BACKUP {{ client_backup_cible }}"
block: |
Host {{ client_backup_cible }}
User {{ client_backup_utilisateur_distant }}
IdentityFile {{ client_backup_ssh_key }}
IdentitiesOnly yes
StrictHostKeyChecking accept-new
- 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 }
- 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
- 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
notify: Recharger systemd