depot hors site : sortir du site ce que le site garde pour tout le monde
Some checks are pending
verifier / verifier (push) Waiting to run

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
Daniel Allaire 2026-09-05 14:20:19 -04:00
parent dcbb57db69
commit 9a823a5ea2
2 changed files with 144 additions and 0 deletions

View file

@ -575,6 +575,18 @@ cles-exporter: ## Sort les cles du poste, chiffrees et relues — VERS=<repertoi
python3 scripts/exporter_cles.py --vers "$(VERS)"
.PHONY: cles-recenser cles-exporter
depot-hors-site: ## Copie le depot de sauvegarde du site HORS du site — VERS=<repertoire>
@# 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=<repertoire de destination>.\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=<repertoire>
@# POUR UNE CLE EXISTANTE. L'archive n'est pas refaite : ni phrase de passe redemandee,
@# ni risque d'ecraser ce qui est deja verifie.

View file

@ -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=<repertoire>` 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."