Ein automatisches Backupscript auf Basis von Perl zum Backup des Systems.
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.
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!
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.
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.
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 |
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
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
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 (<KDATEI>){ 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); </code> ===== 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: <code> # backupscript 0 </code> Bei Verwendung von //tar// ist kein Parameter notwendig und das Backupscript wird einfach wie folgt gestartet: <code> # backupscript
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).
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.