由于 GitHub 近期的不稳定,以及自建的 runner 消耗比较多的上下行流量,我尝试将代码仓库迁移到 gitea。在这个过程中,我踩了不少 runner 的坑。这里记录一下。
TL;DR
如果读者希望自己的 LLM 不踩坑,可以这样:
这是别人迁移 gitea 的时候踩的坑,我希望你做参考。<本文链接>
状态:闭环验证通过(2026-06-17,run #39404 / PR #5)(inside id)
适用:self-hosted Gitea 1.26.x + gitea-runner 1.0.8 + 自签证书环境
详细调查记录:journal/2026-06-17-default-actions-url-self-investigation.md(续一~续四)(svtter/ops journal)
背景
把 GitHub Actions workflow(sun-praise/latex-agent 21 个 workflow)迁到内网 Gitea。目标是杀掉 Personal Access Token(PAT),让所有跨仓 action 访问用 gitea 原生的 job token + DEFAULT_ACTIONS_URL=self 机制。
PAT 方案(06-14 journal 记录)能用但脏:每个消费方仓要存 OPENCODE_ACTIONS_PAT secret,action 代码要做本地 checkout workaround。kill-PAT 后 workflow 写法干净(uses: org/action@ref 直接生效),跟 GitHub Actions 一致。
前置条件
| 项 | 要求 |
|---|---|
| Gitea 版本 | ≥ 1.26.2(含 PR #32562 collaborative owners + #36173 job token 跨仓权限) |
| Runner | gitea-runner 1.0.8(act_runner 改名后的新仓 gitea.com/gitea/runner) |
| 网络 | runner host 能访问 gitea server(同内网) |
| 证书 | 自签或受信任 CA(本方案处理自签场景) |
核心配置(5 处改动)
1. Gitea server:启用 self 模式
/volume1/docker/gitea/gitea/gitea/conf/app.ini(容器内 /data/gitea/conf/app.ini):
| |
重启 gitea:docker compose restart server。
验证:改成 bogus_value 重启应启动失败,证明配置在读。
2. Runner 注册地址对齐 gitea AppURL
/var/lib/act_runner/.runner(runner host 上):
| |
必须跟 gitea 的 ROOT_URL / AppURL host 完全一致。否则 shouldCloneURLUseToken(act/runner/reusable_workflow.go:306)做 host 字符串比较会 mismatch → 跨仓 clone 不带 token → 401。
我们这里 gitea.local(旧注册)→ gitea.my-nas.lan(gitea AppURL)。
3. Runner image:自构建(cert + gitconfig + curlrc)
官方 gitea/runner-images:ubuntu-latest 在自签证书环境会撞 TLS 校验。自构建一层 overlay:
| |
构建 + 打 tag:
| |
关键:gitea-my-nas.crt 是 gitea 的 self-signed leaf 证书(NAS 上 /volume1/docker/gitea/gitea/gitea/npm-gitea.cert.pem)。update-ca-certificates 装它其实无效(leaf 不是 CA),但留着没坏处。真正起作用的是 git/curl 的 sslVerify/insecure。
4. Runner config.yaml:labels + force_pull
/var/lib/act_runner/config.yaml:
| |
两个关键点:
config.yaml是 source of truth,daemon 启动时覆盖.runner文件。改.runnerlabels 无效。force_pull: true每次都去 docker hub 拉 image,runner host 访问不了 hub 就全挂。离线必须false。
5. actions org + 公共 action 镜像
消费方 workflow 里 uses: actions/cache@v5 这种裸写的公共 action,在 self 模式下会解析到 gitea.local/actions/cache。需要镜像。
| |
org 必须是 public——private org 即使 repo 标 public,对外仍不可见(anonymous git 访问 401)。
按 workflow 实际 uses: 列表镜像。常见需要:actions/checkout(如果不用绝对 URL)、actions/cache、actions/setup-node 等。我们这次 workflow 对 actions/checkout 用了绝对 URL(uses: https://github.com/actions/checkout@v4),只需镜像 actions/cache(multi-review 传递依赖)。
Workflow 改造
消费方仓的 workflow 从 PAT+local-checkout 配方改成干净 native uses:
| |
关键约定:
GITHUB_TOKEN(gitea Actions 自动注入的 job token)替代 PAT。所有跨仓认证用它。permissions:显式声明,受 org/repo Actions 设置 clamp(PR #36173)。- 公共 action 用绝对 URL 或镜像——二选一,看是否要 forward compat(绝对 URL 写法在 GitHub 上也认,镜像写法只在 gitea 上认)。
Gitea 仓级配置
消费方仓的 Settings → Actions → General:
- Actions enabled:开
- Default Actions Permissions:Allow all actions(或按需 restrict)
- Cross-Repository Access:Selected,勾上消费方要用的所有自有 action repo(如
opencode-actions)——这是 PR #32562 collaborative owners 功能,让 job token 能跨仓读
自有 action 仓(sun-praise/opencode-actions):
- 可以是 private(job token 通过 collaborative owners 有读权限)
验证
闭环验证(推荐)
消费方仓提一个测试 PR,触发 on: pull_request workflow。预期:
- workflow run status → success
- PR 上出现 review 评论(或 action 的预期产出)
分层验证(debug 用)
| 层 | 验证方法 |
|---|---|
| gitea self 配置 | app.ini 改 bogus_value 重启应失败 |
| runner URL 对齐 | .runner address == gitea AppURL host |
| runner image TLS | container 内 curl -sI https://gitea.../api/v1/version 不报证书错 |
| actions mirror | curl https://gitea.local/actions/cache.git/info/refs?service=git-upload-pack 匿名 200 |
| job token 跨仓 | DB 查 action_run_job.token_permissions,Code unit 有 read 权限 |
真正 kill PAT(闭环验证通过后)
- 删消费方仓的
OPENCODE_ACTIONS_PATsecret:1 2curl -X DELETE "$GITEA/api/v1/repos/<owner>/<repo>/actions/secrets/OPENCODE_ACTIONS_PAT" \ -H "Authorization: token $ADMIN_TOKEN" - 删 org 级 PAT(如果之前配在 org):同上换
/orgs/<org>/actions/secrets/ - revoke admin 用户 token:web UI → Settings → Applications → Revoke
- workflow 文件清理:去掉 PAT 相关的
with:和本地 checkout workaround
踩坑总结(按踩中顺序)
- 以为 act_runner 有 bug——读了三仓源码,写了 patch,最后发现是配置问题。教训:先怀疑配置,再怀疑代码。
.runner文件改了不生效——daemon 用config.yaml覆盖。改 labels 要改 config.yaml。update-ca-certificates装自签 leaf 无效——leaf 不是 CA,不能签其他证书。要靠 sslVerify/insecure 绕。- git 和 curl 的 TLS 校验独立——git 的 sslVerify=false 不管 curl。curl 读
~/.curlrc(不是/etc/curlrc)。 force_pull: true离线致命——每次 run 强拉 docker hub。离线必关。- private org 的 public repo 仍不可见——org visibility 是外层约束。镜像公共 action 的 org 必须 public。
- host 字符串严格相等——
gitea.local≠gitea.my-nas.lan,即使解析到同 IP。runner 注册地址必须跟 gitea AppURL 完全一致。 - 闭环验证才算数——每层"通了"都是局部判断。multi-review 真跑通 PR review + post 评论,端到端通了才算。
参考链接
- 调查 journal:
journal/2026-06-17-default-actions-url-self-investigation.md(续一~续四) - gitea PR #32562(collaborative owners):https://github.com/go-gitea/gitea/pull/32562
- gitea PR #36173(job token 权限):https://github.com/go-gitea/gitea/pull/36173
- gitea issue #36817(self 模式的误报):https://github.com/go-gitea/gitea/issues/36817
- act_runner 源码:https://gitea.com/gitea/runner
- runner image 源码:https://gitea.com/gitea/runner_images
附录:本次验证的实际 run
- PR:https://gitea.my-nas.lan/sun-praise/test-opencode-consumer/pulls/5
- Run:https://gitea.my-nas.lan/sun-praise/test-opencode-consumer/actions/runs/39404
- 结果:multi-review 拿到 PR diff(1282 chars)→ 调 LiteLLM/DeepSeek review → post 评论到 PR。全程零 PAT。
