0 Votes

Code source wiki de Sauvegarde

Version 7.1 par john le 2026/09/26 14:56

Afficher les derniers auteurs
1 Sauvegarde quotidienne du serveur principal vers le Raspberry Pi avec [[Plakar>>https://plakar.io]] : dépôt chiffré et dédupliqué, transport SFTP.
2
3 == Fonctionnement ==
4
5 |=Élément|=Valeur
6 |Machine sauvegardée|[[Serveur principal>>Wiki Batcave John.Infrastructure.ServeurPrincipal.WebHome]]
7 |Dépôt|SSD USB du [[Raspberry Pi>>Wiki Batcave John.Infrastructure.RaspberryPi.WebHome]]
8 |Transport|SFTP
9 |Déclenchement|Cron, chaque nuit à 3 h 30
10 |Rétention|30 jours glissants
11 |Script|##/root/backup-plakar.sh##
12 |Journal|##/var/log/backup-plakar.log##, avec rotation par logrotate
13
14 Chaque exécution :
15
16 * exporte toutes les bases en SQL dans ##<APPDATA>/dumps## ;
17 * attend la fin de la sauvegarde Borg de [[Nextcloud>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Nextcloud.WebHome]] AIO (planifiée à 3 h 00), au plus une heure ;
18 * sauvegarde les données des conteneurs, la bibliothèque Immich, le contenu de Joplin et le dépôt Borg de Nextcloud AIO, en excluant les fichiers bruts des bases et les données brutes de Nextcloud ;
19 * supprime les instantanés de plus de 30 jours.
20
21 {{info}}
22 Les fichiers bruts d'une base en fonctionnement ne sont pas fiables pour une restauration. Ce sont les dumps SQL qui font foi.
23
24 Nextcloud AIO fait exception : il n'y a pas de dump, sa base, ses fichiers et sa configuration sont dans son propre dépôt Borg, cohérent car produit conteneurs arrêtés. Plakar copie ce dépôt tel quel, d'où l'attente de la fin de la sauvegarde Borg : copier un dépôt en cours d'écriture donnerait une copie incohérente.
25 {{/info}}
26
27 == Mise en place du dépôt ==
28
29 === Sur le Raspberry Pi (dépôt) ===
30
31 Utilisateur dédié, sans shell :
32
33 {{code language="bash"}}
34 sudo useradd -m -d /srv/plakar -s /usr/sbin/nologin plakar
35 sudo mkdir -p <CHEMIN_DEPOT>
36 sudo chown plakar:plakar <CHEMIN_DEPOT>
37 sudo install -d -m 700 -o plakar -g plakar /srv/plakar/.ssh
38 {{/code}}
39
40 Limitation au SFTP, dans ##/etc/ssh/sshd_config.d/plakar.conf## :
41
42 {{code}}
43 Match User plakar
44 ForceCommand internal-sftp
45 AllowTcpForwarding no
46 X11Forwarding no
47 {{/code}}
48
49 {{code language="bash"}}
50 sudo systemctl reload ssh
51 {{/code}}
52
53 Même en cas de vol de la clé, elle ne donne ni shell ni tunnel : seulement un accès SFTP.
54
55 === Sur le serveur principal (source) ===
56
57 Clé SSH dédiée, sans phrase de passe puisqu'elle sert à la tâche cron :
58
59 {{code language="bash"}}
60 sudo ssh-keygen -t ed25519 -f /root/.ssh/plakar_matrix -N ""
61 {{/code}}
62
63 Contenu de ##/root/.ssh/plakar_matrix.pub## à copier dans ##/srv/plakar/.ssh/authorized_keys## sur le Pi (propriétaire ##plakar##, droits ##600##).
64
65 ##/root/.ssh/config## :
66
67 {{code}}
68 Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com
69
70 Host <HOTE_DEPOT>
71 User plakar
72 IdentityFile /root/.ssh/plakar_matrix
73 {{/code}}
74
75 La ligne ##Ciphers## limite, pour toutes les connexions SSH de root, les algorithmes de chiffrement à des variantes modernes.
76
77 Première connexion, pour valider l'empreinte du Pi (sinon la tâche cron échouera) :
78
79 {{code language="bash"}}
80 sudo sftp <HOTE_DEPOT>
81 {{/code}}
82
83 Phrase secrète du dépôt et création :
84
85 {{code language="bash"}}
86 sudo sh -c 'umask 077; openssl rand -base64 12 > /root/.plakar-pass'
87 sudo plakar -keyfile /root/.plakar-pass at "sftp://plakar@<HOTE_DEPOT>:<CHEMIN_DEPOT>" create
88 {{/code}}
89
90 {{warning}}
91 Le dépôt est chiffré avec cette phrase secrète. Sans elle, les sauvegardes sont illisibles, même pour leur propriétaire : en conserver une copie dans le gestionnaire de mots de passe, jamais uniquement sur le serveur.
92 {{/warning}}
93
94 == Restaurer si le serveur principal est perdu ==
95
96 Le dépôt peut s'ouvrir directement sur le Raspberry Pi, où Plakar est aussi installé, avec la phrase secrète récupérée dans le gestionnaire de mots de passe :
97
98 {{code language="bash"}}
99 sudo plakar at <CHEMIN_DEPOT> ls
100 sudo plakar at <CHEMIN_DEPOT> restore -to /tmp/restore <ID_INSTANTANE>
101 {{/code}}
102
103 Plakar demande alors la phrase secrète de façon interactive.
104
105 == Script ==
106
107 {{code language="bash"}}#!/bin/bash
108 set -euo pipefail
109 umask 077
110
111 # Alertes Telegram
112 . /root/.backup-telegram
113 notifier() {
114 curl -fsS -m 10 "https://api.telegram.org/bot$TG_TOKEN/sendMessage" \
115 -d chat_id="$TG_CHAT" --data-urlencode text="$1" >/dev/null || true
116 }
117 attention() { echo "ATTENTION: $1" >&2; notifier "⚠️ Sauvegarde : $1"; }
118 trap 'notifier "❌ Sauvegarde interrompue à la ligne $LINENO"' ERR
119
120 REPO="sftp://<UTILISATEUR>@<HOTE_DEPOT>:<CHEMIN_DEPOT>"
121 KEYFILE="<FICHIER_PHRASE_SECRETE>"
122 DUMPS="<APPDATA>/dumps"
123 mkdir -p "$DUMPS"
124
125 # Dumps MariaDB/MySQL
126 for c in xwiki-mariadb-db passbolt-db castopod-test-db; do
127 docker exec "$c" sh -c 'exec mysqldump -uroot -p"${MYSQL_ROOT_PASSWORD:-$MARIADB_ROOT_PASSWORD}" --all-databases' \
128 > "$DUMPS/$c.sql" 2>/dev/null \
129 || docker exec "$c" sh -c 'exec mariadb-dump -uroot -p"${MYSQL_ROOT_PASSWORD:-$MARIADB_ROOT_PASSWORD}" --all-databases' \
130 > "$DUMPS/$c.sql" 2>/dev/null \
131 || attention "dump $c échoué"
132 done
133
134 # Dumps PostgreSQL
135 for c in joplin-db flus-db wallabag-db immich_postgres vikunja-db jellystat-db n8n-postgres dawarich_db; do
136 docker exec "$c" sh -c 'exec pg_dumpall -U "$POSTGRES_USER"' \
137 > "$DUMPS/$c.sql" 2>/dev/null \
138 || attention "dump $c échoué"
139 done
140
141 # Attente de la fin de la sauvegarde Borg d'AIO (planifiée à 3h00)
142 attente=0
143 while docker ps --format '{{.Names}}' | grep -qx nextcloud-aio-borgbackup; do
144 if [ "$attente" -ge 3600 ]; then
145 attention "Borg AIO encore en cours après 1 h : dépôt Borg possiblement incohérent"
146 break
147 fi
148 sleep 60
149 attente=$((attente + 60))
150 done
151
152 # Backup avec exclusions : core dumps, binlogs, fichiers bruts des bases
153 # et données brutes de Nextcloud AIO (déjà dans le dépôt Borg, de façon cohérente)
154 plakar --concurrency 2 -keyfile "$KEYFILE" at "$REPO" backup \
155 -ignore "**/joplin-app/core.*" \
156 -ignore "**/Nextcloud-AIO/data/**" \
157 -ignore "**/log/mysql/**" \
158 -ignore "**/xwiki/mariadb/**" \
159 -ignore "**/store.bak.*/**" \
160 <APPDATA> \
161 <BIBLIOTHEQUE_IMMICH> \
162 <CONTENU_JOPLIN> \
163 <DEPOT_BORG_AIO>
164
165 # Rétention : 30 jours glissants
166 plakar -keyfile "$KEYFILE" at "$REPO" rm -before 30d -apply
167 notifier "✅ Sauvegarde terminée"
168 {{/code}}
169
170 Tâche cron, ##/etc/cron.d/backup-plakar## :
171
172 {{code}}
173 30 3 * * * root /root/backup-plakar.sh >> /var/log/backup-plakar.log 2>&1
174 {{/code}}
175
176 == Alertes ==
177
178 Le script envoie un message Telegram à chaque exécution :
179
180 |=Message|=Signification
181 |✅ Sauvegarde terminée|Tout s'est bien passé
182 |⚠️ Sauvegarde : dump … échoué|Un dump de base a échoué, la sauvegarde a continué sans lui
183 |⚠️ Sauvegarde : Borg AIO encore en cours après 1 h …|La sauvegarde Borg de Nextcloud n'était pas finie : la copie du dépôt Borg de cette nuit peut être incohérente, celle des nuits précédentes reste utilisable
184 |❌ Sauvegarde interrompue à la ligne …|Erreur bloquante, la sauvegarde n'a pas eu lieu
185
186 Si aucun message n'arrive un matin, le script n'a pas démarré (serveur éteint, tâche cron cassée).
187
188 Jeton du bot et identifiant du groupe dans ##/root/.backup-telegram##, lisible par root seul :
189
190 {{code language="bash"}}
191 TG_TOKEN="<JETON_BOT>"
192 TG_CHAT="<ID_GROUPE>"
193 {{/code}}
194
195 {{warning}}
196 Dans le script, chaque ligne continuée doit se terminer par ##\## sans rien après, pas même un espace. Une barre oblique inverse suivie d'un espace ne continue plus la ligne : c'est ce qui a interrompu plusieurs sauvegardes en septembre 2026. Contrôle après toute modification : ##sudo bash -n /root/backup-plakar.sh##, et ##cat -A## pour voir les caractères invisibles en fin de ligne.
197 {{/warning}}
198
199 == Ajouter une base ==
200
201 Ajouter le nom du conteneur dans la boucle MariaDB ou PostgreSQL du script, puis exclure ses fichiers bruts avec un ##-ignore## s'ils se trouvent dans ##<APPDATA>##.
202
203 == Vérifier ==
204
205 {{code language="bash"}}
206 tail -n 50 /var/log/backup-plakar.log
207 grep ATTENTION /var/log/backup-plakar.log
208 sudo plakar -keyfile <FICHIER_PHRASE_SECRETE> at <REPO> ls
209 {{/code}}
210
211 == Restaurer ==
212
213 Restaurer un instantané complet dans un dossier temporaire :
214
215 {{code language="bash"}}
216 sudo plakar -keyfile <FICHIER_PHRASE_SECRETE> at <REPO> restore -to /tmp/restore <ID_INSTANTANE>
217 {{/code}}
218
219 Réinjecter une base MariaDB :
220
221 {{code language="bash"}}
222 docker exec -i <CONTENEUR> sh -c 'exec mariadb -uroot -p"$MARIADB_ROOT_PASSWORD"' < /tmp/restore/<CHEMIN>/dumps/<CONTENEUR>.sql
223 {{/code}}
224
225 Réinjecter une base PostgreSQL (dump complet de l'instance) :
226
227 {{code language="bash"}}
228 docker exec -i <CONTENEUR> sh -c 'exec psql -U "$POSTGRES_USER" -d postgres' < /tmp/restore/<CHEMIN>/dumps/<CONTENEUR>.sql
229 {{/code}}
230
231 Restaurer Nextcloud AIO : restaurer le dossier ##<DEPOT_BORG_AIO>## sur l'hôte, déployer le mastercontainer sur une installation neuve, puis choisir la restauration dans l'interface AIO (chemin du dépôt, mot de passe Borg, archive). Voir la page [[Nextcloud>>Wiki Batcave John.Infrastructure.ServeurPrincipal.Nextcloud.WebHome]].
232
233 {{warning}}
234 Une sauvegarde qui n'a jamais été restaurée n'est pas une sauvegarde vérifiée. Faire un essai de restauration de temps en temps, dans un dossier temporaire.
235 {{/warning}}
236
237 == Incidents connus ==
238
239 |=Symptôme|=Cause|=Solution
240 |Verrou restant après une exécution interrompue|Le processus a été coupé en cours|Supprimer le verrou orphelin avant de relancer
241 |Avertissements VFS|Cache local incohérent|Vider ##~~/.cache/plakar## de root
242 |Transfert coupé (EOF SFTP) sur de gros fichiers|Lien Wi-Fi du dépôt instable|Économie d'énergie Wi-Fi désactivée sur le Pi ; passer à ##~-~-concurrency 1## pour une première sauvegarde volumineuse
243 |Déconnexion du SSD pendant la sauvegarde|Pont USB en mode UAS|Voir la configuration SSD sur la page [[Raspberry Pi>>Wiki Batcave John.Infrastructure.RaspberryPi.WebHome]]