Skip to content
backup.inc.php 63.9 KiB
Newer Older
                }
                $excludes = self::generateExcludeList($backup_excludes, '-x');
                $zip_command = 'rar a ' . $options . ' '.$excludes.' ?';
                if ($backup_mode == 'user_zip') {
                    $app->system->exec_safe($find_user_files . ' | ' . $zip_command . ' ? @', $web_path, $web_user, $web_group, $http_server_user, $web_path, $web_backup_dir . '/' . $web_backup_file);
                } else {
                    $app->system->exec_safe('cd ? && ' . $zip_command . ' .', $web_path, $web_backup_dir . '/' . $web_backup_file);
                }
                $exit_code = $app->system->last_exec_retcode();
                return ($exit_code == 0 || $exit_code == 1);
        }
        if (strpos($format, "tar_7z_") === 0) {
            $options = self::get7zCompressOptions($format, $password);
            $zip_command = '7z a ' . $options . ' -si ?';
            if ($backup_mode == 'user_zip') {
                $app->system->exec_safe($find_user_files . ' | ' . $tar_input . ' | '. $zip_command, $web_path, $web_user, $web_group, $http_server_user, $web_path, $web_backup_dir . '/' . $web_backup_file);
            } else {
                $app->system->exec_safe($tar_dir . ' | ' . $zip_command, $web_path, $web_backup_dir . '/' . $web_backup_file);
            }
            $exit_code = $app->system->last_exec_retcode();
            return $exit_code == 0;
        }
        return false;
    }

    /**
     * Runs a database compression routine
     * @param string $format
     * @param string $db_backup_dir
     * @param string $db_backup_file
     * @param string $compressed_backup_file
     * @param string $backup_tmp
     * @param string|null $password
     * @return bool true if success
     * @author Ramil Valitov <ramilvalitov@gmail.com>
     */
    protected static function runDatabaseCompression($format, $db_backup_dir, $db_backup_file, $compressed_backup_file, $backup_tmp, $password)
    {
        global $app;

        $app->log('Performing database backup to file ' . $compressed_backup_file . ' in format ' . $format, LOGLEVEL_DEBUG);
        switch ($format) {
            case 'gzip':
                if ($app->system->is_installed('pigz')) {
                    //use pigz
                    $zip_cmd = 'pigz';
                } else {
                    //use gzip
                    $zip_cmd = 'gzip';
                }
                $app->system->exec_safe($zip_cmd . " -c ? > ?", $db_backup_dir . '/' . $db_backup_file, $db_backup_dir . '/' . $compressed_backup_file);
                $exit_code = $app->system->last_exec_retcode();
                return $exit_code == 0;
            case 'zip':
            case 'zip_bzip2':
                $zip_options = ($format === 'zip_bzip2') ? ' -Z bzip2 ' : '';
                if (!empty($password)) {
                    $zip_options .= ' --password ' . escapeshellarg($password);
                }
                $app->system->exec_safe('zip ' . $zip_options . ' -j -b ? ? ?', $backup_tmp, $db_backup_dir . '/' . $compressed_backup_file, $db_backup_dir . '/' . $db_backup_file);
                $exit_code = $app->system->last_exec_retcode();
                // zip can return 12(due to harmless warnings) and still create valid backups
                return ($exit_code == 0 || $exit_code == 12);
            case 'bzip2':
                $app->system->exec_safe("bzip2 -q -c ? > ?", $db_backup_dir . '/' . $db_backup_file, $db_backup_dir . '/' . $compressed_backup_file);
                $exit_code = $app->system->last_exec_retcode();
                return $exit_code == 0;
            case 'xz':
                $app->system->exec_safe("xz -q -q -c ? > ?", $db_backup_dir . '/' . $db_backup_file, $db_backup_dir . '/' . $compressed_backup_file);
                $exit_code = $app->system->last_exec_retcode();
                return $exit_code == 0;
            case 'rar':
                $options = self::getRarOptions($backup_tmp, $password);
                $zip_command = 'rar a ' . $options . ' ? ?';
                $app->system->exec_safe($zip_command, $db_backup_dir . '/' . $compressed_backup_file, $db_backup_dir . '/' . $db_backup_file);
                $exit_code = $app->system->last_exec_retcode();
                return ($exit_code == 0 || $exit_code == 1);
        }
        if (strpos($format, "7z_") === 0) {
            $options = self::get7zCompressOptions($format, $password);
            $zip_command = '7z a ' . $options . ' ? ?';
            $app->system->exec_safe($zip_command, $db_backup_dir . '/' . $compressed_backup_file, $db_backup_dir . '/' . $db_backup_file);
            $exit_code = $app->system->last_exec_retcode();
            return $exit_code == 0;
        }
        return false;
    }

    /**
     * Mounts the backup directory if required
     * @param int $server_id
     * @return bool true if success
     * @author Ramil Valitov <ramilvalitov@gmail.com>
     * @see backup_plugin::unmount_backup_dir()
     */
    public static function mount_backup_dir($server_id)
    {
        global $app;

        $server_config = $app->getconf->get_server_config($server_id, 'server');
        if ($server_config['backup_dir_is_mount'] == 'y')
            return $app->system->mount_backup_dir($server_config['backup_dir']);
        return true;
    }

    /**
     * Unmounts the backup directory if required
     * @param int $server_id
     * @return bool true if success
     * @author Ramil Valitov <ramilvalitov@gmail.com>
     * @see backup_plugin::mount_backup_dir()
     */
    public static function unmount_backup_dir($server_id)
    {
        global $app;

        $server_config = $app->getconf->get_server_config($server_id, 'server');
        if ($server_config['backup_dir_is_mount'] == 'y')
            return $app->system->umount_backup_dir($server_config['backup_dir']);
        return true;
    }

    /**
     * Makes backup of database.
     * The backup directory must be mounted before calling this method.
     * This method is for private use only, don't call this method unless you know what you're doing.
     * @param array $web_domain
     * @param string $backup_job type of backup job: manual or auto
     * @return bool true if success
     * @author Ramil Valitov <ramilvalitov@gmail.com>
     * @see backup_plugin::run_backup() recommeneded to use if you need to make backups
     */
    protected static function make_database_backup($web_domain, $backup_job)
    {
        global $app;

        $server_id = intval($web_domain['server_id']);
        $domain_id = intval($web_domain['domain_id']);
        $server_config = $app->getconf->get_server_config($server_id, 'server');
        $backup_dir = trim($server_config['backup_dir']);
        $backup_tmp = trim($server_config['backup_tmp']);
        $db_backup_dir = $backup_dir . '/web' . $domain_id;
        $success = false;

        if (empty($backup_job))
            $backup_job = "auto";

        $records = $app->db->queryAllRecords("SELECT * FROM web_database WHERE server_id = ? AND parent_domain_id = ?", $server_id, $domain_id);
        if (empty($records)){
            $app->log('Skipping database backup for domain ' . $web_domain['domain_id'] . ', because no related databases found.', LOGLEVEL_DEBUG);
            return true;
        }

        self::prepare_backup_dir($server_id, $web_domain);

        include '/usr/local/ispconfig/server/lib/mysql_clientdb.conf';

        //* Check mysqldump capabilities
        exec('mysqldump --help', $tmp);
        $mysqldump_routines = (strpos(implode($tmp), '--routines') !== false) ? '--routines' : '';
        unset($tmp);

        foreach ($records as $rec) {
            $password = ($web_domain['backup_encrypt'] == 'y') ? trim($web_domain['backup_password']) : '';
            $backup_format_db = $web_domain['backup_format_db'];
            if (empty($backup_format_db)) {
                $backup_format_db = 'gzip';
            }
            $backup_extension_db = self::getBackupDbExtension($backup_format_db);

            if (!empty($backup_extension_db)) {
                //* Do the mysql database backup with mysqldump
                $db_name = $rec['database_name'];
                $db_file_prefix = 'db_' . $db_name . '_' . date('Y-m-d_H-i');
                $db_backup_file = $db_file_prefix . '.sql';
                $db_compressed_file = ($backup_job == 'manual' ? 'manual-' : '') . $db_file_prefix . $backup_extension_db;
                $command = "mysqldump -h ? -u ? -p? -c --add-drop-table --create-options --quick --max_allowed_packet=512M " . $mysqldump_routines . " --result-file=? ?";
                /** @var string $clientdb_host */
                /** @var string $clientdb_user */
                /** @var string $clientdb_password */
                $app->system->exec_safe($command, $clientdb_host, $clientdb_user, $clientdb_password, $db_backup_dir . '/' . $db_backup_file, $db_name);
                $exit_code = $app->system->last_exec_retcode();

                //* Compress the backup
                if ($exit_code == 0) {
                    $exit_code = self::runDatabaseCompression($backup_format_db, $db_backup_dir, $db_backup_file, $db_compressed_file, $backup_tmp, $password) ? 0 : 1;
                    if ($exit_code !== 0)
                        $app->log('Failed to make backup of database ' . $rec['database_name'], LOGLEVEL_ERROR);
                } else {
                    $app->log('Failed to make backup of database ' . $rec['database_name'] . ', because mysqldump failed', LOGLEVEL_ERROR);
                }

                if ($exit_code == 0) {
                    if (is_file($db_backup_dir . '/' . $db_compressed_file)) {
                        chmod($db_backup_dir . '/' . $db_compressed_file, 0750);
                        chown($db_backup_dir . '/' . $db_compressed_file, fileowner($db_backup_dir));
                        chgrp($db_backup_dir . '/' . $db_compressed_file, filegroup($db_backup_dir));

                        //* Insert web backup record in database
                        $file_size = filesize($db_backup_dir . '/' . $db_compressed_file);
                        $sql = "INSERT INTO web_backup (server_id, parent_domain_id, backup_type, backup_mode, backup_format, tstamp, filename, filesize, backup_password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
                        //Making compatible with previous versions of ISPConfig:
                        $sql_mode = ($backup_format_db == 'gzip') ? 'sqlgz' : ('sql' . $backup_format_db);
                        $app->db->query($sql, $server_id, $domain_id, 'mysql', $sql_mode, $backup_format_db, time(), $db_compressed_file, $file_size, $password);
                        if ($app->db->dbHost != $app->dbmaster->dbHost)
                            $app->dbmaster->query($sql, $server_id, $domain_id, 'mysql', $sql_mode, $backup_format_db, time(), $db_compressed_file, $file_size, $password);
                        $success = true;
                    }
                } else {
                    if (is_file($db_backup_dir . '/' . $db_compressed_file)) unlink($db_backup_dir . '/' . $db_compressed_file);
                }
                //* Remove the uncompressed file
                if (is_file($db_backup_dir . '/' . $db_backup_file)) unlink($db_backup_dir . '/' . $db_backup_file);

                //* Remove old backups
                self::backups_garbage_collection($server_id, 'mysql', $domain_id);
                $prefix_list = array(
                            'db_'.escapeshellarg($db_name).'_',
                            'manual-db_'.escapeshellarg($db_name).'_',
                        );
                self::clearBackups($server_id, $domain_id, intval($rec['backup_copies']), $db_backup_dir, $prefix_list);
            } else {
                $app->log('Failed to process mysql backup format ' . $backup_format_db . ' for database ' . $rec['database_name'], LOGLEVEL_ERROR);
            }
        }

        unset($clientdb_host);
        unset($clientdb_user);
        unset($clientdb_password);

        return $success;
    }

    /**
     * Makes backup of web files.
     * The backup directory must be mounted before calling this method.
     * This method is for private use only, don't call this method unless you know what you're doing
     * @param array $web_domain info about domain to backup, SQL record of table 'web_domain'
     * @param string $backup_job type of backup job: manual or auto
     * @return bool true if success
     * @author Ramil Valitov <ramilvalitov@gmail.com>
     * @see backup_plugin::mount_backup_dir()
     * @see backup_plugin::run_backup() recommeneded to use if you need to make backups
     */
    protected static function make_web_backup($web_domain, $backup_job)
    {
        global $app;

        $server_id = intval($web_domain['server_id']);
        $domain_id = intval($web_domain['domain_id']);
        $server_config = $app->getconf->get_server_config($server_id, 'server');
        $global_config = $app->getconf->get_global_config('sites');
        $backup_dir = trim($server_config['backup_dir']);
        $backup_mode = $server_config['backup_mode'];
        $backup_tmp = trim($server_config['backup_tmp']);
        if (empty($backup_mode))
            $backup_mode = 'userzip';

        $web_config = $app->getconf->get_server_config($server_id, 'web');
        $http_server_user = $web_config['user'];

        if (empty($backup_dir)) {
            $app->log('Failed to make backup of web files for domain id ' . $domain_id . ' on server id ' . $server_id . ', because backup directory is not defined', LOGLEVEL_ERROR);
            return false;
        }
        if (empty($backup_job))
            $backup_job = "auto";

        $backup_format_web = $web_domain['backup_format_web'];
        //Check if we're working with data saved in old version of ISPConfig
        if (empty($backup_format_web)) {
            $backup_format_web = 'default';
        }
        if ($backup_format_web == 'default') {
            $backup_format_web = self::getDefaultBackupFormat($backup_mode, 'web');
        }
        $password = ($web_domain['backup_encrypt'] == 'y') ? trim($web_domain['backup_password']) : '';
        $backup_extension_web = self::getBackupWebExtension($backup_format_web);
        if (empty($backup_extension_web)) {
            $app->log('Failed to make backup of web files, because of unknown backup format ' . $backup_format_web . ' for website ' . $web_domain['domain'], LOGLEVEL_ERROR);
            return false;
        }

        $web_path = $web_domain['document_root'];
        $web_user = $web_domain['system_user'];
        $web_group = $web_domain['system_group'];
        $web_id = $web_domain['domain_id'];

        self::prepare_backup_dir($server_id, $web_domain);
        $web_backup_dir = $backup_dir . '/web' . $web_id;

	# default exclusions
	$backup_excludes = array(
		escapeshellarg('./backup\*'),
		'./bin', './dev', './etc', './lib', './lib32', './lib64', './opt', './sys', './usr', './var', './proc', './run', './tmp',
		'./log',
		);

        $b_excludes = explode(',', trim($web_domain['backup_excludes']));
        if (is_array($b_excludes) && !empty($b_excludes)) {
            foreach ($b_excludes as $b_exclude) {
                $b_exclude = trim($b_exclude);
                if ($b_exclude != '') {
                    array_push($backup_excludes, escapeshellarg($b_exclude));
                }
            }
        }

        $web_backup_file = ($backup_job == 'manual' ? 'manual-' : '') . 'web' . $web_id . '_' . date('Y-m-d_H-i') . $backup_extension_web;
        $full_filename = $web_backup_dir . '/' . $web_backup_file;
        if (self::runWebCompression($backup_format_web, $backup_excludes, $backup_mode, $web_path, $web_backup_dir, $web_backup_file, $web_user, $web_group, $http_server_user, $backup_tmp, $password)) {
            if (is_file($full_filename)) {
                $backup_username = ($global_config['backups_include_into_web_quota'] == 'y') ? $web_user : 'root';
                $backup_group = ($global_config['backups_include_into_web_quota'] == 'y') ? $web_group : 'root';
                chown($full_filename, $backup_username);
                chgrp($full_filename, $backup_group);
                chmod($full_filename, 0750);

                //Insert web backup record in database
                $file_size = filesize($full_filename);
                $sql = "INSERT INTO web_backup (server_id, parent_domain_id, backup_type, backup_mode, backup_format, tstamp, filename, filesize, backup_password) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)";
                $app->db->query($sql, $server_id, $web_id, 'web', $backup_mode, $backup_format_web, time(), $web_backup_file, $file_size, $password);
                if ($app->db->dbHost != $app->dbmaster->dbHost)
                    $app->dbmaster->query($sql, $server_id, $web_id, 'web', $backup_mode, $backup_format_web, time(), $web_backup_file, $file_size, $password);
                unset($file_size);
                $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' completed successfully to file ' . $full_filename, LOGLEVEL_DEBUG);
            } else {
                $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' reported success, but the resulting file ' . $full_filename . ' not found.', LOGLEVEL_ERROR);
            }

        } else {
            if (is_file($full_filename))
                unlink($full_filename);
            $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' failed using path ' . $web_path . ' failed.', LOGLEVEL_ERROR);
        }

	$prefix_list = array(
		    'web',
		    'manual-web',
		);
        self::clearBackups($server_id, $web_id, intval($web_domain['backup_copies']), $web_backup_dir, $prefix_list);
        return true;
    }

    /**
     * Creates and prepares a backup dir
     * @param int $server_id
     * @param array $domain_data
     * @author Ramil Valitov <ramilvalitov@gmail.com>
     */
    protected static function prepare_backup_dir($server_id, $domain_data)
    {
        global $app;

        $server_config = $app->getconf->get_server_config($server_id, 'server');
        $global_config = $app->getconf->get_global_config('sites');

        if (isset($server_config['backup_dir_ftpread']) && $server_config['backup_dir_ftpread'] == 'y') {
            $backup_dir_permissions = 0755;
        } else {
            $backup_dir_permissions = 0750;
        }

        $backup_dir = $server_config['backup_dir'];

        if (!is_dir($backup_dir)) {
            mkdir($backup_dir, $backup_dir_permissions, true);
        } else {
            chmod($backup_dir, $backup_dir_permissions);
        }

        $web_backup_dir = $backup_dir . '/web' . $domain_data['domain_id'];
        if (!is_dir($web_backup_dir))
            mkdir($web_backup_dir, 0750);
        chmod($web_backup_dir, 0750);

        $backup_username = 'root';
        $backup_group = 'root';

        if ($global_config['backups_include_into_web_quota'] == 'y') {
            $backup_username = $domain_data['system_user'];
            $backup_group = $domain_data['system_group'];
        }
        chown($web_backup_dir, $backup_username);
        chgrp($web_backup_dir, $backup_group);
    }

    /**
     * Makes a backup of website files or database.
     * @param string|int $domain_id
     * @param string $type backup type: web or mysql
     * @param string $backup_job how the backup is initiated: manual or auto
     * @param bool $mount if true, then the backup dir will be mounted and unmounted automatically
     * @return bool returns true if success
     * @author Ramil Valitov <ramilvalitov@gmail.com>
     */
    public static function run_backup($domain_id, $type, $backup_job, $mount = true)
    {

        $domain_id = intval($domain_id);

        $sql = "SELECT * FROM web_domain WHERE (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias') AND domain_id = ?";
        $rec = $app->db->queryOneRecord($sql, $domain_id);
        if (empty($rec)) {
            $app->log('Failed to make backup of type ' . $type . ', because no information present about requested domain id ' . $domain_id, LOGLEVEL_ERROR);
            return false;
        }
        $server_id = intval($rec['server_id']);

        if ($mount && !self::mount_backup_dir($server_id)) {
            $app->log('Failed to make backup of type ' . $type . ' for domain id ' . $domain_id . ', because failed to mount backup directory', LOGLEVEL_ERROR);
            return false;
        }
        $ok = false;

        switch ($type) {
            case 'web':
                $ok = self::make_web_backup($rec, $backup_job);
                break;
            case 'mysql':
                $ok = self::make_database_backup($rec, $backup_job);
                break;
            default:
                $app->log('Failed to make backup, because backup type is unknown: ' . $type, LOGLEVEL_ERROR);
                break;
        }
        if ($mount)
            self::unmount_backup_dir($server_id);
        return $ok;
    }

    /**
     * Runs backups of all websites that have backups enabled with respect to their backup interval settings
     * @param int $server_id
     * @param string $backup_job backup tupe: auto or manual
     * @author Ramil Valitov <ramilvalitov@gmail.com>
     */
    public static function run_all_backups($server_id, $backup_job = "auto")
    {
        global $app;

        $server_id = intval($server_id);

        $sql = "SELECT * FROM web_domain WHERE server_id = ? AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias') AND active = 'y' AND backup_interval != 'none' AND backup_interval != ''";
        $domains = $app->db->queryAllRecords($sql, $server_id);

        if (!self::mount_backup_dir($server_id)) {
            $app->log('Failed to run regular backups routine because failed to mount backup directory', LOGLEVEL_ERROR);
            return;
        }
        self::backups_garbage_collection($server_id);

        $date_of_week = date('w');
        $date_of_month = date('d');
        foreach ($domains as $domain) {
            if (($domain['backup_interval'] == 'daily' or ($domain['backup_interval'] == 'weekly' && $date_of_week == 0) or ($domain['backup_interval'] == 'monthly' && $date_of_month == '01'))) {
                self::run_backup($domain['domain_id'], 'web', $backup_job, false);
                self::run_backup($domain['domain_id'], 'mysql', $backup_job, false);
            }
        }
        self::unmount_backup_dir($server_id);
    }
}

?>