Compare commits
3
Commits
d45d713a50
...
e4fc4af912
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4fc4af912 | ||
|
|
d63b006135 | ||
|
|
3280c8a7bc |
@@ -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
|
||||
)
|
||||
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
# Lyra 目标结构
|
||||
|
||||
日期:2026-10-16
|
||||
|
||||
本文记录已确认的目标目录结构与职责边界。Lyra 当前仅有 go.mod 和文档,代码尚未实施;以下文件为计划形态,不代表已存在。选型与讨论过程见 [plans/2026-10-16-lyra-architecture.md](plans/2026-10-16-lyra-architecture.md)。
|
||||
|
||||
## 已确认技术栈
|
||||
|
||||
Gin(HTTP 路由)、PostgreSQL、Squirrel + sqlx(数据访问,pgx stdlib 驱动)、Goose(数据库迁移)。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```text
|
||||
lyra/
|
||||
├── cmd/lyra/main.go # 启动、依赖组装、退出清理
|
||||
├── internal/
|
||||
│ ├── api/ # HTTP 请求/响应 DTO,按资源分文件
|
||||
│ │ ├── links.go # links 资源示例(下同)
|
||||
│ │ ├── categories.go
|
||||
│ │ └── settings.go
|
||||
│ ├── config/
|
||||
│ │ └── config.go # 配置定义与加载
|
||||
│ ├── postgres/
|
||||
│ │ └── postgres.go # 数据库连接
|
||||
│ ├── router/
|
||||
│ │ └── router.go # 路由注册、项目路径前缀
|
||||
│ ├── handler/
|
||||
│ │ └── links.go # 参数绑定、DTO 转换、响应输出
|
||||
│ ├── service/
|
||||
│ │ ├── links.go # 业务规则、事务协调
|
||||
│ │ └── links_test.go # 测试与源文件同目录
|
||||
│ ├── model/
|
||||
│ │ ├── link.go # Link 数据结构定义
|
||||
│ │ ├── link_model.go # 数据访问对象:CRUD 与 SQL(示例)
|
||||
│ │ └── link_model_test.go # 查询测试(示例)
|
||||
│ └── httpx/
|
||||
│ └── response.go # 公共响应辅助,按需实现
|
||||
├── migrations/ # Goose SQL 迁移文件
|
||||
├── docs/
|
||||
│ ├── plans/
|
||||
│ └── architecture.md # 本文
|
||||
├── README.md
|
||||
├── go.mod
|
||||
└── go.sum # 引入依赖后由 Go 工具生成
|
||||
```
|
||||
|
||||
- 标注示例的文件展示一个资源在各层的文件分布;分类、设置等资源按同样方式在 api/handler/service/model 新增对应文件。model/link.go 定义 Link 数据结构,model/link_model.go 定义数据访问与 SQL,model/link_model_test.go 测试查询,均为示例,实际文件随实现新增。
|
||||
- 只创建实际需要的文件;不预建空目录或空测试文件,具体迁移文件名随首个迁移确定。
|
||||
|
||||
## 组织原则
|
||||
|
||||
- 不按业务项目建代码子目录(无 polaris 等项目目录);项目通过 `/api/polaris` 等路由前缀区分,代码按技术职责分组,包内文件按资源划分。
|
||||
- 不保留 platform/modules 分类层;配置在 internal/config,连接在 internal/postgres,HTTP 辅助在 internal/httpx。
|
||||
- 简单接口不强制走完整三层,也不按每张表机械复制层级。
|
||||
|
||||
## 层间边界
|
||||
|
||||
- 主调用链为 handler→service→model→PostgreSQL;service 负责业务规则及需要的事务协调,model 支持同一事务内的数据库操作,不将跨多次写入各自独立提交当成原子业务。
|
||||
- 请求/响应 DTO 集中在 internal/api,与内部模型分开;即使初期字段相近,也不默认把数据库模型直接暴露为响应,接受必要的显式转换代码。
|
||||
- handler 引用 API DTO 完成绑定与转换;service/model 不依赖 Gin,也不依赖 internal/api DTO;api 包不反向引用 handler/service/model,避免传输契约与实现耦合或循环引用。
|
||||
- model 承载数据结构定义与数据访问:Squirrel/SQL 构造、sqlx 执行与结果映射,驱动为 pgx stdlib;不是 ORM 自动持久化,也不要求每种类型机械建立一套结构。
|
||||
- service/model 使用标准 context.Context,不接收 gin.Context。
|
||||
|
||||
## 尚未确定
|
||||
|
||||
以下仍为计划中的推荐或待确认项,未定为已决:
|
||||
|
||||
- 代码尚未实施,也未进行数据库连接或结构修改。
|
||||
- Goose 的部署执行入口与并发控制(串行执行或数据库锁)方案。
|
||||
- PostgreSQL 按项目划分 schema 的做法(组织边界,不等于权限隔离)。
|
||||
- 日志方案(候选标准库 slog)与中间件集成方式。
|
||||
@@ -0,0 +1,130 @@
|
||||
-- Polaris 导航站 PostgreSQL 新库初始化示例(非幂等,仅执行一次)。
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- updated_at 维护函数
|
||||
CREATE FUNCTION public.set_updated_at()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
NEW.updated_at := now();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$;
|
||||
|
||||
-- 用户表
|
||||
CREATE TABLE public.users (
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
account text NOT NULL UNIQUE,
|
||||
password_hash text NOT NULL,
|
||||
display_name text NOT NULL,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
CONSTRAINT users_account_not_empty CHECK (btrim(account) <> ''),
|
||||
CONSTRAINT users_display_name_not_empty CHECK (btrim(display_name) <> '')
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.users IS '后台管理用户表';
|
||||
COMMENT ON COLUMN public.users.id IS '用户 ID';
|
||||
COMMENT ON COLUMN public.users.account IS '登录账号,当前大小写敏感';
|
||||
COMMENT ON COLUMN public.users.password_hash IS '密码哈希';
|
||||
COMMENT ON COLUMN public.users.display_name IS '展示昵称';
|
||||
COMMENT ON COLUMN public.users.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN public.users.updated_at IS '更新时间,触发器自动刷新';
|
||||
|
||||
-- 分类表
|
||||
CREATE TABLE public.categories (
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
slug text NOT NULL UNIQUE,
|
||||
title text NOT NULL,
|
||||
description text NOT NULL DEFAULT '',
|
||||
icon text NOT NULL,
|
||||
sort integer NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
CONSTRAINT categories_slug_format CHECK (slug ~ '^[a-z0-9]+(-[a-z0-9]+)*$'),
|
||||
CONSTRAINT categories_title_not_empty CHECK (btrim(title) <> '')
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.categories IS '链接分类表';
|
||||
COMMENT ON COLUMN public.categories.id IS '分类 ID,内部关联用';
|
||||
COMMENT ON COLUMN public.categories.slug IS '页面地址(路由)标识';
|
||||
COMMENT ON COLUMN public.categories.title IS '分类标题';
|
||||
COMMENT ON COLUMN public.categories.description IS '分类描述';
|
||||
COMMENT ON COLUMN public.categories.icon IS '分类图标标识';
|
||||
COMMENT ON COLUMN public.categories.sort IS '分类排序,越小越靠前';
|
||||
COMMENT ON COLUMN public.categories.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN public.categories.updated_at IS '更新时间,触发器自动刷新';
|
||||
|
||||
-- 链接表
|
||||
CREATE TABLE public.links (
|
||||
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
|
||||
category_id bigint NOT NULL,
|
||||
title text NOT NULL,
|
||||
description text NOT NULL DEFAULT '',
|
||||
url text NOT NULL,
|
||||
icon text,
|
||||
keywords text[] NOT NULL DEFAULT ARRAY[]::text[],
|
||||
sort integer NOT NULL DEFAULT 0,
|
||||
pinned boolean NOT NULL DEFAULT false,
|
||||
pinned_sort integer,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now(),
|
||||
CONSTRAINT links_category_fk
|
||||
FOREIGN KEY (category_id) REFERENCES public.categories (id)
|
||||
ON DELETE RESTRICT,
|
||||
CONSTRAINT links_title_not_empty CHECK (btrim(title) <> ''),
|
||||
CONSTRAINT links_url_not_empty CHECK (btrim(url) <> ''),
|
||||
CONSTRAINT links_pinned_sort_consistent CHECK (pinned = (pinned_sort IS NOT NULL)),
|
||||
CONSTRAINT links_keywords_one_dimension
|
||||
CHECK (cardinality(keywords) = 0 OR array_ndims(keywords) = 1),
|
||||
CONSTRAINT links_keywords_no_null_elements
|
||||
CHECK (array_position(keywords, NULL) IS NULL)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE public.links IS '链接表,归属分类';
|
||||
COMMENT ON COLUMN public.links.id IS '链接 ID';
|
||||
COMMENT ON COLUMN public.links.category_id IS '所属分类 ID,删分类前需先处理链接';
|
||||
COMMENT ON COLUMN public.links.title IS '链接标题';
|
||||
COMMENT ON COLUMN public.links.description IS '链接描述';
|
||||
COMMENT ON COLUMN public.links.url IS '链接地址,应用层校验,去尾斜杠后全局唯一';
|
||||
COMMENT ON COLUMN public.links.icon IS '链接图标,可空,前端回退';
|
||||
COMMENT ON COLUMN public.links.keywords IS '搜索关键词数组,供本地搜索匹配';
|
||||
COMMENT ON COLUMN public.links.sort IS '分类内排序,越小越靠前';
|
||||
COMMENT ON COLUMN public.links.pinned IS '首页快捷入口(手动固定),非点击排行';
|
||||
COMMENT ON COLUMN public.links.pinned_sort IS '首页快捷入口顺序,越小越靠前';
|
||||
COMMENT ON COLUMN public.links.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN public.links.updated_at IS '更新时间,触发器自动刷新';
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX categories_order_idx
|
||||
ON public.categories (sort, id);
|
||||
|
||||
CREATE INDEX links_category_order_idx
|
||||
ON public.links (category_id, sort, id);
|
||||
|
||||
CREATE INDEX links_pinned_order_idx
|
||||
ON public.links (pinned_sort, id)
|
||||
WHERE pinned;
|
||||
|
||||
CREATE UNIQUE INDEX links_normalized_url_unique
|
||||
ON public.links (rtrim(url, '/'));
|
||||
|
||||
-- 触发器
|
||||
CREATE TRIGGER users_set_updated_at
|
||||
BEFORE UPDATE ON public.users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.set_updated_at();
|
||||
|
||||
CREATE TRIGGER categories_set_updated_at
|
||||
BEFORE UPDATE ON public.categories
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.set_updated_at();
|
||||
|
||||
CREATE TRIGGER links_set_updated_at
|
||||
BEFORE UPDATE ON public.links
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION public.set_updated_at();
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,123 @@
|
||||
# Lyra 技术选型与架构讨论记录
|
||||
|
||||
日期:2026-10-16
|
||||
|
||||
状态:HTTP 路由、数据访问、数据库迁移工具与目标目录结构已确认(结构见 [architecture.md](../architecture.md));本文档方案已获文档层面批准,代码实施尚未批准。
|
||||
|
||||
## 已确认
|
||||
|
||||
- Lyra 是个人 Go 后端,先承接 Polaris,未来承接其他项目接口。
|
||||
- Polaris 采用一人管理、匿名公开浏览的模式。
|
||||
- 已有服务器和 PostgreSQL;具体部署与连接配置尚未核实。
|
||||
- 数据访问选择 Squirrel + sqlx,PostgreSQL 驱动使用 pgx stdlib。
|
||||
- HTTP 路由选择 Gin;用户无明显接口风格偏好,结合文档与开发便利性作出选择。
|
||||
- 数据库迁移工具选择 Goose。
|
||||
- 本次确认仅涉及选型,不代表批准搭建代码或接入真实数据库。
|
||||
|
||||
## 数据访问约定
|
||||
|
||||
- 固定查询直接写 SQL,动态查询使用 Squirrel,统一由 sqlx 执行。
|
||||
- Squirrel 使用 Dollar 占位符,普通值使用参数绑定,动态标识符采用服务端白名单。
|
||||
- 使用带 Context 的执行方法;需要原子性的业务操作使用事务。
|
||||
- 显式声明查询字段,处理可空值和 JOIN 列名冲突。
|
||||
- 不同时引入 sqlc 或 golang-migrate。
|
||||
- 已讨论的取舍:无代码生成步骤,但查询与结果映射主要在运行时校验;上游维护低频。
|
||||
|
||||
## 当前实现状态
|
||||
|
||||
- Lyra 仅有 go.mod 与 docs/ 下的文档,尚无接口、数据库或业务代码。
|
||||
- Polaris 的 services 仍同步读取本地 JSON,未接入 Lyra。
|
||||
- 未进行数据库连接或数据库结构修改。
|
||||
|
||||
## 架构建议(尚未定案)
|
||||
|
||||
- 一个 Go 服务;根据用户不希望按 Polaris 建立独立项目目录的偏好,调整为按技术职责组织代码,项目在 HTTP 路由上使用不同前缀。
|
||||
- 各项目使用独立 HTTP 前缀,共享配置、日志、连接池等基础设施。
|
||||
- PostgreSQL 可按项目划分 schema;这是组织边界,不等于权限隔离。
|
||||
- 不提前建设微服务、统一用户中心或通用 CRUD 框架。
|
||||
- 日志候选为标准库 slog,具体中间件与日志集成方式另行确认。
|
||||
|
||||
## 目录结构与边界(已确认)
|
||||
|
||||
2026-10-16 用户确认以下目标组织方式;同日确认将数据访问职责并入 model,不再设独立 internal/store 目录。完整结构记录见 [architecture.md](../architecture.md)。确认的是结构与职责划分,不代表立即创建所有文件或占位目录,代码实施仍需另行批准。
|
||||
|
||||
```text
|
||||
lyra/
|
||||
├── cmd/lyra/main.go
|
||||
├── internal/
|
||||
│ ├── api/
|
||||
│ │ ├── links.go
|
||||
│ │ ├── categories.go
|
||||
│ │ └── settings.go
|
||||
│ ├── config/
|
||||
│ │ └── config.go
|
||||
│ ├── postgres/
|
||||
│ │ └── postgres.go
|
||||
│ ├── router/
|
||||
│ │ └── router.go
|
||||
│ ├── handler/
|
||||
│ │ └── links.go
|
||||
│ ├── service/
|
||||
│ │ ├── links.go
|
||||
│ │ └── links_test.go
|
||||
│ ├── model/
|
||||
│ │ ├── link.go
|
||||
│ │ ├── link_model.go
|
||||
│ │ └── link_model_test.go
|
||||
│ └── httpx/
|
||||
│ └── response.go
|
||||
├── migrations/
|
||||
│ └── 00001_polaris_create_tables.sql
|
||||
├── docs/plans/
|
||||
├── README.md
|
||||
├── go.mod
|
||||
└── go.sum
|
||||
```
|
||||
|
||||
- cmd/lyra 负责启动、依赖组装、调用路由注册和退出清理,不放业务规则。
|
||||
- 用户提出统一管理接口请求与响应;推荐 internal/api,只放 HTTP 请求与响应 DTO 及其字段标签,按资源组织文件,不放 handler、业务逻辑或数据库查询。
|
||||
- internal/router 注册路由与项目路径前缀;handler 处理参数绑定、DTO 转换与响应输出;service 管理业务规则;model 承载数据结构定义与数据访问(Squirrel/SQL 构造、sqlx 执行与结果映射),驱动为 pgx stdlib。测试与被测文件同目录。
|
||||
- 树中 handler/service/model 仅列出链接资源示例;model/link.go 定义 Link 数据结构,model/link_model.go 定义数据访问与 SQL,model/link_model_test.go 测试查询,均为示例,实际文件随实现新增,不创建空占位目录或文件。
|
||||
- handler 引用 API DTO 并转换为业务输入或响应;service/model 不依赖 API DTO 包,API DTO 包不引用 handler/service/model,避免传输契约和实现相互耦合或循环引用。
|
||||
- 请求、响应与数据库模型职责分开,即使初期字段相近,也不默认直接把数据库模型暴露为响应;接受必要的显式转换代码。
|
||||
- 主调用链为 handler→service→model→PostgreSQL;service 负责业务规则及需要的事务协调,model 支持同一事务内的数据库操作,不将跨多次写入各自独立提交当成原子业务。
|
||||
- 上述文件是职责划分建议,不要求简单接口机械地走完整三层,也不按每张表或每种类型机械复制一套层级与文件。
|
||||
- HTTP 层可以依赖 Gin;service 和 model 使用标准 context.Context,不接收 gin.Context。
|
||||
- 移除 platform 和 modules 分类层;配置、连接和 HTTP 辅助直接位于 internal/config、internal/postgres、internal/httpx,减少无必要的路径深度。httpx 只在需要统一响应输出时实现,不提前抽象用户、设置、通用 CRUD 等业务。
|
||||
- go.sum 在引入依赖后由 Go 工具生成;测试文件随实际功能编写,不创建空占位文件。
|
||||
- 新项目可以在路由上增加独立前缀并复用上述技术层目录,不强制增加项目目录。不同领域的同名类型需要明确命名;文件名本身不提供 Go 包级符号隔离。
|
||||
- 路由前缀只区分 HTTP 入口,不自动隔离代码依赖、数据库或权限;业务所有权与鉴权仍需显式设计,不能因路径不同就假定隔离成立。
|
||||
- PostgreSQL 按项目划分 schema 仍为建议,尚未确认;schema 不自动提供权限隔离。
|
||||
|
||||
### 外部示例参考
|
||||
|
||||
- 参考 https://github.com/vespeng/gin-pathway ,读取 main 分支 commit 67582d1。
|
||||
- 借鉴其薄启动入口、显式依赖组装与统一错误处理思路;曾考虑其按业务分组,后按用户偏好改为技术分层,不直接复制具体代码或全部依赖。
|
||||
- 该示例没有独立 API DTO 目录,部分数据库模型直接作为响应,本方案不沿用这一点。
|
||||
- 不沿用其启动时同步表结构的机制;Lyra 仍使用已确认的 Goose 管理迁移,具体执行方式待确认。
|
||||
- 不预建空 pkg、scripts、tests 等目录,不引入当前无需求的 Redis、ORM 或全局数据库单例。
|
||||
- internal/api 与根 api 都是可行布局,小写 api 是命名习惯;推荐 internal/api 是因为当前 DTO 仅供 Lyra 内部使用,而非 Go 语言强制要求。
|
||||
- 另参考 https://github.com/llmons/go-dianping ,读取 main 分支 commit cc7fbcf9:借鉴其独立 API 响应定义、技术分层与构造函数注入依赖;不照搬 ORM 生成层及缓存设施。
|
||||
- go-dianping 的部分请求与响应复用数据库模型,Lyra 的独立请求/响应 DTO 方案不沿用这一做法。
|
||||
|
||||
## 推荐迁移执行方式(待用户确认)
|
||||
|
||||
- 起步采用 Goose CLI,在独立部署步骤中显式执行 SQL 迁移;Web 服务启动时不自动迁移。
|
||||
- 迁移成功后再启动或更新服务;迁移失败则停止后续发布,检查实际状态。
|
||||
- 同一个 Lyra 数据库初期使用一个 migrations 目录和统一版本序列,文件名标注所属模块;不让不同模块共用版本表却各自维护重复版本号。
|
||||
- 部署流程保证同一数据库的迁移串行执行;如以后存在多个迁移入口或并发发布,再选择带数据库锁的迁移执行入口,不假定事务本身能防止并发迁移。
|
||||
- 优先 SQL 迁移,非事务 DDL 单独设计;生产回退需要评估数据影响,不自动执行 Down。
|
||||
- 表结构迁移与 Polaris JSON 初次导入分开设计,不能把 Goose 等同于业务数据导入工具。
|
||||
|
||||
## 后续讨论顺序
|
||||
|
||||
1. 确认 Goose 的执行入口、串行执行或数据库锁方案(技术分层与路由前缀组织已确认,见 [architecture.md](../architecture.md))。
|
||||
2. 确认首阶段范围:建议先做 JSON 导入、只读 API 和 Polaris 接入;管理写入另行确定认证方式。
|
||||
3. 编写实施与验证方案,获得明确批准后再实施。
|
||||
|
||||
## 后续实施方案必须覆盖的验证
|
||||
|
||||
- 使用隔离的 PostgreSQL 测试环境验证迁移、核心查询与事务,不默认操作现有真实数据。
|
||||
- 核对导入前后的分类、链接、排序和置顶语义。
|
||||
- 接入 Polaris 时验证 service 异步化、动态分类路由、缓存与后端故障处理。
|
||||
- 管理写接口上线前验证鉴权,不能以隐藏入口代替权限控制。
|
||||
@@ -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 执行,可以二进制安装或者容器安装,官方文档:<https://docs.gitea.com/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 快速上手:<https://docs.gitea.com/usage/actions/quickstart/>
|
||||
- act_runner 注册:<https://docs.gitea.com/runner/registration/>
|
||||
- act_runner 标签:<https://docs.gitea.com/runner/labels/>
|
||||
@@ -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 管「怎么做」,本文管「为什么这么做、踩了什么坑」;若细节随演进不一致,以仓库内文件为准。
|
||||
> 文中命令均使用占位符(`<owner>`、`<full-commit-sha>`、`sha256:<digest>`、`<server>` 等),
|
||||
> 不含任何真实令牌、私钥或机器标识;`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;本文不写个人绝对路径,部署目录统一以
|
||||
`<deploy-dir>` 代指。注意 `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)
|
||||
└─ <deploy-dir> 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,再由
|
||||
`<deploy-dir>` 里的 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/<owner>/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/<owner>/polaris:<full-commit-sha>`,
|
||||
镜像与提交一一对应、可追溯;不用 `latest`,避免「部署的到底是谁」。
|
||||
3. **部署固定 digest 而非 tag**:tag 理论上可被覆盖,digest 是内容寻址、不可变;
|
||||
部署侧 `.env` 写 `@sha256:<digest>`,回滚也就是「换回旧 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容器名> nginx -t && docker exec <nginx容器名> nginx -s reload
|
||||
```
|
||||
|
||||
**教训**:413 不一定是应用问题,先看反代;registry 场景里
|
||||
`client_max_body_size` 要按「最大镜像层」而不是「镜像总体积」估算。
|
||||
|
||||
### 3.3 推送成功:digest 是什么、不是什么
|
||||
|
||||
push 成功后日志末尾给出 manifest digest(本文一律以占位 `sha256:<digest>` 表示)。
|
||||
两个容易误解的点:
|
||||
|
||||
- **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 <registry用户名> # 交互输入只读 token
|
||||
|
||||
# ② 准备部署文件(仓库 ts/web/nextjs/polaris/deploy/ 拷到服务器,见 README)
|
||||
cd <deploy-dir>
|
||||
cp .env.example .env
|
||||
# ③ .env 里固定 digest(同时注释保留来源 tag,便于溯源与回退):
|
||||
# POLARIS_IMAGE=git.labmem.icu/<owner>/polaris@sha256:<digest>
|
||||
# # 来源:git.labmem.icu/<owner>/polaris:<full-commit-sha>
|
||||
|
||||
# ④ 拉取并启动(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 <nginx容器名> 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容器名> nginx -t && docker exec <nginx容器名> 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:<digest>`) |
|
||||
| 宿主机部署 | ✅ 只读 PAT login → `.env` 固定 digest → pull/up → healthy |
|
||||
| 反代接入 | ✅ net-public 内可达 `polaris:3000` → 动态解析反代 → 外部 curl 核验通过 |
|
||||
@@ -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/<owner>/<repo>.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:
|
||||
@@ -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 <start time>-task-<id>.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=<this runner's 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
|
||||
Reference in New Issue
Block a user