Public Key Hermes: Download
Public Key Backupserver: Download
Sonstige Downloads
Ein- oder zweimal im Jahr kommt es bei mir vor, dass sich von irgend einem meiner Rechner mal wieder die Festplatte verabschiedet und sich entschließt, plötzlich und unerwartet vor ihren Schöpfer zu treten. Und das obwohl sich die Rechner kaum von der Stelle bewegen, nur durchschnittlich oft benutzt und immer sorgfältig behandelt werden. Da ich somit schon viel zu oft irgendwelche Daten verloren habe, musste ich irgendwann zu der Einsicht kommen, dass ein Backup nur dann ein gutes Backup ist, wenn es vollautomatisch und ohne jegliches Zutun von Anwenderseite durchgeführt werden kann. Insbesondere darf es nicht sein, dass ein Backup erst manuell angestoßen werden muss (zum Beispiel durch Einstecken einer Backupplatte), da dies in der Realität viel zu oft vergessen wird. Beim letzten Plattencrash hat es ausgerechnet meinen Hauptcomputer erwischt, wodurch ich ganze zwei Monate zurückfiel, weil eben genauso lange das letzte Backup her war.
Damit sich ein solcher Fall nicht nochmal ereignen kann, beschloss ich einen gerade günstig gekauften Minirechner (400 MHz VIA CPU, 10 EUR gebraucht) als vollautomatischen Backupserver zu konfigurieren. Das Prinzip ist einfach: Mit Hermes habe ich ohnehin einen Rechner, der rund um die Uhr läuft, den ich aber nicht als Backupserver verwenden will. Er weckt jede Nacht um vier Uhr den Backupserver auf (Cronjob) und sorgt per SSH dafür, dass ein vordefiniertes Backupprogramm angestoßen wird. Der Backupserver kümmert sich dann darum, alle Computer im Netzwerk hochzufahren, zu sichern und wieder herunterzufahren. Anschließend schaltet er sich von selbst aus.
Der Aufbau sieht also wie folgt aus:
Abb.: Systemlandschaft mit Backupserver |
[Backupserver]---USB---[Festplatte 1.5 TB] | +---LAN-----+-------------+ | | [Router] [Heimserver] | +--LAN--+---+---+-------+ | | | | [PC1] [PC2] [PC3] [PC4] ... [Heimserver] weckt [Backupserver]. |
...
Natürlich werden nur die Rechner aufgeweckt, die nicht schon ohnehin eingeschaltet sind. Der Heimserver muss also nicht aufgeweckt werden, da er immer an ist. Die Existenz der anderen Rechner kann mit Ping überprüft werden. Damit wird auch überprüft, ob nach einem Wake-On-Lan der Rechner tatsächlich hochgefahren wurde. Beim Abschalten gilt umgekehrt, dass der Heimserver nicht heruntergefahren darf, da somit die automatischen Backups nicht mehr angestoßen werden könnten. Alle anderen Computer dürfen im Prinzip heruntergefahren werden, jedoch nur, wenn kein Benutzer mehr angemeldet ist. Arbeitet noch jemand an dem Rechner, soll der Rechner also einfach eingeschaltet bleiben.
Hinzu kommt, dass manche meiner Rechner nicht wake-on-lan fähig oder nicht ans Netzwerk angebunden sind. Hierbei handelt es sich um historische Rechner mit DR-DOS oder ATARI TOS statt Linux. Aber auch die AW4416 Audio Workstation von Yamaha zählt hierzu. Diese Geräte können nicht vollautomatisch gesichert werden. Für sie muss der Zugriff auf den Backuprechner per FTP oder SFTP möglich sein, um manuelle Backups durchführen zu können.
Voraussetzung für den obigen Plan ist, dass alle Rechner mit einer festen IP-Adresse oder einem festen Domainnamen angesprochen werden können. Hierfür gibt es verschiedene Lösungsansätze; in meinem Fall ist es so, dass dnsmasq als kombinierter DHCP/DNS-Server jedem Rechner anhand seiner MAC-Adresse eine IP und eine Subdomain zuordnet.
Hinzu kommt ein eigenes Python-Script, welches das wakeonlan Kommando (muss unter Debian nachinstalliert werden!) einhüllt. Denn wakeonlan funktioniert nur mit einer MAC-Adresse und die kenne ich in der Regel nicht auswendig. Stattdessen greift das Skript auf eine zentral im Netzwerk abgelegte Konfigurationsdatei zurück, die jeder MAC-Adresse einen Namen zuordnet.
Zuordnung von Namen zu MAC-Adressen |
192.168.178.004 00:50:45:5D:89:0C * jake 192.168.178.005 00:40:63:C9:BD:F3 * backup 192.168.178.020 00:A0:D1:9B:79:6E * blues-mobil |
Demnach sind folgende Aufrufe identisch:
$ wake-on-lan 00:40:63:C9:BD:F3
$ ./wakehost backup
Hier der Quellcode des Skripts:
wakehost.py |
#! /usr/bin/env python #encoding=utf-8 # wakehost.py # Simple wakeonlan wrapper in order to wake named hosts # # Copyright 2010 Dennis Schulmeister <dennis(at)developer-showcase.de> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # Configuration values HOST_FILE_URL = "http://hermes/hosts.txt" TIMEOUT = 10 # Imported modules import subprocess, sys, urllib2 # Function definitions def main(): ''' Main routine of this script. Returns system return code. ''' try: host_file = urllib2.urlopen(HOST_FILE_URL, None, TIMEOUT) except urllib2.URLError as ex: print str(ex) return 1 try: hostname = sys.argv[1] except IndexError: hostname = "" error = False if hostname: mac = "" for line in host_file: line = line.lstrip().rstrip() host = line.split(" ") if host[3] == hostname: mac = host[1] break if mac: try: subprocess.call(["wakeonlan", mac]) except OSError: print "OS Error: Unable to run wakeonlan" error = True else: print "Unknown host: %s" % hostname error = True else: for line in host_file: line = line.lstrip().rstrip() host = line.split(" ") print "%s: %s" % (host[1], host[3]) host_file.close() if error: return 1 else: return 0 # Program start if __name__ == "__main__": sys.exit(main()) |
Aufgabe des Heimservers ist es, das Backup der Cronjob täglich anzustoßen. Das Ergebnis des Backups soll dann per Mail an den Administrator geschickt werden.
Da die Kommunikation mit dem Backupserver nur via SSH funktioniert, muss zunächst ein Schlüsselpaar generiert werden. Der Public Key findet sich dann in der Datei ~/.ssh/id_rsa.pub.
$ ssh-keygen
Anschließend wird ein neuer Cronjob eingeplant:
/etc/cron.d/DES-backup |
# Start periodic backup on backup server # Runs mon, wed, fri at 3:30am # des, Sun Jul 11 2010 #-------------------------------------------------------- # 0=sun, 1=mon, 2=tue, 3=wed, 4=tue, 5=fri, 6=sat, 7=sun #-------------------------------------------------------- # NOTE: many_small contains hermes but not blues-mobil! #-------------------------------------------------------- 00 4 * * * dennis /home/dennis/bin/run_backup.sh -q many_silent 00 14 * * 3 dennis /home/dennis/bin/run_backup.sh -q host_jake |
Dieser Job startet ein Shellscript, das als Parameter mindestens einen Namen des auszuführenden Backupprogramms entgegennimmt. Dieses muss auf dem Backupserver angelegt sein (siehe unten), wodurch sich Art und Umfang des Backups bestimmt.
Das Skript selbst unterteilt sich in zwei Dateien, um die eigentliche Skriptfunktion (das Anstoßen des Backups) vom E-Mailversand des Backupergebnisses zu trennen. Zwar verschickt Cron automatisch alle Ausgaben eines Kommandos per E-Mail, diese landen bei mir aber nicht an der richtigen Stelle. (Cron schickt die Ausgaben an den ausführenden Benutzer, dennis in diesem Fall. Ich will jedoch, dass die Mails an root gehen, weil sie dann automatisch in einen speziellen IMAP-Ordner für Systemmeldungen einsortiert werden können. Mehr dazu etwas weiter unten.) Deshalb werden durch den Parameter -q sämtliche Assgaben unterdrückt und stattdessen vom Skript per E-Mail verschickt. Will man das Skript interaktiv ausführen, lässt man den Parameter natürlich weg; die Ausgaben werden trotzdem per E-Mail verschickt.
~/bin/run_backup.sh (Aufruf und E-Mailversand) |
#! /bin/bash ## des - Sun Jul 11, 2010: Start backup server in order to run a full backup ## This script is called automaticaly by a cron job and sends the results back ## to the system adminstrator. BIN=$(dirname $0) quiet=0 if [ -z "$1" ]; then echo "Usage: $0 [-q] <backup-target> <backup-target> ..." exit 1 fi until [ -z "$1" ]; do case $1 in -q) quiet=1 ;; *) target=$1 mailfile=$(tempfile) if [ $quiet == "0" ]; then $BIN/_run_backup.sh $target | tee -a "$mailfile" else $BIN/_run_backup.sh $target &>> "$mailfile" fi mail -s "[HERMES] Results of backup for target $target" root < "$mailfile" rm "$mailfile" sleep 60 ;; esac shift done |
Die eigentliche Funktion steckt in _run_backup.sh. Hier wird erst geprüft, ob der Backupserver mit ping erreicht werden kann und der Server bei Bedarf hochgefahren. Anschließend wird das geforderte Backupprogramm ausgeführt und der Rechner wieder heruntergefahren. Über ein Lockfile wird sichergestellt, dass nicht zwei Backups gleichzeitig laufen können.
~/bin/_run_backup.sh (Eigentliches Anstarten des Backups) |
#! /bin/bash ## des - Sun Jul 11, 2010: Start backup server in order to run a full backup ## This script is called automaticaly by a cron job. set +e BIN=$(dirname $0) LOCK_FILE="$BIN/.lock_backup" # Find out backup target if [ -n "$1" ]; then echo "Target: $1" target=$1 else echo "Target: NONE --> Doing full backup" target="all" fi echo "backup target: $target" | mail -s "[HERMES] Starting backup for target $target" root # Check no other backup is currently running if [ -e "$LOCK_FILE" ]; then echo "ERROR: Cannot aquire lock file. Is another backup running?" echo "Delete $LOCK_FILE if an old backup aborted unforseen." exit 1 else touch $LOCK_FILE fi # Check whether server is already running ping -q -c 5 -w 5 backup > /dev/null SERVER_ON=$? # Start server if necessary if [ "$SERVER_ON" != 0 ]; then echo "Backup server not running. Wake up." $BIN/wakehost backup COUNT=0 while [ $COUNT -lt 18 ]; do let COUNT=COUNT+1 echo -n "*" ping -q -c 5 backup &> /dev/null SUCCESS=$? if [ "$SUCCESS" == 0 ]; then break else sleep 10 fi done echo if [ "$SUCCESS" != 0 ]; then echo "ERROR: Backup server didn't wake up!" exit 1 fi sleep 10 else echo "Backup server already running." fi # Do backup echo ssh backup /home/dennis/bin/backup-$target echo # Shutdown server if it wasn't running before USER_COUNT=$(ssh backup who | wc -l) if [ "$USER_COUNT" -eq "0" ]; then echo "Noone else logged in. Shutdown backup server." ssh backup sudo poweroff else echo "$USER_COUNT users still loged in. Can't shutdown backup server." fi # Release lock file rm $LOCK_FILE exit 0 |
Jetzt fehlt nurnoch ein kleiner Kniff, damit die beim Backup verschickten Mails in einem gesonderten Ordner meines Postfaches auftauchen. Hier kommt die Tatsache zum Tragen, dass Hermes auch der IMAP-Server für meine Mails ist und diese daher in einem Maildir liegen, das per fdm regelmäßig aus verschiedenen Quellen befüllt wird.
Zunächst wird ein neuer Systembenutzer und ein Alias für root auf diesen Benutzer benötigt. Somit wird erreicht, dass die Mails an den Administrator im Postfach des neuen Benutzers landen. Dieses kann dann von fdm via IMAP ausgelsen werden, um die Mails durch eine neue Regel in einen eigenen Ordner einzusortieren:
$ sudo adduser sysmail
Password:
$ sudo nano /etc/aliases
root:sysmail
Folgende Zeilen müssen in ~/.fdm.conf ergänzt werden:
Anpassungen an ~/.fdm.conf |
account "hermes-sysmail" imap server "localhost" user "sysmail" pass "..." action "md_system" maildir "${maildir}/.System" match account "hermes-sysmail" or "^(To:|Cc:|Bcc:).*root@" in headers or "^(To:|Cc:|Bcc:|X-Envelope-To:).*login@" in headers or "^(To:|Cc:|Bcc:|X-Envelope-To:).*system@" in headers or "^(To:|Cc:|Bcc:|X-Envelope-To:).*security@" in headers or "^(To:|Cc:|Bcc:|X-Envelope-To:).*sicherheit@" in headers action "md_system" |
Zuletzt muss der Heimserver noch wei ein normaler Client eingerichtet werden, damit auch er automatisch gesichert werden kann. Diese Prozedur unterscheidet sich nicht von der unten beschriebenen Konfiguration der Clients.
Auf dem Backupserver kommt eine minimalistische Debianinstallation zum Einsatz (Basiskonfiguration ohne spezielle Zusatzpackete). Auf dieser werden dann ssh, sudo und noch ein Paar weitere Packete eingerichtet (siehe auch Sudo einrichten). rsync wird später die Backups ausführen, mit mercurial können Änderungen an den Backupskripten aufgezeichnet werden.
$ apt-get install rsync openssh-server sudo byobu mercurial
Ebenso wird der root-Account gesperrt:
$ sudo usermod -e 0 root
$ sudo passwd -l root
Dann muss sichergestellt werden, dass die USB-Festplatte beim Hochfahren des Backupservers automatisch eingehängt wird. Dabei ist bekannt, dass die Platte mit dem ReiserFS formatiert wurde und nur ein Dateisystem besitzt.
$ sudo nano /etc/fstab
/dev/sdb1 /media/backup reiserfs notail,noatime 0 2
$ sudo mkdir /media/backup
$ sudo mount /dev/sdb1
Aus Komfortgründen soll die Platte auch unter ~/media verfügbar sein, da auf allen Rechnern überwiegend der gleiche Benutzer (dennis) verwendet wird.
$ cd ~
$ mkdir media
$ ln -s /media/backup /home/dennis/media
Weiter geht's mit der Einrichtung von SSH. Auch hier muss erst ein Schlüsselpaar generiert werden:
$ ssh-keygen
In ~/.ssh/id_rsa.pub befindet sich dann der Public Key des Servers, der in die Datei ~/.ssh/authoirzed_keys jedes Clients kopiert werden muss. Andernfalls kann sich der Backupserver nicht an den Clients anmelden und somit das Backup nicht ausführen. In die Datei ~/.ssh/authoirzed_keys des Backupservers muss der Public Key von Hermes kopiert werden, damit dieser die jeweiligen Backupprogramme antoßen kann.
Die Backupskripte liegen in ~/bin und werden mit Mercurial verwaltet:
$ mkdir ~/bin
$ cd ~/bin
$ hg init
Bei den Skripten selbst handelt es sich um einfache Shellskripte, die alle mit backup- beginnen müssen, um als Backup Target aufgerufen werden zu können. Dabei unterscheide ich nach backup-host_name-Skripten, die einen einzelnen Rechner sichern und backup-many_name-Skripten, die eine Sammlung von Rechnern sichern und hierfür einefach die jeweiligen backup-host_name-Skripte aufrufen.
Ein backup-many_name-Skript sieht daher so aus:
~/bin/backup-many_wol (Beispiel) |
#! /bin/bash ## des - Mon Jul 12 2010: Full backup of all WoL-capable hosts # Include common functions BIN=$(dirname $0) . $BIN/_backup-functions.sh # Do backup echo_section_header "Full backup of all WakeOnLan capable systems" $BIN/backup-host_hermes; echo $BIN/backup-host_developer-showcase; echo $BIN/backup-host_blues-mobil; echo $BIN/backup-host_eeepc; echo $BIN/backup-host_jake; echo $BIN/backup-host_orphilia; echo $BIN/backup-host_backup; echo # Print disk usage statistics echo_end_sections df -h;echo echo_end_sections |
backup-host_name-Skript sieht hingegen so aus:
~/bin/backup-host_demo (Beispiel) |
#! /bin/bash ## des - Mon Jul 12 2010: backup demo set +e # Include common functions BIN=$(dirname $0) . $BIN/_backup-functions.sh # Do backup host=demo login=willy SSH="ssh $host -l $login" dpkg_file="/home/$login/dpkg-selections.txt" echo_new_backup $host $login date wake_host $host wake_rc=$? if [ $wake_rc == $WAKE_ERROR ]; then exit 1 fi $SSH "dpkg --get-selections > $dpkg_file" echo backup_remote_dir $host $login /home echo_separator backup_remote_dir $host $login /etc $SSH "rm $dpkg_file" shutdown_host $host $login date |
Hier sieht man schön, wie der Rechner erst hochgefahren wird und vor dem eigentlichen Backup noch allgemeine Aufgaben erledigt werden, zum Beispiel um eine Liste aller installierten Packete zu erstellen, die dann ebenfalls Teil des Backups wird.
Hinzu kommt ein leicht abgewandeltes Skript, um den Backupserver selbst zu sichern:
~/bin/backup-host_backup |
#! /bin/bash ## des - Mon Jul 12 2010: self-backup set +e # Include common functions BIN=$(dirname $0) . $BIN/_backup-functions.sh # Do backup host=backup login=dennis dpkg_file="/home/$login/dpkg-selections.txt" echo_new_backup $host $login date dpkg --get-selections > $dpkg_file echo backup_local_dir /home echo_separator backup_local_dir /etc rm $dpkg_file date |
All diese Skripte beziehen sich auf allgemeine Funktionen, die in _backup_functions.sh stecken: Download _backup_functions.sh
Mehr gibt es auf dem Backupserver nicht zu sehen. Die gesamte Funktionalität steckt einfach in einer Hand voll Shellskripten, die in regelmäßigen Abständen ausgeführt werden. Dabei muss beachtet werden, dass der Aufruf vom Heimserver aus ohne den backup-Prefix geschieht. Statt backup-host_demo heißt das Backupprogramm dort nur host_demo. Somit wird verhindert, dass andere als die dedizierten Backupskripte ausgeführt werden können.