Some checks are pending
verifier / verifier (push) Waiting to run
serveur_backup verifiait pour tout le monde — juste tant que le depot vivait dans l ecosysteme. Depuis qu ils deposent chez leur hebergeur, le site heberge des octets chiffres COTE CLIENT : il ne peut ni les lire ni dire s ils valent quelque chose. La verification revient donc au seul qui detient la cle, le noeud. client_backup verifie SON depot distant — pas le fait d avoir lance sa sauvegarde. Une unite verte sur un depot vide est ce qui a menti un mois. serveur_icinga n exige plus un hote serveur_backup et se branche sur deux modeles : depot local (services sur son hote, nommes sauvegarde: <noeud>) ou pas de depot (services sur chaque noeud, nommes sauvegarde). Le 404 qui n etait pas une absence : les noeuds recevaient No objects found alors que icinga2 object list montrait le service charge. Le filtre du compte d API ne portait que la premiere forme de nom — c est la PERMISSION qui refusait, avec les mots d une absence. Aussi : ingress 5665 depuis client_backup, le pair ne nommait que serveur_backup. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
178 lines
6.1 KiB
YAML
178 lines
6.1 KiB
YAML
---
|
|
- 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') }}
|
|
|
|
- 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
|
|
|
|
# --- 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
|