Dieser Beitrag ist eine Ergänzung bzw. Erweiterung des vorherigen Posts „FTP Backup Skript in PHP“.
Folgende Änderungen werde ich hier besprechen:
- Erweiterung des Skripts
- Sicherheit: Absicherung mit .htpasswd
- Sicherheit: Absicherung mit .htaccess
- Automatisierung mit All-Inkl Cronjobs
Erweiterung des Backup Skripts
Das neue Skript bietet nun neue Funktionalitäten:
- Farbliche Hervorhebung
- Verbesserungen des Backup Prozesses, zusätzliche Überprüfungen und Debug Infos bei Fehlern
- Verbinden eines externen FTP Server und Kopieren aller neuen Backups
- Angabe eines beliebigen Ports
- Verbindung über FTPs (SSL FTP) Port 21 wird verwendet, unsicheres FTP nur noch als Fallback
- detailliertere Informationen über die Backups in der Benachrichtigungsmail
Nun werden also von beliebig vielen Ordner des FTP-Root Backups erstellt, gegebenenfalls aufgeräumt wenn mehr Backups existieren als aufgehoben werden sollen und anschließend alle neuen Backups auf einen externen FTP Server kopiert. Für den Upload wird der passive FTP Modus verwendet, da dieser in den seltensten Fällen Probleme macht. Sollte der Wechsel zum passiven FTP fehlschlagen, wird dennoch aktives FTP probiert.
Alle nötigen Informationen werden in den Variablen in Zeile 16 bis 25 angegeben. Format und Hilfe steht jeweils dabei, eigentlich sollte da alles klar sein.
Die Erweiterungen machen aus dem Skript eine beispielhafte Backup-Lösung für FTP Inhalte. Auch hier am Beispiel von All-Inkl als Hoster. Wer die Lösung unabhängig von All-Inkl einsetzen möchte, wird das Archive_Tar PHP Modul und irgendeine Art von Cronjob-Funktionalität brauchen.
Screenshot
Code
Update (02.06.2015): Anpassung der Variablennamen zur besseren Lesbarkeit, Fehlerkorrekturen, verbesserte FTP-URI-Verarbeitung, Portangabe möglich, Verbindung über FTPs (FTP über SSL über Port 21) möglich (mit Fallback zu unsicherem FTP wenn FTPs nicht funktioniert), erweiterte Informationen über die erfolgten Backups in der Benachrichtigungsmail, kleine Optimierungen
Code anzeigenDen Code könnt ihr bequem mit den Links/Rechts Pfeiltasten horizontal bewegen.
<? // PHP-Konfiguration optimieren // if no errors are shown, please check htaccess restrictions by "php_flag display_errors off" // in this or parent folders @error_reporting(E_ALL); @ini_set("max_execution_time", 300); @ini_set("memory_limit", "256M"); header('Content-Type: text/html; charset=utf-8'); include "Archive/Tar.php"; $pfad = preg_replace('/(\/www\/htdocs\/\w+\/).*/', '$1', realpath(__FILE__)); $alltime = 0; $newbackups = array(); $backupinfo = array(); echo "<style>.ok{color:#478F47;}.err{color:#DA3833;}.grey{color:grey;}.warn{color:#f0ad4e;}th,td{border-bottom: 1px solid #aaa;}</style>"; // ########## EDIT THIS VARIABLES ################### $foldertobackup = array("tools", "reports"); // which root folders should get backed up? $copytoexternalftp = 1; // copy new backup files to external ftp server? should be 1/"yes"/"ja" or 0/"no"/"nein" // external (ftp) servers to copy new backups to, format: // in general: ftp://username:password@url:port/path (port is required!) // ftp://user:pw@ftp.server.com:21/ // ftp://user:pw@serverurl.com:21/optional/path // secure sftp connection (port 22) in preparation but not ready yet $externalftpuri = "ftp://admin:password@firma.dns.com:21/Data/FTP-Backups"; $backupfilemaximum = 2; // how many archives should be stored? $dir = $pfad."backup/"; // in which subfolder is this backup php file? this would be: "root/backup/" $sendmail = 1; // send notification mail when all backups are done - should be 1/"yes"/"ja" or 0/"no"/"nein" $sendmailto = "adminmail@firma.de"; // valid mail address to send the mail to // ################################################## echo "<span class='ok'>".date("d.m.Y G:i:s")."</span><br>"; foreach ($foldertobackup as $verzeichnis) { $jobtime = time(); echo "<br><br>########################################<br>"; echo "<strong>Verzeichnis $verzeichnis wird gesichert...</strong><br>"; flush(); // check if folder exists if(!file_exists($pfad.$verzeichnis)) { echo "<span class='err'>Sicherung fehlgeschlagen. Zu sichernder Ordner $pfad$verzeichnis existiert nicht.</span>"; continue; } // Name: [verzeichnis]_[Datum]_[Uhrzeit].tar.gz $archivname = $verzeichnis.date('_Y-m-d_His').".tar.gz"; // Name: [All-Inkl-Accountname]_[Datum]_[Uhrzeit].tar.gz //$archivname = preg_replace('/.+\/(.+)\/$/', '$1', $pfad).date('_Y-m-d_His').".tar.gz"; // Auszuschließende Ressourcen $ignorieren = array("*.sql.gz", "*.tar.gz", "usage", "logs"); // ######### create backup $archiv = new Archive_Tar($archivname, true); $archiv->setIgnoreList($ignorieren); $archiv->createModify($pfad.$verzeichnis, "", $pfad); $backuptime = time() - $jobtime; $archivsize = round(filesize($dir.$archivname)/1000000); if (!is_numeric($archivsize)) { $archivsize = "filesize error"; } if (is_int($backuptime)) { echo "<span class='ok'>Backup fertig: $archivname (Größe: $archivsize MB, Dauer: $backuptime Sekunden)</span><br>"; } else { echo "<span class='ok'>Backup fertig: $archivname</span><br>"; } // check created archive if (!is_object($archiv)==1 || !is_numeric($archivsize) || !$archivsize>50) { // abort process due to wrong type or too small filesize (which likely is an error) echo "<span class='err'>Sicherung fehlgeschlagen. Erstelltes Archiv ist fehlerhaft.</span><br>Mehr Infos <a href='http://hannes-schurig.de/09/05/2015/ftp-backup-skript-in-php/' target='blank'>hier</a><br>"; // debug echo "<p class='grey'>Debug:<br>"; echo "Pfad: $dir.$archivname<br>"; echo "Typ: ".gettype($archiv)."<br>"; echo "Größe: $archivsize MB</p>"; continue; } $newbackups[$archivname] = $dir.$archivname; echo "Aufräumen der Backups...<br>"; flush(); // integer starts at 0 before counting $i = 0; $backupfiles = array(); // ######### collect valid backup files if ($handle = opendir($dir)) { while (($file = readdir($handle)) !== false) { if ( is_int(strpos($file, $verzeichnis)) == true && preg_match('/\.tar.gz$/i', $file) && !in_array($file, array('.', '..')) && !is_dir($dir.$file) ) { $backupfiles[$dir.$file] = filectime($dir.$file); } } } echo count($backupfiles)." valide Backups dieses Ordners gefunden, "; echo "$backupfilemaximum Backups sollen behalten werden. "; $backupcountdif = count($backupfiles)-$backupfilemaximum; if ($backupcountdif<=0) { echo "Kein Backup wird gelöscht.<br>"; } else if ($backupcountdif==1) { echo "1 Backup wird gelöscht:<br>"; } else if ($backupcountdif>=2) { echo "$backupcountdif Backups werden gelöscht:<br>"; } flush(); // ######### sort and delete oldest backups // sort backup files by date arsort($backupfiles); // reset counter variable $i = 0; // delete oldest files foreach ($backupfiles as $filepath => $value) { if($i>=$backupfilemaximum) { echo "$filepath wird gelöscht...<br>"; if (unlink($filepath)) { echo "<span class='ok'>Datei erfolgreich gelöscht.</span><br>"; } else { echo "<span class='err'>Fehler beim Löschen der Datei.</span><br>"; } } $i++; } $jobendtime = time() - $jobtime; array_push($backupinfo, array($verzeichnis, $archivname, $archivsize." MB", $jobendtime." Sekunden", date("d.m.Y G:i:s"))); echo "<span class='ok'>Backup für Verzeichnis $verzeichnis abgeschlossen.</span><br>"; if (is_int($jobendtime)) { echo "######################################## (Dauer: $jobendtime Sekunden)<br>"; $alltime += $jobendtime; } else { echo "########################################<br>"; } } if(count($newbackups)>0) { echo "<br><span class='ok'>Die automatische Sicherung des Skripts '".pathinfo(__FILE__, PATHINFO_BASENAME)."' hat ".count($newbackups)." Verzeichnisse in insgesamt $alltime Sekunden gesichert.</span><br><br>"; } else { echo "<br><span class='err'>Es scheint leider so als wenn keine Backups erfolgreich erstellt wurden.</span><br><br>"; } flush(); // ######### copy backups to external storages if (!isset($copytoexternalftp) || $copytoexternalftp== 0 && in_array($copytoexternalftp, array("no", "nein"))) { echo "<span class='warn'>Backups werden nicht auf einen externen FTP kopiert. Option ist deaktiviert.</span><br>"; } else { $ftptime = time(); echo "########################################<br>"; echo "<strong>".count($newbackups)." Backups werden auf externen FTP kopiert...</strong><br>"; flush(); $ftpcon = getFtpConnectionByURI($externalftpuri); flush(); if(gettype($ftpcon)=="resource") { foreach ($newbackups as $filename => $fullpath) { $uploadtime = time(); echo "Kopiere .$filename. (Größe: $archivsize MB) auf den FTP...<br>"; flush(); if (ftp_put($ftpcon, $filename, $filename, FTP_ASCII)) { $uploadendtime = time() - $uploadtime; if (is_int($uploadendtime)) { echo "<span class='ok'>Backup erfolgreich kopiert. (Dauer: $uploadendtime Sekunden)</span><br>"; } else { echo "<span class='ok'>Backup erfolgreich kopiert.</span><br>"; } } else { echo "<span class='err'>Fehler beim Kopieren des Backups.</span><br>"; } flush(); } } $ftpendtime = time() - $ftptime; if (is_int($ftptime)) { echo "######################################## (Dauer: $ftpendtime Sekunden)<br>"; } else { echo "########################################<br>"; } } flush(); // ######### send mail if (!isset($sendmail) || $sendmail== 0 && in_array($sendmail, array("no", "nein"))) { echo "<br><span class='warn'>Benachrichtigungsmail wurde nicht verschickt. Option ist deaktiviert.</span><br>"; } else { if(!preg_match( '/^([a-zA-Z0-9])+([.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-]+)+/' , $sendmailto)) { echo "<br><span class='err'>FEHLER: Mail konnte nicht versendet werden, da die Adresse ungültig ist!</span><br>"; } else { $mailsubject = "Automatische FTP Sicherung abgeschlossen"; $mailtext = "Die automatische Sicherung des FTP-PHP-Backup-Skripts ".pathinfo(__FILE__, PATHINFO_BASENAME)." hat ".count($foldertobackup)." Verzeichnisse in insgesamt $alltime Sekunden gesichert.<br><br>"; // add backup informations as table to mailtext $mailtext .= "<table border=0 style='border-spacing:0;'><thead><tr><th>Verzeichnis</th><th>Dateiname</th><th>Größe</th><th>Dauer</th><th>Timestamp</th></tr></thead><tbody>"; for($i=0;$i<count($backupinfo);$i++) { $mailtext .= "<tr>"; for($j=0;$j<count($backupinfo[$i]);$j++) { $mailtext .= "<td>" . $backupinfo[$i][$j] . "</td>"; } $mailtext .="</tr>"; } $mailtext .= "</tbody></table>"; mail( $sendmailto, $mailsubject, $mailtext, "From: backupscript@{$_SERVER['SERVER_NAME']}\r\n" . "Reply-To: backupscript@{$_SERVER['SERVER_NAME']}\r\n" . "Content-Type: text/html\r\n" ) or die("<br><span class='err'>FEHLER: Mail konnte wegen eines unbekannten Fehlers nicht versendet werden.</span><br>"); echo "<br><span class='ok'>Benachrichtigungsmail wurde erfolgreich verschickt!</span><br>"; } } flush(); // function to get ftp connection object from URI // basics were from: http://php.net/manual/de/function.ftp-connect.php#89811 function getFtpConnectionByURI($uri) { // Split FTP URI into: // $match[0] = ftp://admin:password@barketing.dns.com:21/Data/FTP-Backups // $match[1] = ftp // $match[2] = admin // $match[3] = password // $match[4] = barketing.dns.com // $match[5] = 21 // $match[6] = /Data/FTP-Backups preg_match("/([a-z]*?):\/\/(.*?):(.*?)@(.*?):(.*?)(\/.*)/i", $uri, $match); $ftpcon = null; // check if port is set and uri is formatted correctly if(is_int(intval($match[5]))) { $port = intval($match[5]); // check if ftp(s) or sftp is chosen if($match[1]=="ftp") { // set up and ftp(s) connection, login echo "Stelle (FTPs über SSL - Port $port) Verbindung zu FTP Server $match[4] her...<br>"; // try ftps over ssl, usally through port 21 $ftpcon = ftp_ssl_connect($match[4], $port, 30); if (!gettype($ftpcon)=="resource") { echo "<span class='warn'>FTP über SSL - Port $port - Verbindung fehlgeschlagen!</span><br>"; echo "Stelle (unsicheres FTP - Port $port) Verbindung zu FTP Server $match[4] her...<br>"; // try normal insecure ftp $ftpcon = ftp_connect($match[4], $port, 30); } if (gettype($ftpcon)=="resource") { $login = ftp_login($ftpcon, $match[2], $match[3]); $pasv = ftp_pasv($ftpcon, true); } } else if($match[1]=="sftp") { echo "<span class='err'>SFTP Unterstützung noch nicht implementiert.</span><br>"; // if(!$port==22) { // echo "<span class='warn'>SFTP Übertragung aber Port ist nicht 22. Ist der gewählte Port $port korrekt?</span><br>"; // } // echo "Stelle (sichere sFTP - Port $port) Verbindung zu FTP Server $match[4]$match[5]:$match[6] her...<br>"; // $ftpcon = ssh2_connect($match[4], $port, 30); // ssh2_auth_password($ftpcon, $match[2], $match[3]); // $sftp = ssh2_sftp($ftpcon); } else { echo "<span class='err'>Kein gültiger Verbindungstyp (ftp/sftp) angegeben.</span><br>"; } } else { echo "<span class='err'>Der Port ist fehlerhaft angegeben. Bitte URI prüfen.</span><br>"; } if ($ftpcon && gettype($ftpcon)=="resource") { echo "<span class='ok'>Verbindung hergestellt</span>"; if ($login) { echo "<span class='ok'>, Login erfolgreich</span>"; if ($pasv) { echo "<span class='ok'>, passiver Modus aktiviert</span>"; if(!isset($match[6]) || $match[6] == "") { echo ".<br>"; return $ftpcon; } else if (ftp_chdir($ftpcon, $match[6])) { echo "<span class='ok'>, Verzeichniswechsel zu $match[6] erfolgreich.</span><br>"; return $ftpcon; } else { echo "<span class='err'>, Verzeichniswechsel zu $match[6] fehlerhaft.</span><br>"; return null; } } else { echo "<span class='err'>, passiver Modus konnte nicht aktiviert werden. Upload wird trotzdem probiert.</span><br>"; return $ftpcon; } } else { echo "<span class='err'>, Login fehlgeschlagen.</span><br>"; return null; } } echo "<span class='err'>Fehler beim Verbinden mit dem FTP Server $match[4]$match[5]$match[6].</span><br>"; echo "<p class='grey'>Debug:<br>"; echo "URI (komplett): $match[0]<br>"; echo "Typ: $match[0]<br>"; echo "URI ohne Typ: $match[4]<br>"; echo "Username: $match[2]<br>"; echo "Passwort: $match[3]<br>"; echo "Port: $match[5]<br>"; echo "Unterordner: $match[6]<br>"; echo "</p>"; // Or retun null return null; } // from: http://stackoverflow.com/a/10473026/516047 function startsWith($haystack, $needle) { // search backwards starting from haystack length characters from the end return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE; } ?>
Sicherheit: Absicherung mit .htpasswd
Das Verzeichnis, in dem die backup.php und die Backups liegen, sollte natürlich mit einer .htpasswd abgesichert werden. Mit einer eingerichteten .htpasswd Datei ist zuerst ein Login nötig, eh man auf bestimmte Bereiche des Webspaces zugreifen darf:
Die Datei .htpasswd enthält hierbei die Login Daten und in der .htaccess des Backup Unterordners wird festgelegt, dass eine .htpasswd diesen Ordner schützt. Die .htpasswd generiert ihr euch am besten mit diesem Generator und baut sie dann folgendermaßen in die .htaccess dieses Ordners ein:
AuthType Basic AuthName "Backups" AuthUserFile /www/htdocs/all-inkl-account/backup/.htpasswd Require valid-user
Sicherheit: Absicherung mit .htaccess
Da wir schonmal bei .htaccess sind, erhöhen wir die Sicherheit mit ein paar weiteren grundlegenden Zeilen:
#block access to certain file types <FilesMatch ".(htaccess|htpasswd|ini|phps|log|sh|tar.gz)$"> Order Allow,Deny Deny from all </FilesMatch> # disable directory browsing Options All -Indexes # prevent basic url hacking stuff # from: http://www.queness.com/post/5421/17-useful-htaccess-tricks-and-tips RewriteEngine On # proc/self/environ? no way! RewriteCond %{QUERY_STRING} proc/self/environ [OR] # Block out any script trying to set a mosConfig value through the URL RewriteCond %{QUERY_STRING} mosConfig_[a-zA-Z_]{1,21}(=|\%3D) [OR] # Block out any script trying to base64_encode crap to send via URL RewriteCond %{QUERY_STRING} base64_encode.*(.*) [OR] # Block out any script that includes a <script> tag in URL RewriteCond %{QUERY_STRING} (<|%3C).*script.*(>|%3E) [NC,OR] # Block out any script trying to set a PHP GLOBALS variable via URL RewriteCond %{QUERY_STRING} GLOBALS(=|[|\%[0-9A-Z]{0,2}) [OR] # Block out any script trying to modify a _REQUEST variable via URL RewriteCond %{QUERY_STRING} _REQUEST(=|[|\%[0-9A-Z]{0,2}) # Send all blocked request to homepage with 403 Forbidden error! RewriteRule ^(.*)$ /index.htm [F,L] ErrorDocument 401 /backup/index.htm ErrorDocument 403 /backup/index.htm ErrorDocument 404 /backup/index.htm ErrorDocument 500 /backup/index.htm
Dadurch werden Zugriffe auf bestimmte Dateitypen (auch die Backup Dateien), Verzeichnisse und Zugriffe mit sicherheitskritischen Merkmalen unterbunden. Alle diese nicht validen Zugriffe bekommen die index.htm serviert, welches einfach nur eine leere HTML Datei ist. Somit wird den Abfragenden auch kein detaillierter Grund gegeben, warum der Zugriff fehlschlug.
Automatisierung mit All-Inkl Cronjobs
Zu guter Letzt hilft diese Sicherungslösung natürlich nur, wenn sie automatisiert wird. Auch dies ist stark abhängig von eurem Hoster, System, dem Anwendungsbereich usw.
Im Falle von All-Inkl als Webhoster, könnt ihr die Cronjob Funktionalität im KAS (KAS -> Tools -> Cronjobs) benutzen:
All-Inkl bietet sogar das Zusenden aller Skriptausgaben. Ihr erhaltet also zusätzlich zu dem kurzen E-Mail-Bericht, der im Skript generiert werden kann, noch eine weitere Mail mit allen Ausgaben. Diese sind dann zwar nicht mehr farbig, aber was solls:
Danke an Kenny für das wertvolle Feedback im letzten Artikel, das dazu beigetragen hat, dass ich diese Erweiterung nochmal gepostet habe. Meinst du, dass das jetzt eine Basic Backup Lösung sein könnte? Das einzige, was noch fehlt, ist das Backup Management auf dem externen FTP Server, damit der nicht überläuft. Aber das ist nun wirklich Aufgabe der Admins zu entscheiden und zu verwalten, wie lange Backups aufgehoben werden sollen.
Ahoi. Hast du bei den .htaccess-Regeln geprüft, ob du die tatsächlich brauchst? Die von der Webseite kopierten sehen ziemlich individuell und crappy aus. Oder was fängst du beispielsweise in einem „proc/self/environ“ Ordner an?
Aber den Aufruf habe ich doch mit der htpasswd gesichert, siehe Überschrift „Sicherheit: Absicherung mit .htpasswd“. Ich habe diesen Abschnitt nochmal etwas ausführlicher beschrieben und ein Bild eingebaut, dann sollte das besser auffallen 😉
Also selbst wenn man die URL des Skripts hat – komplett – muss vorher ein Login erfolgen.
Du musst mir mal nochmal grob verraten, wie man die URLs (egal ob vom Skript oder von den Backup Dateien) „potentiell herausfinden“ kann. Ich wähle ja extra schon Subdomain-Namen, die eben nicht einfach sind, also nur „backup.han…“. Da würde es mich schon nervös machen, wenn man die Subdomains und dann die enthaltenen Daten irgendwie rausfischen könnte. Und das dann sogar trotz vereinheitlichter Serverantwort mit einer leeren HTML Datei usw.
Htaccess Regeln… ich weiß nicht welche Taktik man da verfolgen sollte. Ich kenne Zighundert Zeilen aus verschiedensten Quellen, die jede Art von „seltsamer Abfrage“ filtern. Auf Arbeit schützen wir auch unsere Auftritte durch einen ganzen Haufen solcher Filter. Es ist natürlich immer die Frage, ob man sie braucht. Ob jemals ein solcher Aufruf erfolgt. Aber ich finde halt auch, dass es nicht schadet, sie drin zu haben. Irgendwelche Experten werden sich schon was dabei gedacht haben die Filter so zu basteln. Sicher, weil Anfragen dieser Art existieren und benutzt werden.
Sowas wie Base64 enkodierte Befehle oder JS Stuff kann ich mir schon gut vorstellen, dass da automatisierte Scanner immernoch das komplette Internet mit gewissen URLs befeuern auf der Suche nach Servern, die da irgendwie noch verwundbar sind.
Und selbst wenn – gerade bei mir – nie jemand einen solchen Zugriff macht, stehen die Regeln halt einfach so da und stören keinen 😉
bei mir geht seid einiger zeit nix mehr. er rödelt und rödelt wenn ich die url aufrufe aber nichts passiert.
hat all inkl was geändert ????
tja bei mir geht es einfach nichtder cornjob springt an hab die email sende funktion auch aktiviert aber es sichert 5 ftp backups ( ich habe 10 Seiten, fehlen also 5 ) und das wars dann weder die datenbanken noch die fehlenden seiten werden weiter gesichert . in der email ist dass so zu sehen
5 Backups werden also als Dateiform im Ziel-Backupordner abgelegt und es kommt trotzdem die Mail meines Skripts? Die wird ja erst am Ende des Skripts erstellt und verschickt. Ich hätte auf Skript Timeout gesetzt, aber dann würde die Mail nicht rausgehen. Kriegst du die cronjob Mail von All-Inkl oder die Skript Mail meines Skripts?
Kannst du vielleicht mal den Mailinhalt hier posten oder an mich weiterleiten, damit ich mir das mal ansehen kann?
Sind es immer dieselben Seiten, die nicht gehen? Hast du mal versucht nur 2, 3 von denen zu sichern, die sonst nicht gehen? Die Frage ist ja, ob es an der großen Anzahl der Projekte liegt, an der Größe einzelner Projekte. Einfach mal die große Sicherung aufsplitten, Reihenfolge der Sicherung ändern und gucken was passiert.
Hallo billy,
ich hoffe du hast das Verzeichnis, in dem das Backup-Script liegt zum Sichern ausgeschlossen, oder zumindest den Dateityp, den das Script erzeugt. Nicht das sich hier eine endlosschleife ergibt.
Ansonsten schließe ich mich Hannes an, zeige die Mail und wir sehen weiter.
ist das script funktionstüchtig auch wenn man für jede seiner domains eigenen account benzutzt bei all inkl???
Hallo und entschuldige die verspätete Antwort,
du führst das Skript ja innerhalb eines Accounts aus, im Nutzerkontext eines Nutzers deines Accounts oder mit einem Cronjob in einem Account. Es ist also immer im Kontext des Accounts. Des Weiteren dürfte dein Account vermutlich gar nicht auf den Webspace-Inhalt eines anderen Accounts zugreifen, selbst wenn ich das programmieren würde. Also ich denke da ist nicht viel mehr möglich als das Skript in jedem Account als Cronjob einzeln einzurichten.
LG