# Sauvegardes d'owni — faire, et surtout défaire Une sauvegarde qu'on n'a jamais restaurée est une hypothèse. Ce document dit autant comment récupérer que comment sauvegarder, et la première partie utile est la seconde. ## Ce qui est sauvegardé `scripts/sauvegarder-owni.sh`, lancé **depuis le poste**, tire chaque nuit les sept bases de la machine : | Base | Service | |---|---| | `c1_messagerie_db` | messagerie.alpinux.org | | `c1dolibarr` | fichier des adhérents | | `c1gitea` | tous les dépôts | | `c1alpid` | le SSO Keycloak | | `c1_installparty` | installparty.alpinux.org | | `c1_evenements` | événements | | `dbispconfig` | la configuration d'ISPConfig elle-même | Environ **1,5 Mo compressés** pour l'ensemble. Rangées par jour dans `~/Sauvegardes/owni/AAAA-MM-JJ/`, gardées 30 jours. Tiré depuis le poste et non poussé depuis owni : une machine compromise ne doit pas pouvoir atteindre les sauvegardes de ce qu'elle héberge. ### Ce qui n'est pas sauvegardé - **Les fichiers des sites.** Le code est dans Gitea, et Gitea est sur cette même machine — ce qui ne protège de rien. Les dépôts sont clonés sur le poste, ce qui protège un peu mieux. - **Les boîtes mail** (`/var/vmail`, 1,3 Go) : les rebonds et le courrier postmaster, dont l'essentiel est déjà repris en base. - **`/etc`**, donc la configuration système hors ISPConfig. Ces trois points sont des choix, pas des oublis. À reconsidérer le jour où l'un d'eux manquera. ## Lancer ```sh cd ~/Projects/org.alpinux.owni ./scripts/sauvegarder-owni.sh --essai # montre, n'écrit rien ./scripts/sauvegarder-owni.sh ``` En cron, sur le poste — `crontab -e` : ```cron 30 3 * * * cd $HOME/Projects/org.alpinux.owni && ./scripts/sauvegarder-owni.sh >> $HOME/Sauvegardes/owni/journal.txt 2>&1 ``` Le poste doit être allumé à 3 h 30. S'il ne l'est pas, la nuit est sautée : regarder `journal.txt` de temps en temps, ou lancer le script à la main après une longue absence. > **Ces fichiers contiennent des données personnelles** — 258 adresses > d'abonnés et le fichier des adhérents. Le dossier est en `700`, les fichiers > en `600`. Sur un poste portable, envisager un chiffrement du disque ou des > archives. --- # Restaurer ## Avant tout : regarder ce qu'on va restaurer Ne jamais restaurer une sauvegarde sans l'avoir ouverte. Une sauvegarde tronquée écrase des données saines. ```sh S=~/Sauvegardes/owni/2026-09-27/c1_messagerie_db_2026-09-27.sql.gz zcat "$S" | tail -2 # doit finir par « Dump completed on … » zcat "$S" | grep -c "INSERT INTO" zcat "$S" | grep -oE "^CREATE TABLE \`[a-z_]+\`" | wc -l ``` Le script refuse déjà d'enregistrer un dump sans sa ligne finale, mais l'habitude de vérifier vaut mieux que la confiance. ## Essayer d'abord à côté, jamais directement **La bonne méthode** : restaurer dans une base temporaire, regarder, puis basculer. On ne remplace jamais une base en production par un fichier qu'on n'a pas inspecté. ```sh scp ~/Sauvegardes/owni/2026-09-27/c1_messagerie_db_2026-09-27.sql.gz \ abonnelc@owni.alpinux.org:/tmp/ ssh abonnelc@owni.alpinux.org sudo mysql -e "CREATE DATABASE essai_restauration CHARACTER SET utf8mb4;" zcat /tmp/c1_messagerie_db_2026-09-27.sql.gz | sudo mysql essai_restauration # Est-ce bien ce qu'on croit ? sudo mysql -t essai_restauration -e " SELECT COUNT(*) AS abonnes FROM abonnes; SELECT COUNT(*) AS campagnes FROM campagnes; SELECT MAX(creee_le) AS derniere FROM campagnes;" ``` Comparer avec la production **avant** de décider quoi que ce soit : ```sh sudo mysql -t c1_messagerie_db -e " SELECT COUNT(*) AS abonnes FROM abonnes; SELECT COUNT(*) AS campagnes FROM campagnes;" ``` Une fois satisfait, effacer l'essai : `sudo mysql -e "DROP DATABASE essai_restauration;"` ## Restaurer pour de bon ### Le piège de la messagerie : arrêter le worker d'abord `bin/worker-envoi.php` tourne **chaque minute**. Restaurer la base pendant qu'il tourne, c'est lui rendre une file d'envois déjà partis — et **réexpédier une campagne à 250 personnes**. ```sh # 1. Couper le cron de la messagerie sudo mv /etc/cron.d/messagerie /root/messagerie.cron.suspendu # Vérifier qu'aucun worker n'est en cours pgrep -af worker-envoi.php || echo "aucun worker en cours" # 2. Mettre la base de côté plutôt que l'écraser sudo mysqldump --single-transaction c1_messagerie_db \ | gzip -c > /root/avant-restauration-$(date +%Y%m%d-%H%M).sql.gz # 3. Restaurer zcat /tmp/c1_messagerie_db_2026-09-27.sql.gz | sudo mysql c1_messagerie_db # 4. Vérifier avant de rouvrir les vannes sudo mysql -t c1_messagerie_db -e " SELECT statut, COUNT(*) FROM envois GROUP BY statut; SELECT COUNT(*) FROM abonnes WHERE statut='actif';" # 5. Remettre le cron — et seulement alors sudo mv /root/messagerie.cron.suspendu /etc/cron.d/messagerie ``` Entre 1 et 5, le site reste consultable : c'est l'envoi qui est suspendu, pas l'application. > Attention au nom du fichier dans `/etc/cron.d/` : **un point dans le nom et > il est ignoré en silence**. `messagerie` fonctionne, `messagerie.cron` non. > C'est pourquoi on le déplace vers `/root/` plutôt que de le renommer sur > place. ### Les autres services : arrêter avant, redémarrer après ```sh # Gitea sudo systemctl stop gitea zcat /tmp/c1gitea_*.sql.gz | sudo mysql c1gitea sudo systemctl start gitea # Keycloak (le SSO) — plus personne ne peut se connecter nulle part pendant sudo systemctl stop keycloak zcat /tmp/c1alpid_*.sql.gz | sudo mysql c1alpid sudo systemctl start keycloak ``` Restaurer `c1gitea` sans arrêter Gitea laisse le service avec un état en mémoire qui ne correspond plus à la base : au mieux des erreurs, au pire des écritures par-dessus la restauration. ### Ne restaurer qu'une table Le cas le plus fréquent — on a vidé une table par erreur, le reste est bon. ```sh # Extraire la seule table qui compte zcat c1_messagerie_db_2026-09-27.sql.gz \ | sed -n '/^-- Table structure for table `abonnes`/,/^-- Table structure for table `abonnements`/p' \ > /tmp/abonnes.sql grep -c "INSERT INTO" /tmp/abonnes.sql # regarder avant d'appliquer sudo mysql c1_messagerie_db < /tmp/abonnes.sql ``` Le `sed` va d'une table à la suivante dans l'ordre du dump : vérifier que la table nommée en second est bien celle qui suit, sinon on emporte trop ou trop peu. ### `dbispconfig` : en dernier recours seulement La configuration d'ISPConfig décrit les sites, les bases, les boîtes mail et les certificats. La restaurer remet **toute la machine** dans un état antérieur, y compris des sites créés depuis. À ne faire que pour reconstruire un serveur perdu, jamais pour corriger un détail. ## Après une restauration - **Vérifier ce qui compte**, pas seulement que la commande a rendu la main : compter les lignes des tables principales, ouvrir une page, envoyer un message de test. - **La messagerie** : vérifier que la file d'envois ne contient pas de vieux messages en attente avant de rouvrir le cron. - **Noter ce qui s'est passé** — dans le ticket, ou ici. Une restauration est rare ; ce qu'on y apprend se perd si personne ne l'écrit. ## Ce qui a été vérifié, et ce qui ne l'est pas **Le 27/09/2026, la sauvegarde de `c1_messagerie_db` a été restaurée pour de vrai** — dans une base séparée, sur un MariaDB 11 de développement. Elle est donc lisible et complète : | | | |---|---| | tables | 21 | | abonnés | 258, dont 238 actifs | | campagnes | 48 | | envois | 255 | | rebonds | 35 | Ce sont les chiffres de la production. Le dump n'est pas une intention : il remonte. **Ce qui reste non vérifié :** - la restauration **sur owni même**, avec l'arrêt du cron et la bascule décrits plus haut. C'est la partie où l'on peut casser quelque chose, et c'est donc celle qui mériterait un essai un jour de calme ; - les six autres bases, dont la sauvegarde a réussi mais qu'on n'a pas rechargées ; - le comportement du cron nocturne dans la durée — poste éteint, réseau coupé, disque plein. Voir le ticket [#1](https://gitea.alpinux.org/alpinux.cedrica5l/alpinux-owni/issues/1).