Marius Burkard
2016-04-20 4569cae57f127afd093794310ccd290d2d9fdf36
commit | author | age
5de2af 1 <?php
M 2
3 /*
4 Copyright (c) 2013, Marius Cramer, pixcept KG
5 All rights reserved.
6
7 Redistribution and use in source and binary forms, with or without modification,
8 are permitted provided that the following conditions are met:
9
10     * Redistributions of source code must retain the above copyright notice,
11       this list of conditions and the following disclaimer.
12     * Redistributions in binary form must reproduce the above copyright notice,
13       this list of conditions and the following disclaimer in the documentation
14       and/or other materials provided with the distribution.
15     * Neither the name of ISPConfig nor the names of its contributors
16       may be used to endorse or promote products derived from this software without
17       specific prior written permission.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22 IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
23 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
26 OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28 EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 class cronjob_backup extends cronjob {
32
b1a6a5 33     // job schedule
MC 34     protected $_schedule = '0 0 * * *';
5de2af 35
b1a6a5 36     /* this function is optional if it contains no custom code */
MC 37     public function onPrepare() {
38         global $app;
5de2af 39
b1a6a5 40         parent::onPrepare();
MC 41     }
5de2af 42
b1a6a5 43     /* this function is optional if it contains no custom code */
MC 44     public function onBeforeRun() {
45         global $app;
5de2af 46
b1a6a5 47         return parent::onBeforeRun();
MC 48     }
5de2af 49
b1a6a5 50     public function onRunJob() {
MC 51         global $app, $conf;
5de2af 52
b1a6a5 53         $server_config = $app->getconf->get_server_config($conf['server_id'], 'server');
6b166b 54         $global_config = $app->getconf->get_global_config('sites');
b48a0b 55         $backup_dir = trim($server_config['backup_dir']);
b1a6a5 56         $backup_mode = $server_config['backup_mode'];
MC 57         if($backup_mode == '') $backup_mode = 'userzip';
58
59         $web_config = $app->getconf->get_server_config($conf['server_id'], 'web');
60         $http_server_user = $web_config['user'];
61
62         if($backup_dir != '') {
63
64             if(isset($server_config['backup_dir_ftpread']) && $server_config['backup_dir_ftpread'] == 'y') {
65                 $backup_dir_permissions = 0755;
66             } else {
67                 $backup_dir_permissions = 0750;
68             }
69
70             if(!is_dir($backup_dir)) {
71                 mkdir(escapeshellcmd($backup_dir), $backup_dir_permissions, true);
72             } else {
73                 chmod(escapeshellcmd($backup_dir), $backup_dir_permissions);
74             }
990ca8 75             $run_backups = true;
FS 76             //* mount backup directory, if necessary
77             if( $server_config['backup_dir_is_mount'] == 'y' && !$app->system->mount_backup_dir($backup_dir) ) $run_backups = false;
746e52 78             if($run_backups){
2c408f 79                 $web_array = array();
MC 80                 
9f6008 81                 //* backup only active domains
026957 82                 $sql = "SELECT * FROM web_domain WHERE server_id = ? AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias') AND active = 'y'";
FS 83                 $records = $app->db->queryAllRecords($sql, $conf['server_id']);
746e52 84                 if(is_array($records)) {
MC 85                     foreach($records as $rec) {
5de2af 86
746e52 87                         //* Do the website backup
MC 88                         if($rec['backup_interval'] == 'daily' or ($rec['backup_interval'] == 'weekly' && date('w') == 0) or ($rec['backup_interval'] == 'monthly' && date('d') == '01')) {
5de2af 89
746e52 90                             $web_path = $rec['document_root'];
MC 91                             $web_user = $rec['system_user'];
92                             $web_group = $rec['system_group'];
93                             $web_id = $rec['domain_id'];
2c408f 94                             if(!in_array($web_id, $web_array)) $web_array[] = $web_id;
746e52 95                             $web_backup_dir = $backup_dir.'/web'.$web_id;
MC 96                             if(!is_dir($web_backup_dir)) mkdir($web_backup_dir, 0750);
97                             chmod($web_backup_dir, 0750);
98                             //if(isset($server_config['backup_dir_ftpread']) && $server_config['backup_dir_ftpread'] == 'y') {
99                             chown($web_backup_dir, $rec['system_user']);
100                             chgrp($web_backup_dir, $rec['system_group']);
101                             /*} else {
102                                 chown($web_backup_dir, 'root');
103                                 chgrp($web_backup_dir, 'root');
104                             }*/
105                         
106                             $backup_excludes = '';
107                             $b_excludes = explode(',', trim($rec['backup_excludes']));
108                             if(is_array($b_excludes) && !empty($b_excludes)){
109                                 foreach($b_excludes as $b_exclude){
110                                     $b_exclude = trim($b_exclude);
111                                     if($b_exclude != ''){
112                                         $backup_excludes .= ' --exclude='.escapeshellarg($b_exclude);
113                                     }
114                                 }
115                             }
116                         
117                             if($backup_mode == 'userzip') {
118                                 //* Create a .zip backup as web user and include also files owned by apache / nginx user
119                                 $web_backup_file = 'web'.$web_id.'_'.date('Y-m-d_H-i').'.zip';
6495dd 120                                 exec('cd '.escapeshellarg($web_path).' && sudo -u '.escapeshellarg($web_user).' find . -group '.escapeshellarg($web_group).' -print 2> /dev/null | zip -b /tmp --exclude=./backup\*'.$backup_excludes.' --symlinks '.escapeshellarg($web_backup_dir.'/'.$web_backup_file).' -@', $tmp_output, $retval);
MC 121                                 if($retval == 0 || $retval == 12) exec('cd '.escapeshellarg($web_path).' && sudo -u '.escapeshellarg($web_user).' find . -user '.escapeshellarg($http_server_user).' -print 2> /dev/null | zip -b /tmp --exclude=./backup\*'.$backup_excludes.' --update --symlinks '.escapeshellarg($web_backup_dir.'/'.$web_backup_file).' -@', $tmp_output, $retval);
746e52 122                             } else {
MC 123                                 //* Create a tar.gz backup as root user
124                                 $web_backup_file = 'web'.$web_id.'_'.date('Y-m-d_H-i').'.tar.gz';
6495dd 125                                 exec('tar pczf '.escapeshellarg($web_backup_dir.'/'.$web_backup_file).' --exclude=./backup\*'.$backup_excludes.' --directory '.escapeshellarg($web_path).' .', $tmp_output, $retval);
746e52 126                             }
MC 127                             if($retval == 0 || ($backup_mode != 'userzip' && $retval == 1) || ($backup_mode == 'userzip' && $retval == 12)) { // tar can return 1, zip can return 12(due to harmless warings) and still create valid backups  
128                                 if(is_file($web_backup_dir.'/'.$web_backup_file)){
6b166b 129                                     $backupusername = ($global_config['backups_include_into_web_quota'] == 'y') ? $web_user : 'root';
DM 130                                     $backupgroup = ($global_config['backups_include_into_web_quota'] == 'y') ? $web_group : 'root';
131                                     chown($web_backup_dir.'/'.$web_backup_file, $backupusername);
132                                     chgrp($web_backup_dir.'/'.$web_backup_file, $backupgroup);
746e52 133                                     chmod($web_backup_dir.'/'.$web_backup_file, 0750);
5de2af 134
746e52 135                                     //* Insert web backup record in database
6b166b 136                                     $filesize = filesize($web_backup_dir.'/'.$web_backup_file);
0b22b5 137                                     $sql = "INSERT INTO web_backup (server_id, parent_domain_id, backup_type, backup_mode, tstamp, filename, filesize) VALUES (?, ?, ?, ?, ?, ?, ?)";
FS 138                                     $app->db->query($sql, $conf['server_id'], $web_id, 'web', $backup_mode, time(), $web_backup_file, $filesize);
139                                     if($app->db->dbHost != $app->dbmaster->dbHost) 
140                                         $app->dbmaster->query($sql, $conf['server_id'], $web_id, 'web', $backup_mode, time(), $web_backup_file, $filesize);
141                                     unset($filesize);
746e52 142                                 }
MC 143                             } else {
144                                 if(is_file($web_backup_dir.'/'.$web_backup_file)) unlink($web_backup_dir.'/'.$web_backup_file);
145                             }
5de2af 146
746e52 147                             //* Remove old backups
MC 148                             $backup_copies = intval($rec['backup_copies']);
5de2af 149
746e52 150                             $dir_handle = dir($web_backup_dir);
MC 151                             $files = array();
152                             while (false !== ($entry = $dir_handle->read())) {
153                                 if($entry != '.' && $entry != '..' && substr($entry, 0, 3) == 'web' && is_file($web_backup_dir.'/'.$entry)) {
154                                     $files[] = $entry;
155                                 }
156                             }
157                             $dir_handle->close();
5de2af 158
746e52 159                             rsort($files);
MC 160
161                             for ($n = $backup_copies; $n <= 10; $n++) {
162                                 if(isset($files[$n]) && is_file($web_backup_dir.'/'.$files[$n])) {
026957 163                                     $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
FS 164                                     $app->db->query($sql, $conf['server_id'], $web_id, $files[$n]);
165                                     if($app->db->dbHost != $app->dbmaster->dbHost) $app->dbmaster->query($sql, $conf['server_id'],  $web_id, $files[$n]);
2c408f 166                                     @unlink($web_backup_dir.'/'.$files[$n]);
746e52 167                                 }
MC 168                             }
169
170                             unset($files);
171                             unset($dir_handle);
172
173                             //* Remove backupdir symlink and create as directory instead
174                             $app->system->web_folder_protection($web_path, false);
175
176                             if(is_link($web_path.'/backup')) {
177                                 unlink($web_path.'/backup');
178                             }
179                             if(!is_dir($web_path.'/backup')) {
180                                 mkdir($web_path.'/backup');
181                                 chown($web_path.'/backup', $rec['system_user']);
182                                 chgrp($web_path.'/backup', $rec['system_group']);
183                             }
184
185                             $app->system->web_folder_protection($web_path, true);
186                         }
187
188                         /* If backup_interval is set to none and we have a
189                         backup directory for the website, then remove the backups */
190                         if($rec['backup_interval'] == 'none' || $rec['backup_interval'] == '') {
191                             $web_id = $rec['domain_id'];
192                             $web_user = $rec['system_user'];
193                             $web_backup_dir = realpath($backup_dir.'/web'.$web_id);
194                             if(is_dir($web_backup_dir)) {
66fa9b 195                                 $dir_handle = opendir($web_backup_dir.'/');
FS 196                                 while ($file = readdir($dir_handle)) {
197                                     if(!is_dir($file)) {
198                                         unlink ("$web_backup_dir/"."$file");
199                                     }
200                                 }
746e52 201                             }
66fa9b 202                             $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ?";
FS 203                             $app->db->query($sql, $conf['server_id'], $web_id);
204                             if($app->db->dbHost != $app->dbmaster->dbHost) $app->dbmaster->query($sql, $conf['server_id'], $web_id);
746e52 205                         }
MC 206                     }
207                 }
208
026957 209                 $records = $app->db->queryAllRecords("SELECT * FROM web_database WHERE server_id = ? AND backup_interval != 'none' AND backup_interval != ''", $conf['server_id']);
746e52 210                 if(is_array($records)) {
MC 211
212                     include 'lib/mysql_clientdb.conf';
213
214                     foreach($records as $rec) {
215
216                         //* Do the database backup
217                         if($rec['backup_interval'] == 'daily' or ($rec['backup_interval'] == 'weekly' && date('w') == 0) or ($rec['backup_interval'] == 'monthly' && date('d') == '01')) {
218
219                             $web_id = $rec['parent_domain_id'];
2c408f 220                             if(!in_array($web_id, $web_array)) $web_array[] = $web_id;
746e52 221                             $db_backup_dir = $backup_dir.'/web'.$web_id;
MC 222                             if(!is_dir($db_backup_dir)) mkdir($db_backup_dir, 0750);
223                             chmod($db_backup_dir, 0750);
6b166b 224                             $backupusername = 'root';
DM 225                             $backupgroup = 'root';
226                             if ($global_config['backups_include_into_web_quota'] == 'y') {
227                                 $sql = "SELECT * FROM web_domain WHERE domain_id = ".$rec['parent_domain_id'];
228                                 $webdomain = $app->db->queryOneRecord($sql);
229                                 $backupusername = $webdomain['system_user'];
230                                 $backupgroup = $webdomain['system_group'];
231                             }
232                             chown($db_backup_dir, $backupusername);
233                             chgrp($db_backup_dir, $backupgroup);
746e52 234
MC 235                             //* Do the mysql database backup with mysqldump
236                             $db_id = $rec['database_id'];
237                             $db_name = $rec['database_name'];
b1a6a5 238                             $db_backup_file = 'db_'.$db_name.'_'.date('Y-m-d_H-i').'.sql';
MC 239                             //$command = "mysqldump -h '".escapeshellcmd($clientdb_host)."' -u '".escapeshellcmd($clientdb_user)."' -p'".escapeshellcmd($clientdb_password)."' -c --add-drop-table --create-options --quick --result-file='".$db_backup_dir.'/'.$db_backup_file."' '".$db_name."'";
7ee336 240                             $command = "mysqldump -h ".escapeshellarg($clientdb_host)." -u ".escapeshellarg($clientdb_user)." -p".escapeshellarg($clientdb_password)." -c --add-drop-table --create-options --quick --max_allowed_packet=512M --result-file='".$db_backup_dir.'/'.$db_backup_file."' '".$db_name."'";
b1a6a5 241                             exec($command, $tmp_output, $retval);
5de2af 242
b1a6a5 243                             //* Compress the backup with gzip
MC 244                             if($retval == 0) exec("gzip -c '".escapeshellcmd($db_backup_dir.'/'.$db_backup_file)."' > '".escapeshellcmd($db_backup_dir.'/'.$db_backup_file).".gz'", $tmp_output, $retval);
5de2af 245
b1a6a5 246                             if($retval == 0){
746e52 247                                 if(is_file($db_backup_dir.'/'.$db_backup_file.'.gz')){
MC 248                                     chmod($db_backup_dir.'/'.$db_backup_file.'.gz', 0750);
249                                     chown($db_backup_dir.'/'.$db_backup_file.'.gz', fileowner($db_backup_dir));
250                                     chgrp($db_backup_dir.'/'.$db_backup_file.'.gz', filegroup($db_backup_dir));
5de2af 251
746e52 252                                     //* Insert web backup record in database
6b166b 253                                     $filesize = filesize($db_backup_dir.'/'.$db_backup_file.'.gz');
0b22b5 254                                     $sql = "INSERT INTO web_backup (server_id, parent_domain_id, backup_type, backup_mode, tstamp, filename, filesize) VALUES (?, ?, ?, ?, ?, ?, ?)";
FS 255                                     $app->db->query($sql, $conf['server_id'], $web_id, 'mysql', 'sqlgz', time(), $db_backup_file.'.gz', $filesize);
256                                     if($app->db->dbHost != $app->dbmaster->dbHost) 
257                                         $app->dbmaster->query($sql, $conf['server_id'], $web_id, 'mysql', 'sqlgz', time(), $db_backup_file.'.gz', $filesize);
258                                     unset($filesize);
746e52 259                                 }
b1a6a5 260                             } else {
MC 261                                 if(is_file($db_backup_dir.'/'.$db_backup_file.'.gz')) unlink($db_backup_dir.'/'.$db_backup_file.'.gz');
262                             }
263                             //* Remove the uncompressed file
264                             if(is_file($db_backup_dir.'/'.$db_backup_file)) unlink($db_backup_dir.'/'.$db_backup_file);
5de2af 265
746e52 266                             //* Remove old backups
MC 267                             $backup_copies = intval($rec['backup_copies']);
5de2af 268
746e52 269                             $dir_handle = dir($db_backup_dir);
MC 270                             $files = array();
271                             while (false !== ($entry = $dir_handle->read())) {
2c408f 272                                 if($entry != '.' && $entry != '..' && preg_match('/^db_('.$db_name.')_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}\.sql.gz$/', $entry, $matches) && is_file($db_backup_dir.'/'.$entry)) {
746e52 273                                     if(array_key_exists($matches[1], $files) == false) $files[$matches[1]] = array();
MC 274                                     $files[$matches[1]][] = $entry;
275                                 }
276                             }
277                             $dir_handle->close();
5de2af 278
746e52 279                             reset($files);
MC 280                             foreach($files as $db_name => $filelist) {
281                                 rsort($filelist);
282                                 for ($n = $backup_copies; $n <= 10; $n++) {
283                                     if(isset($filelist[$n]) && is_file($db_backup_dir.'/'.$filelist[$n])) {
026957 284                                         $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
FS 285                                         $app->db->query($sql, $conf['server_id'], $web_id, $filelist[$n]);
286                                         if($app->db->dbHost != $app->dbmaster->dbHost) $app->dbmaster->query($sql, $conf['server_id'], $web_id, $filelist[$n]);
2c408f 287                                         @unlink($db_backup_dir.'/'.$filelist[$n]);
b1a6a5 288                                     }
MC 289                                 }
290                             }
5de2af 291
746e52 292                             unset($files);
MC 293                             unset($dir_handle);
b1a6a5 294                         }
MC 295                     }
746e52 296
MC 297                     unset($clientdb_host);
298                     unset($clientdb_user);
299                     unset($clientdb_password);
300
b1a6a5 301                 }
5de2af 302
746e52 303                 // remove non-existing backups from database
026957 304                 $backups = $app->db->queryAllRecords("SELECT * FROM web_backup WHERE server_id = ?", $conf['server_id']);
746e52 305                 if(is_array($backups) && !empty($backups)){
MC 306                     foreach($backups as $backup){
307                         $backup_file = $backup_dir.'/web'.$backup['parent_domain_id'].'/'.$backup['filename'];
308                         if(!is_file($backup_file)){
026957 309                             $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
FS 310                             $app->db->query($sql, $conf['server_id'], $backup['parent_domain_id'], $backup['filename']);
2c408f 311                         }
MC 312                     }
313                 }
314                 if($app->db->dbHost != $app->dbmaster->dbHost){
315                     $backups = $app->dbmaster->queryAllRecords("SELECT * FROM web_backup WHERE server_id = ?", $conf['server_id']);
316                     if(is_array($backups) && !empty($backups)){
317                         foreach($backups as $backup){
318                             $backup_file = $backup_dir.'/web'.$backup['parent_domain_id'].'/'.$backup['filename'];
319                             if(!is_file($backup_file)){
320                                 $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
321                                 $app->dbmaster->query($sql, $conf['server_id'], $backup['parent_domain_id'], $backup['filename']);
322                             }
323                         }
324                     }
325                 }
326                 
327                 // garbage collection (non-existing databases)
328                 if(is_array($web_array) && !empty($web_array)){
329                     foreach($web_array as $tmp_web_id){
330                         $tmp_backup_dir = $backup_dir.'/web'.$tmp_web_id;
331                         if(is_dir($tmp_backup_dir)){
332                             $dir_handle = dir($tmp_backup_dir);
333                             $files = array();
334                             while (false !== ($entry = $dir_handle->read())) {
335                                 if($entry != '.' && $entry != '..' && preg_match('/^db_(.*?)_\d{4}-\d{2}-\d{2}_\d{2}-\d{2}\.sql.gz$/', $entry, $matches) && is_file($tmp_backup_dir.'/'.$entry)) {
336
337                                     $tmp_db_name = $matches[1];
338                                     $tmp_database = $app->db->queryOneRecord("SELECT * FROM web_database WHERE server_id = ? AND parent_domain_id = ? AND database_name = ?", $conf['server_id'], $tmp_web_id, $tmp_db_name);
339
340                                     if(is_array($tmp_database) && !empty($tmp_database)){
341                                         if($tmp_database['backup_interval'] == 'none' || intval($tmp_database['backup_copies']) == 0){
342                                             @unlink($tmp_backup_dir.'/'.$entry);
343                                             $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
344                                             $app->db->query($sql, $conf['server_id'], $tmp_web_id, $entry);
345                                             if($app->db->dbHost != $app->dbmaster->dbHost) $app->dbmaster->query($sql, $conf['server_id'], $tmp_web_id, $entry);
346                                         }
347                                     } else {
348                                         @unlink($tmp_backup_dir.'/'.$entry);
349                                         $sql = "DELETE FROM web_backup WHERE server_id = ? AND parent_domain_id = ? AND filename = ?";
350                                         $app->db->query($sql, $conf['server_id'], $tmp_web_id, $entry);
351                                         if($app->db->dbHost != $app->dbmaster->dbHost) $app->dbmaster->query($sql, $conf['server_id'], $tmp_web_id, $entry);
352                                     }
353                                 }
354                             }
355                             $dir_handle->close();
746e52 356                         }
MC 357                     }
358                 }
52fe9e 359                 //* end run_backups
FS 360                 if( $server_config['backup_dir_is_mount'] == 'y' ) $app->system->umount_backup_dir($backup_dir);
66fa9b 361             } 
b1a6a5 362         }
d19abd 363         
MC 364         // delete files from backup download dir (/var/www/example.com/backup)
365         unset($records, $entry, $files);
366         $sql = "SELECT * FROM web_domain WHERE server_id = ? AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias') AND active = 'y'";
367         $records = $app->db->queryAllRecords($sql, $conf['server_id']);
368         if(is_array($records)) {
369             foreach($records as $rec) {
370                 $backup_download_dir = $rec['document_root'].'/backup';
371                 if(is_dir($backup_download_dir)){
372                     $dir_handle = dir($backup_download_dir);
373                     $files = array();
374                     while (false !== ($entry = $dir_handle->read())) {
375                         if($entry != '.' && $entry != '..' && is_file($backup_download_dir.'/'.$entry)) {
376                             // delete files older than 3 days
377                             if(time() - filemtime($backup_download_dir.'/'.$entry) >= 60*60*24*3) @unlink($backup_download_dir.'/'.$entry);
378                         }
379                     }
380                     $dir_handle->close();
381                 }
382             }
383         }
b1a6a5 384
MC 385         parent::onRunJob();
386     }
387
388     /* this function is optional if it contains no custom code */
389     public function onAfterRun() {
390         global $app;
391
392         parent::onAfterRun();
393     }
5de2af 394
M 395 }
396
397 ?>