接入Gitea项目检查门禁
Project CI / Repository checks (push) Successful in 3m56s
Project CI / Backend checks (push) Successful in 3m14s
Project CI / Native shell checks (push) Successful in 4m13s

新增 master 推送、PR 和手工触发的三路项目检查工作流
收紧 PR 基线、只读权限、action 固定版本与依赖锁漂移检查
补充 Gitea 版本、隔离 runner 和 required context 运维说明
This commit is contained in:
2026-07-21 17:15:49 +08:00
parent 6bed37542c
commit 7e58fd2195
3 changed files with 306 additions and 1 deletions
+280
View File
@@ -0,0 +1,280 @@
name: Project CI
on:
push:
branches:
- master
pull_request:
workflow_dispatch:
permissions:
contents: read
env:
CI: 'true'
CARGO_INCREMENTAL: '0'
CARGO_TERM_COLOR: always
RUSTC_WRAPPER: ''
CARGO_BUILD_RUSTC_WRAPPER: ''
jobs:
repository-checks:
name: Repository checks
runs-on: ubuntu-latest
steps:
- name: Checkout full history
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Install base tools
shell: bash
run: |
set -euo pipefail
command -v apt-get >/dev/null 2>&1 || {
echo 'ubuntu-latest runner must provide an Ubuntu or Debian environment.' >&2
exit 1
}
sudo_command=''
if command -v sudo >/dev/null 2>&1; then
sudo_command='sudo'
fi
${sudo_command} apt-get update
${sudo_command} env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
ca-certificates \
curl
- name: Set up Node.js 22
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'
- name: Resolve comparison base
shell: bash
run: |
set -euo pipefail
base_ref="$(node -e '
const fs = require("node:fs");
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
process.stdout.write(event.pull_request?.base?.sha ?? event.before ?? "");
')"
if [[ -n "${base_ref}" && ! "${base_ref}" =~ ^0+$ ]]; then
git cat-file -e "${base_ref}^{commit}" 2>/dev/null || {
echo "comparison base commit is unavailable: ${base_ref}" >&2
exit 1
}
else
base_ref="$(git merge-base HEAD origin/master 2>/dev/null || git rev-parse HEAD)"
fi
if [[ "${GITHUB_EVENT_NAME:-}" == 'pull_request' ]] \
&& ! git merge-base --is-ancestor "${base_ref}" HEAD; then
echo 'pull request head does not contain the latest base commit; update the branch and rerun CI.' >&2
exit 1
fi
echo "SPACETIME_SCHEMA_BASE_REF=${base_ref}" >> "${GITHUB_ENV}"
- name: Set up repository Rust toolchain
shell: bash
run: |
set -euo pipefail
if ! command -v rustup >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --profile minimal --default-toolchain none
fi
echo "${HOME}/.cargo/bin" >> "${GITHUB_PATH}"
export PATH="${HOME}/.cargo/bin:${PATH}"
toolchain="$(sed -n 's/^channel = "\([^"]*\)"/\1/p' rust-toolchain.toml)"
test -n "${toolchain}"
rustup toolchain install "${toolchain}" --profile minimal --component rustfmt
rustc --version
cargo --version
rustfmt --version
- name: Install npm dependencies
run: npm ci
- name: Run repository lint gates
run: npm run lint
- name: Run frontend and script tests
run: npm run test
- name: Build web applications
run: npm run build
- name: Validate content data
run: npm run check:content
- name: Check committed whitespace
shell: bash
run: |
set -euo pipefail
base_ref="${SPACETIME_SCHEMA_BASE_REF:-}"
test -n "${base_ref}"
git cat-file -e "${base_ref}^{commit}"
git diff --check "${base_ref}"...HEAD
backend-checks:
name: Backend checks
runs-on: ubuntu-latest
steps:
- name: Checkout full history
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Install backend build dependencies
shell: bash
run: |
set -euo pipefail
command -v apt-get >/dev/null 2>&1 || {
echo 'ubuntu-latest runner must provide an Ubuntu or Debian environment.' >&2
exit 1
}
sudo_command=''
if command -v sudo >/dev/null 2>&1; then
sudo_command='sudo'
fi
${sudo_command} apt-get update
${sudo_command} env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
clang \
cmake \
curl \
libclang-dev \
libcurl4-openssl-dev \
libssl-dev \
lld \
pkg-config
- name: Set up Node.js 22
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'
- name: Resolve comparison base
shell: bash
run: |
set -euo pipefail
base_ref="$(node -e '
const fs = require("node:fs");
const event = JSON.parse(fs.readFileSync(process.env.GITHUB_EVENT_PATH, "utf8"));
process.stdout.write(event.pull_request?.base?.sha ?? event.before ?? "");
')"
if [[ -n "${base_ref}" && ! "${base_ref}" =~ ^0+$ ]]; then
git cat-file -e "${base_ref}^{commit}" 2>/dev/null || {
echo "comparison base commit is unavailable: ${base_ref}" >&2
exit 1
}
else
base_ref="$(git merge-base HEAD origin/master 2>/dev/null || git rev-parse HEAD)"
fi
if [[ "${GITHUB_EVENT_NAME:-}" == 'pull_request' ]] \
&& ! git merge-base --is-ancestor "${base_ref}" HEAD; then
echo 'pull request head does not contain the latest base commit; update the branch and rerun CI.' >&2
exit 1
fi
echo "SPACETIME_SCHEMA_BASE_REF=${base_ref}" >> "${GITHUB_ENV}"
- name: Set up repository Rust toolchain
shell: bash
run: |
set -euo pipefail
if ! command -v rustup >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --profile minimal --default-toolchain none
fi
echo "${HOME}/.cargo/bin" >> "${GITHUB_PATH}"
export PATH="${HOME}/.cargo/bin:${PATH}"
toolchain="$(sed -n 's/^channel = "\([^"]*\)"/\1/p' rust-toolchain.toml)"
test -n "${toolchain}"
rustup toolchain install "${toolchain}" --profile minimal --component rustfmt
rustc --version
cargo --version
rustfmt --version
- name: Install npm dependencies
run: npm ci
- name: Check server-rs boundaries
run: npm run check:server-rs-ddd
- name: Check api-server targets
run: cargo check --locked -p api-server --all-targets --manifest-path server-rs/Cargo.toml
- name: Check SpacetimeDB module
run: cargo check --locked -p spacetime-module --manifest-path server-rs/Cargo.toml
native-shell-checks:
name: Native shell checks
runs-on: ubuntu-latest
steps:
- name: Checkout full history
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
with:
fetch-depth: 0
persist-credentials: false
- name: Install native shell build dependencies
shell: bash
run: |
set -euo pipefail
command -v apt-get >/dev/null 2>&1 || {
echo 'ubuntu-latest runner must provide an Ubuntu or Debian environment.' >&2
exit 1
}
sudo_command=''
if command -v sudo >/dev/null 2>&1; then
sudo_command='sudo'
fi
${sudo_command} apt-get update
${sudo_command} env DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
clang \
cmake \
curl \
file \
libayatana-appindicator3-dev \
libssl-dev \
libwebkit2gtk-4.1-dev \
libxdo-dev \
librsvg2-dev \
lld \
patchelf \
pkg-config \
wget
- name: Set up Node.js 22
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
with:
node-version: '22'
- name: Set up repository Rust toolchain
shell: bash
run: |
set -euo pipefail
if ! command -v rustup >/dev/null 2>&1; then
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs \
| sh -s -- -y --profile minimal --default-toolchain none
fi
echo "${HOME}/.cargo/bin" >> "${GITHUB_PATH}"
export PATH="${HOME}/.cargo/bin:${PATH}"
toolchain="$(sed -n 's/^channel = "\([^"]*\)"/\1/p' rust-toolchain.toml)"
test -n "${toolchain}"
rustup toolchain install "${toolchain}" --profile minimal --component rustfmt
rustc --version
cargo --version
rustfmt --version
- name: Install npm dependencies
run: npm ci
- name: Run native shell gates
run: npm run check:native-shells
- name: Ensure native lockfile is unchanged
run: git diff --exit-code -- apps/desktop-shell/src-tauri/Cargo.lock
@@ -262,6 +262,15 @@ DDD 边界检查:
npm run check:server-rs-ddd
```
## Gitea CI 与 PR 检查
- 仓库 CI 入口是 `.gitea/workflows/project-ci.yml`,向 `master` 推送和所有 PR 创建、更新时必须运行,也允许手工触发。
- CI 固定拆分为 `Repository checks``Backend checks``Native shell checks` 三个 required job;对应 PR context 完整名称是 `Project CI / Repository checks (pull_request)``Project CI / Backend checks (pull_request)``Project CI / Native shell checks (pull_request)`,首次运行后仍须从 Gitea 最近一周 context 表复核。前两项覆盖仓库/Web 与后端边界/编译,原生壳验收单独运行以便定位重型构建失败。
- 三个 job 共同覆盖 `npm run check`,并追加 `npm run check:server-rs-ddd``cargo check -p api-server --all-targets --manifest-path server-rs/Cargo.toml``cargo check -p spacetime-module --manifest-path server-rs/Cargo.toml`
- checkout 必须使用完整历史。PR 将 base SHA 写入 `SPACETIME_SCHEMA_BASE_REF`,直接推送 `master` 使用 before SHA;事件基线不可解析时直接失败。Gitea 检查的是 PR head 而非预合并 commitworkflow 必须拒绝不包含最新 base commit 的过期 PR,分支保护同时保持“PR 过期禁止合并”。
- 普通 PR job 不读取业务 secret,不运行真实 API/SpacetimeDB/OSS/支付/生成/live smoke,也不执行会修改外部状态的维护、迁移、发布或备份命令。
- Gitea 至少升级到 `1.26.4` 后才能注册执行 PR job 的 runner`ubuntu-latest` 标签只映射到固定 digest 的 Ubuntu 24.04 级 Docker/临时隔离镜像,不使用浮动镜像 tag,不映射 host,不向 job 暴露 Docker socket、业务 secret 或不必要内网。runner 能访问 Gitea、GitHub Actions 与 `actions/node-versions`、nodejs.org、npm、Rust 分发和 crates.ioworkflow 的官方 action 固定完整 commit,若内网禁用 GitHub,先在当前 Gitea 镜像对应 commit 并改用绝对 URL。受控镜像优先预装 rustup。Gitea 1.26 的任务超时由 runner 全局配置控制;首次运行成功后,`master` 分支保护必须要求上述三个 job 全部成功。
## 后端相关默认验证
后端修改后,按 DDD 文档中的验收命令执行。涉及 API smoke 时:
@@ -1,6 +1,6 @@
# 本地开发验证与生产运维
更新时间:`2026-07-17`
更新时间:`2026-07-21`
## 标准开发流程
@@ -198,6 +198,22 @@ npm run check
`npm run build``scripts/build-gate.mjs` 串行构建主站和后台;该门禁会把 Vite warning 当成失败处理。若看到 `Build gate failed because warnings were emitted`,先看 warning 原文,例如 chunk 体积超过 `vite.config.ts` / `apps/admin-web/vite.config.ts``chunkSizeWarningLimit`,不要先按 Rust 编译失败排查。
### Gitea Actions PR 门禁
仓库级 Gitea Actions 工作流固定为 `.gitea/workflows/project-ci.yml`,在向 `master` 推送、创建或更新 PR,以及手工触发时运行。工作流拆成三个必须通过的 job:
- `Repository checks`:执行 `npm run lint`、完整 Vitest、主站与后台生产构建、内容数据检查和提交差异空白检查。
- `Backend checks`:执行 `npm run check:server-rs-ddd``api-server --all-targets` 编译和 `spacetime-module` 编译。
- `Native shell checks`:独立执行 `npm run check:native-shells`,覆盖微信壳、Expo 和 Tauri 的完整验收,并确认 Tauri `Cargo.lock` 没有被构建过程改写,避免把重型原生壳或依赖锁漂移隐藏在基础检查末尾。
三个 job 合起来覆盖根 `npm run check`,并补齐根检查没有包含的 server-rs DDD 与现役后端编译门禁。普通 PR CI 不注入业务密钥,不启动真实 API、SpacetimeDB、OSS、支付、图片生成或生产 live smoke;需要现场环境、可变外部状态、Docker 编排或发布凭据的 `check:*` 继续按对应专题和 Jenkins 发布流程执行,不能遍历所有同名前缀脚本冒充 PR 门禁。
PR checkout 必须保留完整 Git 历史,并把 PR base SHA 传给 `SPACETIME_SCHEMA_BASE_REF``check:spacetime-schema` 依赖该基线识别已有表字段删除、改名、重排和改类型;事件给出的基线缺失或本地不可解析时必须直接失败,不能退化为空差异检查。Gitea 的 PR checkout 是 PR head,不是与目标分支的预合并 commit,因此 workflow 还会验证 PR head 包含事件中的最新 base commit;分支保护必须继续开启“PR 过期禁止合并”,过期分支先更新再重跑。向 `master` 直接推送时使用 push before SHA,手工触发时回退到 `origin/master`
启用或注册执行 PR job 的 runner 前,Gitea 服务端必须至少升级到 `1.26.4`;不得在 `1.26.2` 上执行不受信任 PR 代码。runner 必须提供 `ubuntu-latest` 标签,并将其映射到经验证且固定 digest 的 Ubuntu 24.04 级 Docker/临时隔离镜像;禁止使用浮动镜像 tag,禁止将该标签映射到 host 执行器,禁止向 job 暴露 Docker socket、业务环境变量、业务密钥或不必要的内网。workflow 会安装 Node 22、仓库 `rust-toolchain.toml` 固定的 Rust 1.96.0,以及 clang/lld 和 Tauri Linux 依赖;受控 runner 镜像应预装 rustupfallback 下载只用于首次引导。runner 仍需能访问 Gitea、GitHub Actions 与 `actions/node-versions`、nodejs.org、npm registry、Rust 分发和 crates.io。workflow 中的 `actions/checkout` / `actions/setup-node` 固定到完整 commit;内网 runner 不允许访问 GitHub 时,先把对应 commit 镜像到当前 Gitea 并把 workflow 改为绝对 action URL。首版不使用 Actions cache,避免未配置 runner cache 网络时把缓存恢复错误变成 PR 失败。Gitea 1.26 不执行 workflow 的 `timeout-minutes`,任务最长运行时间在 runner 全局配置收口,不能只在 YAML 写一个不会生效的超时值。
workflow 首次成功运行后,在 Gitea `master` 分支保护中把 `Project CI / Repository checks (pull_request)``Project CI / Backend checks (pull_request)``Project CI / Native shell checks (pull_request)` 三个完整 context 都设为合并必需检查,并从最近一周已上报 context 表复核名称后再保存。不能只填裸 job 名,否则无法匹配 Gitea 实际上报的 `<workflow> / <job> (<event>)`。只提交 workflow 文件不会自动创建 runner,也不会自动修改分支保护;如果 Actions 长时间停留在等待状态,先到仓库或组织的 Actions runner 页面确认存在在线、带 `ubuntu-latest` 标签的 runner。
视觉小说负向扫描与验收门禁:
```bash