--- # Sauvegarde applicative (logique) d'un nœud vers la cible restic hors-nœud. # Chaque nœud déclare ses "jobs" ; restic chiffre côté client + applique la rétention. client_backup_paquets: - restic # `setops-restaurer` remet les repertoires par `rsync --delete` (absent du gabarit). - rsync # Cible (nœud serveur_backup) + transport SSH. client_backup_cible: "backup-01.{{ domaine_interne }}" client_backup_utilisateur_distant: "restic" client_backup_ssh_key: "/etc/setops/backup_ed25519" # Dépôt restic (un sous-dossier par nœud, relatif au home de l'utilisateur distant). client_backup_repo: "sftp:{{ client_backup_utilisateur_distant }}@{{ client_backup_cible }}:{{ inventory_hostname }}" client_backup_password: "{{ vault_restic_password | default('') }}" client_backup_ssh_privkey: "{{ vault_backup_ssh_privkey | default('') }}" # Dossier de préparation des dumps (pg_dump, slapcat, forgejo dump...). client_backup_staging: "/var/backups/setops" # Rétention (restic forget). client_backup_retention: "--keep-daily 7 --keep-weekly 4 --keep-monthly 6" # L'instantane d'avant rasage (`make sauvegarder-maintenant`) porte cette etiquette, et la # retention la garde EN PLUS de la regle ci-dessus : jusqu'a la reconstruction suivante # (decision de l'exploitant, 2026-10-07). Voir `templates/sauvegarder.sh.j2`. client_backup_etiquette_avant_raser: "avant-raser" # Planification (timer systemd). client_backup_horaire: "*-*-* 02:30:00" # Jobs déclaratifs. Chaque job : # nom : identifiant # commande : (optionnel) dump écrivant dans {{ staging }}/{{ nom }} # chemins : liste de chemins à inclure dans le snapshot restic # # CATALOGUE PAR GROUPE — c'est le rôle qui POSSEDE la donnée qui dit comment la sortir. # On sauvegarde l'ETAT NON REGENERABLE, pas ce que le code reconstruit : ni les zones # PowerDNS ni les tableaux de bord Grafana ne figurent ici, ils se redéploient. # # Les chemins reprennent les defauts du role proprietaire, mais NE PEUVENT PAS y faire # reference : `make deployer` deroule un play par groupe, et les defaults de # `serveur_forgejo` ne sont pas charges pendant le play de `client_backup`. D'ou la forme # `var | default(litteral)` — la variable gagne si l'inventaire la definit, sinon le # defaut du role proprietaire, recopie ici et a garder aligne avec lui. client_backup_catalogue: serveur_step_ca: nom: step_ca chemins: - "{{ serveur_step_ca_steppath | default('/etc/step-ca') }}" serveur_openldap: nom: openldap # slapcat lit la base a plat : pas d'authentification, coherent meme slapd arrete. commande: >- slapcat -b '{{ serveur_openldap_base_dn | default("dc=" + domaine_interne.split(".") | join(",dc=")) }}' > {{ client_backup_staging }}/openldap/annuaire.ldif chemins: ["{{ client_backup_staging }}/openldap"] serveur_postgresql: nom: postgresql # pg_dumpall : TOUTES les bases + les roles et leurs mots de passe. Une base oubliee # ici serait une base perdue — d'ou le dump global plutot qu'une liste a maintenir. commande: >- runuser -u postgres -- pg_dumpall --clean > {{ client_backup_staging }}/postgresql/toutes-bases.sql chemins: ["{{ client_backup_staging }}/postgresql"] serveur_dovecot: nom: courriel chemins: ["{{ serveur_dovecot_vmail_base | default('/var/vmail') }}"] serveur_forgejo: nom: forgejo chemins: - "{{ serveur_forgejo_data | default('/var/lib/forgejo') }}" - "{{ serveur_forgejo_config_dir | default('/etc/forgejo') }}" serveur_nextcloud: nom: nextcloud chemins: - "{{ serveur_nextcloud_data_dir | default('/var/www/nextcloud/data') }}" - "{{ (serveur_nextcloud_racine | default('/var/www/nextcloud')) + '/config' }}" serveur_rspamd: # Le bayes APPRIS est de l'etat : reconstruire le noeud reinstalle rspamd, pas ce # qu'il a appris. Les regles, elles, se redeploient et ne sont pas ici. nom: rspamd chemins: ["/var/lib/rspamd"] # PAS DE `serveur_web_frontal` (retire le 2026-09-30) : le frontal RELAIE, il ne sert # rien lui-meme — ses vhosts, son WAF et sa page 404 se redeploient depuis le depot. # `/srv/web` venait de l'epoque ou il servait du statique ; ses instantanes etaient vides. serveur_icinga: # L'AC D'ICINGA FAIT L'IDENTITE DE SA BASE (2026-10-07). Icinga 2 derive l'environnement # d'Icinga DB de la cle publique de son AC, et chaque identifiant d'hote, de service et # d'historique de cet environnement. Recreee a chaque reconstruction, elle rendait # l'historique restaure orphelin : on l'avait donc jete (decision du 2026-09-30, revue # le 2026-10-07 — l'historique, c'est la disponibilite qu'on doit pouvoir prouver). nom: icinga chemins: - "{{ (serveur_icinga_ca | default('/var/lib/icinga2/ca/ca.crt')) | dirname }}" - /var/lib/icinga2/icingadb.env serveur_web_dorsal: nom: web_dorsal chemins: ["{{ serveur_web_dorsal_racine | default('/srv/webapp') }}"] # Jeux retenus pour CE noeud : intersection du catalogue et de ses groupes. Derive, donc # un tenant qui deplace un service emporte sa sauvegarde avec lui, sans rien re-declarer. client_backup_jobs: >- {{ client_backup_catalogue | dict2items | selectattr('key', 'in', group_names) | map(attribute='value') | list }} # La liste EN CLAIR des memes groupes vit dans `vars/main.yml` — sans Jinja, pour que la # supervision et P36 puissent la lire par `include_vars` sans rendre ce catalogue. # --- LE NOEUD VERIFIE SON PROPRE DEPOT ---------------------------------------- # # POURQUOI ICI, ALORS QUE `serveur_backup` LE FAISAIT DEJA. # # Le depot etait le seul a voir ce qui etait REELLEMENT arrive : un noeud sait qu'il a # lance sa sauvegarde, il ne sait pas qu'elle a abouti. C'etait juste tant que le depot # appartenait au meme ecosysteme. # # Depuis que les ecosystemes deposent chez leur HEBERGEUR, ca ne l'est plus : le site # heberge des octets chiffres par restic COTE CLIENT, avec un mot de passe qui ne quitte # pas la voute du locataire. Il ne peut ni les lire, ni les ouvrir, ni dire s'ils valent # quelque chose. C'est la propriete qui rend la mutualisation acceptable — et elle # deplace la verification chez le seul qui detient la cle : le noeud lui-meme. # # Il verifie donc SON depot distant, pas le fait d'avoir lance sa sauvegarde. La nuance # est tout : une unite systemd verte sur un depot vide est exactement ce qui a menti # pendant un mois (2026-07-03 -> 2026-08-11). client_backup_icinga_hote: "{{ (groups['serveur_icinga'] | default([]) | first) | default('') }}" client_backup_icinga_utilisateur: "setops-depot" client_backup_icinga_motdepasse: "{{ vault_icinga_api_depot | default('') }}" client_backup_ca_verification: "/etc/setops/icinga-ca.crt" # L'Icinga auquel ce noeud a deja rapporte (empreinte de son AC) — voir tasks/verifier.yml. client_backup_marque_icinga: "/etc/setops/client_backup-icinga-entendu" client_backup_icinga_ca_source: "/var/lib/icinga2/ca/ca.crt" # `ttl` du resultat passif : au-dela, Icinga perime le service de lui-meme — c'est ce qui # fait que le SILENCE alerte. Memes valeurs que cote depot : une verification toutes les # 4 h, une execution peut etre manquee sans fausse alerte, deux ne le peuvent pas. client_backup_verification_horaire: "*-*-* 01/4:00:00" client_backup_ttl_icinga: 21600 client_backup_age_warn_h: 26 client_backup_age_crit_h: 50 # LE CONTROLE DE RESTAURATION (2026-09-28) — voir `templates/verifier-restauration.sh.j2`. # Hebdomadaire : il relit une part du depot et restaure le dernier instantane pour de vrai. client_backup_restauration_horaire: "Sun *-*-* 04:00:00" client_backup_restauration_part: "10%" # Huit jours : une semaine entre deux controles, plus une journee de marge. Au-dela sans # nouvelle, Icinga passe le service au rouge (voir `serveur_icinga_fraicheur_restauration`). client_backup_ttl_restauration: 691200 # Combien de temps un script attend un depot verrouille par un autre (`restic --retry-lock`) # avant d'echouer. Le plus long des occupants est le controle de restauration ; il relit # 10 % du depot et le restaure entier. client_backup_attente_verrou: "30m" # --- LA RESTAURATION DANS LA RECONSTRUCTION (2026-09-30) ---------------------------- # # Jusqu'ici une reconstruction repartait d'un etat NEUF : les instantanes se restauraient # pour PROUVER qu'ils s'ouvrent, jamais pour etre remis en service. Desormais chaque role # proprietaire d'un etat inclut `tasks/restaurer.yml` au moment ou il le creerait neuf, et # remet celui de l'incarnation precedente s'il existe. Voir `templates/restaurer.sh.j2`. # # `false` : ne jamais restaurer (chaque jeu est alors acte « desactive », et la # sauvegarde ne s'en trouve pas bloquee). client_backup_restauration_active: true # Imposer un instantane (son `short_id`) au lieu du dernier anterieur a la naissance. # Par hote, et le temps d'une passe : `-e client_backup_restauration_instantane=abcd1234`. client_backup_restauration_instantane: "" # Ou chaque jeu laisse la trace de ce qui lui est arrive (restaure, neuf, en_place...). client_backup_restauration_marques: "/etc/setops/restauration" # Ce qui etait en place avant d'etre ecrase. Hors des chemins sauvegardes : on ne veut # pas deposer chez l'hebergeur l'etat qu'on vient justement de remplacer. client_backup_restauration_mise_de_cote: "/var/backups/setops-avant-restauration"