a58d9fd385
pipefail+SIGPIPE 三处(本机复现rc=141确定性,run724根因): - L82端口释放检测被反转(最危险): netstat大输出|grep -q命中即提前退出→上游tr吃SIGPIPE(141)→pipefail判整管道失败→走||分支谎报「已释放」→下一步/run撞10048竞态 - L91 API等待循环同雷=run724死因: 检测恒假→120轮耗尽exit1→收尾三步全跳过 - L98 /docs探活同族(输出仅3字节几乎不触发,顺手修) 修法: grep -q 'x' → grep 'x' >/dev/null(读完整流不提前退出,无SIGPIPE) workflow内裸ssh全量补 -o ConnectTimeout=20(对齐vps_restart_resident.sh写法): 单次ssh挂死原会让整步无限冻结; vps-deploy.yml 9处+ci-cd.yml 2处(NAS) 收尾三步抽幂等脚本 scripts/ci/vps_finalize.sh(工作包#2): - run681/715/724三实录: 部署本体成功但收尾被前序步骤失败整段跳过,只能人工拼三段补 - 现在workflow尾步自动调; 被跳过时Mac本机手跑即补: GTOKEN=<token> bash scripts/ci/vps_finalize.sh <部署的sha> - 常驻重启失败不阻断tag/issue记账(代码已就位,末尾非零退出提醒); tag -f+push -f天然幂等; issue按真实gap开/关 关issue按真实gap(工作包#3,2026-08-21事故): - 旧代码硬编码VPS_LOG=""无脑关issue——用户dispatch填旧sha(f28f9cf≠be315a0)漏推P0后,gap非空也关→用户误以为推完 - 修=finalize算vps-deployed..origin/master真实gap,非空则issue保持open并列剩余清单 - gap改按subject算(--format='%h %s'|grep,与enforce-label同定义): --grep匹配整个message,commit body里引用的命令文本会误命中(曾误开issue#7); ci-cd.yml check-vps-needed同步改 - vps_pending_check.py: GITHUB_SHA缺失时回退VPS_DEPLOY_SHA(支持Mac手跑); 有上轮部署sha时body加「仍未上VPS」警示
116 lines
6.8 KiB
YAML
116 lines
6.8 KiB
YAML
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 未验证过的代码。
|
|
#
|
|
# 触发方式(2026-08-14 起带口令闸门,见 inputs.confirm):
|
|
# 人工: Gitea → Actions → vps-deploy.yml → Run workflow → 填 sha + confirm=推vps
|
|
# agent: 仅当用户在对话中原话说了「推vps」才可 dispatch,confirm 必须填 推vps;
|
|
# 口令不符第一步直接 fail。不得代填/推断/沿用旧授权。
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
sha:
|
|
description: "NAS 验证通过的 commit SHA(完整 40 位, ci-cd 的 nas-verify 输出)"
|
|
required: true
|
|
type: string
|
|
confirm:
|
|
description: "部署确认口令: 只有用户在对话中说了「推vps」才可填(用户原话,agent 不得代填/推断)"
|
|
required: true
|
|
type: string
|
|
|
|
jobs:
|
|
vps:
|
|
runs-on: macos-arm64
|
|
env:
|
|
HOME: /Users/chufeng
|
|
steps:
|
|
- name: 口令闸门(用户没说「推vps」就不许部署)
|
|
run: |
|
|
if [ "${{ inputs.confirm }}" != "推vps" ]; then
|
|
echo "❌ 口令不符——部署未获用户确认,退出。"
|
|
exit 1
|
|
fi
|
|
echo "✅ 口令确认,继续部署。"
|
|
|
|
- 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 -o ConnectTimeout=20 49.232.102.198 'cd C:\sanguo_vnpy_v2 && C:\Python310\python.exe -X utf8 scripts/data_platform/verify_unified_e2e.py'
|
|
- name: 前端构建 + 推 dist 到 VPS (公网前端无管线, 此处补齐)
|
|
run: |
|
|
set -e
|
|
cd frontend
|
|
npm ci
|
|
npm run build
|
|
cd ..
|
|
tar czf /tmp/dist_latest.tgz -C frontend/dist .
|
|
scp -o ConnectTimeout=20 /tmp/dist_latest.tgz 49.232.102.198:C:/sanguo_vnpy_v2/dist_latest.tgz
|
|
- name: VPS 替换 dist + 重启 sanguo-api (等端口释放防 10048 竞态, 再等起服)
|
|
run: |
|
|
set -e
|
|
ssh -o ConnectTimeout=20 49.232.102.198 'C:\Python310\python.exe -X utf8 C:\sanguo_vnpy_v2\scripts\nas_sync\vps_update_dist.py' | tee /tmp/dist_swap.log
|
|
grep -q DIST_UPDATE_DONE /tmp/dist_swap.log
|
|
# 停 API → 僵尸端口守卫 → 等端口彻底释放(否则 fail) → 重启
|
|
# 2026-08-16 实录: /end 杀不掉 uvicorn reload 孤儿子进程, 旧进程跨两轮
|
|
# 部署占 8000, 新实例静默抢不到端口, /docs 200 验的还是旧代码 → 500 旧 bug 重现
|
|
ssh -o ConnectTimeout=20 49.232.102.198 'schtasks /end /tn sanguo-api' || true
|
|
# 注意 || true 兜底: 端口已空时 grep 无匹配, set -e -o pipefail 会把整步炸掉
|
|
# (2026-08-16 run674 实录: /end 干净杀掉后 180ms 死在这行, API 停在半路)
|
|
ZPID=$(ssh -o ConnectTimeout=20 49.232.102.198 'netstat -ano -p tcp' | tr -d '\r' | grep ':8000' | grep -i LISTENING | awk '{print $NF}' | sort -u | head -1 || true)
|
|
if [ -n "$ZPID" ]; then
|
|
echo "僵尸 API 进程 PID=$ZPID 仍占 8000, 强杀"
|
|
ssh -o ConnectTimeout=20 49.232.102.198 "taskkill /F /T /PID $ZPID" || true
|
|
fi
|
|
# ⚠️ pipefail 雷区(2026-08-22 run724 根因, issue#33 评论1290): 本步 shell 是 -e -o pipefail,
|
|
# `大输出 | grep -q` 命中即提前退出 → 上游 tr 吃 SIGPIPE(141) → pipefail 把「匹配成功」判成
|
|
# 整管道失败。本行曾因此被反转: 端口仍在监听却走 || 分支谎报「已释放」→ 下一步 /run 撞 10048。
|
|
# 修法 = `grep 'x' >/dev/null`(读完整流不提前退出); 下面等待循环同雷同修(run724 死因)。
|
|
PORT_FREED=0
|
|
for i in $(seq 1 30); do
|
|
ssh -o ConnectTimeout=20 49.232.102.198 'netstat -ano -p tcp' | tr -d '\r' | grep ':8000.*LISTEN' >/dev/null || { echo "port freed after $((i*2))s"; PORT_FREED=1; break; }
|
|
sleep 2
|
|
done
|
|
if [ "$PORT_FREED" != "1" ]; then echo "❌ 端口 8000 未释放, 强杀后重启会 10048, 终止"; exit 1; fi
|
|
ssh -o ConnectTimeout=20 49.232.102.198 'schtasks /run /tn sanguo-api'
|
|
# 等新实例监听 + /docs 可用(上限 20 分钟:2026-08-16 run681 实录 19 个常驻进程
|
|
# 负载下 API 启动超 10 分钟,最终正常起服——10 分钟帽误杀,workflow 放弃时
|
|
# API 其实在路上,且跳过了常驻重启/tag/关issue 三步)
|
|
for i in $(seq 1 120); do
|
|
# grep >/dev/null 而非 grep -q: 同上 pipefail+SIGPIPE 雷(run724 死因: 检测恒假→120 轮耗尽 exit 1→收尾三步全跳过)
|
|
if ssh -o ConnectTimeout=20 49.232.102.198 'netstat -ano -p tcp' | tr -d '\r' | grep ':8000.*LISTEN' >/dev/null; then
|
|
echo "API listening after ~$((i*10))s"; break
|
|
fi
|
|
sleep 10
|
|
if [ "$i" = "120" ]; then echo "API 20 分钟未监听 8000"; exit 1; fi
|
|
done
|
|
for i in $(seq 1 12); do
|
|
ssh -o ConnectTimeout=20 49.232.102.198 'powershell -NoProfile -Command "(Invoke-WebRequest -UseBasicParsing http://127.0.0.1:8000/docs).StatusCode"' 2>/dev/null | grep 200 >/dev/null && { echo "API /docs 200"; exit 0; }
|
|
sleep 10
|
|
done
|
|
echo "API 监听但 /docs 2 分钟未返回 200"; exit 1
|
|
# 收尾三步抽成幂等脚本(2026-08-22 基建工作包#2, issue#33): run681/715/724 三次实录
|
|
# 部署成功但收尾被前序步骤失败整段跳过, 只能人工补。现在: 正常走到尾自动执行;
|
|
# 被跳过时 Mac 本机手跑即可补: GTOKEN=<token> bash scripts/ci/vps_finalize.sh <部署的sha>
|
|
# issue 维护按 vps-deployed..origin/master 真实 gap 开/关(2026-08-21 事故: 部署旧 sha 后
|
|
# 无脑关 issue, gap 非空也关 → 用户误以为推完)。
|
|
- name: 收尾三步 (常驻重启 + vps-deployed tag + [待推VPS] issue, 幂等可重入)
|
|
env:
|
|
GTOKEN: ${{ github.token }}
|
|
run: bash scripts/ci/vps_finalize.sh "${{ inputs.sha }}"
|