From 4ddaafbdef60b4b858ddf6c4e4e80847a5a0e204 Mon Sep 17 00:00:00 2001 From: Xiaoxia AI Date: Fri, 19 Jun 2026 08:39:33 +0800 Subject: [PATCH] docs(ci): establish runner infrastructure governance --- docs/CI-CD-稳定性修复专项规划-草案.md | 228 ++++++++++++++++++++++++++ docs/CI-CD.md | 9 +- docs/RUNNER-INFRASTRUCTURE.md | 164 ++++++++++++++++++ scripts/ci/check-runner.ps1 | 59 +++++++ scripts/ci/install-runner.ps1 | 41 +++++ scripts/ci/start-runner.ps1 | 21 +++ scripts/ci/stop-runner.ps1 | 9 + 7 files changed, 530 insertions(+), 1 deletion(-) create mode 100644 docs/CI-CD-稳定性修复专项规划-草案.md create mode 100644 docs/RUNNER-INFRASTRUCTURE.md create mode 100644 scripts/ci/check-runner.ps1 create mode 100644 scripts/ci/install-runner.ps1 create mode 100644 scripts/ci/start-runner.ps1 create mode 100644 scripts/ci/stop-runner.ps1 diff --git a/docs/CI-CD-稳定性修复专项规划-草案.md b/docs/CI-CD-稳定性修复专项规划-草案.md new file mode 100644 index 000000000..af7d11145 --- /dev/null +++ b/docs/CI-CD-稳定性修复专项规划-草案.md @@ -0,0 +1,228 @@ +# CI/CD 稳定性修复专项规划(草案) + +**文档状态**:草案 +**创建时间**:2026-06-19 +**适用范围**:小虾 SaaS 仓库 CI/CD 专项治理 +**专项性质**:独立专项,**不得混入 Phase 7 业务收尾提交** + +--- + +## 一、专项背景 + +在 `feature/phase7-asset-generation-alignment` 分支推进 Phase 7 业务收尾过程中,提交 `e5d5ae4` 对应的 `ci-cd.yml #239` 最终失败。 + +已确认: +- 失败点位于 `Code Quality Check` +- 当前判断更偏向 CI/CD 环境 / 开发依赖工具链可执行性异常 +- 暂未定性为本轮 Phase 7 业务代码主链缺陷 + +根据当前已确认的流程约束: + +- 本轮业务收尾**只记录问题** +- **不得边做边改 CI/CD** +- CI/CD 修复必须进入下一次**单独规划、单独执行**的专项 + +本文件即用于承接该专项。 + +--- + +## 二、专项目标 + +把当前 CI/CD 从“曾经可跑通,但不稳定”收敛为: + +1. `Code Quality Check` 稳定可执行 +2. `Run Tests` 稳定可执行 +3. `Build Summary` 触发逻辑符合预期 +4. `.gitea` / `.github` workflow 不再漂移 +5. feature 分支提交结果可被稳定信任 +6. CI 真正成为交付门禁,而不是偶尔成功的脚本 + +--- + +## 三、专项边界 + +### 只做这些 +- CI workflow 执行链路检查 +- 开发依赖安装链路检查 +- 容器 / `venv` / 工具调用方式一致性检查 +- `.gitea` 与 `.github` workflow 同步关系核查 +- 质量检查工具(如 `black` / `isort` / `flake8` / `mypy` / `bandit`)可执行性验证 +- 与 CI/CD 直接相关的文档修订 + +### 不做这些 +- 不处理 Phase 7 新业务功能 +- 不处理生成链深化 +- 不处理前端新页面开发 +- 不顺手清理无关历史代码债 +- 不把 README、测试、架构等非 CI 主问题混成一个大杂烩专项 + +--- + +## 四、当前已知问题 + +### 问题 0:runner 基础设施缺少正式纳管 +当前已确认: +- workflow 可以触发 +- run 可以入队 +- job 可长期停留在 `Waiting to run` +- 文档里虽然声明 `act_runner` 已注册并持续运行,但当前机器上缺少清晰可验证的 runner 安装位置、配置文件、日志路径和健康检查方式 +- 进一步交叉核对后,现有仓库内多处路径约定实际指向 `xiaoxia-server:/var/lib/xiaoxia-ci`,这意味着 CI 基础设施的真实宿主很可能是服务器侧,而不是当前本机 + +这说明当前问题不仅是 workflow 稳定性问题,更是 CI 执行基础设施没有正式闭环、且宿主边界未被文档明确说明的问题。 + + +### 问题 1:最新提交 `#239` 在质量检查阶段失败 +- 提交:`e5d5ae4` +- run:`ci-cd.yml #239` +- 结果:`failure` +- 失败阶段:`Code Quality Check` + +### 问题 2:CI 工具链可执行性存在疑点 +当前症状表明: +- `requirements-dev.txt` 虽已建立 +- 但质量工具链在 CI 环境中未必稳定成为可执行命令 +- “本地通过”与“CI 稳定通过”之间仍存在断层 + +### 问题 3:CI 真源与镜像副本虽已统一,但仍需持续核查 +根据环境收敛规则: +- `.gitea/workflows/ci-cd.yml` 是真源 +- `.github/workflows/ci-cd.yml` 是镜像/兼容副本 + +专项中必须再次验证两者当前是否完全一致,避免后续再次漂移。 + +--- + +## 五、专项执行原则 + +1. **文档先行** + - 先明确问题清单、修复方案、验证口径,再动配置。 + +2. **最小必要改动** + - 只改和 CI/CD 稳定性直接相关的内容。 + +3. **不混业务提交** + - 所有 CI 专项修复在独立分支完成。 + +4. **先复现再修** + - 先找出稳定复现条件,禁止凭猜测叠补丁。 + +5. **一次只修一个链路问题** + - 避免把依赖、容器、workflow、文档同时大改导致新漂移。 + +6. **修复后必须验证** + - 不能只看本地命令通过,必须看 feature 分支 CI 结果。 + +--- + +## 六、建议执行步骤 + +### Step 1:问题复盘 +输出一份问题复盘清单,至少回答: +- `#239` 失败时实际执行到了哪一步? +- 哪个命令或哪个工具最先不可用? +- 本地与 CI 的差异点有哪些? +- 是安装问题、PATH 问题、容器问题,还是 workflow 写法问题? + +### Step 2:环境链路核查 +逐项核查: +- `requirements-dev.txt` +- `.gitea/workflows/ci-cd.yml` +- `.github/workflows/ci-cd.yml` +- `container: catthehacker/ubuntu:act-latest` +- `python3 -m venv .venv` +- `python -m pip install --index-url https://pypi.org/simple -r requirements-dev.txt` +- 质量工具调用方式 + +### Step 3:形成修复方案 +修复方案必须明确: +- 改哪些文件 +- 为什么改 +- 改完如何验证 +- 是否会影响现有分支保护与门禁规则 + +### Step 4:在独立分支执行修复 +建议分支命名: +- `bugfix/ci-quality-check-stability` +- 或 `refactor/ci-toolchain-alignment` + +### Step 4.5:runner 基础设施正式纳管 +在继续追单次 workflow 结果之前,必须先完成: +- 固定 runner 安装目录 +- 固定 runner 配置文件路径 +- 固定 runner 日志目录 +- 固定 runner 启停脚本 +- 固定 runner 健康检查脚本 +- 文档与现实一致性校验 + +参考文档: +- `docs/RUNNER-INFRASTRUCTURE.md` +- `scripts/ci/check-runner.ps1` +- `scripts/ci/install-runner.ps1` +- `scripts/ci/start-runner.ps1` +- `scripts/ci/stop-runner.ps1` + +### Step 5:专项验证 +至少验证: +- `Code Quality Check` +- `Run Tests` +- `Build Summary` +- `.gitea` / `.github` 一致性 +- feature 分支提交完整 run 结果 + +### Step 6:文档回写 +专项结束后必须更新: +- `PHASE7-PROGRESS.md`(只记录状态变化) +- 专项文档本身 +- 如有必要,再更新环境收敛方案文档 + +--- + +## 七、验收标准 + +本专项完成的标准不是“我觉得差不多行了”,而是以下条件成立: + +- [ ] `Code Quality Check` 稳定通过 +- [ ] `Run Tests` 稳定通过 +- [ ] `Build Summary` 按规则正常执行 +- [ ] `.gitea` / `.github` workflow 保持一致 +- [ ] feature 分支同类提交不再复现“本地过、CI 挂” +- [ ] 本专项过程和结果已文档化 + +--- + +## 八、风险提醒 + +### 风险 1:顺手扩大范围 +最容易犯的错误是:修 CI 时顺手改业务代码、测试、文档、依赖策略,最后变成一锅粥。 + +### 风险 2:局部成功误判为稳定成功 +一次通过不代表已经稳定;必须至少经过一轮 feature 分支真实验证。 + +### 风险 3:修完未回写文档 +如果修完不更新文档,就会再次回到“规则和现实分离”的老问题。 + +--- + +## 九、建议优先级 + +**优先级:P0** + +原因: +- 它是当前最明确的交付阻塞点 +- 它会放大所有后续专项的执行成本 +- 它直接影响质量门禁是否真实有效 + +--- + +## 十、专项结论 + +当前建议非常明确: + +**CI/CD 稳定性修复专项应该作为下一轮最优先启动的独立专项。** + +不是因为它最有趣, +而是因为它最影响整个项目后续的推进质量和交付效率。 + +--- + +**建议人**:小虾 🦐 diff --git a/docs/CI-CD.md b/docs/CI-CD.md index 77b316669..06fab11f2 100644 --- a/docs/CI-CD.md +++ b/docs/CI-CD.md @@ -71,10 +71,17 @@ mypy packages/ apps/ --ignore-missing-imports ## 当前已验证结论 - Gitea Actions 已启用 -- `act_runner` 已注册并持续运行 - staging 可手工部署并已完成真实业务闭环验证 - 当前 CI/CD 的关键目标是让 Gitea push 后自动完成同机部署,而不是只保留占位 YAML +## 当前已确认风险 + +- 现有文档曾把“`act_runner` 已注册并持续运行”写成既成事实 +- 但当前机器排查结果表明,runner 基础设施缺少可观测、可管理、可验证的正式落地形态 +- 仓库中的多处路径约定又指向 `xiaoxia-server:/var/lib/xiaoxia-ci`,说明 CI 基础设施的真实宿主边界尚未在文档中说明白 +- 在 runner 被正式纳管前,不能再把“runner 已持续运行”当作默认前提 +- 统一按 `docs/RUNNER-INFRASTRUCTURE.md` 建立 runner 安装目录、配置路径、日志路径、启动方式与健康检查脚本 + --- ## 故障排查 diff --git a/docs/RUNNER-INFRASTRUCTURE.md b/docs/RUNNER-INFRASTRUCTURE.md new file mode 100644 index 000000000..fc47d91de --- /dev/null +++ b/docs/RUNNER-INFRASTRUCTURE.md @@ -0,0 +1,164 @@ +# Gitea Runner 基础设施规范 + +## 目标 + +把 CI runner 从“文档里假定存在”收敛为“可安装、可启动、可验证、可排障”的正式基础设施。 + +当前已确认的问题不是单个 workflow 命令,而是 runner 基础设施缺少可观测、可管理、可验证的落地形态,导致: + +- workflow 可以触发 +- run 可以入队 +- job 长时间停留在 `Waiting to run` +- 无法快速确认 runner 是否在线、注册、可消费队列 + +--- + +## 正式约定 + +### 安装目录 + +统一约定 runner 安装根目录: + +```text +C:\xiaoxia-ci\act_runner\ +``` + +目录结构: + +```text +C:\xiaoxia-ci\act_runner\ +├── act_runner.exe +├── config.yaml +├── .runner +├── data\ +├── work\ +├── logs\ +└── scripts\ + ├── install-runner.ps1 + ├── start-runner.ps1 + ├── stop-runner.ps1 + └── check-runner.ps1 +``` + +### 启动方式 + +统一使用 **Windows 计划任务或服务化方式** 启动,禁止依赖临时终端手工常驻。 + +最低要求: +- 开机自动启动 +- 失败可重启 +- 有固定工作目录 +- 有固定日志目录 + +### 日志目录 + +```text +C:\xiaoxia-ci\act_runner\logs\ +``` + +至少保留: +- `runner.stdout.log` +- `runner.stderr.log` +- `runner.health.log` + +### 工作目录 + +```text +C:\xiaoxia-ci\act_runner\work\ +``` + +不得把 runner 工作目录放在随机用户临时目录。 + +--- + +## 配置要求 + +### config.yaml 最低要求 + +应明确: +- Gitea 实例地址 +- runner 名称 +- labels +- workdir +- 日志输出位置 +- 容器 / shell 执行策略 + +示例字段(示意,不代表最终 token): + +```yaml +instance: + url: https://api.xiaoxiajianji.com/git + token: CHANGE_ME + +runner: + name: xiaoxia-windows-runner + labels: + - windows + - local + - xiaoxia-ci + workdir: C:\xiaoxia-ci\act_runner\work +``` + +--- + +## 健康检查标准 + +必须能通过固定命令验证以下事实: + +1. runner 进程存在 +2. runner 配置文件存在 +3. runner 工作目录存在 +4. runner 最近日志有心跳/拉取任务痕迹 +5. Gitea 新 run 不再长期停留在 `Waiting to run` + +推荐检查命令: + +```powershell +powershell -ExecutionPolicy Bypass -File C:\xiaoxia-ci\act_runner\scripts\check-runner.ps1 +``` + +--- + +## 与仓库文档的关系 + +以下历史说法在 runner 正式落地前,不能再当作既成事实: + +- `docs/CI-CD.md` 中“act_runner 已注册并持续运行” +- `docs/PHASE7-PROGRESS.md` 中“Gitea Runner 已运行” + +以后必须改成: +- 已验证 runner 基础设施状态 +- 已验证 runner 当前在线 +- 已验证 runner 可消费指定 run + +也就是: +**状态必须来自检查,不来自假设。** + +--- + +## 验收标准 + +runner 基础设施完成的标准: + +- [ ] `act_runner.exe` 有固定安装目录 +- [ ] `config.yaml` 有固定路径 +- [ ] 有固定启动脚本 +- [ ] 有固定停止脚本 +- [ ] 有固定健康检查脚本 +- [ ] 开机自动启动机制已配置 +- [ ] 日志目录固定 +- [ ] 新 run 可以被稳定消费 +- [ ] 文档中的 runner 状态表述与现实一致 + +--- + +## 当前结论 + +本专项当前真正缺的不是另一条 workflow patch, +而是 **runner 作为基础设施的正式纳管**。 + +并且根据现有仓库中的路径约定(如 `xiaoxia-server:/var/lib/xiaoxia-ci/xiaoxia-saas.git`), +runner / Gitea 的真实宿主很可能在服务器侧而非当前本机。 + +因此正式治理必须先回答一个基础问题: +**runner 到底运行在哪台机器上,并把这个事实写进文档和检查脚本。** diff --git a/scripts/ci/check-runner.ps1 b/scripts/ci/check-runner.ps1 new file mode 100644 index 000000000..5ee461d85 --- /dev/null +++ b/scripts/ci/check-runner.ps1 @@ -0,0 +1,59 @@ +$runnerRoot = 'C:\xiaoxia-ci\act_runner' +$configPath = Join-Path $runnerRoot 'config.yaml' +$runnerExe = Join-Path $runnerRoot 'act_runner.exe' +$workDir = Join-Path $runnerRoot 'work' +$logDir = Join-Path $runnerRoot 'logs' + +$results = [ordered]@{ + runnerRootExists = Test-Path $runnerRoot + runnerExeExists = Test-Path $runnerExe + configExists = Test-Path $configPath + workDirExists = Test-Path $workDir + logDirExists = Test-Path $logDir +} + +$processes = Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | + Where-Object { + $_.Name -match 'act_runner' -or + $_.ExecutablePath -eq $runnerExe -or + $_.CommandLine -match 'act_runner' + } | + Select-Object ProcessId, Name, ExecutablePath, CommandLine + +$results['runnerProcessCount'] = @($processes).Count + +$logSummary = @() +if (Test-Path $logDir) { + $logSummary = Get-ChildItem $logDir -File -ErrorAction SilentlyContinue | + Sort-Object LastWriteTime -Descending | + Select-Object -First 10 Name, LastWriteTime, Length +} + +Write-Host '=== Runner Files ===' +$results.GetEnumerator() | ForEach-Object { + Write-Host ("{0}: {1}" -f $_.Key, $_.Value) +} + +Write-Host "`n=== Runner Processes ===" +if (@($processes).Count -eq 0) { + Write-Host 'No runner process found.' +} else { + $processes | Format-Table -AutoSize +} + +Write-Host "`n=== Recent Logs ===" +if (@($logSummary).Count -eq 0) { + Write-Host 'No runner logs found.' +} else { + $logSummary | Format-Table -AutoSize +} + +if (-not $results['runnerExeExists'] -or -not $results['configExists']) { + exit 2 +} + +if (@($processes).Count -eq 0) { + exit 3 +} + +exit 0 diff --git a/scripts/ci/install-runner.ps1 b/scripts/ci/install-runner.ps1 new file mode 100644 index 000000000..08c6dd352 --- /dev/null +++ b/scripts/ci/install-runner.ps1 @@ -0,0 +1,41 @@ +param( + [Parameter(Mandatory = $true)] + [string]$RunnerToken, + + [string]$RunnerRoot = 'C:\xiaoxia-ci\act_runner', + [string]$InstanceUrl = 'https://api.xiaoxiajianji.com/git', + [string]$RunnerName = 'xiaoxia-windows-runner' +) + +$ErrorActionPreference = 'Stop' + +$runnerExe = Join-Path $RunnerRoot 'act_runner.exe' +$configPath = Join-Path $RunnerRoot 'config.yaml' +$workDir = Join-Path $RunnerRoot 'work' +$logDir = Join-Path $RunnerRoot 'logs' +$scriptDir = Join-Path $RunnerRoot 'scripts' + +New-Item -ItemType Directory -Force -Path $RunnerRoot, $workDir, $logDir, $scriptDir | Out-Null + +if (-not (Test-Path $runnerExe)) { + throw "Missing runner executable: $runnerExe" +} + +$config = @" +instance: + url: $InstanceUrl + token: $RunnerToken + +runner: + name: $RunnerName + labels: + - windows + - xiaoxia-ci + - local + workdir: $workDir +"@ + +Set-Content -Path $configPath -Value $config -Encoding UTF8 + +Write-Host "Runner config written: $configPath" +Write-Host "Next step: register/start runner with the official act_runner command for this binary version." diff --git a/scripts/ci/start-runner.ps1 b/scripts/ci/start-runner.ps1 new file mode 100644 index 000000000..4fb62b923 --- /dev/null +++ b/scripts/ci/start-runner.ps1 @@ -0,0 +1,21 @@ +$runnerRoot = 'C:\xiaoxia-ci\act_runner' +$runnerExe = Join-Path $runnerRoot 'act_runner.exe' +$configPath = Join-Path $runnerRoot 'config.yaml' +$stdoutLog = Join-Path $runnerRoot 'logs\runner.stdout.log' +$stderrLog = Join-Path $runnerRoot 'logs\runner.stderr.log' + +if (-not (Test-Path $runnerExe)) { + throw "Missing runner executable: $runnerExe" +} + +if (-not (Test-Path $configPath)) { + throw "Missing runner config: $configPath" +} + +Start-Process -FilePath $runnerExe ` + -ArgumentList "daemon --config `"$configPath`"" ` + -WorkingDirectory $runnerRoot ` + -RedirectStandardOutput $stdoutLog ` + -RedirectStandardError $stderrLog + +Write-Host 'Runner start command issued.' diff --git a/scripts/ci/stop-runner.ps1 b/scripts/ci/stop-runner.ps1 new file mode 100644 index 000000000..1f3534481 --- /dev/null +++ b/scripts/ci/stop-runner.ps1 @@ -0,0 +1,9 @@ +Get-CimInstance Win32_Process -ErrorAction SilentlyContinue | + Where-Object { + $_.Name -match 'act_runner' -or + $_.CommandLine -match 'act_runner' + } | + ForEach-Object { + Stop-Process -Id $_.ProcessId -Force + Write-Host ("Stopped runner process: {0}" -f $_.ProcessId) + }