/*
###############################
###############################
###
### Title: FTP & MySQL Backup Script
### Author: Hannes Schurig
### Date: 09.06.2015
### Version: 1.2
### Changelog:
### 1.0: http://hannes-schurig.de/09/05/2015/ftp-backup-skript-in-php/
### 1.1: http://hannes-schurig.de/15/05/2015/ftp-backup-loesung-mit-php/
### 1.2: http://hannes-schurig.de/09/06/2015/backup-loesung-fuer-ftp-und-mysql-in-php/
### 1.2.1: new: backup ALL root subfolders, backup ALL root with excludes
### 1.2.2: issue fixed, added one more exclude parameter for files and folders
###
###############################
###############################
*/
// 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", 1800);
@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__));
$allFtpTime = 0;
$allSqlTime = 0;
$newFtpBackups = array();
$newSqlBackups = array();
$backupinfo = array();
$mailText = "";
echo "";
// ########## EDIT HERE ###################
// ### FTP Data
// backup ALL root folders:
// if backupAllRootFolders is 1, ftpFoldersToBackup will be ignored and all root folders will be backed up
$backupAllRootFolders = 0;
// exclude specific root folders from ALL-BACKUP
$excludeRootFolders = array();
// specific folder backup:
// if backupAllRootFolders is 0, you can specify multiple folders in ftpFoldersToBackup to backup
// which root folders should get backed up? comment is optional
// format: "foldername___comment"
// or: "foldername"
$ftpFoldersToBackup = array("ordner1", "ordner2", "ordner3");
// exclude files or folders - you should keep these four entries and add your stuff
$ignoreMore = array("*.sql.gz", "*.tar.gz", "usage", "logs");
// ### FTP Export
$copyToExternalFtp = 0; // 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
// $externalFtpUri = "ftp://admin:password@barketing.dns.com:21/Data/FTP-Backups";
// ### MySQL Data
$backupMysqlData = 0;
// one or more databases of the local mysql server to backup, multiple data combined with "___", comment is optional
// format: "dbName___dbUser___dbPassword___comment"
// or: "dbName___dbUser___dbPassword"
$databasesToBackup = array();
// ### Mailing Data
$sendMail = 1; // send notification mail when all backups are done - should be 1/"yes"/"ja" or 0/"no"/"nein"
$mailTo = "admin@backup.com"; // valid mail address to send the mail to
$mailSubject = "Automatische FTP Sicherung abgeschlossen"; // mail subject
// additional mail annotations that gets inserted after the main mail content
$mailAdditionalNotes = "Hiermit kann - wenn gewünscht - zusätzlicher Text in die Mail eingebaut werden.";
$mailBackupDetails = 0; // send information about successful backup files? should be 1/"yes"/"ja" or 0/"no"/"nein"
// ### General Options
$backupFileMaximum = 1; // how many archives should be stored?
$dir = $pfad."backup/"; // in which subfolder is this backup php file? this would be: "root/backup/"
// ######### STOP EDITING HERE ###################
echo "".date("d.m.Y G:i:s")."
";
$allTime = time();
echo "_________________ FTP __________________";
// ###################
// ### FTP BACKUPS ###
// ###################
if ($backupAllRootFolders==1) {
$ftpFoldersToBackup = array_filter(glob($pfad."*", GLOB_ONLYDIR));
$ftpFoldersToBackup = preg_replace('/(\/www\/htdocs\/\w+\/)/', '', $ftpFoldersToBackup);
if (isset($excludeRootFolders))
$ftpFoldersToBackup = array_diff($ftpFoldersToBackup, $excludeRootFolders);
}
foreach ($ftpFoldersToBackup as $folderData) {
// get data from folder string by splitting at "___"
$arrThisJobData = explode("___", $folderData, 2);
// check for minimal size of 1: folderName
if(count($arrThisJobData)>=1) {
$folder = $arrThisJobData[0];
// optional: comment - could be empty
if(empty($arrThisJobData[1])) {
$folderComment = "";
} else {
$folderComment = $arrThisJobData[1];
}
$ftpJobTime = time();
echo "
########################################
";
echo "Verzeichnis $folder wird gesichert...
";
flush();
// check if folder exists
if(!file_exists($pfad.$folder)) {
echo "Sicherung fehlgeschlagen. Zu sichernder Ordner $pfad$folder existiert nicht.";
continue;
}
// Name: [verzeichnis]_[Datum]_[Uhrzeit].tar.gz
$archivName = "FTP_$folder".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";
// ######### create backup
$archiveObject = new Archive_Tar($archivName, true);
$archiveObject->setIgnoreList($ignoreMore);
$archiveObject->createModify($pfad.$folder, "", $pfad);
// process backup
$archivePath = $dir.$archivName;
$archivSize = round(filesize($archivePath)/1000000, 1);
// check created archive
if(!validateBackup($archivePath)) {
// abort process due to wrong type or too small filesize (which likely is an error)
echo "Verzeichnis-Sicherung fehlgeschlagen. Erstelltes Archiv ist fehlerhaft.
";
// debug
echo "
Debug:
";
echo "Pfad: $archivePath
";
echo "Typ: ".gettype($archivePath)."
";
echo "Größe: $archivSize MB
";
continue;
} else {
$backupTime = time() - $ftpJobTime;
if (is_int($backupTime)) {
echo "Backup fertig: $archivName (Größe: $archivSize MB, Dauer: $backupTime Sekunden)
";
} else {
echo "Backup fertig: $archivName
";
}
$newFtpBackups[$archivName] = $archivSize." MB";
array_push($backupinfo, array($folder, $archivName, $archivSize." MB", $backupTime." Sekunden", date("d.m.Y G:i:s"), $folderComment));
}
// ########## delete backups if too many
echo "Aufräumen der FTP Backups...
";
cleanBackups("FTP", $folder, $dir, "gz");
// close job
$ftpBackupEndTime = time() - $ftpJobTime;
echo "Backup für Verzeichnis $folder abgeschlossen.
";
if (is_int($ftpBackupEndTime)) {
echo "######################################## (Dauer: $ftpBackupEndTime Sekunden)
";
} else {
echo "########################################
";
}
flush();
} else {
echo "Angabe der Verzeichnis Daten fehlerhaft.";
echo "Debug:
";
echo "Verzeichnis Name: $arrThisJobData[0]
";
echo "Verzeichnis Comment: $arrThisJobData[1]
";
}
}
// ########## echo backup summary and add it to mailtext
backupSummary($newFtpBackups);
flush();
echo "________________ MySQL ________________";
// ###################
// ### SQL BACKUPS ###
// ###################
if (!isset($backupMysqlData) || $backupMysqlData== 0 && in_array($backupMysqlData, array("no", "nein"))) {
echo "MySQL Datenbanken werden nicht gesichert. Option ist deaktiviert.
";
} else {
if(isset($databasesToBackup)&&count($databasesToBackup)>=1) {
foreach ($databasesToBackup as $databaseData) {
$sqlJobTime = time();
// get data from database string by splitting at "___"
$arrThisJobData = explode("___", $databaseData, 4);
// check for minimal size of 3: dbName, dbUser, dbPassword
if(count($arrThisJobData)>=3) {
$dbName = $arrThisJobData[0];
$dbUser = $arrThisJobData[1];
$dbPassword = $arrThisJobData[2];
// optional: comment - could be empty
if(empty($arrThisJobData[3])) {
$dbComment = "";
} else {
$dbComment = $arrThisJobData[3];
}
echo "
########################################
";
echo "Datenbank $dbName wird gesichert...
";
flush();
$sqlFile = "DB_$dbName".date('_Y-m-d_His').".sql";
exec("mysqldump -u '$dbUser' -p'$dbPassword' --quick --allow-keywords --add-drop-table --complete-insert --quote-names '$dbName' >$sqlFile");
exec("gzip $sqlFile");
$sqlFilePath = $dir.$sqlFile.".gz";
$sqlSize = round(filesize($sqlFilePath)/1000000, 1);
if(!validateBackup($sqlFilePath)) {
// wrong type or too small filesize (which likely is an error)
echo "Datenbank-Sicherung fehlgeschlagen. Erstellter Export ist fehlerhaft.
";
// debug
echo "
Debug:
";
echo "Datenbank Name: $dbName
";
echo "User: $dbUser
";
echo "Passwort: $dbPassword
";
echo "Kommentar: $dbComment
";
echo "Pfad: $sqlFilePath
";
echo "Typ: ".gettype($sqlFilePath)."
";
echo "Größe: $sqlSize MB
";
} else {
$backupTime = time() - $sqlJobTime;
if (is_int($backupTime)) {
echo "Backup fertig: $sqlFile (Größe: $sqlSize MB, Dauer: $backupTime Sekunden)
";
} else {
echo "Backup fertig: $sqlFile
";
}
$newSqlBackups[$sqlFile.".gz"] = $sqlSize." MB";
array_push($backupinfo, array($dbName, $sqlFile, $sqlSize." MB", $backupTime." Sekunden", date("d.m.Y G:i:s"), $dbComment));
}
// ########## delete DB backups if too many
echo "Aufräumen der DB Backups...
";
cleanBackups("DB", $dbName, $dir, "gz");
$sqlJobEndTime = time() - $sqlJobTime;
echo "Backup für Datenbank $dbName abgeschlossen.
";
if (is_int($sqlJobEndTime)) {
echo "######################################## (Dauer: $sqlJobEndTime Sekunden)
";
} else {
echo "########################################
";
}
flush();
} else {
echo "Angabe der DB Daten fehlerhaft.";
echo "Debug:
";
echo "DB Name: $arrThisJobData[0]
";
echo "DB User: $arrThisJobData[1]
";
echo "DB PW: $arrThisJobData[2]
";
echo "DB Comment: $arrThisJobData[3]
";
}
} // foreach
} // if
}
// ########## echo backup summary and add it to mailtext
backupSummary($newSqlBackups);
flush();
echo "________________ Export _________________";
// ########################
// ### COPY TO EXTERNAL ###
// ########################
if (!isset($copyToExternalFtp) || $copyToExternalFtp== 0 && in_array($copyToExternalFtp, array("no", "nein"))) {
echo "Backups werden nicht auf einen externen FTP kopiert. Option ist deaktiviert.
";
} else {
$ftpTime = time();
echo "
########################################
";
// get a ftp connection resource
$ftpCon = getFtpConnectionByURI($externalFtpUri);
flush();
// ########## copy all FTP backups to external FTP
copyBackupsToFtp($newFtpBackups, "FTP", $ftpCon);
flush();
// ########## copy all DB/SQL backups to external FTP
copyBackupsToFtp($newSqlBackups, "DB", $ftpCon);
flush();
ftp_close($ftpCon);
$ftpExternalEndTime = time() - $ftpTime;
array_push($backupinfo, array("Export -> external FTP", count($newSqlBackups) + count($newFtpBackups) . " Sicherungen", "", $ftpExternalEndTime." Sekunden", date("d.m.Y G:i:s"), ""));
if (is_int($ftpTime)) {
echo "######################################## (Dauer: $ftpExternalEndTime Sekunden)
";
} else {
echo "########################################
";
}
}
flush();
// ###################
// ### PRINT INFOS ###
// ###################
echo "
_____________ Zusammenfassung _____________";
$backupDetailsText = backupDetails($backupinfo);
echo $backupDetailsText;
// #################
// ### SEND MAIL ###
// #################
if (!isset($sendMail) || $sendMail== 0 && in_array($sendMail, array("no", "nein"))) {
echo "
Benachrichtigungsmail wurde nicht verschickt. Option ist deaktiviert.
";
} else {
if(!preg_match( '/^([a-zA-Z0-9])+([.a-zA-Z0-9_-])*@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-]+)+/' , $mailTo)) {
echo "
FEHLER: Mail konnte nicht versendet werden, da die Empfängeradresse ungültig ist!
";
} else {
if (empty($mailSubject)) $mailSubject = "Automatische FTP Sicherung abgeschlossen";
// add backup informations as table to mailtext
if (!isset($mailBackupDetails) || $mailBackupDetails== 0 && in_array($mailBackupDetails, array("no", "nein"))) {
// do not append information about backups
} else {
$mailText = backupDetails($backupinfo);
}
if (!empty($mailAdditionalNotes)) $mailText .= "
Anmerkungen:
".$mailAdditionalNotes;
mail(
$mailTo,
$mailSubject,
$mailText,
"From: backupscript@all-inkl-backup.de\r\n" . "Reply-To: backupscript@all-inkl-backup.de\r\n" . "Content-Type: text/html\r\n"
) or die("
FEHLER: Mail konnte wegen eines unbekannten Fehlers nicht versendet werden.
");
echo "
Benachrichtigungsmail wurde erfolgreich verschickt!
";
}
}
flush();
function backupDetails($backups) {
global $allTime;
$columnDefinition = array("Ordner/DB-Name", "Dateiname", "Größe", "Dauer", "Timestamp", "Kommentar");
$backupDetailsText = "
";
for($d=0;$d";
}
$backupDetailsText .= "";
for($i=0;$i";
}
$backupDetailsText .= "";
}
$backupDetailsText .= "
";
$allEndTime = time() - $allTime;
if (is_int($allEndTime)) {
$backupDetailsText .= "Gesamtbackupzeit: $allEndTime Sekunden
";
}
return $backupDetailsText;
}
function validateBackup($filePath) {
if(!file_exists($filePath)) {
echo "Fehler bei der Backup Überprüfung. Die Backup-Datei ist fehlerhaft oder nicht vorhanden.
";
return false;
}
if (!is_numeric(filesize($filePath))) {
echo "Fehler bei der Backup Überprüfung. Die Größe der Backup-Datei ist fehlerhaft.
";
return false;
}
$backupFullSize = filesize($filePath);
// check created dump
if (!is_file($filePath)==1 || !$backupFullSize>50) {
echo "Fehler bei der Backup Überprüfung. Die Backup-Datei ist fehlerhaft.
";
return false;
} else {
return true;
}
}
function cleanBackups($dataTypeIdentifier, $nameIdentifier, $dir, $fileType) {
global $backupFileMaximum;
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, $dataTypeIdentifier."_".$nameIdentifier)) == true &&
pathinfo($file)["extension"] == $fileType &&
!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. ";
if (count($backupFiles)-$backupFileMaximum>0) {
echo count($backupFiles)-$backupFileMaximum;
} else {
echo "0";
}
echo " Backups werden gelöscht:
";
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...
";
if (unlink($filePath)) {
echo "Datei erfolgreich gelöscht.
";
} else {
echo "Fehler beim Löschen der Datei.
";
}
}
$i++;
}
flush();
}
function backupSummary($backupFiles) {
global $allTime;
global $mailText;
$backupTime = time() - $allTime;
if(count($backupFiles)>0) {
echo "
Die automatische Sicherung hat ".count($backupFiles)." Datensätze (Verzeichnisse/Datenbanken) in insgesamt $backupTime Sekunden gesichert.
";
$mailText .= "
Die automatische Sicherung hat ".count($backupFiles)." Datensätze (Verzeichnisse/Datenbanken) in insgesamt $backupTime Sekunden gesichert.
";
} else {
echo "
Es scheint leider so als wenn keine Backups erfolgreich erstellt wurden.
";
$mailText .= "
Es scheint leider so als wenn keine Backups erfolgreich erstellt wurden.
";
}
}
// 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...
";
// try ftps over ssl, usally through port 21
$ftpCon = ftp_ssl_connect($match[4], $port, 30);
if (!gettype($ftpCon)=="resource") {
echo "FTP über SSL - Port $port - Verbindung fehlgeschlagen!
";
echo "Stelle (unsicheres FTP - Port $port) Verbindung zu FTP Server $match[4] her...
";
// 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 "SFTP Unterstützung noch nicht implementiert.
";
// if(!$port==22) {
// echo "SFTP Übertragung aber Port ist nicht 22. Ist der gewählte Port $port korrekt?
";
// }
// echo "Stelle (sichere sFTP - Port $port) Verbindung zu FTP Server $match[4]$match[5]:$match[6] her...
";
// $ftpCon = ssh2_connect($match[4], $port, 30);
// ssh2_auth_password($ftpCon, $match[2], $match[3]);
// $sftp = ssh2_sftp($ftpCon);
} else {
echo "Kein gültiger Verbindungstyp (ftp/sftp) angegeben.
";
}
} else {
echo "Der Port ist fehlerhaft angegeben. Bitte URI prüfen.
";
}
if ($ftpCon && gettype($ftpCon)=="resource")
{
echo "Verbindung hergestellt";
if ($login) {
echo ", Login erfolgreich";
if ($pasv) {
echo ", passiver Modus aktiviert";
if(!isset($match[6]) || $match[6] == "") {
echo ".
";
return $ftpCon;
} else if (ftp_chdir($ftpCon, $match[6])) {
echo ", Verzeichniswechsel zu $match[6] erfolgreich.
";
return $ftpCon;
} else {
echo ", Verzeichniswechsel zu $match[6] fehlerhaft.
";
return null;
}
} else {
echo ", passiver Modus konnte nicht aktiviert werden. Upload wird trotzdem probiert.
";
return $ftpCon;
}
} else {
echo ", Login fehlgeschlagen.
";
return null;
}
}
echo "Fehler beim Verbinden mit dem FTP Server $match[4]$match[5]$match[6].
";
echo "Debug:
";
echo "URI (komplett): $match[0]
";
echo "Typ: $match[1]
";
echo "URI ohne Typ: $match[4]
";
echo "Username: $match[2]
";
echo "Passwort: $match[3]
";
echo "Port: $match[5]
";
echo "Unterordner: $match[6]
";
echo "
";
// Or retun null
return null;
}
function copyBackupsToFtp($backupFiles, $backupType, $ftpCon) {
if(empty($backupFiles)) {
"
Es scheint leider so als wenn keine $backupType Backups erfolgreich erstellt wurden.
";
} else {
echo "".count($backupFiles)." $backupType Backups werden auf externen FTP kopiert...
";
if(gettype($ftpCon)=="resource") {
foreach ($backupFiles as $fileName => $fileSize) {
$uploadTime = time();
echo "Kopiere $fileName (Größe: $fileSize MB) auf den FTP...
";
flush();
if (ftp_put($ftpCon, $fileName, $fileName, FTP_ASCII)) {
$uploadEndTime = time() - $uploadTime;
if (is_int($uploadEndTime)) {
echo "Backup erfolgreich kopiert. (Dauer: $uploadEndTime Sekunden)
";
} else {
echo "Backup erfolgreich kopiert.
";
}
} else {
echo "Fehler beim Kopieren des Backups.
";
continue;
}
flush();
}
}
}
}
// from: http://stackoverflow.com/a/20147885/516047
function debug_to_console($data) {
if (is_array($data)) {
$output = "";
} else {
$output = "";
}
echo $output;
}
// 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;
}
?>