Verbesserte Version des Backup-Skripts

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