Backup Skripte für einen Linux Server

Seit einiger Zeit betreibe ich Zuhause einen kleinen Linux-Server mit großen Festplatten die in einem Software-RAID5-Verbund konfiguriert sind. Auf dem Server speichern wir in mehreren Samba-Netzwerkfreigaben verschiedene Daten.

Wir unterscheiden dabei zwischen „wichtigen“ und „unwichtigen“ Dateien. Alle Daten die wir wiederbeschaffen können oder die im Zweifel verloren gehen dürfen sind dabei „unwichtig“. Das sind beispielsweise Downloads aus den Mediatheken der öffentlichen Rechtlichen Fernsehsender oder auch Linux-Distributionen und Raspberry-Pi-Images.

In die Kategorie „wichtige“ Dateien langen alle Daten die wir nicht wiederbeschaffen können und in irgendeiner Art und Weise wichtig für uns sind. Konkret handelt es sich dabei vor allem um größere Textdokument (zB. Studiumsunterlagen und -arbeiten meiner Frau), kleinere selbst geschriebene Skripte und Programme, Webseiten, einscannte Zeitschriftenartikel und mittlerweile vor allem: Fotos. Die Menge der Fotos ist schlagartig angestiegen nachdem es vor rund 3 Jahren Familienzuwachs gab. Zwischenzeitlich sind wir bei ca. 160 GByte Fotos bzw. 190 GByte an wichtigen Daten insgesamt angelangt.

Wie in jeder Fachzeitschrift immer wieder betont wird hilft ein RAID5 nicht gegen alle denkbaren Probleme (insbesondere: versehentliches Löschen, Ausfall mehrerer Platten gleichzeitig, Feuer, etc). Daher wurde es für mich Zeit mir intensiver Gedanken zu einem echten Backup-Konzept zu machen. Meine primären Anforderungen an das Konzept sind:

  • Preisgünstig
  • So automatisch wie nur möglich
  • Maximal eine Woche Datenverlust, mindestens 4 Wochen rückwirkend wiederherstellbar
  • Einfaches wiederherstellen einzelner Dateien
  • Keine Cloud o.ä.
  • Software aus den Standard Paketquellen (um einfach Updates zu erhalten)Optional: Differentielle und/oder inkrementelle Backups

Ich habe lange und viel im Internet gesucht aber wenig gefunden. Natürlich gibt es tar und ähnliche andere Unix/Linux Urgesteine. Damit ist es aber schwierige die obigen Anforderungen zu erfüllen. Am Ende hab ich mich aber für folgendes Setup entschieden:

  • Backup-Medium Vollbackup: 50 GByte BlueRays-RE (Preis pro Disc ca. 6,00€)
  • Backup-Medium Differentielles Backup: 25 GByte BlueRays-RE (Preis pro Disc ca. 2,50€)
  • Backup-Software: Disk ARchive
  • Eigene Skripte zur Automatisierung

Mit diesem Setup läuft nun alle 2 Monate ein Vollbackup der wichtigen Daten. Des weiteren wird einmal pro Woche ein differentielles Backup relativ zu dem Vollbackup erstellt. Das Vollbackup wird automatisch auf mehrere Dateien aufgeteilt die ich (leider) per Hand auf die BlueRay Discs brenne. Die differentiellen Backups landen auf 25 GByte BlueRays. Normalerweise reicht eine 25er Disc für 3-4 Differentielle Backups. Gegen Ende des 2 Monatszyklus kann ein Differentielles Backup aber auch mal 20 GByte groß werden. Daher rechne ich pro Zyklus aktuelle mit fünf 50er BlueRays (da ich pro Disc max 45 GByte speichere) und 3-4 25er BlueRays für die differentiellen Backups. Insgesamt habe ich zwei komplette Sätze Discs die ich immer im Wechsel überschreibe. Dadurch kann ich mindestens 2 Monate in die Vergangenheit gehen.

BlueRay Discs für das Backup

BlueRay Discs für das Backup

DAR ermöglicht es außerdem ein Dateikatalog zu erstellen. Dieser Katalog wird zum auffinden der korrekten Disc im Falle des Restores sowie für Metainformationen bei differentiellen Backups verwendet.

Last but not least: Um die Backups automatisch erstellen zu lassen hab ich 2 sehr einfach Skripte geschrieben die per Cron aufgerufen werden. Jedes Skript meldet sich per Mail sobald das Backup beendet ist. Dann müssen die Backup-Archive per Handy auf die BlueRays gebrannt werden.

Das erste Skript um ein Vollbackup zu erstellen:

#!/bin/bash

SOURCE_FOLDER="/opt/others/backup/backup_folders"
OUTPUT_FOLDER="/opt/others/backup/backup_data"
CATALOGE_FOLDER="/opt/others/backup/backup_cataloge"
BACKUP_NAME="backup"
MAIL="someone@somewhere.com"
MAX_SLICE_SIZE="45G"
BZIP_LEVEL="6"

DATE="$(date +%Y-%m-%d)"
TIME="$(date +%H-%M)"
MESSAGE=""

if [ ! -r $SOURCE_FOLDER ]; then
   MESSAGE="ERROR: Source folder does not exist or is not readable."
fi

if [ ! -w $OUTPUT_FOLDER ]; then
   MESSAGE="ERROR: Output folder does not exist or is not writeable."
fi

if [ ! -w $CATALOGE_FOLDER ]; then
   MESSAGE="ERROR: Cataloge folder does not exist or is not writeabe."
fi

if [ "$(ls -A ${OUTPUT_FOLDER}/${BACKUP_NAME}* 2> /dev/null)" ]; then
   MESSAGE="ERROR: At least one file with the same basename exists already in ${OUTPUT_FOLDER}/"
fi

if [ ! -z "$MESSAGE" ]; then
   echo $MESSAGE; exit 1
fi

DAR_MESSAGE="$(dar -Q -v -s $MAX_SLICE_SIZE -zbzip2:$BZIP_LEVEL -D -R $SOURCE_FOLDER -c "${OUTPUT_FOLDER}/full_${DATE}_${TIME}_${BACKUP_NAME}" -@ "${CATALOGE_FOLDER}/full_${DATE}_${TIME}_${BACKUP_NAME}_cataloge" 2>&1)"
ERROR_CODE=$?

if [ $ERROR_CODE -ne 0 ]; then
   mail -s "Backup -($BACKUP_NAME)- exited with error(s)" $MAIL <

Das zweite Skript erstellt ein differentielles Backup relativ zu einem vorherigen Vollbackup. Wichtig ist dabei die Variablen CATALOGE_FOLDER und BACKUP_NAME in beiden Skripten gleich zu benennen. Nur dann kann das Skript dar korrekt aufrufen.

#!/bin/bash

SOURCE_FOLDER="/opt/others/backup/backup_folder"
OUTPUT_FOLDER="/opt/others/backup/backup_data"
CATALOGE_FOLDER="/opt/others/backup/backup_cataloge"
BACKUP_NAME="backup"
MAIL="someone@somewhere.com"
MAX_SLICE_SIZE="20G"
BZIP_LEVEL="6"
#REFERENCE="inc"     # reference backup is last incremental (inc) or the last full (full) backup

DATE="$(date +%Y-%m-%d)"
TIME="$(date +%H-%M)"
MESSAGE=""

LAST_CATALOGE="$(ls -1 $CATALOGE_FOLDER/$REFERENCE*$BACKUP_NAME* 2>/dev/null | sort -r | head -n1 | sed 's/\.[0-9]\{1,\}\.dar$//')"

if [ ! -e "$LAST_CATALOGE.1.dar" ]; then
   MESSAGE="ERROR: No old dar cataloge found. Wrong backup name or no full backup available?"
fi

if [ ! -r $SOURCE_FOLDER ]; then
   MESSAGE="ERROR: Source folder does not exist or is not readable."
fi

if [ ! -w $OUTPUT_FOLDER ]; then
   MESSAGE="ERROR: Output folder does not exist or is not writeable."
fi

if [ ! -w $CATALOGE_FOLDER ]; then
   MESSAGE="ERROR: Cataloge folder does not exist or is not writeabe."
fi

if [ "$(ls -A ${OUTPUT_FOLDER}/${BACKUP_NAME}* 2>/dev/null)" ]; then
   MESSAGE="ERROR: At least one file with the same basename exists already in ${OUTPUT_FOLDER}/\nMaybe the old backup files have not been removed?"
fi

if [ ! -z "$MESSAGE" ]; then
   echo $MESSAGE; exit 1
fi

DAR_MESSAGE="$(dar -Q -v -s $MAX_SLICE_SIZE -zbzip2:$BZIP_LEVEL -A $LAST_CATALOGE -D -R $SOURCE_FOLDER -c "${OUTPUT_FOLDER}/inc_${DATE}_${TIME}_${BACKUP_NAME}" -@ "${CATALOGE_FOLDER}/inc_${DATE}_${TIME}_${BACKUP_NAME}_cataloge" 2>&1)"
ERROR_CODE=$?

if [ $ERROR_CODE -ne 0 ]; then
   mail -s "Backup -($BACKUP_NAME)- exited with error(s)" $MAIL <

Als letztes muss noch eine kleine crontab angelegt werden um die Skripte entsprechende automatisch zu starten. In meinem Fall wird das Vollbackup am ersten Sonntag alle 2 Monate um 2 Uhr gestartet. Das differentielle Backup entsprechend jeden Freitag ebenfalls um 2 Uhr. Das Vollbackup braucht etwa 30 Stunden für einen Durchlauf, das differentielle Backup ca 3-4 Stunden. Durch die Wahl der Tage ist sichergestellt, dass sich die beiden Backup nicht gegenseitig beeinflussen können.

  0 2   *   *  5/2   /opt/others/backup/script/do_inc_backup.sh
  0 2  1-7 1,6  *    [ "$date '+\%a'" = "Sun" ] && /opt/others/backup/script/do_full_backup.sh