0 Votes

Code source wiki de Sauvegarde

Version 6.1 par john le 2026/09/19 20:00

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 * sauvegarde les données des conteneurs, la bibliothèque Immich et le contenu de Joplin, en excluant les fichiers bruts des bases ;
18 * supprime les instantanés de plus de 30 jours.
19
20 {{info}}
21 Les fichiers bruts d'une base en fonctionnement ne sont pas fiables pour une restauration. Ce sont les dumps SQL qui font foi.
22 {{/info}}
23
24 == Mise en place du dépôt ==
25
26 === Sur le Raspberry Pi (dépôt) ===
27
28 Utilisateur dédié, sans shell :
29
30 {{code language="bash"}}
31 sudo useradd -m -d /srv/plakar -s /usr/sbin/nologin plakar
32 sudo mkdir -p <CHEMIN_DEPOT>
33 sudo chown plakar:plakar <CHEMIN_DEPOT>
34 sudo install -d -m 700 -o plakar -g plakar /srv/plakar/.ssh
35 {{/code}}
36
37 Limitation au SFTP, dans ##/etc/ssh/sshd_config.d/plakar.conf## :
38
39 {{code}}
40 Match User plakar
41 ForceCommand internal-sftp
42 AllowTcpForwarding no
43 X11Forwarding no
44 {{/code}}
45
46 {{code language="bash"}}
47 sudo systemctl reload ssh
48 {{/code}}
49
50 Même en cas de vol de la clé, elle ne donne ni shell ni tunnel : seulement un accès SFTP.
51
52 === Sur le serveur principal (source) ===
53
54 Clé SSH dédiée, sans phrase de passe puisqu'elle sert à la tâche cron :
55
56 {{code language="bash"}}
57 sudo ssh-keygen -t ed25519 -f /root/.ssh/plakar_matrix -N ""
58 {{/code}}
59
60 Contenu de ##/root/.ssh/plakar_matrix.pub## à copier dans ##/srv/plakar/.ssh/authorized_keys## sur le Pi (propriétaire ##plakar##, droits ##600##).
61
62 ##/root/.ssh/config## :
63
64 {{code}}
65 Ciphers aes128-ctr,aes192-ctr,aes256-ctr,aes128-gcm@openssh.com,aes256-gcm@openssh.com,chacha20-poly1305@openssh.com
66
67 Host <HOTE_DEPOT>
68 User plakar
69 IdentityFile /root/.ssh/plakar_matrix
70 {{/code}}
71
72 La ligne ##Ciphers## limite, pour toutes les connexions SSH de root, les algorithmes de chiffrement à des variantes modernes.
73
74 Première connexion, pour valider l'empreinte du Pi (sinon la tâche cron échouera) :
75
76 {{code language="bash"}}
77 sudo sftp <HOTE_DEPOT>
78 {{/code}}
79
80 Phrase secrète du dépôt et création :
81
82 {{code language="bash"}}
83 sudo sh -c 'umask 077; openssl rand -base64 12 > /root/.plakar-pass'
84 sudo plakar -keyfile /root/.plakar-pass at "sftp://plakar@<HOTE_DEPOT>:<CHEMIN_DEPOT>" create
85 {{/code}}
86
87 {{warning}}
88 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.
89 {{/warning}}
90
91 == Restaurer si le serveur principal est perdu ==
92
93 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 :
94
95 {{code language="bash"}}
96 sudo plakar at <CHEMIN_DEPOT> ls
97 sudo plakar at <CHEMIN_DEPOT> restore -to /tmp/restore <ID_INSTANTANE>
98 {{/code}}
99
100 Plakar demande alors la phrase secrète de façon interactive.
101
102 == Script ==
103
104 {{code language="bash"}}#!/bin/bash
105 set -euo pipefail
106 umask 077
107
108 # Alertes Telegram
109 . /root/.backup-telegram
110 notifier() {
111 curl -fsS -m 10 "https://api.telegram.org/bot$TG_TOKEN/sendMessage" \
112 -d chat_id="$TG_CHAT" --data-urlencode text="$1" >/dev/null || true
113 }
114 attention() { echo "ATTENTION: $1" >&2; notifier "⚠️ Sauvegarde : $1"; }
115 trap 'notifier "❌ Sauvegarde interrompue à la ligne $LINENO"' ERR
116
117 REPO="sftp://<UTILISATEUR>@<HOTE_DEPOT>:<CHEMIN_DEPOT>"
118 KEYFILE="<FICHIER_PHRASE_SECRETE>"
119 DUMPS="<APPDATA>/dumps"
120 mkdir -p "$DUMPS"
121
122 # Dumps MariaDB/MySQL
123 for c in xwiki-mariadb-db passbolt-db castopod-test-db; do
124 docker exec "$c" sh -c 'exec mysqldump -uroot -p"${MYSQL_ROOT_PASSWORD:-$MARIADB_ROOT_PASSWORD}" --all-databases' \
125 > "$DUMPS/$c.sql" 2>/dev/null \
126 || docker exec "$c" sh -c 'exec mariadb-dump -uroot -p"${MYSQL_ROOT_PASSWORD:-$MARIADB_ROOT_PASSWORD}" --all-databases' \
127 > "$DUMPS/$c.sql" 2>/dev/null \
128 || attention "dump $c échoué"
129 done
130
131 # Nextcloud : dump via l'utilisateur applicatif
132 docker exec "$(docker ps -q -f name=nextcloud_db)" sh -c 'exec mariadb-dump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' \
133 > "$DUMPS/nextcloud.sql" 2>/dev/null \
134 || attention "dump nextcloud échoué"
135
136 # Dumps PostgreSQL
137 for c in joplin-db flus-db wallabag-db immich_postgres vikunja-db jellystat-db n8n-postgres dawarich_db; do
138 docker exec "$c" sh -c 'exec pg_dumpall -U "$POSTGRES_USER"' \
139 > "$DUMPS/$c.sql" 2>/dev/null \
140 || attention "dump $c échoué"
141 done
142
143 # Sauvegarde, sans core dumps, binlogs ni fichiers bruts des bases
144 plakar --concurrency 2 -keyfile "$KEYFILE" at "$REPO" backup \
145 -ignore "**/joplin-app/core.*" \
146 -ignore "**/Nextcloud/DB/**" \
147 -ignore "**/log/mysql/**" \
148 -ignore "**/xwiki/mariadb/**" \
149 -ignore "**/store.bak.*/**" \
150 <APPDATA> \
151 <BIBLIOTHEQUE_IMMICH> \
152 <CONTENU_JOPLIN>
153
154 # Rétention : 30 jours glissants
155 plakar -keyfile "$KEYFILE" at "$REPO" rm -before 30d -apply
156 notifier "✅ Sauvegarde terminée"{{/code}}
157 ~{~{/code}}
158
159 Tâche cron, ##/etc/cron.d/backup-plakar## :
160
161 {{code}}
162 30 3 * * * root /root/backup-plakar.sh >> /var/log/backup-plakar.log 2>&1
163 {{/code}}
164
165 == Alertes ==
166
167 Le script envoie un message Telegram à chaque exécution :
168
169 |=Message|=Signification
170 |✅ Sauvegarde terminée|Tout s'est bien passé
171 |⚠️ Sauvegarde : dump … échoué|Un dump de base a échoué, la sauvegarde a continué sans lui
172 |❌ Sauvegarde interrompue à la ligne …|Erreur bloquante, la sauvegarde n'a pas eu lieu
173
174 Si aucun message n'arrive un matin, le script n'a pas démarré (serveur éteint, tâche cron cassée).
175
176 Jeton du bot et identifiant du groupe dans ##/root/.backup-telegram##, lisible par root seul :
177
178 {{code language="bash"}}
179 TG_TOKEN="<JETON_BOT>"
180 TG_CHAT="<ID_GROUPE>"
181 {{/code}}
182
183 {{warning}}
184 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.
185 {{/warning}}
186
187 == Ajouter une base ==
188
189 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>##.
190
191 == Vérifier ==
192
193 {{code language="bash"}}
194 tail -n 50 /var/log/backup-plakar.log
195 grep ATTENTION /var/log/backup-plakar.log
196 sudo plakar -keyfile <FICHIER_PHRASE_SECRETE> at <REPO> ls
197 {{/code}}
198
199 == Restaurer ==
200
201 Restaurer un instantané complet dans un dossier temporaire :
202
203 {{code language="bash"}}
204 sudo plakar -keyfile <FICHIER_PHRASE_SECRETE> at <REPO> restore -to /tmp/restore <ID_INSTANTANE>
205 {{/code}}
206
207 Réinjecter une base MariaDB :
208
209 {{code language="bash"}}
210 docker exec -i <CONTENEUR> sh -c 'exec mariadb -uroot -p"$MARIADB_ROOT_PASSWORD"' < /tmp/restore/<CHEMIN>/dumps/<CONTENEUR>.sql
211 {{/code}}
212
213 Réinjecter une base PostgreSQL (dump complet de l'instance) :
214
215 {{code language="bash"}}
216 docker exec -i <CONTENEUR> sh -c 'exec psql -U "$POSTGRES_USER" -d postgres' < /tmp/restore/<CHEMIN>/dumps/<CONTENEUR>.sql
217 {{/code}}
218
219 {{warning}}
220 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.
221 {{/warning}}
222
223 == Incidents connus ==
224
225 |=Symptôme|=Cause|=Solution
226 |Verrou restant après une exécution interrompue|Le processus a été coupé en cours|Supprimer le verrou orphelin avant de relancer
227 |Avertissements VFS|Cache local incohérent|Vider ##~~/.cache/plakar## de root
228 |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
229 |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]]