MRW
http://piratenpartei.ch

Piratenpartei Zürich
PC 85-112704-0

Der Politnetz Auftritt von Marc Wäckerlin Im Politnetz unterstützen

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:

  1. Zwei monatliche Sicherungen, ausgeführt am 1. unter /media/backup/${HOSTNAME}-Monat.1 und /media/backup/${HOSTNAME}-Monat.2
  2. Zwei wöchentliche Sicherungen ausgeführt am Samstag unter /media/backup/${HOSTNAME}-Woche.1 und /media/backup/${HOSTNAME}-Woche.2
  3. 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}
1) Für das automatische Einbinden, Entschlüsseln und Sichern verwende ich eine Kombination aus der udev Automatik wie in Automatischer Datenabgleich zwischen Linux und einem iAudio X5 beschrieben und der Verschlüsselung, wie in Harddiskverschlüsselung mit Linux beschrieben