diff --git a/docs/superpowers/specs/2026-06-02-deploy-nginx-site-enable-fix-design.md b/docs/superpowers/specs/2026-06-02-deploy-nginx-site-enable-fix-design.md new file mode 100644 index 0000000..0caab5c --- /dev/null +++ b/docs/superpowers/specs/2026-06-02-deploy-nginx-site-enable-fix-design.md @@ -0,0 +1,156 @@ +--- +author: 部署脚本修复会话 +created_at: 2026-06-02 +purpose: 修复 deploy.py 的 nginx 站点启用命令 30s 超时问题(防御性诊断 + 清理) +--- + +# 修复 deploy.py nginx 站点启用超时 — 设计文档 + +## 问题陈述 + +执行 `python deploy.py` 部署时,nginx 站点启用步骤失败: + +``` +[INFO] 启用站点配置... +[ERROR] 启用站点失败: 命令执行超时 (30s): + ssh ... root@101.200.208.45 + ln -snf /etc/nginx/sites-available/lifescript.happylifeos.com.conf + /etc/nginx/sites-enabled/lifescript.happylifeos.com.conf + && rm -f /etc/nginx/sites-enabled/default +``` + +**根因分析**(用户已确认):之前能成功部署,本次突然失败。`ln -snf` 目标在远程服务器上已存在为**目录**或**断链**(非空 symlink 指向已被删除/修改的源文件),`ln` 在某些情况下会递归进入等待而 hang。`&&` 链式让 `rm -f default` 也永不执行。30s 总超时被耗尽。 + +## 方案选择 + +经过 brainstorming 对话,用户选择 **方案 A:防御性诊断 + 清理**(修改脚本使其对异常状态自愈,不依赖手动干预)。 + +**未选方案**: +- 方案 B(仅手动清理):不解决根本问题,未来重现概率高 +- 方案 C(手动 + 脚本加固):用户已先用方案 A 的脚本修复,验证后再决定是否需要手动清理 +- 方案 D(仅增超时):绕过问题,不修复根因 + +## 设计 + +### 核心思路 + +将 `ln -snf ... && rm -f ...` 的**链式单命令**拆为**多步独立 SSH 调用**,每步: +- 独立超时(30s 对单步足够) +- 独立返回 `ok/err/stderr` +- 失败立即停止并打印详细诊断 +- 前置诊断 + 主动清理 hang 源(目录/断链) + +### 新增函数 `enable_nginx_site(remote_conf, domain)` + +签名: +```python +def enable_nginx_site(remote_conf: str, domain: str) -> bool: + """幂等地启用 nginx 站点(sites-enabled symlink)。 + + 流程: + 1. 诊断目标路径当前状态 + 2. 清理异常状态(目录 / 文件 / 断链) + 3. 创建 symlink + 4. 移除 default 站点 + 5. 验证结果 + + Returns: True 成功;False 失败(stderr 已打印) + """ +``` + +### 5 步骤流程 + +| 步骤 | SSH 命令 | 失败处理 | +|---|---|---| +| 1. 诊断 | `ls -ld /etc/nginx/sites-enabled/{domain}.conf 2>&1; echo "---"; readlink -f /etc/nginx/sites-enabled/{domain}.conf 2>&1` | 仅诊断,不失败 | +| 2. 清理 | 若诊断结果是目录:`rm -rf `
若是文件/链/不存在:跳过(rm -f 安全) | 失败时打印 stderr + 提示"权限可能不足" | +| 3. 建链 | `ln -snf {remote_conf} /etc/nginx/sites-enabled/{domain}.conf` | 失败时提示"scp_file 是否成功" | +| 4. 清默认 | `rm -f /etc/nginx/sites-enabled/default` | 失败时提示"default 文件被占用或权限不足" | +| 5. 验证 | `readlink /etc/nginx/sites-enabled/{domain}.conf && ls -la /etc/nginx/sites-enabled/` | 失败时打印实际值与期望值 | + +### 集成位置 + +修改 `G:\IdeaProjects\emotion-museun\deploy.py`: + +- **第 250-256 行**:替换 `ln -snf ... && rm -f ...` 块为 `enable_nginx_site(remote_conf, DOMAIN)` 调用 +- **第 232-266 行**(`deploy_nginx` 函数)保持其他逻辑不变(scp_file 上传 conf、nginx -t、reload) +- **新增函数** `enable_nginx_site` 放在 `deploy_nginx` 之前 + +### 错误信息改进 + +`enable_nginx_site` 内部用统一的 `log_error` 输出结构化信息: + +``` +[ERROR] 启用站点失败 - 步骤 3 建链: Permission denied +[ERROR] 上下文: 目标 = /etc/nginx/sites-enabled/lifescript.happylifeos.com.conf +[ERROR] 提示: 确认 scp_file 是否成功上传了 sites-available 下的 conf 文件 +``` + +替代当前的"命令执行超时 (30s)",便于快速定位卡在哪一步。 + +## 范围 + +### 包含 + +- 修改 `deploy.py` 的 `deploy_nginx()` 函数调用方式 +- 新增 `enable_nginx_site()` 函数(约 50-70 行) +- 错误信息结构化改进 + +### 不包含(明确排除) + +- 不修改其他 deploy.py 子脚本(backend-single/web/web-admin/life-script) +- 不改 `ssh_command()` / `run_ssh_args()` 底层 +- 不增加 `--dry-run` 模式 +- 不改 nginx 配置文件本身 +- 不改默认 30s 超时(单步足够,问题在链式 hang,不在网络慢) + +## 验证计划 + +### 单元级(脚本内) + +部署后通过以下方式验证: + +1. **干净服务器场景**:远程 `/etc/nginx/sites-enabled/{domain}.conf` 不存在 → 期望成功 +2. **断链场景**:手动 `ln -s /nonexistent /etc/nginx/sites-enabled/{domain}.conf` → 期望步骤 2 清理 + 步骤 3 重建 +3. **目录场景**:手动 `mkdir /etc/nginx/sites-enabled/{domain}.conf` → 期望步骤 2 `rm -rf` 清理 + 步骤 3 成功 +4. **文件场景**:手动 `touch /etc/nginx/sites-enabled/{domain}.conf` → 期望步骤 2 `rm -f` 清理 + 步骤 3 成功 +5. **正常 symlink 场景**:手动重建有效 symlink → 期望步骤 2 unlink + 步骤 3 重建 + +### 集成级 + +跑 `python deploy.py nginx` 完整流程,期望: +- 不再出现"启用站点失败" +- `/etc/nginx/sites-enabled/{domain}.conf` 是有效 symlink +- `/etc/nginx/sites-enabled/default` 不存在 +- `nginx -t` 通过 +- `systemctl reload nginx` 成功 + +### 验证前置 + +修复后**必须**执行 `python deploy.py nginx`(或 `python deploy.py all`)进行端到端验证,**不能仅靠代码 review**。这是用户在前次 PATH 修复中明确要求的规则(CLAUDE.md "验证规则(强制)")。 + +## 风险与回滚 + +### 风险 + +1. **远程 rm -rf 误删**:仅针对 `/etc/nginx/sites-enabled/{domain}.conf` 单个路径(已通过 `{domain}` 限定),不会影响其他站点 +2. **步骤 2 误判**:如果 `ls -ld` 输出解析错误,错误地删了文件 → 步骤 3 重新创建,整体仍自愈 +3. **SSH 连接问题掩盖**:如果 SSH 本身不通,新代码会立即在步骤 1 报告 stderr,不会比原代码更差 + +### 回滚 + +- 改动局限在 `deploy.py` 单文件、新增 1 个函数 +- 如新代码有问题,`git checkout deploy.py` 即可恢复 + +## 后续可选增强(不在本次范围) + +- `enable_nginx_site` 通用化到 `disable_nginx_site()`,支持 `deploy.py nginx disable ` +- 把 `enable_nginx_site` 提取到独立模块(`deploy_nginx_helpers.py`),便于其他 deploy.py 复用 +- 增加 `--dry-run` 模式,仅打印将执行命令不实际执行 +- 步骤级重试机制(如步骤 3 失败时自动重试 1 次) + +## 相关文件 + +- `G:\IdeaProjects\emotion-museun\deploy.py` — 主修改目标(第 232-266 行 `deploy_nginx`,新增 `enable_nginx_site`) +- `G:\IdeaProjects\emotion-museun\conf\emotion-museum.conf` — nginx 配置文件(不修改) +- `G:\IdeaProjects\emotion-museun\docs\superpowers\specs\2026-06-02-deploy-nginx-site-enable-fix-design.md` — 本设计文档