Sauvegarde quotidienne du serveur principal vers le Raspberry Pi avec Plakar : dépôt chiffré et dédupliqué, transport SFTP.
Fonctionnement
| Élément | Valeur |
|---|---|
| Machine sauvegardée | Serveur principal |
| Dépôt | SSD USB du Raspberry Pi |
| Transport | SFTP |
| Déclenchement | Cron, chaque nuit à 3 h 30 |
| Rétention | 30 jours glissants |
| Script | /root/backup-plakar.sh |
| Journal | /var/log/backup-plakar.log, avec rotation par logrotate |
Chaque exécution :
- exporte toutes les bases en SQL dans <APPDATA>/dumps ;
- sauvegarde les données des conteneurs, la bibliothèque Immich et le contenu de Joplin, en excluant les fichiers bruts des bases ;
- supprime les instantanés de plus de 30 jours.
Script
#!/bin/bash
set -euo pipefail
umask 077 # Ajout : dumps lisibles par root seul
REPO="sftp://<UTILISATEUR>@<HOTE_DEPOT>:<CHEMIN_DEPOT>"
KEYFILE="<FICHIER_PHRASE_SECRETE>"
DUMPS="<APPDATA>/dumps"
mkdir -p "$DUMPS"
# Dumps MariaDB/MySQL
for c in xwiki-mariadb-db passbolt-db castopod-test-db; do
docker exec "$c" sh -c 'exec mysqldump -uroot -p"${MYSQL_ROOT_PASSWORD:-$MARIADB_ROOT_PASSWORD}" --all-databases' \
> "$DUMPS/$c.sql" 2>/dev/null \
|| docker exec "$c" sh -c 'exec mariadb-dump -uroot -p"${MYSQL_ROOT_PASSWORD:-$MARIADB_ROOT_PASSWORD}" --all-databases' \
> "$DUMPS/$c.sql" 2>/dev/null \
|| echo "ATTENTION: dump $c échoué" >&2
done
# Nextcloud : dump via l'utilisateur applicatif
docker exec "$(docker ps -q -f name=nextcloud_db)" sh -c 'exec mariadb-dump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' \
> "$DUMPS/nextcloud.sql" 2>/dev/null \
|| echo "ATTENTION: dump nextcloud échoué" >&2
# Dumps PostgreSQL
for c in joplin-db flus-db wallabag-db immich_postgres vikunja-db jellystat-db n8n-postgres dawarich_db; do # Ajout : dawarich_db
docker exec "$c" sh -c 'exec pg_dumpall -U "$POSTGRES_USER"' \
> "$DUMPS/$c.sql" 2>/dev/null \
|| echo "ATTENTION: dump $c échoué" >&2
done
# Sauvegarde, sans core dumps, binlogs ni fichiers bruts des bases
plakar --concurrency 2 -keyfile "$KEYFILE" at "$REPO" backup \
-ignore "**/joplin-app/core.*" \
-ignore "**/Nextcloud/DB/**" \
-ignore "**/log/mysql/**" \
-ignore "**/xwiki/mariadb/**" \
-ignore "**/store.bak.*/**" \
<APPDATA> \
<BIBLIOTHEQUE_IMMICH>\
<CONTENU_JOPLIN>
# Rétention : 30 jours glissants
plakar -keyfile "$KEYFILE" at "$REPO" rm -before 30d -apply
set -euo pipefail
umask 077 # Ajout : dumps lisibles par root seul
REPO="sftp://<UTILISATEUR>@<HOTE_DEPOT>:<CHEMIN_DEPOT>"
KEYFILE="<FICHIER_PHRASE_SECRETE>"
DUMPS="<APPDATA>/dumps"
mkdir -p "$DUMPS"
# Dumps MariaDB/MySQL
for c in xwiki-mariadb-db passbolt-db castopod-test-db; do
docker exec "$c" sh -c 'exec mysqldump -uroot -p"${MYSQL_ROOT_PASSWORD:-$MARIADB_ROOT_PASSWORD}" --all-databases' \
> "$DUMPS/$c.sql" 2>/dev/null \
|| docker exec "$c" sh -c 'exec mariadb-dump -uroot -p"${MYSQL_ROOT_PASSWORD:-$MARIADB_ROOT_PASSWORD}" --all-databases' \
> "$DUMPS/$c.sql" 2>/dev/null \
|| echo "ATTENTION: dump $c échoué" >&2
done
# Nextcloud : dump via l'utilisateur applicatif
docker exec "$(docker ps -q -f name=nextcloud_db)" sh -c 'exec mariadb-dump -u"$MYSQL_USER" -p"$MYSQL_PASSWORD" "$MYSQL_DATABASE"' \
> "$DUMPS/nextcloud.sql" 2>/dev/null \
|| echo "ATTENTION: dump nextcloud échoué" >&2
# Dumps PostgreSQL
for c in joplin-db flus-db wallabag-db immich_postgres vikunja-db jellystat-db n8n-postgres dawarich_db; do # Ajout : dawarich_db
docker exec "$c" sh -c 'exec pg_dumpall -U "$POSTGRES_USER"' \
> "$DUMPS/$c.sql" 2>/dev/null \
|| echo "ATTENTION: dump $c échoué" >&2
done
# Sauvegarde, sans core dumps, binlogs ni fichiers bruts des bases
plakar --concurrency 2 -keyfile "$KEYFILE" at "$REPO" backup \
-ignore "**/joplin-app/core.*" \
-ignore "**/Nextcloud/DB/**" \
-ignore "**/log/mysql/**" \
-ignore "**/xwiki/mariadb/**" \
-ignore "**/store.bak.*/**" \
<APPDATA> \
<BIBLIOTHEQUE_IMMICH>\
<CONTENU_JOPLIN>
# Rétention : 30 jours glissants
plakar -keyfile "$KEYFILE" at "$REPO" rm -before 30d -apply
Tâche cron, /etc/cron.d/backup-plakar :
30 3 * * * root /root/backup-plakar.sh >> /var/log/backup-plakar.log 2>&1
Ajouter une base
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>.
Vérifier
tail -n 50 /var/log/backup-plakar.log
grep ATTENTION /var/log/backup-plakar.log
sudo plakar -keyfile <FICHIER_PHRASE_SECRETE> at <REPO> ls
grep ATTENTION /var/log/backup-plakar.log
sudo plakar -keyfile <FICHIER_PHRASE_SECRETE> at <REPO> ls
Restaurer
Restaurer un instantané complet dans un dossier temporaire :
sudo plakar -keyfile <FICHIER_PHRASE_SECRETE> at <REPO> restore -to /tmp/restore <ID_INSTANTANE>
Réinjecter une base MariaDB :
docker exec -i <CONTENEUR> sh -c 'exec mariadb -uroot -p"$MARIADB_ROOT_PASSWORD"' < /tmp/restore/<CHEMIN>/dumps/<CONTENEUR>.sql
Réinjecter une base PostgreSQL (dump complet de l'instance) :
docker exec -i <CONTENEUR> sh -c 'exec psql -U "$POSTGRES_USER" -d postgres' < /tmp/restore/<CHEMIN>/dumps/<CONTENEUR>.sql
Incidents connus
| Symptôme | Cause | Solution |
|---|---|---|
| Verrou restant après une exécution interrompue | Le processus a été coupé en cours | Supprimer le verrou orphelin avant de relancer |
| Avertissements VFS | Cache local incohérent | Vider ~/.cache/plakar de root |
| 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 |
| Déconnexion du SSD pendant la sauvegarde | Pont USB en mode UAS | Voir la configuration SSD sur la page Raspberry Pi |