Files
happy-life-star/docs/superpowers/specs/2026-06-02-deploy-nginx-site-enable-fix-design.md
T

157 lines
6.7 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.
---
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 <target>`<br>若是文件/链/不存在:跳过(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 <domain>`
-`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` — 本设计文档