Revert "Refactor Gitea production deployment process to utilize SSH for remote operations"
Deploy production / deploy (push) Successful in 24s

This reverts commit 99bc36439e.
This commit is contained in:
yuxuanhui
2026-09-25 00:54:16 +08:00
parent a3cb6dbacf
commit 79b432c3d0
9 changed files with 76 additions and 491 deletions
+47 -106
View File
@@ -1,141 +1,78 @@
# Gitea 生产部署
服务器分工:**A 提供 Gitea 和 Packages 镜像仓库,B 运行应用,C 运行 PostgreSQL**,B 与 C 通过内网连接。Gitea Runner 构建前后端镜像并推送到 A,然后通过 SSH 在 B 拉取镜像、检查配置与数据库、执行迁移并启动服务。Runner 可以在 A 或其他构建机上,无需访问 B 的 Docker socket。
本方案在目标 Linux 服务器上由已有 Gitea Runner 构建并启动 Docker Compose,复用已有 PostgreSQL。入口绑定 `127.0.0.1:8112`,由宿主机反向代理提供公网 HTTPS。现有 `compose.yaml`、`compose.public.yaml` 保持独立,不与生产文件叠加。
生产使用独立的 `compose.production.yaml`,只引用镜像,不在 B 构建。B 上的 backend 和 migrate 通过 `DATABASE_URL` 连接 C,使用 Compose 默认网络,不依赖外部数据库 Docker 网络。Web 绑定 `127.0.0.1:8112`,由 B 的宿主机反向代理提供 HTTPS。`compose.yaml`、`compose.public.yaml` 的本地构建方式不变,不与生产文件叠加。
## 1. 填写配置和数据库账号密码
## 1. 配置 Gitea Secrets 和 Variables
进入 **Gitea 仓库 → 设置 → Actions → Secrets / Variables**,按下表创建同名配置。敏感值填写在 Secrets,工作流仅在部署步骤通过环境变量注入,不生成服务器凭据文件。
进入 **仓库 → 设置 → Actions → Secrets / Variables**。现有应用 Secrets 继续使用,新增镜像仓库和 SSH 配置。
### Secrets
| 名称 | 必填 | 填写内容 |
| 位置 | 名称 | 填写内容 |
| --- | --- | --- |
| `PROD_HOST` | 是 | B 的 SSH 主机名或 IP,不带协议、用户名和端口 |
| `PROD_USER` | 是 | B 的 SSH 登录用户,能直接执行 Docker 命令,无需交互式 sudo |
| `PROD_SSH_KEY` | 是 | 专用 SSH 私钥完整内容,保留换行;对应公钥放入 B 用户的 `~/.ssh/authorized_keys`;使用无需交互输入口令的部署密钥 |
| `PROD_KNOWN_HOSTS` | 是 | 经核验的 B 主机公钥,采用 OpenSSH `known_hosts` 格式,见下文 |
| `PROD_PORT` | 否 | B 的 SSH 端口,默认 `22` |
| `REGISTRY_USERNAME` | 是 | 有权访问镜像 owner 的 Gitea 用户名 |
| `REGISTRY_PASSWORD` | 是 | 该用户的 Gitea Personal Access Token,至少具备 `write:package` 权限(包含拉取权限),并有目标 owner 的包访问权限 |
| `DATABASE_URL` | 是 | `postgresql+asyncpg://数据库账号:数据库密码@C的内网IP或域名:数据库端口/数据库名`,须从 B 的容器内可连接 |
| `ADMIN_PASSWORD` | 是 | 初始管理员密码,至少 12 字符,与数据库密码独立 |
| `ENCRYPTION_KEY` | 是 | Fernet 密钥,升级必须保持不变 |
| `WQ_EMAIL` | 是 | WorldQuant 登录邮箱 |
| `WQ_PASSWORD` | 是 | WorldQuant 登录密码,按原样填写,不加引号 |
| Secrets(必填) | `DATABASE_URL` | `postgresql+asyncpg://数据库账号:数据库密码@数据库网络别名:5432/数据库名` |
| Secrets(必填) | `ADMIN_PASSWORD` | 系统初始管理员密码,至少 12 字符,与数据库密码独立 |
| Secrets(必填) | `ENCRYPTION_KEY` | 下面命令生成的 Fernet 密钥,升级保持不变 |
| Secrets(必填) | `WQ_EMAIL` | WorldQuant 登录邮箱 |
| Secrets(必填) | `WQ_PASSWORD` | WorldQuant 登录密码,按原样填写,不加引号 |
| Variables(必填) | `DATABASE_NETWORK` | PostgreSQL 所在的现有 Docker 网络,例如 `1panel-network` |
| Variables(必填) | `PUBLIC_ORIGIN` | 实际 HTTPS 来源,例如 `https://alpha.your-domain.com`,不带路径或末尾 `/` |
| Variables(可选) | `ADMIN_USERNAME` | 初始管理员账号,默认 `admin` |
| Variables(可选) | `MCP_ENABLED` | 设置 `true` 启用 MCP;未配置时默认关闭 |
### Variables
端口与 AI 限制使用 `compose.production.yaml` 的默认值,不需要在 Gitea 配置:`WEB_PORT=8112`、`AI_REQUEST_LIMIT=12`、`AI_TOOL_LIMIT=12`、`AI_OUTPUT_TOKENS=4096`、`AI_TIMEOUT=180`。需要调整时修改 Compose 中对应默认值;工作流不再读取这些同名 Gitea Variables。
| 名称 | 必填 | 填写内容 |
| --- | --- | --- |
| `IMAGE_PREFIX` | 是 | `A的仓库地址/owner/镜像名前缀`,全小写,不带 `https://` 或标签;当前仓库可填 `tea.bcc-life.cn/sakibcc/worldquant-alpha-system` |
| `DEPLOY_PATH` | 是 | B 上的绝对部署目录,例如 `/opt/wq-alpha`;SSH 用户须可写 |
| `BUILD_PLATFORM` | 否 | B 的 CPU 平台,默认 `linux/amd64`;ARM64 服务器填 `linux/arm64` |
| `PUBLIC_ORIGIN` | 是 | 实际 HTTPS 来源,例如 `https://alpha.your-domain.com`,不带路径或末尾 `/` |
| `ADMIN_USERNAME` | 否 | 初始管理员账号,默认 `admin` |
| `MCP_ENABLED` | 否 | `true` 启用 MCP,默认关闭 |
镜像命名遵循 Gitea 的 `registry/owner/image:tag` 规则。以上示例产生两个包:
数据库连接串示例(实际填写时替换示例值):
```text
tea.bcc-life.cn/sakibcc/worldquant-alpha-system-backend:<完整提交 SHA>
tea.bcc-life.cn/sakibcc/worldquant-alpha-system-web:<完整提交 SHA>
postgresql+asyncpg://wq_user:YOUR_PASSWORD@postgresql:5432/wq_alpha
```
`REGISTRY_USERNAME` 是登录用户,`IMAGE_PREFIX` 中的 owner 是包所有者,两者在组织仓库中可能不同。Packages 属于该用户或组织,可在 Gitea 中关联到当前仓库。默认使用同一个 Token 推送和远程拉取,B 的部署进程因此也会接收该 Token。
主机公钥应从可信的 B 控制台取得,并与 SSH 扫描结果核对。例如在可信管理终端运行 `ssh-keyscan -p 22 B_HOST`,通过 B 的控制台核对其指纹后,将核验过的完整行保存为 `PROD_KNOWN_HOSTS`。非默认端口的主机字段应为 `[B_HOST]:PORT`;主机名必须与 `PROD_HOST` 一致。工作流严格校验主机身份,不会在每次部署时自动信任扫描结果。
生成新安装的加密密钥:
生成加密密钥:
```bash
python3 -c 'import base64,secrets; print(base64.urlsafe_b64encode(secrets.token_bytes(32)).decode())'
```
`DATABASE_URL` 中账号和密码的特殊字符须 URL 编码,例如 `@` → `%40`、`#` → `%23`、`/` → `%2F`。在 Gitea 输入框填写值本身,不加包裹引号或 `KEY=` 前缀;其他密码中的 `$`、引号和换行原样填写,SSH 传递会进行 Bash 转义。不要开启 `set -x` 或打印凭据。
`DATABASE_URL` 的用户名和密码中的特殊字符须做 URL 百分号编码,例如 `@` → `%40`、`#` → `%23`、`/` → `%2F`、`%` → `%25`。在 Gitea 输入框填写值本身,不添加包裹引号,不填写 `DATABASE_URL=` 前缀。管理员密码中的 `$`、引号等按原样填写,由环境变量传递,无需 shell 转义。不要在日志打印变量,或开启 `set -x`。
端口与 AI 限制继续使用 Compose 默认值:`WEB_PORT=8112`、`AI_REQUEST_LIMIT=12`、`AI_TOOL_LIMIT=12`、`AI_OUTPUT_TOKENS=4096`、`AI_TIMEOUT=180`。需要调整时修改 Compose 默认值;工作流不读取这些同名 Gitea Variables。
在现有 PostgreSQL 管理界面先创建专用数据库与账号,账号须可连接并拥有该库中应用 schema 的建表和迁移权限。确认网络存在、数据库别名可解析、PostgreSQL 允许该 Docker 网络访问。应用使用异步驱动 `asyncpg`;远程数据库或强制 TLS 的实例需另外按该实例的 TLS 配置调整连接参数,此模板默认同机 Docker 网络。
`DATABASE_URL` 示例(假设 C 的内网 IP 为 `10.0.0.30`,按实际账号、密码、端口和库名替换):
如果从已有安装迁移数据,需恢复完整数据库并沿用原 `ENCRYPTION_KEY`;修改管理员环境变量不会重置已有管理员密码。
```text
postgresql+asyncpg://wq_user:YOUR_PASSWORD@10.0.0.30:5432/wq_alpha
```
WorldQuant 凭据由环境变量管理,启动时加密写入数据库;页面只保留连接操作。修改 `WQ_PASSWORD` 后重新运行部署即可更新。`WQ_EMAIL` 必须与已有绑定账户一致,避免混入其他账户数据;生产不读取 `account.json` 或本地 `.env`。
本版本不再读取或校验 `DATABASE_NETWORK`,Gitea 中的旧变量可以删除。不要用 C 上的容器名或 Docker 网络名作为跨主机连接地址。
## 2. 配置 Gitea Runner
已有安装需沿用数据库和 `ENCRYPTION_KEY`;修改管理员环境变量不会重置已有密码。WorldQuant 凭据启动时加密写入数据库,`WQ_EMAIL` 必须与已有绑定账户一致;修改 `WQ_PASSWORD` 后重新部署即可更新。生产不读取 `account.json` 或 `.env`。
沿用已成功部署 `zhixing-system` 的运行器标签 **`ubuntu-latest`**,无需注册新的 `wq-production` 运行器。该运行器的执行环境须能通过 Docker CLI/Compose 操作同一台 1Panel 服务器的 Docker daemon;可以复用现有 Docker 连接方式,不强制 host 模式。
## 2. 准备 A、Runner、B 和 C
执行环境需要 Git、Bash、Node.js 20(checkout v4)以及支持 `up --wait --wait-timeout` 的 Docker Compose。本流程不需要 `flock` 或预建 `/opt/wq-alpha`。服务器需能访问 checkout action、基础镜像仓库和依赖源。
### A:Gitea Packages
在仓库启用 Actions,`main` push 或手动运行会部署。Secrets 通过部署步骤的环境变量注入,`--env-file /dev/null` 防止误读开发 `.env`。构建可并行;构建完成后,以 Docker 唯一容器名 `wq-alpha-production-deploy-lock` 互斥保护预检、迁移和服务切换。锁容器不启动、不携带凭据,正常结束或失败时删除;竞争失败的任务不会删除其他任务的锁。
启用仓库 Actions 和 Gitea Packages。Runner 与 B 都须能解析并访问 `IMAGE_PREFIX` 中的仓库地址;HTTPS 证书须被两端 Docker 信任。Gitea 前的反向代理须正确转发 `/v2/` 并允许镜像层上传。本流程不自动修改 Docker 的 insecure registry 或 TLS 设置。
如果 Runner 被强制终止或 Docker 断连,可能留下锁容器。先确认没有本项目部署正在执行,再手动运行 `docker rm wq-alpha-production-deploy-lock` 后重试。不要在部署进行时删除锁。
### Runner:构建与 SSH
## 3. 配置反向代理并首次运行
继续使用 `ubuntu-latest` 标签,该标签对应的**实际执行环境**需要 Git、Bash、Node.js 20(checkout v4)、Docker CLI/Buildx、OpenSSH 客户端、tar、base64,并能连接构建机的 Docker daemon。容器模式 Runner 也需要具备这些工具和 Docker 连接能力。
为 `PUBLIC_ORIGIN` 对应域名配置 HTTPS,转发至 **`http://127.0.0.1:8112`**(或你的 `WEB_PORT`)。代理须运行在宿主机网络中;独立 bridge 容器中的 `127.0.0.1` 不指向宿主机。若使用容器化 1Panel/OpenResty,先确认其网络模式。AI 流式响应需要关闭代理缓冲,并允许长连接/足够长的读取超时。
Runner 需要访问 checkout action、基础镜像仓库、依赖源、A 的镜像仓库和 B 的 SSH 端口。`BUILD_PLATFORM` 必须匹配 B;若与构建机架构不同,需事先在构建机配置对应的跨架构构建能力。
### B:运行环境
安装 Bash、tar、base64、Docker Engine 和支持 `pull --policy`、`run --pull`、`up --wait --wait-timeout` 的 Docker Compose 插件。SSH 用户需要直接操作 B 的本机 Docker daemon;不需要 Git、Node.js、Python 或源码 checkout。脚本使用临时 `DOCKER_CONFIG`,不要依赖用户配置目录中的命名 Docker context;使用本机默认 socket。
在 B 创建部署目录并交给部署用户,例如将示例用户 `deploy` 换成实际的 `PROD_USER`:
```bash
sudo install -d -m 700 -o deploy -g deploy /opt/wq-alpha
```
将部署公钥加入该用户的 `authorized_keys`,核对 B 的反向代理及应用容器到 C 数据库端口的连通性。B、C 主机内网互通不能替代容器内的连接验证。
### C:数据库
在 C 创建专用数据库和账号,赋予该库应用 schema 的建表与迁移权限。PostgreSQL 须监听内网可访问的接口,防火墙和 `pg_hba.conf` 须允许 B 的连接来源。默认 Docker bridge 出站通常使用 B 的主机地址,若配置了额外路由或 NAT,以 C 实际收到的来源地址为准。
若 PostgreSQL 运行在 C 的 Docker 容器中,须发布到 C 的内网地址,例如 `10.0.0.30:5432:5432`;`DATABASE_URL` 使用 C 的内网 IP 和发布端口。强制 TLS 的实例还需按数据库证书及连接要求配置客户端参数。部署流程只在 B 执行迁移,通过数据库连接访问 C,无需 C 的 SSH 凭据。
## 3. 部署流程与首次验收
`main` push 或手动运行工作流会依次执行:
1. checkout 当前提交,在 Runner 构建 backend/web,分别推送到 A 的 Gitea Packages。两个镜像均推送成功才执行 SSH 部署。
2. `scripts/deploy-remote.sh` 严格验证 B 主机公钥,通过同一 SSH 连接传入应用环境变量、生产 Compose 和部署脚本。
3. B 为每次尝试创建 `DEPLOY_PATH/releases/<提交 SHA>.<随机后缀>/`,只保存 `compose.production.yaml` 和 `scripts/deploy-production.sh`,无需更新项目源码。
4. B 登录 A 的仓库并拉取两个镜像。随后获得 Docker 部署锁,运行新 backend 镜像校验配置和数据库连接。
5. 记录旧镜像 ID/标签,停止 web/backend,执行一次性迁移,再使用已拉取的镜像启动并等待健康检查。后端保持一个实例、一个 worker。
Compose 项目名固定为 `wq-alpha-production`,所以独立发布目录不会创建第二套应用或卷。迁移使用同一 backend 镜像;停止服务后不再访问仓库或构建。迁移或健康检查失败会让 SSH 和 Actions 返回失败。
应用凭据通过 SSH 标准输入传递,不写入发布目录或 `.env`;Compose 显式使用 `--env-file /dev/null`。SSH 私钥与 Docker 登录信息使用权限受限的临时目录,正常结束或失败时清理,不覆盖用户原有 Docker 登录配置。进程被强制杀死时可能残留临时文件;拥有 Runner、B 或 Docker 管理权限的人仍可读取进程和容器运行时配置。
Docker 唯一容器名 `wq-alpha-production-deploy-lock` 保护预检、迁移及服务切换;锁容器不运行、不携带凭据。并发部署竞争失败会退出,且只清理自己取得的锁。Runner 被强制终止或 Docker 断连时可能留下锁;确认没有部署正在执行后,可在 B 手动删除:
```bash
docker rm wq-alpha-production-deploy-lock
```
B 的宿主机 HTTPS 反向代理应转发到 `http://127.0.0.1:8112`。独立 bridge 容器中的 `127.0.0.1` 不指向宿主机;使用 1Panel/OpenResty 时需确认其网络模式。AI 流式响应需要关闭代理缓冲,并设置足够的读取超时。
首次部署建议手动运行工作流,确认 Packages 中有两个提交标签、B 上容器健康,再通过公网检查 `/api/v1/health`、登录、写请求和 Cookie。容器健康检查不替代 DNS、HTTPS 和真实 Runner 验收。
## 4. 升级、备份和失败处理
每次升级包含停机窗口;提前暂停或结束长时间任务。发布前通过数据库管理工具备份专用库,并在独立安全位置备份加密密钥及必要配置。此工作流不会自动备份或自动恢复数据库。
构建、推送、建立 SSH 连接、拉取或预检阶段失败时不会停止旧服务。停止服务后的迁移、健康检查失败或 SSH 中断需要在 B 核对实际状态并人工处理;不要对可能已变更的 schema 自动降级。Actions 日志记录切换前的镜像 ID/标签,成功后输出当前提交。两端均不执行 image prune;保留需要回退的 Packages 标签及 B 上的发布目录。
提交标签用于定位版本;同一提交重跑会重新构建并覆盖同名标签,严格核对历史产物时使用日志中的镜像 ID/digest。回退须先确认旧代码与当前 schema 兼容,然后从安全渠道注入对应配置、`IMAGE_PREFIX`、旧 `DEPLOY_TAG` 和仓库凭据,在匹配旧版本的发布目录运行:
首次部署建议在 Gitea Actions 页面手动运行工作流。需要在服务器排障运行时,须先从安全渠道将上表必填配置注入当前进程环境,再执行:
```bash
bash scripts/deploy-production.sh
```
新流程产生的发布目录只含部署文件,手动执行仍需注入完整环境。若旧 schema 不兼容,先停止应用、人工确认后从经过验证的备份恢复数据库,再使用原加密密钥和对应旧版本启动;恢复会丢失备份后的写入。不要仅为回退重跑旧的“同机构建”工作流。
固定 Compose 项目名为 `wq-alpha-production`,后端保持一个实例、一个 worker。脚本先验证 Compose,构建按 Git 提交标记的镜像,再检查密钥格式和数据库连通性;随后停止 web/backend、执行一次性迁移、启动服务并等待健康检查。Web 健康检查同时覆盖页面和经 Caddy 转发的数据库健康接口。迁移或启动失败时非零退出并显示容器状态,不继续标记成功。
以下命令在 B 执行,不依赖凭据环境变量:
首次部署完成后,检查公网 `/api/v1/health`,再通过真实域名登录并确认页面、写请求和 Cookie 正常。容器健康通过不能替代 HTTPS、DNS 和真实 Gitea Runner 验收。
## 4. 升级、备份和失败处理
升级包含停机窗口;提前结束或暂停长时间任务。每次发布前通过现有数据库管理工具备份专用库,并在独立安全位置备份加密密钥及必要配置,先在独立库验证恢复。此工作流不会自动备份或自动恢复数据库。
构建及预检失败时旧服务继续运行。停止服务后的迁移或健康检查失败需要人工处理;不要对可能已变更的 schema 直接自动降级。脚本在 Actions 日志输出切换前的镜像 ID/标签,成功后输出当前提交标识;保留旧镜像且不执行 prune。请保留部署日志作为回退参考,不再依赖宿主机版本记录文件。
如旧代码与当前 schema 兼容,可检出旧提交并指定其镜像标签启动;否则先停止应用,使用经过验证的备份恢复数据库,再用原加密密钥和对应旧版本启动。数据库恢复会丢失备份后的写入,必须人工确认后执行。本配置没有自动数据库降级,也不承诺无停机升级。
以下排查命令不依赖凭据环境变量(可能含业务信息的日志请勿公开):
```bash
docker ps -a --filter label=com.docker.compose.project=wq-alpha-production
@@ -143,9 +80,13 @@ docker logs --tail=100 wq-alpha-production-backend-1
docker logs --tail=100 wq-alpha-production-web-1
```
不要公开含业务信息的日志,普通维护不要使用 `down -v`。发布目录可在确认无需回退后人工清理,不影响 named volumes;数据库备份和恢复在数据库管理端进行。
普通维护不要使用 `down -v`。这里的数据库由外部管理,备份与恢复应在数据库管理端进行。
配置依据:[Gitea Container Registry](https://docs.gitea.com/usage/packages/container/)、[Docker 登录](https://docs.docker.com/reference/cli/docker/login/)、[Compose pull](https://docs.docker.com/reference/cli/docker/compose/pull/)、[Compose run](https://docs.docker.com/reference/cli/docker/compose/run/)、[Compose up](https://docs.docker.com/reference/cli/docker/compose/up/)。
## 配置依据
采用 compound 经验 1 中的生产/开发隔离、显式外部网络、必填凭据、固定项目名、独立迁移和部署健康检查。未照搬旧项目端口、业务 Job 或卷权限初始化:本应用后端不写 named volume,已有镜像使用 UID 10001。
凭据存放在 Gitea Secrets,通过步骤级环境变量交给 Compose,不落地到配置文件。Docker 容器仍需持有运行时配置,因此拥有 Runner 或 Docker 管理权限的人仍可能读取它们。若此前手动创建了旧 `.env.production`,新流程不再读取它;确认配置已迁移到 Gitea 并妥善备份密钥后可自行移除旧文件。相关官方资料:[Compose 外部网络](https://docs.docker.com/reference/compose-file/networks/)、[环境变量插值](https://docs.docker.com/compose/how-tos/environment-variables/variable-interpolation/)、[Gitea Runner 标签](https://gitea.com/gitea/runner/src/branch/main/README.md)。
## 5. 1Panel 夜间全量目录同步