fix: address PR #43 review feedback (M1-M3, S1)
CI / lint (pull_request) Successful in 6s
CI / test (pull_request) Successful in 8s
CI / notify-on-failure (pull_request) Successful in 1s

M1: git pull in dev dir + rsync to install dir (install dir has no .git)
M2: use asyncio.create_subprocess_exec instead of subprocess.run
M3: add repo whitelist (only sanguo/sanguo_moziplus_v2 triggers auto-deploy)
S1: notify jiangwei-infra on rsync/pm2 restart failure
This commit is contained in:
cfdaily
2026-06-12 13:29:13 +08:00
parent d76c5855f1
commit 6340c02de4
2 changed files with 67 additions and 31 deletions
+12 -6
View File
@@ -141,15 +141,21 @@ async def _handle_pull_request(payload: Dict[str, Any]) -> None:
**触发**`_handle_pr_closed` 合并事件处理完成后
**逻辑**
1. `git pull origin main`(安装目录
2. 获取 PR 变更文件列表(复用 `_fetch_pr_files`
3. 判断是否需要重启:文件路径包含 `src/``templates/``frontend/``*.py` 后缀 → 重启
4. `docs/` 变更 → 只 pull 不重启
5. 部署失败仅 log,不影响 Mail 通知
1. 仓库白名单检查(仅 `sanguo/sanguo_moziplus_v2`
2. `git pull origin main`(开发目录 `~/.openclaw/sanguo_projects/sanguo_moziplus_v2/`
3. `rsync` 同步到安装目录(排除 `.git`/`node_modules`/`__pycache__`
4. 获取 PR 变更文件列表(复用 `_fetch_pr_files`
5. 判断是否需要重启:文件路径包含 `src/``templates/``frontend/``*.py` 后缀 → 重启
6.`docs/` 变更 → 只 pull + rsync 不重启
7. rsync 或 pm2 restart 失败 → 通知 `jiangwei-infra`
8. 部署失败仅 log + Mail 通知,不影响合并通知
**设计决策**
- **git pull 在开发目录**(有 `.git`),rsync 到安装目录:安装目录无 `.git`,直接 git pull 必然失败
- **全异步**:所有子进程调用使用 `asyncio.create_subprocess_exec`,不阻塞 event loop
- **仓库白名单**:只对 `sanguo/sanguo_moziplus_v2` 触发自动部署,其他仓库忽略
- **部署失败通知**rsync 或 pm2 restart 失败时发 Mail 给 `jiangwei-infra`S1
- 不做优雅等待(sentinel file 方案):daemon 正在执行任务时重启,已 spawn 的子进程独立运行不受影响,最坏情况是当前 tick 中断、下一轮 PM2 拉起后继续
- 部署失败不阻塞通知:Mail 通知和部署解耦,保证信息触达
### 不做的事
+55 -25
View File
@@ -481,40 +481,70 @@ async def _handle_pr_closed(payload: Dict[str, Any]) -> None:
title = f"PR 已合并: {pr_title} ({repo}#{pr_number})"
_send_mail(pr_author, title, text)
# 自动部署:git pull + 按需 pm2 restart
# 自动部署:git pull + rsync + 按需 pm2 restart(仅 sanguo/sanguo_moziplus_v2
try:
import subprocess
import os
if repo != "sanguo/sanguo_moziplus_v2":
return
dev_dir = os.path.expanduser("~/.openclaw/sanguo_projects/sanguo_moziplus_v2")
install_dir = os.environ.get("SANGUO_PROJECTS_DIR", os.path.expanduser("~/.sanguo_projects"))
repo_dir = os.path.join(install_dir, "sanguo_moziplus_v2")
install_repo_dir = os.path.join(install_dir, "sanguo_moziplus_v2")
# git pull
pull_result = subprocess.run(
["git", "pull", "origin", "main"],
cwd=repo_dir, capture_output=True, text=True, timeout=30
# Step 1: git pull in dev dir
proc = await asyncio.create_subprocess_exec(
"git", "pull", "origin", "main",
cwd=dev_dir,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
if pull_result.returncode == 0:
logger.info("Auto-deploy: git pull success for %s", repo)
stdout, stderr = await asyncio.wait_for(proc.communicate(), timeout=30)
# 判断是否需要重启:获取 PR 变更文件列表
files = await _fetch_pr_files(repo, pr_number)
needs_restart = any(
f.startswith("src/") or f.startswith("templates/") or f.startswith("frontend/") or f.endswith(".py")
for f in files[0]
if proc.returncode != 0:
logger.warning("Auto-deploy: git pull failed: %s", stderr.decode())
return
logger.info("Auto-deploy: git pull success for %s", repo)
# Step 2: rsync to install dir
rsync_proc = await asyncio.create_subprocess_exec(
"rsync", "-a", "--exclude=.git", "--exclude=node_modules", "--exclude=__pycache__",
f"{dev_dir}/", f"{install_repo_dir}/",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
_, rsync_err = await asyncio.wait_for(rsync_proc.communicate(), timeout=60)
if rsync_proc.returncode != 0:
logger.error("Auto-deploy: rsync failed: %s", rsync_err.decode())
_send_mail("jiangwei-infra", f"[Auto-Deploy] rsync 失败 ({repo}#{pr_number})",
f"PR {pr_title} 合并后自动部署 rsync 失败。\n\nstderr: {rsync_err.decode()}")
return
# Step 3: 判断是否需要重启
files = await _fetch_pr_files(repo, pr_number)
needs_restart = any(
f.startswith("src/") or f.startswith("templates/") or f.startswith("frontend/") or f.endswith(".py")
for f in files[0]
)
if needs_restart:
restart_proc = await asyncio.create_subprocess_exec(
"pm2", "restart", "sanguo-moziplus-v2",
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
_, restart_err = await asyncio.wait_for(restart_proc.communicate(), timeout=15)
if needs_restart:
restart_result = subprocess.run(
["pm2", "restart", "sanguo-moziplus-v2"],
capture_output=True, text=True, timeout=15
)
if restart_result.returncode == 0:
logger.info("Auto-deploy: pm2 restart triggered (files: %s)", ", ".join(files[0][:5]))
else:
logger.error("Auto-deploy: pm2 restart failed: %s", restart_result.stderr)
if restart_proc.returncode == 0:
logger.info("Auto-deploy: pm2 restart triggered (files: %s)", ", ".join(files[0][:5]))
else:
logger.error("Auto-deploy: pm2 restart failed: %s", restart_err.decode())
_send_mail("jiangwei-infra", f"[Auto-Deploy] pm2 restart 失败 ({repo}#{pr_number})",
f"PR {pr_title} 合并后 pm2 restart 失败。\n\nstderr: {restart_err.decode()}")
else:
logger.warning("Auto-deploy: git pull failed: %s", pull_result.stderr)
logger.info("Auto-deploy: docs-only change, skip restart")
except asyncio.TimeoutError:
logger.error("Auto-deploy: timeout")
except Exception as e:
logger.error("Auto-deploy: unexpected error: %s", e)