# Pangolin CI Runner 记录 > gitea Actions self-hosted runner 设置记录。CI 工作流见 `.gitea/workflows/ci.yml`(9 个 job)。 ## 决定:用 1 个 runner | 项 | 值 | |---|---| | 名称 | **mac-pangolin-2**(gitea id 6) | | 级别 | **项目级**(绑 `wangjia/pangolin`,只服务本仓库) | | 机器 | mac 本机(`/Users/wangjia/bin/act_runner` v0.6.1) | | 执行模式 | **host**(非容器) | | label | `nas`(即 `runs-on: nas`) | | 连接 | 经 jiu 的 TCP relay `127.0.0.1:13000` → NAS gitea `192.168.3.200:3000` | ### 为什么是 host 模式 + mac - ci.yml 的 9 个 job **全是 docker-in-docker**(每个 job 内部 `docker run …` 拉 golang/flutter/redis 等镜像)。container 模式(像 jiu 的 `ubuntu-latest:docker://catthehacker`)在非特权容器里跑不了 DinD,所以必须 **host 模式**。 - mac 的 Docker Desktop 能跑 Linux 容器,host 模式下 `docker run ` 可用。 - 理想归宿其实是 **NAS(Linux)host runner**(给 NAS 现有 runner 加 `nas:host` label),mac 只是当前落点。 ### 为什么要 relay(13000) mac 上的 Shadowrocket 网络扩展会拦截 act_runner 这个 Go 二进制的流量,直连 NAS gitea 失败。jiu 已有一个常驻 python TCP relay(`~/bin/tcp_relay.py`,`127.0.0.1:13000 → 192.168.3.200:3000`),act_runner 连本地 relay 绕过。pangolin runner **复用**这个 relay。 ## 配置文件:`~/.act_runner_pangolin2_config.yaml` ```yaml log: level: info runner: file: /Users/wangjia/.act_runner_pangolin2 # 注册凭证(.runner),勿入 git capacity: 1 timeout: 3h labels: - "nas:host" cache: enabled: true host: workdir_parent: /Users/wangjia/.cache/act-pangolin2 ``` ## 持久化(launchd 开机自启) 仿 jiu 的 `com.jiu.act-runner`。文件在仓库 `deploy/runner/`: - `act_runner_pangolin_start.sh` —— 启动脚本(等 jiu 的 relay 13000 就绪 → 起 daemon → 退出自重启)。 - `com.pangolin.act-runner.plist` —— launchd(RunAtLoad + KeepAlive)。 **部署步骤**(一次性): ```bash # 1. 文件就位 cp deploy/runner/act_runner_pangolin_start.sh ~/bin/ && chmod +x ~/bin/act_runner_pangolin_start.sh cp deploy/runner/com.pangolin.act-runner.plist ~/Library/LaunchAgents/ # 2. 停掉临时 daemon(否则和 launchd 起的抢同一注册) pkill -f act_runner_pangolin2_config.yaml # 3. 加载常驻服务(开机自启 + 崩溃自重启) launchctl load ~/Library/LaunchAgents/com.pangolin.act-runner.plist ``` 之后 mac 重启 / 退出登录都会自动拉起。日志 `/tmp/act_runner_pangolin2.log`。 卸载:`launchctl unload ~/Library/LaunchAgents/com.pangolin.act-runner.plist`。 > 依赖 jiu 的 relay(13000)常驻:jiu 的 `com.jiu.act-runner` 在 → relay 在 → 本 runner 能连 NAS gitea。 > ⚠️ **持久化激活状态**:上面三条一次性命令含 `pkill`/`launchctl`,需在 mac 本机手动执行 > (Claude 跑不了)。未激活前 runner 是临时 daemon,mac 重启即停。激活后用下面的离线告警兜底。 ## 韧性:runner 离线告警(单点兜底) CI 整条吊在这一台 mac 上,mac 关机/掉线 → CI 全停且**无人知道**。脚本 `deploy/runner/runner-deadman.sh`(仿 `bootstrap/monitor/deadman-watch.sh`)放在**另一台 常在线主机**上,每 3 分钟查 gitea Actions API 里 `mac-pangolin-2` 的 `status`,连续 offline 到阈值 → Telegram 告警,恢复也通知;API 取不到时跳过不误报。 **部署**(在常在线主机,如 jiu 的 mac / NAS): ```bash cp deploy/runner/runner-deadman.sh /usr/local/bin/ && chmod +x /usr/local/bin/runner-deadman # /etc/pangolin-runner-deadman.env(不入 git): # GITEA_URL=http://127.0.0.1:13000 GITEA_TOKEN= # GITEA_REPO=wangjia/pangolin RUNNER_NAME=mac-pangolin-2 # TELEGRAM_BOT_TOKEN=... TELEGRAM_CHAT_ID=... FAIL_THRESHOLD=2 # cron(每 3 分钟): echo '*/3 * * * * /usr/local/bin/runner-deadman >/dev/null 2>&1' | crontab - ``` mac 宿主可改 launchd LaunchAgent(`StartInterval` 180 + 同一脚本),仿 `com.pangolin.act-runner.plist`。响应字段已对照真 API 核验: `{"runners":[{"name":"mac-pangolin-2","status":"online",...}]}`。 ## 已删除:mac-pangolin(第一次注册的,id 5) 第一次注册的 runner(label `nas`,config `~/.act_runner_pangolin_config.yaml`)。job 调试时已从 **gitea 删除注册**(API DELETE,204)。本机残留待清: ```bash # orphan daemon 进程(拿不到任务、空转报 "unregistered runner",无害但占进程) pkill -f 'act_runner_pangolin_config.yaml' # 只匹配 orphan,不碰 -2 和 jiu # orphan 文件 rm -f ~/.act_runner_pangolin ~/.act_runner_pangolin_config.yaml rm -rf ~/.cache/act-pangolin ``` ## 验证 - gitea → 仓库 Settings → Actions → Runners 应只见 **mac-pangolin-2**(online)。 - 向 `main` 开 PR 触发 10 个 job(后增覆盖率闸 + go-integration);首次全绿见 run #298(commit `65bd7c3`)。