Hetzner Level 19: Datei- und Datenbank-Backups anlegen

Nachdem ich nun eine ganze Weile an den Feinheiten gesessen und zwischendurch sogar den Hetzner-Support angeschrieben hatte, wollte ich euch nun mein Backup-Script vorstellen, das ich geschrieben habe, um alle relevanten Daten meines Level 19-Paketes von Hetzner sichern zu können. Dazu gehören:

  • MySQL-Datenbanken
  • PostgreSQL-Datenbanken
  • empfangene E-Mails
  • sämtliche Dateien

Diese Variante funktioniert leider nicht mit niedrigeren Hosting-Paketen von Hetzner, da dabei die SSH-Zugänge zum Einsatz kommen, die erst ab Level 19 zur Verfügung stehen. Während dem Backup kommen mehrere Tools zum Einsatz, die ihr vorher besorgen müsst. Dabei handelt es sich um die Programme PuTTY, PSCP und PSFTP.

Solltet ihr Addon-Domains auf euren Account aufgeschaltet haben, ist es wichtig, dass für alle Accounts dieser Addon-Domains der Shell-Zugriff freigeschaltet worden ist. Sollte dies nicht der Fall sein, könnt ihr unter Windows die E-Mails der Domains NICHT sichern!

Kommen wir nun aber erstmal zum eigentlichen Backup-Script - das ich wieder mal in PHP geschrieben habe. Anschließend werde ich euch die einzelnen Schritte erklären, die während dem Backup durchgeführt werden.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
<?php
  /* THESE SETTINGS ARE FREE TO EDIT */
 
  $mysqlConfig = array(array("host" => "[HOST]", "database" => "[DATABASE]", "user" => "[USER]", "password" => "[PASSWORD]"));
 
  $postgresqlConfig = array(array("host" => "[HOST]", "database" => "[DATABASE]", "user" => "[USER]", "password" => "[PASSWORD]"));
 
  $sshConfig = array(array("host" => "[HOST]", "user" => "[USER]", "password" => "[PASSWORD]"));

  $backupFile   = "backup.bck";
  $backupFolder = ".backup/";                    
  $usersDeref   = "users_deref.tar.gz";
  $usersRef     = "users_ref.tar.gz";

  /* STOP EDITING HERE IF YOU DO NOT KNOW WHAT YOU ARE DOING */
                       
  $useCMD = "";

  $pscp       = "pscp.exe";
  $pgpass     = ".pgpass";
  $psftp      = "psftp.exe";
  $putty      = "putty.exe";
  $remotePath = "/usr/home/";
  $users      = "users/";
   
  /* STOP EDITING HERE */
 
  function pathToGeneral($path) {
    return str_replace(DIRECTORY_SEPARATOR, "/", $path);
  }
 
  function pathFromGeneral($path) {
    return str_replace("/", DIRECTORY_SEPARATOR, $path);
  }

  $currentDate = date("Ymd_His");
  $done        = false;
  foreach ($sshConfig as $ssh) {
    $sshHost     = $ssh["host"];
    $sshPassword = $ssh["password"];
    $sshUser     = $ssh["user"];
 
    $localPath = pathToGeneral(dirname(__FILE__)) . "/";
    $localHome = "$localPath$currentDate/$sshUser/";
   
    if ((file_exists($localHome) && is_dir($localHome)) || mkdir($localHome, 0777, true)) {
      $remoteHome = "$remotePath$sshUser/";

      $backupScript = "mkdir $remotehome$backupFolder\n";
      // if this is the first round
      if (!$done) {
        // handle PostgreSQL backup
        $backupScript .= "chmod 0600 \"$remoteHome$pgpass\"\n";
        $pgpassScript = "";
        foreach ($postgresqlConfig as $postgresql) {
          $postgresqlDatabase = $postgresql["database"];
          $postgresqlHost     = $postgresql["host"];
          $postgresqlPassword = $postgresql["password"];
          $postgresqlUser     = $postgresql["user"];         

          $backupScript .= "pg_dump -f \"$remoteHome$backupFolder$postgresqlDatabase.postgresql\" -h \"$postgresqlHost\" -U \"$postgresqlUser\" \"$postgresqlDatabase\"\n";
          $pgpassScript .= "$postgresqlHost:*:$postgresqlDatabase:$postgresqlUser:$postgresqlPassword\n";
        }

        // handle MySQL backup
        foreach ($mysqlConfig as $mysql) {
          $mysqlDatabase = $mysql["database"];
          $mysqlHost     = $mysql["host"];
          $mysqlPassword = $mysql["password"];
          $mysqlUser     = $mysql["user"];
         
          $backupScript .= "mysqldump -h\"$mysqlHost\" -p\"$mysqlPassword\" -r\"$remoteHome$backupFolder$mysqlDatabase.mysql\" -u\"$mysqlUser\" \"$mysqlDatabase\"\n";
        }
      }

      // handle users backup
      $backupScript .= "tar -c -f \"$remoteHome$backupFolder$usersDeref\" -h -z \"$remoteHome$users\"\n";
      $backupScript .= "tar -c -f \"$remoteHome$backupFolder$usersRef\" -z \"$remoteHome$users\"\n";

      // if this is the first round
      if (!$done) {
        // put .pgpass on server
        file_put_contents("$localPath$backupFile", $pgpassScript);
        $tempCall = "\"" . pathFromGeneral("$localPath$pscp") . "\" -batch -l \"$sshUser\" -pw \"$sshPassword\" -sftp -v \"" . pathFromGeneral("$localPath$backupFile") . "\" \"$sshHost:$remoteHome$pgpass\"\n";  
        print("$useCMD$tempCall"); 
        exec("$useCMD$tempCall");
      }

      // put backup.bck on server
      file_put_contents("$localPath$backupFile", $backupScript);
      $tempCall = "\"" . pathFromGeneral("$localPath$pscp") . "\" -batch -l \"$sshUser\" -pw \"$sshPassword\" -sftp -v \"" . pathFromGeneral("$localPath$backupFile") . "\" \"$sshHost:$remoteHome$backupFile\"\n";
      print("$useCMD$tempCall");                   
      exec("$useCMD$tempCall");

      // execute backup.bck on server
      file_put_contents("$localPath$backupFile", "chmod 0700 $remoteHome$backupFile\n$remoteHome$backupFile\n");
      $tempCall = "\"" . pathFromGeneral("$localPath$putty") . "\" -l \"$sshUser\" -m \"" . pathFromGeneral("$localPath$backupFile") . "\" -pw \"$sshPassword\" \"$sshHost\"\n";
      print("$useCMD$tempCall");                   
      exec("$useCMD$tempCall");
     
      // download all files
      $sftpScript  = "lcd \"" . pathFromGeneral($localHome) . "\"\n";
      $sftpScript .= "cd \"$remoteHome\"\n";
      $sftpScript .= "mget -r \"$remoteHome*\"\n";
      $sftpScript .= "quit\n";
      file_put_contents("$localPath$backupFile", $sftpScript);
      $tempCall = "\"" . pathFromGeneral("$localPath$psftp") . "\" -b \"" . pathFromGeneral("$localPath$backupFile") . "\" -batch -be -l \"$sshUser\" -pw \"$sshPassword\" -v \"$sshHost\"\n";
      print("$useCMD$tempCall");                   
      exec("$useCMD$tempCall");
     
      // first round is over
      $done = true;
    }
  }
?>

Dieses Script könnt ihr, ohne Angabe weiterer Parameter, über das Command-Line Interface von PHP aufrufen. Es kümmert sich dann um das Anlegen der Backup-Ordner und das Aufrufen der benötigten Programme. Diese müssen übrigens im gleichen Ordner wie das Backup-Script liegen.

Bevor das Script verwendet werden kann, müssen einige Dinge konfiguriert werden. Dies sind primär die Arrays $mysqlConfig, $postgresqlConfig und $sshConfig. Diese enthalten die Zugangsdaten für die MySQL- und PostgreSQL-Datenbanken, sowie für die SSH-Zugänge der einzelnen Domains. Ihr könnt den einzelnen Arrays neue Elemente hinzufügen, falls mehrere Datenbanken oder Accounts gesichert werden sollen.
Zudem gibt es noch ein paar Dateinamen, die ihr konfigurieren könnt. Ihr solltet bedenken, dass all diese Dateien und Ordner während dem Backup angelegt und tlw. mehrfach überschrieben werden. Die entsprechenden Namen sollten also nicht anderweitig verwendet werden!

Kommen wir nun aber endlich zur eigentlichen Funktionalität. Das Script funktioniert wie folgt: Es geht durch die einzelnen SSH-Accounts und führt für jeden dieser Accounts eine Reihe von Befehlen aus. Der allererste Account hat zudem die zusätzliche Aufgabe, sich um die Datenbank-Backups zu kümmern.

In das Home-Verzeichnis jedes SSH-Accounts wird eine Datei mit dem Namen in der Variable $backupFile hochgeladen. Diese enthält die eigentlichen Backup-Aufgaben. Zuerst wird ein Ordner mit dem Namen in der Variable $backupFolder erstellt. In diesen werden dann u.a. die MySQL-Datenbanken (mit der Endung "*.mysql") und die PostgreSQL-Datenbanken (mit der Endung "*.postgresql") gesichert.
Um das Backup der PostgreSQL-Datenbanken durchführen zu können, muss zudem eine Datei mit dem Namen ".pgpass" erstellt werden. Diese enthält sämtliche Zugangsdaten der einzelnen PostgreSQL-Datenbanken.
In dem entsprechenden Ordner werden darüber hinaus auch Dateien (mit den Namen in den Variablen $usersDeref und $usersRef) abgelegt, die den gepackten Inhalt des "users/"-Ordners enthalten. Diese Archive enthalten sämtliche empfangene E-Mails. Das Packen ist wichtig, da die Mail-Dateien für Windows ungültige Dateinamen haben (sie beinhalten Doppelpunkte). Durch das Packen können sie nun trotzdem auf einem Windows-Rechner gesichert werden.
Abschließend werden alle Dateien heruntergeladen. Dieser Vorgang wird die meiste Zeit in Anspruch nehmen, da der gesamte Download über eine gesicherte SFTP-Verbindung stattfindet.

Natürlich können alle angelegten Dateien nach dem Backup wieder gelöscht werden - dies wird vom Backup-Script nicht selbst vorgenommen. Wirklich nötig ist es jedoch nicht, da die Dateien beim nächsten Backup-Vorgang sowieso wieder überschrieben bzw. neu angelegt werden.
Ihr solltet auch beachten, dass das Backup-Script nicht überprüft, ob die einzelnen Aufgaben korrekt ausgeführt worden sind. Das Script automatisiert lediglich den Abruf und nicht die Kontrolle des Backups. Diese Aufgabe verbleibt weiterhin bei euch.

Zum Schluss noch etwas Rechtliches:
Der Autor dieses Programms haftet nicht für Schäden an Soft- oder Hardware oder Vermögensschäden, die durch das Benutzen des Programms entstehen, es sei denn, diese beruhen auf einem grob fahrlässigen oder vorsätzlichen Handeln des Autors, seiner Erfüllungsgehilfen oder seiner gesetzlichen Vertreter.

Automatisierte Grüße, Kenny

5 Kommentare » Schreibe einen Kommentar

  1. Was für einen Shellzugang hat man den bei einem Hetzner-Hosting Paket? Kann man sich auf dem ganzen Server bewegen oder ist man in irgendeinem jail / chroot? Kann man Konfigurationsdateien aus /etc lesen?

    • Jailing ist, soweit ich das überblicken kann, nicht vorgesehen. Die Sicherung des Systems wurde über entsprechende Zugriffsrechte (chmod) realisiert. Der "/etc"-Ordner ist zugänglich. Massiv schützenswerte Ordner, wie z.B. "/etc/apache/ssl.key/" - in dem u.a. auch mein privater Schlüssel liegt - sind hingegen entsprechend gesichert.

      • Interessant. Angeblich setzen einige Provider als jail /home/benutzername oder /var/www/domain oder k.A. was für einen pfad sie verwenden. Aber man sieht bei denen dann wohl nicht mehr als im FTP-Client.

        Bei All-inkl.com, die keinen SSH anbieten (wenigstens in den billigen Paketen nicht) haut es einen sofort raus, wenn man per FTP eine PHP-Shell hochlädt. Die eigene IP landet in fail2ban und gar nix mehr geht. Nicht einmal mehr Mails abholen. Hab dann ganz nichts-ahnend beim Support angerufen, sie sollen mich wieder freischalten, und das hat er dann auch - genauso kommentarlos gemacht *ROFL*.

        Hintergrund ist vermutlich der: PHP läuft scheinbar als Modul, d.h. mit Apache-Rechten. Man könnte sich tatsächlich in Ruhe in Verzeichnissen anderer Benutzer umsehen.

        • Ich muss sagen, bei diesem Thema scheint Hetzner gute Arbeit bei den eigenen Server-Images zu leisten. Als ich meinen SSH-Zugang erhalten hatte, gab es tatsächlich Probleme in dieser Richtung - diese waren jedoch vollständig den entsprechenden Kunden geschuldet und wurden von Hetzner korrigiert, als ich sie darauf angesprochen hatte.

          Besonders hat mich bei Hetzner übrigens gefreut, dass sie von vorneherein ein anderes Problem berücksichtigt haben, über das ich mal berichtet hatte. Jeder Nutzer hat seinen eigenen "tmp"-Ordner - mit entsprechenden Zugriffsrechten - im eigenen Home-Verzeichnis. Damit sind solche Angriffe garnicht erst möglich.

          Und achja: Laut "ps" werden bei PHP-Aufrufen die entsprechenden FCGI-Instanzen mit den Rechten des entsprechenden Nutzers gestartet - dadurch hat man später auch keine Probleme bei Dateizugriffsrechten und ähnlichem...

Hinterlasse eine Antwort

Pflichtfelder sind mit * markiert.


Du kannst folgende HTML-Tags benutzen: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>