======Allgemein======
Ein automatisches Backupscript auf Basis von Perl zum [[Backup des Systems]].
====== dump vs. tar ======
Das Backupscript kann seit Version 0.7 zusätzlich zu //dump// das Programm //tar// zum Erzeugen von Backups verwenden.
Ob //dump// oder //tar// zum Erzeugen der Backups eingesetzt wird ist abhängig vom Anwendungsfall und muss im Einzelnen entschieden werden.
===== dump =====
Ermöglicht das Backup von ganzen Partitionen (und nur von ganzen Partitionen) im laufenden Betrieb. Es erzeugt hierzu einen Snapshot des Dateisystems. Das Erzeugen von inkrementellen Backups erlaubt das Vorhalten einer gewissen Historie ohne dabei große Mengen Backup-Speicher zu verbrauchen. Das Erzeugen eines //dump// ist jedoch durch das Erzeugen des Snapshots recht ressourcen-hungrig und führt meist dazu, dass das System für einige Sekunden bis Minuten voll ausgelastet ist.
Die Verwendung von //dump// auf Partitionen die mit [[Journaling Softupdates]] formatiert wurden ist derzeit nicht möglich und führt zum Systemabsturz!
===== tar =====
Da //tar// Verzeichnisorientiert arbeitet ist es damit möglich ausgewählte Teile des Systems zu sichern, nicht jedoch ganze Partitionen. Es ist nicht möglich mit //tar// inkrementelle Backups zu erzeugen. Da es direkt auf den Dateien arbeitet (nicht wie //dump// auf Snapshots) ist das Sichern geöffneter Dateien problematisch.
====== WARNUNG ======
Das folgende Script verwendet wahlweise das Programm **dump** zum Erzeugen der Backups. Dieses führt jedoch unter FreeBSD 9.0 zu Systemabstürzen wenn es in Kombination mit [[Journaling Softupdates]] verwendet wird. Anwender von Journaling Softupdates sollten daher hier Vorsicht walten lassen oder auf die Backupmethode //tar// zurückgreifen. Wird kein Journal verwendet (also nur die normalen Softupdates) treten diese Probleme nicht auf.
======Konfigurationsdatei ======
===== Beschreibung der Parameter =====
Das Backupscript setzt eine Konfigurationsdatei voraus. Diese muss unter /usr/local/etc/backupscriptrc abgelegt sein. Das Script muss folgenden Aufbau haben:
parameter = wert
Leerzeilen sowie Zeilen die mit # beginnen (Kommentare) werden ignoriert. Es darf kein Kommentar innerhalb einer Parameter-Zeile verwendet werden.
Folgende Parameter sind zulässig:
^ Parameter ^ Beschreibung des Wertes ^ Hinweise ^
| methode | Mögliche Werte sind **dump** (standard) und **tar**. Hiermit wird festgelegt mit Hilfe welcher Methode das Backup erzeugt wird. | Globaler Wert |
| zip | Einzig möglicher Wert **ja**. Bei Setzen dieses Werts wird definiert, dass die Backups mit gzip komprimiert werden | Globaler Wert |
| cipher | Mögliche Werte sind alle gültigen Werte für **openssl enc** (siehe //man enc//). Ist dieser Parameter gesetzt wird das Backup entsprechend des ausgewählten Algorithmus verschlüsselt. Wird der Parameter nicht gesetzt erfolgt keine Verschlüsselung. (Benötigt //key// oder //keyfile//). | Globaler Wert\\ Benötigt //key// oder //keyfile//. |
| key | Ist //cipher// gesetzt kann hier das Passwort angegeben werden welches zur Verschlüsselung verwendet werden soll. Aus Sicherheitsgründen ist hiervon abzuraten und statt dessen //keyfile// zu verwenden. | Globaler Wert\\ Benötigt //cipher// |
| keyfile | ist //cipher// gesetzt kann hier eine Datei angegeben werden welche das Passwort zur Verschlüsselung enthält. Diese Methode ist der Verwendung von //key// vorzuziehen. Es ist jedoch darauf zu achten, dass ein Backup des Keyfiles existiert und, dass dieses nur von root gelesen werden kann. | Globaler Wert\\ Benötigt //cipher// |
| nodump | Verzeichnis oder Datei welche **nicht** gesichert werden soll. Darf mehrmals verwendet werden. | Globaler Wert |
| ziel | Zielverzeichnis in welches die Dumps gespeichert werden sollen. | Globaler Wert |
| mounten | Gibt an ob das Zielverzeichnis auf einer extra Partition liegt welche zuvor gemountet werden muss. Dass die Platte nur zum Backup gemountet wird erhöht dich Sicherheit des Backups. Damit das automatische Mounten funktioniert muss das entsprechend zu mountende Verzeichnis in ///etc/fstab// (ideal per noauto) angegeben sein. Mehr Infos im nächsten Punkt. Der einzig erlaubte Wert ist **ja**. Ist kein Mounten gewünscht ist der Parameter weg zu lassen. | Globaler Wert|
| mountpfad | Falls //ziel// nicht direkt das Verzeichnis ist welches gemountet werden muss so ist hier das Mountverzeichnis anzugeben. Sollen die Backups z.B. unter ///data/backups// abgelegt werden, jedoch ///data// muss hierzu gemountet werden (backup ist demnach ein Unterverzeichnis auf der /data-Partition) dann wäre der entsprechende Parameter **/data**. Sind Zielverzeichnis und Mountverzeichnis identisch muss dies nicht extra angegeben werden (z.B. wenn die Dumps direkt nach ///data// gespeichert werden sollten). | Globaler Wert |
| device | Die Devicenode welche die zu sichernde Partition beschreibt. z.B. ///dev/ad4p1//. Mehrfachnennungen möglich. | Nur für Methode //dump// |
| tarfile | Definiert den "Basisnamen" des zu sichernden Tar-Files. z.B. "backup" ergibt später den Namen "backup-20121217.tar.gz" falls das Backup am 17.12.2012 durchgeführt und gezipt wurde. | Nur für Methode //tar// |
| quelle | Verzeichnis welches gesichert werden soll. | Nur für Methode //tar// |
===== Beispiel Konfiguration mit dump =====
methode = dump
# Nicht zu sichernde Verzeichnisse
nodump = /usr/ports
nodump = /usr/obj
nodump = /usr/src
# Zu sichernde Partitionen
device = /dev/ad4p2
device = /dev/ad4p4
device = /dev/ad4p6
#/data muss gemountet werden. Die Backups landen dann unter /data/backup
mounten = ja
mountpfad = /data
ziel = /data/backup
===== Beispiel Konfiguration mit tar, AES-256-Verschlüsselung und gzip-komprimiert =====
methode = tar
ziel = /backups
zip = ja
cipher = aes256
keyfile = /usr/local/etc/backupkey/backup.key
quelle = /root
quelle = /etc
quelle = /usr/local/etc
nodump = /root/testVerzeichnis
====== Backupscript ======
Das Backupscript sieht wie folgt aus und sollte ausführbar gemacht unter ///usr/local/bin// abgelegt werden.
Das aktuelle Script ist immer unter [[https://github.com/rakor/backupscript]] zu finden.
#!/usr/bin/perl
$version = "0.8";
$T_SCRIPTNAME = "backupscript";
$OPENSSL = "/usr/bin/openssl";
$TAR = "/usr/bin/tar";
$DUMP = "/sbin/dump";
$CHFLAGS = "/bin/chflags";
$MOUNT = "/sbin/mount";
$UMOUNT = "/sbin/umount";
$GZIP = "/usr/bin/gzip";
$KONFIGDATEI = "/usr/local/etc/backupscriptrc";
@KONFWERTE = ("nodump", "device", "ziel", "mounten", "mountpfad", "cipher", "methode", "key", "keyfile", "quelle", "tarfile", "zip");
$methode = "dump"; # Standard und somit Kompartible zu ver < 0.7
$tarErweiterung = "tar";
$default_tar_name = "backup";
## Ausgabetexte ##
$T_KONFDATEINICHTDA = "Konfigurationsdatei $KONFIGDATEI nicht vorhanden!\n";
$T_KONFDATEINICHTOEFFNEN = "Konnte Konfigurationsdatei nicht oeffnen!\n";
$T_KONFDATEIDEFEKT = "Konfigurationsdatei $KONFIGDATEI ist fehlerhaft!\n";
$T_KONFDATEIDEFEKT_TAR_DEVICE = "Konfigurationsdatei $KONFIGDATEI ist fehlerhaft! Wenn die Methode tar gewählt ist darf kein \"device\" definiert sein. \n";
#$T_KONFDATEIDEFEKT_TAR_OUTPUT = "Konfigurationsdatei $KONFIGDATEI ist fehlerhaft! Wenn die Methode tar gewählt ist muss der Outputname in \"tarfile\" definiert sein. \n";
$T_KONFDATEIDEFEKT_DUMP_TAROPTS = "Konfigurationsdatei $KONFIGDATEI ist fehlerhaft! Wenn die Methode dump gewählt ist darf weder \"tarfile\" noch \"quelle\" definiert sein. \n";
$T_KONFDATEIDEFEKT_KEY_KEYFILE = "Konfigurationsdatei $KONFIGDATEI ist fehlerhaft! Es darf entweder \"key\" oder \"keyfile\" definiert sein, nicht beide.\n";
$T_KONFDATEIDEFEKT_KEY_NOCIPHER = "Konfigurationsdatei $KONFIGDATEI ist fehlerhaft! Um die Verschlüsselung der Zieldatei zu aktivieren muss \"cipher\" mit einer openssl-kompatiblen Verschlüsselung gesetzt werden. (z.B. aes256)\n";
$T_KONFDATEIDEFEKT_CIPHER_NOKEY = "Konfigurationsdatei $KONFIGDATEI ist fehlerhaft! \n";
$T_MOUNTFEHLER = "Fehler beim Mounten des Zielpfades\n";
$T_UMOUNTFEHLER = "Fehler beim Unmounten des Zielpfades\n";
$T_MOUNTVARIABLEFALSCH = "Die Variable 'mounten' wurde gesetzt enthaelt aber keinen zulaessigen Wert. Es wird kein Mountversuch unternommen.\n";
$T_NODUMPVERZFALSCH = "Ein 'nodump'-Verzeichnis ist falsch";
$T_ZIELVERZFALSCH = "Das Ziel-Verzeichnis existiert nicht.\n";
$T_UMOUNTWEGENFEHLER = "Wegen eines Fehlers wurde das Mountverzeichnis vorzeitig getrennt.\n";
$T_DEVICEVERZFALSCH = "Ein 'device' ist falsch";
$T_SCHLUESSELLAENGEZUKURZ = "Die Länge des gewählten Schlüssels zur Verschlüsselung ist zu kurz. Zur Sicheren Verschlüsselung bitte einen längeren Schlüssel definieren\n";
$T_KEYFILENICHTNUTZBAR = "Das Keyfile zur Verschlüsselung wurde nicht gefunden. Bitte prüfen Sie ob es existiert und gelesen werden kann.\n";
$T_HILFETEXT = "usage:\t$T_SCRIPTNAME [OPTION]\n\n[Options]:\n\t0-9\tDumplevel - Eine Ziffer die das Dumplevel angibt (nur bei Backups mit dump)\n\t-h\tLange Hilfe\n\n";
$T_LANGEHILFE = "Version: $version
Das Script dient der erleichterten Erstellung von Backups unter FreeBSD unter
Verwendung des Systemprogrammes 'dump' oder 'tar'. Zusätzlich können die
Backups mittels 'OpenSSL' verschlüsselt werden. Zur Steuerung wird eine
Konfigurationsdatei unter folgendem Link benoetigt:
\t$KONFIGDATEI
Die Datei bietet folgende Parameter:
Allgemeine Parameter
method : Methode mit welcher das Backup erzeugt werden soll.
Mögliche Parameter sind 'dump' oder 'tar'.
nodump : Verzeichnisse die NICHT gesichert werden sollen.
ziel : Zielverzeichnis fuer die Backups.
mounten : 'ja' falls vor dem Backup eine Partition gemountet
werden muss.
mountpfad : Falls das zu mountende Verzeichnis von 'ziel'
abweicht.
zip : 'ja' falls das Backup komprimiert werden soll.
Zur Komprimierung wird gzip verwendet.
Parameter für Verschlüsselung
cipher : Eine OpenSSL-kompatible Verschlüsselungsmethode.
z.B. aes256
key : Passwort welches zur Verschlüsselung verwendet
werden soll.
Die empfohlene Methode ist 'keyfile' zu verwenden.
(schliesst die Verwendung von keyfile aus).
keyfile : Datei welche das zu verwendende Passwort enthält.
(schliesst die Verwendung von key aus).
Parameter ausschließlich für dump-Backups
device : Partition die gesichert werden soll
Parameter ausschliesslich für tar-Backups
tarfile : Name des erzeugten tar-Archivs. Nur der Name,
die Erweiterung wird automatisch ergänzt.
quelle : Verzeichnisse die gesichert werden sollen. Je ein
Eintrag pro Verzeichnis.
Das zu mountende Verzeichnis, egal ob extra angegeben oder nicht, muss immer
unter /etc/fstab aufgefuehrt sein. Es sollte die 'noauto'-Option in /etc/fstab
verwendet werden damit die Backupplatte nicht unnoetig gemountet bleibt.
Beispiel einer $KONFIGDATEI für normale nichtverschlüsselte Backups mit dump:
------------------------------------------------
method = dump
nodump = /usr/ports
nodump = /usr/obj
nodump = /usr/src
device = /dev/ad4p2
device = /dev/ad4p4
device = /dev/ad4p6
mounten = ja
mountpfad = /data
ziel = /data/backup
-------------------------------------------------
Beispiel einer $KONFIGDATEI für tar-basierte, verschlüsselte Backups:
------------------------------------------------
method = tar
tarfile = backup
zip = ja
ciper = aes256
key = testpasswort
nodump = /usr/home/unimportant
quelle = /usr/home
quelle = /etc
mounten = ja
mountpfad = /data
ziel = /data/backup
-------------------------------------------------
\n";
###########################################################################
sub zeigeHilfe{
print $T_HILFETEXT;
exit;
}
###########################################################################
sub zeigeLangeHilfe{
print $T_LANGEHILFE;
exit;
}
###########################################################################
sub KonfdateiEinlesen{
#
# Liest die Datei ein und speichert die Werte im Array
# Incl. Ueberpruefung der Werte gegen @KONFWERTE.
#
open KDATEI, "<",$KONFIGDATEI or die $T_KONFDATEINICHTOEFFNEN;
foreach (){
unless (/^\s*#/ or /^\s*$/){ # keine Leerzeilen oder Kommentare
$ok = 0;
$_ =~/^\s*(\S+)\s*=\s*(.*?)\s*$/; # $1=Parameter; $2=Wert
foreach (@KONFWERTE){ # Testen ob Werte zulaessig sind
$ok = 1 if ($_ eq $1);
}
die $T_KONFDATEIDEFEKT unless $ok;
## Verarbeitung der Daten
if ($1 eq "nodump"){
push @nodump, $2;
}
elsif($1 eq "device"){
push @device, $2;
}
elsif($1 eq "quelle"){
push @quellverzeichnisse, $2;
}
elsif($1 eq "ziel"){
$ziel = $2;
}
elsif($1 eq "zip"){
if ($2 =~/^[jJ][aA]$/){
$zip = 1;
}
else{
warn $T_ZIPVARIABLEFALSCH;
}
}
elsif($1 eq "mounten"){
if ($2 =~ /^[jJ][aA]$/){
$mounten = 1;
}
else{
warn $T_MOUNTVARIABLEFALSCH;
}
}
elsif($1 eq "mountpfad"){
$mountpfad = $2;
}
elsif($1 eq "cipher"){
$cipher = $2;
}
elsif($1 eq "tarfile"){
$tarOutput = $2;
}
elsif($1 eq "methode"){
unless ($2 eq "tar" or $2 eq "dump"){
warn $T_METHODENVARIABLEFALSCH;
}
if ($2 eq "tar"){
$methode = "tar";
} else {
$methode = "dump";
}
}
elsif($1 eq "key"){
$key = $2;
}
elsif($1 eq "keyfile"){
$keyfile = $2;
}
## Ende Verarbeitung der Daten
}
}
close $KDATEI;
} # Ende sub KonfdateiEinlesen
###########################################################################
sub zielpfadZuschneiden{
$ziel =~s/^(.*?)\/?$/$1/;
}
###########################################################################
sub istZielGemountet{
$gefunden = 0;
@gemountet = `mount`;
foreach (@gemountet){
$gefunden = 1 if (/$_[0]/);
}
$gefunden;
}
###########################################################################
sub zielMounten{
unless (defined $mountpfad){
$mountpfad = $ziel;
}
unless (&istZielGemountet($mountpfad)){
!system "$MOUNT $mountpfad" or die "$T_MOUNTFEHLER";
}
}
###########################################################################
sub zielUmounten{
unless (defined $mountpfad){
$mountpfad = $ziel;
}
if (&istZielGemountet($mountpfad)){
!system "$UMOUNT $mountpfad" or die "$T_UMOUNTFEHLER";
}
}
###########################################################################
sub konfigAufSinnTesten{
die $T_KONFDATEIDEFEKT_TAR_DEVICE if ($methode eq "tar" and defined @device);
# die $T_KONFDATEIDEFEKT_TAR_OUTPUT if ($methode eq "tar" and not defined $tarOutput);
die $T_KONFDATEIDEFEKT_DUMP_TAROPTS if (($methode eq "dump" and defined @quellverzeichnisse) or ($methode eq "dump" and defined $tarOutput));
die $T_KONFDATEIDEFEKT unless (defined $ziel and (defined @device or defined @quellverzeichnisse));
die $T_KONFDATEIDEFEKT_KEY_KEYFILE if (defined $key and defined $keyfile);
die $T_KONFDATEIDEFEKT_CIPHER_NOKEY if (defined $cipher and not (defined $keyfile or defined $key));
die $T_KONFDATEIDEFEKT_KEY_NOCIPHER if ((defined $keyfile or defined $key) and not defined $cipher);
}
###########################################################################
sub VerzeichnissePruefen{
foreach (@nodump){
warn "$T_NODUMPVERZFALSCH: $_\n" unless (-e $_);
}
unless (-d $ziel){
&zielUmounten if (defined $mounten);
warn $T_UMOUNTWEGENFEHLER;
die $T_ZIELVERZFALSCH;
}
foreach (@device){
die "$T_DEVICEVERZFALSCH: $_\n" unless (-e $_);
}
}
###########################################################################
sub cipherpipeErzeugen{
if (defined $cipher){
$cipherpipe = "| $OPENSSL enc -$cipher -e -salt -pass";
if (defined $key){
warn $T_SCHLUESSELLAENGEZUKURZ if (length $key < 8);
$cipherpipe = $cipherpipe." pass:$key"
} elsif (defined $keyfile){
die $T_KEYFILENICHTNUTZBAR unless (-e $keyfile and -r $keyfile);
$cipherpipe = $cipherpipe." file:$keyfile";
}
return "-";
} else {
$cipherpipe = "";
return $_[0];
}
}
###########################################################################
sub dateiinfosAusgeben{
@Einheiten = ("B", "kB", "MB", "GB", "TB");
$division = 0;
if (-e $_[0]){
$groesse = -s $_[0];
while ($groesse >= 1024){
$groesse = $groesse / 1024;
$division += 1;
}
$ausgabe = sprintf("%s %.2f %s",$_, $groesse, $Einheiten[$division]);
} else {
$ausgabe = "$_ wurde nicht angelegt!";
}
print "$ausgabe\n";
}
###########################################################################
# Parameter abpruefen
&zeigeLangeHilfe if ($ARGV[0] eq "-h");
# Ist Konfigdatei da?
die $T_KONFDATEINICHTDA unless (-f $KONFIGDATEI);
# Konfigdatei abarbeiten
&KonfdateiEinlesen;
&konfigAufSinnTesten;
if ($methode eq "dump"){
&zeigeHilfe unless ($ARGV[0] =~/^\d$/);
$dumplevel = $ARGV[0];
} else {
&zeigeHilfe if ($#ARGV > -1);
}
&zielpfadZuschneiden;
# Muessen wir mounten? Dann los.
&zielMounten if (defined $mounten);
# Zielverzeichnis kann ggf erst nach dem Mounten geprueft werden.
&VerzeichnissePruefen;
# Datumsstring erzeugen
($sek, $min, $std, $mtag, $mon, $jahr, , ,) = localtime(time);
$datum = sprintf("%04d%02d%02d",$jahr+1900,$mon+1,$mtag);
if ($methode eq "dump"){ # FALLS MIT DUMP GEARBEITET WIRD
# Vorarbeiten... gemountete Laufwerke einlesen
@mounts = `mount`;
foreach (@device){
$deviceName = $_;
foreach (@mounts){
$_ =~/^\s*(.*) on (.*)\s+\(/;
$devicenodepfadausmount = $1;
$devicemountpfad = $2;
if ($devicenodepfadausmount=~/$deviceName$/){
if ($devicemountpfad eq "/") { $devicemountpfad="root" }
else{
$devicemountpfad =~ s/^\/(.*)$/$1/;
$devicemountpfad =~ s/\//_/g;
}
$devicemountpfad{$deviceName}=$devicemountpfad;
}
}
}
# 'nodump' setzen falls dump verwendet wird
foreach (@nodump){
system ("$CHFLAGS -R nodump $_") if (-e $_);
}
# dumps durchfuehren
foreach (@device){
/^.*?([^\/]+)$/;
$deviceKurzName = $1;
$dumpDateiname = "$ziel/$devicemountpfad{$_}-$deviceKurzName-$datum-L$dumplevel";
&cipherpipeErzeugen($dumpDateiname);
if (defined $zip){
$zippipe = " | $GZIP";
$dumpDateiname = "$dumpDateiname.gz";
} else {
$zippipe = "";
}
push @erzeugteDateien, $dumpDateiname;
system ("$DUMP -$dumplevel -h0 -Lauf - $_ $zippipe $cipherpipe | dd of=$dumpDateiname");
}
} elsif ($methode eq "tar"){ # FALLS MIT TAR GEARBEITET WIRD
if (defined @nodump){
$exclude = "";
foreach (@nodump){
$exclude = $exclude." --exclude $_";
}
}
$tarOutput = $default_tar_name unless (defined $tarOutput);
$tarOutput =~ s/^([^\.]*)\.(.*?)$/$1/;;
if (length $2 > 1){
$tarErweiterung = $2;
}
$output = "$ziel/$tarOutput-$datum.$tarErweiterung";
if (defined $zip){
$z = "-z";
$output = "$output.gz";
} else {
$z ="";
}
push @erzeugteDateien, $output;
$tarOutputDateiname = &cipherpipeErzeugen($output);
system ("$TAR -c $z -f - $exclude --exclude $output @quellverzeichnisse $cipherpipe | dd of=$output");
}
# Dateigroessen ausgeben
print "\nEs wurden folgende Backups durch $methode angelegt";
print " und mittels $cipher verschlüsselt" if (defined $cipher);
print ":\n";
foreach (@erzeugteDateien){
&dateiinfosAusgeben($_);
}
&zielUmounten if (defined $mounten);
===== Anwendung =====
Wird das Backupscript mit //dump// verwendet erwartet es als einzigen Parameter das Dumplevel welches angewendet werden soll. Ein Dump-0 wird also wie folgt gestartet:
# backupscript 0
Bei Verwendung von //tar// ist kein Parameter notwendig und das Backupscript wird einfach wie folgt gestartet:
# backupscript
====== Automatische Backups per periodic ======
Das Script kann verwendet werden um automatische Backups per //periodic// durchzuführen. Hierzu werden entsprechende Dateien unter ///etc/periodic// angelegt. Vorrausgesetzt es sollen Backups nach folgendem Schema durchgeführt werden. (Bei Verwendung von //dump//).
*Monatlich ein neues Level-0
*Wöchentlich ein neues Level-1
*Täglich ein neues Level-2
Ein entsprechendes monatliches Periodicscript (z.B. /etc/periodic/monthly/970.backup-L0) hat dann folgenden Inhalt:
#!/bin/sh
/usr/local/bin/backupscript 0
Das Script muss natürlich ausführbar sein. Ähnlich ist mit den anderen Scripten vorzugehen.