Disaster Recovery¶
Cette page est l’autorité opérationnelle pour P1.3 — Disaster Recovery / restore drill.
L’objectif n’est pas de prouver qu’un dump SQL est lisible. L’objectif est de démontrer qu’un état critique CMonChoix peut être reconstruit de manière isolée, vérifiable et sans toucher aux volumes de production.
Statut global¶
P1.3 est CLOSED / PASS au 3 septembre 2026.
La certification finale repose sur cinq tranches :
- P1.3A — audit DR en lecture seule ;
- P1.3B — correction et certification live de l’autorité de sauvegarde ;
- P1.3C — restauration MariaDB isolée ;
- P1.3D — restauration isolée du snapshot Restic
critical-state; - P1.3E — restauration isolée des uploads WordPress.
Le main certifié à la clôture est :
3ff973e27809e1356d6e1b038fe1cf4857fe118c
Le checkout historique live reste préservé sur :
4810873d49ff2a360c43ec909471cc20b026db2b
Le statut CLOSED signifie que les sauvegardes applicatives et critical-state ont été réellement restaurées dans des cibles isolées. Il ne constitue pas un engagement contractuel de RPO ou RTO.
Principes¶
- une sauvegarde n’est pas considérée restaurable uniquement parce que ses hashes sont valides ;
- Une PR verte ne certifie pas l’autorité live. ;
- une sauvegarde offsite ne certifie pas son propre périmètre : son manifeste doit être confronté au runtime actif ;
- aucun volume de production ne doit être utilisé comme cible de restauration ;
- le restore drill doit être isolé, temporaire et nettoyé après certification ;
- toute restauration de production reste une opération destructive séparée nécessitant une autorisation explicite ;
- RPO et RTO ne doivent jamais être déduits uniquement de la fréquence des timers de sauvegarde.
P1.3A — audit DR en lecture seule¶
L’audit du 3 septembre 2026 a établi la baseline suivante :
- checkout historique live :
4810873d49ff2a360c43ec909471cc20b026db2b; - timer backup quotidien actif ;
- 15 bundles applicatifs locaux présents au moment de l’audit ;
- dernier bundle alors audité :
cmonchoix-20260903T013003Z-4810873d49ff; - dump MariaDB présent et vérifié ;
- uploads présents et vérifiés ;
- image MariaDB immuable du bundle présente localement ;
- repository Restic off-VPS lisible ;
- quatre snapshots
critical-stateprésents.
Le snapshot critical-state alors le plus récent était :
7833787a7b08019263a7e78c98779c38587073cdf25f2cf69ae72f8f47f62f2d
Dérive Grafana découverte¶
Le runtime Grafana actif utilisait :
cmonchoix-platform_grafana_data_v2
Le snapshot 7833787a… déclarait pourtant :
grafana_volume=cmonchoix-platform_grafana_data
La configuration live /etc/cmonchoix/offsite-backup.env ne définissait pas CCX_GRAFANA_VOLUME, ce qui laissait l’ancien défaut V1 devenir l’autorité de sauvegarde.
Cette dérive a été classée fail-closed : le helper canonique doit comparer le volume configuré au mount /var/lib/grafana du conteneur actif avant tout nouveau snapshot.
Dérive de l’autorité installée¶
Le manifeste Git canonique prévoit exactement 7 helpers et 4 unités systemd. backup-restore-check.sh était absent du live au moment de l’audit : seuls 6 helpers étaient installés.
P1.3A est CLOSED avec ces deux défauts confirmés comme conditions d’entrée de P1.3B.
P1.3B — autorité de sauvegarde live¶
P1.3B a corrigé puis certifié l’autorité de sauvegarde.
Autorité Git¶
Le helper scripts/backup-critical-state-restic.sh :
- utilise
cmonchoix-platform_grafana_data_v2comme défaut ; - accepte
CCX_GRAFANA_VOLUME; - identifie Grafana via
CCX_GRAFANA_CONTAINER, défautccx_grafana; - exige que le conteneur soit actif ;
- résout le mount
/var/lib/grafana; - exige un mount Docker de type
volume; - exige l’égalité entre le volume runtime et l’autorité de backup ;
- échoue avant Restic en cas de divergence ;
- inscrit
grafana_volumeetgrafana_containerdans le manifeste.
Le manifeste systemd reste limité à exactement 11 artefacts : 7 helpers et 4 unités.
Correctif metadata Git de l’installateur¶
Une première installation live a révélé qu’un git status exécuté comme root pouvait rafraîchir l’index du worktree de release et le réécrire en root:root.
Le correctif final, mergé sur main à :
3ff973e27809e1356d6e1b038fe1cf4857fe118c
impose GIT_OPTIONAL_LOCKS=0 avant toute lecture Git de l’installateur et traite explicitement l’échec de git status comme une erreur.
Le worktree déjà affecté a été réparé de manière chirurgicale : contenu de l’index inchangé, ownership restauré à l’utilisateur opérateur, aucun chown -R.
Certification live¶
Le run contrôlé P1.3B a confirmé :
- 7 helpers installés ;
- 4 unités systemd installées ;
- timers backup et maintenance actifs et enabled ;
/etc/cmonchoix/offsite-backup.envépingle explicitementCCX_GRAFANA_VOLUME=cmonchoix-platform_grafana_data_v2etCCX_GRAFANA_CONTAINER=ccx_grafana;- runtime Grafana réellement monté sur
cmonchoix-platform_grafana_data_v2; - installateur root n’altérant plus l’index Git de release ;
- nouveau bundle applicatif créé et vérifié ;
- nouveau snapshot critical-state créé et vérifié ;
- Portainer arrêté brièvement puis revenu sur le même conteneur ;
- runtime
cmonchoix-platforminchangé.
Bundle applicatif certifié :
/mnt/data/cmonchoix-backups/cmonchoix-20260903T202436Z-4810873d49ff
Snapshot application offsite créé pendant ce run :
ba80814b…
Snapshot critical-state certifié :
89153e3db0eae65ca6b670a368ced0720b1c5c3718c81e8d777ede176c63d72f
Son manifeste contient :
scope=critical-state;grafana_volume=cmonchoix-platform_grafana_data_v2;grafana_container=ccx_grafana.
P1.3B est CLOSED / PASS.
P1.3C — restore drill isolé — restauration MariaDB¶
Le bundle applicatif certifié a été vérifié puis réellement restauré via backup-restore-check.sh.
Le drill a utilisé :
- l’image MariaDB immuable enregistrée dans le manifeste :
sha256:78a5047d3ba33975f183f183c2464cc7f1eab13ec8667e57cc9a5821d6da7577; - un conteneur éphémère ;
--network none;--security-opt no-new-privileges:true;- stockage MariaDB en
tmpfs; - limite mémoire
2g; - limite CPU
1.0; - aucun volume nommé de production.
Résultat :
- import réel de
database.sql.gz: PASS ; mariadb-check: PASS ;- base :
ccx; - tables restaurées : 59 ;
- conteneur éphémère supprimé ;
- inventaire des volumes Docker inchangé ;
- inventaire des réseaux Docker inchangé ;
- runtime plateforme inchangé ;
- aucune mutation de la base live.
P1.3C est CLOSED / PASS.
P1.3D — restauration isolée du critical-state Restic¶
Le snapshot exact :
89153e3db0eae65ca6b670a368ced0720b1c5c3718c81e8d777ede176c63d72f
a été restauré sous un scratch root-only dédié sur /mnt/data, jamais dans /etc ni dans un volume Docker de production.
Résultats :
- 10 fichiers réguliers restaurés ;
- aucun symlink ou fichier spécial dans l’arbre restauré ;
- manifeste
critical-state: PASS ; - Grafana V2 dans le manifeste : PASS ;
SHA256SUMS: PASS ;platform.envrestauré, mode 600, root-owned et SHA conforme à l’environnement certifié ;wp-config.phpprésent, mode 600, root-owned ;- aucun secret affiché dans les preuves ;
- NPM SQLite
PRAGMA quick_check: PASS, 18 tables ; - Grafana SQLite V2
PRAGMA quick_check: PASS, 88 tables ; npm-data.tar.gz: PASS ;npm-letsencrypt.tar.gz: PASS ;grafana-data.tar.gz: PASS ;portainer-data.tar.gz: PASS ;- archive Portainer contenant les clés/certificats/configurations obligatoires ;
portainer.dbBoltDB réellement extrait, non vide, taille observée 524288 octets ;- scratch supprimé ;
- volumes, réseaux et runtime plateforme inchangés.
P1.3D est CLOSED / PASS.
P1.3E — restauration isolée des uploads WordPress¶
uploads.tar.gz du bundle applicatif certifié a été inspecté avant extraction puis extrait dans un scratch root-only sur /mnt/data.
Contrôles pré-extraction :
- aucun chemin absolu ;
- aucun composant
..; - aucun chemin hors de
uploads/; - aucun symlink ;
- aucun hardlink ;
- aucun device, FIFO ou fichier spécial ;
- aucun doublon de membre ;
- racine
uploads/obligatoire.
Résultats :
- SHA256 de l’archive compressée :
f6d700eac6bfecf54af5ec6c5ae4c6d4dca562200df762628083ba4d20504c8d; - membres tar : 83 ;
- fichiers réguliers : 76 ;
- répertoires : 7 ;
- octets de fichiers restaurés : 6610520 ;
- agrégat SHA256 archive :
b12fbf86790bcae250d1dabc68063df4d2502ca28a147f391b8b35eb996ce79c; - agrégat SHA256 fichiers extraits :
b12fbf86790bcae250d1dabc68063df4d2502ca28a147f391b8b35eb996ce79c; - ensemble de chemins archive/extraction identique ;
- scratch supprimé ;
- aucune écriture dans le volume uploads de production ;
- volumes, réseaux et runtime plateforme inchangés.
P1.3E est CLOSED / PASS.
Verdict P1.3¶
La chaîne DR certifiée couvre désormais :
| Actif | Backup | Restore isolé | Résultat |
|---|---|---|---|
| MariaDB | bundle applicatif | MariaDB éphémère | PASS — 59 tables |
| uploads WordPress | bundle applicatif | scratch /mnt/data |
PASS — 76 fichiers |
platform.env |
critical-state Restic | scratch /mnt/data |
PASS |
wp-config.php |
critical-state Restic | scratch /mnt/data |
PASS |
| NPM SQLite | critical-state Restic | scratch /mnt/data |
PASS — 18 tables |
| NPM data / Let’s Encrypt | critical-state Restic | scratch /mnt/data |
PASS |
| Grafana V2 SQLite | critical-state Restic | scratch /mnt/data |
PASS — 88 tables |
| Grafana V2 data | critical-state Restic | scratch /mnt/data |
PASS |
| Portainer | critical-state Restic | scratch /mnt/data |
PASS — archive + BoltDB présents |
Marqueur de clôture opérationnelle :
CCX_P1_3_DISASTER_RECOVERY_CERTIFICATION=PASS
Aucune restauration de certification n’a utilisé comme cible un volume de production.
RPO / RTO — non contractuellement définis¶
La fermeture de P1.3 prouve la restaurabilité des actifs couverts. Elle ne constitue pas encore une certification d’un RPO/RTO business.
Avant de publier un engagement RPO/RTO, il faut exécuter un runbook chronométré de reconstruction complète, conserver au minimum :
- âge du bundle restauré ;
- âge du snapshot critical-state ;
- durée de récupération offsite ;
- durée d’import MariaDB ;
- durée d’extraction des uploads ;
- durée de contrôle des états critiques ;
- durée totale du drill ;
- taille des actifs restaurés ;
- erreurs et actions manuelles nécessaires.
Aucune promesse RPO/RTO ne doit être dérivée uniquement de la fréquence du timer de backup.