feat(ci): CI/CD分层(NAS自动验证+VPS dispatch)
promote.sh 加 --target nas|vps|all(拆 NAS/VPS,默认 all 兼容)。ci-cd.yml 重构: push→test→nas-deploy(推NAS后端+前端dist+restart容器)→nas-verify(login+strategy/list+dbbardata 秒级 gate),不再自动推 VPS。vps-deploy.yml 新建: workflow_dispatch+sha input,checkout 指定 commit→promote --target vps→verify,严格版本一致(VPS=NAS验证过的 commit)。两种 approve: 人工 Gitea UI Run workflow / agent MCP dispatch_workflow。原因: Gitea 不支持 environment approval gate,此前 promote.sh 同时推 NAS+VPS 无隔离,push 即上 prod。
This commit is contained in:
+62
-61
@@ -1,16 +1,15 @@
|
||||
name: CI/CD
|
||||
# 三机流水线:push master → Mac runner 单测 → NAS 冒烟(ssh) → [prod gate] → VPS 部署+冒烟
|
||||
# Runner: 复用 online 的 mac-mini-arm64 (label: macos-arm64),host executor 直接跑 Mac shell。
|
||||
# 关键:①不用 actions/checkout(避免从 github 拉 action 国内 EOF),改 git fetch Gitea 本仓库
|
||||
# ②env HOME=/Users/chufeng 恢复 Mac 全环境(ssh config/key + venv310),act_runner 默认 HOME 是临时目录
|
||||
# ③git config --local http.proxy "" 禁代理(LAN Gitea 直连,不动全局 .gitconfig)
|
||||
# 三机流水线(NAS 分层验证 + VPS 单独 dispatch):
|
||||
# push master → test → nas-deploy(推 NAS 后端+前端+restart) → nas-verify(API健康+数据可读)
|
||||
# VPS 部署走单独 vps-deploy.yml(workflow_dispatch 人工 UI 点 / agent API dispatch),不再这里自动推。
|
||||
# Runner: 复用 online mac-mini-arm64 (label macos-arm64), host executor 跑 Mac shell。
|
||||
# 关键: ①不用 actions/checkout(国内 EOF),改 git fetch Gitea ②HOME=/Users/chufeng ③git config --local http.proxy ""
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
workflow_dispatch: {}
|
||||
|
||||
jobs:
|
||||
# ---------- P2: 验证 ----------
|
||||
test:
|
||||
runs-on: macos-arm64
|
||||
env:
|
||||
@@ -26,74 +25,76 @@ jobs:
|
||||
git remote add origin "http://oauth2:${TOKEN}@192.168.2.154:3000/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --depth 1 origin "${GITHUB_SHA}"
|
||||
git checkout FETCH_HEAD
|
||||
- name: pytest 冒烟 (venv310 python 绝对路径, 数据层核心)
|
||||
# CI 冒烟=数据层核心(memory验证112 pass); source activate非交互不生效故用venv绝对python;
|
||||
# api/live 需 fastapi(venv310未装)留各session自测; PYTHONPATH=workspace 跑 checkout 版本
|
||||
- name: pytest 冒烟 (venv310 数据层核心)
|
||||
run: |
|
||||
PY=/Users/chufeng/.openclaw/sanguo_projects/sanguo_vnpy_v2/venv310/bin/python
|
||||
PYTHONPATH="$GITHUB_WORKSPACE" $PY -m pytest tests/data_platform -q
|
||||
|
||||
nas-smoke:
|
||||
# ---------- NAS 部署(后端代码 + 前端 dist + restart 容器) ----------
|
||||
nas-deploy:
|
||||
needs: test
|
||||
runs-on: macos-arm64
|
||||
env:
|
||||
HOME: /Users/chufeng
|
||||
steps:
|
||||
- name: NAS 冒烟 (replica dbbardata 可读)
|
||||
# smoke_e2e.py 的 docker -v $(pwd):/app 覆盖镜像 /app 致 entrypoint/vnpy 丢失, 改直接查 NAS 副本冒烟
|
||||
- name: checkout
|
||||
env:
|
||||
TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
git init .
|
||||
git config --local http.proxy ""
|
||||
git config --local https.proxy ""
|
||||
git remote add origin "http://oauth2:${TOKEN}@192.168.2.154:3000/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --depth 1 origin "${GITHUB_SHA}"
|
||||
git checkout FETCH_HEAD
|
||||
- name: 推后端代码到 NAS (promote --target nas, 不推 VPS)
|
||||
run: bash scripts/nas_sync/promote.sh --target nas
|
||||
- name: build 前端 + 部署 dist 到 NAS
|
||||
run: |
|
||||
export PATH="/opt/homebrew/bin:$PATH"
|
||||
cd frontend
|
||||
npm ci
|
||||
npm run build
|
||||
rsync -az --delete frontend/dist/ sanguo-nas:/volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2/frontend/dist/
|
||||
- name: restart NAS 容器(加载新后端代码) + 等 web 就绪
|
||||
run: |
|
||||
ssh sanguo-nas '/var/packages/Docker/target/usr/bin/docker restart sanguo_vnpy_v2'
|
||||
for i in $(seq 1 30); do
|
||||
curl -sf http://192.168.2.154:8000/ -o /dev/null && { echo "web ready"; exit 0; }
|
||||
sleep 2
|
||||
done
|
||||
echo "web 未就绪超时"; exit 1
|
||||
|
||||
# ---------- NAS 验证(API 健康 + 数据可读,秒级 gate) ----------
|
||||
nas-verify:
|
||||
needs: nas-deploy
|
||||
runs-on: macos-arm64
|
||||
env:
|
||||
HOME: /Users/chufeng
|
||||
steps:
|
||||
- name: NAS web API 健康(login + strategy/list 200 非空)
|
||||
run: |
|
||||
BASE=http://192.168.2.154:8000
|
||||
token=$(curl -s -X POST $BASE/api/v1/auth/login -H "Content-Type: application/json" \
|
||||
-d '{"username":"admin","password":"Ccf7561523*"}' \
|
||||
| python3 -c "import json,sys;print(json.load(sys.stdin).get('access_token',''))")
|
||||
[ -n "$token" ] || { echo "❌ login 失败"; exit 1; }
|
||||
echo "✅ login OK"
|
||||
code=$(curl -s -o /tmp/sl.json -w "%{http_code}" $BASE/api/v1/strategy/list -H "Authorization: Bearer $token")
|
||||
[ "$code" = "200" ] || { echo "❌ strategy/list HTTP $code"; exit 1; }
|
||||
[ -s /tmp/sl.json ] || { echo "❌ strategy/list 空响应"; exit 1; }
|
||||
echo "✅ strategy/list 200 非空, 后端路由通"
|
||||
- name: NAS 副本数据可读(dbbardata)
|
||||
run: |
|
||||
ssh sanguo-nas "python3 -c 'import sqlite3
|
||||
c=sqlite3.connect(\"/volume1/stock/sanguo_vnpy_v2/data_backup/quant_trading.db\")
|
||||
n=c.execute(\"SELECT COUNT(*) FROM dbbardata WHERE symbol=?\",(\"600519\",)).fetchone()[0]
|
||||
print(\"dbbardata 600519 rows:\", n)
|
||||
exit(0 if n>0 else 1)'"
|
||||
- name: 提示 VPS 部署方式(验证通过后才推 prod)
|
||||
run: |
|
||||
echo "::notice::NAS 验证通过 ✅ 。部署 VPS 触发 vps-deploy.yml, 传 sha=${GITHUB_SHA::7}"
|
||||
echo " 人工: Gitea → Actions → vps-deploy.yml → Run workflow, sha=${GITHUB_SHA}"
|
||||
echo " agent: dispatch_workflow workflow_id=vps-deploy.yml inputs={sha:${GITHUB_SHA}}"
|
||||
|
||||
# ---------- 前端 build + 部署 NAS(与 nas-smoke 并行; VPS 无 web 不部署前端) ----------
|
||||
nas-frontend:
|
||||
needs: test
|
||||
runs-on: macos-arm64
|
||||
env:
|
||||
HOME: /Users/chufeng
|
||||
steps:
|
||||
- name: checkout
|
||||
env:
|
||||
TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
git init .
|
||||
git config --local http.proxy ""
|
||||
git config --local https.proxy ""
|
||||
git remote add origin "http://oauth2:${TOKEN}@192.168.2.154:3000/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --depth 1 origin "${GITHUB_SHA}"
|
||||
git checkout FETCH_HEAD
|
||||
- name: build 前端 (node 在 /opt/homebrew/bin; npm ci 复用 HOME 的 ~/.npm cache)
|
||||
run: |
|
||||
export PATH="/opt/homebrew/bin:$PATH"
|
||||
cd frontend
|
||||
npm ci
|
||||
npm run build
|
||||
- name: 部署 dist 到 NAS (容器 bind mount homes/admin->/app; StaticFiles 实时读文件无需 restart)
|
||||
run: |
|
||||
rsync -az --delete frontend/dist/ sanguo-nas:/volume1/homes/admin/.sanguo_projects/sanguo_vnpy_v2/frontend/dist/
|
||||
|
||||
# ---------- P3: VPS CD (prod gate) ----------
|
||||
vps:
|
||||
needs: [nas-smoke, nas-frontend]
|
||||
runs-on: macos-arm64
|
||||
env:
|
||||
HOME: /Users/chufeng
|
||||
environment: prod # 人工审批 gate;删此行 = 验证通过全自动推 VPS
|
||||
steps:
|
||||
- name: checkout
|
||||
env:
|
||||
TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
git init .
|
||||
git config --local http.proxy ""
|
||||
git config --local https.proxy ""
|
||||
git remote add origin "http://oauth2:${TOKEN}@192.168.2.154:3000/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --depth 1 origin "${GITHUB_SHA}"
|
||||
git checkout FETCH_HEAD
|
||||
- name: promote 代码到 NAS+VPS
|
||||
run: bash scripts/nas_sync/promote.sh
|
||||
- name: VPS 冒烟 (LocalUnifiedProvider 真数据)
|
||||
run: ssh 49.232.102.198 'cd C:\sanguo_vnpy_v2 && C:\Python310\python.exe -X utf8 scripts/data_platform/verify_unified_e2e.py'
|
||||
# 注: VPS 部署已拆到 .gitea/workflows/vps-deploy.yml (workflow_dispatch 人工/agent 触发)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
name: VPS Deploy
|
||||
# VPS 生产部署(人工 UI 点 Run workflow 或 agent 通过 API dispatch 触发)。
|
||||
# 与 ci-cd.yml 分离: ci-cd(push) 只到 NAS 验证; VPS 必须 nas-verify 绿后由此 workflow 主动触发。
|
||||
#
|
||||
# 严格版本一致: 接收 sha input, checkout 该 commit(= NAS 验证通过的那个), 部署到 VPS。
|
||||
# 不会部署 nas-verify 未验证过的代码。
|
||||
#
|
||||
# 触发方式:
|
||||
# 人工: Gitea → Actions → vps-deploy.yml → Run workflow → 填 sha(NAS 验证通过的 commit)
|
||||
# agent: MCP dispatch_workflow workflow_id=vps-deploy.yml ref=master inputs={sha:<full-sha>}
|
||||
# 或 curl POST /api/v1/repos/sanguo/sanguo_vnpy_v2/actions/workflows/vps-deploy.yml/dispatches
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
sha:
|
||||
description: "NAS 验证通过的 commit SHA(完整 40 位, ci-cd 的 nas-verify 输出)"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
vps:
|
||||
runs-on: macos-arm64
|
||||
env:
|
||||
HOME: /Users/chufeng
|
||||
steps:
|
||||
- name: checkout 指定 commit(= NAS 验证通过的版本)
|
||||
env:
|
||||
TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
set -e
|
||||
SHA="${{ inputs.sha }}"
|
||||
git init .
|
||||
git config --local http.proxy ""
|
||||
git config --local https.proxy ""
|
||||
git remote add origin "http://oauth2:${TOKEN}@192.168.2.154:3000/${GITHUB_REPOSITORY}.git"
|
||||
git fetch --depth 1 origin "$SHA"
|
||||
git checkout FETCH_HEAD
|
||||
echo "部署 commit: $(git rev-parse HEAD)"
|
||||
- name: promote 代码到 VPS (只推 VPS, 不动 NAS)
|
||||
run: bash scripts/nas_sync/promote.sh --target vps
|
||||
- name: VPS 冒烟 (LocalUnifiedProvider 真数据)
|
||||
run: ssh 49.232.102.198 'cd C:\sanguo_vnpy_v2 && C:\Python310\python.exe -X utf8 scripts/data_platform/verify_unified_e2e.py'
|
||||
+32
-26
@@ -24,20 +24,22 @@ ALL_MODS=(sanguo_api sanguo_backtest sanguo_common sanguo_data sanguo_factor
|
||||
sanguo_research sanguo_trader sanguo_web scripts config tests)
|
||||
ROOT_FILES=(pyproject.toml pytest.ini requirements-lock.txt run_web.py)
|
||||
|
||||
# 解析 --module 参数
|
||||
# 解析参数: --module <名>(单模块) / --target <nas|vps|all>(目标机器,默认 all)
|
||||
PUSH_MOD=""
|
||||
if [[ "${1:-}" == "--module" ]]; then
|
||||
if [ -z "${2:-}" ]; then
|
||||
echo "ERROR: --module 需要模块名参数" >&2
|
||||
echo "可用模块: ${ALL_MODS[*]}" >&2
|
||||
exit 1
|
||||
fi
|
||||
PUSH_MOD="$2"
|
||||
if [ ! -d "$PUSH_MOD" ]; then
|
||||
echo "ERROR: 本地目录不存在: $PUSH_MOD" >&2
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
TARGET="all"
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--module)
|
||||
[ -z "${2:-}" ] && { echo "ERROR: --module 需要模块名. 可用: ${ALL_MODS[*]}" >&2; exit 1; }
|
||||
PUSH_MOD="$2"; shift 2
|
||||
[ ! -d "$PUSH_MOD" ] && { echo "ERROR: 本地目录不存在: $PUSH_MOD" >&2; exit 1; }
|
||||
;;
|
||||
--target)
|
||||
case "${2:-}" in nas|vps|all) TARGET="$2"; shift 2;; *) echo "ERROR: --target 需 nas|vps|all" >&2; exit 1;; esac
|
||||
;;
|
||||
*) echo "ERROR: 未知参数 $1 (支持 --module <名> / --target <nas|vps|all>)" >&2; exit 1;;
|
||||
esac
|
||||
done
|
||||
|
||||
# rsync exclude: 前导 / = 仅根级匹配,不影响模块内同名子目录
|
||||
RSYNC_EXCLUDE=(--exclude='/data' --exclude='/data_backup' --exclude='/logs'
|
||||
@@ -60,21 +62,24 @@ else
|
||||
fi
|
||||
echo "=============================================="
|
||||
|
||||
# ---- Step 1/3: Mac → NAS (rsync 代码镜像) ----
|
||||
echo ""
|
||||
echo "[1/3] rsync Mac → NAS ($NAS:$NAS_CODE) ..."
|
||||
if [ -n "$PUSH_MOD" ]; then
|
||||
rsync -az "${RSYNC_EXCLUDE[@]}" "$PUSH_MOD/" "$NAS:$NAS_CODE/$PUSH_MOD/"
|
||||
echo " ✅ NAS 单模块镜像完成: $PUSH_MOD"
|
||||
else
|
||||
# 不用 --delete! NAS 有 data_backup 等独有目录,删了就丢数据
|
||||
rsync -avz "${RSYNC_EXCLUDE[@]}" ./ "$NAS:$NAS_CODE/"
|
||||
echo " ✅ NAS 全量镜像完成"
|
||||
if [[ "$TARGET" == "nas" || "$TARGET" == "all" ]]; then
|
||||
# ---- Step: Mac → NAS (rsync 代码镜像) ----
|
||||
echo ""
|
||||
echo "[*] rsync Mac → NAS ($NAS:$NAS_CODE) ..."
|
||||
if [ -n "$PUSH_MOD" ]; then
|
||||
rsync -az "${RSYNC_EXCLUDE[@]}" "$PUSH_MOD/" "$NAS:$NAS_CODE/$PUSH_MOD/"
|
||||
echo " ✅ NAS 单模块镜像完成: $PUSH_MOD"
|
||||
else
|
||||
# 不用 --delete! NAS 有 data_backup 等独有目录,删了就丢数据
|
||||
rsync -avz "${RSYNC_EXCLUDE[@]}" ./ "$NAS:$NAS_CODE/"
|
||||
echo " ✅ NAS 全量镜像完成"
|
||||
fi
|
||||
fi
|
||||
|
||||
# ---- Step 2/3: Mac → VPS (scp 按模块/根文件推) ----
|
||||
if [[ "$TARGET" == "vps" || "$TARGET" == "all" ]]; then
|
||||
# ---- Step: Mac → VPS (scp 按模块/根文件推) ----
|
||||
echo ""
|
||||
echo "[2/3] scp Mac → VPS ($VPS:$VPS_CODE) ..."
|
||||
echo "[*] scp Mac → VPS ($VPS:$VPS_CODE) ..."
|
||||
if [ -n "$PUSH_MOD" ]; then
|
||||
MODS=("$PUSH_MOD")
|
||||
ROOT_PUSH=() # 单模块不推根文件
|
||||
@@ -134,10 +139,11 @@ if ssh -o ConnectTimeout=20 "$VPS" "$VERIFY_CMD" 2>/dev/null | grep -q '\.py'; t
|
||||
else
|
||||
echo " ⚠️ VPS 验证未取到 $VERIFY_MOD 的 .py 文件(可能目录为空或 ssh 超时,请手动核查)"
|
||||
fi
|
||||
fi # end target=vps|all
|
||||
|
||||
echo ""
|
||||
echo "=============================================="
|
||||
echo " ✅ 晋升完成 $(date '+%F %T')"
|
||||
echo " ✅ 晋升完成 $(date '+%F %T') [target=$TARGET]"
|
||||
echo " - NAS 代码镜像: $NAS:$NAS_CODE"
|
||||
echo " - VPS 生产代码: $VPS:$VPS_CODE"
|
||||
echo " - reload: VPS 无常驻 web,下次 schtask/回测启动自动生效"
|
||||
|
||||
Reference in New Issue
Block a user