Files
Nexus/docs/design/plans/2026-05-17-server-add-optimize.md
T
Your Name c9a99f4fb3 fix: 全项目文档对齐 + 代码清理
代码修复:
- 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>
2026-05-22 08:19:56 +08:00

202 lines
6.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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_pathagent_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()` 全部通过