feat(ci-cd): add self-hosted Gitea Actions runner + CI & production CD
Some checks are pending
CI / test (push) Waiting to run

- CI image (deploy/ci/Dockerfile) with warm deps, no GitHub/Docker Hub dependency
- act_runner deployment templates (compose/config/.env) for Gitea host 47.111.21.147
- Rewrite ci.yml: manual git clone (GITHUB_TOKEN), runs-on agenteval-ci, reuse ci-check.sh
- Add cd-production.yml: tag v*/dispatch trigger, test gate + version gate + ssh deploy
  via scripts/deploy-volcengine-102.sh (unchanged)
- t480 stays manual (internal network, unreachable from Gitea host)
This commit is contained in:
sinohqb 2026-08-12 15:02:29 +08:00
parent fed52f3920
commit b451ac9625
6 changed files with 241 additions and 20 deletions

View File

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

View File

@ -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 → tscnode_modules 已装,不会跳过)
- name: CI checks (version / ruff / pytest / tsc)
run: scripts/ci-check.sh

53
deploy/ci/Dockerfile Normal file
View File

@ -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"]

View File

@ -0,0 +1,2 @@
# repo → Settings → Actions → Runners → Create new Runner 获取(只显示一次)
GITEA_RUNNER_REGISTRATION_TOKEN=

View File

@ -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: ""

View File

@ -0,0 +1,24 @@
# act_runnerAgentEvalTool 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-ondocker:// 前缀指向 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 容器)