Il y avait deux dépôts d'infrastructure et aucune frontière entre eux. La seule distinction défendable — « documentation » d'un côté, « fichiers de configuration » de l'autre — ne tenait plus : infra avait fini par contenir plus de documentation qu'owni, pendant qu'owni décrivait du concret (IP, bases, comptes). Tout vient donc ici : conf/ (vhosts de référence), dns/ (export de zone), services/ (units systemd), scripts/ (sauvegarde) et docs/ (courrier sortant, certificats, sauvegardes, et le déploiement par service). Dans ce sens plutôt que l'inverse parce qu'« owni » nomme la machine, là où « infra » ne dit pas de quoi il s'agit — et c'est le nom que Cédric emploie spontanément, y compris pour le dossier de secrets. Les renvois croisés entre les deux dépôts deviennent des liens internes : un lien vers un dépôt qu'on s'apprête à archiver aurait pourri en silence. Reste une incohérence, signalée plutôt que corrigée à la hâte : docs/admin.md, static.md, wiki.md, dynamic.md et proxy-calendar.md décrivent le déploiement de sites, pas la machine. Ils ont la même place ici que celle que la messagerie n'avait pas — ils devraient rejoindre leurs dépôts. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SFbwnJurBwTs7x7t93ecku
8 KiB
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
cd ~/Projects/org.alpinux.owni/infra
./scripts/sauvegarder-owni.sh --essai # montre, n'écrit rien
./scripts/sauvegarder-owni.sh
En cron, sur le poste — crontab -e :
30 3 * * * cd $HOME/Projects/org.alpinux.owni/infra && ./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 en600. 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.
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é.
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 :
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.
# 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.messageriefonctionne,messagerie.cronnon. 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
# 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.
# 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.