Maintained Java CI toolchain image for Forgejo runners
  • Shell 98.7%
  • Dockerfile 1.3%
查找文件
ZH SXIN f497b0fce9
一些检测失败了
reusable-frontend-publish.yml / Merge pull request '增加前端静态站点三阶段发布工作流' (#83) from codex/frontend-static-publication into main (push) Failing after 0s
中央前端发布脚本校验 / validate (push) Successful in 10s
Merge pull request '增加前端静态站点三阶段发布工作流' (#83) from codex/frontend-static-publication into main
Reviewed-on: #83
Reviewed-by: ZH SXIN <zhsxin@qq.com>
2026-10-10 04:15:36 +00:00
.forgejo/workflows feat: add static frontend publication pipeline 2026-10-09 20:33:27 +08:00
ci fix(ci): avoid non-Azul Maven Java dependency 2026-10-08 15:45:22 +08:00
docs docs: clarify static frontend publication contract 2026-10-09 20:42:28 +08:00
scripts docs: clarify static frontend publication contract 2026-10-09 20:42:28 +08:00
.dockerignore Add maintained Forgejo Java CI tools image 2026-07-25 20:04:28 +08:00
Dockerfile fix(ci): avoid non-Azul Maven Java dependency 2026-10-08 15:45:22 +08:00
README.md feat: add static frontend publication pipeline 2026-10-09 20:33:27 +08:00

ci-tools

Forgejo Runner 的统一构建、依赖发布、服务制品发布和服务部署实现。业务仓库只保留 触发条件与中央工作流调用,规则和脚本集中由 java/ci-tools 维护。

前端项目使用中央 Yarn/pnpm/npm 安装与构建校验,并可通过独立入口发布静态站点 ZIP、 frontend-static Manifest 和 OCI 镜像;Python 项目默认执行离线安全 的源码编译检查和已有单元测试。两类校验均不包含制品发布或环境部署权限。

中央工作流

  • reusable-ci.yml:依赖库与 Java 服务校验。
  • reusable-frontend-ci.yml:Yarn、pnpm 或 npm 前端项目校验。
  • reusable-frontend-publish.yml:校验并发布静态站点,不执行部署或修改业务版本。
  • reusable-python-ci.yml:Python 源码与已有单元测试校验。
  • reusable-package-publish.yml:Maven/npm 依赖制品统一发布。
  • reusable-generic-service-release.yml:服务 ZIP 与 OCI 镜像统一发布。
  • reusable-generic-service-deploy.yml:按制品版本向一个或多个服务器部署。
  • reusable-initialize-work-branch.yml:新建工作分支后初始化独立 Snapshot 版本。
  • reusable-create-release-tag.yml:主分支 POM 变为正式版本后自动创建 .release Tag。

本次调整直接替换现有接口,不保留第二套版本或兼容入口。

reusable-frontend-ci.yml 始终自动递归检出 Git 子模块,业务仓库不再通过代码选择是否 检出。中央入口优先使用显式映射的 FORGEJO_BOT_TOKEN,也支持调用方 secrets: inherit 直接提供的组织 Secret CI_BOT_TOKEN;两者都不存在时使用当前任务令牌,此时只能读取 当前仓库和公开子模块。私有子模块必须由机器人账号具备只读权限。

发布源码与通道

依赖和服务的手动发布使用相同的三个源码选择器,且每次必须且只能填写一个:

选择器 来源 发布类型
release_tag X.Y.Z[.N].release 指向的提交 Release
source_ref 完整 40 位提交 SHA Snapshot
source_branch feature/、bugfix/、hotfix/ 工作分支在检出时的最新提交 Snapshot

发布类型由中央工作流推导,调用方不再传通道。分支构建会在 Manifest 中同时记录 分支名和实际检出的完整提交 SHA。

依赖仓库地址和服务镜像地址直接读取 Forgejo Variables:

  • PACKAGE_SNAPSHOT_REPOSITORY_URL
  • PACKAGE_RELEASE_REPOSITORY_URL
  • IMAGE_REPOSITORY

IMAGE_REPOSITORY 是业务仓库级 Variable,格式必须为 gitea.sankcrm.cn/<owner>/<源仓库名>,例如 gitea.sankcrm.cn/java/oauth-centre。 Registry 地址和镜像名不能改写;owner 可以与源代码组织不同,但目标组织必须通过 Forgejo API 校验为 public,并且 PACKAGE_REGISTRY_USERNAME 对该组织的权限响应必须 包含 can_write=true。公开组织只影响镜像的可读性,推送账号仍然需要目标组织的写权限。 中央脚本在构建前执行这些只读校验,失败时不会构建或推送镜像。

正式版本在构建前检查远端是否已存在;Snapshot 可以重复发布。Maven 工作分支 版本为 <base>-<ISSUE>-SNAPSHOT,npm 为 <base>-<issue>.snapshot。服务 Snapshot 最终制品版本还会附加源码 SHA、工作流运行号和重试号。

服务仓库和依赖库只保留一个 publish-snapshot.yml。推送到 develop 时自动 发布;手动运行时使用 Forgejo 自带的分支选择器,允许选择 develop 或 feature|bugfix|hotfix/<PROJECT>-<ISSUE_NUMBER>[-description]。PROJECT 非空且不含 /、- 或控制字符;除此之外不限制大小写、字母数字组成或前导零。Issue 编号必须是无 前导零的正整数。 例如 feature/0-40-work-item、bugfix/030-5-fix 和 hotfix/app_name-7-fix 均符合 分支结构。npm 保留既有小写工作项版本标记,生成结果仍须满足 npm 自身的 SemVer 规则。 服务发布同时生成 ZIP 和 OCI 镜像,项目版本还必须符合 Docker Tag 原生格式 [A-Za-z0-9_][A-Za-z0-9_.-]{0,127};不符合时会在构建前拒绝,这属于制品生态限制, 不改变分支项目标记规则。 薄工作流统一传入 automatic_snapshot: true 和 ${{ forgejo.ref_name }},中央 CI 校验分支后锁定 该次事件的完整 SHA,因此构建期间分支继续推进也不会改变源码。

所有入口都只允许测试版本。develop 可使用 Maven <base>-SNAPSHOT、npm <base>-snapshot,也可保留工作分支合入后的 Issue Snapshot;工作分支必须使用 <base>-<ISSUE>-SNAPSHOT(npm <base>-<issue>.snapshot),且版本中的 Issue 必须与分支一致。原 publish-snapshot-manual.yml 已删除,不保留重复入口。

工作分支版本初始化

开发工作分支指每个具体需求或缺陷使用的分支:

feature/OAUTH-123-login
bugfix/OAUTH-456-token
hotfix/OAUTH-789-expiry

Forgejo create 事件触发业务仓库的薄工作流,并调用中央初始化入口。中央脚本 确认分支刚创建且尚未被推进后,在新分支修改版本、提交并执行普通 fast-forward push;不会 force-push。一个活动 Issue 不允许同时存在多个上述类型的工作分支。

Java 服务工作分支通常从 develop 创建;develop 可能携带正式基础版本、 <base>-SNAPSHOT 或上一 Issue 的 <base>-<ISSUE>-SNAPSHOT,中央脚本统一 提取数字基础版本后设置为 <base>-<当前ISSUE>-SNAPSHOT。Maven 依赖和 npm 项目仍必须从构建文件中的正式基础版本创建。版本只在 Effective POM 或 package.json 中定义,交付模型不再重复保存版本。

主分支校验成功后可调用中央自动 Tag 工作流。只有 Effective POM 版本相对上一个 main 提交发生变化并变为 X.Y.Z[.N] 时,中央机器人才能创建对应的 X.Y.Z[.N].release Tag;Tag 随后触发现有正式发布流程。

多目标版本部署

部署工作流只接收已经发布的 Maven 声明 artifact_version 和目标 ID 数组,不区分测试、 生产等环境。一个版本可选择多个目标,每个目标分别配置 Runner、服务器、账号、 部署根目录、部署方式和凭据名称。

目标定义来自 Forgejo Variable DEPLOY_TARGETS_JSON,例如:

[
  {
    "id": "server-a",
    "runner": "deploy-a",
    "host": "server-a.internal",
    "user": "deployer_a",
    "deploy_root": "/opt/apps/example",
    "deployment_type": "zip",
    "deploy_command": "/opt/deploy/sankcrm/bin/deploy-generic-service",
    "health_url": "https://server-a.internal/actuator/health",
    "password_secret": "SERVER_A_PASSWORD",
    "known_hosts_secret": "SERVER_A_KNOWN_HOSTS"
  }
]

部署只解析 Generic Package 中已经发布并带摘要的 Manifest,不检出源码、不执行 Maven,也不重新构建制品。每个目标独立产生部署证据,单个目标串行执行,不同目标 可并行。

ZIP 服务器目录

DEPLOY_ROOT/
├── runtime.env
├── releases/
│   ├── <application>-run-<artifact_version>-1.war
│   └── <application>-run-<artifact_version>-2.war
├── <application>-run.war -> releases/<application>-run-<artifact_version>-2.war
├── startup
├── conf/
│   ├── logback-spring.xml
│   ├── app/
│   └── i18n/
├── .state/
└── logs/
    └── gc/

ZIP 制品本身只包含 <application>-run.war。startup 仅在 DEPLOY_ROOT 中不存在时 由中央 CI 安装统一模板;若已存在由 root 或部署账号所有、可执行且不被 group/other 写入的普通 startup 文件,则原样保留并拒绝覆盖。中央部署器使用 SSH 登录账号自动创建 DEPLOY_ROOT、releases/、.state/、logs/gc/、conf/app/ 和 conf/i18n/; runtime.env、配置文件、.state/ 和 logs/ 跨版本保留。首次运行新版部署器时, 已有的 state/ 会在安全校验后原子迁移为 .state/;两个目录同时存在时拒绝部署。

每次 ZIP 部署都会先在 .state/next-index 原子预占新索引,再安装为 <application>-run-<artifact_version>-<index>.war。相同版本可以重复部署; 索引全局递增,部署失败的索引也不复用。活动软链接原子切换,启动或健康检查失败 时恢复上一软链接和上一进程。 首次接管已有的普通 <application>-run.war 时,会先将旧 WAR 归档到 releases/ 并转换为符号链接,因此新版本健康检查失败时仍可回滚到旧版本。

ZIP 部署不使用 deploy.env。

Docker 服务器目录

Docker 目标使用同一个 DEPLOY_ROOT 约定,并额外要求:

DEPLOY_ROOT/
├── deploy.env
├── runtime.env
├── releases/
├── .state/
└── logs/

deploy.env 只保存 Docker 运行配置,包括 CONTAINER_NAME、 PORT_MAPPINGS_JSON、VOLUME_MOUNTS_JSON,以及可选的 DOCKER_NETWORK、RESTART_POLICY 和健康检查重试参数。runtime.env 通过 Docker --env-file 注入容器。远端部署器拒绝 host 网络、Docker Socket 挂载和 非摘要镜像引用。

镜像仓库路径不在服务器重复配置。中央部署客户端从不可变 IMAGE_REFERENCE(例如 gitea.sankcrm.cn/java/oauth-centre@sha256:...)自动提取 java/oauth-centre,远端再次校验 Registry、Digest、镜像路径与源仓库名;同组织和 跨组织部署均不需要 EXPECTED_REPOSITORY。 业务仓库的 PACKAGE_USERNAME/PACKAGE_TOKEN 由中央工作流分别映射为脚本运行时的 PACKAGE_REGISTRY_USERNAME/PACKAGE_REGISTRY_PASSWORD(Maven/Generic)或 PACKAGE_REGISTRY_USERNAME/PACKAGE_REGISTRY_TOKEN(npm);跨组织发布的 Token 还必须能查询目标组织权限并确认 can_write=true。

目标服务器前置条件

固定中央入口为:

/opt/deploy/sankcrm/bin/deploy-generic-service

部署账号直接同步并执行中央脚本,不使用 sudo。运维只需保证中央入口和 DEPLOY_ROOT 的最近已存在父目录由部署账号所有且可写;中央部署器会逐级创建缺失 目录,并校验它们不是符号链接、由部署账号所有、可写且不能被 group/other 写入。 Java 或 Docker、curl、jq、unzip 等运行依赖仍由运维安装。runtime.env、 logback-spring.xml、application-*.yaml、deploy.env 等配置文件不会自动生成 或覆盖,应由 root 或部署账号维护,且不能被 group/other 写入;startup 一经存在 同样由服务器维护,中央部署器不再按摘要更新或覆盖。

完整调用示例、变量、Secret 和服务器配置见 docs/中央CI工作流与脚本使用说明.md; 设计决策、实施范围与验收标准见 docs/中央CI统一发布与部署调整方案.md。

工具镜像

  • gitea.sankcrm.cn/java/ci-tools:2026.09.2:中央作业固定版本。
  • gitea.sankcrm.cn/java/ci-tools:runner-stable:Runner 永久使用的稳定入口; 只有固定版本镜像完成全部校验后才会提升该标签。
  • gitea.sankcrm.cn/java/ci-tools:latest:仅用于人工验证。

业务工作流只声明 ci、build-image、publish-package、deploy-test 或 deploy-production 等稳定 Runner 标签,不声明任务镜像。Runner 配置永久引用 runner-stable 并启用强制拉取,因此中央工具镜像升级不需要修改业务工作流或 重新注册 Runner。每个不可变版本标签仍保留,用于审计和快速回滚稳定入口。

镜像包含 Azul Zulu JDK 11、17、21、Maven、Bash、Git、curl、jq、Node.js/npm、 Yarn Classic 1.22.22、PNPM 12.6.0、 OpenSSH、zip/unzip 与摘要工具。Maven 依赖库和 Java 服务根据包含 Parent 的 Effective POM 自动选择 JDK。当前 Runner 在可复用工作流中不执行任务镜像覆盖,因此中央脚本 只接受预装的同版本 Azul JDK。三套 JDK 都来自 Forgejo 内部 java/azul-alpine:11-jdk、17-jdk、21-jdk;缺失或厂商不符时立即失败, 运行中的任务不再安装或下载另一套 JDK。中央流程不使用高版本 JDK 加 --release 的回退方式,因为该参数不能兼容 访问 javac 内部 API 的 Lombok、QueryDSL 等注解处理器。业务仓库不配置 Java 版本或任务镜像。

系统工具与三套内部 JDK 镜像使用独立稳定缓存层,Yarn/PNPM 及最终镜像版本位于其后; 升级前端工具或镜像元数据不会重新从公共仓库下载 JDK。Docker Engine 构建与推送日志 实时显示当前 Layer、目标镜像、耗时和最终状态,并在失败时分别报告传输、日志留存和 JSON 解析状态,便于直接从 Actions 日志定位最后完成的步骤。

镜像把 ci/maven-settings.xml 安装为 Maven 全局配置。业务 POM 仍先解析 https://gitea.sankcrm.cn/api/packages/java/maven;内部制品缺失时,仅仓库 ID jkpPublic 可以回退到固定地址 http://maven.sankcrm.cn/repository/maven-public。其他外部 HTTP 仓库继续被 Maven 阻断,业务仓库无需复制 settings.xml 或传入额外参数。

Java 服务校验与发布统一显式设置 maven.javadoc.skip=true。服务交付仍完整执行 编译、测试、WAR、ZIP 和 OCI 镜像构建,但不会因为仅影响文档附件的 Javadoc 错误阻断运行制品;Maven 依赖库不采用该规则,仍按库发布流程处理源码和文档附件。

服务 Dockerfile 的全部基础镜像必须来自 gitea.sankcrm.cn。历史 Tag 仍引用 Docker Hub 时,中央流程会按该 Tag 的 Effective POM Java 版本使用对应内网 JRE 模板,不移动历史 Tag,也不让生产 DIND 访问外部镜像仓库。

本地验证:

docker build --build-arg IMAGE_VERSION=2026.09.2 \
  -t gitea.sankcrm.cn/java/ci-tools:2026.09.2 .
docker run --rm gitea.sankcrm.cn/java/ci-tools:2026.09.2 verify-ci-toolchain