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 --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