From d63b0061357b31e599b9102322843d18cb996bb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E8=82=B2=E6=96=B0?= <2635131263@qq.com> Date: Fri, 16 Oct 2026 15:02:25 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=20chi=20=E7=BB=83?= =?UTF-8?q?=E4=B9=A0=E4=B8=8E=20Gitea=20CI=20=E5=AE=9E=E8=B7=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 chi 路由示例并更新依赖 - 用简单 CI 实践笔记替换 Polaris 部署笔记 - 添加本地 Gitea 与 runner 配置示例,注册令牌从环境变量读取 --- go/practice/go.mod | 1 + go/practice/go.sum | 2 + go/practice/testing/chi/main.go | 17 + note/资源/DevOps/Gitea/Gitea 简单 CI 实践.md | 143 ++++++++ .../Gitea/Polaris 前端容器化 CI 与部署实践.md | 331 ------------------ note/资源/DevOps/Gitea/local-ci/compose.yaml | 88 +++++ .../DevOps/Gitea/local-ci/runner-config.yaml | 302 ++++++++++++++++ 7 files changed, 553 insertions(+), 331 deletions(-) create mode 100644 go/practice/testing/chi/main.go create mode 100644 note/资源/DevOps/Gitea/Gitea 简单 CI 实践.md delete mode 100644 note/资源/DevOps/Gitea/Polaris 前端容器化 CI 与部署实践.md create mode 100644 note/资源/DevOps/Gitea/local-ci/compose.yaml create mode 100644 note/资源/DevOps/Gitea/local-ci/runner-config.yaml diff --git a/go/practice/go.mod b/go/practice/go.mod index f44fce0..c5367f1 100644 --- a/go/practice/go.mod +++ b/go/practice/go.mod @@ -5,6 +5,7 @@ go 1.27.0 require ( github.com/gin-contrib/sessions v1.1.2 github.com/gin-gonic/gin v1.12.0 + github.com/go-chi/chi/v5 v5.5.0 golang.org/x/crypto v0.56.0 ) diff --git a/go/practice/go.sum b/go/practice/go.sum index c84f951..305a5f7 100644 --- a/go/practice/go.sum +++ b/go/practice/go.sum @@ -17,6 +17,8 @@ github.com/gin-contrib/sse v1.1.1 h1:uGYpNwTacv5R68bSGMapo62iLTRa9l5zxGCps4hK6ko github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= +github.com/go-chi/chi/v5 v5.5.0 h1:xdKQ1dJmVJsmgahrXSKhUx8NyVPzpwdlyrrQFpMPJgg= +github.com/go-chi/chi/v5 v5.5.0/go.mod h1:d1IG7GtKPSjPyBP365PH8sAGVHk3qfB/IhjFNRH1wWM= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= diff --git a/go/practice/testing/chi/main.go b/go/practice/testing/chi/main.go new file mode 100644 index 0000000..9722da6 --- /dev/null +++ b/go/practice/testing/chi/main.go @@ -0,0 +1,17 @@ +package main + +import ( + "net/http" + + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" +) + +func main() { + r := chi.NewRouter() + r.Use(middleware.Logger) + r.Get("/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte("Hello World!")) + }) + http.ListenAndServe(":30000", r) +} diff --git a/note/资源/DevOps/Gitea/Gitea 简单 CI 实践.md b/note/资源/DevOps/Gitea/Gitea 简单 CI 实践.md new file mode 100644 index 0000000..55e305b --- /dev/null +++ b/note/资源/DevOps/Gitea/Gitea 简单 CI 实践.md @@ -0,0 +1,143 @@ +# Gitea 简单 CI 实践 + +> 用 Gitea Actions 给仓库搭一条最简单的持续集成:推送代码后自动做语法检查、跑单元测试。 + +## 1. 启用 Gitea Actions + +- Gitea 1.21 及以上版本的实例默认启用 Actions(`app.ini` 中 `[actions] ENABLED` 默认为 true); +- 但仓库级默认禁用,需在仓库 **设置 → Actions** 中打开。 + +## 2. 准备并注册 act_runner + +job 由 act_runner 执行,可以二进制安装或者容器安装,官方文档: + +准备前提: + +- runner 所在机器能通过网络访问 Gitea 实例; +- 本例用 Docker 执行器(label 里的 `docker://` 前缀),runner 所在机器需有可用的 Docker。 + +本地 [Compose 示例](local-ci/compose.yaml) 用 `gitea/runner` 容器跑 Runner: +首次启动时 entrypoint 读取 `GITEA_INSTANCE_URL`、`GITEA_RUNNER_REGISTRATION_TOKEN`、 +`GITEA_RUNNER_LABELS` 自动完成注册,注册结果写入挂载的 `/data` 下的 `.runner` +文件并持久化,重启容器不必手动注册。只有不用 Compose、直接安装二进制运行时, +才需要手动执行注册与启动命令(见官方文档)。 + +注册令牌等同凭据:妥善保管,只在环境变量、secrets 或 runner 自己的配置文件里 +出现,不要写进命令行历史、文档或仓库文件。 + +label 的格式是 `名称:执行器://参数`:以 Compose 中的 +`demo-ci:docker://node:22-bookworm` 为例,名称为 `demo-ci`, +执行器为 Docker,参数为 job 容器镜像 `node:22-bookworm`。 +工作流的 `runs-on` 按**名称部分**匹配。 + +### Runner 的 Docker 运行方式 + +`docker://` 表示 job 在容器中运行,但不决定 Runner 使用哪一个 Docker daemon: + +| 术语 | 含义 | 取舍 | +|---|---|---| +| DinD(Docker-in-Docker) | Runner 容器里另起 Docker daemon,由它创建 job 容器 | 与宿主 Docker 分开;job 默认也与外层 Compose 网络分开,需要配置访问 Gitea 的路径 | +| DooD(Docker-outside-of-Docker) | Runner 通过宿主机 Docker socket 使用宿主 daemon,不在容器里另起 daemon | 配置和缓存复用较简单;能接触 socket 的 job 可能控制宿主 Docker,不宜在共享宿主上运行不可信代码 | +| Rootless | Docker daemon 以非 root 身份运行,不是 DinD 的反义词 | 可以与 DinD 组合;降低部分权限风险,但仍需留意网络、存储限制及宿主资源访问 | + +本地 [Compose 示例](local-ci/compose.yaml) 使用需要特权容器的 `3-dind`, +并非 Rootless。三种术语并不处于同一分类:DinD / DooD 说明**使用哪个 daemon**, +Rootless 说明**daemon 以什么权限运行**;它也不等于完全隔离。 + +## 3. 最小 Node demo + +两个零依赖文件,放在仓库根目录。 + +`demo.js`(CommonJS,一个加法函数): + +```js +function add(a, b) { + return a + b; +} + +module.exports = { add }; +``` + +`demo.test.js`(用 Node 内置的 `node:test` 与 `node:assert/strict`): + +```js +const test = require('node:test'); +const assert = require('node:assert/strict'); +const { add } = require('./demo'); + +test('整数相加', () => { + assert.equal(add(1, 2), 3); +}); + +test('负数相加', () => { + assert.equal(add(-1, -2), -3); +}); +``` + +先在本地验证能跑:`node --test demo.test.js`。 + +## 4. 编写工作流 + +在仓库根目录新建 `.gitea/workflows/ci.yml`: + +```yaml +name: Demo CI + +on: + push: + branches: [main] # 默认分支不是 main 的仓库按需修改 + +jobs: + test: + runs-on: demo-ci + steps: + - name: 检出仓库 + uses: https://gitea.com/actions/checkout@v4 + + - name: 语法检查 + run: node --check demo.js && node --check demo.test.js + + - name: 运行测试 + run: node --test demo.test.js +``` + +要点: + +- `runs-on: demo-ci` 必须与 runner 注册 label 的名称部分一致; +- Gitea 中 `uses` 不带完整 URL 时默认从 GitHub 拉取,这里显式写 gitea.com + 的绝对地址;runner 仍需能访问该 action 源; +- job 会跑在 label 指定的 `node:22-bookworm` 容器里:Node 内置测试运行器 + 开箱即用,不需要 `npm install`,也没有任何第三方依赖。 + +## 5. 推送并查看结果 + +```bash +git add demo.js demo.test.js .gitea/workflows/ci.yml +git commit -m "add demo CI" +git push +``` + +打开仓库 **Actions** 页,能看到这次 push 触发的 run; +点进 job 可展开每个 step 的日志。 + +常见排查: + +| 现象 | 排查方向 | +|---|---| +| run 一直等待、没有 job 被领取 | 仓库 Actions 是否启用;runner 是否在线(runner 页看状态);`runs-on` 与 runner label 名称是否一致。不同版本对不匹配的处理有差异,结合 runner 日志与实际执行环境核对 | +| checkout 步骤失败或超时 | runner 到 Gitea 实例、到 action 源(gitea.com)的网络连通性 | +| 语法检查或测试失败 | 直接看该 step 日志里的报错/断言输出,在本地用同样命令复现 | + +本地用 Compose + Docker-in-Docker 时,还要区分 runner 容器与 job 容器的网络: +runner 能解析 `gitea`,不代表 job 也能。若 checkout 的 `git fetch` 报 +`Could not resolve host: gitea`,先检查 job 容器能否访问 Gitea,而不只是检查 runner。 +本地测试可在 Runner 配置的 `container:` 下设 `network: host`,通过 Compose +挂载该配置、设置 `CONFIG_FILE` 并重建 runner,见 [本地 Compose 示例](local-ci/compose.yaml)。 +这里的 `host` 指 runner 容器所在的网络命名空间,不是宿主机网络;job 与 runner +共用网络会降低隔离性,只适合可信的本地测试,不应直接当作生产环境通用方案。 + +## 参考 + +- Gitea Actions 快速上手: +- act_runner 注册: +- act_runner 标签: diff --git a/note/资源/DevOps/Gitea/Polaris 前端容器化 CI 与部署实践.md b/note/资源/DevOps/Gitea/Polaris 前端容器化 CI 与部署实践.md deleted file mode 100644 index 6bf4028..0000000 --- a/note/资源/DevOps/Gitea/Polaris 前端容器化 CI 与部署实践.md +++ /dev/null @@ -1,331 +0,0 @@ -# Polaris 前端容器化 CI 与部署实践 - -> 复盘 + 可复用教程:记录 Polaris(Next.js 导航站,纯前端、数据为仓库内本地 JSON) -> 在单机服务器上从 0 到「CI 出镜像、人工按 digest 部署上线」的完整过程与踩坑。 -> -> **本文位置与定位**:本文位于仓库 `note/`(系统性知识),与代码严格分离。 -> 操作步骤的权威版本在 [`ts/web/nextjs/polaris/deploy/README.md`](../../../../ts/web/nextjs/polaris/deploy/README.md)—— -> README 管「怎么做」,本文管「为什么这么做、踩了什么坑」;若细节随演进不一致,以仓库内文件为准。 -> 文中命令均使用占位符(``、``、`sha256:`、`` 等), -> 不含任何真实令牌、私钥或机器标识;`git.labmem.icu` / `labmem.icu` 是已公开域名,仅作示例。 - -## 1. 环境拓扑:单机多角色 - -一台服务器同时承担多个角色,容器按 Docker 网络分工:同一 bridge 网络内可互通;不同网络的容器不能直接通过彼此的容器名互访,但宿主端口映射等路径仍可能提供访问。 -注意这只是宿主本地的网络分层,**不是**公网隔离 / 物理隔离的同义词。 - -| 层 | 容器 | 网络 | 说明 | -|---|---|---|---| -| web | nginx | 仅 `net-public` | 对外入口 / 反代,按域名分流 | -| web | gitea | `net-public` + `net-db` | 同时服务 git 与 registry,桥接两网 | -| 应用 | polaris | 仅 `net-public` | 纯前端(本地 JSON),无 DB/Redis;3000 仅容器内 expose | -| infra | PostgreSQL、Redis | 仅 `net-db` | 数据库不暴露公网 | -| CI | gitea act runner | `net-public`(runner 自身) | 镜像 `gitea/runner:3-dind-rootless`;job 容器/rootless daemon 的内部网络是另一套,与宿主的 `net-public` **不是同一个网络** | - -几个关键点: - -- **runner 是 rootless dind**:`gitea/runner:3-dind-rootless` 内跑一个非 root(uid 1000) - 的 Docker daemon,socket 为 `unix:///var/run/user/1000/docker.sock`,再挂进 job 容器的 - `/var/run/docker.sock`;数据落在 `runner-docker` 卷里。CI 里只装 Docker CLI, - `DOCKER_HOST` 不用设,但**这个 daemon 不是宿主机的 Docker**(见第 4 节,这是最容易误解的一点)。 -- **网络最小化**:Polaris 只需要被 nginx 反代,所以只加入外部网络 `net-public`; - `net-db` 里是 PG/Redis,前端用不到,**不连**——网络面越小越安全。 -- 账号与路径约定:服务器操作 shell 实际为 root;本文不写个人绝对路径,部署目录统一以 - `` 代指。注意 `docker login` 与 `docker compose` 需在**同一宿主账号**下执行—— - 登录凭据存在该账号的 `~/.docker/config.json`,换了账号就读不到私有镜像。 - -### 1.1 网络 / 流量总图(纯文本,等宽字体下查看) - -```text -外部访客(浏览器 / git 客户端 / docker CLI) - │ 只见宿主 80 / 443 / 222;80 仅重定向到 HTTPS - ▼ -═══════════════════════════ 宿主机(单机) ════════════════════════════ - - [nginx](net-public,TLS 终结)── 按 Host 域名分流 - ├─ labmem.icu ──────HTTP──► [polaris :3000](net-public) - │ └ 3000 仅容器内 expose,宿主无映射 - └─ git.labmem.icu ──HTTP──► [gitea](net-public + net-db) - (网页 / git http / registry API 同一路径) - - 宿主 :222 ──► gitea :22(git over SSH,直连容器端口,不过 nginx) - - [gitea] ──仅 net-db──► [postgres :5432] [redis :6379] - 宿主侧 127.0.0.1:5432/6379 为回环绑定,非公网开放 - - [runner](net-public,gitea/runner:3-dind-rootless) - └─ 内部 rootless daemon(uid 1000):job 容器跑在其自建网络里 - └─ build 出镜像 ──push(HTTPS 经 nginx)──► Gitea packages storage - - 宿主 Docker daemon(与 runner 内的 daemon 是两个独立进程) - └─ docker login / pull ◄── Gitea registry(HTTPS 经 nginx) - └─ compose 按 digest up - - net-public / net-db:宿主创建的 external bridge 网络,多个 compose 项目共用 -══════════════════════════════════════════════════════════════════════ -``` - -读图路径(与正文对应的主线): - -1. 访客页面:`labmem.icu:443` → nginx → `polaris:3000`(仅容器网络内,宿主无 3000 端口)。 -2. git / registry:`git.labmem.icu:443` → 同一个 nginx → gitea; - CI 的 push 与宿主的 pull 走的是这同一条 HTTPS 路径,只是方向和客户端不同。 -3. git SSH:宿主 222 → gitea 容器 22,直连端口映射,不经 nginx。 -4. CI 上行:runner 自身在 net-public,其内部 rootless daemon 为每个 job 建独立网络, - 构建出的镜像经 HTTPS(经 nginx)推入 Gitea packages。 -5. 部署下行:宿主另一个 Docker daemon 从 Gitea registry pull,再由 - `` 里的 compose 按固定 digest 启动 polaris。 -6. 后端依赖:gitea → postgres/redis 只走 net-db;Polaris 不在其列,前端不直接连 DB。 - -### 1.2 网络、端口与容器职责 - -| 容器/进程 | 所在网络 | 宿主端口发布 | 容器端口 | 职责 | -|---|---|---|---|---| -| nginx | net-public | 80、443 | 80、443 | 统一入口:TLS 终结、按域名分流、反代 | -| polaris | net-public | 无(宿主不映射 3000) | 3000(仅 expose) | Polaris 前端;无 DB/Redis 依赖 | -| gitea | net-public + net-db | 222 → 22 | 22(SSH);HTTP 走 nginx 反代 | git 托管 + 容器 registry(packages) | -| postgres | 仅 net-db | 127.0.0.1:5432 → 5432 | 5432 | infra 数据库(回环绑定,非公网开放) | -| redis | 仅 net-db | 127.0.0.1:6379 → 6379 | 6379 | infra 缓存(回环绑定,非公网开放) | -| runner | net-public(自身) | 无 | — | 执行 CI job;内部 rootless daemon 自建 job 网络 | -| 宿主 dockerd | —(创建/管理上述 external 网络) | 发布 80、443、222 | — | 运行全部 compose 项目;与 CI 内 daemon 相互独立 | - -补充说明: - -- `net-public` / `net-db` 是**宿主创建的 external bridge 网络**(compose 里 `external: true`, - 多个 compose 项目共用);名字里的 public / db 只是用途约定,**不等于**公网或物理隔离。 -- 示意 CIDR:当时截图 net-db ≈ `172.22.0.0/16`、net-public ≈ `172.23.0.0/16`; - 实地以 `docker network inspect net-public net-db` 为准,本文不硬编码。 -- 回环绑定(127.0.0.1)意味着 PG/Redis 的宿主端口只服务本机管理,不经公网可达; - 宿主防火墙规则未核验,但不影响回环绑定本身的限定。 -- 面板自带的 `1panel-network` 与默认 bridge 属既有背景,与 Polaris 主链路无关; - 其网关 / 路由规则不在本文展开,也不猜测。 - -### 1.3 两个 Docker daemon 与持久化落点 - -| Docker daemon | 跑在哪 | 负责什么 | 数据 / 持久化落点 | -|---|---|---|---| -| 宿主 dockerd | 宿主机 | web / infra / polaris 各 compose 项目 | gitea:`/data` → 项目目录下 `./data/gitea`(packages 在其内部的精确路径依 Gitea 配置而定,不写死、也不复制物理文件);postgres / redis:bind 挂载的数据目录;polaris:无卷 | -| runner 内 rootless daemon | runner 容器内(uid 1000,`unix:///var/run/user/1000/docker.sock`) | CI job 的 build / run / 冒烟 | `runner-docker` 卷(镜像与构建缓存),与宿主 daemon 互不相通 | - -镜像如何从 CI daemon → Gitea packages → 宿主 daemon 的完整旅程见第 4 节。 - -> 本节图表的信息来源:用户提供的各 compose 定义与实际上线反馈 -> (流量路径、端口发布、健康状态均为已验证行为); -> 物理存储内部布局与防火墙规则未逐一核验。 - -## 2. CI 流水线:verify 之前不出镜像 - -流水线定义在 [`.gitea/workflows/polaris-ci.yml`](../../../../.gitea/workflows/polaris-ci.yml), -两个 job 串联: - -``` -push main(路径过滤:ts/web/nextjs/polaris/** 或 workflow 本身) - └─ verify job(node:22.23.2-bookworm 容器) - lint → typecheck → test → next build(standalone) - → 备齐 standalone 产物 → 起本地进程冒烟(/、分类页、静态资源、/_next 资产) - └─ publish job(needs: verify,仅 main 分支 push 触发) - 装 Docker CLI(仅 CLI,无宿主 socket) - → 解析镜像引用 git.labmem.icu//polaris:${GITHUB_SHA} - → docker build - → 对镜像本身冒烟(docker run + 容器内 node fetch 探测) - → docker login(secrets)→ push → 从 push 日志解析 digest 并校验格式 -``` - -设计动机,值得复用的三条: - -1. **校验不过不出镜像**:`publish` 依赖 `verify`,坏代码最多到测试为止,永远进不了 registry。 -2. **tag 用完整 commit SHA**:`git.labmem.icu//polaris:`, - 镜像与提交一一对应、可追溯;不用 `latest`,避免「部署的到底是谁」。 -3. **部署固定 digest 而非 tag**:tag 理论上可被覆盖,digest 是内容寻址、不可变; - 部署侧 `.env` 写 `@sha256:`,回滚也就是「换回旧 digest」。 - -三个工程细节: - -- 镜像冒烟复用了与 verify 相同的 node fetch 探测思路(镜像内没有 curl),且用 `docker run -d` - 返回的**唯一容器 ID**,避免共享 runner daemon 上并发跑撞名。 -- 登录凭据放在 `mktemp -d` 的临时 `--config` 目录里,step 结束即销毁, - 不落 job 容器的 `~/.docker/config.json`。 -- digest 解析脚本带格式校验(`sha256:[0-9a-f]{64}`),解析不到就**失败退出**并提示 - 去 registry 界面人工确认——宁可失败,也不把一个没核对过的引用输出成「部署参考」。 - -## 3. 踩坑复盘:两次失败,两种典型 - -### 3.1 第一次失败:缺 registry 凭据(secrets 未配置) - -现象:`publish` 的登录步骤直接报缺少 -`secrets.POLARIS_REGISTRY_USER` / `secrets.POLARIS_REGISTRY_TOKEN`。 - -解决(一次性,管理员操作): - -1. Gitea 个人设置 → 应用(Applications)→ 新建 token,权限勾选 **package 写**; -2. 仓库 → 设置 → Actions → Secrets,分别新增: - - `POLARIS_REGISTRY_USER` = 登录用户名 - - `POLARIS_REGISTRY_TOKEN` = 上一步的 PAT -3. 重跑失败的工作流。 - -**教训:凭据要按用途分清(四类)。** 这台服务器上至少有四种长得很像的凭据,用途完全不同: - -| 凭据 | 用途 | 给谁用 | -|---|---|---| -| package 写权限 PAT | 推送镜像 | 只进 Gitea Actions secrets | -| 只读(拉取)PAT | 拉取镜像 | 只在宿主机 `docker login` | -| runner 注册令牌 | 注册 act runner | 属 runner 部署配置;注册令牌可长期保留在 runner 的 compose env 中持续生效 | -| SSH 部署密钥 | git 拉取 / 服务器登录 | 与 registry 无关 | - -不要把 SSH 部署密钥或 runner 注册令牌当成 registry token 填进 secrets—— -名字对不上时登录直接失败;名字碰巧对得上的凭据更隐蔽:权限错位、行为怪异,排查更绕。 - -### 3.2 第二次失败:413 PUT /v2/blobs/uploads - -现象:只出现在 CI 的 push 步骤日志里——推送输出报 `413`(`PUT /v2/blobs/uploads/...`)。 -服务端侧当时的日志并未留档,判断依据是反代默认 `client_max_body_size 1m` 远小于镜像层体积。 - -原因:registry 流量走 gitea 站点前面的 nginx,`client_max_body_size` 默认只有 1m, -而镜像层动辄上百 MB。 - -解决:在 **Gitea 站点对应的 server 块**放开限制(按最大单层留余量,这里给 1g): - -```nginx -server { - # ... 监听/TLS/gitea 反代配置保持不变 ... - client_max_body_size 1g; -} -``` - -```bash -# nginx 运行在容器里,用 docker exec 操作,不是宿主机命令 -docker exec nginx -t && docker exec nginx -s reload -``` - -**教训**:413 不一定是应用问题,先看反代;registry 场景里 -`client_max_body_size` 要按「最大镜像层」而不是「镜像总体积」估算。 - -### 3.3 推送成功:digest 是什么、不是什么 - -push 成功后日志末尾给出 manifest digest(本文一律以占位 `sha256:` 表示)。 -两个容易误解的点: - -- **digest 对应的是 manifest**。push 日志里 digest 旁边的 `size` 是 manifest JSON 本身的 - 大小(几 KB),**不是镜像总体积**;镜像体积是各层 blob 之和,另看各层输出。 -- **tag 是可变的名字**:SHA 命名的好处是便于追溯,但并不阻止同名 tag 被重推覆盖; - 一旦重推,tag 指向新内容,digest 随之改变。真正锁定内容的是 digest—— - 部署记录里应同时留 tag(溯源)和 digest(部署事实)。 - -## 4. 三个 Docker/存储的边界:CI 里的镜像 ≠ 宿主的镜像 - -这是本次实践里最值得写下来的一条认知修正 -(网络 / 端口视角见第 1 节架构图;本节只看镜像与数据在三个 daemon/存储之间的旅程): - -``` -CI job 里的 docker build/push Gitea 服务端 宿主机 -┌────────────────────────┐ push ┌──────────────────┐ pull ┌──────────────┐ -│ runner 的 rootless │ ───────→ │ packages storage │ ───────→ │ 宿主 Docker │ -│ daemon(runner-docker │ │(由 Gitea 管理) │ │ (另一个 daemon│ -│ 卷,uid 1000) │ │ 存储布局随配置, │ │ 独立拉取) │ -└────────────────────────┘ │ 不必关心精确路径 │ └──────────────┘ - └──────────────────┘ -``` - -- CI 构建出的镜像只存在于 **runner 的 rootless daemon**(`runner-docker` 卷)里, - 宿主机 `docker images` 看不到它,这是设计使然而非故障; -- push 成功后,镜像的权威副本由 **Gitea packages storage** 管理(存储后端与磁盘布局 - 随 Gitea 配置而变,不需要也不应写死具体路径); -- 宿主机 `docker pull` 是**第三个独立 daemon** 从 registry 的一次全新拉取。 - -所以「CI 构建成功」和「宿主机上能跑」是两件事,中间的桥梁就是 registry + digest。 - -## 5. 服务器手动部署(第一阶段:人工、按 digest) - -第一阶段刻意保持**手动部署、非自动**:CI 只负责出镜像,上不上线由人决定。 - -```bash -# ① 宿主机登录 registry —— 用独立的只读 PAT,与 CI 写权限分离 -docker login git.labmem.icu -u # 交互输入只读 token - -# ② 准备部署文件(仓库 ts/web/nextjs/polaris/deploy/ 拷到服务器,见 README) -cd -cp .env.example .env -# ③ .env 里固定 digest(同时注释保留来源 tag,便于溯源与回退): -# POLARIS_IMAGE=git.labmem.icu//polaris@sha256: -# # 来源:git.labmem.icu//polaris: - -# ④ 拉取并启动(compose 顶层 name: polaris,单服务,仅 net-public) -docker compose pull polaris -docker compose up -d polaris -docker compose ps # health: starting → healthy -docker inspect -f '{{.State.Health.Status}}' polaris - -# ⑤ 服务连通性(net-public 内,从 nginx 侧验证服务名可达) -docker exec wget -qO- http://polaris:3000/ >/dev/null && echo ok -``` - -compose 侧的约束(详见 [`deploy/compose.yaml`](../../../../ts/web/nextjs/polaris/deploy/compose.yaml)): -镜像 digest 必填(`${POLARIS_IMAGE:?...}`)、不映射宿主端口、无卷/secrets 挂载、 -healthcheck 用镜像内现成的 node 原生 fetch 探测 `/`(不引入 curl); -应用本身在镜像里就是非 root 的 `node` 用户、监听 3000 -(见 [`Dockerfile`](../../../../ts/web/nextjs/polaris/Dockerfile) 的 runner 阶段)。 - -## 6. Nginx 反代接入:从占位首页到动态解析 - -背景:`labmem.icu` 的 HTTPS 站点此前只是一张占位首页;80 端口的重定向保持不变。 -接入 Polaris 时改的是该站点的 `location /`: - -```nginx -# labmem.icu 站点内(示意;域名/证书路径以实际配置为准) -location / { - # Docker 内嵌 DNS;变量式 proxy_pass 才会走 resolver 动态解析 - resolver 127.0.0.11 valid=10s ipv6=off; - set $polaris_backend polaris:3000; - proxy_pass http://$polaris_backend; # 变量式:运行时经 resolver 动态解析 - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; -} -``` - -为什么用动态解析:静态 `proxy_pass http://polaris:3000;` 在 nginx 载入配置时只解析一次, -polaris 容器每次重建 IP 都可能变化,静态写法会 502;变量 + `resolver`(Docker 内嵌 DNS -`127.0.0.11`,`valid=10s` 缓存十秒,`ipv6=off` 关掉 AAAA 噪声)让 nginx 跟着容器重建走。 - -收尾三步: - -```bash -docker exec nginx -t && docker exec nginx -s reload -curl -I https://labmem.icu/ # 外部路由核验:应返回 Polaris 首页响应 -curl -s https://labmem.icu/categories/dev/ -o /dev/null -w '%{http_code}\n' # 分类页(应用开了 trailingSlash) -``` - -注意证书生命周期:续期(如 certbot renew)后需要 reload nginx 才会生效; -这与容器重建是两件独立的事,都要纳入巡检。 - -## 7. 运维边界、风险与展望 - -- **回滚**:`.env` 换回上一个 digest(建议平时就把旧 digest 留成注释行)→ `pull + up -d`; - 首发失败且无旧版本可回退时,直接 `docker compose down`(只影响 polaris 项目, - 不碰 nginx/gitea/PG/Redis 所在项目)。 -- **停机风险**:`up -d` 是停旧建新,有秒级中断;第一阶段单实例、不做零停机承诺, - 换版尽量选低峰。 -- **凭据维护**:宿主机 `~/.docker/config.json` 是 base64 明文,注意账号权限, - 不用时 `docker logout git.labmem.icu`;只读 PAT 泄露面小,但也要有轮换预案。 -- **磁盘维护**:每个 commit 一个镜像 tag,Gitea packages 会持续增长, - 定期在 packages 界面清理旧版本;runner 的 `runner-docker` 卷同样会积累构建缓存与悬空镜像, - 需要周期性清理——两边都不要按「镜像总体积」估算磁盘,按层与历史版本算。 -- **安全红线**:不要为了「自动化部署」把宿主机 Docker socket 挂给 CI/runner—— - 那等于交出宿主机 root;runner 已用 rootless dind 隔离,保持这个边界。 -- **展望(未实施)**:后续可探索「CI 通过受限通道触发部署脚本」之类的自动化, - 但第一阶段保持人工按 digest 部署:少一层自动化,少一类事故。 -- **net-db 仍不相干**:Polaris 目前没有后端依赖;将来若接入真实数据源, - 再单独评估网络与凭据设计。 - -## 8. 时间线小结 - -| 步骤 | 结果 | -|---|---| -| verify(lint/typecheck/test/build/standalone 冒烟) | ✅ 首次即通过 | -| publish:build + 镜像冒烟 | ✅ 通过 | -| publish:login/push | ❌ 缺 secrets → 配置 package 写 PAT 后修复 | -| push blob | ❌ 413 → gitea 站点 `client_max_body_size 1g` + 容器内 reload 后修复 | -| push 成功 | ✅ 得到 manifest digest(`sha256:`) | -| 宿主机部署 | ✅ 只读 PAT login → `.env` 固定 digest → pull/up → healthy | -| 反代接入 | ✅ net-public 内可达 `polaris:3000` → 动态解析反代 → 外部 curl 核验通过 | diff --git a/note/资源/DevOps/Gitea/local-ci/compose.yaml b/note/资源/DevOps/Gitea/local-ci/compose.yaml new file mode 100644 index 0000000..cfc13cb --- /dev/null +++ b/note/资源/DevOps/Gitea/local-ci/compose.yaml @@ -0,0 +1,88 @@ +# 本地单机 Gitea + runner(DinD)测试环境,仅用于本机可信代码测试。 +# 配套笔记:上级目录《Gitea 简单 CI 实践》——让其中的 demo workflow +# (runs-on: demo-ci、checkout@v4、Node 22)不依赖云服务器即可本机跑通。 +# +# ── 使用步骤(命令均从仓库根目录执行;若已 cd 进本目录,可去掉 -f 直接 `docker compose up -d`)── +# +# 1. 生成并导出注册令牌(只放在当前 shell 里,切勿写进仓库或 .env 文件): +# export GITEA_RUNNER_REGISTRATION_TOKEN="$(openssl rand -hex 32)" +# Gitea >= 1.23:在 Gitea 首次初始化(完成安装向导)之前设置该变量, +# 它会被固定为 runner 注册令牌;实例已初始化后再设置不会改变令牌, +# 此时以 管理后台/仓库 → Actions → Runners 页面显示的令牌为准。 +# +# 2. 启动(首次运行会拉取两个镜像): +# docker compose -f note/资源/DevOps/Gitea/local-ci/compose.yaml up -d +# +# 3. 打开 http://localhost:3000 完成首次安装(数据库保持默认 SQLite 即可)。 +# 若 runner 早于安装完成启动,注册会失败并按 restart 策略自动重试, +# 安装完成后无需手动干预,稍等即可看到 runner 在线。 +# +# 4. 注册/登录 → 新建仓库 → 仓库 设置 → Actions 启用; +# 按《Gitea 简单 CI 实践》加入 demo.js、demo.test.js 与 +# .gitea/workflows/ci.yml。本机 git 克隆/推送走 HTTP 协议: +# git clone http://localhost:3000//.git +# +# 5. 仓库 Actions 页查看 run;需要排查 runner 时: +# docker compose -f note/资源/DevOps/Gitea/local-ci/compose.yaml logs runner +# +# 6. 账号、仓库、runner 注册与镜像缓存在三个命名卷里,保留卷即保留数据; +# 彻底重来:docker compose -f note/资源/DevOps/Gitea/local-ci/compose.yaml down -v +# +# ── 安全提醒 ── +# runner 容器 privileged: true(DinD 必需),权限等同宿主 root; +# 本文件只用于本机可信仓库的本地测试,勿用于不可信仓库或对外提供服务。 + +services: + gitea: + image: docker.gitea.com/gitea:latest + restart: unless-stopped + environment: + # 单机演示固定 SQLite,数据随 gitea-data 卷持久化 + GITEA__database__DB_TYPE: sqlite3 + # 页面/克隆地址显示为 localhost,方便本机浏览器与 git 客户端 + GITEA__server__ROOT_URL: http://localhost:3000/ + # 与 runner 用同一个固定注册令牌(含义见文件头第 1 步) + GITEA_RUNNER_REGISTRATION_TOKEN: "${GITEA_RUNNER_REGISTRATION_TOKEN:?set GITEA_RUNNER_REGISTRATION_TOKEN}" + ports: + # 只绑定回环地址,不暴露到局域网;git 走 HTTP,无需映射 SSH 端口 + - "127.0.0.1:3000:3000" + volumes: + - gitea-data:/data + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:3000/api/healthz"] + interval: 10s + timeout: 5s + retries: 12 + start_period: 30s + + runner: + image: docker.io/gitea/runner:3-dind + restart: unless-stopped + # DinD:job 容器跑在 runner 内部的嵌套 Docker daemon 里,需要 privileged + privileged: true + depends_on: + gitea: + condition: service_healthy + environment: + CONFIG_FILE: /config.yaml + # 注册与克隆都发生在 runner 容器自身,它在 compose 网络内可直连 gitea 服务 + GITEA_INSTANCE_URL: http://gitea:3000 + GITEA_RUNNER_NAME: local-demo + # 与 demo workflow 的 runs-on: demo-ci 一致;node:22-bookworm 提供 Node 22 + GITEA_RUNNER_LABELS: demo-ci:docker://node:22-bookworm + GITEA_RUNNER_REGISTRATION_TOKEN: "${GITEA_RUNNER_REGISTRATION_TOKEN:?set GITEA_RUNNER_REGISTRATION_TOKEN}" + volumes: + - ./runner-config.yaml:/config.yaml:ro + # runner 状态(含 .runner 注册文件) + - runner-data:/data + # 嵌套 dockerd 的镜像与构建缓存 + - runner-docker:/var/lib/docker + # DinD 默认给 job 建独立网络;checkout 的 git fetch 会访问 http://gitea:3000, + # job 若无法解析 gitea 就会失败,即使 runner 容器能解析也没用。 + # runner-config.yaml 设置 container.network: host,使 job 共用 runner 容器的 + # 网络命名空间(不是宿主机网络);隔离性随之降低,仅限本地可信测试。 + +volumes: + gitea-data: + runner-data: + runner-docker: diff --git a/note/资源/DevOps/Gitea/local-ci/runner-config.yaml b/note/资源/DevOps/Gitea/local-ci/runner-config.yaml new file mode 100644 index 0000000..49c80e2 --- /dev/null +++ b/note/资源/DevOps/Gitea/local-ci/runner-config.yaml @@ -0,0 +1,302 @@ +# Every option with its default value, all commented out. Read this file, do not copy it. +# `./gitea-runner config init` writes a config file to copy the lines you change into. + +# Logging for the runner process itself (messages printed to stderr), plus the copy of +# each task's log kept under log.job. Neither controls how workflow step output is streamed +# to the Gitea UI; tune that with runner.log_report_* below. +log: + # logrus severity: trace, debug, info, warn, error, fatal, panic. + # trace and debug turn on caller/file:line in log lines. Default if omitted: info. + #level: info + # Write a copy of each task's log to dir as -task-.log, so a job's output + # survives a failure to send it to Gitea. A path turns them on, empty turns them off. + # retention is how long a log is kept (0s keeps all), max_size caps one log (0 is no limit). + #job: + # dir: "" + # retention: 168h + # max_size: 1GB + +runner: + # Where to store the registration result. + #file: .runner + # Execute how many tasks concurrently at the same time. + # With `container.network` empty, each concurrent docker job takes a subnet from the + # daemon's address pool, so a high capacity can exhaust it. See `default-address-pools` + # in the docker daemon config. + #capacity: 1 + # Extra environment variables to run jobs. + #envs: + # A_TEST_ENV_NAME_1: a_test_env_value_1 + # A_TEST_ENV_NAME_2: a_test_env_value_2 + # Extra environment variables to run jobs from a file. + # It will be ignored if it's empty or the file doesn't exist. + #env_file: .env + # The timeout for a job to be finished. + # Please note that the Gitea instance also has a timeout (3h by default) for the job. + # So the job could be stopped by the Gitea instance if its timeout is shorter than this. + #timeout: 3h + # The timeout for the runner to wait for running jobs to finish when shutting down. + # Any running jobs that haven't finished after this timeout will be cancelled. + #shutdown_timeout: 0s + # Whether skip verifying the TLS certificate of the Gitea instance. + #insecure: false + # The timeout for fetching the job from the Gitea instance. + # Values above the 60s RPC timeout are capped to it. + #fetch_timeout: 5s + # The interval for fetching the job from the Gitea instance. + #fetch_interval: 2s + # The maximum interval for fetching the job from the Gitea instance. + # The runner uses exponential backoff when idle, increasing the interval up to this maximum. + # Set to 0 or same as fetch_interval to disable backoff. + #fetch_interval_max: 5s + # While idle, remove stale bind-workdir task directories and orphaned host-mode + # scratch directories (left behind when a host cleanup delete stalls) older than + # this duration. Setting either workdir_cleanup_age or idle_cleanup_interval to 0 + # (or any non-positive value) disables stale-directory cleanup entirely, along with + # the docker network and volume cleanup below. + #workdir_cleanup_age: 24h + # Cadence for the idle cleanup pass. Besides the directories above, on runners that use + # docker it removes the per-job networks and volumes of jobs this runner did not live to + # tear down, which would otherwise hold a subnet of the daemon address pool and the job's + # workspace contents until the host is rebuilt. + #idle_cleanup_interval: 10m + # The base interval for periodic log flush to the Gitea instance. + # Logs may be sent earlier if the buffer reaches log_report_batch_size + # or if log_report_max_latency expires after the first buffered row. + #log_report_interval: 5s + # The maximum time a log row can wait before being sent. + # This ensures even a single log line appears on the frontend within this duration. + # Must be less than log_report_interval to have any effect. + #log_report_max_latency: 3s + # Flush logs immediately when the buffer reaches this many rows. + # This ensures bursty output (e.g., npm install) is delivered promptly. + #log_report_batch_size: 100 + # The interval for reporting task state (step status, timing) to the Gitea instance. + # State is also reported on step transitions (start/stop) and after each log flush. + #state_report_interval: 5s + # Per-attempt deadline for flushing the final logs and task state when a job + # finishes, on a detached context so a server cancel can't block the acknowledgement. + #report_close_timeout: 10s + # The github_mirror of a runner is used to specify the mirror address of the github that pulls the action repository. + # It works when something like `uses: actions/checkout@v4` is used and DEFAULT_ACTIONS_URL is set to github, + # and github_mirror is not empty. In this case, + # it replaces https://github.com with the value here, which is useful for some special network environments. + #github_mirror: '' + # When true (the default), fetch only the requested ref of an action repository (e.g. actions/checkout@v4) at depth 1 instead of cloning every branch's full history. + # Set to false to clone the full history. + #action_shallow_clone: true + # When true (the default), inject the ACT=true environment variable into jobs. + # Set to false so workflows gated on `if: ${{ !env.ACT }}` behave like they do on GitHub. + #set_act_env: true + # When true (the default), apply compatibility patches to the actions a job runs, so actions + # written for GitHub work against this instance. Set to false to run them exactly as published, + # at the price of the stock artifact actions refusing and the cache client keeping to v1. + #patch_actions: true + # The labels of a runner are used to determine which jobs the runner can run, and how to run them. + # Like: "macos-arm64:host" or "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest" + # Find more images provided by Gitea at https://gitea.com/gitea/runner-images . + # If it's empty when registering, it will ask for inputting labels. + # If it's empty when execute `daemon`, will use labels in `.runner` file. + #labels: + # - "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest" + # - "ubuntu-24.04:docker://docker.gitea.com/runner-images:ubuntu-24.04" + # - "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04" + # Allocate a pseudo-TTY for each step's process. Applies to both host and docker backends. + # Default false matches GitHub actions/runner. Enable only for jobs that need an interactive + # terminal; tools like `docker build` emit redrawing progress frames into the captured log + # when a TTY is present. + #allocate_pty: false + # Image for a job whose runs-on matches none of the labels above. A runner without a + # docker daemon runs such a job on the host instead. + #default_image: "docker.gitea.com/runner-images:ubuntu-latest" + # What to mount at RUNNER_TOOL_CACHE (/opt/hostedtoolcache), where setup actions install tools: + # none: nothing. A docker job sees what its image ships there, a host job an empty dir, and + # either way what it installs is gone when the job ends. + # shared: one tool cache every job reuses. Two jobs writing the same tool version at once + # corrupt it, so use it only with capacity 1. + #tool_cache_mode: none + # Optional executable on the host, run once after each task's built-in cleanup + # (post-steps, container teardown, bind-workdir removal). Additive only. + # + # IMPORTANT: While this script runs the runner stops task heartbeats and stays + # offline from Gitea's perspective until the script exits. A script that never + # returns blocks new work until post_task_script_timeout kills it (default 5m). + # Keep scripts short; set post_task_script_timeout to a safe upper bound. + # + # Output -> runner process log (not the job log). Non-zero exit -> warning only. + # Windows: use .exe, .bat, or .cmd. PowerShell (.ps1) is not supported yet as + # the configured path; wrap PowerShell commands in a .cmd file instead. + # Full guide: docs/post-task-script.md + #post_task_script: '' + # Hard limit on post_task_script runtime. Default if omitted: 5m. + #post_task_script_timeout: 5m + # Scripts run inside the job environment before the job's first step and after its last + # one, the equivalent of GitHub's ACTIONS_RUNNER_HOOK_JOB_STARTED and + # ACTIONS_RUNNER_HOOK_JOB_COMPLETED, which are read when these are unset. The paths are + # resolved inside the job environment. Either one failing fails the job. + # Full guide: docs/job-hooks.md + #hooks: + # job_started: '' + # job_completed: '' + +cache: + # Enable caching (used by actions/cache and similar actions). Off means no built-in server is + # started and no job is registered with an external_server either. + #enabled: true + # Directory where cache blobs are stored on disk. Default: $HOME/.cache/actcache + # Ignored when external_server is set. + #dir: "" + # Outbound IP or hostname that job containers use to reach this runner's cache server. + # Leave empty to detect automatically. 0.0.0.0 is not valid here. + # If the runner itself runs in Docker, automatic detection can choose an + # address on the runner container's network that job containers cannot reach + # when the runner creates a separate per-job network. In that case, set this + # to a hostname/IP reachable from job containers, and set port to a fixed + # published port or put the job containers on a shared Docker network. + # Ignored when external_server is set. + #host: "" + # Port for the built-in cache server. 0 picks a random free port. + # Ignored when external_server is set. + #port: 0 + # URL of a shared `gitea-runner cache-server` to use instead of starting a local one. + # Set on every runner that should share a cache pool. A trailing slash is optional. + # Jobs reach the server at this URL too, so set it to the reverse proxy when one fronts the server. + # Example: "http://cache-host:8088/" + # Requires external_secret (below) to match the value on the cache-server. + #external_server: "" + # Shared secret between this runner and the external cache-server. + # Required when external_server is set. Must be identical on every runner and the cache-server. + # Generate with: openssl rand -hex 32 + #external_secret: "" + # Path to a file containing the shared secret, as an alternative to external_secret. + # Use this to keep the secret out of this file. + # Surrounding whitespace is trimmed, so a trailing newline in the file is fine. + # Setting both external_secret and external_secret_file is an error. + #external_secret_file: "" + # When true, reuse a cached action instead of fetching from the remote on every job. + # A moved tag (e.g. a re-tagged "v6") or an updated branch stays at the cached commit + # until its cache entry expires or is manually removed. + #offline_mode: false + # Serve the actions cache service v2 API. The actions that use it fall back to v1 on any host + # they do not take for GitHub, so reaching it means editing that check out of their own bundle, + # put back after the copy into the job. That edit is made either way, this only governs the API + # advertised. A bundle that does not match is left alone. With v2, artifact uploads go via the + # cache server unless container jobs cannot reach its Docker network. + #v2: true + # How the cache server discards entries, ignored when external_server is set since that + # server applies its own. Leave a setting out for its default; 0s or 0 turns the three + # limits off. Sizes accept 10GB, 512mb, 1TiB or a plain byte count, binary either way. + # Whatever these allow, the cache still sheds entries to keep free space on its volume + # above health_check.min_free_disk_space_mb when health checks are enabled. + # Remove entries nothing has read or written within this window. Only last access counts. + #retention: 168h + # Cap one repository, removing its least recently accessed entries until it fits. An entry + # larger than the limit is dropped rather than emptying the repository to make room. + #repo_size_limit: 10GB + # Cap the whole cache the same way. Off by default, since the free space floor bounds it. + #size_limit: 0 + # Minimum time between two eviction sweeps. This one has no "off". + #sweep_interval: 1h + +container: + # Specifies the network to which the container will connect. + # Could be host, bridge or the name of a custom network. + # If it's empty, runner will create a network automatically. + # For dockerized runners using the built-in cache server, a custom shared + # network can be required so job containers can reach cache.host/cache.port. + # Deprecated: `network_mode` is still accepted for old configs; use `network` instead. + network: host + # network_create_options only apply when `network` is left empty and the runner + # auto-creates a per-job network that does not already exist. They have no effect + # when a custom `network` name is set, because that network is used as-is and never + # created by the runner. Omit the entire block to use Docker's defaults. An auto-created + # network is labelled com.gitea.runner.uuid=, which is how the idle + # cleanup tells its own leftovers apart from those of other runners on the same daemon. + #network_create_options: + # enable_ipv4: true # Omit to use Docker's default (IPv4 enabled). Set false to disable IPv4. + # enable_ipv6: false # Omit to use Docker's default (IPv6 disabled). Enabling it requires dockerd started with --ipv6. + # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker). + #privileged: false + # Any other options to be used when the container is started, for example: + # options: --add-host=my.gitea.url:host-gateway + # A volume declared here replaces the one the runner would mount on the same container path, so the + # tool cache can be kept on the host. Its source must also be allowed by valid_volumes below: + # options: --volume /host/toolcache:/opt/hostedtoolcache + # With privileged disabled, options that could escape the container (--security-opt, --device, + # --cap-add, --pid, ...) are ignored in a workflow's container.options, but keep working here. + #options: + # The parent directory of a job's working directory. + # NOTE: There is no need to add the first '/' of the path as runner will add it automatically. + # If the path starts with '/', the '/' will be trimmed. + # For example, if the parent directory is /path/to/my/dir, workdir_parent should be path/to/my/dir + # If it's empty, /workspace will be used. + # Purely numeric subdirectories under this path are reserved for task workspaces and may be removed by idle cleanup. + #workdir_parent: + # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob + # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted. + # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to: + # valid_volumes: + # - data + # - /src/*.json + # If you want to allow any volume, please use the following configuration: + # valid_volumes: + # - '**' + #valid_volumes: [] + # Overrides the docker client host with the specified one. + # If it's empty, runner will find an available docker host automatically. + # If it's "-", runner will find an available docker host automatically, but won't mount it into the job and action containers. + # If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work. + #docker_host: "" + # Pull docker image(s) even if already present. + # Defaults to false when the key is omitted. + # Two exceptions: an image pinned by digest (image@sha256:...) cannot change, so it is never + # re-pulled, and a pull that fails while a copy is already on the host does not fail the job, + # which runs on that copy with a warning in its log. + #force_pull: false + # Rebuild docker image(s) even if already present + #force_rebuild: false + # Always require a reachable docker daemon, even if not required by runner + #require_docker: false + # Timeout to wait for the docker daemon to be reachable, if docker is required by require_docker or runner + #docker_timeout: 0s + # Mount the workspace from a host directory instead of a Docker volume, so jobs + # can bind-mount it by its own path into sibling containers (".:/app" in docker + # compose). Not needed when jobs get the Docker proxy or workflows use the + # GITEA_DOCKER_WORKSPACE variable. + # The workspace parent directory must be mounted into the runner container. + #bind_workdir: false + # How long a job waits for a service container that declares a healthcheck to become + # healthy. A negative value (e.g. -1s) starts the steps without waiting. + #service_ready_timeout: 5m + +host: + # The parent directory of a job's working directory. + # If it's empty, $HOME/.cache/act/ will be used. + #workdir_parent: + +# Optional local task-admission checks. Disabled by default. When enabled, low +# disk space or a failing script pauses new task fetching; existing jobs continue. +# No health checks run while any job is active; the last result is reused until idle. +health_check: + #enabled: false + # Minimum free space required on the filesystem holding runner workspaces. + # Defaults to 1024 MiB when omitted or set to zero. + #min_free_disk_space_mb: 1024 + # Optional additional executable. A non-zero exit, timeout, or startup failure + # marks the runner unavailable. + #script: '' + # How long a script result is cached and its maximum execution time. + #interval: 30s + #timeout: 10s + +metrics: + # Enable the Prometheus metrics endpoint. + # When enabled, metrics are served at /metrics, liveness at /healthz, and + # task-admission readiness at /readyz. + #enabled: false + # The address for the metrics HTTP server to listen on. + # Defaults to localhost only. Set to ":9101" to allow external access, + # but ensure the port is firewall-protected as there is no authentication. + #addr: "127.0.0.1:9101" + # Consecutive polling failures may last this long before /readyz returns 503. + #readiness_grace: 30s