c9a99f4fb3
代码修复: - web/agent/agent.py: datetime.utcnow() → datetime.now(timezone.utc) - requirements.txt: 移除 paramiko==3.5.0 (已无活跃引用) - 删除 server/infrastructure/ssh/pool.py (DEPRECATED, 无引用) 连接池三层对齐 (config.py/.env.example/session.py): - DB_POOL_SIZE 100→160, DB_MAX_OVERFLOW 100→120 - 基于 MySQL max_connections=400, install.php 公式 文档修复 (21项): - P0: 硬编码域名/IP替换为配置变量占位符 - P1: 10个过时设计文档加归档标注 (引用旧文件结构) - P2: step-3-webssh报告 paramiko引用修正 - 6份审查报告连接池参数勘误 100/100→160/120 - ECC安全报告 EC5 datetime.utcnow 标记已修复 - docs/README.md 文档索引重写 - docs/memory/mem_nexus_overview.md 移除硬编码凭证 - docs/project/tech-stack-inventory.md paramiko标记已移除 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
202 lines
6.1 KiB
Markdown
202 lines
6.1 KiB
Markdown
# servers.php 添加服务器优化 — 实现计划
|
||
|
||
> ⚠️ **已归档** — 2026-05-22
|
||
> 本文档引用的文件结构已过时:`web/servers.php` 已迁移到 `web/app/servers.html` (Tailwind CSS v4 + Alpine.js);`server/services/ssh_direct.py` 已被 asyncssh 连接池替代;`server/database.py` 已拆分为 `server/domain/models/`。功能已实现,保留本文档供历史参考。
|
||
|
||
**基于设计**: docs/superpowers/specs/2026-05-17-server-add-optimize-design.md
|
||
**日期**: 2026-05-17
|
||
|
||
## 任务 1 — 后端:create_server 自动生成密钥 + 目标路径入表单
|
||
|
||
**文件**: `server/api/servers.py`
|
||
|
||
```python
|
||
# ServerCreate 模型:加 target_path,agent_api_key 改为 Optional(不从前端收)
|
||
class ServerCreate(BaseModel):
|
||
...
|
||
target_path: str # 新增必填
|
||
agent_api_key: Optional[str] = None # 自动生成
|
||
|
||
# create_server 路由:自动生成 agent_api_key
|
||
def create_server(server_data):
|
||
import secrets
|
||
data = server_data.model_dump()
|
||
data["agent_api_key"] = data.get("agent_api_key") or secrets.token_hex(16)
|
||
...
|
||
```
|
||
|
||
**验证**: `curl -X POST /api/servers/ -d '{"name":"t","domain":"1.1.1.1","target_path":"/tmp"}'` 返回含 agent_api_key
|
||
|
||
---
|
||
|
||
## 任务 2 — 后端:异步 SSH 安装 Agent 服务
|
||
|
||
**文件**: 新建 `server/services/agent_installer.py`
|
||
|
||
```python
|
||
async def install_agent_on_server(server_id: int):
|
||
"""SSH 到子服务器执行 Agent 安装脚本,通过 Redis pub/sub 推送进度"""
|
||
# 1. 读 DB 获取 server
|
||
# 2. 构造安装命令:curl install.sh | bash --url ... --key ... --dirs ...
|
||
# 3. SSH exec_command 执行
|
||
# 4. 逐行读取 stdout → Redis PUBLISH install:{id}:log
|
||
# 5. 完成/失败 → 更新 server.is_online + Redis state
|
||
```
|
||
|
||
**Redis 消息格式**:
|
||
```
|
||
PUBLISH install:3:log {"percent":45,"line":"[3/5] 下载 Agent..."}
|
||
PUBLISH install:3:log {"percent":100,"line":"✅ 安装完成","status":"done"}
|
||
```
|
||
|
||
**安装命令**:
|
||
```bash
|
||
curl -fsSL https://${NEXUS_DOMAIN}/agent/install.sh | bash -s -- \
|
||
--url http://${NEXUS_DOMAIN}:8600 \
|
||
--key <agent_api_key> \
|
||
--dirs <target_path>
|
||
```
|
||
|
||
**验证**: `python -m py_compile server/services/agent_installer.py` 通过
|
||
|
||
---
|
||
|
||
## 任务 3 — 后端:create_server 触发异步安装
|
||
|
||
**文件**: `server/api/servers.py`
|
||
|
||
```python
|
||
def create_server():
|
||
...
|
||
db.commit(); db.refresh(server)
|
||
# 触发后台安装
|
||
import asyncio
|
||
from server.services.agent_installer import install_agent_on_server
|
||
asyncio.create_task(install_agent_on_server(server.id))
|
||
return server
|
||
```
|
||
|
||
**验证**: 创建服务器后,查看 Redis `install:{id}:state` 是否有进度
|
||
|
||
---
|
||
|
||
## 任务 4 — 后端:WebSocket 转发安装进度
|
||
|
||
**文件**: `server/main.py`
|
||
|
||
```python
|
||
# 已有 broadcast_push_progress,新增 install 进度广播
|
||
async def broadcast_install_progress(server_id: int, data: dict):
|
||
"""将 Redis 安装进度转发到 WebSocket"""
|
||
for ws in connected_ws:
|
||
await ws.send_json({"type":"install_progress","server_id":server_id,**data})
|
||
```
|
||
|
||
**验证**: WebSocket 连接后能收到 `{"type":"install_progress",...}`
|
||
|
||
---
|
||
|
||
## 任务 5 — 前端:表单改造
|
||
|
||
**文件**: `web/servers.php`
|
||
|
||
**变更**:
|
||
1. 移除 `agent_api_key` 输入框
|
||
2. 隐藏 `agent_port` → 固定 8601
|
||
3. 新增 `target_path` 必填输入框
|
||
4. SSH 密码标记必填
|
||
5. 提交按钮改为「保存并安装 Agent」
|
||
|
||
```html
|
||
<!-- 目标路径 -->
|
||
<div class="form-group">
|
||
<label>目标路径 <span class="required">*</span></label>
|
||
<input type="text" name="target_path" class="form-control" required
|
||
placeholder="/www/wwwroot/project">
|
||
</div>
|
||
```
|
||
|
||
**PHP POST 处理**: 加 `target_path` 到 `$formData`
|
||
|
||
**验证**: 表单提交后服务器创建成功,密码可空→必填校验生效
|
||
|
||
---
|
||
|
||
## 任务 6 — 前端:安装进度显示 + 日志弹窗
|
||
|
||
**文件**: `web/servers.php`
|
||
|
||
**变更**:
|
||
1. 服务器列表:离线且 `install_state != done` → 显示进度条
|
||
2. WebSocket 监听 `install_progress` → 更新行内进度和日志弹窗
|
||
3. 新增日志弹窗 DOM
|
||
|
||
```js
|
||
// 日志弹窗
|
||
<div class="push-overlay" id="installLogOverlay">
|
||
<div class="push-dialog">
|
||
<h3>安装日志: <span id="installLogTitle"></span></h3>
|
||
<div id="installLogContent" style="font-family:monospace;font-size:12px;padding:16px;max-height:400px;overflow-y:auto;background:#1e293b;color:#e2e8f0;border-radius:6px;"></div>
|
||
<div class="push-footer"><button onclick="closeInstallLog()">关闭</button></div>
|
||
</div>
|
||
</div>
|
||
```
|
||
|
||
**ws.onmessage 处理**:
|
||
```js
|
||
if (msg.type === 'install_progress') {
|
||
updateInstallProgress(msg.server_id, msg.percent, msg.line);
|
||
if (msg.status === 'done') location.reload();
|
||
}
|
||
```
|
||
|
||
**验证**: 添加服务器后列表出现进度,点击弹出日志
|
||
|
||
---
|
||
|
||
## 任务 7 — 前端:目标路径快速编辑
|
||
|
||
**文件**: `web/servers.php`
|
||
|
||
**变更**:
|
||
1. 列表每行 `target_path` 后加 ✏️ 按钮
|
||
2. 点击 → `td` 内出现 `<input>` + 确认按钮
|
||
3. 回车 → `PUT /api/servers/{id}` 更新 target_path
|
||
|
||
```html
|
||
<td class="path-cell">
|
||
<span id="path_<?=$s['id']?>"><?= htmlspecialchars($s['target_path']??'-') ?></span>
|
||
<button class="btn btn-sm" onclick="editPath(<?=$s['id']?>)">✏️</button>
|
||
</td>
|
||
```
|
||
|
||
```js
|
||
function editPath(id) {
|
||
const span = document.getElementById('path_' + id);
|
||
const old = span.textContent;
|
||
span.innerHTML = '<input id="path_input_' + id + '" value="'+old+'" style="width:160px;">' +
|
||
'<button onclick="savePath('+id+')">✓</button>';
|
||
}
|
||
|
||
function savePath(id) {
|
||
const v = document.getElementById('path_input_' + id).value;
|
||
fetch(API_BASE + '/api/servers/' + id, {method:'PUT',headers:{'Content-Type':'application/json','X-API-Key':API_KEY},
|
||
body: JSON.stringify({target_path: v})})
|
||
.then(() => location.reload());
|
||
}
|
||
```
|
||
|
||
**验证**: 点击 ✏️ → 修改路径 → 回车 → 刷新后路径更新
|
||
|
||
---
|
||
|
||
## 任务 8 — 端到端验证
|
||
|
||
1. 访问 `https://${NEXUS_DOMAIN}/servers.html` (添加服务器页面)
|
||
2. 填写表单、提交
|
||
3. 列表出现安装进度
|
||
4. 安装完成 → 绿点在线
|
||
5. ✏️ 编辑目标路径生效
|
||
|
||
**验证**: `mcp__multisync__run_test()` 全部通过
|