合并 master 的预览部署控制面
Project CI / Repository checks (pull_request) Successful in 55s
Project CI / Frontend tests (pull_request) Successful in 2m56s
Project CI / Backend tests (pull_request) Successful in 3m35s
Project CI / Native shell tests (pull_request) Successful in 17m44s

保留 Spine 序列帧与 Jenkins 容器预览决策记录

合入 preview-deployer-web、preview-deployer-server 及部署脚本
This commit is contained in:
2026-08-15 17:48:16 +08:00
39 changed files with 5433 additions and 2 deletions
@@ -0,0 +1,106 @@
# Jenkins 容器预览部署控制面技术方案
日期:`2026-08-15`
## 目标
为内网同事提供独立 SPA,用于填写源码分支和可选 commit,触发 Jenkins 构建并发布完整 Docker Compose 预览栈。页面持续刷新排队、构建、发布和卸载状态,成功后展示容器健康状态与 Web 内网地址。
控制面不替代 Jenkins:Jenkins 继续负责源码检出、全量镜像构建、SpacetimeDB 模块发布、容器启动、健康验证、卸载和构建日志。浏览器不得持有 Jenkins 用户名、API Token、Crumb 或 Docker 权限。
## 架构
```text
同事浏览器
-> preview-deployer SPA
-> preview-deployer-server 同源 API
-> 固定 Jenkins Job shared/Genarrative-Preview-Deployer
-> 独立 Compose project + 端口租约 + preview-result.json
```
- SPA 只通过内网 `http://192.168.35.82/build/` 提供页面,API 使用同源 `/api/preview-deployer/`;不配置公网域名,不复用后台管理员 Token 或业务 API 登录态。
- `preview-deployer-server` 只代理固定 Jenkins Job,Jenkins 凭据只从服务端环境变量读取,不进入前端 bundle、JSON 响应或日志。
- Jenkins Job 使用仓库现有 `deploy/container/` 全量容器资产,并通过独立 Compose project 支持多个分支实例并存;为保护端口和共享 Docker 状态,构建动作在首版中串行排队。
- 页面只读取控制面归一化后的状态,不解析 Jenkins console,也不直连 Jenkins API。
## 部署身份与端口
`deploymentId` 由规范化分支名与分支名摘要确定,同一分支稳定得到同一 ID;commit 不进入 ID,因此同一分支重新构建或指定不同 commit 时复用同一预览实例和 Web 端口。
Web 端口池固定为 `8400..8499`
1. Jenkins 在全局文件锁内读取受控状态目录。
2. 已登记分支复用原端口;新分支选择状态表、宿主监听和 Docker 映射均未占用的端口。
3. Jenkins 为实例使用独立 Compose project,例如 `genarrative-preview-a81f39c2d43e76ab`
4. 端口租约必须在容器完成真实绑定前持续受锁保护;失败构建不得把端口分配给第二个实例。
5. 卸载成功后删除该实例的 Compose 栈、受控状态和端口租约。
同一分支重新构建时先完成新镜像构建,再停止旧 Compose 栈并启动新版本;源码或镜像构建失败不会提前删除原有健康实例。容器切换后的失败仍会在页面明确显示,不伪装成旧版本继续运行。
页面每次刷新运行中实例时,控制服务直接探测受控 Web URL;后续容器异常会将健康状态更新为 `unhealthy`,不会永久沿用 Jenkins 部署完成时的快照。
目标分支只提供应用构建上下文。Compose 编排文件、Dockerfile 入口和 Jenkins 执行脚本固定取自受保护的 `master` 控制器 checkout,避免普通分支替换编排文件挂载宿主路径或启用特权容器。该 Job 仍只应开放给受信任开发者,并建议在隔离构建节点运行。
SpacetimeDB 与 OTLP 不映射宿主端口;Jenkins 通过受控 Compose 网络中的 SpacetimeDB 容器地址完成模块发布,运行服务之间继续使用 Compose DNS。页面只展示 Web 内网地址 `http://<预览宿主>:<webPort>`
## Jenkins 参数与产物
固定 Job`shared/Genarrative-Preview-Deployer`
参数:
- `ACTION``DEPLOY``UNINSTALL``STATUS`
- `SOURCE_BRANCH`:部署时必填。
- `COMMIT_HASH`:部署时可选,7 到 40 位十六进制。
- `DEPLOYMENT_ID`:卸载和状态查询时必填。
分支名只允许数字、字母、点、下划线、短横线和斜杠,不允许首尾斜杠或连续点号。Jenkins 必须复用 `scripts/jenkins-checkout-source.sh`,验证 commit 真实属于目标远端分支;SPA 和代理服务的输入校验不能代替流水线校验。
每次运行归档 `preview-result.json`,至少包含:
```json
{
"schemaVersion": 1,
"deploymentId": "feature-login-a81f39c2",
"branch": "feature/login",
"requestedCommit": "",
"resolvedCommit": "0123456789abcdef",
"status": "running",
"health": "healthy",
"webPort": 8403,
"webUrl": "http://192.168.35.82:8403",
"composeProject": "genarrative-preview-feature-login-a81f39c2",
"updatedAt": "2026-08-15T08:00:00.000Z"
}
```
控制面只读取固定 artifact 路径和受限字段;不得把完整 console 内容返回浏览器。
## 控制面 API
- `POST /api/preview-deployer/session`:使用控制面访问口令建立 `HttpOnly + SameSite=Strict` 会话。
- `GET /api/preview-deployer/session`:查询当前会话状态。
- `DELETE /api/preview-deployer/session`:退出。
- `GET /api/preview-deployer/deployments`:列出由控制面触发和恢复的部署。
- `POST /api/preview-deployer/deployments`:提交 `{ branch, commitHash? }`
- `GET /api/preview-deployer/deployments/{id}`:刷新队列、构建与 artifact 状态。
- `POST /api/preview-deployer/deployments/{id}/uninstall`:触发固定 Job 的卸载动作。
页面状态统一为 `queued / building / deploying / running / uninstalling / stopped / failed / cancelled`,健康状态统一为 `pending / healthy / unhealthy / unknown`
## 安全边界
- 服务端缺少控制面访问口令或 Jenkins service account 凭据时必须拒绝启动,不允许退化成匿名写接口。
- Jenkins service account 只授予 `shared/Genarrative-Preview-Deployer``Job/Read``Job/Build` 和读取构建产物所需权限,不授 `Overall/Administer``Job/Configure``Job/Delete`
- 后端固定 Jenkins origin、Job 路径和参数白名单;客户端不能传 URL、Job 名、Compose project、容器名、宿主端口或 Jenkins 凭据。
- Jenkins POST 支持动态 CrumbAPI Token 即使免 Crumb,也不能把 Token 放进 URL 或日志。
- API 默认只接受同源请求,写请求校验 Origin;内网本身不作为认证。
- 同一 deployment 的发布和卸载串行执行;重复请求必须幂等或明确返回冲突。
- 卸载只接受受控状态中存在且 ID 完全匹配的实例,并进行二次确认;禁止执行任意 Docker、Git、shell 或 Compose project 参数。
## 验收
- 后端:输入校验、登录会话、Origin、Crumb、Jenkins `401/403/404/5xx`、queue 到 build 状态机、artifact schema、卸载所有权和幂等测试。
- 前端:登录、分支与可选 commit、自动刷新、排队/构建/成功/失败状态、内网链接、卸载确认和刷新恢复测试。
- Jenkins:两个分支依次发布后在不同端口并存;同一分支换 commit 优先复用端口;非分支 commit 被拒绝;卸载只删除目标实例并释放端口。
- 通用:`npm run check:encoding`、相关 typecheck/build/test、Rust 定向测试和 `git diff --check`