Inkrementelle Datensicherung mit rsync
Einleitung
Das Werkzeug rsync kann Daten von einem Verzeichnis zum anderen spiegeln. Nachdem man alle Daten einmal gesichert hat, werden in den folgenden Aufrufen nur noch die Differenzen gespeichert. Normalerweise will man aber nicht nur den aktuellen Stand halten, sondern auch noch die letzten paar Stände, z.B. eine monatliche, eine wöchentliche und ein tägliche Sicherung. Mit der Option --link-dest=DIR kann man ein Verzeichnis angeben, in da man zuletzt gesichert hat. Ohnen nun den Inhalt dieses Verzeichnisses zu überschreiben, wird ein neues Verzeichnis angelegt, in das nur die Dateien kopiert werden, zum angegebenen Verzeichnis geändert haben. Alle anderen werden als harte Verweise angelegt. Ein harter Verweis verhält sich wie eine Kopie, nur dass der Inhalt der Datei nur einmal gespeichert wird. Man spart folglich Platz und muss nur die Differenzen sichern.
Die Sicherung
Ich möchte nun folgende Sicherungsstrategie:
- Zwei monatliche Sicherungen, ausgeführt am 1. unter
/media/backup/${HOSTNAME}-Monat.1und/media/backup/${HOSTNAME}-Monat.2 - Zwei wöchentliche Sicherungen ausgeführt am Samstag unter
/media/backup/${HOSTNAME}-Woche.1und/media/backup/${HOSTNAME}-Woche.2 - Zwei tägliche Sicherungen unter
/media/backup/${HOSTNAME}-Tag.1und/media/backup/${HOSTNAME}-Tag.2
Dabei markiert die Zahl das Alter der Sicherung in Monaten, Wochen, respektive Tagen. Die Sicherung erfolgt mit rsync über einen cron-Auftrag. Als Bedingung wird erst geprüft, ob eine externe Harddisk unter /media/backup eingehängt wurde. Hierzu schreibe ich folgendes Skript und speichere es unter
/etc/cron.daily/waeckerlin.org-backup, unter
/etc/cron.weekly/waeckerlin.org-backup und unter
/etc/cron.monthly/waeckerlin.org-backup und gebe ihm jeweils ausführrechte (mit chmod ugo+x). SuSE sorgt dann dafür, dass täglich alle Skripte unter /etc/cron.daily ausgeführt werden. Ebenso entsprechend in den anderen Verzeichnissen.
Alternativ dazu habe ich auf meinem Notebook eine Sicherungsstrategie, wo ich nur sichern will, wenn das Notebook zu Hause an die Sicherungsharddisk angeschlossen wurde. Diese ist verschlüsselt und wird mittels udev automatisch eingebunden, wenn der Schlüssel in dem Moment eingebunden ist, wo die Harddisk eingeschaltet wird. Dann startet auch automatisch das Backup.1) Hier will ich gegen die letzte manuelle Sicherung sichern.
#!/bin/bash
# Setup:
# for period in daily weekly monthly; do cd /etc/cron.$period; for h in dev.swisssign.com dev0001 dev0002 dev0004 devmac0001; do echo sudo ln -s ~/bin/backup-generic.sh backup-$h.sh; done; done
#################################################################################
# Konfiguration #
#################################################################################
# Liste der Dateisysteme, die gesichert werden
HOST=$(basename ${0##*/backup-} .sh)
if [ "$HOST" = "generic" -o -z "$HOST" ]; then
HOST=$HOSTNAME
fi
BACKUP="-e ssh"
TARGET="/var/backup"
SOURCES=$(ssh $HOST mount | awk '$2=="on" {print $3}' | sed '/\/\(proc\|sys\|dev\|var\|lib\)\|'"${TARGET//\//\\/}"'\|\(archive\|exchange\)$/d')
ARGS="-aqx --exclude ${TARGET}"
LOCK=${TARGET}/${HOST}.lock
# Anzahl Kopien, die aufzubewahren sind
NUM=3
#################################################################################
umask 022
export PATH=/sbin:/bin:/usr/sbin:/usr/bin
(
# locking
echo "******** $(date) Aquire lock for $HOST from $0"
flock -x 200
echo "******** $(date) Got lock for $HOST from $0"
echo "######################################################## $(date)"
# Prüfen, ob Server erreichbar ist
if ! ping -q -w 5 -c 1 $HOST 2> /dev/null > /dev/null; then
echo "Server $HOST ist nicht erreichbar!" 1>&2
exit -1
fi
## Prüfen, ob Sicherungsmedium vorhanden ist
if [ "$TARGET" != "${TARGET#/media/}" ]; then
if ! ( test -d "${TARGET}" \
|| (mount | grep "$TARGET" 2> /dev/null > /dev/null ) ); then
echo "Die Sicherungspartition $TARGET ist nicht eingebunden!" 1>&2
exit -1
fi
# # Lesbar machen
# mount -o remount,rw "$TARGET" || \
# (echo "Fehler: Kann nicht auf $TARGET schreiben!" 1>&2 && exit -1)
fi
FULL=$(LANG= df -P /media/backup/ | awk '$1 !~ /^File/ {print $5}' | tr -d '%')
if [ "$FULL" -gt 80 ]; then
if [ "$FULL" -gt 98 ]; then
echo "**** Alarm! **** ${TARGET} ist $FULL% voll" 1>&2
exit 1
fi
echo "**** Warnung! **** ${TARGET} ist $FULL% voll"
fi
# Sicherungstyp eruieren
if echo "$(pwd)/$0" | grep hourly 2> /dev/null > /dev/null; then
TYPE="Stunde"
elif echo "$(pwd)/$0" | grep daily 2> /dev/null > /dev/null; then
TYPE="Tag"
elif echo "$(pwd)/$0" | grep weekly 2> /dev/null > /dev/null; then
TYPE="Woche"
elif echo "$(pwd)/$0" | grep monthly 2> /dev/null > /dev/null; then
TYPE="Monat"
else
TYPE="Manuell"
fi
# 3. Was ist die Referenz?
#
# Die Sicherung macht Hardlinks entweder zu Stunde.1 oder wenn der nicht
# existiert zu Tag.1 oder wenn der nicht existiert zu Manuell.1
#
# Die Idee ist, immer zum aktuellsten zu verlinken. Auf einem System
# mit regelmässigem Backup ist das gestern, auf einem System mit
# Backup von Hand, ist das das letzte manuelle. (Beispiel: Mein Server
# hat ein tägliches Backup, mein Notebook ein manuelles: Automatisch
# wenn ich es am USB angehängt habe.)
#
# Prüfung Tag oder Manuell?
# a) gibt es den Stunde.1? -> Stunde
# b) gibt es den Tag.1? -> Tag
# c) gibt es Manuell.1? -> Manuell
# d) sonst -> Was ich bin...
if [ -e ${TARGET}/${HOST}-Stunde.1 ]; then
echo "**** Found an hourly backup for reference"
REFTYPE=Stunde
elif [ -e ${TARGET}/${HOST}-Tag.1 ]; then
echo "**** Found an daily backup for reference"
REFTYPE=Tag
elif [ -e ${TARGET}/${HOST}-Manuell.1 ]; then
echo "**** Found an manual backup for reference"
REFTYPE=Manuell
else
echo "**** No reference found, try $TYPE"
REFTYPE=$TYPE
fi
if [ $TYPE == $REFTYPE ]; then
REFNUM=2
else
REFNUM=1
fi
# 4. Altern
if [ -d ${TARGET}/${HOST}-${TYPE}.1 ]; then
# 1. Älteste Sicherungskopie löschen
echo "**** REMOVE: ${TARGET}/${HOST}-${TYPE}.${NUM}"
rm -rf "${TARGET}/${HOST}-${TYPE}.${NUM}"
# 2. Bestehende Sicherungskopien um eine Stufe altern lassen
while [ $((--NUM)) -gt 0 ]; do
echo "**** MOVE: ${TARGET}/${HOST}-${TYPE}.${NUM}"
mv ${TARGET}/${HOST}-${TYPE}.${NUM} \
${TARGET}/${HOST}-${TYPE}.$((NUM+1)) 2> /dev/null
done
fi
echo "**** REFERENCE: ${TARGET}/${HOST}-${REFTYPE}.${REFNUM}"
# 5. Prüfen ob aktuelle Sicherung verfügbar ist, und gegebenfalls anlegen
if [ ! -e ${TARGET}/${HOST}-${REFTYPE}.${REFNUM} ]; then
for SRC in ${SOURCES}; do
CMD="rsync $ARGS ${BACKUP} ${HOST}:${SRC} ${TARGET}/${HOST}-${REFTYPE}.1/${SRC%/}"
echo "**** INITIAL: ${CMD}"
$CMD || echo "**** ERROR: ${CMD}" 1>&2
done
touch ${TARGET}/${HOST}-§${REFTYPE}.1/backup-timestamp
else
# 5b. Sonst aktuelle Sicherungskopie gegen neueste Sicherung anlegen
for SRC in ${SOURCES}; do
CMD="rsync $ARGS --link-dest=${TARGET}/${HOST}-${REFTYPE}.${REFNUM} ${BACKUP} ${HOST}:${SRC} ${TARGET}/${HOST}-${TYPE}.1/${SRC%/}"
echo "**** DIFFERENTIAL: ${CMD}"
$CMD || echo "**** ERROR: ${CMD}" 1>&2
done
touch ${TARGET}/${HOST}-${TYPE}.1/backup-timestamp
fi
## 6. Backup Medium bis zur nächsten Sicherung schreibschützen
#if [ "$TARGET" != "${TARGET#/media/}" ]; then
# mount -o remount,ro ${TARGET} || \
# (echo "Fehler: Kann Schreibschutz auf ${TARGET} nicht erzwingen!" 1>&2 \
# && exit -1)
#fi
echo "######################################################## Finished"
) 2>&1 >> /var/log/backup.log 200> ${LOCK}
udev Automatik wie in Automatischer Datenabgleich zwischen Linux und einem iAudio X5 beschrieben und der Verschlüsselung, wie in Harddiskverschlüsselung mit Linux beschrieben
