Skip to content
backup.inc.php 105 KiB
Newer Older
                    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);
    protected static function getBackupReposFolder($backup_mode, $backup_type)
    {
        switch ($backup_mode) {
            case 'borg': return 'borg_' . $backup_type;
        }
        return null;
    }

    /**
     * Prepares repository for backup. Initialization, etc.
     */
    protected static function prepareRepos($backup_mode, $repos_path, $password)
    {
        global $app;
        if (is_dir($repos_path)) {
            self::getReposArchives($backup_mode, $repos_path, $password);
            if ($app->system->last_exec_retcode() == 0) {
                return true;
            }
            if ($app->system->last_exec_retcode() == 2 && preg_match('/passphrase supplied in.*is incorrect/', $app->system->last_exec_out()[0])) {
                //Password was updated, so we rename folder and alert the event.
                $repos_stat = stat($repos_path);
                $mtime = $repos_stat['mtime'];
                $new_repo_path = $repos_path . '_' . date('Y-m-d_H-i', $mtime);
                rename($repos_path, $new_repo_name);
                $app->log('Backup of web files for domain ' . $web_domain['domain'] . ' are encrypted under a different password. Original repos was moved to ' . $new_repo_name, LOGLEVEL_WARN);
            } else {
                return false;
            }
        }
        switch ($backup_mode) {
            case 'borg':
                if ($password) {
                    $command = self::getBorgCommand('borg init', $password, true);
                    $app->system->exec_safe($command . ' --make-parent-dirs -e authenticated ?', $repos_path);
                } else {
                    $app->system->exec_safe('borg init --make-parent-dirs -e none ?', $repos_path);
                }
                return $app->system->last_exec_retcode() == 0;
        }
        return false;
    }

    /**
     * Obtains archive compressed size from specific repository.
     * @param string $backup_mode Server backup mode.
     * @param string $backup_repos_path Absolute path to repository.
     * @param string $backup_archive Name of the archive to obtain size from.
     * @param string $password Provide repository password or empty string if there is none.
     * @author Jorge Muñoz <elgeorge2k@gmail.com>
     */
    protected static function getReposArchiveSize($backup_mode, $backup_repos_path, $backup_archive, $password)
    {
        $info = self::getReposArchiveInfo($backup_mode, $backup_repos_path, $backup_archive, $password);
        if ($info) {
            return $info['compressed_size'];
        }
        return false;
    }
    /**
     * Obtains archive information for specific repository archive.
     * @param string $backup_mode Server backup mode.
     * @param string $backup_repos_path Absolute path to repository.
     * @param string $backup_archive Name of the archive to obtain size from.
     * @param string $password Provide repository password or empty string if there is none.
     * @return array Can contain one or more of the following keys:
     *      'created_at': int unixtime
     *      'created_by': string
     *      'comment': string
     *      'original_size': int
     *      'compressed_size': int
     *      'deduplicated_size': int
     *      'num_files': int
     *      'compression': string
     * @author Jorge Muñoz <elgeorge2k@gmail.com>
     */
    protected static function getReposArchiveInfo($backup_mode, $backup_repos_path, $backup_archive, $password)
    {
        global $app;
        $info = [];
        switch ($backup_mode) {
            case 'borg':
                $command = self::getBorgCommand('borg info', $password);
                $full_archive_path = $backup_repos_path . '::' . $backup_archive;
                $app->system->exec_safe($command . ' --json ?', $full_archive_path);
                if ($app->system->last_exec_retcode() != 0) {
                    $app->log('Command `borg info` failed for ' . $full_archive_path . '.', LOGLEVEL_ERROR);
                }
                $out = implode("", $app->system->last_exec_out());
                if ($out) {
                    $out = json_decode($out, true);
                }
                if (empty($out)) {
                    $app->log('No json result could be parsed from `borg info --json` command for repository ' . $full_archive_path . '.', LOGLEVEL_ERROR);
                    return false;
                }
                if (empty($out['archives'])) {
                    $app->log('No archive ' . $backup_archive . ' found for repository ' . $backup_repos_path . '.', LOGLEVEL_WARN);
                    return false;
                }
                $info['created_at'] = strtotime($out['archives'][0]['start']);
                $info['created_by'] = $out['archives'][0]['username'];
                $info['comment'] = $out['archives'][0]['comment'];
                $info['original_size'] = (int)$out['archives'][0]['stats']['original_size'];
                $info['compressed_size'] = (int)$out['archives'][0]['stats']['compressed_size'];
                $info['deduplicated_size'] = (int)$out['archives'][0]['stats']['deduplicated_size'];
                $info['num_files'] = (int)$out['archives'][0]['stats']['nfiles'];
                $prev_arg = null;
                foreach ($out['archives'][0]['command_line'] as $arg) {
                    if ($prev_arg == '-C' || $prev_arg == '--compression') {
                        $info['compression'] = $arg;
                        break;
                    }
                    $prev = $arg;
                }
        }
        return $info;
    }

    /**
     * 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)
    {
        global $app, $conf;

        $domain_id = intval($domain_id);

        $sql = "SELECT * FROM web_domain WHERE (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias') AND domain_id = ?";
        $rec = $app->dbmaster->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($conf['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':
                $rec['server_id'] = $server_id;
                $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->dbmaster->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);
        $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 != ''";
        $databases = $app->dbmaster->queryAllRecords($sql, $server_id);

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

?>