diff --git a/.gitea/workflows/cd-production.yml b/.gitea/workflows/cd-production.yml new file mode 100644 index 0000000..8189c5f --- /dev/null +++ b/.gitea/workflows/cd-production.yml @@ -0,0 +1,111 @@ +name: CD Production (volcengine-102) +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: "要部署的已存在 tag(如 v1.2.0)" + required: true + type: string + +env: + PIP_INDEX_URL: https://mirrors.aliyun.com/pypi/simple/ + PIP_TRUSTED_HOST: mirrors.aliyun.com + NPM_CONFIG_REGISTRY: https://registry.npmmirror.com + # deploy-volcengine-102.sh 读取此变量作为 ssh Host alias, + # alias 在下方 Setup SSH 步骤写入 ~/.ssh/config。 + AGENTEVAL_PROD_HOST: agenteval-prod + +jobs: + # 门禁:部署前必须全绿(tag push 不触发 CI workflow,因此 CD 自带 test job) + test: + name: Pre-deploy checks + runs-on: agenteval-ci + steps: + - name: Checkout + shell: bash + run: | + set -euo pipefail + git clone "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@git.solahqb22.cn/${{ github.repository }}.git" . + TARGET_TAG="${{ github.event.inputs.tag }}" + if [ -n "$TARGET_TAG" ]; then + git checkout --detach "tags/$TARGET_TAG" + else + git checkout --detach "$GITHUB_SHA" + fi + git --no-pager log -1 --oneline + + - name: Install backend deps + run: pip install --no-cache-dir -e ".[dev]" + + - name: Install frontend deps + working-directory: frontend/web + run: npm ci --no-audit --no-fund + + - name: CI checks + run: scripts/ci-check.sh + + deploy: + name: Deploy volcengine-102 + needs: test + runs-on: agenteval-ci + steps: + - name: Checkout + shell: bash + run: | + set -euo pipefail + git clone "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@git.solahqb22.cn/${{ github.repository }}.git" . + TARGET_TAG="${{ github.event.inputs.tag }}" + if [ -n "$TARGET_TAG" ]; then + git checkout --detach "tags/$TARGET_TAG" + else + git checkout --detach "$GITHUB_SHA" + fi + # 部署只读(git archive 为本地操作),回收 remote URL 中的 token + git remote set-url origin "https://git.solahqb22.cn/${{ github.repository }}.git" + git --no-pager log -1 --oneline + + # 安全门:tag 名必须与 pyproject 版本一致,防止错 tag 误发布 + - name: Version gate (tag == pyproject version) + shell: bash + run: | + set -euo pipefail + TAG_NAME="${{ github.event.inputs.tag }}" + TAG_NAME="${TAG_NAME:-$GITHUB_REF_NAME}" + case "$TAG_NAME" in + v*) ;; + *) echo "tag must start with 'v': $TAG_NAME"; exit 1 ;; + esac + PYVER=$(python3 -c "import re; print(re.search(r'^version\s*=\s*\"([^\"]+)\"', open('pyproject.toml').read(), re.M).group(1))") + if [ "${TAG_NAME#v}" != "$PYVER" ]; then + echo "version mismatch: tag=$TAG_NAME pyproject=$PYVER" + exit 1 + fi + echo "version gate OK: $TAG_NAME" + + - name: Setup SSH + shell: bash + run: | + set -euo pipefail + mkdir -p ~/.ssh && chmod 700 ~/.ssh + umask 077 + printf '%s\n' "${{ secrets.PROD_SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519 + printf '%s\n' "${{ secrets.PROD_KNOWN_HOSTS }}" > ~/.ssh/known_hosts + { + echo "Host agenteval-prod" + echo " HostName ${{ secrets.PROD_HOST }}" + echo " User ${{ secrets.PROD_SSH_USER }}" + echo " IdentityFile ~/.ssh/id_ed25519" + echo " IdentitiesOnly yes" + echo " StrictHostKeyChecking yes" + } > ~/.ssh/config + chmod 600 ~/.ssh/config + ssh -o ConnectTimeout=10 agenteval-prod "echo ssh-ok" + + # 部署逻辑零重写:git archive HEAD → rsync → 备份数据卷 → + # docker compose build(注入 IMAGE_TAG/BUILD_COMMIT/BUILD_TIME)→ + # up -d → 健康检查(version+commit 双校验)→ 6 个认证 API smoke + - name: Deploy production + run: scripts/deploy-volcengine-102.sh diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index dfa4886..175690e 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -4,27 +4,32 @@ on: branches: [main] pull_request: +env: + PIP_INDEX_URL: https://mirrors.aliyun.com/pypi/simple/ + PIP_TRUSTED_HOST: mirrors.aliyun.com + NPM_CONFIG_REGISTRY: https://registry.npmmirror.com + jobs: test: - runs-on: ubuntu-latest + runs-on: agenteval-ci steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - name: Install backend deps - run: pip install -e ".[dev]" - - name: Version consistency - run: python scripts/sync_version.py --check - - name: Lint - run: ruff check backend/ - - name: Tests - run: python -m pytest -q - - uses: actions/setup-node@v4 - with: - node-version: "20" - - name: Frontend type check + # 不用 actions/checkout(依赖 GitHub,国内不可达)。手动 clone: + # secrets.GITHUB_TOKEN 由 Gitea 自动注入,仅对本仓库有效的临时 token。 + - name: Checkout (manual git clone, no actions/checkout) + shell: bash run: | - cd frontend/web - npm ci - npx tsc --noEmit + set -euo pipefail + git clone "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@git.solahqb22.cn/${{ github.repository }}.git" . + git checkout --detach "$GITHUB_SHA" + git --no-pager log -1 --oneline + + - name: Install backend deps + run: pip install --no-cache-dir -e ".[dev]" + + - name: Install frontend deps + working-directory: frontend/web + run: npm ci --no-audit --no-fund + + # 唯一检查入口:版本一致性 → ruff → pytest → tsc(node_modules 已装,不会跳过) + - name: CI checks (version / ruff / pytest / tsc) + run: scripts/ci-check.sh diff --git a/deploy/ci/Dockerfile b/deploy/ci/Dockerfile new file mode 100644 index 0000000..3f1d32f --- /dev/null +++ b/deploy/ci/Dockerfile @@ -0,0 +1,53 @@ +# Gitea Actions CI 镜像(runner label: agenteval-ci) +# 预装 Python 3.11 + Node 20 + git/openssh/rsync + 项目依赖缓存。 +# 基础镜像全部来自私有 registry 的 mirror,构建/运行均不触达 Docker Hub / GitHub。 +# +# 构建与推送(在 Gitea 服务器或任意 linux/amd64 且能访问 registry 的机器上): +# docker build -f deploy/ci/Dockerfile -t registry.solahqb22.cn/ci/agenteval-ci:latest . +# docker push registry.solahqb22.cn/ci/agenteval-ci:latest +# 注意:本地 macOS(arm64) 构建必须 --platform linux/amd64 并直接 --push;推荐在 Gitea 服务器上构建。 + +FROM registry.solahqb22.cn/mirror/node:20-bookworm AS node +FROM registry.solahqb22.cn/mirror/python:3.11-slim-bookworm + +# Node 20:复制官方 node 镜像的二进制与内置 npm/corepack +COPY --from=node /usr/local/bin/node /usr/local/bin/node +COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules +RUN ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm \ + && ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx \ + && ln -sf /usr/local/lib/node_modules/corepack/dist/corepack.js /usr/local/bin/corepack \ + && node --version && npm --version + +# 国内镜像源(pip / npm / apt) +ENV PIP_INDEX_URL=https://mirrors.aliyun.com/pypi/simple/ \ + PIP_TRUSTED_HOST=mirrors.aliyun.com \ + PIP_DISABLE_PIP_VERSION_CHECK=1 \ + NPM_CONFIG_REGISTRY=https://registry.npmmirror.com \ + PYTHONDONTWRITEBYTECODE=1 \ + PYTHONUNBUFFERED=1 + +RUN sed -i \ + -e 's|deb.debian.org|mirrors.aliyun.com|g' \ + -e 's|security.debian.org|mirrors.aliyun.com/debian-security|g' \ + /etc/apt/sources.list.d/debian.sources \ + && apt-get update \ + && apt-get install -y --no-install-recommends \ + git openssh-client rsync curl ca-certificates gcc jq \ + && rm -rf /var/lib/apt/lists/* + +# 骨架 trick 预热 Python 依赖:依赖列表以 pyproject.toml 为唯一数据源, +# CI 时 `pip install -e ".[dev]"` 全命中已装依赖,秒级完成。 +WORKDIR /opt/seed +COPY pyproject.toml README.md ./ +RUN mkdir -p backend/agenteval backend/cli \ + && touch backend/agenteval/__init__.py backend/cli/__init__.py \ + && pip install --no-cache-dir ".[dev]" + +# 预热 npm 缓存:~/.npm 留在镜像层,CI 的 `npm ci` 主要命中本地缓存 +COPY frontend/web/package.json frontend/web/package-lock.json ./frontend/web/ +RUN cd frontend/web && npm ci --ignore-scripts --no-audit --no-fund + +WORKDIR / +RUN rm -rf /opt/seed + +CMD ["bash"] diff --git a/deploy/ci/runner/.env.example b/deploy/ci/runner/.env.example new file mode 100644 index 0000000..89a04fc --- /dev/null +++ b/deploy/ci/runner/.env.example @@ -0,0 +1,2 @@ +# repo → Settings → Actions → Runners → Create new Runner 获取(只显示一次) +GITEA_RUNNER_REGISTRATION_TOKEN= diff --git a/deploy/ci/runner/config.yaml b/deploy/ci/runner/config.yaml new file mode 100644 index 0000000..0b5fd67 --- /dev/null +++ b/deploy/ci/runner/config.yaml @@ -0,0 +1,26 @@ +log: + level: info +runner: + file: .runner + capacity: 1 # 单人项目串行执行即可 + timeout: 1h + fetch_timeout: 5s + fetch_interval: 2s + workdir_parent: /work # runner 容器内路径 + labels: + - "agenteval-ci:docker://registry.solahqb22.cn/ci/agenteval-ci:latest" + envs: # 注入所有 job 的环境变量(双保险,workflow 里也写了) + PIP_INDEX_URL: https://mirrors.aliyun.com/pypi/simple/ + NPM_CONFIG_REGISTRY: https://registry.npmmirror.com +cache: + enabled: true + dir: /data/cache +container: + # 关键:job 容器由宿主机 daemon 创建,bind mount 源路径按宿主机解析, + # 因此 job 侧 workdir 必须指向宿主机上与 runner 容器 /work 对应的宿主机路径。 + workdir_parent: /opt/act_runner/work + valid_volumes: + - /var/run/docker.sock + # options: --add-host git.solahqb22.cn:host-gateway # 仅当 EIP hairpin 不通时启用(见计划 6.4) +host: + workdir_parent: "" diff --git a/deploy/ci/runner/docker-compose.yml b/deploy/ci/runner/docker-compose.yml new file mode 100644 index 0000000..aab6447 --- /dev/null +++ b/deploy/ci/runner/docker-compose.yml @@ -0,0 +1,24 @@ +# act_runner:AgentEvalTool repo-level runner,运行在 Gitea 服务器本机。 +# 首次部署: +# 1) mkdir -p /opt/act_runner/{data,work} +# 2) cp deploy/ci/runner/config.yaml /opt/act_runner/data/config.yaml +# 3) cp deploy/ci/runner/docker-compose.yml /opt/act_runner/ && cd /opt/act_runner +# 4) cp deploy/ci/runner/.env.example .env 并填入 registration token +# (token 来源:repo → Settings → Actions → Runners → Create new Runner) +# 5) docker compose up -d && docker logs -f act_runner # 看到 runner registered 即成功 +services: + act_runner: + image: registry.solahqb22.cn/mirror/gitea/act_runner:0.2.11 + container_name: act_runner + restart: unless-stopped + env_file: .env + environment: + CONFIG_FILE: /data/config.yaml + GITEA_INSTANCE_URL: https://git.solahqb22.cn + GITEA_RUNNER_NAME: gitea-host-agenteval + # label 即 workflow 的 runs-on;docker:// 前缀指向 job 容器镜像 + GITEA_RUNNER_LABELS: "agenteval-ci:docker://registry.solahqb22.cn/ci/agenteval-ci:latest" + volumes: + - /opt/act_runner/data:/data # .runner 注册状态 + config.yaml(删了需重新注册) + - /opt/act_runner/work:/work # job workspace(与 config.yaml 的 workdir_parent 对应) + - /var/run/docker.sock:/var/run/docker.sock # job 容器由宿主机 daemon 拉起(sibling 容器)