QNAP NAS: Alte Backups per Script löschen

Meine beiden QNAP Geräte verrichten seit nunmehr über einem halben Jahr ihren Dienst und das mehr als zufriedenstellend. Nachdem ich bereits vor ein paar Wochen den OpenVPN Server auf den Geräten installiert ergab sich in den letzten Wochen eine neue Herausforderung. Die beiden Geräte sind jeweils mit 2 x 1 TB Festplatte bestückt und als Raid-0 konfiguriert. Anfangs dachte ich noch, dass der Platz lange reichen wird doch leider hab ich da weit gefehlt und durch neue Anforderungen reicht der Platz nur noch in Maßen.

Die Sicherung von der einen Virtuellen Maschine läuft derzeit jede Nacht und verbraucht pro Sicherungsvorgang ~20 GB Speicher, was das Gerät rein rechnerisch nach ca. 50 Sicherungsvorgängen voll sein lässt. Mein Backup Script speichert die Backups der VM in einem Ordner auf demNAS.  Der Unterordner hat den Namen des aktuellen Datums, was ich mir noch zu nutzen machen werde. Dazu kommen noch viele andere Sachen die ebenfalls auf dem NAS abgelegt sind, sodass der Speicher eng wird. Bisher habe ich den Speicher wieder durch das manuelle Löschen von alten Backups freigegeben, doch auf Dauer ist das eine zu lästige und auch fehleranfällige Arbeit. Aus diesem Grund heraus brauchte ich eine Lösung für diese Aufgabe.

Ein Script musste her, dass automatisch die Sicherungen durchscannt, alte Backups erkennt, prüft ob sie noch behalten werden sollen und ggf. löscht. Ich hab mich der Einfachheit halber für PHP als Mittel der Wahl entschieden, da es schnell gehen sollte, ich Benachrichtigungsmail verschicken wollte und meine Bash-Script-Kenntnisse ehr ausreichend sind.

Webserver auf QNAP

Das Qnap hat einen eingebauten Webserver, der in der Administrationsoberfläche unter „Network Services“, „Web Server“ aktiviert werden kann. Der Webserver läuft dann direkt auf der IP Adresse des NAS und ist bei mir dann unter http://10.10.0.10/ erreichbar. Der root-Ordner des Webservers ist der Unterordner /Qweb/, der per Samba-Share, Windows-Freigabe, FTP oder wie auch immer erreicht werden kann.

Das Script

Ich will das Script hier nicht groß erläutern da es eigentlich keiner großen Erläuterung bedarf. Ich verwende nur PHP Boardmittel, keine Klassenstrukturen sondern einfach nur ein kleines Konfigurationsscript zum ablegen einiger Konstanten, ein Script welches mir aus der Ordnerstruktur ein Array mit den Files und dem Erstellungsdatum generiert. Dieser Array wird dann einer Funktion übergeben die intern den Array durchgeht, prüft ob die Backups älter als X-Tage sind oder vom Monatsersten stammen und löscht dann immer nur einen Eintrag von den alten Sicherungen.

Es wird deshalb nur eine Sicherung pro Scriptdurchlauf gelöscht, weil PHP eine Max-Execution Time von Scripten hat und diese nicht überschritten werden darf, der in der php.ini (auch über die Weboberfläche) konfiguriert werden kann. Ich möchte diesen Wert nicht ändern, da auch noch andere Skripte laufen, die dann auch von der Erhöhung betroffen wären. Natürlich steht es jedem frei diesen Wert hochzusetzen und das Script an der entsprechenden Stelle anzupassen.

Die config.inc.php enthält ein paar grundlegende Einstellungen, die ich nicht öfter eingeben möchte. Das Script verwende ich bisher an drei Stellen, sodass ich das dreimalige Definieren der Konstanten ausgelagert habe.

[code language=“php“]

<?php
error_reporting(E_ALL);
ob_start();
echo "<pre>";

define(‚REPORT_MAIL_ACTIVE‘, true);
define(‚REPORT_MAIL_ADDR‘, ’stephan@watermeyer.info‘);
define(‚REPORT_MAIL_FROM‘, ‚ahrens-nas@watermeyer.info‘);
define(‚DELETE_ONLY_ONE_BACKUP‘, true);

include_once ‚execute-delete.php‘;
?>

[/code]

Das Script zum herausfinden der Dateinamen ist auch gleichzeit das Script, dass ich nachher per Cronjob einbinden will. Außerdem werden hier Job-spezifische Konstanten definiert wie z.B. der Name des Jobs und die Anzahl der Backups die behalten werden sollen.

[code language=“php“]
<?php
include_once ‚config.inc.php‘;

define(‚MIN_BACKUPS_TO_KEEP‘, 5);
define(‚DONT_DELETE_NEW_BACKUPS‘, 7 * 24 * 60 * 60);
define(‚JOB_TITLE‘, ‚SRV-ALT: cleanup old backup‘);
define(‚TESTBETRIEB‘, false);
define(‚BASEFILE‘, ‚/share/backup/VM/srv-alt/‘);

$files = array();

$handle = opendir(BASEFILE);
while($file = readdir($handle)) {
if($file != "." && $file != "..") {
$files[strtotime($file)] = $file;
}
}
closedir($handle);

deleteBackups($files);
?>
[/code]

Das eigentliche Lösch-Script „execute-delete.php“ macht dann die eigentliche Arbeit. Es gibt über jeden wichtigen Arbeitsschritt ein paar Logmeldungen aus, die auch nachher als Email verpackt verschickt werden.

[code language=“php“]
<?php

function deleteBackups($files) {
$toDelete = array();

// sortieren nach timestamp
asort($files);

echo "\n<h1>Files</h1>\n";
print_r($files);

echo "\n<h1>Filter</h1>\n";

if(count($files) > MIN_BACKUPS_TO_KEEP) {
$toDelete = $files;
foreach($files as $key => $value) {
if(date("d", $key) == 1) {
// monats ersten herausfilter
unset($toDelete[$key]);
echo "Datei ist Sicherung des Monatsersten: [" . $key . "] => " . $value . "\n";
} else
// neue backups älter als X nicht löschen (die der aktuellen woche)
if(time() < ($key + DONT_DELETE_NEW_BACKUPS)) {
unset($toDelete[$key]);
echo "Datei ist neuer als " . (DONT_DELETE_NEW_BACKUPS / (60 * 60 * 24)) . " Tage: [" . $key . "] => " . $value . "\n";
}

}
}

echo "\n<h1>Dateien zum löschen</h1>\n";
print_r($toDelete);

echo "\n<h1>Datei die gelöscht wird</h1>\n";
flush();

$start = time();
if(count($toDelete) >= 1) {
foreach($toDelete as $key => $value) {
echo "rm -fR " . BASEFILE . $value . "\n";
echo "… kann ca. 3 Minuten dauern. Fenster offen lassen. Seite NICHT aktualisieren!\n";
flush();
if(TESTBETRIEB == false) {
exec("rm -fR " . BASEFILE . $value . " ");
}
if(DELETE_ONLY_ONE_BACKUP == true) {
break;
}
}
} else {
echo "Keine Dateien zum löschen übrig.";
}

echo "\n<h1>Duration</h1>\n";
echo "Dauer: " . (time() – $start) . " Sekunden\n";
flush();

echo "\n<h1>Diskspace</h1>\n";
$df = array();
exec(escapeshellcmd("df -h /dev/md0"), $df);

foreach($df as $entry) {
echo $entry . "\n";
}
echo "</pre>\n";

if(REPORT_MAIL_ACTIVE) {
echo "\n<strong>Status Mail wird gesendet</strong>\n";
mail(REPORT_MAIL_ADDR, JOB_TITLE, ob_get_contents(), "from:" . REPORT_MAIL_FROM);
}
}
?>
[/code]

Crontab / Cronjob

Das Script soll jede Nacht durchlaufen werden und das NAS aufräumen. Um das hinzubekommen habe ich einfach einen Cronjob definiert. Dazu muss man sich per SSH auf dem Qnap einklinken und dann folgendes machen:

[code language=“shell“]

vi /etc/config/crontab

[/code]

Ans Ende der Datei einen neuen Eintrag hinzufügen, der z.B. so aussehen kann. Dieser Eintrag führt dazu, dass jede Nacht um 1 Uhr das PHP Script mittels wget aufgerufen wird. Möglich wäre hier auch das Script direkt über den PHP Compiler aufzurufen, was aber von NAS und NAS unterschiedlich funktioniert

[code language=“bash“]

0 1 * * * /usr/bin/wget http://10.10.0.10/delete-vm-backups.php

[/code]

Wer Unterstützung beim Erstellen des Cronjobs braucht und nicht weiß, wie er dieses umständliche Format bedienen soll, der kann sich den Eintrag auch bei diesen kostenlosen Online Crontab Generator erstellen lassen.