842d711ed6
用户裁定「我不想看到编号」。测试验证层级这一族的整族清退分三道并行:本笔是**代码与 CI 道** (CI 步骤与门禁输出面),代码注释面另走、文档面由文档道走。 口径(旧 → 语义名 → ASCII 段名,段名只进日志/旗标/job id/文件名): L0 静态门禁 static | L0a 钩子解释器预检 preflight | L0b 提交信息门禁 commitmsg L0c 体积门禁 size | L0.5 主机侧纯逻辑单测 host-unit | L0.5c 覆盖率 coverage - scripts/ci_local.py 43 站点:段标题与 [SUMMARY] 标签改 ASCII 段名(static: / host-unit:),函数 run_l0/run_l05 → run_static_gates/run_host_unit_tests, 收尾「已跑:」列表用段名。旗标改 --static-only / --host-unit-only,**旧旗标 --l0-only / --l05-only 保留为 argparse 别名**(肌肉记忆与既有脚本片段不断档, 实测 --help 两名列出)。 - .gitea/workflows/ci.yml 42 站点:job id l0-gates → static-gates、 l05-host-unit-tests → host-unit-tests;步骤名与 step-summary 标题改语义名; 日志名 l0-pre-commit.log → static-pre-commit.log、l05-<模块>.log → host-unit-<模块>.log;::error::/::notice:: 文案同步。yaml.safe_load 核过 两个 job id 与步骤名。 host-unit-tests;步骤名与 step-summary 标题改语义名;日志名 l0-pre-commit.log → static-pre-commit.log、l05-<模块>.log → host-unit-<模块>.log;::error::/::notice:: 文案同步。yaml.safe_load 核过两个 job id 与步骤名。 - 门禁脚本 18 站点:check_registration(登记簿理由串 7)、check_test_coverage 5、 README 8、check_generated_fresh 2、ram_baseline 2、check_size_gate 1、 ci_find_python.ps1 2、ci_runner_bootstrap.ps1 2、.pre-commit-config.yaml 2、 requirements.txt 1(保持纯 ASCII)。 - skills 7 站点:task-worktree(含 [SUMMARY] 样例两行)、mem-budget、ui-verify、 task_worktree.py 的实跑标签。 - AGENTS.md 3 站点:CI 段「只跑静态门禁 + 主机侧纯逻辑单测」、自查命令段、 「2026-10-11 静态门禁全灭」事件名。 - 归档:编号治理历史.md 新增 §10 换算表 + **撞族警示**(同形 L0/L1/L2/L3 还属 UI 设计 令牌层 / UI 菜单层级 / 采集链 / 链路时延段 / CubeMX 三层 / 硬件位号与 FTP 应答字面量 等六个族,其中 6 处在字符串字面量里,改了就是改行为——故全程只按行内置换、先判族)。 - docs/产品需求 §7.4 测试分层表:去号 + 顺带修掉上一批**错族替换疤**(该表 L1/L2 行曾被 采集链层名「语义取数与有效性 / 全周傅氏相量」「保护判据」顶替,现按内容改写为板端测试 套件 / 主机侧回归两行)。同文档 §1.5 G5 判据行同步。 门禁:ci_local --help 两面旗标并列;py_compile 7 个 .py 全过;yaml 解析通过; check_ids_new --worktree rc=0;git diff --check 干净;17 个改动文件 FFFD=0、无 CR、 BOM 与原文件一致(含 requirements.txt 纯 ASCII)。本族无机器识别面(单字母+数字是 新增行门禁的自述盲区),口径来自自建逐行扫描器,复扫本族形态归零。 - 交接说明:文档道 teammate 在改 CI 指南时中断,剩余 9 处双写疤与 29 处 CI 标识 (job id / 日志名 / 汇总行字面量)由 Lead 接手改完,逐处仍走断言式行内置换。
1373 lines
94 KiB
Markdown
1373 lines
94 KiB
Markdown
# 远程 CI 完整指南(Gitea Actions · 本机 Windows Runner)
|
||
|
||
> **适用范围(先读这一段)**:本仓库的远程 CI **只跑在本机**——runner 名 `win-stm32-ci`,
|
||
> 装在 `D:\Docker\act_runner`,连接的是**本机原生 Gitea**(`http://127.0.0.1:3001`)。
|
||
> **其他机器、其他项目都不需要、也不会用到本文这套配置**,勿照搬。
|
||
> 日常改代码的等价自查是本地跑 [`scripts/ci_local.py`](../../scripts/ci_local.py)
|
||
> (与 CI 同一套判定),**不依赖远程 CI 是否在线**——runner 掉线不影响本地门禁。
|
||
|
||
> **适用**:为 STM32F4-Base 部署 / 排障 CI(`.gitea/workflows/ci.yml`,静态门禁 + 主机侧纯逻辑单测)。
|
||
> **前置阅读**:[docs/测试说明/测试开发标准流程.md §9](../测试说明/测试开发标准流程.md)(CI 覆盖边界与判定细节)、
|
||
> [docs/环境与部署/项目部署环境要求.md §2.11](../环境与部署/项目部署环境要求.md)(runner 环境要求)。
|
||
> 本文只讲**怎么配**,不重复"为什么这么设计"。
|
||
> 本篇由原两篇 CI 指南 `CI配置指南.md` 与 `Gitea_Runner部署指南.md`(原位于「环境与部署」目录,
|
||
> **两篇均已删除**)于 2026-10-12 合并而成,去重后为单一来源。
|
||
|
||
> **适用环境(2026-10-02 实测确认)**:
|
||
> - Gitea **1.25.4**,**Windows 原生二进制**(`D:\Docker\Gitea\gitea.exe`,**不是 Docker**),
|
||
> 以 **Windows 服务**运行(`tasklist` 显示 Session = `Services`),监听 `0.0.0.0:3001`
|
||
> - 配置 `D:\Docker\Gitea\custom\conf\app.ini`,`[actions] ENABLED = true` **已开启** ✅
|
||
> - 数据库 MySQL(`127.0.0.1:3306`),仓库根 `D:\Docker\Gitea\data\gitea-repositories`
|
||
> - 对外域名 `http://gitea.bitnasdaq.vip/`(经反代),本地直连 `http://127.0.0.1:3001` ✅
|
||
> - Docker Desktop **已装但 daemon 未运行** → **本文不使用容器,全程原生**
|
||
|
||
> ⚠️ **重要版本变更(2026-10-02 实测)**:act_runner 已更名为 **`gitea-runner`**,
|
||
> 当前最新版 **v4.0.1**(发布页 `https://gitea.com/gitea/runner/releases`,
|
||
> 注意仓库路径已从 `gitea/act_runner` 变为 **`gitea/runner`**)。
|
||
> **v4 移除了 `daemon install/start/stop/status` 子命令**(那些是 v0.2.x 的接口)。
|
||
> 另:v4 的 `log` 段只有 `level` 和 `job`(**无 `file` 键**),配置用
|
||
> `gitea-runner config init / set / get / generate` 管理。
|
||
|
||
> 🔴 **常驻方式修正(2026-10-02 实测推翻)**:`gitea-runner daemon` 是**纯控制台程序**,
|
||
> **不支持作为 Windows 服务运行**(二进制内无服务管理代码,也无 `service.go`);
|
||
> 用 `sc.exe` / `New-Service` 注册后 `Start-Service` 必然失败。
|
||
> **正确方式是 Windows 任务计划程序** —— 见 **§3**;且 2026-10-11 实测进一步要求
|
||
> 在计划任务之上加**看门狗循环重拉** —— 见 **§2.4**。
|
||
|
||
---
|
||
|
||
## 0.0 ⚠️ CI 体积门禁:从盲区到同码凭证强制(必读,「体积门禁 CI 强制路径」残余已销项)
|
||
|
||
> **曾经的盲区已关闭为强制路径(2026-10-25,「体积门禁 CI 强制路径」残余销项,`task/A5-size-gate`→564715c)。**
|
||
> `mem-numbers` 钩子的事实源是 Keil 构建产物 `MDK-ARM/**/*.map`(已 gitignore),CI 刻意不跑
|
||
> `@build` → 该钩子在远端只会被跳过(现以 `SKIP` 名单诚实显示 Skipped,不再伪装 Passed)。
|
||
> 补上的强制路径 = **同码凭证**:本地 `@build` 0/0 后 `ram_baseline.py --update` 顺手盖
|
||
> `scripts/size_attestation.json`(三道前置:`.map` 可解析 / `.map` 不早于指纹内源码 mtime /
|
||
> 受管区 ≤92%);CI 无需 Keil,静态门禁作业末尾的「体积门禁」步用 `scripts/check_size_gate.py --ci`
|
||
> 复核两件事——**工作树指纹 == 凭证指纹**(改了代码从没测过 = 拦)+ **按凭证数字复跑 92% 红线**。
|
||
> 主干 push 拦红,其余事件 `--warn-only`;本地 `ci_local.py` 的 静态门禁c 步无条件 fail-closed。
|
||
>
|
||
> 仍守不住的三项(正文唯一口径 = `scripts/check_size_gate.py` docstring):CI 不重算绝对数字
|
||
> (只复核同码性与红线复算);编译器选项(`.uvprojx`)不在指纹内;凭证由本地真实构建产生,
|
||
> 链路本身不经 Keil 授权就无法伪造,但"盖戳后换机器环境"的差异只能靠 mtime 前置兜。
|
||
>
|
||
> 本地兜底动作不变(每次 `@build` 后):`ram_baseline.py --update`(含盖戳)+
|
||
> `check_mem_numbers.py --scan`。若 runner 以后装了 Keil,`mem-numbers` 会在有 `.map` 时照常执行。
|
||
|
||
---
|
||
|
||
## 0. 全景:CI 需要三样东西就位
|
||
|
||
```
|
||
① Gitea 服务端开关 ② Windows Runner 注册 ③ 仓库里的 workflow 文件
|
||
[actions] ENABLED=true labels 含 windows .gitea/workflows/ci.yml
|
||
│ │ │
|
||
└────────────┬───────────┴──────────────────────────────┘
|
||
▼
|
||
push / PR 触发 → runner 拉起容器/壳 → 跑 静态门禁 + 主机侧纯逻辑单测 → 结果回显到提交
|
||
```
|
||
|
||
**三者缺一,CI 就是空转的**(workflow 存在但永不执行 / 执行但拉不到 runner)。
|
||
当前仓库状态:**③ 已就绪(本机提交 `e904865`,尚未 push);① ② 需人工确认。**
|
||
|
||
### 0.1 本机拓扑:Gitea 和 Runner 在同一条机器上
|
||
|
||
**Gitea 和 Runner 在同一条机器上** —— 这是本部署最关键的事实:
|
||
|
||
```
|
||
┌─────────────────────────── 本机 (Windows) ────────────────────────────┐
|
||
│ │
|
||
│ gitea.exe ── Windows 服务 ── 监听 0.0.0.0:3001 │
|
||
│ ▲ │
|
||
│ │ ① runner 注册 / 心跳 / 领任务:走 http://127.0.0.1:3001 │
|
||
│ │ (本地回环,不经公网、不依赖 DNS/反代) │
|
||
│ │ │
|
||
│ gitea-runner.exe ── 计划任务 GiteaActionsRunner(win-stm32-ci) │
|
||
│ │ │
|
||
│ └─ ② 领到 job 后,直接在**本机**跑: │
|
||
│ git fetch / pre-commit / cppcheck / cl.exe │
|
||
│ (标签 windows:host = 用宿主环境,非容器) │
|
||
│ │
|
||
│ 外部用户 → https://gitea.bitnasdaq.vip → 反代 → 127.0.0.1:3001 │
|
||
└───────────────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
**两个地址要分清**(最容易搞错的地方):
|
||
|
||
| 用途 | 地址 | 说明 |
|
||
|---|---|---|
|
||
| **Runner 连接 Gitea** | `http://127.0.0.1:3001` | 本机回环,**最稳**。用 `gitea.bitnasdaq.vip` 也行,但会多绕一圈 DNS + 反代,且若反代没配对内网 IP 会失败 |
|
||
| **用户在浏览器访问** | `https://gitea.bitnasdaq.vip` | 走反代;注册时网页上显示的示例命令用的是这个域名,**你要手动换成 127.0.0.1:3001** |
|
||
|
||
> ⚠️ **注册时用哪个地址,决定了 runner 以后一直连哪里**。
|
||
> 注册信息存在 `.runner` 里,之后改起来要重新注册,**建议一开始就用 `http://127.0.0.1:3001`**。
|
||
|
||
### 0.2 部署前置清单(需人工确认)
|
||
|
||
> 2026-10-12 自 `docs/测试说明/测试开发标准流程.md` §9.4 并入——这几项是"CI 能不能跑起来"的前提,
|
||
> 属于机器侧知识,故与本文其余内容放在一起,不再散在测试流程文档里。
|
||
|
||
| 项 | 要求 |
|
||
|---|---|
|
||
| Gitea Actions | 实例开启 `[actions] ENABLED = true`(site admin 配置),见 §1 |
|
||
| Windows runner | 已注册且 **labels 含 `windows`**;装有 Python 3.x、cppcheck、pre-commit、MSVC(VS2019/2022 C++ 工具链 + Windows SDK) |
|
||
| **runner 必须有看门狗** | `gitea-runner` v4 的 `daemon` **无服务支持、且首次 Declare 失败就直接退出进程** → Gitea 重启一次,CI 就静默停摆(2026-10-11 实测:run 16~21 共 12 个 job 零日志)。用 **`scripts/ci_runner_bootstrap.ps1`** 装"等 Gitea 就绪 + 崩溃自拉起"的看门狗 + 计划任务,见 §2.4 |
|
||
| 机器级工具变量 | runner 以 **SYSTEM** 运行,看不见交互用户的 PATH/用户级 site。用 `ci_runner_bootstrap.ps1 -SetEnv` 写入 **MACHINE** 级 `DTU_PRECOMMIT_PYTHON` / `DTU_CPPCHECK` / `DTU_MSVC_CL`,见 §4.1.3 |
|
||
| 缺 MSVC 时 | 主机侧纯逻辑单测降级为 WARNING 跳过(不硬失败),但**等于该层裸奔**,须尽快补装 |
|
||
|
||
---
|
||
|
||
## 1. 服务端:开启 Gitea Actions
|
||
|
||
### 1.1 确认版本与开关
|
||
|
||
Gitea **≥ 1.21** 才内置 Actions(实测本实例 **1.25.4**,满足)。以 **site admin** 登录后:
|
||
|
||
- 管理后台 → **站点管理 → 配置 / Site Administration → Configuration**,搜索 `actions`:
|
||
|
||
```ini
|
||
[actions]
|
||
ENABLED = true
|
||
```
|
||
|
||
### 1.2 编辑配置文件(推荐,永久生效)
|
||
|
||
配置文件位置(按部署方式二选一):
|
||
|
||
| 部署方式 | 配置文件路径 |
|
||
|---|---|
|
||
| 二进制 / 直接跑 | `<gitea 目录>/custom/conf/app.ini` |
|
||
| Docker | 容器内 `/data/gitea/conf/app.ini`(挂载卷里的 `custom/conf/app.ini`) |
|
||
|
||
在 `app.ini` 追加(若无 `[actions]` 段):
|
||
|
||
```ini
|
||
[actions]
|
||
ENABLED = true
|
||
```
|
||
|
||
改完 **重启 Gitea 服务**(Docker:`docker restart <容器名>`;二进制:重启进程)。
|
||
|
||
#### 1.2.1 `DEFAULT_ACTIONS_URL` 是什么(**容易搞反**)
|
||
|
||
它**不是你的 Gitea 地址**,而是控制 **`uses: actions/checkout@v4` 这类 action 从哪里下载**:
|
||
|
||
| 取值 | 行为 |
|
||
|---|---|
|
||
| **不配(默认)** | 从 **[github.com](https://github.com)** 拉取 action |
|
||
| `self` | 从**你自己的 Gitea 实例**拉取(要求 action 已镜像进你的实例) |
|
||
| 具体 URL(如 `https://gitea.com`) | 从该地址拉取 |
|
||
|
||
> ⚠️ **不要把你的实例域名写进去当普通 URL**——要"从自己实例拉"就用关键字 **`self`**;
|
||
> 且**写了 `self` 还得先把 action 镜像进自己实例**,否则 `actions/checkout` 依然拉不到。
|
||
|
||
**按你的网络环境选择**:
|
||
|
||
| 场景 | 配置 |
|
||
|---|---|
|
||
| 服务器**能**访问 github.com | **什么都不配**(默认走 github.com,最省事) |
|
||
| 内网,**不能**访问 github.com | 建议**改用零依赖写法**(见本文 §5.3 方案 3),`DEFAULT_ACTIONS_URL` 配不配都行;<br>若坚持用 `actions/checkout@v4`,则需 `DEFAULT_ACTIONS_URL = self` **且**把该 action 镜像进实例 |
|
||
|
||
> Gitea 官方 FAQ 原文:*"From `https://github.com` by default, or from your own instance
|
||
> when `[actions].DEFAULT_ACTIONS_URL` is `self`."* 使用别处 action 也可在 workflow 里直接写全路径:
|
||
> `uses: https://gitea.com/owner/repo@ref` / `uses: self:owner/repo@ref`。
|
||
|
||
### 1.3 验证
|
||
|
||
浏览器打开仓库地址,顶部/侧栏应出现 **Actions** 标签页。
|
||
若仍提示"此仓库未启用 Actions",说明开关没生效,回到 1.1 检查。
|
||
|
||
> ⚠️ 若实例装在**内网且不能访问公网**,`actions/checkout@v4` 这类官方 action 会拉不下来。
|
||
> 处置见本文 §5.3。
|
||
|
||
---
|
||
|
||
## 2. Runner 端:注册 Windows Runner
|
||
|
||
CI 的两个 job 都是 `runs-on: windows`,所以**必须有一个 labels 含 `windows` 的 runner**。
|
||
推荐用 `act_runner`(Gitea 官方 runner)。
|
||
|
||
### 2.1 获取注册令牌
|
||
|
||
- **仓库级 runner**(推荐,只服务本仓库):
|
||
仓库 → **设置 / Settings → Actions → Runners → 创建 Runner**,复制页面显示的
|
||
**Registration Token**(形如 `xxxYYYzzz...`)。
|
||
- **全局 runner**(服务整个实例):站点管理 → Actions → Runners。
|
||
|
||
记下 Gitea 地址,例如 `https://gitea.bitnasdaq.vip`。
|
||
|
||
本机(原生 Gitea)的具体操作路径:
|
||
|
||
1. 浏览器登录 `https://gitea.bitnasdaq.vip`
|
||
2. 进仓库 **`wandering1/STM32F4-Base`**
|
||
3. **Settings(设置)→ Actions → Runners → 创建 Runner / Create new Runner**
|
||
4. 页面会显示一段命令,形如:
|
||
|
||
```
|
||
./gitea-runner register --no-interactive --instance https://gitea.bitnasdaq.vip --token xxxxxxxxxxxxxxxxxxxx
|
||
```
|
||
|
||
**复制其中的 `--token` 后面那一串**(这是 Registration Token,一次性用途)。
|
||
页面上的 `--instance` 值**不要照抄**——见 §2.2.3。
|
||
|
||
> 💡 **Token 存在于 `action_runner_token` 表**(MySQL,见本文 §6.1)。
|
||
> 每仓库一个有效 token,**注册一次后即被消费**;过期(约 1 小时)或已用则需重新创建。
|
||
|
||
### 2.2 下载并注册 runner
|
||
|
||
**本机现状(唯一口径)**:二进制 **`gitea-runner.exe` v4.0.1**(实测 20,623,872 B ≈ 20.6 MB),
|
||
装在 **`D:\Docker\act_runner`**,注册 `--instance` 用 **`http://127.0.0.1:3001`**。
|
||
具体步骤见 §2.2.1 起,命令集与字段语义见 §2.2.2 / §2.3。
|
||
|
||
> 🗑 **历史备查,勿照抄**:本文初版写的是 `act_runner` **v0.2.11** + `C:\act_runner` +
|
||
> `--instance https://gitea.bitnasdaq.vip`。三点均已过时,且**本机从未这样部署过**
|
||
> (2026-10-12 核实:`C:\act_runner` 至今不存在):
|
||
> ① v0.2.x 与 v4 的**二进制名与命令集都不同**(v4 已无 `daemon install/start/stop/status`,见 §3.0);
|
||
> ② 目录不同;③ `--instance` 填公网域名会多绕一圈 DNS + 反代,反代没配对内网 IP 就直接失败。
|
||
> **照抄那段会装出一个连不上 Gitea 的 runner**,故原命令块已删除,只留此警示。
|
||
|
||
**关键点**:`--labels "windows:host"` 中的 `windows` 必须**逐字**与 workflow 的
|
||
`runs-on: windows` 对应(Gitea runner 的 label 名是自定义的,默认是 `ubuntu-latest`,
|
||
**不注册 `windows` 这个 label 就永远匹配不到 job**)。
|
||
|
||
> `:host` 表示直接跑在宿主 Windows 上(不是容器)。本场景**必须用 `host`**——
|
||
> 容器里拿不到宿主已装的 Keil/MSVC/cppcheck,且 Windows 容器支持很差。
|
||
|
||
#### 2.2.1 选对版本并下载 gitea-runner
|
||
|
||
| 项 | 值 |
|
||
|---|---|
|
||
| 架构 | **amd64**(`PROCESSOR_ARCHITECTURE = AMD64`,实测) |
|
||
| 版本 | **v4.0.1**(2026-10-02 实测最新稳定版) |
|
||
| 下载页 | https://gitea.com/gitea/**runner**/releases ← 注意仓库名已从 `act_runner` 改为 `runner` |
|
||
| 文件名 | `gitea-runner-4.0.1-windows-amd64.exe` ← 二进制名也从 `act_runner` 改为 **`gitea-runner`** |
|
||
|
||
```powershell
|
||
# 建目录(与 Gitea 同级,便于管理)
|
||
New-Item -ItemType Directory -Force -Path "D:\Docker\act_runner" | Out-Null
|
||
cd "D:\Docker\act_runner"
|
||
|
||
# 下载 v4.0.1(版本号请替换为 releases 页当前最新)
|
||
Invoke-WebRequest -Uri "https://gitea.com/gitea/runner/releases/download/v4.0.1/gitea-runner-4.0.1-windows-amd64.exe" `
|
||
-OutFile "gitea-runner.exe"
|
||
|
||
# 校验能跑
|
||
.\gitea-runner.exe --version
|
||
```
|
||
|
||
**期望输出**:`gitea-runner version v4.0.1`。
|
||
|
||
> 💡 **本机实测**(2026-10-02):下载 20.6 MB,耗时约 5 秒,`gitea.com` 可直连(`HTTP 200`)。
|
||
|
||
#### 2.2.2 v4.0.1 的命令集(与 v0.2.x 的差异)
|
||
|
||
```
|
||
Available Commands:
|
||
bug-report Print information useful when filing a bug report
|
||
cache-server Start a cache server for the cache action
|
||
config Generate, read and edit config files
|
||
daemon Run as a runner daemon
|
||
exec Run workflow locally.
|
||
help Help about any command
|
||
register Register a runner to the server
|
||
```
|
||
|
||
> ⚠️ **没有 `daemon install/start/stop/status`** —— 这些是 v0.2.x 的接口,v4 已移除。
|
||
> 常驻必须用 Windows 原生方式(见 §3)。
|
||
>
|
||
> `config` 子命令很方便,建议优先用它而不是手改 YAML:
|
||
> `config init`(生成最小配置)/ `config generate`(打印全部选项)
|
||
> / `config set <key> <value>` / `config get <key>`。
|
||
|
||
#### 2.2.3 执行注册(关键:instance 用 127.0.0.1)
|
||
|
||
```powershell
|
||
cd "D:\Docker\act_runner"
|
||
|
||
.\gitea-runner.exe register `
|
||
--no-interactive `
|
||
--instance "http://127.0.0.1:3001" `
|
||
--token "粘贴刚才复制的 Registration Token" `
|
||
--name "win-stm32-ci" `
|
||
--labels "windows:host"
|
||
```
|
||
|
||
**四个参数逐一说明**:
|
||
|
||
| 参数 | 值 | 为什么 |
|
||
|---|---|---|
|
||
| `--instance` | `http://127.0.0.1:3001` | **本机回环**。因为 Gitea 就在这台机器上,回环最稳、最快、不受反代/DNS 影响 |
|
||
| `--token` | 网页复制的那串 | 绑定到具体仓库(仓库级 runner) |
|
||
| `--name` | `win-stm32-ci` | runner 显示名,随意但建议有辨识度 |
|
||
| `--labels` | **`windows:host`** | 🔴 **最容易踩的坑**:`windows` 必须与 workflow 的 `runs-on: windows` **逐字一致**;`:host` 表示用**宿主环境**直接跑(非容器)——本场景**必须 host**,容器里拿不到 MSVC/cppcheck |
|
||
|
||
> ⚠️ **`--labels` 写错的后果**:runner 会注册成功、显示绿色,但**任务永远排队不执行**
|
||
> (没有任何 runner 匹配 `runs-on: windows`)。这是最隐蔽的失败模式。
|
||
|
||
**期望输出**:`Runner registered successfully`,并在 `D:\Docker\act_runner\` 生成 `.runner` 文件(记录注册凭据)。
|
||
|
||
#### 2.2.4 验证注册
|
||
|
||
浏览器刷新 **Settings → Actions → Runners**,应看到:
|
||
|
||
| 列 | 期望值 |
|
||
|---|---|
|
||
| Name | `win-stm32-ci` |
|
||
| Labels | `windows`(`:host` 是执行方式,UI 可能只显示 `windows`) |
|
||
| Status | 🟢 **Idle** |
|
||
|
||
### 2.3 配置 `config.yaml`(可选但建议)
|
||
|
||
`D:\Docker\act_runner\config.yaml` 里确认/补充(本机完整落地版见 §2.3.2):
|
||
|
||
```yaml
|
||
runner:
|
||
labels:
|
||
- "windows:host" # 与 register 一致
|
||
capacity: 1 # 并行 job 数;单机建议 1,避免 cppcheck 抢 CPU
|
||
timeout: 30m0s
|
||
fetch_timeout: 1m0s # 🔴 不得超过 RPC timeout(1m0s),否则被自动截断并告警
|
||
cache:
|
||
enabled: true
|
||
```
|
||
|
||
#### 2.3.1 用 `config` 子命令管理(v4 推荐)
|
||
|
||
**推荐用 `config` 子命令生成/修改**(v4 新增,比手改 YAML 安全):
|
||
|
||
```powershell
|
||
cd "D:\Docker\act_runner"
|
||
.\gitea-runner.exe config init # 生成最小 config.yaml
|
||
.\gitea-runner.exe config set runner.capacity 1 # 逐项设置
|
||
.\gitea-runner.exe config set runner.timeout 30m
|
||
.\gitea-runner.exe config set cache.enabled true
|
||
.\gitea-runner.exe config set log.level info
|
||
.\gitea-runner.exe config get runner.capacity # 校验
|
||
.\gitea-runner.exe config generate # 打印全部可用选项(含注释)
|
||
```
|
||
|
||
#### 2.3.2 本机已落地的配置与字段语义
|
||
|
||
**本机已落地的配置**(`D:\Docker\act_runner\config.yaml`):
|
||
|
||
```yaml
|
||
log:
|
||
# trace/debug/info/warn/error/fatal/panic;排障时临时改 debug
|
||
level: info
|
||
# 每个 job 的任务日志另存一份到本地(防上报 Gitea 失败时日志丢失)
|
||
job:
|
||
dir: "D:\\Docker\\act_runner\\log\\job"
|
||
retention: 168h
|
||
max_size: 100MB
|
||
|
||
runner:
|
||
file: .runner # 注册凭据(由 register 生成,勿手改)
|
||
capacity: 1 # 并行 job 数;单机建议 1(cppcheck 吃 CPU)
|
||
timeout: 30m0s # 单 job 超时;静态门禁+主机侧纯逻辑单测约 1~3 分钟
|
||
fetch_timeout: 1m0s # 🔴 不得超过 RPC timeout(1m0s):原写 5m0s 会被自动截断并刷告警,2026-10-02 已改
|
||
fetch_interval: 2s
|
||
# 🔴 标签:windows 必须与 workflow 的 runs-on: windows 逐字一致
|
||
labels:
|
||
- "windows:host"
|
||
|
||
cache:
|
||
enabled: true
|
||
dir: "D:\\Docker\\act_runner\\cache"
|
||
|
||
container:
|
||
network: ""
|
||
privileged: false
|
||
options: ""
|
||
valid_volumes: []
|
||
docker_host: ""
|
||
force_pull: false
|
||
```
|
||
|
||
> ⚠️ **v4 的 `log` 段只有 `level` 和 `job` 两个键** —— **没有 `file` 键**
|
||
> (`config set log.file ...` 会报 `unknown config key`)。
|
||
> 想要本地日志用 `log.job.dir`(按 job 落文件),或把服务输出重定向到文件。
|
||
>
|
||
> ⚠️ **改了 `runner.labels` 必须重启 runner 才生效**
|
||
> (Gitea 官方 FAQ:*"A runner declares its labels every time it starts ... so change them there and restart the runner."*)
|
||
|
||
### 2.4 常驻方式:**必须用看门狗,不能只起 daemon**(2026-10-11 实测重写)
|
||
|
||
> 🔴 **`daemon install` 在 v4 已不存在**:本机 runner 是 `gitea-runner v4.0.1`,命令集只有
|
||
> `bug-report / cache-server / config / daemon / exec / register`,**没有任何 Windows 服务
|
||
> 管理代码**(`daemon` 是纯控制台程序,无法向 SCM 回报 `SERVICE_RUNNING`)。
|
||
> 旧版本文写的 `act_runner.exe daemon install` 在本机**不可用**。
|
||
|
||
> 🔴🔴 更关键的坑:**`daemon` 在首次 Declare 失败时直接退出进程**(不重试)。
|
||
> runner 日志(`D:\Docker\act_runner\log\runner-stdout.log`)实测:
|
||
> ```
|
||
> level=error msg="fail to invoke Declare" error="unavailable:
|
||
> dial tcp 127.0.0.1:3001: connectex: ... actively refused it."
|
||
> Error: unavailable: dial tcp 127.0.0.1:3001 ...
|
||
> ```
|
||
> 之后**没有**任何重试记录——进程已经没了。于是 **Gitea 重启一次 / 机器重启一次,
|
||
> CI 就静默停摆**:2026-10-08 起 runner 掉线,run **16~21 共 12 个 job 一行日志都没产生**;
|
||
> 又因 `ci.yml` 的 `concurrency.cancel-in-progress: true`,队列里的 run 会被**下一次 push 直接取消**
|
||
> (16~20 = `cancelled`、21 = 长期 `queued`)——症状看起来是"任务凭空消失",而不是"任务失败",
|
||
> 极难从 UI 反推根因。
|
||
|
||
> 🔴 **为什么"已经有计划任务"仍然会死**(2026-10-11 实测,别以为注册过就安全):
|
||
> 旧任务跑的是 `run_runner.cmd`,里面是**单次** `gitea-runner.exe daemon ...`(无循环、无重启语义)。
|
||
> 开机时 Gitea 往往还没起来 → daemon 首次 Declare 失败**立刻退出** → 任务"成功结束" →
|
||
> **计划任务认为一切正常,永远不会再拉**。日志里 10-09 18:50 连续三次
|
||
> `Starting runner daemon` + 三次 Declare 失败,就是三次"起了就死、死了没人管"。
|
||
> 所以必须换成**看门狗**(等就绪 + 循环重拉),单靠计划任务的"失败重启"救不了
|
||
> ——daemon 是"正常退出",不是"失败"。
|
||
|
||
**正确做法:一条命令装好"等 Gitea 就绪 + 崩溃自拉起 + 开机自启"**(管理员权限,仓库根目录):
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -SetEnv -StartNow
|
||
```
|
||
|
||
它做三件事(细节见脚本头注释):
|
||
|
||
| 动作 | 内容 |
|
||
|---|---|
|
||
| ① 机器级工具变量 | 按 CI 同一套探测写 **MACHINE** 级 `DTU_PRECOMMIT_PYTHON` / `DTU_CPPCHECK` / `DTU_MSVC_CL`(已存在则跳过,`-Force` 才覆盖) |
|
||
| ② 看门狗启动器 | 生成 `D:\Docker\act_runner\run_runner_watchdog.ps1`:先轮询 `<GiteaUrl>/api/v1/version` 直到 Gitea 应答,再起 `daemon`;daemon 退出 → 记日志 → 10s 后重拉,**永不放弃** |
|
||
| ③ 计划任务 | `GiteaActionsRunner`:开机 +1 分钟触发、以 **SYSTEM** + 最高权限运行、**运行时限=不限**、失败每 1 分钟重启(×999)、不允许多实例 |
|
||
|
||
**先干跑,再真装**(不需要管理员,把 action/trigger/settings 全构建一遍并打印,什么都不写):
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -DryRun
|
||
```
|
||
|
||
随时体检(只读,不改任何东西):
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -Status
|
||
```
|
||
|
||
输出会告诉你:计划任务是否存在(含上次运行时间与返回码)、daemon 是否在跑、Gitea 是否可达、
|
||
三个机器级变量是否已设。
|
||
|
||
> ⚠️ **`-Status` 要用管理员跑**:非管理员会话**不一定看得见** `GiteaActionsRunner`
|
||
> (本次排障就被这一点误导过一次,以为任务从未注册)。脚本在非管理员下会额外提示这一点。
|
||
|
||
> 🔴 **改这个脚本时守住两条**(都是踩出来的):
|
||
> ① `StartWhenAvailable` 是**任务设置**属性(`MSFT_TaskSettings`,被 `MSFT_TaskSettings3` 继承),
|
||
> **触发器上没有这个属性**——写 `$trigger.StartWhenAvailable` 会抛 `SetValueInvocationException`
|
||
> 中断脚本(实测,且 CIM 属性列表里看不到它,只能直接赋值);
|
||
> ② **必须"先构建全部对象、后注销旧任务"**——早期版本先 `Unregister` 再构建,一旦构建阶段抛异常,
|
||
> 旧任务已被删、新任务没建成,CI 直接裸奔。
|
||
|
||
注册成功后,回到 **仓库 → Settings → Actions → Runners**,应看到 `win-stm32-ci`
|
||
状态为 **Idle / 绿色**。
|
||
|
||
> ⚠️ **`.runner` 里的 `address` 是 runner 实际连接的地址**(本机 `http://127.0.0.1:3001`),
|
||
> 与 `ROOT_URL`(`http://gitea.bitnasdaq.vip/`)不是一回事;bootstrap 会以 `.runner` 为准。
|
||
|
||
---
|
||
|
||
## 3. 常驻底层:为什么不能用 Windows 服务 + 任务计划程序手动方式
|
||
|
||
> 本节是 2026-10-02 的实测记录("不能用 Windows 服务"的根因)与任务对象字段说明,
|
||
> 保留作根因依据与手动排障参考。**正式部署一律用 §2.4 的 `scripts/ci_runner_bootstrap.ps1`**:
|
||
> §3.2 的旧脚本 `deploy_runner_task.ps1` 生成的是**单次** daemon,2026-10-11 实测证明它
|
||
> 首次 Declare 失败就退出、死了没人管;§3.3 已改写为与现行 bootstrap **等价**的看门狗任务
|
||
> 对象,只用于理解字段或在别的机器上手搓。
|
||
|
||
### 3.0 🔴 关键结论:**不能用 Windows 服务**
|
||
|
||
> **本条为 2026-10-02 实测推翻既有假设后的修正结论,务必先读。**
|
||
|
||
先前文档(及 `install_service.ps1`)主张用 `sc.exe` / `New-Service` 把
|
||
`gitea-runner daemon` 注册成 Windows 服务。**实测不可行**,`Start-Service` 必然失败:
|
||
|
||
```
|
||
Start-Service : 由于以下错误无法启动服务"Gitea Actions Runner (gitea-runner)" ...
|
||
```
|
||
|
||
**根因(对 `gitea-runner.exe` v4.0.1 二进制逆向确认)**:
|
||
|
||
| 证据 | 结论 |
|
||
|---|---|
|
||
| 命令集只有 `bug-report` / `cache-server` / `config` / `daemon` / `exec` / `register`(对应源码 `internal/app/cmd/{bug_report,cache-server,cmd,config,daemon,exec,register}.go`) | **没有 `service.go`,没有任何服务管理子命令** |
|
||
| `daemon --help` 仅 `--labels` / `--once` 两个 flag | daemon 无服务模式开关 |
|
||
| 二进制内虽出现 `OpenSCManagerW` / `CreateServiceW` / `StartServiceCtrlDispatcherW` 等符号 | 它们来自 **Docker SDK**(容器作业路径),**不是** runner 的服务安装代码——切勿据此误判 |
|
||
|
||
**`daemon` 是纯控制台程序**:它从不调用 `StartServiceCtrlDispatcher`,
|
||
因此 SCM 启动它后收不到 `SERVICE_RUNNING` 回报,判定超时 → 报错 1053。
|
||
|
||
**正确做法:Windows 任务计划程序(Task Scheduler)。**
|
||
它是控制台程序常驻的标准手段,功能上等价于服务:
|
||
|
||
| 服务能做的 | 任务计划程序对应能力 |
|
||
|---|---|
|
||
| 开机自启 | 触发器 `-AtStartup`(+ 延迟 30s 让 Gitea 先起) |
|
||
| 不登录也运行 | 以 `SYSTEM` 账户运行 |
|
||
| 最高权限 | `-RunLevel Highest` |
|
||
| 崩溃自动重启 | `-RestartCount 999 -RestartInterval 1min` |
|
||
| 开机/手动启停 | `Enable/Disable/Start/Stop-ScheduledTask` |
|
||
|
||
### 3.1 前台先试跑(强烈建议)
|
||
|
||
**先别急着常驻**——用前台模式确认能连上:
|
||
|
||
```powershell
|
||
cd "D:\Docker\act_runner"
|
||
.\gitea-runner.exe daemon --config .\config.yaml
|
||
```
|
||
|
||
看到这两行即成功:
|
||
|
||
```
|
||
level=info msg="Starting runner daemon"
|
||
level=info msg="runner: win-stm32-ci, with version: v4.0.1, with labels: [windows], declare successfully"
|
||
```
|
||
|
||
**让这个窗口开着**,去 Gitea 页面确认 runner 是 Idle,然后 Ctrl+C 停掉、再做常驻。
|
||
|
||
### 3.2 🗑 已废弃:`deploy_runner_task.ps1`(2026-10-02 方案,被 §2.4 推翻)
|
||
|
||
**不要再用它部署。** 它生成的 `run_runner.cmd` 跑的是**单次** `gitea-runner.exe daemon`
|
||
(无循环、无重启语义),而 §2.4 已实测证明 daemon 首次 Declare 失败就**正常退出**——
|
||
计划任务把它记成"成功结束",`-RestartCount` 永不触发,于是 Gitea 重启一次 CI 就永久停摆。
|
||
|
||
脚本流程(供辨认用):提权检查 → 前置文件检查 → 清理旧 Windows 服务 → 生成 `run_runner.cmd`
|
||
→ `Register-ScheduledTask`(开机+登录双触发器、SYSTEM、最高权限、崩溃重启)→ 启动 → 校验存活。
|
||
它的"崩溃重启"参数看着齐全,但**对"正常退出"完全无效**——这正是它失效的那一点。
|
||
|
||
> ✅ **已于 2026-10-12 删除** `D:\Docker\act_runner\deploy_runner_task.ps1` 与 `run_runner.cmd`
|
||
> (删除前核实:只有 `check_env.ps1` 的一句提示引用它,已同步改指 `ci_runner_bootstrap.ps1`;
|
||
> 计划任务的动作目标是 `run_runner_watchdog.ps1`,与这两个文件无关,删除不影响在跑的 runner)。
|
||
> 现行部署一律用仓库里的 `scripts/ci_runner_bootstrap.ps1`。
|
||
|
||
### 3.3 手动等价命令(等价于 §2.4 的 bootstrap,用于理解任务对象)
|
||
|
||
日常请直接跑 `scripts/ci_runner_bootstrap.ps1 -SetEnv -StartNow`。下面是它注册的**等价**对象,
|
||
只在需要理解字段或在别的机器上手搓时使用:
|
||
|
||
```powershell
|
||
$Dir = 'D:\Docker\act_runner'
|
||
$launcher = Join-Path $Dir 'run_runner_watchdog.ps1' # 由 bootstrap 生成的看门狗
|
||
|
||
# ① 动作:跑【看门狗】而不是 daemon 本体(单次 daemon 就是 §3.2 失效的根因)
|
||
$action = New-ScheduledTaskAction -Execute 'powershell.exe' `
|
||
-Argument "-NoProfile -ExecutionPolicy Bypass -WindowStyle Hidden -File `"$launcher`"" `
|
||
-WorkingDirectory $Dir
|
||
|
||
# ② 触发器:只要开机触发
|
||
$trigger = New-ScheduledTaskTrigger -AtStartup
|
||
$trigger.Delay = 'PT1M' # 给 Gitea 启动留时间(看门狗自己也会等,双保险)
|
||
|
||
# ③ 设置
|
||
$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
|
||
-RestartCount 999 -RestartInterval (New-TimeSpan -Minutes 1) `
|
||
-ExecutionTimeLimit ([TimeSpan]::Zero) -MultipleInstances IgnoreNew
|
||
$settings.StartWhenAvailable = $true # 🔴 属【任务设置】,写在触发器上会抛异常
|
||
|
||
Register-ScheduledTask -TaskName 'GiteaActionsRunner' -Action $action -Trigger $trigger `
|
||
-Settings $settings -User 'SYSTEM' -RunLevel Highest `
|
||
-Description 'Gitea Actions Runner for STM32F4-Base CI (labels: windows:host)'
|
||
|
||
# ④ 启动
|
||
Start-ScheduledTask -TaskName 'GiteaActionsRunner'
|
||
```
|
||
|
||
> ⚠️ **`ExecutionTimeLimit = 0`(不限时长)必须显式设置**——
|
||
> 任务计划程序默认 72 小时强制结束任务,会让 runner 每 3 天被杀一次。
|
||
|
||
> ⚠️ **`-WorkingDirectory` 只对 `New-ScheduledTaskAction` 有效**(这是它区别于
|
||
> WinPS 5.1 `New-Service` 的地方)。SYSTEM 账户的默认工作目录不可控,所以启动器里
|
||
> 仍要显式 `Set-Location` 或用绝对路径。
|
||
|
||
> 🗑 旧方案还额外挂了 `-AtLogOn` 触发器并以 `-LogonType ServiceAccount` 运行。现行口径
|
||
> **只留开机触发**:runner 与登录会话无关,多一个触发器只会让人误以为"必须有人登录才行"。
|
||
|
||
### 3.4 任务管理速查
|
||
|
||
```powershell
|
||
Get-ScheduledTask -TaskName GiteaActionsRunner # 状态
|
||
Get-ScheduledTaskInfo -TaskName GiteaActionsRunner # 上次运行结果
|
||
Start-ScheduledTask -TaskName GiteaActionsRunner # 启动
|
||
Stop-ScheduledTask -TaskName GiteaActionsRunner # 停止
|
||
Disable-ScheduledTask -TaskName GiteaActionsRunner # 禁用自启
|
||
Enable-ScheduledTask -TaskName GiteaActionsRunner # 恢复自启
|
||
Unregister-ScheduledTask -TaskName GiteaActionsRunner -Confirm:$false # 完全卸载
|
||
```
|
||
|
||
改完 `config.yaml` 后需 **Stop 再 Start** 任务才生效(**纯注释改动不需要**——注释不参与解析)。
|
||
|
||
> 🔴 **重跑 `ci_runner_bootstrap.ps1` 会先注销再注册任务,而注销不会带走旧实例的进程**:
|
||
> 旧看门狗变成孤儿、继续养着旧 daemon,旧 daemon 占住 `.runner.lock`,新看门狗就永远起不来
|
||
> (症状见 §7"online 却每 10 秒重启"那行)。2026-10-12 起 `-StartNow` 会**先停掉残留的看门狗与
|
||
> daemon 再启动**并回显单实例确认;`-Status` 也会把重复实例与锁冲突报出来。
|
||
|
||
> 💡 **`LastTaskResult` 数值解读**(别被非 0 吓到):
|
||
>
|
||
> | 值 | 含义 |
|
||
> |---|---|
|
||
> | `267009` (`0x41301`) | ✅ **`SCHED_S_TASK_RUNNING` —— 任务正在运行,不是错误**(常驻任务常态值) |
|
||
> | `0` (`0x0`) | ✅ 上次运行正常结束 |
|
||
> | `267011` (`0x41303`) | 任务尚未运行过 |
|
||
> | `267014` (`0x41306`) | 任务被用户终止 |
|
||
>
|
||
> **判断 runner 是否健康,看这三项而不是 `LastTaskResult`**:
|
||
> ① `Get-Process gitea-runner` 有进程(其父进程应是跑 `run_runner_watchdog.ps1` 的 powershell);
|
||
> ② `log\runner-daemon.log` 最新一行是 `declare successfully`;
|
||
> ③ Gitea 页面 runner 显示 **Idle**(或 MySQL 查 `action_runner.last_online`)。
|
||
>
|
||
> ⚠️ **`Get-ScheduledTaskInfo` 的 `Next Run Time` 为空是正常的**——触发器是"开机 +1 分钟",
|
||
> 没有下一个日历时间可报;常驻进程靠看门狗保活,不靠计划器反复拉起。别把空值当成"任务坏了"。
|
||
|
||
### 3.5 ⚠️ 清理遗留的无效服务
|
||
|
||
若早前按旧文档建过服务,删掉它(留着只会误导):
|
||
|
||
```powershell
|
||
Stop-Service gitea-runner -Force -ErrorAction SilentlyContinue
|
||
sc.exe delete gitea-runner
|
||
```
|
||
|
||
> **`gitea-runner` 这个名字将不再作为服务存在。** 之后提到"gitea-runner"
|
||
> 一律指**计划任务 `GiteaActionsRunner`** 或**进程 `gitea-runner.exe`**。
|
||
|
||
### 3.6 ⚠️ 权限要求(本机实测的硬约束)
|
||
|
||
**注册计划任务必须用「提权的管理员」Shell**。本机实测:
|
||
|
||
| 检查项 | 结果 |
|
||
|---|---|
|
||
| 当前用户 | `DESKTOP-KM0OG7Q\Administrator` |
|
||
| 是否提权(`IsInRole(Administrator)`) | **False** |
|
||
| 非提权下注册任务 | ❌ `拒绝访问`(`Register-ScheduledTask` 需要 elevated token) |
|
||
|
||
> **"是 Administrator 用户" ≠ "有管理员权限"** —— Windows 的 UAC 会把普通登录会话降权。
|
||
> 必须**右键 PowerShell → 以管理员身份运行**,标题栏会显示「管理员」字样。
|
||
|
||
> 💡 计划任务以 **`SYSTEM`** 账户运行,拥有完整权限,可正常访问本地
|
||
> `127.0.0.1:3001` 的 Gitea。
|
||
|
||
---
|
||
|
||
## 4. Runner 软件环境:装什么
|
||
|
||
runner 是一台干净的 Windows,**下面这些必须装**,否则 job 会失败或降级跳过。
|
||
|
||
| 项 | 必需性 | 影响 | 安装 |
|
||
|---|---|---|---|
|
||
| **Python 3.x** | 🔴 必需 | `static-gates` 环境自检失败 → **job 硬失败** | `winget install Python.Python.3.12` |
|
||
| **cppcheck** | 🔴 必需 | 同上(`cppcheck --version` 探测不过) | `winget install Cppcheck.Cppcheck` |
|
||
| **pre-commit** | 🔴 必需 | 同上(`python -m pre_commit --version` 探测) | `pip install pre-commit` 或 `uv tool install pre-commit` |
|
||
| **MSVC(VS + Windows SDK)** | 🟡 建议 | 缺则 `host-unit-tests` **告警降级跳过**(不硬失败,但 主机侧纯逻辑单测裸奔) | `winget install Microsoft.VisualStudio.2022.BuildTools`,勾"使用 C++ 的桌面开发" |
|
||
| Keil / ST-LINK | ⛔ 不需要 | CI 刻意不跑 `@build` | — |
|
||
| 开发板 / 串口 | ⛔ 不需要 | 板端测试套件/主机侧回归/人工目检不上 CI | — |
|
||
|
||
Git 另算:`git --version` 任意版本即可(你已在用)。
|
||
|
||
**本机实测状态(2026-10-02)**:
|
||
|
||
```
|
||
Python : C:\ProgramData\anaconda3\python.exe → 3.12.4 ✅
|
||
cppcheck : C:\Program Files\Cppcheck\cppcheck.exe → 2.21.0 ✅
|
||
pre-commit: 4.6.0 (anaconda3) ✅
|
||
MSVC : VS2019 BuildTools 14.29.30133 (Hostx64\x64\cl.exe) ✅
|
||
```
|
||
|
||
**→ 四项全齐,无需额外安装。**
|
||
|
||
### 4.1 关键:变量必须对 **SYSTEM** 可见(不是"对你可见")
|
||
|
||
runner 以 **SYSTEM 服务/计划任务**运行,它看到的 PATH 与 site-packages **和你登录时完全不同**:
|
||
|
||
| 你以为 | SYSTEM 实际看到 | 后果 |
|
||
|---|---|---|
|
||
| `python` 在 PATH 里 | PATH 上是 `C:\msys64\ucrt64\bin\python.exe`,**没有 pre-commit** | 静态门禁环境自检失败 |
|
||
| `pip install --user pre-commit` 装好了 | **用户级 site 对 SYSTEM 完全不可见**(换任何 python 路径都没用) | 同上 |
|
||
| 仓库里有 `.venv` | `.venv` 已 `.gitignore`,runner 全新工作区**根本没有** | 同上 |
|
||
| `cppcheck` 在 PATH 里 | 机器 PATH 无 `C:\Program Files\Cppcheck` | 静态门禁环境自检失败 |
|
||
|
||
> 机制补充(2026-10-02 实测):计划任务以 **`SYSTEM`** 账户运行时,继承的是**系统级 PATH**
|
||
> (注册表 `HKLM:\SYSTEM\CurrentControlSet\Control\Session Manager\Environment` 的 `Path`),
|
||
> **不含任何用户级 PATH**。核对方法:用 `check_env.ps1`(见 §9)的「系统 PATH 视角」检查项,
|
||
> 或以 SYSTEM 身份跑一次 `where python` 验证。
|
||
|
||
CI 侧的探测统一走 **`scripts/ci_find_python.ps1`**(两个 job 共用、可离线自测):
|
||
|
||
```
|
||
DTU_PRECOMMIT_PYTHON(显式;设了就必须有效,不静默回退)
|
||
-> PATH python -> 仓库 .venv -> 常见机器级安装(Program Files\Python3xx、
|
||
%LOCALAPPDATA%\Programs\Python\Python3xx、%ProgramData%\anaconda3|miniconda3)
|
||
```
|
||
|
||
cppcheck 同理:`DTU_CPPCHECK` → PATH → `%ProgramFiles%\Cppcheck\cppcheck.exe`
|
||
(及 `%ProgramFiles(x86)%\Cppcheck\cppcheck.exe`)。
|
||
|
||
**推荐做法**:跑一次 bootstrap,把机器级变量固化下来(探测逻辑与 CI 完全一致):
|
||
|
||
```powershell
|
||
powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -SetEnv
|
||
```
|
||
|
||
手工自检(三条都必须对 SYSTEM 成立,而不只是对你的终端成立):
|
||
|
||
```powershell
|
||
python --version # 期望 Python 3.x
|
||
cppcheck --version # 期望 Cppcheck 2.21.0 或更高
|
||
python -m pre_commit --version
|
||
```
|
||
|
||
> ⚠️ 改了机器级变量后**必须重启 runner**(计划任务:`Stop-ScheduledTask` + `Start-ScheduledTask`)
|
||
> ——环境变量只在**进程启动时**读取,daemon 不重启就吃不到新值。
|
||
|
||
#### 4.1.1 ⚠️⚠️ 关键坑:`pre-commit` 必须在「全局 site-packages」,装到用户态等于没装
|
||
|
||
这是 2026-10-02 排查 **静态门禁 job 反复失败**(`::error::no Python with a SYSTEM-visible pre-commit`)的真正根因,
|
||
也是本环境最隐蔽的一个坑。
|
||
|
||
**现象**:`pre-commit` 在你自己的终端里好好的(`pre-commit --version` 有输出),
|
||
但 CI 的 `static-gates` job 三个候选 Python 全部探测失败:
|
||
|
||
```
|
||
::error::no Python with a SYSTEM-visible pre-commit. Tried:
|
||
C:\msys64\ucrt64\bin\python.exe |
|
||
<workdir>\.venv\Scripts\python.exe |
|
||
C:\ProgramData\anaconda3\python.exe
|
||
```
|
||
|
||
> 📌 上为 2026-10-02 的历史报错原文。workflow 现已改为
|
||
> **`DTU_PRECOMMIT_PYTHON`(机器级)→ PATH 的 `python` → 工作区 `.venv`** 顺序(§4.1.3),
|
||
> 且报错行会指明是变量无效还是自动发现落空。
|
||
|
||
**根因(两层)**:
|
||
|
||
1. **用户态 site-packages 对 SYSTEM 不可见**。
|
||
`pip install pre-commit`(不带提权)默认装到 **user-site**:
|
||
`C:\Users\Administrator\AppData\Roaming\Python\Python312\site-packages`。
|
||
而 SYSTEM 账户的 `%APPDATA%` 是
|
||
`C:\Windows\system32\config\systemprofile\AppData\Roaming` —— **完全是另一个目录**,
|
||
它根本看不到 Administrator 的 user-site。**任何 `python.exe` 路径都救不了这个**。
|
||
|
||
2. **pip 的「已满足」判定陷阱**(更阴)。
|
||
就算你**提权**执行 `pip install --no-user pre-commit`,pip 在判断
|
||
「这个包是不是已经装了」时看的是**整个 `sys.path`**——其中包含 user-site。
|
||
既然 user-site 里已有同版本的包,pip 会直接打印
|
||
`Requirement already satisfied: xxx in ...\roaming\...\site-packages`
|
||
然后**完全跳过**安装到全局目录。
|
||
结果:pip 报"成功",全局目录里却什么都没有。
|
||
|
||
> 实测:9 个依赖里 8 个因版本与用户态不同而被 pip 升级(侥幸落到全局),
|
||
> 唯独 `cfgv` 用户态已是同版本 **3.5.0** → 被静默跳过 →
|
||
> `-s` 视角下 `ModuleNotFoundError: No module named 'cfgv'`。
|
||
|
||
**诊断命令**(`-s` = 禁用 user-site,模拟 SYSTEM 视角):
|
||
|
||
```powershell
|
||
# 看全局目录到底有没有(而不是 pip 怎么说)
|
||
& 'C:\ProgramData\anaconda3\python.exe' -s -m pre_commit --version
|
||
|
||
# 逐个依赖点名,找出被静默跳过的那个
|
||
& 'C:\ProgramData\anaconda3\python.exe' -s -c "import cfgv, identify, nodeenv, virtualenv, distlib, filelock, platformdirs, yaml; print('all ok')"
|
||
|
||
# 反查 pip 为何跳过(会显示 "Requirement already satisfied ... roaming ...")
|
||
& 'C:\ProgramData\anaconda3\python.exe' -m pip install --no-user --dry-run cfgv
|
||
```
|
||
|
||
**修复(必须提权)**:脚本 `install_precommit_global.ps1` +
|
||
`install_precommit_global2.ps1`(逐包安装 + 逐个 import 复验);
|
||
对**被静默跳过**的包补一支 `fix_cfgv_global.ps1`,核心是强制重装:
|
||
|
||
```powershell
|
||
& $Py -m pip install --no-user --force-reinstall --no-deps `
|
||
--disable-pip-version-check --no-warn-script-location cfgv
|
||
```
|
||
|
||
> `--force-reinstall` 无视"已满足"判定,`--no-deps` 避免牵连其它包。
|
||
> 三个脚本都在 `D:\Docker\act_runner\`。
|
||
|
||
**验证(四条全过才算好)**:
|
||
|
||
```powershell
|
||
$Py = 'C:\ProgramData\anaconda3\python.exe'
|
||
& $Py -s -m pre_commit --version # pre-commit 4.6.2
|
||
Test-Path 'C:\ProgramData\anaconda3\Lib\site-packages\cfgv.py' # True
|
||
# 关键:确认文件真在【全局】目录,而不是 roaming
|
||
Get-ChildItem 'C:\ProgramData\anaconda3\Lib\site-packages' -Filter 'cfgv*'
|
||
```
|
||
|
||
> ⚠️ 别再往 `.venv` 里装就以为 CI 能用了——CI 虽然会检查 `.venv\Scripts\python.exe`,
|
||
> 但 `actions/checkout@v4` 拉的是**干净克隆**,`.venv` 是 gitignore 的,**根本不存在**。
|
||
|
||
#### 4.1.2 ⚠️ 关键坑:`doc-links` 钩子「本地过、CI 挂」= 引用了 gitignore 的运行时文件
|
||
|
||
**现象**:`文档链接存在性` 钩子在**本地** `pre-commit run --all-files` 通过,
|
||
一上 CI 就 `Failed`(exit 1),报的行/路径你在本地明明存在。
|
||
|
||
**根因**:CI 的 `actions/checkout@v4` 拉的是**干净克隆**,而钩子校验的是
|
||
**仓库根相对路径是否存在**(`check_backtick_paths`,见 `scripts/check_doc_links.py`)。
|
||
若文档引用了 **gitignore 的运行时产物**——
|
||
如 `.dsh/skills/serial-assistant/scripts/state/meta.json`(运行时未创建,`.gitignore:132` 排除整个 `state/`)——
|
||
你本机因为跑过工具所以文件在,**克隆里则不存在** → 判为失效引用。
|
||
|
||
**修复**:按 [docs/文档规范.md §3.3](../文档规范.md) 给该行加**状态词**(脚本的 `STATUS_HINTS`:
|
||
`计划 / 未创建 / 尚未创建 / 已删除 / 删除 / 不存在 / 缺失 / 断链 / 待建 / 待创建`)。
|
||
|
||
```markdown
|
||
| `.dsh/skills/serial-assistant/scripts/state/meta.json`(运行时未创建) | daemon 元数据(pid / 版本 / gen) |
|
||
```
|
||
|
||
> ⚠️ **状态词必须与引用在【同一行】且 ≤40 字符**(`STATUS_PROXIMITY`)。
|
||
> 把说明写在**上一行**是无效的——脚本逐行调用 `has_status_hint(line, ...)`,
|
||
> 跨行的文字不在窗口内。
|
||
|
||
**本地复现 CI 条件的姿势**(否则永远测不出来):
|
||
|
||
```bash
|
||
# 把运行时目录临时移开,模拟干净克隆,跑完再移回
|
||
mv .dsh/skills/serial-assistant/scripts/state .dsh/skills/serial-assistant/scripts/state.bak_ci
|
||
& '.venv\Scripts\python.exe' scripts/check_doc_links.py --all # 或全局 python -m pre_commit run doc-links --all-files
|
||
mv .dsh/skills/serial-assistant/scripts/state.bak_ci .dsh/skills/serial-assistant/scripts/state
|
||
```
|
||
|
||
> 更一般的教训:**任何校验"文件是否存在/数字是否一致"的钩子,都要在
|
||
> "无本地产物"的干净克隆里验一次**,别只在开发机上跑。
|
||
|
||
#### 4.1.3 机器级 `DTU_*` 环境变量(runner 的正式覆盖通道,2026-10-02 起)
|
||
|
||
workflow 已改为统一消费 `DTU_*` 接口(见 [本地工具路径环境变量方案.md §3.3](../环境与部署/本地工具路径环境变量方案.md)):
|
||
**显式变量优先 → 自动发现回退**,三个 job 步骤相关的变量:
|
||
|
||
| 变量 | 作用 | 未设置时的回退 |
|
||
|---|---|---|
|
||
| `DTU_PRECOMMIT_PYTHON` | 指向装有 pre-commit 的 python.exe(静态门禁/主机侧纯逻辑单测用) | PATH 的 `python` → 工作区 `.venv`(干净克隆里通常没有) |
|
||
| `DTU_CPPCHECK` | 指向 cppcheck.exe(静态门禁钩子用) | SYSTEM 的 PATH(用户级 PATH 不可见) |
|
||
| `DTU_MSVC_CL` | 指向 x64 `cl.exe`(主机侧纯逻辑单测用) | `%ProgramFiles%`/`%ProgramFiles(x86)%` 推导的 VS 标准根自动探测 |
|
||
|
||
设置(**必须管理员**;scope 用 `Machine` 才能被 SYSTEM 任务看到):
|
||
|
||
```powershell
|
||
[Environment]::SetEnvironmentVariable('DTU_PRECOMMIT_PYTHON', 'C:\ProgramData\anaconda3\python.exe', 'Machine') # 示例:本机 runner
|
||
[Environment]::SetEnvironmentVariable('DTU_CPPCHECK', 'C:\Program Files\Cppcheck\cppcheck.exe', 'Machine')
|
||
[Environment]::SetEnvironmentVariable('DTU_MSVC_CL', 'C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\cl.exe', 'Machine')
|
||
# ★ 设完必须重启 runner 任务(环境块是任务启动时快照的)
|
||
Stop-ScheduledTask -TaskName GiteaActionsRunner
|
||
Start-ScheduledTask -TaskName GiteaActionsRunner
|
||
```
|
||
|
||
> 与 §4.1.1 的关系:机器级 `DTU_PRECOMMIT_PYTHON` 是**正式通道**(指向哪个解释器由你说了算,
|
||
> 值无效时 job 直接报变量名、不猜);"把 pre-commit 装进全局 site-packages"仍是**兜底**
|
||
> (变量未设 + 全局 site 可见也能过)。两条路并存不冲突:变量设了就用变量,没设走探测。
|
||
> 显式变量值无效(文件不存在 / 无 pre-commit 模块)→ job **硬失败**并指明变量名——**不会**
|
||
> 静默回退到别的解释器,避免 runner 悄悄换了 Python 导致钩子行为漂移。
|
||
|
||
### 4.2 MSVC 的路径要求(探测逻辑)
|
||
|
||
`host-unit-tests` 的 MSVC 探测优先读机器级 `DTU_MSVC_CL`(x64 `cl.exe` 完整路径;
|
||
值无效 → 硬失败、不静默回退);未设置时按**标准布局自动探测**(根目录由
|
||
`%ProgramFiles` / `%ProgramFiles(x86)` 推导,不写死盘符;与
|
||
`test/host/common/unit_test_harness.py` 的 `_default_vs_roots()` 保持一致):
|
||
2019 → `Program Files (x86)`;2022 / 2026(实际目录名 `18`)→ `Program Files`,
|
||
各取 BuildTools/Community/Professional/Enterprise 子目录,找
|
||
`VC\Tools\MSVC\*\bin\Hostx64\x64\cl.exe`。
|
||
|
||
**本机实测命中第 1 条(2026-10-02)**:
|
||
`C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\cl.exe` ✅
|
||
|
||
> ⚠️ **装在完全非标准位置(如 D 盘自定义目录)会探测不到** → 被当成"没装 MSVC"跳过 主机侧纯逻辑单测(仅告警)。
|
||
> 处置(二选一):设机器级 `DTU_MSVC_CL` 指向 `cl.exe`(推荐;值无效会硬报错),
|
||
> 或把 VS 装回标准位置(探测按 `%ProgramFiles*` 推导,换盘符的标准布局仍命中)。
|
||
>
|
||
> Windows SDK 同理:默认 `<ProgramFiles(x86)>\Windows Kits\10\`;非标准位置用 `DTU_WINDOWS_KITS_ROOT` 指定。
|
||
|
||
> 📌 2026-10-02:workflow 与本地 harness 同步改为"变量优先 + 标准布局推导"
|
||
> ([本地工具路径环境变量方案.md](../环境与部署/本地工具路径环境变量方案.md))——旧写法要求"同步改两处",现已不需要。
|
||
|
||
### 4.3 验证运行器能找到工具链(注册前先跑一遍)
|
||
|
||
```powershell
|
||
& '.venv\Scripts\python.exe' scripts/ci_local.py
|
||
```
|
||
|
||
这条命令跑的就是 CI 的同一套判定。**本地能过,runner 上基本就能过**(前提是 PATH 一致)。
|
||
|
||
---
|
||
|
||
## 5. 仓库端:workflow 文件
|
||
|
||
### 5.1 文件位置与提交
|
||
|
||
workflow 必须位于**仓库根**的 `.gitea/workflows/` 下(不是 `.github/`):
|
||
|
||
```
|
||
<仓库根>/
|
||
└─ .gitea/
|
||
└─ workflows/
|
||
└─ ci.yml ← 本仓库已有,见该文件
|
||
```
|
||
|
||
**它必须被 push 到远程才能生效**——目前本仓库的 `e904865` 还在本地
|
||
(`origin/v2-on-ui` 尚未包含),所以**现在 push 之前 CI 不会跑**:
|
||
|
||
```powershell
|
||
git push origin v2-on-ui # ⚠️ 需你明确要求才推送(本仓库约定:默认不推送)
|
||
```
|
||
|
||
### 5.2 触发条件
|
||
|
||
```yaml
|
||
on:
|
||
push:
|
||
paths-ignore: ['docs/**', '**/*.md', '.workbuddy-ai/**']
|
||
pull_request:
|
||
paths-ignore: ['docs/**', '**/*.md', '.workbuddy-ai/**']
|
||
workflow_dispatch: # 页面可手动触发
|
||
```
|
||
|
||
- 纯文档改动**不触发**(省 runner);
|
||
- 想手动跑一次:仓库 → Actions → 选 `CI` → **Run workflow**。
|
||
|
||
### 5.3 内网无公网时的 action 拉取(重要)
|
||
|
||
workflow 用了 `actions/checkout@v4`。**默认从 `github.com` 拉取**(见 §1.2.1;
|
||
不是从你自己的实例,也不是从 gitea.com —— 这点很反直觉);实例不通
|
||
github.com 会在 checkout 步骤卡住/失败。三种处置:
|
||
|
||
1. **镜像到自有实例**:`DEFAULT_ACTIONS_URL = self`,**并先把 `actions/checkout` 导入你自己的 Gitea**;
|
||
2. **指向可达镜像**:`DEFAULT_ACTIONS_URL` 指向某个能访问的 action 站(或在 workflow 里
|
||
直接写全路径 `uses: https://<host>/owner/repo@ref`);
|
||
3. **零依赖**(内网最稳,推荐):把 checkout 换成纯 git 命令,不依赖任何 action:
|
||
|
||
```yaml
|
||
- name: Checkout
|
||
shell: pwsh
|
||
run: |
|
||
git init .
|
||
git remote add origin $env:GITHUB_SERVER_URL/$env:GITHUB_REPOSITORY.git
|
||
git fetch --depth 1 origin $env:GITHUB_SHA
|
||
git checkout $env:GITHUB_SHA
|
||
```
|
||
|
||
> 方案 3 无需任何外部 action 与任何 `DEFAULT_ACTIONS_URL` 配置;代价是失去 checkout 的缓存优化
|
||
> (对本仓库影响很小)。
|
||
> 参考:Gitea FAQ「Where will the runner download scripts when using actions such as `actions/checkout@v4`?」
|
||
|
||
**本机环境判断(2026-10-02)**:Gitea 装了 `OFFLINE_MODE = true`,说明这台机器的外网访问
|
||
**可能受限**。如果 runner 拉不到 github.com,checkout 步骤会卡住。
|
||
|
||
> ⚠️ 若走方案 1:**`self` 是关键字,不是写你的域名**(见 §1.2.1)。
|
||
|
||
**建议**:**先按原样试一次**。若 checkout 步骤报网络错误,再换成上面的零依赖写法(方案 3)。
|
||
|
||
### 5.4 产物存档与结果汇总(2026-10-02 起)
|
||
|
||
**目标**:CI 失败时**不查 MySQL / 不翻 runner 磁盘**,在 Gitea 页面即可看到完整输出与摘要。
|
||
|
||
**零依赖方案**(内网首选,**不用 `actions/upload-artifact`**):
|
||
|
||
| 手段 | 说明 |
|
||
|---|---|
|
||
| `Tee-Object` 落盘 | 两个 job 把关键输出 `Tee` 到 `ci-artifacts/static-pre-commit.log`、`ci-artifacts/host-unit-<模块>.log`(**留在 runner 工作区**,已 gitignore) |
|
||
| `$GITHUB_STEP_SUMMARY` | 写入 job 的 **step-summary**(Gitea UI 运行页直接可见,无需下钻日志) |
|
||
| `::notice::` 汇总行 | 控制台醒目行,便于日志检索 |
|
||
|
||
**汇总行格式**:
|
||
|
||
| 层 | 汇总行 |
|
||
|---|---|
|
||
| 静态门禁 | `static: pre-commit hooks -- passed=N failed=M skipped=K (log: ci-artifacts\static-pre-commit.log)` |
|
||
| 主机侧纯逻辑单测 | `host-unit: <K> runner(s), <A>/<B> assertions passed, fail=<F>`(逐模块另有 `::notice::<模块>: N/M PASSED (failed=0)`) |
|
||
|
||
> ⚠️ **`ci-artifacts/` 已加入 `.gitignore`**:它是 runner 工作区的运行时产物,不要提交。
|
||
>
|
||
> ⚠️ **若后续改为 `actions/upload-artifact`**(需确认实例能拉该 action):把 `Tee` 落盘
|
||
> 保留即可,上传步骤叠加在其后;本方案的落盘文件即上传源。
|
||
|
||
**PowerShell 陷阱(已在 workflow 内处理)**:runner 注入 `$ErrorActionPreference='Stop'`,
|
||
而 `& native 2>&1 | Tee-Object` 会把 native 的 stderr 变成 ErrorRecord → 在 `Stop` 下**整段中止**。
|
||
故 pipeline 前后需 `Save/Restore` 该偏好为 `Continue`(与"环境自检"步骤同法)。
|
||
`$LASTEXITCODE` 在 `Tee-Object` 之后**仍保留** native 退出码(实测),可直接用于判定。
|
||
|
||
### 5.5 两个 job 的步骤要点(2026-10-12 自 docs/测试说明/测试开发标准流程.md §9.2 并入)
|
||
|
||
| job | `runs-on` | 步骤要点 |
|
||
|---|---|---|
|
||
| `static-gates` | `windows` | checkout(`fetch-depth: 0`,gitlint/cubemx 需完整历史)→ 环境自检(**Python 探测走 `scripts/ci_find_python.ps1`**、cppcheck 按 `DTU_CPPCHECK` → PATH → 标准安装目录,缺则 `::error::` + exit 1)→ `pre_commit install --install-hooks` → **提交信息门禁 `scripts/ci_gitlint_range.ps1`**(见 §7 排障表"钩子条数对不上"行)→ **决定跳过哪些钩子**(探测 `.map`,写入 `GITHUB_OUTPUT`;无 `.map` 时 `::warning::` 声明体积门禁未跑)→ `pre-commit run --all-files --show-diff-on-failure`(`env: SKIP`,输出 `Tee` 到 `ci-artifacts/static-pre-commit.log`)→ 打印 `::notice::static: passed/failed/skipped` 汇总行 → 归档日志说明 |
|
||
| `host-unit-tests` | `windows` | checkout → **探测 MSVC**(按 VS_ROOTS 找 `Hostx64\x64\cl.exe`)→ 遍历 `test/host/**/run_unit_test.py` 逐个跑(输出 `Tee` 到 `ci-artifacts/host-unit-<模块>.log`;解析各模块 `N/M PASSED` 汇总行 → 聚合 `::notice::host-unit: K runner(s), A/B assertions passed`;失败 `::error::` 并置退出码);**未探测到 MSVC 时打印醒目 WARNING 并降级跳过**(不硬失败,避免"没装 VS 的 runner 全红") |
|
||
|
||
> 🔴 **两条 Python 探测链必须同序同步**:本地 `scripts/ci_local.py` 与 CI 侧
|
||
> `scripts/ci_find_python.ps1` 的候选顺序要一致。2026-10-11 那次 静态门禁全灭,就是 workflow
|
||
> 单方面把候选收窄成"PATH 上的 python + 仓库 `.venv`"(而 runner 上这两条**都是死路**:
|
||
> PATH python 没装 pre-commit、`.venv` 被 gitignore 根本不存在)导致的。改一边必须改另一边。
|
||
|
||
---
|
||
|
||
## 6. 端到端验证清单
|
||
|
||
按顺序确认,任一步不过就停在那里排障:
|
||
|
||
| # | 检查 | 期望 |
|
||
|---|---|---|
|
||
| 1 | 仓库顶部有 **Actions** 标签页 | 服务端开关已开 |
|
||
| 2 | Settings → Actions → Runners 里 runner 为 **Idle** | runner 注册成功且**在线**(`offline` = 后面全都不会跑,先做第 3 步) |
|
||
| 3 | `scripts\ci_runner_bootstrap.ps1 -Status` | 计划任务已注册 / daemon 在跑 / Gitea 可达 / 三个 `DTU_*` 已设——**四项全绿** |
|
||
| 4 | runner 上 `python --version` / `cppcheck --version` / `python -m pre_commit --version` | 三条全通(且对 SYSTEM 成立,见 §4.1) |
|
||
| 5 | `git push` 一次(或页面 Run workflow) | Actions 页出现运行中任务 |
|
||
| 6 | `static-gates` 日志 | `using python (…)` + `runner environment check passed`;`::notice::static: pre-commit hooks -- passed=.. failed=0 skipped=..`;无 `.map` 时见 `::warning::` 体积门禁未跑 |
|
||
| 7 | `static-gates` 的 **提交信息门禁** 步骤 | `::notice::gitlint: N commit message(s) checked, 0 failed`(**这一行以前根本不存在**,见 §7) |
|
||
| 8 | `host-unit-tests` 日志 | `::notice::host-unit: <N> runner(s), A/A assertions passed, fail=0`。**`N` = `test/host/` 下 `run_unit_test.py` 的个数**(2026-10-12 实测:`master` 3 个 / 288 断言,`v2-on-ui` 10 个);断言总数随单测增减而变,判定只看 **`fail=0` 且 A==B**,不要拿历史数字当基准 |
|
||
| 9 | job 运行页的 **Summary** 区块 | 出现 "静态门禁 static gates" / "Commit message gate (gitlint)" / "主机侧纯逻辑单测 host-side unit tests" 小标题与汇总行 |
|
||
| 10 | 提交记录旁出现 ✅ | CI 全绿 |
|
||
|
||
**本地先自查**(与 CI 同一套判定,避免推上去才发现问题):
|
||
|
||
```powershell
|
||
& '.venv\Scripts\python.exe' scripts/ci_local.py
|
||
```
|
||
|
||
> ⚠️ **第 5 步的前提(2026-10-02 记录)是当时的最大阻塞**:本仓库的 `.gitea/workflows/ci.yml` 还在本地
|
||
> (提交 `e904865` / `1e01950` / `52e6dcb` / `c305a8d` 均未 push)。**workflow 不上远程,CI 永远不会跑**,
|
||
> 哪怕 runner 配得再完美。
|
||
|
||
### 6.1 无网页登录时的状态核验(MySQL 直查)
|
||
|
||
runner 是否在线,可以直接查 Gitea 数据库确认(本机 MySQL 凭据见
|
||
`D:\Docker\Gitea\custom\conf\app.ini` 的 `[database]` 段):
|
||
|
||
```bash
|
||
MYSQL="/c/Program Files/MySQL/MySQL Server 8.4/bin/mysql.exe"
|
||
"$MYSQL" -h 127.0.0.1 -P 3306 -u wandering -p<pwd> -D gitea -e \
|
||
"SELECT id,name,version,last_online,agent_labels FROM action_runner;"
|
||
```
|
||
|
||
| 字段 | 判读 |
|
||
|---|---|
|
||
| `name` / `version` | 注册名与版本(应为 `win-stm32-ci` / `v4.0.1`) |
|
||
| `agent_labels` | 应为 `["windows"]` ← **不含 windows 就是标签配错了** |
|
||
| `last_online` | Unix 秒;**与当前时间相差 < 30 秒即在正常心跳** |
|
||
|
||
```bash
|
||
date +%s # 对比 last_online
|
||
```
|
||
|
||
相关表:`action_runner`(已注册 runner)、`action_runner_token`(注册令牌,
|
||
字段 `repo_id` 对应 `repository.id`)。
|
||
|
||
---
|
||
|
||
## 7. 常见故障排查
|
||
|
||
| 现象 | 根因 | 处置 |
|
||
|---|---|---|
|
||
| 运行状态是 **`cancelled`** 且**一行日志都没有** | runner 掉线时 run 只在队列里等,**下一次 push 触发 `cancel-in-progress` 把它们取消**——所以表现为"任务凭空消失"而不是"任务失败" | 先按下一行修 runner;被取消的 run 可在页面 Re-run,或重新 push |
|
||
| Runners 页 runner 显示 **`offline`** | daemon 进程不在:`gitea-runner` v4 的 `daemon` **首次 Declare 失败就直接退出**(Gitea 重启/机器重启后无人拉起) | §2.4 用 `ci_runner_bootstrap.ps1` 装看门狗 + 计划任务;`-Status` 复查 |
|
||
| `static-gates` 报 `no Python with a SYSTEM-visible pre-commit` | runner 是 SYSTEM:PATH 上的 python 无 pre-commit、用户级 site 不可见、`.venv` 未入库(§4.1 三重坑) | `ci_runner_bootstrap.ps1 -SetEnv` 写机器级 `DTU_PRECOMMIT_PYTHON`,或把 pre-commit 装进机器级解释器;**改完必须重启 runner** |
|
||
| `static-gates` 报 `runner missing cppcheck` | 机器 PATH 无 Cppcheck 且无机器级 `DTU_CPPCHECK` | `-SetEnv`(会自动发现 `C:\Program Files\Cppcheck`)或装 `Cppcheck.Cppcheck` |
|
||
| **钩子条数对不上**(CI 报告条数 = 定义数 − 1;2026-10-12 实测 `master` 18 定义 / 17 报告,`v2-on-ui` 26 定义 / 25 报告——**增删钩子的分支必须重算这个差值**) | `gitlint` 的 `stages: [commit-msg]` 写在**上游** `.pre-commit-hooks.yaml`(项目 `.pre-commit-config.yaml` 里 grep 不到,别据此断定"没限制"),`pre-commit run --all-files` **跑不到它**,且表现为全绿——静默空转 | 已由 `scripts/ci_gitlint_range.ps1` 单独校验;**以后增删钩子必须核对"定义数 == 日志条数 + gitlint"** |
|
||
| gitlint 步骤 **Failed 但提交信息其实合规**(同一条命令在本地全绿),日志里是 `exit code: 254` + `Missing git configuration: please set user.name` | `actions/checkout` 把 HOME 临时指向每次任务的临时目录,而 SYSTEM 账号没有全局 git 身份 → gitlint **在读到任何规则之前**就因配置错误退出(**254 不是规则违规**)。旧脚本末尾那句 `::error::...rule: single-line Chinese title, < 100 chars` 是**写死的猜测文案**,把环境问题伪装成"提交信息不合格";且该步 `exit 1` 会让后面的 `--all-files` **整个不跑**——看着像"门禁拦住了",实际 26 个钩子一个都没执行 | `ci_gitlint_range.ps1` 先探测身份,缺失就用 `GIT_CONFIG_COUNT/KEY/VALUE` 注入一次性身份(**不写任何文件**,开发者机器不受影响),并把 254 与真实违规分开报(**2 = 门禁没跑起来**,1 = 信息确实违规)。实测同一命令:空 HOME 下改前 254、改后 `0 failed`(2026-10-12 task 31 首次暴露) |
|
||
| push 后 Actions 页**空白/无任务** | ① 服务端开关未开 ② workflow 没 push 到远程 ③ 只改了 `docs/**`(被 `paths-ignore` 滤掉) ④ **新建分支的首次 push**(实测 2026-10-12:创建 `v2-on-ui-ci` 那次 push 没产生 run——runner 在线空闲、job 日志零新增;紧接着一次普通 push 立刻出 task。原因:`before` 是全零 SHA,变更文件集算不出来,`paths-ignore` 求值时被滤空) | ①②③ 逐条核对 §1 / §5.1 / §5.2;④ 用 `workflow_dispatch` 手动触发,或再推一个非文档路径的提交 |
|
||
| 任务一直 **Waiting / 排队** | 没有 label 匹配 `windows` 的 runner | 检查 `--labels "windows:host"`;Settings→Runners 是否 Idle |
|
||
| `static-gates` 报 `::error::runner 缺少:Python, cppcheck` | runner 没装或不在 PATH | 装完后**重启 runner 服务**(新进程才吃到新 PATH) |
|
||
| `checkout` 步骤卡住/超时 | 拉不到 `actions/checkout@v4`(内网) | 见 §5.3 |
|
||
| `l05` 日志出现 `::warning::runner 未安装 MSVC` | VS 装在非默认路径或未装 | 见 §4.2(默认路径 / 同步改两处根目录) |
|
||
| `mem-numbers` 显示 `Skipped` | 正常(CI 无 Keil,无 `.map`) | 无需处理;但注意 **CI 未守体积门禁**(见 §0.0),改代码后本地 `@build` + `check_mem_numbers.py --scan` 兜底 |
|
||
| 主机侧纯逻辑单测显示 `2 runner(s), X/Y ...` 但 X<Y | 有单测用例未通过 | 看该模块的 `::error::unit test failed` 行与其 `ci-artifacts/host-unit-<模块>.log` |
|
||
| 主机侧纯逻辑单测报 `produced no '=== ... N/M PASSED ===' summary line` | 该模块运行器未输出汇总行(改了 harness 或运行器崩溃) | 核对 `test/host/common/unit_test_harness.py` 的 `summary_marker` 与该模块 `TestBuild` 是否一致 |
|
||
| `Tee-Object` 步骤报 `TerminatorExpectedAtEndOfString` / 字符串缺少终止符 | `run:` 块混入了非 ASCII(GBK 解码错位) | 见文件头 🔴🔴;`run:` 内必须纯 ASCII |
|
||
| `l05` 报 `fatal error C1083: 无法打开包括文件 "stdio.h"` | Windows SDK 缺失 | 装 VS 时勾"使用 C++ 的桌面开发"+ SDK |
|
||
| 单测退出码 2 | 环境错误(多为无 MSVC) | 同上 |
|
||
| `pre-commit` 首次运行久 | 首次要下载各钩子的环境 | 正常,runner 缓存后会快 |
|
||
| `daemon` 启动报 **connection refused / dial tcp** | `--instance` 填了不可达地址 | 删 `.runner` → 重新 `register`,`--instance http://127.0.0.1:3001` |
|
||
| 启动报 **token** 相关错误 | Registration Token 过期(约 1 小时)或已用过 | 回网页重新创建 runner 拿新 token,重新 `register` |
|
||
| 任务跑到一半报 `python: 不是内部或外部命令` | SYSTEM 账户 PATH 没有 Python | 确认 Python 在**系统** PATH → 重启任务(§4.1) |
|
||
| **改了 `.runner` / 换机器** | 注册信息失效 | 删 `.runner` → 重新 `register` |
|
||
| `daemon install` 报 `accepts at most 0 arg(s)` | **v4 已移除该子命令,且无任何服务子命令** | **改用任务计划程序**(§3) |
|
||
| 🔴 `Start-Service gitea-runner` 报 **无法启动服务**(错误 1053) | **`daemon` 不是服务型程序**,不向 SCM 回报 `SERVICE_RUNNING` | **删除该服务,改用任务计划程序**(§3.0 / §3.5) |
|
||
| `Register-ScheduledTask` 报 **拒绝访问** | Shell 未提权(`IsAdmin=False`) | 右键 PowerShell → **以管理员身份运行**(§3.6) |
|
||
| 任务 `LastTaskResult` 非 0 | 启动器路径/权限问题,或任务被手工终止 | 看 `log\runner-watchdog.log` 与 `log\runner-daemon.log`;确认 `run_runner_watchdog.ps1` 存在且为纯 ASCII;`0x41301` 是"正在运行"不是错误 |
|
||
| 🔴 **runner 网页显示 online,但 `runner-watchdog.log` 每 10 秒刷 `starting daemon` → `daemon exited, exit code 1`** | **两个看门狗抢 `.runner.lock`**:`Unregister-ScheduledTask` 不会带走旧实例启动的进程,孤儿看门狗养着的旧 daemon 仍占着锁,新看门狗起不来(daemon 日志写 `another gitea-runner process is already using ".runner"` / `runner file is already locked by another process`)。此时 online 是**旧 daemon 在硬撑**,任务实际谁也没管 | `scripts\ci_runner_bootstrap.ps1 -Status` 会报出重复实例;**管理员**跑 `-StartNow`(现已会先清残留进程再启动,runner 约离线几秒)。应急可手工:`Get-CimInstance Win32_Process -Filter "Name='powershell.exe'" \| ? CommandLine -match 'run_runner_watchdog'` 找出多个 pid,杀掉**旧的那个**,再 `taskkill /PID <旧 daemon>` 让在册看门狗接管。**✅ 2026-10-12 实测收敛**:`-StartNow` 输出 `stopping stale watchdog pid 26900 / 19716` → `stopping stale daemon pid 53848` → `daemon up: pid 40360 (single instance, holds .runner.lock)`;之后看门狗日志 25 秒零增长、runner 回 `online`。切换瞬间新看门狗可能先失败 1~2 次(旧 daemon 刚被杀、锁尚未释放),10 秒重拉后自愈,属预期 |
|
||
| `static-gates` 报 `Cannot find: pwsh in PATH` | `shell: pwsh` = PowerShell 7(本机只有 Windows PowerShell 5.1) | 改 `shell: powershell` |
|
||
| 🔴 `doc-links` 钩子本地过、CI 挂 | 引用了 **gitignore 的运行时文件**(如 `.dsh/skills/serial-assistant/scripts/state/*.json`),本地存在、干净克隆里没有 | 该行加状态词「未创建/不存在」等(§4.1.2) |
|
||
| 任务跑满 72 小时被杀 | 计划任务默认 `ExecutionTimeLimit` | 设为 `0`(不限时长),见 §3.3 警示 |
|
||
| `config set log.file ...` 报 `unknown config key` | **v4 的 log 段无 `file` 键** | 用 `log.job.dir` 或输出重定向(§2.3) |
|
||
| **脚本报一堆 `源文本中包含无法识别的标记` / `字符串缺少终止符` / 中文显示成 `鐒跺悗鎵ц`** | 🔴 **`.ps1` 无 BOM,WinPS 5.1 按 GBK 解读 UTF-8** | **给脚本加 UTF-8 BOM**(§7.1) |
|
||
|
||
### 7.1 🔴 必读:给含中文的 `.ps1` 加 UTF-8 BOM
|
||
|
||
**这是本机实测踩过的坑**(2026-10-02)。Windows PowerShell 5.1 对**无 BOM** 的脚本文件
|
||
按**系统 ANSI 代码页**(简中 = **GBK / CP936**)解码:
|
||
|
||
- UTF-8 的中文字节被当 GBK 解 → 显示成 `鐒跺悗鎵ц` 这类乱码;
|
||
- 更糟的是,**乱码字符会撞坏字符串引号配对**(GBK 双字节的尾字节可能是 `"` 或 `\`)
|
||
→ 解析器报 `源文本中包含无法识别的标记` / `字符串缺少终止符: "` / `缺少右"}"`
|
||
—— 报错行号看起来毫无规律,**极易误判成逻辑问题**。
|
||
|
||
**修法**:给文件头加 3 字节 `EF BB BF`(UTF-8 BOM)。与仓库里带 BOM 的源文件
|
||
(`Drivers/BSP/UI/*`、`App/cfg_file.c`)是同一个道理。
|
||
|
||
```powershell
|
||
# 方法 A:PowerShell 重存为 UTF8BOM
|
||
$c = Get-Content "D:\Docker\act_runner\check_env.ps1" -Raw -Encoding UTF8
|
||
[System.IO.File]::WriteAllText("D:\Docker\act_runner\check_env.ps1", $c, (New-Object System.Text.UTF8Encoding($true)))
|
||
```
|
||
|
||
```bash
|
||
# 方法 B:直接前缀 BOM 字节(bash/Git Bash)
|
||
cd "/d/Docker/act_runner"
|
||
for f in check_env.ps1; do
|
||
printf '\xef\xbb\xbf' > "$f.tmp" && cat "$f" >> "$f.tmp" && mv "$f.tmp" "$f"
|
||
done
|
||
```
|
||
|
||
**校验**:用 PowerShell 解析器(不执行)确认语法干净:
|
||
|
||
```powershell
|
||
foreach ($f in @("check_env.ps1","run_runner_watchdog.ps1")) {
|
||
$errs = $null
|
||
$null = [System.Management.Automation.Language.Parser]::ParseFile(
|
||
"D:\Docker\act_runner\$f", [ref]$null, [ref]$errs)
|
||
if ($errs.Count) { "$f : FAIL -> $($errs[0].Message)" } else { "$f : OK" }
|
||
}
|
||
```
|
||
|
||
> 💡 替代方案:改用 **PowerShell 7+(`pwsh`)**,它默认按 UTF-8 读脚本、无需 BOM。
|
||
> 但本机只有 WinPS 5.1,故加 BOM 最省事。
|
||
>
|
||
> 💡 **纯 ASCII 的脚本不受影响,也不需要 BOM**。现行两个文件的口径:
|
||
> `check_env.ps1` 含中文注释 → **必须带 BOM**;`run_runner_watchdog.ps1`(由
|
||
> `scripts/ci_runner_bootstrap.ps1` 生成)**刻意写成纯 ASCII**,因此对编码免疫——
|
||
> 生成它的模板也要求纯 ASCII,理由见仓库 `AGENTS.md` 的编码铁律。
|
||
> 🗑 旧脚本 `deploy_runner_task.ps1` 已废弃(§3.2),不再作为示例对象。
|
||
|
||
### 7.2 日志位置与查看方式
|
||
|
||
**计划任务模式下看日志(两种)**:
|
||
|
||
| 日志 | 位置 |
|
||
|---|---|
|
||
| runner **自身** stdout/stderr | `D:\Docker\act_runner\log\runner-daemon.log`(由看门狗落盘,显式 `-Encoding utf8`;**2026-10-12 之前的行**夹带 PowerShell 包装噪声,见 §7.3) |
|
||
| **看门狗自身**动作(等 Gitea / 拉起 / 退出码 / 重拉) | `D:\Docker\act_runner\log\runner-watchdog.log`(ASCII) |
|
||
| **每个 job** 的详细日志 | `D:\Docker\act_runner\log\job\` |
|
||
| 🗑 历史遗留 | `log\runner-stdout.log` 是旧 `run_runner.cmd` 时代的文件,**已不再写入**;它内部混着 ASCII 与 UTF-16LE 两种编码(PS 5.1 `*>>` 默认 UTF-16LE),按 UTF-8 读会看到"每个字节后跟一个 NUL"的 `t i m e = ...` 形态,属正常现象不是新故障 |
|
||
|
||
**看日志**:v4 的 `log` 段**没有 `file` 键**,两种落盘方式:
|
||
|
||
```yaml
|
||
# 方式 A:按 job 落文件(推荐,防上报失败丢日志)
|
||
log:
|
||
level: info
|
||
job:
|
||
dir: "D:\\Docker\\act_runner\\log\\job"
|
||
```
|
||
|
||
```powershell
|
||
# 方式 B:前台运行 / 包装脚本里重定向。
|
||
# 🔴 不要写成 `.\gitea-runner.exe daemon > log 2>&1`:PS 5.1 的 `>` 就是 Out-File,
|
||
# 默认编码 UTF-16LE,且会把原生 stderr 包成 ErrorRecord(见 §7.3)。要这样写:
|
||
& '.\gitea-runner.exe' daemon 2>&1 | ForEach-Object { "$_" } |
|
||
Out-File -FilePath 'D:\Docker\act_runner\log\runner.log' -Append -Encoding utf8
|
||
```
|
||
|
||
任务本身的历史记录:**任务计划程序 → 任务计划程序库 → `GiteaActionsRunner` → 历史记录**;
|
||
或用 `Get-ScheduledTaskInfo -TaskName GiteaActionsRunner` 看 `LastTaskResult`。
|
||
|
||
### 7.3 🔴 原生命令 stderr 的两个 PS 5.1 陷阱(本仓库实测,均已修)
|
||
|
||
runner 的日志走 **stderr**,于是上面两个日志文件都栽在这两条上。两条都在
|
||
`scripts/ci_runner_bootstrap.ps1` 生成的启动器里修掉了,改脚本时勿回退:
|
||
|
||
**① 重定向的默认编码是 UTF-16LE。** `*>>` / `>` 在 PS 5.1 里就是 `Out-File`,默认
|
||
`Unicode`(UTF-16LE)。同一文件里若还有 `Add-Content -Encoding ascii` 写的行,就会
|
||
**一个文件两种编码**,按 UTF-8 读出来是 `t i m e = 2 0 2 6 ...`(每字节后跟 NUL)。
|
||
→ **永远显式写 `-Encoding utf8`**(别用 ascii:job/提交文本可能含非 ASCII)。
|
||
|
||
**② `2>&1` 把原生 stderr 包成 ErrorRecord。** 实测(用无效子命令产生真实 stderr):
|
||
|
||
| 写法 | 2 行真实日志写成 | 噪声 |
|
||
|---|---|---|
|
||
| `& $exe 2>&1 \| Out-File` | **8 行** | `gitea-runner.exe : ` 前缀 + 4 行 `CategoryInfo`/`FullyQualifiedErrorId` 块 |
|
||
| `& $exe 2>&1 \| ForEach-Object { "$_" } \| Out-File` | **2 行** | 0 |
|
||
| `& cmd.exe /c "… 2>&1" \| Out-File` | 2 行 | 0(等价,但要额外操心 cmd 对含空格路径的引号剥离) |
|
||
|
||
即**每 4 行日志有 3 行是 PowerShell 噪声**,而且它就出现在文档让大家第一个打开的文件里。
|
||
`ErrorRecord.ToString()` 就是原始 stderr 行,所以逐项字符串化即可去掉装饰,
|
||
且 `$LASTEXITCODE` 不受影响(看门狗靠它打印退出码)。
|
||
|
||
> 💡 顺带一条同类教训:**别用"日志尾部还有没有错误串"判断健康状态**。日志不轮转,
|
||
> 修好之后旧失败仍留在尾部几十行内——`-Status` 曾据此对健康 runner 报假警。
|
||
> 现在改看**进程存活时长**(活过 10 秒重拉间隔就不可能在重启循环里)。
|
||
|
||
---
|
||
|
||
## 8. 日常维护
|
||
|
||
- **每周(或每次改完 Gitea/机器重启后)体检**:`scripts\ci_runner_bootstrap.ps1 -Status`——
|
||
计划任务 / daemon 进程 / Gitea 可达 / 三个机器级 `DTU_*`,四项全绿才算 CI 活着;
|
||
- **升级 runner**:`Stop-ScheduledTask GiteaActionsRunner` → 换 `gitea-runner.exe` →
|
||
`Start-ScheduledTask GiteaActionsRunner`(注册信息在 `.runner`,配置在 `config.yaml`);
|
||
若换了目录/地址,重跑 `ci_runner_bootstrap.ps1` 重新生成看门狗与计划任务;
|
||
- **改了工具链探测逻辑**:Python 侧只有一处事实源 **`scripts/ci_find_python.ps1`**
|
||
(两个 job 共用),本地 `scripts/ci_local.py` 的候选表须与它**同序**;
|
||
MSVC 探测仍是 workflow 内联 + `test/host/common/unit_test_harness.py` **两处一致**;
|
||
- **钩子集变化**(`.pre-commit-config.yaml` 增删钩子):CI 自动跟随,无需改 workflow;
|
||
但**必须核对条数**——`pre-commit run --all-files` 只跑 `pre-commit` stage,
|
||
`commit-msg` stage 的钩子(gitlint)不在其中,靠 `ci_gitlint_range.ps1` 单独守(见 §7);
|
||
- **`@build` / 板端测试套件 / 主机侧回归**:永远留在本地,不要往 CI 加(需 Keil 授权与真机);
|
||
- **runner 长时间不可用**时:CI 只是"多一道网",**提交前的本地 `ci_local.py` 仍是 静态门禁的唯一保障**,
|
||
不能因"已经配了 CI"放宽(见 [docs/任务规划/登记簿.md](../任务规划/登记簿.md) 的条目「CI 远端闭环未验证」)。
|
||
|
||
---
|
||
|
||
## 9. 一键检查脚本(可选)
|
||
|
||
存为 `D:\Docker\act_runner\check_env.ps1`,注册前跑一遍:
|
||
|
||
```powershell
|
||
Write-Host "=== Gitea 可达性 ===" -ForegroundColor Cyan
|
||
try {
|
||
$v = (Invoke-RestMethod -Uri "http://127.0.0.1:3001/api/v1/version" -TimeoutSec 8).version
|
||
Write-Host " OK Gitea $v (127.0.0.1:3001)" -ForegroundColor Green
|
||
} catch { Write-Host " FAIL 连不上 Gitea: $_" -ForegroundColor Red }
|
||
|
||
Write-Host "=== 工具链 ===" -ForegroundColor Cyan
|
||
foreach ($c in @("python", "cppcheck", "git")) {
|
||
$p = Get-Command $c -ErrorAction SilentlyContinue
|
||
if ($p) { Write-Host " OK $c -> $($p.Source)" -ForegroundColor Green }
|
||
else { Write-Host " FAIL 缺 $c" -ForegroundColor Red }
|
||
}
|
||
$pc = (python -m pre_commit --version 2>&1)
|
||
if ($LASTEXITCODE -eq 0) { Write-Host " OK $pc" -ForegroundColor Green }
|
||
else { Write-Host " FAIL 缺 pre-commit" -ForegroundColor Red }
|
||
|
||
Write-Host "=== DTU_* 机器级变量(§4.1.3)===" -ForegroundColor Cyan
|
||
foreach ($n in @("DTU_PRECOMMIT_PYTHON", "DTU_CPPCHECK", "DTU_MSVC_CL")) {
|
||
$v = [Environment]::GetEnvironmentVariable($n, "Machine")
|
||
if ($v) { Write-Host " OK $n = $v" -ForegroundColor Green }
|
||
else { Write-Host " -- $n(未设置:workflow 将走自动发现)" -ForegroundColor Yellow }
|
||
}
|
||
|
||
Write-Host "=== MSVC cl.exe(DTU_MSVC_CL 优先,否则标准布局推导)===" -ForegroundColor Cyan
|
||
if ($env:DTU_MSVC_CL -and (Test-Path $env:DTU_MSVC_CL)) {
|
||
Write-Host " OK $($env:DTU_MSVC_CL)(来自 DTU_MSVC_CL)" -ForegroundColor Green
|
||
} else {
|
||
$pf = ${env:ProgramFiles}
|
||
$pf86 = ${env:ProgramFiles(x86)}
|
||
$roots = @(
|
||
(Join-Path $pf86 'Microsoft Visual Studio\2019\BuildTools'),
|
||
(Join-Path $pf86 'Microsoft Visual Studio\2019\Community'),
|
||
(Join-Path $pf86 'Microsoft Visual Studio\2019\Professional'),
|
||
(Join-Path $pf 'Microsoft Visual Studio\2022\BuildTools'),
|
||
(Join-Path $pf 'Microsoft Visual Studio\2022\Community'),
|
||
(Join-Path $pf 'Microsoft Visual Studio\2022\Professional'),
|
||
(Join-Path $pf 'Microsoft Visual Studio\2022\Enterprise'),
|
||
(Join-Path $pf 'Microsoft Visual Studio\18\BuildTools'),
|
||
(Join-Path $pf 'Microsoft Visual Studio\18\Community')
|
||
)
|
||
$hit = $null
|
||
foreach ($r in $roots) {
|
||
if (Test-Path $r) {
|
||
$hit = Get-ChildItem -Path $r -Recurse -Filter 'cl.exe' -ErrorAction SilentlyContinue |
|
||
Where-Object { $_.FullName -like '*Hostx64\x64*' } | Select-Object -First 1
|
||
if ($hit) { break }
|
||
}
|
||
}
|
||
if ($hit) { Write-Host " OK $($hit.FullName)" -ForegroundColor Green }
|
||
else { Write-Host " FAIL 未找到 MSVC (Hostx64\x64\cl.exe);可设机器级 DTU_MSVC_CL" -ForegroundColor Red }
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 10. 最小操作清单(TL;DR)
|
||
|
||
```powershell
|
||
# 1) 下载(v4.0.1;注意仓库名是 gitea/runner,二进制名是 gitea-runner)
|
||
New-Item -ItemType Directory -Force -Path "D:\Docker\act_runner" | Out-Null
|
||
cd "D:\Docker\act_runner"
|
||
Invoke-WebRequest -Uri "https://gitea.com/gitea/runner/releases/download/v4.0.1/gitea-runner-4.0.1-windows-amd64.exe" -OutFile "gitea-runner.exe"
|
||
|
||
# 2) 注册(token 从网页 Settings→Actions→Runners 拿;instance 用回环)
|
||
.\gitea-runner.exe register --no-interactive `
|
||
--instance "http://127.0.0.1:3001" `
|
||
--token "<粘贴 token>" `
|
||
--name "win-stm32-ci" `
|
||
--labels "windows:host"
|
||
|
||
# 3) 前台试跑,确认能连上
|
||
.\gitea-runner.exe daemon # 看到 "labels: [windows] ... declare successfully" 即成功,Ctrl+C 停
|
||
|
||
# 4) 常驻 —— ⚠️ daemon 不是服务型程序,且首次 Declare 失败就直接退出进程
|
||
# 所以必须【看门狗循环 + 开机计划任务】,一条命令搞定(管理员 PowerShell,在仓库根目录):
|
||
powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -SetEnv -StartNow
|
||
|
||
# 体检(四项:计划任务 / daemon 进程 / Gitea 可达 / 机器级 DTU_*)—— 须管理员:
|
||
powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -Status
|
||
# 干跑(只打印将要写入的启动器与任务对象,不落盘不注册)—— 免管理员:
|
||
powershell -ExecutionPolicy Bypass -File scripts\ci_runner_bootstrap.ps1 -DryRun
|
||
|
||
# 5) 工具链必须对 SYSTEM 可见(runner 以 SYSTEM 跑,看不见交互用户的 PATH 与用户级 site)
|
||
# 正式通道 = 机器级 DTU_* 变量,上一步的 -SetEnv 已自动发现并写入;需手工覆盖时:
|
||
# (管理员 PowerShell;下面是本机实测现值,换机按实际路径改)
|
||
# [Environment]::SetEnvironmentVariable('DTU_PRECOMMIT_PYTHON', 'C:\ProgramData\Anaconda3\python.exe', 'Machine')
|
||
# [Environment]::SetEnvironmentVariable('DTU_CPPCHECK', 'C:\Program Files\Cppcheck\cppcheck.exe', 'Machine')
|
||
# [Environment]::SetEnvironmentVariable('DTU_MSVC_CL', '<本机 x64 cl.exe 完整路径>', 'Machine')
|
||
# 兜底通道(只有变量没设时才会走到):把 pre-commit 装进【全局】site-packages,见 §4.1.1
|
||
# ⚠️ 改过任何环境变量都必须 Stop 再 Start 任务——环境块是任务启动时的快照
|
||
|
||
# 6) 复验(-s = 模拟 SYSTEM 视角,必须过)
|
||
& 'C:\ProgramData\Anaconda3\python.exe' -s -m pre_commit --version # pre-commit 4.6.2
|
||
|
||
# 7) 回网页确认 runner 为 Idle → 触发 CI
|
||
# push 触发(注意:纯 .md 改动在 workflow 的 paths-ignore 里,不会触发);
|
||
# 手动触发:
|
||
# curl -u "<user>:<pass>" -X POST -H "Content-Type: application/json" `
|
||
# -d '{"ref":"v2-on-ui"}' `
|
||
# "https://gitea.bitnasdaq.vip/api/v1/repos/wandering1/STM32F4-Base/actions/workflows/ci.yml/dispatches"
|
||
```
|
||
|
||
> 🗑 本清单第 4 步在 2026-10-02 版里是"计划任务跑**单次** daemon"(`deploy_runner_task.ps1`)。
|
||
> 2026-10-11 实测证明单次 daemon 首次 Declare 失败即退出、计划任务的失败重启救不到它,
|
||
> 故已改写为看门狗方案;旧脚本的失效机理见 §3.2。
|
||
|
||
> **CI 验收标准**:两个 job 都要 `status=1`。**口径是规则,不是具体数字**(2026-10-12):
|
||
> `static-gates` 报 **定义数 − 1 条钩子**(gitlint 属 `commit-msg` stage,不进 `--all-files` 报告,
|
||
> 见 §7),其中 `mem-numbers` 在无 `.map` 时为 `Skipped`(本机唯一常见跳过项);另有提交信息
|
||
> 门禁 `::notice::gitlint: N checked, 0 failed`。`host-unit-tests` 报
|
||
> **`<N> runner(s), A/A assertions passed, fail=0`**,`N` = `test/host/` 下 `run_unit_test.py` 个数。
|
||
> 实测快照:`master` 18 定义 / 17 报告、3 套件 / 288 断言;`v2-on-ui` 26 定义 / 25 报告、10 套件。
|
||
> 🗑 更早快照(勿当基准):run#8(2026-10-02)当时是"14 个钩子全 Passed + 39/39 单测"。
|
||
> **判定只看 `fail=0`、`failed=0` 与 A==B**;增删钩子或单测后按上面的规则重算,别抄历史数字。
|
||
|
||
**配套脚本**:
|
||
|
||
| 脚本 | 位置 | 用途 | 需要管理员 |
|
||
|---|---|---|---|
|
||
| `ci_runner_bootstrap.ps1` | **仓库 `scripts/`** | **现行唯一部署/体检入口**(`-Status` / `-DryRun` / `-SetEnv -StartNow`) | `-Status`、`-SetEnv -StartNow` 需要 |
|
||
| `check_env.ps1` | `D:\Docker\act_runner\` | 环境自检(Gitea 可达 / 工具链 / MSVC / 任务状态) | 否 |
|
||
| `run_runner_watchdog.ps1` | `D:\Docker\act_runner\` | **看门狗启动器**,由 bootstrap 生成,是计划任务的动作目标。**纯 ASCII,不需要 BOM** | 由任务以 SYSTEM 跑 |
|
||
| 🗑 `deploy_runner_task.ps1`、`run_runner.cmd` | `D:\Docker\act_runner\` | **已废弃**(单次 daemon,§3.2)。仍在磁盘上,建议改名加 `.deprecated` 或删除,免得误执行把看门狗任务覆盖回单次任务 | — |
|
||
|
||
> 更早基于 Windows 服务的 `deploy_runner.ps1` / `install_service.ps1` 已删除
|
||
> —— 那条路走不通(§3.0)。
|
||
|
||
---
|
||
|
||
## 变更记录
|
||
|
||
| 日期 | 变更 |
|
||
|---|---|
|
||
| 2026-10-25 | 🔒 **A-5 残余销项联动(§0.0 重写)**:体积门禁由"CI 不守"改为**同码凭证强制**——本地 `@build` 0/0 后 `ram_baseline.py --update` 盖 `scripts/size_attestation.json`,静态门禁作业末尾 `check_size_gate.py --ci` 复核同码性 + 红线复算(runner 无需 Keil);主干 push 拦红、其余事件 warn-only;`mem-budget` 补进 SKIP 名单诚实显示。指纹取 git 索引 blob(跨 checkout 稳定,修 7c4628b);三项仍守不住的口径唯一正文 = 脚本 docstring |
|
||
| 2026-10-12 | 🐛 **第一次真跑出 run,当场暴露 gitlint 门禁空转**:把本轮修复推到新分支 `v2-on-ui-ci` 后拿到 task 31/32——**主机侧纯逻辑单测成功**(`10 runner(s), 647/647 assertions passed, fail=0`,与本地逐字一致),**静态门禁失败**却与提交信息毫无关系:`exit code: 254` + `Missing git configuration: please set user.name`。根因是 `actions/checkout` 把 HOME 指向每次任务的临时目录、SYSTEM 账号没有 git 身份,gitlint 在读到规则之前就配置错误退出;旧脚本还把失败原因**写死**成"标题超 100 字符",加上该步 `exit 1` 让后面 26 个钩子**一个都没跑**。修法:脚本先探测身份,缺失就用 `GIT_CONFIG_COUNT/KEY/VALUE` 注入一次性身份(不写文件、不碰开发者机器),并区分 254(门禁没跑起来 → `exit 2`)与真实违规(→ `exit 1`)。另记一条新坑:**新建分支的首次 push 不产生 run**(§7 排障表 ④) |
|
||
| 2026-10-12 | 🔀 **随 `master` 合入 `v2-on-ui`:数字口径改为分支中立**。本指南原先写死 master 的数字(18 钩子 / 17 报告、3 runner / 288 断言),而 `v2-on-ui` 是 **26 钩子 / 10 套件**——同一篇文档在两个分支上必然有一个是错的。§6 验收第 8 行、§7 排障表、§10 验收标准三处改为讲规则(**报告数 = 定义数 − 1**;**runner 数 = `test/host/` 下 `run_unit_test.py` 个数**)+ 分分支实测快照。本次合并只带文档与脚本,**零 C 代码改动**;合并后本分支 `@build` 0 错 0 警、`ci_local.py` 全绿(静态门禁 25 钩子 + 主机侧纯逻辑单测 10 runner / 647 断言) |
|
||
| 2026-10-12 | ✅ **双看门狗缺陷实测收敛** + 修掉日志里的 PS 5.1 stderr 包装噪声与 `-Status` 假警。① 管理员 `-StartNow` 杀掉 3 个残留(孤儿看门狗 26900/19716 + 占锁 daemon 53848)→ `daemon up: pid 40360 (single instance)`,监管链 `svchost → 看门狗 → daemon`,看门狗日志 25 秒零增长、runner 回 online。② 新坑:daemon 日志写 **stderr**,PS 5.1 的 `2>&1` 把每行包成 ErrorRecord → `gitea-runner.exe : ` 前缀 + 4 行 `CategoryInfo` 块,实测 2 行真日志写成 8 行(**每 4 行有 3 行是噪声**,且就在文档让大家第一个打开的文件里)。启动器改为 `2>&1 \| ForEach-Object { "$_" } \| Out-File -Encoding utf8`(实测与 cmd 包装等价但免引号剥离坑,`$LASTEXITCODE` 不受影响);§7.2 方式 B 的示例原本正是 `> log 2>&1`(UTF-16LE + 包装双坑),已改;新增 §7.3 汇总这两条。③ `-Status` 两处诚实化:非管理员**读不到 SYSTEM 进程命令行**(`CommandLine` 为 null → 过滤器静默匹配 0 个),改为明说"需提权"而不是谎报"没有看门狗";重启循环判据不再靠"日志尾部有没有错误串"(文件不轮转,修好后旧失败仍在尾部 → 对健康 runner 报假警),改看 **daemon 进程存活时长**(CIM `CreationDate` 非管理员可读,`Process.StartTime` 对 SYSTEM 进程不可读) |
|
||
| 2026-10-12 | 🔴 **实测揪出"双看门狗抢锁"缺陷并修复** + 机器侧清理。① 现象:runner 网页 online、`-Status` 说 daemon 在跑,但 `runner-watchdog.log` 每 10 秒刷 `starting daemon → daemon exited, exit code 1`,daemon 日志 `runner file is already locked by another process`。② 根因:`Unregister-ScheduledTask` **不停旧实例启动的进程**——13:22 重跑 bootstrap 时旧看门狗(12:53 起)成孤儿并继续养着旧 daemon、占住 `.runner.lock`,新看门狗永远抢不到锁,任务实际谁也没管,日志以约 20 MB/天在刷。③ 修复:`ci_runner_bootstrap.ps1` 的 `-StartNow` 现在**先停孤儿看门狗与残留 daemon 再启动**并回显单实例确认;`-Status` 新增重复实例计数与锁冲突检测。④ 机器侧清理:`config.yaml` 头部那句**错误指引**("常驻改用 Windows sc.exe / New-Service",正是 §3.0 证伪的路)改为看门狗口径并指向本文;删除已废弃的 `deploy_runner_task.ps1` 与 `run_runner.cmd`(§3.2),`check_env.ps1` 指向它们的提示同步改指 bootstrap。⑤ 核实:`config.yaml` 改后 YAML 可解析且**所有配置值与改前逐项一致**(只动注释);`check_env.ps1` 带 BOM 且语法 0 错;删除前确认计划任务动作目标是 `run_runner_watchdog.ps1`,删除不影响在跑 runner |
|
||
| 2026-10-12 | 🧹 **单一口径清理(以本机实测现状为准)**:合并稿初版对 4 处分歧采取"两说并存 + 衔接注",本次统一。① 注册口径——`act_runner v0.2.11` + `C:\act_runner` + 公网域名 instance 一段**删除可复制命令**,只留"勿照抄"警示(核实 `C:\act_runner` 至今不存在,本机是 `gitea-runner v4.0.1` + `D:\Docker\act_runner` + `127.0.0.1:3001`);② `config.yaml` 示例 `fetch_timeout` 由 `5m0s` 改为磁盘实际值 **`1m0s`**、`timeout` 统一 `30m0s`,并补"不得超过 RPC timeout(1m0s)";③ 常驻口径统一为看门狗——§3.2 `deploy_runner_task.ps1` 标为**已废弃**并说明失效机理(单次 daemon = 正常退出,失败重启永不触发),§3.3 手动等价命令**重写为看门狗版任务对象**(旧版会教人手搓出坏配置),§10 TL;DR 第 4 步改指 `ci_runner_bootstrap.ps1`;④ 数字口径——主机侧纯逻辑单测期望值更新为 **288/288**(run#8 的 39/39 与 278/278 标注为历史快照,判定只看 `fail=0` 与 A==B),钩子数按现行 18 定义 / 17 报告。另同步:健康三查与排障表改用现行日志名(`runner-daemon.log` / `runner-watchdog.log`,`runner-stdout.log` 标为遗留且说明其混编码)、BOM 示例对象换成 `check_env.ps1` 并注明看门狗是纯 ASCII 无需 BOM、配套脚本表补 `run_runner_watchdog.ps1` 并标出磁盘上仍存的废弃脚本建议删除、补 `Next Run Time` 为空属正常的说明 |
|
||
| 2026-10-12 | **文档合并**:本次把 `CI配置指南.md` 与 `Gitea_Runner部署指南.md` 合并为本篇(`docs/CI/远程CI完整指南.md`)并迁入 `docs/CI/`,去重(runner 注册 / config.yaml / 装软件 / 排障表两篇均有)、补入 runner 看门狗与机器级变量实测教训;相对链接按新位置改写,原两篇互引改为本文内部引用 |
|
||
| 2026-10-11 | **bootstrap 自身修复**:`StartWhenAvailable` 误设到**触发器**上(该属性属**任务设置**,触发器没有)→ 脚本在"已注销旧任务、尚未注册新任务"之间抛 `SetValueInvocationException`,把 runner 任务删没了。现改为①先构建 action/trigger/settings、**后**注销旧任务,②`Register-ScheduledTask` 失败时显式告警"旧任务已删、CI 裸奔",③新增 **`-DryRun`**(免管理员即可全量校验任务对象),④`-Status` 在非管理员下额外提示"看不见任务 ≠ 任务不存在"。同时更正根因:旧任务其实存在,但跑的是**单次** `daemon`——daemon 属"正常退出"而非"失败",计划任务的失败重启**救不到**它,必须用看门狗循环重拉 |
|
||
| 2026-10-11 | **修"很多任务不执行"(runner 静默停摆)+ 补 gitlint 门禁**:① §2.4 重写——`gitea-runner` v4 的 `daemon` **无服务支持、首次 Declare 失败即退出进程**(日志实证),Gitea/机器重启一次 CI 就停摆,**run 16~21 共 12 个 job 零日志**(`cancelled`/`queued`,叠加 `cancel-in-progress` 后表现为"任务凭空消失"而非失败);新增 **`scripts/ci_runner_bootstrap.ps1`**(等 Gitea 就绪 + 崩溃自拉起的看门狗 + 开机计划任务 + `-SetEnv` 写机器级 `DTU_*` + `-Status` 只读体检)。② §3.1 重写——SYSTEM 可见性三重坑(PATH python 是 msys64 无 pre-commit / 用户级 site 对 SYSTEM 不可见 / `.venv` 已 gitignore)是 run 14~15 静态门禁全灭根因(`33a4aa4` 把候选收窄成这两个、又删了 cppcheck 目录兜底,而机器级 `DTU_*` 从未设置);探测收进 **`scripts/ci_find_python.ps1`**(两 job 共用、可离线自测、失败列出全部候选)并恢复机器级安装兜底。③ 新增 **gitlint 提交信息门禁**:钩子定义数比 CI 报告条数多 **1**(当时 17/16,master 并入 `test-cppcheck` 后为 18/17),缺的正是 gitlint——其 `stages: [commit-msg]` 声明在**上游** `.pre-commit-hooks.yaml`(`--all-files` 跑不到,且表现为全绿 → 长期无人发现)→ **`scripts/ci_gitlint_range.ps1`** 逐条校验本次 push/PR 范围(消息文件须写在仓库内 + UTF-8 无 BOM)。④ `GITHUB_ENV/PATH/OUTPUT` 写入统一显式 `-Encoding ascii`(PS 5.1 `Out-File -Append` 默认 **UTF-16LE**,实测落盘 `42 00 42 00 42 00 3D 00 31 00`)。§5 清单 8→10 条、§6 排障表 +5 行、§7 维护项重写 |
|
||
| 2026-10-02 | **S3 + S14**:新增 §0.0「CI 不覆盖的体积门禁」醒目声明;新增 §4.4「产物存档与结果汇总」(零依赖 `Tee-Object` + `$GITHUB_STEP_SUMMARY` + `::notice::` 汇总行,不用 `actions/upload-artifact`;记录 `$ErrorActionPreference='Stop'` 与 `2>&1 \| Tee-Object` 的陷阱及处置)。§5 验证清单(7→8 条)与 §6 排障表同步更新 |
|
||
| 2026-10-02 | **workflow 接入 `DTU_*` 机器级变量**(`本地工具路径环境变量方案.md` 实施):`static-gates` 的 Python 探测改为 `DTU_PRECOMMIT_PYTHON`(显式,无效即硬失败)→ PATH → `.venv`(删除 `C:\ProgramData\anaconda3` 固定候选);cppcheck 改为 `DTU_CPPCHECK` 优先 → PATH(**移除 `C:\Program Files\Cppcheck` 固定回退**);`host-unit` 的 MSVC 探测改为 `DTU_MSVC_CL` 优先 → `%ProgramFiles*` 推导的标准根(与 `unit_test_harness.py` 同步,不再写死盘符);新增 §5.1.3 机器级变量设置方法(`Machine` scope + 重启任务)、§5.2 与 check_env 样例、排障表 cppcheck 行、TL;DR 5b 步骤。§5.1.1 的报错原文标注为历史记录 |
|
||
| 2026-10-02 | ✅ **首次 CI 全绿(run#8:静态门禁 + 主机侧纯逻辑单测双 job Success)**。定位并修复最后两个拦路虎:① **§5.1.1 用户态 site-packages 陷阱**——`pre-commit` 只装在 `%APPDATA%\Roaming\Python\...`(SYSTEM 不可见);且提权 `pip install --no-user` 会因 pip 扫**整个 `sys.path`**、见 user-site 已满足而**静默跳过**全局安装(9 依赖中唯 `cfgv` 因版本相同被跳过→`-s` 下 `ModuleNotFoundError`)。修法:提权 + `--force-reinstall --no-deps` 强制落全局,并以 `-s` 复验。② **§5.1.2 doc-links 假阳性**——文档引用了 gitignore 的运行时文件 `.dsh/skills/serial-assistant/scripts/state/meta.json`,本地存在、干净克隆无 → 按《文档规范》§3.3 加同状态词「运行时未创建」。排障表补 6 条(pre-commit 不可见 / GBK 中文 / pwsh / cppcheck / doc-links / MSVC 降级)。新增配套脚本 `install_precommit_global.ps1`、`install_precommit_global2.ps1`、`fix_cfgv_global.ps1` |
|
||
| 2026-10-02 | 🔴 **常驻方式重大修正(实测推翻)**:`Start-Service gitea-runner` 必然失败。经二进制逆向确认 `gitea-runner v4.0.1` **无任何服务管理代码**(命令集仅 `bug-report/cache-server/config/daemon/exec/register`,无 `service.go`;`OpenSCManagerW` 等符号来自 Docker SDK,非服务安装代码),`daemon` 是纯控制台程序、不向 SCM 回报 `SERVICE_RUNNING`。**§4 全节重写为「任务计划程序」方案**(新增 §4.0 根因分析、§4.2 `deploy_runner_task.ps1`、§4.3 手动命令 + 72h 超时警示、§4.5 清理遗留服务、§4.6 提权);删除失效的 `deploy_runner.ps1`/`install_service.ps1`;排障表补「错误 1053」「LastTaskResult 非 0」「72 小时被杀」3 条;拓扑图/§5.1/§7 日志/§10 TL;DR 同步更新。另修 `config.yaml` 的 `fetch_timeout` 5m→1m(超出 RPC 1m 会被截断并告警) |
|
||
| 2026-10-02 | **修 `.ps1` 编码坑(实测)**:无 BOM 的 UTF-8 脚本被 WinPS 5.1 按 GBK 解读 → 中文乱码(`鐒跺悗鎵ц`)并撞坏引号配对,报 `源文本中包含无法识别的标记`/`字符串缺少终止符`。**新增 §7.1「给含中文的 `.ps1` 加 UTF-8 BOM」**(两种修法 + 解析器校验命令);排障表补该条目;配套脚本已统一加 BOM |
|
||
| 2026-10-02 | **按实测修正为 v4.0.1**:act_runner 已更名 **`gitea-runner`**,发布页迁至 `gitea.com/gitea/runner`。**v4 移除 `daemon install/start/stop/status`**;**v4 `log` 段无 `file` 键**(§3 改用 `log.job.dir`);补 §1.3 命令集对照、§6.1 MySQL 直查核验、排障表(`accepts at most 0 arg(s)` / `unknown config key log.file` 等) |
|
||
| 2026-10-02 | 初版:针对**本机 Gitea 原生部署**(`D:\Docker\Gitea\gitea.exe` + Windows 服务 + `127.0.0.1:3001`)定制 Runner 端部署全流程。核心要点:`--instance` 用回环地址、`--labels "windows:host"` 必须精确匹配 `runs-on`、PATH 运行账户坑、MSVC 路径约束、内网 action 拉取的零依赖改法 |
|
||
| 2026-10-01 | **修正 `DEFAULT_ACTIONS_URL` 说明**(原 §1.2 示例误导):新增 §1.2.1 明确它控制的是 **action 下载源**(默认 `github.com`;`self` = 从自己实例拉且需先镜像 action),**不是填自己的 Gitea 地址**;§4.3 同步修正(原文误写"gitea.com 代理"),并补零依赖写法为内网首选。依据:Gitea 官方 FAQ + config-cheat-sheet |
|
||
| 2026-10-01 | 初版:Gitea Actions 配置全流程(服务端开关 / Windows runner 注册 / runner 软件环境 / workflow 位置与触发 / 内网 action 拉取 / 端到端验证清单 / 故障排查 / 日常维护) |
|