Vor ein paar Monaten habe ich ein Skript vorgestellt, mit dem sich unter Linux inkrementelle Backups erstellen lassen. Das Backup habe ich auch schon ein paar Mal gebraucht, weil im Parallelbetrieb mit Windows 8.1 ein paar Daten zerstört wurden. Unter Windows 8.1 binde ich die /home-Partition (Ext4-Dateisystem) mithilfe des Treibers von Ext2FSD ein. Wenn Windows in den Standby-Modus versetzt wird und dann aber (versehentlich) Linux gestartet wird, kann es schnell zum Datenverlust kommen. Zum Glück ist aber jetzt ja immer ein Backup zur Hand. Trotzdem passe ich mittlerweile auf. Ergänzend werde ich vielleicht mal dafür sorgen, dass Linux das erkennt und dann gar nicht erst bootet.
Das Backup-Skript setze ich nun täglich ein und habe es auch nach und nach verbessert. Das Einbinden per UDEV klappte leider nicht so wie erhofft, stattdessen starte ich das Skript jeweils per Hand, aber das war es auch schon an nötigen Handgriffen.
Was mich noch gestört hat, war, dass das Umbenennen von Dateien nicht automatisch erkannt wurde. Wenn Ordner mit vielen Daten umbenannt oder verschoben habe, wurden die Daten komplett neu übertragen, was ja nicht der Sinn eines inkrementellen Backups ist.
Ein Skript, das dies löst, ist hrsync von Daniele Paroli (Lizenz siehe dort): Es wird ein verstecktes „Shadow“-Verzeichnis angelegt, das Hardlinks auf alle Dateien enthält. Dies wird beim Backup mit übertragen. Wenn eine Datei verschoben wird, zeigt die Shadow-Datei noch auf dieselbe Stelle im Dateisystem, sodass rsync beim Backup nur den Link aktualisieren muss. Durch diesen Trick wird auf dem Backupmedium Speicherplatz und beim Backup Zeit und Übertragungsvolumen gespart.
Hier ist das aktualisierte Skript (inklusive der UDEV-Abschnitte, die bei mir nicht mehr aufgerufen werden):
#!/bin/bash Source="/home/henrik" Target="/backup" Config="$Source/.backup.conf" Shadow=".backup.shadow" Logfile="$Target/backup.0/backup.log" # falls von UDEV aufgerufen if [ "$1" = "udev" ]; then # UDEV triggert mehrfach hintereinander. Die Datensicherung aber nur einmal (minimale Wartezeit 10 Minuten) anstoßen if [ -f /tmp/backup.lock ]; then filemtime=$(stat -c %Y /tmp/backup.lock) currtime=$(date +%s) diff=$(($currtime-$filemtime)) if [ $diff -lt 600 ]; then exit; fi fi touch /tmp/backup.lock DISPLAY=:0 kdialog --msgbox "Backup wird erstellt." # Ausgabe umleiten exec 2>&1 >> /tmp/backup.log fi echo "Mounten des Backup-Geräts: $Target" mount "$Target" sleep 1 cd "$Target" # backup.$n verschieben nach backup.{$n+1} i=0 while [ -d "backup.$i" ]; do i=$(($i+1)) done i=$(($i-1)) while [ $i -gt 0 ]; do j=$(($i+1)) echo "Verschiebe backup.$i nach backup.$j" mv "backup.$i" "backup.$j" i=$(($i-1)) done # letztes Backup kopieren mit Hardlinks -> inkrementelles Backup if [ -d "backup.0" ]; then echo "Kopiere backup.0 nach backup.1" cp -al backup.0 backup.1 else mkdir backup.0 fi # Datum setzen und neue Logdatei touch backup.0 if [ -f "$Logfile" ]; then rm "$Logfile" fi cd "$Source" # zu sichernde Ordner aus Konfigurationsdatei lesen IFS=$'\n' dirs=$(cat "$Config") # Ordner sichern for dir in $dirs; do echo "Erstelle Backup für Ordner $dir." | tee -a "$Logfile" # Ordner wurde noch nie gesichert: Shadow-Ordner anlegen if [ ! -d "$dir/$Shadow" ]; then echo "Erstelle Shadow-Ordner" | tee -a "$Logfile" rsync -a --delete --link-dest=".." --exclude="/$Shadow" "$dir"/ "$dir/$Shadow" | tee -a "$Logfile" fi # synchronisieren echo "synchronisiere" | tee -a "$Logfile" rsync -axXhHv --stats --no-inc-recursive --numeric-ids --delete --delete-after "$dir"/ "$Target/backup.0/$dir/" | tee -a "$Logfile" status=$? if [ $status -eq 0 ]; then echo "Synchronisation abgeschlossen." | tee -a "$Logfile" echo "Quell-Shadow-Ordner aktualisieren" | tee -a "$Logfile" rsync -a --delete --link-dest=".." --exclude="/$Shadow" "$dir"/ "$dir/$Shadow" echo "Ziel-Shadow-Ordner aktualisieren" | tee -a "$Logfile" rsync -a --delete --link-dest=".." --exclude="/$Shadow" "$Target/backup.0/$dir/" "$Target/backup.0/$dir/$Shadow" else echo "Synchronisation ist fehlgeschlagen. (Status $status)" | tee -a "$Logfile" fi echo "" | tee -a "$Logfile" done echo "Unmounten von $Target" umount "$Target" if [ "$1" = "udev" ]; then DISPLAY=:0 kdialog --msgbox "Datensicherung abgeschlossen." echo "Datensicherung abgeschlossen." fi