Créer et restaurer des sauvegardes incrémentales avec GNU tar
Utilisez l’option --listed-incremental de GNU tar pour créer une sauvegarde complète suivie d’archives des
modifications. Ce tutoriel vous fournit une chaîne de sauvegarde locale et un exercice de restauration
qui vérifie le contenu des fichiers, y compris ce qui se passe lorsque vous ajoutez, modifiez ou
supprimez des fichiers. Chaque sauvegarde reçoit sa propre copie de l’instantané : une exécution qui
échoue ne peut donc pas faire avancer l’état de la dernière sauvegarde réussie.
Préparer une petite source à sauvegarder
Utilisez Linux, Bash, GNU tar, gzip, ainsi que l’utilitaire standard diff et les utilitaires de
fichiers standard. Les exemples ont été testés avec GNU tar 1.35 et Bash 5 ; vérifiez que
tar --version identifie bien GNU tar. Les archives incrémentales GNU utilisent des extensions que
d’autres implémentations de tar peuvent ne pas prendre en charge. Consultez le
manuel GNU tar sur les sauvegardes incrémentales.
tar --version
bash --version
gzip --version
Exécutez les blocs ci-dessous dans Bash, depuis le même répertoire de départ. Les parenthèses
limitent à chaque bloc les changements de répertoire et les options du shell. La préparation refuse
de réutiliser un répertoire tar-demo existant.
(
set -euo pipefail
mkdir tar-demo
cd -- tar-demo
mkdir -p project/cache backups
printf 'version one\n' > project/notes.txt
printf 'keep this file\n' > project/unchanged.txt
printf 'remove this file later\n' > project/obsolete.txt
printf 'rebuildable cache\n' > project/cache/item.txt
)
Placez backups en dehors de l’arborescence source, comme ici, afin que les archives ne puissent
pas inclure leur propre sortie. Utilisez une source dans laquelle rien n’est écrit pendant une
sauvegarde. Cet exercice porte sur des fichiers ordinaires dans un répertoire local ; il ne produit
pas de sauvegarde cohérente d’une base de données en cours d’exécution et ne préserve pas tous les
types de métadonnées système.
Enregistrer chaque sauvegarde avec son instantané
Le fichier d’instantané enregistre ce que tar a vu lors de la sauvegarde précédente. En l’absence d’instantané, tar lance une sauvegarde complète, dite de niveau zéro. La réutilisation d’un instantané enregistre les modifications survenues depuis celui-ci, et tar le met à jour pendant la création. Copiez l’instantané précédent avant de créer l’archive suivante ; conservez intacte la copie précédente.
Enregistrez ce script sous le nom tar-demo/backup.sh. Son premier argument désigne un nouveau répertoire
de sauvegarde. Le second argument, facultatif, désigne la sauvegarde réussie précédente. Les noms
peuvent contenir des lettres ASCII, des chiffres, des traits de soulignement et des traits d’union.
#!/usr/bin/env bash
set -euo pipefail
unset TAR_OPTIONS
cd -- "$(dirname -- "$0")"
if (( $# < 1 || $# > 2 )); then
printf 'Usage: bash backup.sh NAME [PREVIOUS]\n' >&2
exit 2
fi
for name in "$@"; do
case "$name" in
''|*[!a-zA-Z0-9_-]*)
printf 'Invalid backup name: %s\n' "$name" >&2
exit 2
;;
esac
done
output="backups/$1"
previous=${2:-}
if [[ -n "$previous" ]]; then
for file in complete archive.tar.gz state.snar; do
if [[ ! -f "backups/$previous/$file" ]]; then
printf 'Previous backup is incomplete: %s\n' "$previous" >&2
exit 1
fi
done
fi
# Reserve a new directory before installing cleanup that can remove it.
mkdir -- "$output"
trap 'rm -rf -- "$output"' EXIT
trap 'exit 1' HUP INT TERM
if [[ -n "$previous" ]]; then
cp -- "backups/$previous/state.snar" "$output/state.snar"
fi
tar --create --file=- \
--listed-incremental="$output/state.snar" \
--exclude='project/cache' \
-- project | gzip > "$output/archive.tar.gz"
gzip --test -- "$output/archive.tar.gz"
touch -- "$output/complete"
trap - EXIT
printf 'Created %s\n' "$output"
Exécutez-le une fois sans prédécesseur :
bash tar-demo/backup.sh 00-full
Cette commande crée backups/00-full/archive.tar.gz, state.snar et un marqueur complete vide dans
tar-demo. Le marqueur indique que la création de l’archive et la vérification gzip se sont
terminées. Il ne remplace pas un test de restauration. Si le répertoire de sortie existe déjà, le
script échoue avant d’en modifier le contenu, même lorsque l’entrée n’est pas interactive.
N’exécutez qu’une seule sauvegarde à la fois.
Garder des exclusions cohérentes
L’exclusion --exclude='project/cache', entre guillemets, omet ce répertoire et son contenu. Gardez le même
chemin source et les mêmes exclusions tout au long d’une chaîne. Si vous modifiez ce qui fait partie
de la sauvegarde, lancez une nouvelle sauvegarde complète sous un nouveau nom. Sélectionner les
fichiers récents avec find -mtime ne fournit pas l’enregistrement des entrées de répertoire
supprimées sur lequel repose cette procédure ; laissez tar parcourir le répertoire source.
Gzip compresse chaque archive indépendamment. Il ne détermine pas quels fichiers ont changé. Pour un examen distinct des options de compression, consultez tar avec Zstd et LZ4.
Capturer les modifications dans l’ordre
Modifiez un fichier, ajoutez-en un autre et supprimez le fichier obsolète. La courte pause sépare les horodatages de ce petit exemple sur les systèmes de fichiers dont la résolution temporelle est grossière. La sélection incrémentale de GNU tar dépend des horodatages : ne reculez donc pas l’horloge au cours d’une chaîne.
(
set -euo pipefail
cd -- tar-demo
sleep 1
printf 'version two\n' > project/notes.txt
printf 'new file\n' > project/added.txt
rm -- project/obsolete.txt
bash backup.sh 01-change 00-full
)
Examinez l’archive des fichiers modifiés :
tar --list --gzip --file=tar-demo/backups/01-change/archive.tar.gz
Elle contient project/, project/notes.txt et project/added.txt ; l’ordre peut varier. L’entrée
du répertoire porte aussi des métadonnées incrémentales. unchanged.txt dépend toujours de la
sauvegarde complète, tandis que la suppression de obsolete.txt sera appliquée lors de l’extraction
incrémentale.
Apportez une autre modification, en utilisant 01-change comme prédécesseur :
(
set -euo pipefail
cd -- tar-demo
sleep 1
printf 'version three\n' > project/notes.txt
bash backup.sh 02-change 01-change
)
La chaîne obtenue est 00-full → 01-change → 02-change. Pour atteindre l’état final, conservez les trois
archives. Choisir de nouveau 00-full comme prédécesseur créerait une autre sauvegarde relative
à cette sauvegarde complète, au lieu de prolonger cette séquence.
Restaurer la chaîne complète dans un répertoire vide
L’extraction incrémentale de GNU tar peut supprimer les fichiers absents d’un répertoire archivé.
Ne restaurez que vos propres archives de confiance, dans une nouvelle destination, jamais par-dessus
votre source active. Utilisez --listed-incremental=/dev/null pour chaque extraction, y compris celle de la
sauvegarde complète. L’extraction lit l’historique des répertoires dans les archives ; elle n’a pas
besoin des fichiers .snar enregistrés.
(
set -euo pipefail
cd -- tar-demo
mkdir restore
for generation in 00-full 01-change 02-change; do
test -f "backups/$generation/complete"
gzip --test -- "backups/$generation/archive.tar.gz"
tar --extract --gzip --listed-incremental=/dev/null \
--file="backups/$generation/archive.tar.gz" --directory=restore
done
diff --recursive --exclude=cache project restore/project
test ! -e restore/project/obsolete.txt
printf 'Restored files match the source, excluding cache.\n'
)
En cas de succès, diff n’affiche rien et la dernière ligne confirme la comparaison. Une
fois restauré, notes.txt contient version three, added.txt contient new file, unchanged.txt est
conservé et obsolete.txt a disparu. Aucun répertoire cache n’est restauré. Cette vérification
compare les noms et le contenu des fichiers, pas la propriété, les permissions ni les attributs
étendus.
Une nouvelle exécution refuse un répertoire restore existant avant toute extraction. Après une
restauration qui a échoué, le répertoire peut contenir des résultats partiels ; examinez l’erreur et
choisissez une nouvelle destination vide pour l’exercice suivant. Ne considérez pas une restauration
partielle comme des données récupérées.
Rejeter les sauvegardes incomplètes
La réussite du compresseur ne prouve pas que le programme qui produit son entrée a réussi.
L’option pipefail de Bash fait échouer le pipeline en cas d’échec
de tar | gzip, même si gzip termine avec succès la compression d’une archive partielle. Le script
supprime alors uniquement le nouveau répertoire qu’il avait réservé. L’archive et l’instantané
précédents restent inchangés : réessayez donc avec le même prédécesseur après avoir corrigé le
problème.
N’ajoutez pas --ignore-failed-read à ce script. D’après la documentation de GNU tar, cette option fait que
les entrées manquantes ou illisibles, ainsi que les fichiers modifiés pendant leur lecture,
n’affectent plus le code de sortie. C’est l’inverse de la détection d’une sauvegarde incomplète.
Consultez --ignore-failed-read.
Gardez les diagnostics visibles et rejetez tout code de sortie non nul, y compris le code 1 pour les
fichiers modifiés pendant la création. Arrêtez le processus qui écrit ou utilisez un instantané de
système de fichiers approprié avant de réessayer avec une source qui change.
tar --list vérifie si tar peut lire la structure de l’archive ; les sommes de contrôle des
en-têtes tar ne couvrent pas le contenu des fichiers. gzip --test
vérifie l’intégrité du flux compressé, mais ne peut pas déterminer si un fichier d’entrée a été
omis. C’est pourquoi cette procédure vérifie les échecs des commandes et restaure le résultat
pour le comparer. Les
notes de GNU tar sur la corruption
expliquent cette distinction.
Conserver ensemble les chaînes de sauvegarde complètes
Conservez ensemble l’archive et l’instantané de chaque répertoire de sauvegarde réussie. L’archive
est nécessaire à la récupération ; l’instantané est nécessaire pour prolonger la chaîne. Une coupure
de courant ou SIGKILL peut contourner le trap de nettoyage et laisser un répertoire sans
complete. Une fois qu’aucun processus de sauvegarde n’est en cours, examinez ce répertoire
incomplet et supprimez-le avant de réessayer ; ne l’utilisez pas comme prédécesseur.
Ces copies locales ne protègent pas contre la perte du disque. Copiez les répertoires de sauvegarde terminés vers un stockage distinct et répétez l’exercice de restauration à partir de ces copies. Cet exemple n’implémente ni transfert SSH ni découpage d’archives. Si un système de stockage découpe une archive, tous ses morceaux doivent être réassemblés avant la vérification gzip et la restauration.
Conservez la sauvegarde complète et chaque archive incrémentale nécessaire à un point de restauration conservé. Supprimer des archives individuelles en fonction de leur ancienneté peut rompre cette chaîne. Lancez régulièrement une nouvelle sauvegarde complète, testez son processus de récupération et retirez les chaînes plus anciennes comme des ensembles indivisibles, conformément à votre politique de rétention.
