Sauvegarder ses fichiers avec rsync, tar, cron et GPG
J'ai écrit cet article à l'origine en décembre 2011. J'étais architecte logiciel chez un registrar de noms de domaine, fan d'outils Unix, et je voulais un script de sauvegarde qui ne dépende ni de Time Machine ni de Dropbox. L'article a bien marché. Le script fonctionnait.
Quinze ans plus tard, je l'ai ressorti, j'ai un peu grincé des dents, et je l'ai réécrit.
Les quatre outils sont les mêmes : rsync, tar, cron et gpg. La stratégie aussi. Ce qui a changé, c'est tout ce qui les entoure — les choses qu'on n'apprend qu'en faisant tourner du code assez longtemps pour le voir casser de façons imprévues.
Voici cette réécriture. Le script est sur GitHub et tourne sur tout système POSIX — macOS, Linux, FreeBSD. L'original ne marchait que sur macOS. C'est une des premières choses que j'ai corrigées.
La stratégie
Toujours la même approche en quatre couches :
- rsync — Snapshot incrémental avec hard links (seuls les fichiers modifiés consomment de l'espace)
- tar — Archives compressées pour les snapshots plus anciens
- gpg — Chiffrement avant tout envoi hors de la machine
- cron — Rien de tout ça ne s'exécute à la main
La nouveauté, c'est la rotation : les archives quotidiennes se regroupent en hebdomadaires, les hebdomadaires en mensuelles. En 2011, j'archivais quotidiennement et basta. Ça va, jusqu'au jour où on a besoin de quelque chose vieux de trois mois et qu'on réalise qu'on l'a écrasé.
Ce que j'avais raté en 2011
Le script n'avait aucune gestion d'erreurs
Le script original continuait tranquillement si rsync échouait. Si le snapshot cassait en silence, on lançait tar sur un répertoire vide et on chiffrait le résultat. On ne le découvrait qu'au moment de la restauration — le pire moment possible.
Le correctif tient en deux caractères :
set -eu
-e quitte immédiatement si une commande retourne un code non nul. -u traite les variables non définies comme des erreurs au lieu de les développer silencieusement en chaînes vides. À utiliser systématiquement. Je ne le faisais pas en 2011, je ne savais pas.
La commande date ne marchait que sur macOS
L'original utilisait :
YESTERDAY=$(date -v -1d +%Y%m%d)
C'est la syntaxe BSD date — macOS. Sur Linux (GNU date), c'est :
YESTERDAY=$(date -d "1 day ago" +%Y%m%d)
Complètement différent. Le script cassait en silence sur toute machine Linux. La version actuelle détecte la variante installée au démarrage et branche en conséquence :
date_subtract() {
_fmt="$1" _unit="$2" _n="$3"
if date -d "now" +%s >/dev/null 2>&1; then
# GNU date (Linux)
case "$_unit" in
d) date -d "$_n day ago" +"$_fmt" ;;
m) date -d "$_n month ago" +"$_fmt" ;;
esac
else
# BSD date (macOS)
case "$_unit" in
d) date -v "-${_n}d" +"$_fmt" ;;
m) date -v "-${_n}m" +"$_fmt" ;;
esac
fi
}
L'astuce de détection : GNU date accepte -d, BSD date non. On teste une fois, on branche proprement.
Des chemins en dur
Le script original avait les chemins hardcodés : mon nom d'utilisateur, mon e-mail, le chemin de mon disque. C'était un script perso, donc ça marchait pour moi. Mais le partager ou le réutiliser sur une autre machine devenait un exercice de rechercher-remplacer.
La version actuelle passe par un fichier de configuration (backup.conf) avec des valeurs par défaut raisonnables :
BACKUP_SOURCE_DIR="${BACKUP_SOURCE_DIR:-$HOME/Documents}"
BACKUP_HOME="${BACKUP_HOME:-$HOME/backups}"
GPG_RECIPIENT="${GPG_RECIPIENT:-}" # vide = pas de chiffrement
${VAR:-default} est du parameter expansion POSIX — si VAR n'est pas définie ou vide, la valeur par défaut s'applique. Pas de commandes externes, pas de conditionnelles.
Le cœur : rsync avec hard links
Cette partie n'a pas beaucoup changé — elle était déjà correcte.
rsync -aH --link-dest="$CURRENT_LINK" "$BACKUP_SOURCE_DIR" "$SNAPSHOT_DIR/$NOW"
--link-dest est la clé. Pour chaque fichier inchangé depuis le dernier snapshot, rsync crée un hard link au lieu d'une copie. Résultat : un snapshot qui ressemble à une copie complète mais ne consomme quasiment pas d'espace disque supplémentaire.
$ du -sch backups/snapshots/*
143M snapshots/202602211400
16K snapshots/202602211430
16K snapshots/202602211500
178M total
Trois snapshots. Une journée de données réelles.
Un point à connaître : les hard links ne fonctionnent pas sur NTFS ou FAT (montages Samba, disques Windows). rsync bascule en silence vers des copies complètes. Mieux vaut le savoir avant d'en avoir besoin.
Après chaque snapshot, on met à jour le lien symbolique current vers le plus récent :
ln -snf "$LATEST" "$CURRENT_LINK"
Le flag -n compte ici. Sans lui, ln suit le symlink existant s'il pointe vers un répertoire et imbrique le nouveau lien dans l'ancien snapshot. Avec -n, il remplace le symlink. Un caractère, un comportement piégeux si on le rate.
Archivage et rotation
Les snapshots sont non compressés et navigables — facile de récupérer un fichier d'il y a une heure. Les archives sont compressées et chiffrées — pour le stockage long terme.
Le pipeline actuel :
- Snapshots plus anciens qu'aujourd'hui → archive quotidienne
.tar.gz - Archives quotidiennes de plus d'une semaine → archive hebdomadaire
- Archives hebdomadaires de plus d'un mois → archive mensuelle
$BACKUP_HOME/
├── current -> snapshots/202602211430
├── snapshots/
│ ├── 202602211400/
│ └── 202602211430/
└── archives/
├── daily/
│ └── 20260220.tar.gz.gpg
├── weekly/
│ └── 202601.WK_2.tar.gz.gpg
└── monthly/
└── 202512.tar.gz.gpg
Créer une archive des snapshots d'hier :
if [ $(ls -d "$SNAPSHOT_DIR/$YESTERDAY"* 2>/dev/null | wc -l) != "0" ]; then
tar -czf "$ARCHIVES_DIR/$YESTERDAY.tar.gz" \
"$SNAPSHOT_DIR/$YESTERDAY"* \
&& rm -rf "$SNAPSHOT_DIR/$YESTERDAY"*
fi
Le && avant rm n'est pas optionnel. On ne supprime que si l'archive a réussi.
Chiffrement avec GPG
Tout ce qui quitte la machine est chiffré. Cette partie n'a pas changé depuis 2011, à un ajout près : --batch --yes pour l'exécution non interactive dans cron.
gpg --batch --yes -r "$GPG_RECIPIENT" --encrypt-files archive.tar.gz
Sans --batch --yes, GPG demande confirmation dans certains cas. Un job cron ne peut pas répondre aux prompts. Il échoue en silence, et on le découvre plus tard.
Si on n'a pas de clé asymétrique configurée, le chiffrement symétrique fait l'affaire pour les sauvegardes locales/mono-utilisateur :
gpg --symmetric --cipher-algo AES256 archive.tar.gz
Pour déchiffrer le jour où on en a besoin :
gpg --decrypt-files archive.tar.gz.gpg
Planifier avec cron
crontab -e
Toutes les 30 minutes pendant les heures de bureau en semaine :
*/30 8-18 * * 1-5 /path/to/backup.sh
Le caractère % est spécial dans crontab — il est traité comme un saut de ligne. Si la commande contient % (fréquent dans les chaînes de format date), il faut les échapper avec \%.
Tests
En 2011, je testais le script en le lançant et en regardant la sortie. C'est comme ça que la plupart des scripts sont « testés ».
La version actuelle a une suite de tests en shell POSIX qui couvre les helpers de portabilité, le chargement de configuration et le pipeline de bout en bout — y compris la déduplication par hard links. Elle tourne sur Ubuntu (GNU) et macOS (BSD) via GitHub Actions, avec en prime l'analyse statique ShellCheck.
Ce n'est pas que les scripts de sauvegarde aient besoin de tests sophistiqués. C'est que les scripts qui tournent sans surveillance à 2 h du matin, sur une machine que personne ne regarde, doivent être fiables. Les tests, c'est comme ça qu'on construit cette confiance avant d'en avoir besoin.
Restauration
Les snapshots sont de simples répertoires — on copie ce dont on a besoin :
cp ~/backups/current/Documents/report.txt ~/Documents/
Depuis une archive chiffrée :
gpg --decrypt-files ~/backups/archives/daily/20260220.tar.gz.gpg
tar -xzf ~/backups/archives/daily/20260220.tar.gz -C /tmp/restore/
À tester avant d'en avoir besoin. Une sauvegarde qu'on n'a jamais restaurée, c'est une sauvegarde qu'on n'a pas vérifiée.
Pour conclure
Les outils — rsync, tar, cron, gpg — sont les mêmes qu'en 2011. Ils seront encore là en 2036. C'est ça, les fondamentaux Unix : ça ne se démode pas.
Ce qui change, c'est le savoir-faire autour. La portabilité. La gestion d'erreurs. La configuration. Les tests. La différence entre un script qui marche sur sa machine et un script auquel on peut faire confiance.
Le code source complet est sur github.com/aadlani/backup-manager. Clonez-le, adaptez-le, faites-en le vôtre.