Intermédiaire 5 min de lecture · 1 081 mots

Un script de sauvegarde complet avec rsync : snapshots, rotation et notification

Des sauvegardes datées qui ne prennent de la place que pour ce qui a changé, une rotation automatique et un message sur le téléphone : un script bash de 40 lignes, expliqué pas à pas.

Un script de sauvegarde complet avec rsync : snapshots, rotation et notification
Intermédiaire · 2026.10.22

Copier un dossier ailleurs avec cp -r, ce n’est pas une sauvegarde. C’est une copie, écrasée à chaque fois, qui reproduit aussi bien tes données que tes erreurs : si tu supprimes un fichier par accident, la copie suivante le supprime aussi.

Ce qu’on veut, c’est une sauvegarde datée : un dossier par jour, chacun montrant l’état des fichiers à ce moment-là. Et sans que chaque jour occupe la taille totale des données. C’est exactement ce que permet rsync avec l’option --link-dest.

Le principe : des liens physiques

Sous Linux, un fichier peut avoir plusieurs « noms » qui pointent vers les mêmes données sur le disque : ce sont les liens physiques (hard links). Le contenu n’est stocké qu’une fois.

Avec --link-dest, rsync compare la source à la sauvegarde précédente :

  • un fichier inchangé n’est pas recopié : il est simplement lié à celui de la veille (pas d’espace supplémentaire) ;
  • un fichier modifié ou nouveau est copié.

Chaque dossier daté ressemble donc à une copie complète, que tu peux parcourir normalement, mais il ne pèse que ce qui a changé.

/mnt/sauvegardes/data/
├── 2026-10-01_0330/    ← copie complète (la première fois)
├── 2026-10-02_0330/    ← n'a pris de place que pour les changements
├── 2026-10-03_0330/
└── latest -> 2026-10-03_0330

Le script

#!/usr/bin/env bash
set -euo pipefail

SOURCE="/srv/data/"
DEST="/mnt/sauvegardes/data"
GARDER=14
NTFY_URL="${NTFY_URL:-}"

HORODATAGE="$(date +%F_%H%M)"
CIBLE="$DEST/$HORODATAGE"
LATEST="$DEST/latest"

notifier() {   # $1 = titre, $2 = message, $3 = priorité (facultatif)
  [[ -n "$NTFY_URL" ]] || return 0
  curl -fsS -H "Title: $1" -H "Priority: ${3:-default}" -d "$2" "$NTFY_URL" >/dev/null || true
}

echec() {
  echo "ERREUR : $1" >&2
  notifier "Sauvegarde ECHOUEE" "$(hostname) : $1" high
  exit 1
}
trap 'echec "erreur à la ligne $LINENO"' ERR

[[ -d "$SOURCE" ]] || echec "source introuvable : $SOURCE"
mkdir -p "$DEST"

# 1. Copie vers un dossier « partiel », puis renommage : jamais de sauvegarde à moitié finie
rsync -aH --delete --link-dest="$LATEST" "$SOURCE" "$CIBLE.partiel/"
mv "$CIBLE.partiel" "$CIBLE"
ln -sfn "$CIBLE" "$LATEST"

# 2. Rotation : on garde les $GARDER sauvegardes les plus récentes
find "$DEST" -mindepth 1 -maxdepth 1 -type d -name '20*' ! -name '*.partiel' 
  | sort -r | tail -n +"$((GARDER + 1))" 
  | while IFS= read -r ancienne; do
      rm -rf -- "$ancienne"
    done

notifier "Sauvegarde OK" "$(hostname) : $HORODATAGE"
echo "Sauvegarde terminée : $CIBLE"

Ce que fait chaque partie

  • -a (archive) : conserve les permissions, les dates, les propriétaires, les liens symboliques. Presque toujours ce qu’on veut.
  • -H : conserve les liens physiques qui existent déjà dans la source.
  • --delete : supprime dans la destination ce qui n’existe plus dans la source, pour que le snapshot reflète fidèlement l’état du jour.
  • --link-dest="$LATEST" : le dossier de référence pour les fichiers inchangés.

À la toute première exécution, latest n’existe pas : rsync affiche un avertissement sur ce link_dest, puis fait une copie complète. C’est normal.

Le slash final de SOURCE="/srv/data/" compte : avec lui, rsync copie le contenu du dossier ; sans lui, il copierait le dossier data lui-même dans la destination.

Le dossier .partiel et le renommage

On copie dans 2026-10-03_0330.partiel, puis on renomme en 2026-10-03_0330 seulement quand tout s’est bien passé. Si la machine s’éteint au milieu, il reste un dossier .partiel (reconnaissable, et ignoré par la rotation grâce au ! -name '*.partiel'), jamais une fausse sauvegarde qui a l’air complète.

trap … ERR

Grâce à set -e, toute commande qui échoue déclenche le piège ERR, qui appelle la fonction echec : un message d’erreur, une notification urgente, et l’arrêt. $LINENO donne la ligne en cause, pratique pour le dépannage.

La rotation

find … | sort -r liste les dossiers datés du plus récent au plus ancien, et tail -n +15 ignore les 14 premières lignes pour ne garder que tous les suivants : ce sont ceux qu’on supprime. La date au format AAAA-MM-JJ_HHMM est triable alphabétiquement, ce qui rend le tri trivial. Le ! -name '*.partiel' exclut les copies inachevées du décompte, et cette formulation fonctionne aussi bien sous Debian que sous macOS.

Supprimer un ancien snapshot ne casse pas les suivants : grâce aux liens physiques, les données restent tant qu’au moins un nom pointe dessus.

La notification

Un message sur le téléphone à la fin, de succès comme d’échec. J’ai détaillé la mise en place de ntfy dans Être alerté sur son téléphone quand ça plante. Le script ne l’impose pas : si NTFY_URL est vide, notifier ne fait rien.

Tester sans risque

Avant la première vraie exécution :

# 1. Simulation : rsync affiche ce qu'il ferait, sans rien copier
rsync -aH --delete --dry-run --itemize-changes /srv/data/ /tmp/test-sauvegarde/

# 2. Syntaxe et pièges classiques
shellcheck sauvegarde.sh

Puis une exécution réelle sur un petit dossier de test, en modifiant SOURCE et DEST. Regarde ensuite l’occupation avec du -sh /tmp/test/* : le deuxième snapshot doit être minuscule.

Sauvegarder vers une autre machine

La destination peut être distante, via SSH :

rsync -aH --delete -e ssh --link-dest="../latest" /srv/data/ utilisateur@nas:/volume1/sauvegardes/data/2026-10-03/

Avec une destination distante, --link-dest désigne un chemin sur la machine de destination, et un chemin relatif est interprété à partir du dossier cible. Pour une configuration sans mot de passe, utilise une clé SSH dédiée, limitée à cette tâche.

La seule vérification qui compte

Une sauvegarde qui n’a jamais été restaurée est une hypothèse. Une fois par mois, restaure un dossier de test :

rsync -a /mnt/sauvegardes/data/latest/documents/ /tmp/restauration-test/
diff -r /srv/data/documents /tmp/restauration-test && echo "identique"

À retenir

  • --link-dest donne des snapshots complets pour le prix des seuls changements.
  • Copier dans un dossier .partiel puis renommer évite les fausses sauvegardes.
  • Une rotation simple (garder les N dernières) suffit pour un homelab.
  • Un message de succès et d’échec : une sauvegarde silencieuse est une sauvegarde dont on ne sait pas si elle marche.
  • Teste la restauration, pas seulement la copie.

Il reste à lancer ce script chaque nuit, sans qu’il s’exécute en double : c’est le sujet de l’article suivant.

Une remarque, un retour ?

Cet article est vivant - corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisis celui qui te convient.

Laisser un commentaire