recette : un depot occupe n est pas une sauvegarde cassee
Some checks are pending
verifier / verifier (push) Waiting to run
Some checks are pending
verifier / verifier (push) Waiting to run
make valider rendait ECHEC sur data-sql-01, et la meme commande rejouee a la main restaurait 5 fichiers. restic verrouille son depot pendant qu il ecrit : une recette qui croise la fenetre de sauvegarde lit un echec de RESTAURATION la ou il n y a qu une attente. Verdict juste sur l instant, faux sur le fond, et DEPENDANT DE L HEURE a laquelle on la lance. ON REESSAIE, MAIS ON NE MASQUE PAS. Trois tentatives espacees couvrent un verrou d ecriture ; au-dela, l echec est REEL et la recette rend la RAISON que restic a donnee, au lieu d un -1 muet qui obligeait a se connecter pour comprendre. CONTROLE VERIFIE SUR LA MACHINE : cle de dechiffrement retiree -> « ECHEC apres 3 tentatives — restic dit : Fatal: Resolving password failed ». La recette attrape toujours une vraie panne, et elle en donne la cause. DEUX FAUTES A MOI EN CHEMIN, CORRIGEES. `[ "$f" -lt 0 ] && printf ...` : la DERNIERE commande d un script decide de son code de sortie, et un test faux rend 1. La tache echouait donc exactement sur les machines ou la restauration avait REUSSI — trois hotes verts declares en echec. Puis la ligne `raison=` n etait emise qu en cas d echec : le gabarit cherchait un champ absent, rendait None, et explosait sur les DIX machines saines. Elle est desormais TOUJOURS emise, vide en cas de succes. Un champ toujours present coute un octet et supprime un cas. make valider : rc=0. make verifier : vert. make prouver : 55 OK, 0 echec. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
This commit is contained in:
parent
0dd57ba995
commit
35f3881218
2 changed files with 72 additions and 6 deletions
34
CHANGELOG.md
34
CHANGELOG.md
|
|
@ -1,5 +1,39 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-08-31 — La recette dependait de l'heure : un depot occupe n'est pas une sauvegarde cassee
|
||||||
|
|
||||||
|
**55 preuves. `make valider` : rc=0.** La reconstruction de Chezlepro est validee de bout
|
||||||
|
en bout — 16 cibles Prometheus UP, 6 vhosts HTTPS, un courriel **reellement remis** (envoi
|
||||||
|
→ LMTP → Maildir), et six depots restic **restaures et verifies**, dont l'annuaire dont on
|
||||||
|
prouve qu'il se rejoue (`slapadd -u`).
|
||||||
|
|
||||||
|
Un seul verdict rouge au premier passage : `data-sql-01`, la base. Et la meme commande,
|
||||||
|
rejouee a la main, restaurait ses cinq fichiers.
|
||||||
|
|
||||||
|
**`restic` verrouille son depot pendant qu'il ecrit.** Une recette qui croise la fenetre de
|
||||||
|
sauvegarde lit donc un echec de RESTAURATION la ou il n'y a qu'une attente — *verdict juste
|
||||||
|
sur l'instant, faux sur le fond, et dependant de l'heure a laquelle on l'a lancee.*
|
||||||
|
|
||||||
|
**On reessaie, mais on ne masque pas.** Trois tentatives espacees couvrent un verrou
|
||||||
|
d'ecriture ; au-dela l'echec est reel, et la recette rend desormais **la raison que restic
|
||||||
|
a donnee** au lieu d'un `-1` muet. Controle sur la machine — cle de dechiffrement retiree :
|
||||||
|
|
||||||
|
```
|
||||||
|
ECHEC — la RESTAURATION a échoué après 3 tentatives (snapshots=0)
|
||||||
|
— restic dit : Fatal: Resolving password failed
|
||||||
|
```
|
||||||
|
|
||||||
|
### Deux fautes a moi, en chemin
|
||||||
|
|
||||||
|
`[ "$f" -lt 0 ] && printf ...` : **la derniere commande d'un script decide de son code de
|
||||||
|
sortie**, et un test faux rend 1. La tache echouait donc exactement sur les machines ou la
|
||||||
|
restauration avait REUSSI — trois hotes verts declares en echec.
|
||||||
|
|
||||||
|
Puis la ligne `raison=` n'etait emise qu'en cas d'echec : le gabarit cherchait un champ
|
||||||
|
absent, rendait `None`, et explosait sur les **dix machines saines**. Elle est desormais
|
||||||
|
toujours emise, vide en cas de succes. *Un champ toujours present coute un octet et
|
||||||
|
supprime un cas.*
|
||||||
|
|
||||||
## 2026-08-31 — Deux filets : un cache mort ne rend plus un tenant irreparable
|
## 2026-08-31 — Deux filets : un cache mort ne rend plus un tenant irreparable
|
||||||
|
|
||||||
**55 preuves.** Le cache d'un tenant est **un maillon dont depend sa propre
|
**55 preuves.** Le cache d'un tenant est **un maillon dont depend sa propre
|
||||||
|
|
|
||||||
|
|
@ -137,11 +137,40 @@
|
||||||
n=$(restic snapshots 2>/dev/null | grep -cE '^[0-9a-f]{8} ' || echo 0)
|
n=$(restic snapshots 2>/dev/null | grep -cE '^[0-9a-f]{8} ' || echo 0)
|
||||||
if [ "$n" -lt 1 ]; then echo "sansobjet=0 snapshots=0 restored_files=-1 ldif_rejouable=-1"; exit 0; fi
|
if [ "$n" -lt 1 ]; then echo "sansobjet=0 snapshots=0 restored_files=-1 ldif_rejouable=-1"; exit 0; fi
|
||||||
tmp=$(mktemp -d)
|
tmp=$(mktemp -d)
|
||||||
if restic restore latest --target "$tmp" >/dev/null 2>&1; then
|
# LE DEPOT PEUT ETRE OCCUPE, ET CE N'EST PAS UNE SAUVEGARDE CASSEE (2026-08-31).
|
||||||
f=$(find "$tmp" -type f | wc -l)
|
#
|
||||||
else
|
# `restic` verrouille son depot pendant qu'il ecrit. Une recette qui croise la
|
||||||
f=-1
|
# fenetre de sauvegarde lit donc un echec de RESTAURATION la ou il n'y a qu'une
|
||||||
fi
|
# attente — verdict juste sur l'instant, faux sur le fond, et dependant de
|
||||||
|
# l'heure a laquelle on l'a lancee. Constate sur `data-sql-01`, dont la
|
||||||
|
# sauvegarde de 02h38 tenait encore le depot : `ECHEC` au premier passage,
|
||||||
|
# `OK — 5 fichiers` en rejouant la meme commande a la main.
|
||||||
|
#
|
||||||
|
# ON REESSAIE, MAIS ON NE MASQUE PAS. Trois tentatives espacees couvrent
|
||||||
|
# largement un verrou d'ecriture ; au-dela, l'echec est REEL et on rend la
|
||||||
|
# RAISON que restic a donnee, au lieu d'un `-1` muet qui laisse chercher.
|
||||||
|
f=-1
|
||||||
|
err=""
|
||||||
|
for essai in 1 2 3; do
|
||||||
|
if err=$(restic restore latest --target "$tmp" 2>&1 >/dev/null); then
|
||||||
|
f=$(find "$tmp" -type f | wc -l)
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp"; tmp=$(mktemp -d)
|
||||||
|
[ "$essai" -lt 3 ] && sleep 20
|
||||||
|
done
|
||||||
|
# La raison voyage sur une ligne a part : le verdict la cite, et l'exploitant
|
||||||
|
# n'a pas a se connecter pour savoir ce que restic a dit.
|
||||||
|
#
|
||||||
|
# `if` ET NON `[ ... ] && ...` : la DERNIERE commande d'un script decide de son
|
||||||
|
# code de sortie, et un test faux rend 1. Ecrit en `&&`, cette ligne faisait
|
||||||
|
# echouer la tache exactement sur les machines ou la restauration avait REUSSI
|
||||||
|
# — trois hotes verts declares en echec (2026-08-31).
|
||||||
|
# TOUJOURS EMISE, vide en cas de succes. Une ligne parfois absente oblige son
|
||||||
|
# lecteur a gerer l'absence, et c'est la que le gabarit s'est casse : la
|
||||||
|
# recherche rendait `None` et le verdict explosait sur les DIX machines saines
|
||||||
|
# (2026-08-31). Un champ toujours present coute un octet et supprime un cas.
|
||||||
|
printf 'raison=%s\n' "$(printf '%s' "$err" | tr '\n' ' ' | cut -c1-160)"
|
||||||
# L'annuaire est la seule charge dont on peut prouver ICI qu'elle est REJOUABLE,
|
# L'annuaire est la seule charge dont on peut prouver ICI qu'elle est REJOUABLE,
|
||||||
# sans rien écrire : `slapadd -u` est un essai à blanc. Éprouvé le 2026-08-12,
|
# sans rien écrire : `slapadd -u` est un essai à blanc. Éprouvé le 2026-08-12,
|
||||||
# avec contrôle négatif (un LDIF corrompu sort en 1).
|
# avec contrôle négatif (un LDIF corrompu sort en 1).
|
||||||
|
|
@ -163,6 +192,8 @@
|
||||||
restaures: "{{ recette_restore.stdout | regex_search('restored_files=(-?\\d+)', '\\1') | first | int }}"
|
restaures: "{{ recette_restore.stdout | regex_search('restored_files=(-?\\d+)', '\\1') | first | int }}"
|
||||||
snaps: "{{ recette_restore.stdout | regex_search('snapshots=(\\d+)', '\\1') | first | int }}"
|
snaps: "{{ recette_restore.stdout | regex_search('snapshots=(\\d+)', '\\1') | first | int }}"
|
||||||
rejouable: "{{ ((recette_restore.stdout | regex_search('ldif_rejouable=(-?\\d+)', '\\1')) or [-1]) | first | int }}"
|
rejouable: "{{ ((recette_restore.stdout | regex_search('ldif_rejouable=(-?\\d+)', '\\1')) or [-1]) | first | int }}"
|
||||||
|
# Ce que restic a dit, quand il a refuse. Vide sinon — la ligne est toujours la.
|
||||||
|
raison: "{{ ((recette_restore.stdout | regex_search('raison=(.*)', '\\1')) or ['']) | first | trim }}"
|
||||||
# « L'instantané n'emporte aucun fichier » n'est PAS un échec de restauration : la
|
# « L'instantané n'emporte aucun fichier » n'est PAS un échec de restauration : la
|
||||||
# restauration a parfaitement fonctionné, il n'y avait rien à rendre. La machine ne
|
# restauration a parfaitement fonctionné, il n'y avait rien à rendre. La machine ne
|
||||||
# peut pas distinguer « les données ont disparu » de « il n'y en a pas encore ».
|
# peut pas distinguer « les données ont disparu » de « il n'y en a pas encore ».
|
||||||
|
|
@ -182,5 +213,6 @@
|
||||||
'OK — ' ~ snaps ~ ' snapshot(s), restauration vérifiée (' ~ restaures ~ ' fichiers)'
|
'OK — ' ~ snaps ~ ' snapshot(s), restauration vérifiée (' ~ restaures ~ ' fichiers)'
|
||||||
~ ((rejouable | int == 1) | ternary(', annuaire REJOUABLE (slapadd -u).', '.')))) }}
|
~ ((rejouable | int == 1) | ternary(', annuaire REJOUABLE (slapadd -u).', '.')))) }}
|
||||||
fail_msg: >-
|
fail_msg: >-
|
||||||
ÉCHEC — la RESTAURATION elle-même a échoué (snapshots={{ snaps }}){{
|
ÉCHEC — la RESTAURATION elle-même a échoué après 3 tentatives
|
||||||
|
(snapshots={{ snaps }}){{ raison | ternary(' — restic dit : ' ~ raison, '') }}{{
|
||||||
(rejouable | int == 0) | ternary(', et l annuaire restauré N EST PAS rejouable', '') }}.
|
(rejouable | int == 0) | ternary(', et l annuaire restauré N EST PAS rejouable', '') }}.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue