Phase 1 of audit-hardening CI expansion (baseline origin/main aebc623):
- lint: ruff E9 hard gate; full E,F@120 report-only (baseline 111 items)
- typecheck: mypy default mode, production scope, report-only (baseline 24)
- security: bandit production scope, report-only (baseline 287, High 5)
- depscan: pip-audit on requirements-ci.txt, hard gate (0 vulns; repo has
no production dependency manifest, see summary)
- coverage: production 58 files, fail-under=75 (baseline measured 76%)
- release build: E9 + pip-audit hard gates before packaging; full reports
stay in host-verify to avoid drift
No production code changed; CI billing failure mode unchanged.
106 lines
4.5 KiB
YAML
106 lines
4.5 KiB
YAML
name: release
|
||
# 发布链路:主干推送绝不自动部署生产。
|
||
# 触发只有两种,两种都要经过 host-verify 同款测试:
|
||
# 1. 手动 workflow_dispatch(在 Actions 里点 Run,选 environment: production,
|
||
# GitHub 的 environment 审批人要点批准才会继续,见仓库 Settings > Environments)。
|
||
# 2. 打 tag v*(例如 v0.2.0),同样进 production environment 审批。
|
||
# 部署本身不由 Actions 直接拼 ssh 命令,而是复用 deploy/deploy.py:
|
||
# - 构建产物来自 git archive,含 DEPLOYED_COMMIT + sha256;
|
||
# - 传输、远端验 sha、主机 staging 换目录、失败回滚、RELEASE_OK 核对
|
||
# 全在 deploy.py / host_deploy.sh 里,Actions 只负责“何时触发 + 审批”。
|
||
# SSH 凭据只走 secrets,不写进日志:DEPLOY_HOST、DEPLOY_USER、
|
||
# DEPLOY_SSH_KEY(私钥内容,Actions 里写 ~/.ssh/id_ed25519,0600)。
|
||
# 没有配这些 secrets 时,build 作业仍产出可下载的发布包 artifact,
|
||
# deploy 作业会明确失败并提示“未配部署凭据,改走 VM 侧 deploy.py”,
|
||
# 绝不静默跳过部署又报成功。
|
||
on:
|
||
workflow_dispatch:
|
||
inputs:
|
||
ref:
|
||
description: "要部署的 ref(默认 main 的 HEAD)"
|
||
required: false
|
||
default: "main"
|
||
push:
|
||
tags:
|
||
- "v*"
|
||
permissions:
|
||
contents: read
|
||
jobs:
|
||
build:
|
||
runs-on: ubuntu-latest
|
||
outputs:
|
||
commit: ${{ steps.build.outputs.commit }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ inputs.ref || github.ref }}
|
||
- uses: actions/setup-python@v5
|
||
with:
|
||
python-version: "3.12"
|
||
- name: 测试(与 host-verify 同款,失败则不产发布包)
|
||
run: |
|
||
python3 -m unittest discover -s tests -v
|
||
python3 -m unittest discover -s cloud/tests -v
|
||
python3 -m unittest discover -s deploy/tests -v
|
||
# CI 扩项在发布链路的最小硬门禁:语法 E9 + CI 工具链依赖扫描。
|
||
# 全量 lint/mypy/bandit 报告与覆盖率门槛只在 host-verify 里跑
|
||
# (PR 与 main 推送都会跑,走到 tag 的提交此前已过 host-verify),
|
||
# 这里不重复跑三套报告,避免发布作业与 host-verify 的检查漂移;
|
||
# 这里的两项是硬失败,失败则不产发布包。
|
||
- name: CI 扩项硬门禁(语法 + 依赖扫描)
|
||
run: |
|
||
pip install -r requirements-ci.txt
|
||
ruff check --select E9 .
|
||
pip-audit -r requirements-ci.txt
|
||
- name: 构建发布包(git archive + DEPLOYED_COMMIT + sha256)
|
||
id: build
|
||
run: |
|
||
python3 deploy/deploy.py --build-only --skip-tests \
|
||
--ref "${{ inputs.ref || github.ref }}" \
|
||
--out-dir "$GITHUB_WORKSPACE/release-artifact"
|
||
echo "commit=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||
ls -lh release-artifact/
|
||
- name: 上传发布包 artifact
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: codex-app-server-release
|
||
path: release-artifact/
|
||
retention-days: 30
|
||
deploy:
|
||
needs: build
|
||
runs-on: ubuntu-latest
|
||
environment: production
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
ref: ${{ inputs.ref || github.ref }}
|
||
- uses: actions/setup-python@v5
|
||
with:
|
||
python-version: "3.12"
|
||
- name: 下载发布包
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
name: codex-app-server-release
|
||
path: release-artifact
|
||
- name: 检查部署凭据是否齐全
|
||
run: |
|
||
if [ -z "${{ secrets.DEPLOY_HOST }}" ] || [ -z "${{ secrets.DEPLOY_SSH_KEY }}" ]; then
|
||
echo "未配 DEPLOY_HOST / DEPLOY_SSH_KEY,无法自动部署生产。" >&2
|
||
echo "发布包已在 artifact 里,改走 VM 侧:python3 deploy/deploy.py <host> --tarball <包> --skip-tests" >&2
|
||
exit 1
|
||
fi
|
||
- name: 写 SSH key(0600,不打印)
|
||
run: |
|
||
mkdir -p ~/.ssh
|
||
printf '%s\n' "${{ secrets.DEPLOY_SSH_KEY }}" > ~/.ssh/id_ed25519
|
||
chmod 600 ~/.ssh/id_ed25519
|
||
- name: 部署(deploy.py:传输验 sha + 主机换目录 + 回滚 + 双重核对)
|
||
env:
|
||
SSH_PROXY_CMD: ""
|
||
run: |
|
||
TARBALL=$(ls release-artifact/*.tar.gz | head -n 1)
|
||
DEPLOY_USER="${{ secrets.DEPLOY_USER }}"
|
||
python3 deploy/deploy.py "${{ secrets.DEPLOY_HOST }}" \
|
||
"${DEPLOY_USER:-root}" \
|
||
--tarball "$TARBALL" --skip-tests
|