# Gitea Actions Runner 与保护分支运维手册

> **版本**: v1.1  
> **最后更新**: 2026-05-18  
> **适用范围**: `FFAIWorkspace/workspace` 仓库、Gitea 主机 `43.130.59.228`

---

## 1. 目标

- 记录当前 Gitea、Actions runner 与保护分支的实际部署状态。
- 为后续维护 `agent-assets-check`、排查 runner 故障、调整保护分支规则提供单一事实来源。

---

## 2. 当前环境

### Gitea 主机

- 主机：`43.130.59.228`
- 系统：`Ubuntu 24.04`
- SSH：
  - 系统 SSH：`ssh ubuntu@43.130.59.228`
  - Gitea SSH：`ssh -p 2222 git@43.130.59.228`

### Gitea 服务

- 版本：`1.25.4`
- 服务名：`gitea.service`
- 启动方式：systemd 直接运行
- 可执行文件：`/usr/local/bin/gitea`
- 配置文件：`/etc/gitea/app.ini`
- 数据库：`/var/lib/gitea/data/gitea.db`
- 仓库根目录：`/var/lib/gitea/data/git/repositories`
- 站点地址：`http://43.130.59.228/`

---

## 3. Actions Runner 当前状态

历史关键节点：

- **2026-04-30** 主力 runner 迁出 Gitea 主机（issue #169），原 host runner 留作待命兜底
- **2026-05-18** 加第二台 dedicated runner（`dedicated-runner-2` on `43.166.182.155`），缓解单 runner 容量瓶颈（之前队列堆 5+ runs、Setup Node.js 跑 8 分钟）

### 主力 A：dedicated-runner-1（专用 runner 主机 #1）

- 主机：`43.166.205.48`（4c16G / Ubuntu 24.04，独立机）
- SSH：`ssh ubuntu@43.166.205.48`（sudo 免密）
- runner 名称：`dedicated-runner-1`，runner id `3`
- 作用域：**仓库级**（`FFAIWorkspace/workspace`，**历史**注册路径）
- 标签：`ubuntu-latest:host`、`ubuntu-24.04:host`
- 执行模式：`host`
- act_runner 版本：`v1.0.2`（2026-05-10 升级，配 Gitea 1.26+）
- 二进制：`/usr/local/bin/act_runner`
- 配置：`/etc/act_runner/config.yaml`（capacity=3）

> **为什么 capacity=3 而不是 4**：16G 内存预算下，最坏 2 个 build-check 并发 (~8G) + 1 个中型 job (~2G) = 11G，留 3G 余量给 next build 偶发飙升。cap=4 时最坏 14G 紧绷，next build 升版易 OOM。CPU 4 核 throttle 不会让 job 失败，单 job 慢一点比拒绝接活划算。
- 工作目录：`/var/lib/act_runner/{workdir,cache}`（owner: ubuntu）
- systemd：`/etc/systemd/system/act-runner.service`（User=ubuntu）

```yaml
log:
  level: info
runner:
  file: /var/lib/act_runner/.runner
  capacity: 3
  timeout: 3h
  labels:
    - "ubuntu-latest:host"
    - "ubuntu-24.04:host"
host:
  workdir_parent: /var/lib/act_runner/workdir
cache:
  enabled: true
  dir: /var/lib/act_runner/cache
```

### 主力 B：dedicated-runner-2（专用 runner 主机 #2，2026-05-18 新加）

- 主机：`43.166.182.155`（8c30G / Ubuntu 24.04，**与 Gitea backup-hub 共宿**）
- SSH：`ssh ubuntu@43.166.182.155`（sudo 免密）
- runner 名称：`dedicated-runner-2`，runner id `4`
- 作用域：**组织级**（`FFAIWorkspace`，所有 repo 共用）—— 注册路径选择见
  [.learnings/2026-05-18-gitea-org-scoped-runner-no-admin.md](../../.learnings/2026-05-18-gitea-org-scoped-runner-no-admin.md)
- 标签 / 执行模式 / act_runner 版本 / 路径 / capacity / systemd unit：**与 dedicated-runner-1 完全一致**
- SSH 镜像（`~/.ssh/config` + `deploy_key` + `known_hosts`）已从 .48 拷贝，可接 deploy job

> **与 backup-hub 共宿**：本机同时是 `10-backup-strategy.md` 的 `offsite-1` 备份枢纽。
> 备份枢纽 cron 每天 04:30 跑 ~1 分钟（dump+rsync），CI 在该时段偶尔有 I/O 重叠但非致命。
> backup-hub 工作路径 `/backups/gitea/`、key `/etc/backup-hub/keys/`；act_runner 工作路径
> `/var/lib/act_runner/`、二进制 `/usr/local/bin/act_runner`——**两者路径不重叠**，互不影响。

### 兜底：workspace-runner（Gitea 主机自带，长期 offline）

- 主机：`43.130.59.228`（与 Gitea web 共宿，仅 1.9G RAM）
- runner 名称：`workspace-runner`，runner id `1`
- 作用域：仓库级
- 配置：`/etc/act_runner/config.yaml` `capacity: 0`，且 systemd service 当前 `inactive(dead)`
- **不要重启它接活**。Gitea 主机内存 1.9G，跑 nest build / next build 必 OOM（详见 issue #169 历史）；
  扩内存 follow-up 在 issue #273

### 服务管理命令

```bash
# dedicated-runner-1 (43.166.205.48)
ssh ubuntu@43.166.205.48 sudo systemctl status act-runner.service
ssh ubuntu@43.166.205.48 sudo journalctl -u act-runner.service -n 200 --no-pager

# dedicated-runner-2 (43.166.182.155)
ssh ubuntu@43.166.182.155 sudo systemctl status act-runner.service
ssh ubuntu@43.166.182.155 sudo journalctl -u act-runner.service -n 200 --no-pager

# workspace-runner (43.130.59.228) — 长期 offline，**不要启动**
# ssh ubuntu@43.130.59.228 sudo systemctl status act-runner.service
```

### Runner 健康检查（API）

```bash
# repo-scoped runner（dedicated-runner-1 / workspace-runner）
curl -s -H "Authorization: token $GITEA_API_TOKEN" \
  http://43.130.59.228/api/v1/repos/FFAIWorkspace/workspace/actions/runners \
  | jq '.runners[] | {id, name, status, busy}'

# org-scoped runner（dedicated-runner-2 / uat-runner）
curl -s -H "Authorization: token $GITEA_API_TOKEN" \
  http://43.130.59.228/api/v1/orgs/FFAIWorkspace/actions/runners \
  | jq '.runners[] | {id, name, status, busy}'
```

> 两个 list 加起来才是当前活动 runner 全量。`/api/v1/admin/runners` 是 instance 级视图，
> **非 admin 调用会返回空或 404**，不要拿它作为全量来源。

---

## 4. 保护分支当前状态

已配置保护分支：

- `develop`
- `staging`
- `production`

每条规则当前包含：

- 禁止直接 push
- 禁止 force push
- 启用状态检查
- 开启 `block_on_outdated_branch`
- 开启 `block_admin_merge_override`

必需状态检查（develop）：

- `quality-gates / verify-agent-assets (pull_request)`
- `quality-gates / build-check (pull_request)`
- `quality-gates / migration-file-count (pull_request)`
- `quality-gates / contract-check (pull_request)`
- `quality-gates / backend-integration (pull_request)`
- `ai-review / ai-review (pull_request)` —— 已转正（2026-05-11，auto-eval 综合分 72.5% ≥ 70%）

说明：

- `pull_request` 是 PR 准入检查，应作为保护分支的必需检查。
- 原 `push: develop` 重跑触发已移除（合并后由 deploy-test.yml 服务器自带 build 当门禁）。
- staging / production 的 PR 上 quality-gates 重型 job 通过 `if: base_ref == 'develop'`
  自动 skip，仅保留 `quality-gates / verify-agent-assets` 跑（防止 .agents 资产漂移）。

---

## 5. 相关工作流

仓库内工作流文件：

- `.gitea/workflows/quality-gates.yml`（PR → develop/staging/production 门禁，
  原 `agent-assets-check.yml` 已并入为 `verify-agent-assets` job）
- `.gitea/workflows/ai-review.yml`（三种 mode：hard-rules-block / batch-summary / release-risk，
  2026-05-11 转正，已写 Gitea 评论 + 更 commit status）
- `.gitea/workflows/deploy-{dev,uat,production}.yml`（按分支 push 触发部署）

当前用途：

- 在三层 PR 上执行 verify-agent-assets（校验 `.agents/skills/` 与 `.claude/skills/` 同步）
- 在 PR → develop 上跑全部 5 个 quality-gates job + ai-review
- 在 PR → staging/production 上仅跑 verify-agent-assets + ai-review

---

## 6. 检查命令

检查命令由 infra-check skill 执行。

---

## 7. 后续维护规则

1. 调整保护分支规则时，优先通过 Gitea API 或后台页面修改，不直接写数据库。
2. 变更 `quality-gates / *` 任一 job 名称前，必须同步更新保护分支里的状态检查上下文（job 重命名 = required check 失效）。
3. 若任一 dedicated runner 离线，**优先用另一台兜住**而不是动 workspace-runner：
   - 查另一台是否还在：`ssh ubuntu@43.166.205.48 sudo systemctl status act-runner.service`
     / `ssh ubuntu@43.166.182.155 sudo systemctl status act-runner.service`
   - 单台扛得住短时 CI 压力（实测 capacity=3 单机日常 OK，队列偶尔慢）
4. 若需要新增 runner，独立机最佳；**默认走 org-scoped 注册**（见 [.learnings/2026-05-18-gitea-org-scoped-runner-no-admin.md](../../.learnings/2026-05-18-gitea-org-scoped-runner-no-admin.md)，不需要 admin）；不要再往 Gitea 主机加 capacity——会拖垮 web/SSH（见 issue #169 / ERR-20260429-001）。
5. **绝不启动 workspace-runner 接活**，1.9G 内存跑 nest/next build 必 OOM；扩内存到 4GB+ 是 issue #273 follow-up。
6. **加新 runner 必须镜像运行时依赖**（每漏镜像一类，跨 runner 漂移就再踩一次）。
   一键脚本：`bash scripts/ops/mirror-runner.sh <OLD_HOST> <NEW_HOST>`（脚本头部有用法）。手工对照清单：

   | 类别 | 内容 | 漏镜像后果 |
   |---|---|---|
   | SSH 资料 | `~/.ssh/config`（HostKey 算法白名单）+ `~/.ssh/deploy_key`（仅 test deploy；指纹 `SHA256:HWsf8ce4Ao+hVuctreL8uY/mwg3REdf0Ws+7us6J2yk`）+ `~/.ssh/known_hosts` | deploy job silent hang |
   | claude CLI | `sudo npm install -g @anthropic-ai/claude-code@<pin-to-old-runner-version>` | ai-review 12 秒挂在 `Verify claude CLI` step（[ERR-20260519-002](../../.learnings/ERRORS/ERR-20260519-002-claude-cli-missing-on-new-runner.md)） |
   | claude 登录态 | `~/.claude/.credentials.json`（chmod 600）+ `~/.claude/settings.json` | CLI 装上但 token 用不了，第一次实际调用才暴露 |

   单文件手工镜像方法：`ssh OLD "cat ~/.ssh/<file>" \| ssh NEW "cat > ~/.ssh/<file> && chmod 600 ..."`。
   验证："SSH" 跑一次 deploy / "claude" 跑 `claude -p "ping"` 真的拿到回包，不能只看 `--version`。

---

## 8. Gitea 数据备份

**状态**：✅ 已配（2026-05-13，工单 [#310](http://43.130.59.228/FFAIWorkspace/workspace/issues/310)）。架构：**pull-based 集中备份枢纽** 拉到异地辅助机 `43.166.182.155`。完整架构 / 加新源 SOP / 灾难恢复见 [`10-backup-strategy.md`](./10-backup-strategy.md)。

**调度**：备份枢纽 `crontab -u backup-hub` `30 4 * * *`（错开 03:00 应用 DB 备份；Gitea 主机 1.9G 内存极紧，并发会 OOM）。

**关键路径**：

| 角色 | 主机 | 路径 |
|---|---|---|
| 源用户 | Gitea (`43.130.59.228`) | `gitea-backup` |
| Dispatcher | Gitea | `/usr/local/bin/backup-dispatch.sh` |
| 本地 dump（7d） | Gitea | `/var/backups/gitea/` |
| Dispatcher 日志 | Gitea | `/var/log/backup-dispatch.log` |
| 枢纽 key | offsite-1 (`43.166.182.155`) | `/etc/backup-hub/keys/gitea_backup` |
| 异地落点（30d） | offsite-1 | `/backups/gitea/` |
| 调度脚本 | offsite-1 | `/opt/backup-hub/bin/backup-all.sh` |

**实测**：单份 ~456 MB，dump 17s + rsync 44s，Gitea 主机内存峰值 +35 MB。

**手工触发**（应急或验证用）：
```bash
ssh ubuntu@43.166.182.155 'sudo -n -u backup-hub /opt/backup-hub/bin/backup-all.sh'
sudo tail /opt/backup-hub/log/backup-all.log
```

**月度验证**：抓最近 dump → staging 机 `gitea restore` → 检 5 个仓库 git log + 1 个 issue → 销毁 staging + 在 #310 评论记日期。详见 [`10-backup-strategy.md § 4`](./10-backup-strategy.md#4-月度验证-sop)。

---

## 9. 风险与注意事项

- 当前 runner 全部是 host 模式，会直接使用宿主机环境执行工作流。
- 如果工作流后续开始依赖 Docker，则需要额外安装 Docker 并重新评估 runner 配置。
- 保护分支已禁止直接 push，后续开发应统一走 `feature/*`、`bugfix/*`、`hotfix/*` 分支与 PR 流程。
- Gitea `runner.capacity: 0` 不是硬阻断——会以"软优先级"形式仍偶尔分派轻量 job（实测 verify-agent-assets / contract-check 仍可能落到 cap=0 runner）。要彻底拉黑某 runner 必须 `systemctl stop`。
- **Gitea 主机 1.9G 内存极紧**，任何同机服务（act_runner / 业务进程）会触发 OOM 杀光全机（详见工单 #273 评论 5）。act_runner 已永久 disable 同机部署，CI 由 **dedicated-runner-1 (43.166.205.48) + dedicated-runner-2 (43.166.182.155) 两台 host runner 双主力承载**。**IT 加内存到 4GB+ 列 follow-up**（#273 待办 4）。

## 10. 升级 SOP（Gitea / act_runner）

从 2026-05-10 一次 Gitea 1.25→1.26 + act_runner 升级 saga 总结。详细复盘见
`.learnings/2026-05-10-gitea-1.26-upgrade-and-act-runner-compat-saga.md`。

### 10.1 升级前必查（mandatory）

```bash
free -h | head -2   # 至少 4 GB 才升 minor / major
df -h               # 至少 5 GB free
uptime              # load < 2
```

**1.9 GB 主机升任何 minor 都是高风险**——一次实测：升 Gitea 1.26.1 + act_runner
v1.0.2 三个并发 → OOM Killer 把 sshd + gitea + nginx 都杀光，systemd 无法重启
（资源未释放），全机宕机。先扩容到 ≥ 4 GB 再升级。

### 10.2 不变量

- **Gitea 主机 ≠ act_runner 主机**——同机任一升级或负载就 OOM。**workspace-runner**
  跑在 Gitea 主机现已**长期 offline（不要启动）**；CI 由 **dedicated-runner-1
  (43.166.205.48) + dedicated-runner-2 (43.166.182.155)** 双主力 host runner 承载
  （每台 capacity=3）。详 §3。
- **act_runner 跟 Gitea 跨版本协议非显式不兼容**——升 Gitea 必须同步评估 act_runner
  版本兼容矩阵。v0.6 跑 1.26 的实测表现是"间歇性 silent hang"，CI log 不报错但
  task 永不返回。升级 Gitea 前先确认 act_runner 版本能兼容新 Gitea。
- **act_runner 配 `runner.capacity` 限并发**（`/etc/act_runner/config.yaml`）；Gitea
  主机的 workspace-runner 已 `systemctl disable + stop`，**即便有人误启动，
  config 里仍保留 `capacity: 0` 作为兜底保险**——否则会被 next build 吃光内存。
- **act_runner repo 改名了**：从 `gitea/act_runner` 改成 `gitea/runner`——下载链接
  对应替换。

### 10.3 升级 runbook（sqlite + binary 安装）

```bash
# Gitea 1.25 → 1.26
sudo systemctl stop gitea act-runner
BAK=/root/gitea-backup-pre-1.26.1-$(date +%Y%m%d-%H%M%S)
sudo mkdir -p "$BAK"
sudo cp -a /usr/local/bin/gitea "$BAK/gitea-OLD.bin"
sudo cp -a /etc/gitea/app.ini "$BAK/"
sudo cp -a /var/lib/gitea/data/gitea.db "$BAK/"
sudo tar czf "$BAK/repos.tar.gz" -C /var/lib/gitea/data/git repositories

cd /tmp
wget https://dl.gitea.com/gitea/<VER>/gitea-<VER>-linux-amd64
wget https://dl.gitea.com/gitea/<VER>/gitea-<VER>-linux-amd64.sha256
sha256sum -c gitea-<VER>-linux-amd64.sha256
sudo install -m 755 gitea-<VER>-linux-amd64 /usr/local/bin/gitea
sudo systemctl start gitea
curl -s http://localhost:3000/api/v1/version  # 应返回新版

# act_runner v0.x → v1.x（注意 repo 改名）
sudo systemctl stop act-runner
sudo cp /usr/local/bin/act_runner /root/act_runner-bak/
wget https://gitea.com/gitea/runner/releases/download/v<VER>/gitea-runner-<VER>-linux-amd64
sudo install -m 755 gitea-runner-<VER>-linux-amd64 /usr/local/bin/act_runner
sudo systemctl start act-runner
sudo journalctl -u act-runner -n 10 --no-pager
# 应看到: "runner: <name>, with version: v<VER>, ..., declare successfully"
```

### 10.4 升级后烟测

跑一个 push 触发的 quality-gates workflow 等完整 PR 流程通过，再判定升级 OK。
观察期 24h——上面教训里 v0.6+1.26 的间歇性 silent hang 是只在某些 task type
触发。

### 10.5 SSH HostKeyAlgorithms 防御（跨 runner 可移植）

runner 机的 ssh client 默认会 offer FIDO `sk-*` host key 类型，对端 sshd 不支持
就直接 `Connection closed [preauth]`——CI log 空白，看不到错。**所有 deploy
workflow 的 ssh 调用应显式指定算法白名单**：

```yaml
ssh -i ~/.ssh/deploy_key \
  -o StrictHostKeyChecking=accept-new \
  -o HostKeyAlgorithms=ssh-ed25519,ecdsa-sha2-nistp256,rsa-sha2-512,rsa-sha2-256,ssh-rsa \
  ubuntu@"$DEPLOY_HOST" bash -s
```

诊断 ssh hang 的 5 步 SOP（详 learning 文件）：CI log size → runner 机 `ps -ef` →
`lsof -p <ssh_pid> -i` 看 TCP state → **远端机** `/var/log/auth.log` grep runner
IP → 用同样 private key 从别的机器手动复现。
