Réserve mémoire de Nova alignée sur l'usage réel de l'hôte (15 Go) : Nova refuse proprement au-delà de 22 VM eleve.petit, au lieu d'en accepter ~41 et de saturer la mémoire. Le plafond du portail suit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
5.9 KiB
Runbook — Sauvegardes vers Dedibackup
Principe
Chaque jour à 03:15 (±5 min aléatoires), le timer systemd sauvegarde-dedibackup.timer
déclenche sauvegarde-dedibackup.service, qui exécute
/usr/local/sbin/sauvegarde-dedibackup.sh (déployé par le rôle Ansible
sauvegardes, voir ansible/roles/sauvegardes/).
Le script :
- Archive
/etc,/home,/rootentar.zst(streaming, sans fichier intermédiaire en clair), plus, depuis l'étape 13, le portail : exportmariadb-dump --single-transactionde sa base (portail.sql, à la racine de l'archive),/var/www/portail/.env(APP_KEY) et/var/www/portail/storage/app. Avant le 2026-10-09, la base du portail n'était pas sauvegardée. - Chiffre le flux avec age, avec la clé publique du serveur (
/root/.age/cle-sauvegardes.txt). - Envoie l'archive chiffrée en FTP vers Dedibackup (
dedibackup-dc3.online.net, joignable uniquement depuis ce serveur). - Le dimanche (jour 7), duplique aussi l'archive du jour comme hebdomadaire.
- Applique la rotation : garde les 7 quotidiennes et 4 hebdomadaires les plus récentes, supprime le reste sur Dedibackup (qui est limité à 1000 fichiers).
Le FTP de Dedibackup n'est pas chiffré en transport, mais le contenu de l'archive l'est déjà (age) avant l'envoi : une interception du transfert ne révèle rien.
Où est la clé de déchiffrement
- Clé privée age :
/root/.age/cle-sauvegardes.txt, permissions600, ne quitte jamais le serveur. - Clé publique (sans risque à diffuser) : voir la sortie de
age-keygenlors du déploiement, ou :sudo grep 'public key' /root/.age/cle-sauvegardes.txt
⚠️ Rappel pour Adam (pas fait automatiquement par Claude) : garder une copie de
/root/.age/cle-sauvegardes.txt en dehors du serveur (gestionnaire de mots de
passe, clé USB chiffrée, etc.). Si le serveur est perdu, cette clé est la seule
façon de déchiffrer les sauvegardes existantes. Exemple pour la récupérer (à
lancer depuis le Mac, le fichier arrive en clair sur le Mac donc à stocker
immédiatement dans un endroit sûr puis à effacer du téléchargement) :
scp paillasse:/root/.age/cle-sauvegardes.txt ~/Desktop/
# puis la mettre immédiatement dans un gestionnaire de mots de passe,
# et supprimer la copie en clair sur le Desktop.
Identifiants Dedibackup
Dans /root/.dedibackup (permissions 600, jamais dans Git) :
DEDIBACKUP_HOST, DEDIBACKUP_USER, DEDIBACKUP_PASS — valeurs entre
guillemets simples si elles contiennent des caractères spéciaux (#, $,
espace…), sinon bash les tronque silencieusement en les lisant.
Lancer une sauvegarde manuellement
ssh paillasse
sudo systemctl start sauvegarde-dedibackup.service
sudo journalctl -u sauvegarde-dedibackup.service --no-pager | tail -20
Lister les sauvegardes présentes sur Dedibackup
sudo bash -c '
source /root/.dedibackup
lftp -u "$DEDIBACKUP_USER,$DEDIBACKUP_PASS" "$DEDIBACKUP_HOST" \
-e "set ftp:ssl-allow no; cls -1 *.tar.zst.age; quit"
'
Test de restauration du portail (automatique, chaque lundi 04:30)
/usr/local/sbin/syllabus-test-restauration (rôle sauvegardes, minuteur
syllabus-test-restauration.timer) télécharge la dernière quotidienne, la
déchiffre, restaure la base dans un conteneur MariaDB jetable (réseau
portail_default, aucun port publié), fait tourner le code du portail contre
cette copie (migrate:status, comptage), compare chaque table à la
production, puis publie syllabus_derniere_restauration_reussie_timestamp.
Alerte RestaurationNonTestee au-delà de 8 jours. Rien n'est écrit en
production ; le conteneur et les fichiers temporaires sont supprimés à la fin.
ssh paillasse "sudo /usr/local/sbin/syllabus-test-restauration" # à la demande
ssh paillasse "journalctl -u syllabus-test-restauration --no-pager | tail -40"
Des écarts de comptage sont normaux si la production a changé depuis la sauvegarde ; une table absente de la copie fait échouer le test.
Restaurer réellement le portail (sinistre)
- Extraire l'archive dans un dossier temporaire (voir plus bas), puis
portail.sqletvar/www/portail/.env. - Redéployer le portail (
ansible-playbook portail.yml) sur le serveur reconstruit ; remettre le.envrestauré avant (APP_KEY identique, sinon sessions et données chiffrées perdues). - Importer la base :
docker exec -i portail_mariadb sh -c 'MYSQL_PWD="$MARIADB_ROOT_PASSWORD" exec mariadb -uroot' < portail.sql(écrase la baseportail). php artisan migrate --force, puis vérifier une connexion prof.
Restaurer un fichier (procédure testée le 2026-10-07)
ssh paillasse
sudo bash -c '
set -e
source /root/.dedibackup
RESTORE_DIR=/tmp/restauration-$(date +%Y%m%d-%H%M)
mkdir -p "$RESTORE_DIR"
# 1. Télécharger l'archive voulue depuis Dedibackup
lftp -u "$DEDIBACKUP_USER,$DEDIBACKUP_PASS" "$DEDIBACKUP_HOST" \
-e "set ftp:ssl-allow no; get quotidienne-AAAAMMJJ.tar.zst.age -o $RESTORE_DIR/archive.tar.zst.age; quit"
# 2. Déchiffrer puis extraire (tout le contenu, ou un chemin précis en argument de tar)
age -d -i /root/.age/cle-sauvegardes.txt "$RESTORE_DIR/archive.tar.zst.age" \
| tar -I zstd -xf - -C "$RESTORE_DIR" etc/un/fichier/precis
ls -la "$RESTORE_DIR"
'
Pour tout restaurer (sans préciser de chemin après -xf -), ne jamais le
faire directement vers /etc, /home ou /root sans réfléchir : ça
écraserait l'état actuel. Toujours restaurer vers un répertoire temporaire
(/tmp/...) puis copier à la main ce qui est nécessaire.
Les chemins dans l'archive n'ont pas de / ni de ./ au début (ex.
etc/hostname, pas /etc/hostname ni ./etc/hostname) — tar les a retirés
à la création.
Vérifié le 2026-10-07
- Sauvegarde envoyée :
quotidienne-20261007.tar.zst.age, confirmée présente sur Dedibackup viacls. - Restauration testée :
etc/hostnameextrait dans/tmp/test-restauration-20261007/, contenu conforme (sd-124443).