Aller au contenu

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-state pré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 :

  1. utilise cmonchoix-platform_grafana_data_v2 comme défaut ;
  2. accepte CCX_GRAFANA_VOLUME ;
  3. identifie Grafana via CCX_GRAFANA_CONTAINER, défaut ccx_grafana ;
  4. exige que le conteneur soit actif ;
  5. résout le mount /var/lib/grafana ;
  6. exige un mount Docker de type volume ;
  7. exige l’égalité entre le volume runtime et l’autorité de backup ;
  8. échoue avant Restic en cas de divergence ;
  9. inscrit grafana_volume et grafana_container dans 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 explicitement CCX_GRAFANA_VOLUME=cmonchoix-platform_grafana_data_v2 et CCX_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-platform inchangé.

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.env restauré, mode 600, root-owned et SHA conforme à l’environnement certifié ;
  • wp-config.php pré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.db BoltDB 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.