[add] first

This commit is contained in:
建喵 2025-08-04 17:11:47 +08:00
commit 6a9fa3321e
6 changed files with 301 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/*.DS_Store
/conf/conf.d

71
README.md Normal file
View File

@ -0,0 +1,71 @@
# NGINX 動態 Server 管理工具
此專案提供一個簡易的 PHP 網頁介面,可以動態新增和刪除 NGINX Server 配置,並即時重載 NGINX 配置,方便本地多專案開發環境管理。
---
## 主要功能
- 顯示目前所有 NGINX 動態 Server以 conf.d/*.conf 設定檔為基礎)
- 新增 Server指定 Port 與根目錄(根目錄為指定的相對路徑,會自動補上預設絕對路徑前綴)
- 刪除 Server
- 新增、刪除後自動重載 NGINX 配置
- 預設排除保留端口(如 80、8080及已被使用的端口
- 前端輸入檢查,避免重複端口與保留端口使用
---
## 系統需求
- PHP 7.0 以上
- NGINX 已安裝且可透過命令行重載
- PHP 有權限讀寫 NGINX conf.d 目錄
- 適用於 macOS 或 Linux 環境,路徑請自行調整
---
## 專案結構
nginx-manager/
├── conf/
│ └── conf.d/ # NGINX 動態 Server 設定檔目錄
├── index.php # 主頁面,顯示及新增刪除 Server
├── servers.php # 處理新增刪除 Server 請求並重載 NGINX
└── README.md
---
## 使用說明
1. 修改 `servers.php` 中的 `$confDir``$nginxExe` 變數,設定對應的 NGINX 配置目錄與 nginx 可執行檔路徑
2. 修改 `index.php``$basePath` 變數(預設為 `/Users/catantech/Desktop/`,用於拼接使用者輸入的相對路徑)
3. 將此專案放置於支援 PHP 的 Web 伺服器中(例如內建 PHP server 或 Apache
4. 開啟瀏覽器進入 `index.php`,即可看到目前動態 Server並可新增或刪除
5. 新增時,請輸入相對於 `$basePath` 的路徑(例如:`Project/Line_Project_1/Official/out-dev`
---
## 注意事項
- 確保 PHP 執行者有讀寫 NGINX 動態設定檔目錄的權限
- 新增 Server 會立即寫入設定檔並重載 NGINX請確保 NGINX 配置無誤,避免重載失敗
- 本專案不包含完整的安全檢查,建議用於本地開發環境
---
## 授權
MIT License
---
## 聯絡
如有問題,歡迎聯絡建喵。

BIN
favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

140
index.php Normal file
View File

@ -0,0 +1,140 @@
<?php
session_start();
function listServerFiles() {
$files = glob(__DIR__ . '/conf/conf.d/*.conf');
$servers = [];
foreach ($files as $file) {
$port = basename($file, '.conf');
$servers[] = [
'port' => $port,
'file' => $file,
'content' => file_get_contents($file)
];
}
return $servers;
}
$servers = listServerFiles();
$reservedPorts = [80, 8080];
// 合併所有已使用 port 與保留 port
$usedPorts = array_unique(array_merge(
$reservedPorts,
array_map('intval', array_column($servers, 'port'))
));
function findRandomAvailablePort($start = 3000, $end = 9000, $usedPorts = []) {
$candidates = range($start, $end);
shuffle($candidates);
foreach ($candidates as $port) {
if (!in_array($port, $usedPorts)) {
return $port;
}
}
return null;
}
$autoPort = findRandomAvailablePort(3000, 9000, $usedPorts);
$resultMsg = '';
if (isset($_SESSION['resultMsg'])) {
$resultMsg = $_SESSION['resultMsg'];
unset($_SESSION['resultMsg']);
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>NGINX 動態 Server 管理</title>
<link rel="icon" href="/favicon.png" data-rh="true">
<style>
body { font-family: sans-serif; padding: 20px; }
pre { background: #f0f0f0; padding: 10px; overflow-x: auto; }
.message { padding: 10px; margin-bottom: 20px; border-radius: 4px; }
.success { background-color: #d4edda; color: #155724; }
.error { background-color: #f8d7da; color: #721c24; }
input[type="number"] { width: 80px; }
</style>
</head>
<body>
<?php if ($resultMsg): ?>
<div class="message <?= strpos($resultMsg, '成功') !== false ? 'success' : 'error' ?>">
<?= nl2br(htmlspecialchars($resultMsg)) ?>
</div>
<?php endif; ?>
<h2>📡 現有 Servers</h2>
<ul>
<?php foreach ($servers as $s): ?>
<?php
// 從 content 透過正規表達式取 root 路徑
preg_match('/root\s+([^\s;]+);/', $s['content'], $matches);
$rootPath = $matches[1] ?? '未知';
// 排除顯示 /Users/catantech/Desktop/
$displayPath = str_replace('/Users/catantech/Desktop/', '', $rootPath);
?>
<li>
<strong>Port <?= htmlspecialchars($s['port']) ?></strong><br>
<strong>Root 路徑: <?= htmlspecialchars($displayPath) ?></strong><br>
<form method="post" action="servers.php" style="display:inline; margin-right: 10px;">
<input type="hidden" name="action" value="delete" />
<input type="hidden" name="port" value="<?= htmlspecialchars($s['port']) ?>" />
<button type="submit">刪除</button>
</form>
<a href="http://192.168.5.45:<?= htmlspecialchars($s['port']) ?>" target="_blank" style="color: blue; text-decoration: underline;">
連結到此 Server
</a>
</li>
<br>
<?php endforeach; ?>
</ul>
<hr>
<h2> 新增 Server</h2>
<form method="post" action="servers.php" id="addServerForm">
<input type="hidden" name="action" value="add" />
<label for="portInput">🎲 Port:</label>
<input type="number" id="portInput" name="port" min="3000" max="9000" required value="<?= $autoPort ?>" />
<br />
<label for="rootInput">Root 目錄:</label>
<input type="text" id="rootInput" name="root" required style="width:400px;" value="Project/Line_Project_1/Official/out-dev" />
<br />
<button type="submit">新增</button>
</form>
<script>
(() => {
const reservedPorts = [80, 8080];
const usedPorts = <?= json_encode($usedPorts) ?>;
const portInput = document.getElementById('portInput');
const form = document.getElementById('addServerForm');
form.addEventListener('submit', e => {
const portVal = Number(portInput.value);
if (isNaN(portVal) || portVal < 3000 || portVal > 9000) {
alert('❌ Port 必須是 3000 到 9000 之間的數字');
e.preventDefault();
return;
}
if (reservedPorts.includes(portVal)) {
alert(`❌ Port ${portVal} 是保留端口,請選擇其他 Port`);
e.preventDefault();
return;
}
if (usedPorts.includes(portVal)) {
alert(`❌ Port ${portVal} 已被使用,請選擇其他 Port`);
e.preventDefault();
return;
}
});
})();
</script>
</body>
</html>

82
servers.php Normal file
View File

@ -0,0 +1,82 @@
<?php
session_start();
$confDir = realpath('/Users/catantech/Desktop/Project/php/nginx-manager/conf/conf.d');
$nginxExe = '/opt/homebrew/bin/nginx';
if (!$confDir) {
die("❌ 找不到 conf.d 目錄");
}
function reloadNginx() {
global $nginxExe;
$configPath = '/opt/homebrew/etc/nginx/nginx.conf'; // 請根據實際路徑調整
$cmd = "\"$nginxExe\" -s reload -c \"$configPath\" 2>&1";
exec($cmd, $output, $code);
return ['code' => $code, 'output' => $output];
}
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$port = intval($_POST['port']);
$confFile = "$confDir/{$port}.conf";
$resultMsg = '';
if ($_POST['action'] === 'add') {
$baseDir = '/Users/catantech/Desktop/';
$relativeRoot = trim($_POST['root']);
// 防止目錄跳脫
if (strpos($relativeRoot, '..') !== false) {
$resultMsg = "❌ Root 路徑不能包含 '..' ";
} else {
// 拼接完整路徑
$fullRoot = rtrim($baseDir, '/') . '/' . ltrim($relativeRoot, '/');
$serverBlock = <<<CONF
server {
listen $port;
server_name localhost;
location / {
root $fullRoot;
index index.html;
try_files \$uri \$uri/ /index.html;
}
}
CONF;
$writeOk = file_put_contents($confFile, $serverBlock) !== false;
if (!$writeOk) {
$resultMsg = "❌ 寫入設定檔失敗";
} else {
$reload = reloadNginx();
if ($reload['code'] !== 0) {
$resultMsg = "❌ NGINX 重啟失敗:" . implode("\n", $reload['output']);
} else {
$resultMsg = "✅ 新增 Server 成功,並已重啟 NGINX";
}
}
}
} elseif ($_POST['action'] === 'delete') {
if (file_exists($confFile)) {
$delOk = unlink($confFile);
if (!$delOk) {
$resultMsg = "❌ 刪除設定檔失敗";
} else {
$reload = reloadNginx();
if ($reload['code'] !== 0) {
$resultMsg = "❌ NGINX 重啟失敗:" . implode("\n", $reload['output']);
} else {
$resultMsg = "✅ 刪除 Server 成功,並已重啟 NGINX";
}
}
} else {
$resultMsg = "❌ 設定檔不存在";
}
}
$_SESSION['resultMsg'] = $resultMsg;
header("Location: index.php");
exit;
}

6
test.php Normal file
View File

@ -0,0 +1,6 @@
<?php
echo '<pre>';
echo shell_exec('whoami');
echo shell_exec('C:\nginx\nginx.exe -t 2>&1');
echo shell_exec('C:\nginx\nginx.exe -s reload 2>&1');
echo '</pre>';