Aller au contenu

Bases de données

PostgreSQL et MySQL suivent la même recette en quatre temps :

  1. fetch — récupérer le dump ;
  2. unpack — le décompresser si besoin ;
  3. start_sandbox — démarrer un serveur neuf, vide, jetable ;
  4. restore_postgres / restore_mysql — rejouer le dump dedans.

Puis les sondes comptent ce qui est arrivé dans ce serveur.

La version du bac à sable

Prenez la même version majeure qu'en production. Un dump pris sur un serveur plus récent ne se rejoue pas sur un serveur plus ancien. Si votre plan échoue sur une erreur de syntaxe SQL incompréhensible, c'est presque toujours ça.

Les plans ci-dessous ne portent aucun mot de passe pour le conteneur jetable : le runner reconnaît le type de conteneur qu'il démarre et fabrique lui-même les identifiants dont il a besoin, à chaque exécution. Il n'y a rien à saisir. Voir le mot de passe du bac à sable. Voir aussi comment adapter le bloc fetch et comment trouver les seuils.

PostgreSQL

La sauvegarde en entrée

Un pg_dump compressé, un fichier par nuit, dans un dossier que le runner peut lire : docmost_2026-09-02T03-00.sql.gz.

Le champ format de l'étape restore_postgres dit comment le rejouer :

Votre dump format Outil utilisé
pg_dump sans option (SQL en clair) plain psql
pg_dump -Fc (format custom) custom pg_restore
pg_dump -Fd (répertoire) directory pg_restore
pg_dump -Ft (archive tar) tar pg_restore
vous ne savez pas auto détecté au contenu

Le plan

{
  "steps": [
    {
      "type": "fetch",
      "name": "Récupérer le dump Docmost",
      "config": {
        "source": "local",
        "path": "/backups/docmost/postgres",
        "file_pattern": "docmost_*.sql.gz",
        "selection_strategy": "latest_modified",
        "max_age": "3h",
        "output": "docmost.sql.gz"
      }
    },
    {
      "type": "unpack",
      "name": "Décompresser",
      "config": { "format": "gzip", "input": "docmost.sql.gz", "output": "docmost.sql" }
    },
    {
      "type": "start_sandbox",
      "name": "Démarrer PostgreSQL",
      "config": {
        "image": "postgres:16-alpine",
        "env": { "POSTGRES_DB": "docmost" }
      }
    },
    {
      "type": "restore_postgres",
      "name": "Restaurer la base",
      "config": {
        "database": "docmost",
        "input": "docmost.sql",
        "format": "plain"
      }
    }
  ],
  "probes": [
    {
      "name": "schema",
      "type": "postgres",
      "timeout": 60,
      "config": {
        "RESTOREPROOF_PG_TABLE": ""
      }
    },
    {
      "name": "comptes",
      "type": "postgres",
      "timeout": 60,
      "config": {
        "RESTOREPROOF_PG_TABLE": "users",
        "RESTOREPROOF_PG_ASSERT": "gte",
        "RESTOREPROOF_PG_EXPECTED_ROWS": "1"
      }
    }
  ]
}

Ce que chaque sonde prouve

  • schema — table laissée vide : la sonde vérifie la base entière et échoue si le dump n'y a recréé aucune table. C'est le contrôle grossier : il attrape la restauration qui n'a rien écrit, pas la table vidée.
  • comptes — il reste au moins un compte. Une base parfaitement restaurée mais sans utilisateur est une base dans laquelle personne n'entre.

Le nom de la sonde (schema, comptes) est libre : c'est ce que vous lirez dans le rapport, écrivez-le pour vous.

Les variables de la sonde postgres

L'hôte, le port, l'utilisateur et la base ne se saisissent pas : le runner reconnaît le bac à sable PostgreSQL qu'il vient de démarrer et pose lui-même PGHOST, PGPORT et PGUSER sur la sonde, ainsi que PGDATABASE, repris du champ Base de données du pas restore_postgres. Dans l'éditeur comme en JSON, une sonde ne renseigne donc que ce qu'elle vérifie :

  • PGSSLMODE (disable par défaut, le bac à sable est sur un réseau privé) ;
  • ce qu'on compte : RESTOREPROOF_PG_TABLE, facultatif, qui accepte une clause WHERE et même une jointure, ou RESTOREPROOF_PG_QUERY pour votre propre SELECT. Table vide, c'est toute la base qui est vérifiée ;
  • l'affirmation : RESTOREPROOF_PG_ASSERT (gte, eq, lte, zero) et RESTOREPROOF_PG_EXPECTED_ROWS.

Un PGHOST (ou PGUSER, PGPORT) écrit explicitement dans la sonde l'emporte toujours sur celui que le runner aurait posé — l'échappatoire pour viser un service hors du bac à sable.

zero est l'opérateur qui prouve une absence : « aucune ligne ne correspond ». C'est lui qui attrape les données incohérentes — un article sans auteur, une entrée sans propriétaire. Voir Gitea et Vaultwarden pour des plans qui s'en servent.

Trouver les bons nombres

1 ci-dessus est la valeur de la pile de démonstration. Pour votre base : laissez 1 partout, lancez un essai, et laissez l'écran vous proposer les seuils à partir de ce que la sonde a réellement compté. Les chiffres remontent dans le rapport, vous n'avez rien à compter vous-même.

Un cas à connaître : une sonde dont la table est vide ne compte pas de lignes, donc RestoreProof ne lui propose aucun seuil. Nommez une table pour obtenir un nombre à figer.

MySQL

La sauvegarde en entrée

Un mysqldump compressé : wordpress_2026-09-02T03-00.sql.gz. Le champ format existe ici aussi, mais on le laisse presque toujours vide : le runner reconnaît un mysqldump ou un mysqlpump et le rejoue avec le client mysql, et un répertoire produit par mysqlsh avec mysqlsh. Une sauvegarde XtraBackup est reconnue mais refusée : elle ne se restaure pas par le réseau.

Le plan

C'est le plan WordPress de la pile de démonstration ; hors WordPress, seuls les noms de tables changent.

{
  "steps": [
    {
      "type": "fetch",
      "name": "Récupérer le dump WordPress",
      "config": {
        "source": "local",
        "path": "/backups/wordpress/mysql",
        "file_pattern": "wordpress_*.sql.gz",
        "selection_strategy": "latest_modified",
        "max_age": "3h",
        "output": "wordpress.sql.gz"
      }
    },
    {
      "type": "unpack",
      "name": "Décompresser",
      "config": { "format": "gzip", "input": "wordpress.sql.gz", "output": "wordpress.sql" }
    },
    {
      "type": "start_sandbox",
      "name": "Démarrer MySQL",
      "config": {
        "image": "mysql:8.0",
        "env": { "MYSQL_DATABASE": "wordpress" }
      }
    },
    {
      "type": "restore_mysql",
      "name": "Restaurer la base",
      "config": {
        "database": "wordpress",
        "input": "wordpress.sql"
      }
    }
  ],
  "probes": [
    {
      "name": "wp_admin",
      "type": "mysql",
      "timeout": 60,
      "config": {
        "RESTOREPROOF_MYSQL_TABLE": "wp_users",
        "RESTOREPROOF_MYSQL_ASSERT": "gte",
        "RESTOREPROOF_MYSQL_EXPECTED_ROWS": "1"
      }
    },
    {
      "name": "wp_content",
      "type": "mysql",
      "timeout": 60,
      "config": {
        "RESTOREPROOF_MYSQL_TABLE": "wp_posts WHERE post_status = 'publish' AND post_type = 'post'",
        "RESTOREPROOF_MYSQL_ASSERT": "gte",
        "RESTOREPROOF_MYSQL_EXPECTED_ROWS": "12"
      }
    },
    {
      "name": "wp_site_url",
      "type": "mysql",
      "timeout": 60,
      "config": {
        "RESTOREPROOF_MYSQL_TABLE": "wp_options WHERE option_name IN ('siteurl','home') AND option_value <> ''",
        "RESTOREPROOF_MYSQL_ASSERT": "eq",
        "RESTOREPROOF_MYSQL_EXPECTED_ROWS": "2"
      }
    },
    {
      "name": "wp_orphans",
      "type": "mysql",
      "timeout": 60,
      "config": {
        "RESTOREPROOF_MYSQL_TABLE": "wp_posts p LEFT JOIN wp_users u ON u.ID = p.post_author WHERE p.post_author <> 0 AND u.ID IS NULL",
        "RESTOREPROOF_MYSQL_ASSERT": "zero"
      }
    }
  ]
}

Ce que chaque sonde prouve

  • wp_admin — au moins un compte peut encore se connecter.
  • wp_content — les articles publiés sont revenus. C'est le nombre qui distingue un site restauré d'une installation neuve : une installation neuve a déjà toutes ses tables wp_, et zéro contenu à vous.
  • wp_site_url — siteurl et home sont là et non vides. Sans elles, WordPress sert un site que personne n'atteint, aussi complète que soit la restauration. eq: 2 parce qu'on en attend exactement deux.
  • wp_orphans — aucun article n'a perdu son auteur. zero : la restauration n'a pas seulement rempli les tables, elle a gardé les liens entre elles.

Les variables de la sonde mysql

Même règle que PostgreSQL : l'hôte, le port, l'utilisateur et la base ne se saisissent pas, le runner les pose lui-même (MYSQL_HOST, MYSQL_PORT, MYSQL_USER, et MYSQL_DATABASE repris du pas restore_mysql). Restent à votre charge RESTOREPROOF_MYSQL_CHARSET et RESTOREPROOF_MYSQL_TLS, puis RESTOREPROOF_MYSQL_TABLE ou RESTOREPROOF_MYSQL_QUERY, RESTOREPROOF_MYSQL_ASSERT et RESTOREPROOF_MYSQL_EXPECTED_ROWS.

Un dump mysqldump --all-databases est le seul cas où la base se saisit sur la sonde, dans l'option avancée Base à interroger (MYSQL_DATABASE, ou RESTOREPROOF_MYSQL_DATABASE) : le pas de restauration n'en désigne alors aucune, puisque le dump porte ses propres CREATE DATABASE.

MariaDB se teste avec le même plan : changez l'image du bac à sable pour mariadb:11 ou mariadb:10.11.

Le préfixe des tables

wp_ est un défaut, pas une règle. Un site qui l'a changé a besoin de son propre préfixe partout, sinon les quatre sondes interrogent des tables qui n'existent pas — et une sonde qui échoue parce qu'elle regarde au mauvais endroit ne prouve rien sur votre sauvegarde. Le modèle WordPress Database de l'interface demande ce préfixe et le reporte dans les quatre sondes.