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]
|
branches: [main]
|
||||||
pull_request:
|
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:
|
jobs:
|
||||||
test:
|
test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: agenteval-ci
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
# 不用 actions/checkout(依赖 GitHub,国内不可达)。手动 clone:
|
||||||
- uses: actions/setup-python@v5
|
# secrets.GITHUB_TOKEN 由 Gitea 自动注入,仅对本仓库有效的临时 token。
|
||||||
with:
|
- name: Checkout (manual git clone, no actions/checkout)
|
||||||
python-version: "3.11"
|
shell: bash
|
||||||
- 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
|
|
||||||
run: |
|
run: |
|
||||||
cd frontend/web
|
set -euo pipefail
|
||||||
npm ci
|
git clone "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@git.solahqb22.cn/${{ github.repository }}.git" .
|
||||||
npx tsc --noEmit
|
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