mirror of
https://github.com/zyycn/codex-proxy-rs.git
synced 2026-11-06 16:06:08 +00:00
Follow-up to 720eb0c2: the manifest was self-attested — editing a released
migration and regenerating its sha256 line in the same PR passed the
checksum and membership checks, and the next release then bricked every
deployment on the startup checksum mismatch the guard exists to prevent.
On pull requests, fetch the base commit and require every line in the base
manifest to survive verbatim, so a changed hash (edit) or a dropped line
(delete) fails CI; genuinely new migrations only add lines and still pass.
Co-authored-by: Claude <noreply@anthropic.com>
206 lines
7.4 KiB
YAML
206 lines
7.4 KiB
YAML
name: Reusable Quality
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
ref:
|
|
description: Exact commit SHA to validate
|
|
required: true
|
|
type: string
|
|
upload_frontend:
|
|
description: Upload the built frontend for release jobs
|
|
required: false
|
|
default: false
|
|
type: boolean
|
|
run_backend:
|
|
description: Run backend quality checks
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
run_frontend:
|
|
description: Run frontend quality checks
|
|
required: false
|
|
default: true
|
|
type: boolean
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
backend:
|
|
if: inputs.run_backend
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
env:
|
|
CARGO_TERM_COLOR: always
|
|
CPR_TEST_DATABASE_URL: postgres://codex_proxy:codex_proxy@127.0.0.1:5432/codex_proxy
|
|
CPR_TEST_REDIS_URL: redis://127.0.0.1:6379
|
|
services:
|
|
postgres:
|
|
image: postgres:18-bookworm@sha256:d9c83446333daec3f0588cc709adb80c26090b7f9f0f7ec8d43c243385d79818
|
|
env:
|
|
POSTGRES_DB: codex_proxy
|
|
POSTGRES_USER: codex_proxy
|
|
POSTGRES_PASSWORD: codex_proxy
|
|
POSTGRES_INITDB_ARGS: --auth-local=scram-sha-256 --auth-host=scram-sha-256
|
|
ports:
|
|
- 5432:5432
|
|
options: >-
|
|
--health-cmd "pg_isready -U codex_proxy -d codex_proxy"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 10
|
|
redis:
|
|
image: redis:8-alpine@sha256:9d317178eceac8454a2284a9e6df2466b93c745529947f0cd42a0fa9609d7005
|
|
ports:
|
|
- 6379:6379
|
|
options: >-
|
|
--health-cmd "redis-cli ping"
|
|
--health-interval 5s
|
|
--health-timeout 5s
|
|
--health-retries 10
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
# PR 场景需要 base 提交做冻结清单的 append-only 比对。
|
|
fetch-depth: ${{ github.event_name == 'pull_request' && 0 || 1 }}
|
|
|
|
# 已应用迁移的字节被 sqlx 启动校验钉死:改动已发布迁移会让所有
|
|
# 存量部署重启即失败。冻结清单保证旧文件不被改、新文件必须入册。
|
|
- name: Migration freeze check
|
|
working-directory: backend/migrations
|
|
shell: bash
|
|
env:
|
|
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
run: |
|
|
set -euo pipefail
|
|
sha256sum --check --strict .frozen-sha256
|
|
for file in *.sql; do
|
|
if ! awk '{print $2}' .frozen-sha256 | grep -Fxq "$file"; then
|
|
echo "::error file=backend/migrations/$file::migration $file is not frozen; append its sha256 to backend/migrations/.frozen-sha256"
|
|
exit 1
|
|
fi
|
|
done
|
|
# 清单自证不足以防「改迁移 + 同 PR 刷新清单行」:校验对新哈希通过。
|
|
# 对 PR,断言 .frozen-sha256 相对 base 只增不改——base 里的每一行
|
|
# 必须原样保留(改哈希=删行,删迁移=删行,都会被挡)。
|
|
if [ -n "${BASE_SHA:-}" ]; then
|
|
base_manifest="$(git show "${BASE_SHA}:backend/migrations/.frozen-sha256" 2>/dev/null || true)"
|
|
if [ -n "$base_manifest" ]; then
|
|
while IFS= read -r line; do
|
|
[ -z "$line" ] && continue
|
|
if ! grep -Fxq "$line" .frozen-sha256; then
|
|
file="$(printf '%s' "$line" | awk '{print $2}')"
|
|
echo "::error file=backend/migrations/.frozen-sha256::frozen migration $file was modified or removed; released migrations are append-only — add a new numbered migration instead"
|
|
exit 1
|
|
fi
|
|
done <<< "$base_manifest"
|
|
fi
|
|
fi
|
|
|
|
- name: Setup Rust
|
|
uses: dtolnay/rust-toolchain@1a3a6d54512beeaffd394f8d516ca16f2c506a20 # 1.97
|
|
with:
|
|
components: rustfmt,clippy
|
|
|
|
- name: Cache Rust
|
|
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
|
|
with:
|
|
workspaces: backend -> target
|
|
|
|
- name: Rust format
|
|
working-directory: backend
|
|
run: cargo fmt --check
|
|
|
|
- name: Rust clippy
|
|
working-directory: backend
|
|
run: cargo clippy --all-targets --all-features --locked -- -D warnings
|
|
|
|
- name: Configure PostgreSQL and Redis test authentication
|
|
shell: bash
|
|
run: |
|
|
set -euo pipefail
|
|
postgres_password="$(openssl rand -hex 24)"
|
|
redis_password="$(openssl rand -hex 24)"
|
|
echo "::add-mask::$postgres_password"
|
|
echo "::add-mask::$redis_password"
|
|
docker exec --env PGPASSWORD=codex_proxy "${{ job.services.postgres.id }}" psql \
|
|
--username=codex_proxy --dbname=codex_proxy \
|
|
--set=ON_ERROR_STOP=1 \
|
|
--command="ALTER USER codex_proxy WITH PASSWORD '${postgres_password}'" >/dev/null
|
|
docker exec --env PGPASSWORD="$postgres_password" "${{ job.services.postgres.id }}" psql \
|
|
--username=codex_proxy --dbname=codex_proxy \
|
|
--set=ON_ERROR_STOP=1 --command='SELECT 1' >/dev/null
|
|
docker exec "${{ job.services.redis.id }}" redis-cli CONFIG SET requirepass "$redis_password" >/dev/null
|
|
docker exec "${{ job.services.redis.id }}" redis-cli --no-auth-warning -a "$redis_password" ping >/dev/null
|
|
{
|
|
echo "CPR_TEST_DATABASE_URL=postgres://codex_proxy:${postgres_password}@127.0.0.1:5432/codex_proxy"
|
|
echo "CPR_TEST_REDIS_URL=redis://:${redis_password}@127.0.0.1:6379"
|
|
} >> "$GITHUB_ENV"
|
|
|
|
- name: Rust tests
|
|
working-directory: backend
|
|
run: cargo test --test main --locked
|
|
|
|
frontend:
|
|
if: inputs.run_frontend
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 15
|
|
env:
|
|
CI: 'true'
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
|
|
- name: Setup pnpm
|
|
uses: pnpm/action-setup@0ebf47130e4866e96fce0953f49152a61190b271 # v6.0.9
|
|
with:
|
|
version: 11.7.0
|
|
|
|
- name: Setup Node
|
|
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
|
with:
|
|
node-version: 24
|
|
cache: pnpm
|
|
cache-dependency-path: frontend/pnpm-lock.yaml
|
|
|
|
- name: Install frontend dependencies
|
|
run: pnpm --dir frontend install --frozen-lockfile
|
|
|
|
- name: Frontend format
|
|
run: pnpm --dir frontend format:check
|
|
|
|
- name: Frontend build
|
|
run: pnpm --dir frontend build
|
|
|
|
- name: Upload frontend artifact
|
|
if: inputs.upload_frontend
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
name: frontend-dist
|
|
path: frontend/dist
|
|
if-no-files-found: error
|
|
retention-days: 1
|
|
|
|
workflow-lint:
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 5
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
|
|
- name: Lint GitHub Actions workflows
|
|
run: >-
|
|
docker run --rm
|
|
--volume "$PWD:/repo"
|
|
--workdir /repo
|
|
rhysd/actionlint:1.7.7@sha256:887a259a5a534f3c4f36cb02dca341673c6089431057242cdc931e9f133147e9
|
|
-color
|
|
.github/workflows/*.yml
|