Skip to content

Commit 8e777cd

Browse files
author
Marius Burkard
committed
Merge branch '5866-database-backups' into 'develop'
fix cleanup of untracked backup files Closes #5956 and #5945 See merge request ispconfig/ispconfig3!1335
2 parents eb3f9d0 + 3a0383c commit 8e777cd

File tree

1 file changed

+82
-53
lines changed

1 file changed

+82
-53
lines changed

server/lib/classes/backup.inc.php

Lines changed: 82 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,7 @@ protected static function clearBackups($server_id, $web_id, $max_backup_copies,
721721
}
722722

723723
/**
724-
* Garbage collection: deletes records from database about files that do not exist and deletes untracked files.
724+
* Garbage collection: deletes records from database about files that do not exist and deletes untracked files and cleans up backup download directories.
725725
* The backup directory must be mounted before calling this method.
726726
* @param int $server_id
727727
* @param string|null $backup_type if defined then process only backups of this type
@@ -734,33 +734,38 @@ protected static function backups_garbage_collection($server_id, $backup_type =
734734
global $app;
735735

736736
//First check that all records in database have related files and delete records without files on disk
737-
$args = array();
738-
$args_domains = array();
737+
$args_sql = array();
738+
$args_sql_domains = array();
739+
$args_sql_domains_with_backups = array();
739740
$server_config = $app->getconf->get_server_config($server_id, 'server');
740741
$backup_dir = trim($server_config['backup_dir']);
741742
$sql = "SELECT * FROM web_backup WHERE server_id = ?";
742743
$sql_domains = "SELECT domain_id,document_root,system_user,system_group,backup_interval FROM web_domain WHERE server_id = ? AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias')";
743-
array_push($args, $server_id);
744-
array_push($args_domains, $server_id);
744+
$sql_domains_with_backups = "SELECT domain_id,document_root,system_user,system_group,backup_interval FROM web_domain WHERE domain_id in (SELECT parent_domain_id FROM web_backup WHERE server_id = ?" . ((!empty($backup_type)) ? " AND backup_type = ?" : "") . ") AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias')";
745+
array_push($args_sql, $server_id);
746+
array_push($args_sql_domains, $server_id);
747+
array_push($args_sql_domains_with_backups, $server_id);
745748
if (!empty($backup_type)) {
746749
$sql .= " AND backup_type = ?";
747-
array_push($args, $backup_type);
750+
array_push($args_sql, $backup_type);
751+
array_push($args_sql_domains_with_backups, $backup_type);
748752
}
749753
if (!empty($domain_id)) {
750754
$sql .= " AND parent_domain_id = ?";
751755
$sql_domains .= " AND domain_id = ?";
752-
array_push($args, $domain_id);
753-
array_push($args_domains, $domain_id);
756+
$sql_domains_with_backups .= " AND domain_id = ?";
757+
array_push($args_sql, $domain_id);
758+
array_push($args_sql_domains, $domain_id);
759+
array_push($args_sql_domains_with_backups, $domain_id);
754760
}
755-
array_unshift($args, $sql);
756-
array_unshift($args_domains, $sql_domains);
757761

758762
$db_list = array($app->db);
759763
if ($app->db->dbHost != $app->dbmaster->dbHost)
760764
array_push($db_list, $app->dbmaster);
761765

766+
// Cleanup web_backup entries for non-existent backup files
762767
foreach ($db_list as $db) {
763-
$backups = call_user_func_array(array($db, "queryAllRecords"), $args);
768+
$backups = $app->db->queryAllRecords($sql, true, $args_sql);
764769
foreach ($backups as $backup) {
765770
$backup_file = $backup_dir . '/web' . $backup['parent_domain_id'] . '/' . $backup['filename'];
766771
if (!is_file($backup_file)) {
@@ -771,27 +776,42 @@ protected static function backups_garbage_collection($server_id, $backup_type =
771776
}
772777
}
773778

774-
foreach ($db_list as $db) {
775-
$domains = call_user_func_array(array($db, "queryAllRecords"), $args_domains);
776-
foreach ($domains as $rec) {
777-
$domain_id = $rec['domain_id'];
778-
$domain_backup_dir = $backup_dir . '/web' . $domain_id;
779-
$files = self::get_files($domain_backup_dir);
780-
781-
//Delete files that are in backup directory, but do not exist in database
782-
if (!empty($files)) {
783-
$sql = "SELECT backup_id,filename FROM web_backup WHERE server_id = ? AND parent_domain_id = ?";
784-
$backups = $db->queryAllRecords($sql, $server_id, $domain_id);
779+
// Cleanup backup files with missing web_backup entries (runs on all servers)
780+
$domains = $app->dbmaster->queryAllRecords($sql_domains_with_backups, true, $args_sql_domains_with_backups);
781+
foreach ($domains as $rec) {
782+
$domain_id = $rec['domain_id'];
783+
$domain_backup_dir = $backup_dir . '/web' . $domain_id;
784+
$files = self::get_files($domain_backup_dir);
785+
786+
if (!empty($files)) {
787+
// leave out server_id here, in case backup storage is shared between servers
788+
$sql = "SELECT backup_id, filename FROM web_backup WHERE parent_domain_id = ?";
789+
$untracked_backup_files = array();
790+
foreach ($db_list as $db) {
791+
$backups = $db->queryAllRecords($sql, $domain_id);
785792
foreach ($backups as $backup) {
786793
if (!in_array($backup['filename'],$files)) {
787-
$backup_file = $backup_dir . '/web' . $domain_id . '/' . $backup['filename'];
788-
$app->log('Backup file ' . $backup_file . ' is not contained in database, deleting this file from disk', LOGLEVEL_DEBUG);
789-
@unlink($backup_file);
794+
$untracked_backup_files[] = $backup['filename'];
790795
}
791796
}
792797
}
798+
array_unique( $untracked_backup_files );
799+
foreach ($untracked_backup_files as $f) {
800+
$backup_file = $backup_dir . '/web' . $domain_id . '/' . $f;
801+
$app->log('Backup file ' . $backup_file . ' is not contained in database, deleting this file from disk', LOGLEVEL_DEBUG);
802+
@unlink($backup_file);
803+
}
804+
}
805+
}
806+
807+
// This cleanup only runs on web servers
808+
$domains = $app->db->queryAllRecords($sql_domains, true, $args_sql_domains);
809+
foreach ($domains as $rec) {
810+
$domain_id = $rec['domain_id'];
811+
$domain_backup_dir = $backup_dir . '/web' . $domain_id;
793812

794-
//Remove backupdir symlink and create as directory instead
813+
// Remove backupdir symlink and create as directory instead
814+
if (is_link($backup_download_dir) || !is_dir($backup_download_dir)) {
795815
$web_path = $rec['document_root'];
796816
$app->system->web_folder_protection($web_path, false);
797817

@@ -806,23 +826,23 @@ protected static function backups_garbage_collection($server_id, $backup_type =
806826
}
807827

808828
$app->system->web_folder_protection($web_path, true);
829+
}
809830

810-
// delete old files from backup download dir (/var/www/example.com/backup)
811-
if (is_dir($backup_download_dir)) {
812-
$dir_handle = dir($backup_download_dir);
813-
$now = time();
814-
while (false !== ($entry = $dir_handle->read())) {
815-
$full_filename = $backup_download_dir . '/' . $entry;
816-
if ($entry != '.' && $entry != '..' && is_file($full_filename)) {
817-
// delete files older than 3 days
818-
if ($now - filemtime($full_filename) >= 60 * 60 * 24 * 3) {
819-
$app->log('Backup file ' . $full_filename . ' is too old, deleting this file from disk', LOGLEVEL_DEBUG);
820-
@unlink($full_filename);
821-
}
831+
// delete old files from backup download dir (/var/www/example.com/backup)
832+
if (is_dir($backup_download_dir)) {
833+
$dir_handle = dir($backup_download_dir);
834+
$now = time();
835+
while (false !== ($entry = $dir_handle->read())) {
836+
$full_filename = $backup_download_dir . '/' . $entry;
837+
if ($entry != '.' && $entry != '..' && is_file($full_filename) && ! is_link($full_filename)) {
838+
// delete files older than 3 days
839+
if ($now - filemtime($full_filename) >= 60 * 60 * 24 * 3) {
840+
$app->log('Backup file ' . $full_filename . ' is too old, deleting this file from disk', LOGLEVEL_DEBUG);
841+
@unlink($full_filename);
822842
}
823843
}
824-
$dir_handle->close();
825844
}
845+
$dir_handle->close();
826846
}
827847
}
828848
}
@@ -897,16 +917,24 @@ protected static function get_files($directory, $prefix_list = null, $endings_li
897917
* Generates excludes list for compressors
898918
* @param string[] $backup_excludes
899919
* @param string $arg
920+
* @param string $pre
921+
* @param string $post
900922
* @return string
901923
* @author Ramil Valitov <ramilvalitov@gmail.com>
902924
*/
903-
protected static function generateExcludeList($backup_excludes, $arg)
925+
protected static function generateExcludeList($backup_excludes, $arg, $pre='', $post='')
904926
{
905-
$excludes = implode(" " . $arg, $backup_excludes);
906-
if (!empty($excludes)) {
907-
$excludes = $arg . $excludes;
927+
$excludes = "";
928+
foreach ($backup_excludes as $ex) {
929+
# pass through escapeshellarg if not already done
930+
if ( preg_match( "/^'.+'$/", $ex ) ) {
931+
$excludes .= "${arg}${pre}${ex}${post} ";
932+
} else {
933+
$excludes .= "${arg}" . escapeshellarg("${pre}${ex}${post}") . " ";
934+
}
908935
}
909-
return $excludes;
936+
937+
return trim( $excludes );
910938
}
911939

912940
/**
@@ -965,12 +993,14 @@ protected static function runWebCompression($format, $backup_excludes, $backup_m
965993
if (!empty($password)) {
966994
$zip_options .= ' --password ' . escapeshellarg($password);
967995
}
996+
$excludes = self::generateExcludeList($backup_excludes, '-x ');
997+
$excludes .= " " . self::generateExcludeList($backup_excludes, '-x ', '', '/*');
968998
if ($backup_mode == 'user_zip') {
969999
//Standard casual behaviour of ISPConfig
970-
$app->system->exec_safe($find_user_files . ' | zip ' . $zip_options . ' -b ? ' . $excludes . ' --symlinks ? -@', $web_path, $web_user, $web_group, $http_server_user, $backup_tmp, $web_backup_dir . '/' . $web_backup_file);
1000+
$app->system->exec_safe($find_user_files . ' | zip ' . $zip_options . ' -b ? --symlinks ? -@ ' . $excludes, $web_path, $web_user, $web_group, $http_server_user, $backup_tmp, $web_backup_dir . '/' . $web_backup_file);
9711001
} else {
972-
//Use cd to have a correct directory structure inside the archive, extra options to zip hidden (dot) files
973-
$app->system->exec_safe('cd ? && zip ' . $zip_options . ' -b ? ' . $excludes . ' --symlinks -r ? * .* -x "../*"', $web_path, $backup_tmp, $web_backup_dir . '/' . $web_backup_file);
1002+
//Use cd to have a correct directory structure inside the archive, zip current directory "." to include hidden (dot) files
1003+
$app->system->exec_safe('cd ? && zip ' . $zip_options . ' -b ? --symlinks -r ? . ' . $excludes, $web_path, $backup_tmp, $web_backup_dir . '/' . $web_backup_file);
9741004
}
9751005
$exit_code = $app->system->last_exec_retcode();
9761006
// zip can return 12(due to harmless warnings) and still create valid backups
@@ -1215,8 +1245,8 @@ protected static function make_database_backup($web_domain, $backup_job)
12151245
//* Remove old backups
12161246
self::backups_garbage_collection($server_id, 'mysql', $domain_id);
12171247
$prefix_list = array(
1218-
'db_'.escapeshellarg($db_name).'_',
1219-
'manual-db_'.escapeshellarg($db_name).'_',
1248+
"db_${db_name}_",
1249+
"manual-db_${db_name}_",
12201250
);
12211251
self::clearBackups($server_id, $domain_id, intval($rec['backup_copies']), $db_backup_dir, $prefix_list);
12221252
} else {
@@ -1291,9 +1321,8 @@ protected static function make_web_backup($web_domain, $backup_job)
12911321

12921322
# default exclusions
12931323
$backup_excludes = array(
1294-
escapeshellarg('./backup\*'),
1324+
'./backup*',
12951325
'./bin', './dev', './etc', './lib', './lib32', './lib64', './opt', './sys', './usr', './var', './proc', './run', './tmp',
1296-
'./log',
12971326
);
12981327

12991328
$b_excludes = explode(',', trim($web_domain['backup_excludes']));
@@ -1419,7 +1448,7 @@ public static function run_backup($domain_id, $type, $backup_job, $mount = true)
14191448
$ok = self::make_web_backup($rec, $backup_job);
14201449
break;
14211450
case 'mysql':
1422-
$rec['server_id'] = $server_id;
1451+
$rec['server_id'] = $server_id;
14231452
$ok = self::make_database_backup($rec, $backup_job);
14241453
break;
14251454
default:
@@ -1460,7 +1489,7 @@ public static function run_all_backups($server_id, $backup_job = "auto")
14601489
}
14611490
}
14621491

1463-
$sql = "SELECT DISTINCT d.*, db.server_id as `server_id` FROM web_database as db INNER JOIN web_domain as d ON (d.domain_id = db.parent_domain_id) WHERE db.server_id = ? AND db.active = 'y' AND d.backup_interval != 'none' AND d.backup_interval != ''";
1492+
$sql = "SELECT DISTINCT d.*, db.server_id as `server_id` FROM web_database as db INNER JOIN web_domain as d ON (d.domain_id = db.parent_domain_id) WHERE db.server_id = ? AND db.active = 'y' AND d.backup_interval != 'none' AND d.backup_interval != ''";
14641493
$databases = $app->dbmaster->queryAllRecords($sql, $server_id);
14651494

14661495
foreach ($databases as $database) {

0 commit comments

Comments
 (0)