From 9a823a5ea219dc9c5534e20fadeed39770db5a69 Mon Sep 17 00:00:00 2001 From: Daniel Allaire Date: Sat, 5 Sep 2026 14:20:19 -0400 Subject: [PATCH] depot hors site : sortir du site ce que le site garde pour tout le monde MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le depot heberge la racine de l AC, la forge du genome, et l etat de CHAQUE locataire — 109 Mo sur une seule machine, dans un seul batiment. C est le probleme du filet range dans la flotte qu il protege, un etage plus haut. On emporte AUSSI les depots des locataires : si le site brule, ils perdent leurs sauvegardes avec lui, et eux ne peuvent rien y faire. Ils ont depose chez l hebergeur, c est a l hebergeur de tenir cette promesse. La copie est OPAQUE — chiffree cote client, illisible par qui la porte. C est ce qui permet de la deposer chez un pair sans lui demander autre chose que de la disponibilite. L empreinte est prise A LA SOURCE avant la copie, puis recalculee sur la copie et comparee une a une. Sans ca on rentre chez soi avec un repertoire. Un pipe sans pipefail masque l echec de tout ce qui n est pas le dernier maillon : ici sed, qui reussit toujours. Une empreinte vide des deux cotes aurait passe la comparaison. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on --- Makefile | 12 ++ playbooks/maintenance/depot-hors-site.yml | 132 ++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 playbooks/maintenance/depot-hors-site.yml diff --git a/Makefile b/Makefile index ed41294..c5c5485 100644 --- a/Makefile +++ b/Makefile @@ -575,6 +575,18 @@ cles-exporter: ## Sort les cles du poste, chiffrees et relues — VERS= + @# Le depot heberge la racine de l'AC, la forge du genome, et l'etat de CHAQUE + @# locataire — sur une seule machine, dans un seul batiment. La copie est opaque + @# (chiffree cote client) : elle se depose chez un pair sans rien lui demander + @# d'autre que de la disponibilite. + @if [ -z "$(VERS)" ]; then \ + printf 'Refus: relancer avec VERS=.\n'; \ + printf 'Ex. make depot-hors-site VERS=/media/danallaire/setops-coffre\n'; exit 2; fi + ansible-playbook -i scripts/site_inventaire.py \ + playbooks/maintenance/depot-hors-site.yml -e vers="$(VERS)" + +.PHONY: depot-hors-site cles-compagnons: ## Depose le script de restauration sur une cle DEJA faite — VERS= @# POUR UNE CLE EXISTANTE. L'archive n'est pas refaite : ni phrase de passe redemandee, @# ni risque d'ecraser ce qui est deja verifie. diff --git a/playbooks/maintenance/depot-hors-site.yml b/playbooks/maintenance/depot-hors-site.yml new file mode 100644 index 0000000..b4c917e --- /dev/null +++ b/playbooks/maintenance/depot-hors-site.yml @@ -0,0 +1,132 @@ +--- +# SORTIR LES SAUVEGARDES DU SITE, DU SITE. +# +# Le depot du site heberge trois choses irremplacables : la racine de son autorite de +# certification, la forge du genome, et l'etat de CHAQUE locataire. Tout cela vit sur une +# seule machine, dans un seul batiment. C'est le probleme du filet range dans la flotte +# qu'il protege — celui qu'on a mis deux jours a fermer pour Chezlepro — un etage plus haut. +# +# POURQUOI ON EMPORTE AUSSI LES DEPOTS DES LOCATAIRES. Si le site brule, les locataires +# perdent leurs sauvegardes avec lui, et EUX NE PEUVENT RIEN Y FAIRE : ils ont depose chez +# l'hebergeur, c'est a l'hebergeur de tenir cette promesse. 109 Mo au total — la question +# du volume ne se pose pas. +# +# CE QUE LE PORTEUR DE LA COPIE PEUT EN FAIRE : rien. Les depots sont chiffres par restic +# COTE CLIENT, avec des mots de passe qui vivent dans les voutes de chacun. Cette copie +# est un bloc opaque — c'est ce qui permet de la deposer chez un pair sans lui demander +# autre chose que de la disponibilite. +# +# ansible-playbook -i scripts/site_inventaire.py \ +# playbooks/maintenance/depot-hors-site.yml -e vers=/media/…/setops-coffre +- name: Copier le depot du site hors du site + hosts: serveur_backup_site + gather_facts: false + become: true + + vars: + racine: "{{ serveur_backup_site_parent | default('/srv/restic') }}" + + pre_tasks: + - name: Exiger une destination + ansible.builtin.assert: + that: + - vers is defined + - vers | length > 0 + fail_msg: >- + `-e vers=` requis. Viser un support amovible ou une machine tierce. + delegate_to: localhost + become: false + + # UNE COPIE DANS L'INFRASTRUCTURE NE PROTEGE DE RIEN — elle boucle. Meme raisonnement + # que pour les cles : un coffre range dans ce qu'il protege n'est pas un coffre. + - name: Refuser une destination interne + ansible.builtin.assert: + that: + - not (vers is search('/srv/restic')) + - not (vers is search('Espace Chezlepro')) + fail_msg: >- + {{ vers }} est DANS l'infrastructure. Une copie qui partage le sort de + l'original ne protege de rien. + delegate_to: localhost + become: false + + - name: La destination existe-t-elle ? + ansible.builtin.stat: + path: "{{ vers }}" + delegate_to: localhost + become: false + register: dest_etat + + - name: Exiger une destination reelle + ansible.builtin.assert: + that: + - dest_etat.stat.exists + - dest_etat.stat.isdir + fail_msg: "{{ vers }} n'existe pas ou n'est pas un repertoire. Support monte ?" + delegate_to: localhost + become: false + + tasks: + # L'EMPREINTE EST PRISE A LA SOURCE, AVANT LA COPIE. La comparer apres coup a une + # empreinte calculee sur la copie est la seule facon de savoir qu'on a emporte ce + # qu'on croit avoir emporte. Sans ca, on rentre chez soi avec un repertoire. + - name: Empreinte de ce qui est a la source + ansible.builtin.shell: + executable: /bin/bash + cmd: >- + set -o pipefail; + find {{ racine }} -type f -print0 | sort -z | + xargs -0 sha256sum | sed 's| {{ racine }}/| |' + register: empreinte_source + changed_when: false + + - name: Combien, et quelle taille + ansible.builtin.shell: + executable: /bin/bash + cmd: "set -o pipefail; du -sb {{ racine }} | cut -f1" + register: taille_source + changed_when: false + + - name: Copier vers la destination + ansible.posix.synchronize: + src: "{{ racine }}/" + dest: "{{ vers }}/depot-du-site/" + mode: pull + archive: true + delete: true + become: false + + - name: Empreinte de ce qui est arrive + ansible.builtin.shell: + executable: /bin/bash + cmd: >- + set -o pipefail; + find {{ vers }}/depot-du-site -type f -print0 | sort -z | + xargs -0 sha256sum | sed 's| {{ vers }}/depot-du-site/| |' + delegate_to: localhost + become: false + register: empreinte_copie + changed_when: false + + - name: Exiger une copie fidele + ansible.builtin.assert: + that: + - empreinte_source.stdout == empreinte_copie.stdout + fail_msg: >- + LA COPIE DIFFERE DE LA SOURCE. + Source : {{ empreinte_source.stdout_lines | length }} fichier(s). + Copie : {{ empreinte_copie.stdout_lines | length }} fichier(s). + Ne pas s'en servir : une copie qu'on croit fidele et qui ne l'est pas est pire + qu'aucune copie. + success_msg: >- + COPIE FIDELE : {{ empreinte_source.stdout_lines | length }} fichier(s), + {{ (taille_source.stdout | int / 1048576) | round(1) }} Mo, empreintes + identiques une a une. + + - name: Ce qu'il reste a faire, et qui n'est pas facultatif + ansible.builtin.debug: + msg: + - "La copie est OPAQUE : chiffree cote client, illisible par qui la porte." + - "Elle ne vaut que si elle vit AILLEURS que le site — un tiroir a cote du" + - "serveur ne protege que du disque, pas de l'incendie ni du vol." + - "A refaire quand l'etat change. `restic` ne recopie que les differences."