首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

cPanel PHP 自動備份腳本:網站文件與數據庫定時異地備份

     編輯:PHP 相關知識

備份策略設計

層級內容頻率保留存儲
L1: 代碼/配置文件 (public_html, .env, config)每日30 天異地 FTP/S3
L2: 數據庫完整 SQL Dump每日/每 6 小時30 天異地 FTP/S3
L3: 增量Git 提交 / rsync每小時7 天本地/異地
L4: 全量鏡像服務器快照/鏡像每週4 週雲廠商

方案一:cPanel 官方 API 完整備份(原文改進版)

調用 cPanel UAPI 執行完整備份(含文件、數據庫、郵件、DNS),並通過 FTP/SFTP 推送到遠程存儲。

<?php
/**
 * cPanel 完整備份自動化
 * 需:cPanel 帳號、API Token(WHM 生成)或密碼
 * 將腳本放在 public_html 外部,設置 600 權限
 */
class CpanelBackup
{
    private string $host;
    private string $user;
    private string $token; // 或 password
    private int $port = 2083; // SSL
    private string $apiBase;

    public function __construct(string $host, string $user, string $token, bool $useSsl = true)
    {
        $this->host = rtrim($host, '/');
        $this->user = $user;
        $this->token = $token;
        $this->port = $useSsl ? 2083 : 2082;
        $this->apiBase = ($useSsl ? 'https' : 'http') . "://{$this->host}:{$this->port}/execute";
    }

    /**
     * 創建完整備份並可選推送到遠程 FTP
     */
    public function createFullBackup(array $options = []): array
    {
        $params = [
            'destination' => $options['destination'] ?? 'home', // home, ftp, scp, s3
            'email'       => $options['email'] ?? '',           // 完成通知郵箱
        ];

        // FTP 遠程存儲參數
        if (($params['destination'] ?? '') === 'ftp') {
            $params += [
                'server'     => $options['ftp_host'] ?? '',
                'user'       => $options['ftp_user'] ?? '',
                'pass'       => $options['ftp_pass'] ?? '',
                'port'       => $options['ftp_port'] ?? 21,
                'dir'        => $options['ftp_dir'] ?? '/',
                'passive'    => $options['ftp_passive'] ?? 1,
            ];
        }

        return $this->uapi('Backup', 'fullbackup', $params);
    }

    /**
     * 下載已生成的備份文件
     */
    public function listBackups(): array
    {
        return $this->uapi('Fileman', 'list_files', ['dir' => '/backup']);
    }

    private function uapi(string $module, string $function, array $params = []): array
    {
        $url = "{$this->apiBase}/{$module}/{$function}";
        $query = http_build_query($params);
        
        $ch = curl_init("{$url}?{$query}");
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_HTTPHEADER     => [
                "Authorization: cpanel {$this->user}:{$this->token}",
                'Accept: application/json',
            ],
            CURLOPT_SSL_VERIFYPEER => false, // 自簽證書時
            CURLOPT_TIMEOUT        => 300,   // 備份可能耗時
        ]);
        
        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);

        if ($httpCode !== 200) {
            throw new RuntimeException("cPanel API 失敗 [$httpCode]: $response");
        }
        
        $data = json_decode($response, true);
        if (($data['metadata']['result'] ?? 0) !== 1) {
            throw new RuntimeException("cPanel API 錯誤: " . ($data['metadata']['reason'] ?? '未知'));
        }
        return $data['data'] ?? [];
    }
}

// === 使用示例 ===
$backup = new CpanelBackup('cpanel.yourdomain.com', 'cpanel_user', 'API_TOKEN_FROM_WHM');

try {
    $result = $backup->createFullBackup([
        'destination' => 'ftp',
        'email'       => 'admin@example.com',
        'ftp_host'    => 'backup.storage.com',
        'ftp_user'    => 'backup_user',
        'ftp_pass'    => 'strong_password',
        'ftp_dir'     => '/backups/cpanel/',
    ]);
    echo "備份任務已提交:\n";
    print_r($result);
} catch (Exception $e) {
    error_log("cPanel 備份失敗: " . $e->getMessage());
    // 發送告警...
}

方案二:純 PHP 文件 + 數據庫備份(不依賴 cPanel API,通用性更強)

<?php
/**
 * 通用網站備份腳本
 * 功能:打包文件、導出數據庫、加密、上傳 FTP/S3、清理舊備份
 * 部署:放在 /home/user/backup/backup.php,chmod 600
 * 定時:crontab -e → 0 2 * * * /usr/bin/php /home/user/backup/backup.php >> /home/user/backup/log.txt 2>&1
 */

class SiteBackup
{
    private array $config;

    public function __construct(array $config)
    {
        $this->config = array_merge([
            'site_path'      => $_SERVER['DOCUMENT_ROOT'] ?? '/var/www/html',
            'exclude'        => ['.git', 'node_modules', 'vendor', 'storage/logs', '*.log', 'cache/*'],
            'db_host'        => '127.0.0.1',
            'db_port'        => 3306,
            'db_name'        => '',
            'db_user'        => '',
            'db_pass'        => '',
            'db_tables'      => '*', // 或 ['users', 'orders']
            'storage'        => 'ftp', // ftp, s3, local
            'ftp_host'       => '',
            'ftp_user'       => '',
            'ftp_pass'       => '',
            'ftp_dir'        => '/backups/',
            's3_key'         => '',
            's3_secret'      => '',
            's3_region'      => 'us-east-1',
            's3_bucket'      => '',
            'encrypt_key'    => '', // OpenSSL AES-256-CBC 加密密鑰(32字節)
            'keep_days'      => 30,
            'temp_dir'       => sys_get_temp_dir() . '/site_backup_' . getmypid(),
        ], $config);
    }

    public function run(): void
    {
        $this->prepareTempDir();
        
        $date = date('Y-m-d_H-i-s');
        $baseName = "backup_{$date}";
        $archive = "{$this->config['temp_dir']}/{$baseName}.tar.gz";
        $encrypted = $archive . '.enc';

        // 1. 打包文件
        $this->createArchive($archive);
        
        // 2. 導出數據庫
        $sqlFile = "{$this->config['temp_dir']}/{$baseName}.sql";
        $this->dumpDatabase($sqlFile);
        // 將 SQL 加入壓縮包
        $this->appendToArchive($archive, $sqlFile, $baseName . '.sql');
        @unlink($sqlFile);

        // 3. 加密(可選)
        if ($this->config['encrypt_key']) {
            $this->encryptFile($archive, $encrypted);
            @unlink($archive);
            $finalFile = $encrypted;
        } else {
            $finalFile = $archive;
        }

        // 4. 上傳
        $remotePath = $this->config['ftp_dir'] . basename($finalFile);
        $this->upload($finalFile, $remotePath);

        // 5. 清理本地臨時文件
        @unlink($finalFile);
        $this->cleanOldBackups();
        
        echo "備份完成:$remotePath\n";
    }

    private function prepareTempDir(): void
    {
        if (!is_dir($this->config['temp_dir'])) {
            mkdir($this->config['temp_dir'], 0700, true);
        }
    }

    private function createArchive(string $archive): void
    {
        $excludes = implode(' ', array_map(fn($p) => "--exclude='$p'", $this->config['exclude']));
        $cmd = "tar -czf " . escapeshellarg($archive) . " $excludes -C " . escapeshellarg($this->config['site_path']) . " .";
        $this->exec($cmd, "打包文件失敗");
    }

    private function appendToArchive(string $archive, string $file, string $nameInArchive): void
    {
        $cmd = "tar -rf " . escapeshellarg($archive) . " -C " . escapeshellarg(dirname($file)) . " " . escapeshellarg(basename($file));
        $this->exec($cmd, "添加 SQL 到壓縮包失敗");
        // 重新壓縮(tar -rf 不壓縮,簡化:直接重新打包含 SQL)
        // 實際建議:先打包文件,再打包 SQL,最後合並或分開存儲
    }

    private function dumpDatabase(string $outputFile): void
    {
        $db = $this->config;
        $tables = $db['db_tables'] === '*' ? '' : implode(' ', $db['db_tables']);
        $cmd = "mysqldump -h{$db['db_host']} -P{$db['db_port']} -u{$db['db_user']} -p" . escapeshellarg($db['db_pass']) . " --single-transaction --quick --lock-tables=false $db[db_name] $tables > " . escapeshellarg($outputFile);
        $this->exec($cmd, "數據庫導出失敗");
    }

    private function encryptFile(string $input, string $output): void
    {
        $key = $this->config['encrypt_key'];
        if (strlen($key) !== 32) throw new Exception('加密密鑰必須 32 字節');
        $iv = random_bytes(16);
        $cmd = "openssl enc -aes-256-cbc -K " . bin2hex($key) . " -iv " . bin2hex($iv) . " -in " . escapeshellarg($input) . " -out " . escapeshellarg($output);
        $this->exec($cmd, "加密失敗");
        // 將 IV 存儲在文件名或單獨文件中,解密時需要
        file_put_contents($output . '.iv', $iv);
    }

    private function upload(string $localFile, string $remotePath): void
    {
        switch ($this->config['storage']) {
            case 'ftp':
                $this->uploadFtp($localFile, $remotePath);
                break;
            case 's3':
                $this->uploadS3($localFile, $remotePath);
                break;
            default:
                copy($localFile, $this->config['temp_dir'] . '/' . basename($remotePath));
        }
    }

    private function uploadFtp(string $local, string $remote): void
    {
        $conn = ftp_ssl_connect($this->config['ftp_host']) ?: ftp_connect($this->config['ftp_host']);
        if (!$conn) throw new Exception("FTP 連接失敗");
        if (!ftp_login($conn, $this->config['ftp_user'], $this->config['ftp_pass'])) {
            throw new Exception("FTP 登錄失敗");
        }
        ftp_pasv($conn, true);
        // 確保遠程目錄存在
        $this->ftpMkdirRecursive($conn, dirname($remote));
        if (!ftp_put($conn, $remote, $local, FTP_BINARY)) {
            throw new Exception("FTP 上傳失敗");
        }
        ftp_close($conn);
    }

    private function ftpMkdirRecursive($conn, string $path): void
    {
        $parts = explode('/', trim($path, '/'));
        $current = '';
        foreach ($parts as $part) {
            $current .= '/' . $part;
            if (!@ftp_chdir($conn, $current)) {
                ftp_mkdir($conn, $current);
                ftp_chdir($conn, $current);
            }
        }
    }

    private function uploadS3(string $local, string $remote): void
    {
        // 需 aws/aws-sdk-php
        // $s3 = new Aws\S3\S3Client([...]);
        // $s3->putObject(['Bucket' => $this->config['s3_bucket'], 'Key' => $remote, 'SourceFile' => $local]);
    }

    private function cleanOldBackups(): void
    {
        // 列出遠程文件並刪除超過 keep_days 的
        // 這裡簡化:實際需根據存儲類型實現
    }

    private function exec(string $cmd, string $errorMsg): void
    {
        $output = [];
        $code = 0;
        exec($cmd, $output, $code);
        if ($code !== 0) {
            throw new Exception("$errorMsg: $cmd\n" . implode("\n", $output));
        }
    }
}

// === 配置文件(單獨存放,如 /home/user/backup/config.php,chmod 600) ===
/*
return [
    'site_path' => '/home/user/public_html',
    'db_name' => 'wp_db',
    'db_user' => 'wp_user',
    'db_pass' => 'strong_db_pass',
    'storage' => 'ftp',
    'ftp_host' => 'backup.example.com',
    'ftp_user' => 'backup_user',
    'ftp_pass' => 'strong_ftp_pass',
    'ftp_dir' => '/backups/site1/',
    'encrypt_key' => '32_bytes_random_key_here_12345678', // openssl_random_pseudo_bytes(32)
    'keep_days' => 30,
];
*/

// === 入口文件 backup.php ===
/*
require __DIR__ . '/config.php';
$backup = new SiteBackup($config);
try {
    $backup->run();
} catch (Exception $e) {
    error_log("備份失敗: " . $e->getMessage());
    // 發送郵件/釘釘/企微告警
    mail('admin@example.com', '網站備份失敗', $e->getMessage());
    exit(1);
}
*/

方案三:rsync + 硬鏈接增量備份(最高效、類 Time Machine)

#!/bin/bash
# backup_rsync.sh - 放在備份服務器上運行,拉取源服務器
# 原理:rsync --link-dest 指向上一次備份,未變文件創建硬鏈接,極大節省空間

SOURCE="user@source-server:/home/user/public_html/"
DEST="/backups/source-server"
DATE=$(date +%Y-%m-%d_%H-%M-%S)
LATEST="$DEST/latest"
CURRENT="$DEST/$DATE"

mkdir -p "$CURRENT"

rsync -avz --delete \
    --link-dest="$LATEST" \
    --exclude={'.git','node_modules','vendor','storage/logs','*.log','cache/*'} \
    "$SOURCE" "$CURRENT/"

# 更新 latest 指向
rm -f "$LATEST"
ln -s "$CURRENT" "$LATEST"

# 清理 30 天前備份
find "$DEST" -maxdepth 1 -type d -mtime +30 -name '20*' -exec rm -rf {} \;

# 同步數據庫(單獨腳本 mysqldump + gzip)
ssh user@source-server "mysqldump -u db_user -p'pass' db_name | gzip" > "$CURRENT/db.sql.gz"

定時任務配置

# crontab -e (以站點用戶身份運行,非 root)

# 方案二:每天凌晨 2 點備份
0 2 * * * /usr/bin/php /home/user/backup/backup.php >> /home/user/backup/backup.log 2>&1

# 方案三:備份服務器每天凌晨 3 點拉取
0 3 * * * /backups/scripts/backup_rsync.sh >> /backups/logs/rsync.log 2>&1

# 數據庫單獨高頻備份(每 6 小時)
0 */6 * * * /usr/bin/php /home/user/backup/dump_db.php >> /home/user/backup/db.log 2>&1

安全與最佳實踐清單

  1. 腳本權限:`chmod 600 backup.php config.php`,放在 `public_html` 外
  2. 敏感信息:密碼/密鑰存環境變量或單獨配置文件,不入 Git
  3. 加密備份:AES-256 加密,密鑰離線保存(紙質/密碼管理器)
  4. 異地存儲:最少兩地(FTP/S3/NAS/雲盤),驗證可恢復
  5. 定期演練:每季度測試恢複流程,記錄 RTO/RPO
  6. 監控告警:備份失敗、空間不足、文件大小異常(過小可能失敗)發送告警
  7. 日誌保留:備份日誌保留 1 年,便於審計

恢複腳本模板

<?php
// restore.php - 緊急恢複用
// 1. 下載備份文件
// 2. 解密:openssl enc -d -aes-256-cbc -K $KEY -iv $IV -in backup.tar.gz.enc -out backup.tar.gz
// 3. 解壓:tar -xzf backup.tar.gz -C /target/path
// 4. 導入數據庫:mysql -u user -p db < db.sql
// 5. 修復權限、清理緩存、測試訪問
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved