feat(ci-cd): add self-hosted Gitea Actions runner + CI & production CD
Some checks are pending
CI / test (push) Waiting to run
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:
parent
fed52f3920
commit
b451ac9625
111
.gitea/workflows/cd-production.yml
Normal file
111
.gitea/workflows/cd-production.yml
Normal 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
|
||||
@ -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
|
||||
|
||||
53
deploy/ci/Dockerfile
Normal file
53
deploy/ci/Dockerfile
Normal 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"]
|
||||
2
deploy/ci/runner/.env.example
Normal file
2
deploy/ci/runner/.env.example
Normal file
@ -0,0 +1,2 @@
|
||||
# repo → Settings → Actions → Runners → Create new Runner 获取(只显示一次)
|
||||
GITEA_RUNNER_REGISTRATION_TOKEN=
|
||||
26
deploy/ci/runner/config.yaml
Normal file
26
deploy/ci/runner/config.yaml
Normal 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: ""
|
||||
24
deploy/ci/runner/docker-compose.yml
Normal file
24
deploy/ci/runner/docker-compose.yml
Normal file
@ -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 容器)
|
||||
Loading…
Reference in New Issue
Block a user