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

PHP 管理 Nginx 虛擬主機 Shell 腳本實作與自動化部署

     編輯:PHP 綜合知識

背景與動機

使用 PHP 撰寫 CLI 腳本管理 Nginx 虛擬主機,優勢在於:

  • 熟悉的語法、豐富的字串/陣列處理函數
  • 易於整合現有 PHP 專案的設定、資料庫、日誌系統
  • 跨平台 (Linux/macOS/WSL) 相容性佳
  • 可搭配 Composer 管理依賴、單元測試

腳本架構設計

vhost-manager/
├── bin/
│   └── vhost              # 入口執行檔 (shebang #!/usr/bin/env php)
├── src/
│   ├── Command/
│   │   ├── CreateCommand.php
│   │   ├── DeleteCommand.php
│   │   └── ListCommand.php
│   ├── Service/
│   │   ├── NginxConfigService.php
│   │   └── FileSystemService.php
│   └── Template/
│       └── vhost.conf.tpl # Nginx 設定模板
├── composer.json
└── phpunit.xml

核心實作:Nginx 設定模板

templates/vhost.conf.tpl

server {
    listen 80;
    listen [::]:80;
    server_name {$domain};

    root {$rootPath};
    index index.php index.html index.htm;

    access_log /var/log/nginx/{$domain}_access.log;
    error_log  /var/log/nginx/{$domain}_error.log;

    # 安全性:隱藏版本號、限制請求大小
    server_tokens off;
    client_max_body_size 20M;

    # 忽略 favicon/robots 記錄
    location = /favicon.ico {
        access_log off; log_not_found off;
    }
    location = /robots.txt {
        access_log off; log_not_found off; allow all;
    }

    # PHP 處理
    location ~ \.php$ {
        fastcgi_pass unix:/run/php/php8.2-fpm.sock;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_intercept_errors on;
    }

    # 靜態資源快取
    location ~* \.(?:css|js|jpg|jpeg|gif|png|ico|svg|woff|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, no-transform";
    }

    # 禁止存取隱藏檔、備份檔
    location ~ /\. {
        deny all;
    }
    location ~* \.(?:bak|backup|sql|log)$ {
        deny all;
    }
}

服務類:NginxConfigService

<?php
// src/Service/NginxConfigService.php
declare(strict_types=1);

namespace VhostManager\Service;

class NginxConfigService
{
    private string $sitesAvailableDir;
    private string $sitesEnabledDir;
    private string $templatePath;
    private string $webRootBase;

    public function __construct(
        string $sitesAvailableDir = '/etc/nginx/sites-available',
        string $sitesEnabledDir = '/etc/nginx/sites-enabled',
        string $templatePath = __DIR__ . '/../../templates/vhost.conf.tpl',
        string $webRootBase = '/var/www'
    ) {
        $this->sitesAvailableDir = $sitesAvailableDir;
        $this->sitesEnabledDir = $sitesEnabledDir;
        $this->templatePath = $templatePath;
        $this->webRootBase = $webRootBase;
    }

    public function create(string $domain): bool
    {
        $this->validateDomain($domain);

        $configFile = "{$this->sitesAvailableDir}/{$domain}.conf";
        $linkFile   = "{$this->sitesEnabledDir}/{$domain}.conf";
        $rootPath   = "{$this->webRootBase}/{$domain}";

        // 1. 檢查是否已存在
        if (file_exists($configFile) || is_link($linkFile)) {
            throw new \RuntimeException("虛擬主機 {$domain} 已存在");
        }

        // 2. 建立網站根目錄
        if (!is_dir($rootPath)) {
            mkdir($rootPath, 0755, true);
            // 建立預設 index.php
            file_put_contents("{$rootPath}/index.php", "templatePath);
        $config = strtr($template, [
            '{$domain}'   => $domain,
            '{$rootPath}' => $rootPath,
        ]);

        // 4. 寫入設定檔
        file_put_contents($configFile, $config);

        // 5. 建立軟連結啟用
        symlink($configFile, $linkFile);

        // 6. 測試 Nginx 設定並重載
        $this->reloadNginx();

        return true;
    }

    public function delete(string $domain): bool
    {
        $configFile = "{$this->sitesAvailableDir}/{$domain}.conf";
        $linkFile   = "{$this->sitesEnabledDir}/{$domain}.conf";
        $rootPath   = "{$this->webRootBase}/{$domain}";

        if (!file_exists($configFile)) {
            throw new \RuntimeException("虛擬主機 {$domain} 不存在");
        }

        // 1. 移除啟用連結
        if (is_link($linkFile)) {
            unlink($linkFile);
        }

        // 2. 備份設定檔
        $backupDir = "/etc/nginx/backup";
        if (!is_dir($backupDir)) {
            mkdir($backupDir, 0755, true);
        }
        $backupFile = "{$backupDir}/{$domain}.conf." . date('YmdHis');
        rename($configFile, $backupFile);

        // 3. 封存並移除網站目錄
        $archive = "/var/backups/{$domain}.tar.gz";
        exec("tar -czf {$archive} {$rootPath} 2>/dev/null");
        exec("rm -rf {$rootPath}");

        // 4. 重載 Nginx
        $this->reloadNginx();

        return true;
    }

    public function list(): array
    {
        $files = glob("{$this->sitesAvailableDir}/*.conf");
        $domains = [];
        foreach ($files as $file) {
            $domain = basename($file, '.conf');
            $enabled = is_link("{$this->sitesEnabledDir}/{$domain}.conf");
            $domains[] = ['domain' => $domain, 'enabled' => $enabled];
        }
        return $domains;
    }

    private function validateDomain(string $domain): void
    {
        if (!preg_match('/^([a-z0-9-]+\.)+[a-z]{2,}$/i', $domain)) {
            throw new \InvalidArgumentException("無效域名格式: {$domain}");
        }
    }

    private function reloadNginx(): void
    {
        // 測試設定語法
        $test = exec('nginx -t 2>&1', $output, $returnCode);
        if ($returnCode !== 0) {
            throw new \RuntimeException("Nginx 設定測試失敗: " . implode("\n", $output));
        }

        // 重載 (無中斷)
        exec('systemctl reload nginx 2>&1', $output, $returnCode);
        if ($returnCode !== 0) {
            // 嘗試 service 指令
            exec('service nginx reload 2>&1', $output, $returnCode);
            if ($returnCode !== 0) {
                throw new \RuntimeException("Nginx 重載失敗: " . implode("\n", $output));
            }
        }
    }
}
?>

命令列介面 (使用 Symfony Console)

<?php
// bin/vhost
#!/usr/bin/env php
add(new CreateCommand());
$app->add(new DeleteCommand());
$app->add(new ListCommand());
$app->run();
?>
<?php
// src/Command/CreateCommand.php
namespace VhostManager\Command;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use VhostManager\Service\NginxConfigService;

class CreateCommand extends Command
{
    private NginxConfigService $service;

    public function __construct(NginxConfigService $service)
    {
        $this->service = $service;
        parent::__construct();
    }

    protected function configure(): void
    {
        $this->setName('create')
             ->setDescription('建立新的虛擬主機')
             ->addArgument('domain', InputArgument::REQUIRED, '域名 (例: example.com)');
    }

    protected function execute(InputInterface $input, OutputInterface $output): int
    {
        $domain = $input->getArgument('domain');
        try {
            $this->service->create($domain);
            $output->writeln("虛擬主機 {$domain} 建立成功");
            return Command::SUCCESS;
        } catch (\Throwable $e) {
            $output->writeln("{$e->getMessage()}");
            return Command::FAILURE;
        }
    }
}
?>

使用方式

# 安裝依賴
composer require symfony/console symfony/filesystem

# 設定執行權限
chmod +x bin/vhost

# 建立虛擬主機
./bin/vhost create example.com

# 列出所有虛擬主機
./bin/vhost list

# 刪除虛擬主機
./bin/vhost delete example.com

進階功能擴展

  • SSL 憑證自動申請:整合 Certbot/Let's Encrypt,create 時自動申請並設定 HTTPS。
  • PHP 版本切換:模板支援多 PHP-FPM socket,參數指定版本。
  • 反向代理模式:支援 proxy_pass 至上游應用 (Node.js, Go, Java)。
  • 變更追蹤:Git 管理 /etc/nginx,每次變更自動 commit。
  • API 介面:封裝為 REST API,供控制台/自動化系統呼叫。

安全性考量

  1. 輸入驗證:嚴格驗證域名格式,防止路徑遍歷注入。
  2. 最小權限:腳本以專用低權限用戶執行,透過 sudoers 僅允許 nginx -t、systemctl reload nginx、tar、rm 特定路徑。
  3. 審計日誌:所有操作記錄至 syslog/資料庫,含操作者、時間、參數、結果。
  4. 備份保留:刪除前自動備份設定檔與網站檔案,保留 30 天。

總結

使用 PHP 開發 Nginx 虛擬主機管理工具,結合模板引擎、Symfony Console、服務層封裝,能構建出結構清晰、可測試、可擴展的自動化運維工具。關鍵在於:模板化設定、原子性操作 (測試→寫入→重載)、完善錯誤處理與安全審計。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved