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
233 lines
8 KiB
Markdown
233 lines
8 KiB
Markdown
# 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/infra
|
|
./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/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
|
|
> 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).
|