guanghulab/.github/workflows/refresh-autodl-endpoint.yml
Guanghu Domestic Migration d1e47f4565
Some checks are pending
自动更新代码和重启 / update-and-restart (push) Waiting to run
CI检查 + 自动部署 / check (push) Waiting to run
CI检查 + 自动部署 / deploy (push) Blocked by required conditions
重启聊天服务 / restart (push) Waiting to run
chore: import sanitized domestic snapshot for REPO-002
Source snapshot: ca48d3ddf926d79aa138306164169baf764bb829
2026-07-17 15:54:41 +08:00

347 lines
15 KiB
YAML
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

name: '🔄 刷新推理端点'
# ════════════════════════════════════════════════════════════
# AutoDL 推理机端口刷新 · refresh-autodl-endpoint
# 编号: ZY-WF-REFRESH-AUTODL-ENDPOINT
# 守护: 铸渊 · ICE-GL-ZY001
# 版权: 国作登字-2026-A-00037559
#
# 触发场景:
# AutoDL 实例每次开机 host:port 都漂移. 冰朔/Awen 在 AutoDL 开
# 机后跑 setup-inference.sh, 然后来这里填新的 host:port + 误触
# 锁口令「刷新推理端点」, 工作流自动:
# 1. check-secrets 校验
# 2. curl /v1/health 探活 (不通就不写, 避免污染 2C2G)
# 3. SSH 到 ZY-SVR-CN01 写 inference-endpoint.json
# 4. pm2 reload portal (热加载)
# 5. 中文回执
#
# 误触锁: confirm_phrase = 「刷新推理端点」
# 因果链: cc-003 (动态适配) + cc-004 (中文一次性)
# ════════════════════════════════════════════════════════════
on:
workflow_dispatch:
inputs:
autodl_host:
description: 'AutoDL 推理服务公网 host (例: connect.westa.seetacloud.com 或 8.131.xx.xx)'
required: true
type: string
autodl_port:
description: 'AutoDL 推理服务公网端口 (推理服务 HTTPS 暴露端口, 不是 SSH 端口)'
required: true
type: string
use_https:
description: '协议: https (推荐) / http'
required: true
default: 'https'
type: choice
options:
- 'https'
- 'http'
confirm_phrase:
description: '误触锁 · 必须输入「刷新推理端点」才会真写, 其他值降级 dry-run'
required: false
default: ''
type: string
dry_run:
description: '只打印计划, 不真写域名机 (true/false)'
required: false
default: 'false'
type: choice
options:
- 'false'
- 'true'
permissions:
contents: read
concurrency:
group: refresh-autodl-endpoint
cancel-in-progress: false
jobs:
preflight:
name: 启动前预检 · 密钥 + 误触锁 + 探活
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
effective_dry_run: ${{ steps.misclick.outputs.effective_dry_run }}
health_ok: ${{ steps.health.outputs.health_ok }}
gpu_name: ${{ steps.health.outputs.gpu_name }}
gpu_mem_gb: ${{ steps.health.outputs.gpu_mem_gb }}
active_model: ${{ steps.health.outputs.active_model }}
tier: ${{ steps.health.outputs.tier }}
quant: ${{ steps.health.outputs.quant }}
endpoint_url: ${{ steps.endpoint.outputs.endpoint_url }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- name: 启动前最小密钥校验 (cn-domain-deploy)
env:
ZY_CN_SERVER_HOST: ${{ secrets.ZY_CN_SERVER_HOST }}
ZY_CN_SERVER_USER: ${{ secrets.ZY_CN_SERVER_USER }}
ZY_CN_SERVER_KEY: ${{ secrets.ZY_CN_SERVER_KEY }}
ZY_CN_SERVER_PATH: ${{ secrets.ZY_CN_SERVER_PATH }}
ZY_CN_SSH_PORT: ${{ secrets.ZY_CN_SSH_PORT }}
run: |
# 这里只校验 ZY_CN_SERVER_*, AutoDL 的 host:port 是 input 不是 secret
node scripts/preflight/check-secrets.js \
--workflow cn-domain-deploy \
--stage update \
--target main
- name: 误触锁 · 必须输入「刷新推理端点」
id: misclick
env:
PHRASE: ${{ inputs.confirm_phrase }}
DRY: ${{ inputs.dry_run }}
run: |
set -e
EFFECTIVE_DRY="$DRY"
REQUIRED="刷新推理端点"
if [ "$PHRASE" != "$REQUIRED" ]; then
echo "═══════════════════════════════════════════════"
echo " ⚠️ 误触保护"
echo " 要真写 2C2G 域名机, 必须在 confirm_phrase"
echo " 字段精确输入: 「$REQUIRED」"
echo " 当前值=「$PHRASE」 → 自动切到 dry-run."
echo "═══════════════════════════════════════════════"
EFFECTIVE_DRY="true"
else
echo "✅ 已显式确认 (phrase=「$PHRASE」), 即将真写"
fi
echo "effective_dry_run=$EFFECTIVE_DRY" >> "$GITHUB_OUTPUT"
- name: 组装端点 URL
id: endpoint
env:
HOST: ${{ inputs.autodl_host }}
PORT: ${{ inputs.autodl_port }}
PROTO: ${{ inputs.use_https }}
run: |
set -euo pipefail
# 简易格式校验 — 防止 Awen 误粘 ssh 命令进来
if echo "$HOST" | grep -qE '[[:space:]]'; then
echo "❌ host 含空白字符, 请只填裸 host (不要带 ssh -p ...)" >&2; exit 1
fi
if ! echo "$PORT" | grep -qE '^[0-9]+$'; then
echo "❌ port 必须是纯数字, 当前=「$PORT」" >&2; exit 1
fi
if [ "$PORT" -lt 1 ] || [ "$PORT" -gt 65535 ]; then
echo "❌ port 越界 (1..65535), 当前=$PORT" >&2; exit 1
fi
URL="${PROTO}://${HOST}:${PORT}"
echo "endpoint_url=$URL" >> "$GITHUB_OUTPUT"
echo "✅ 目标端点: $URL"
- name: 探活 /v1/health (不通就不往下走)
id: health
env:
URL: ${{ steps.endpoint.outputs.endpoint_url }}
run: |
set -uo pipefail
echo "[health] curl $URL/v1/health (重试 3 次, 每次 10s 超时)"
OK="false"
BODY=""
for i in 1 2 3; do
BODY="$(curl -fsS -m 10 "$URL/v1/health" 2>/dev/null || true)"
if [ -n "$BODY" ] && echo "$BODY" | grep -q '"status"'; then
OK="true"
break
fi
echo " 第 $i 次失败, 2s 后重试..."
sleep 2
done
if [ "$OK" != "true" ]; then
echo "═══════════════════════════════════════════════════════════"
echo " ❌ 推理服务不可达: $URL/v1/health"
echo "═══════════════════════════════════════════════════════════"
echo " 常见原因:"
echo " 1. AutoDL 实例没开机 / 自定义服务端口没暴露"
echo " 2. host:port 抄错了 (注意是推理服务端口, 不是 SSH 端口)"
echo " 3. setup-inference.sh 还没跑完 (大模型加载需要 60-120s)"
echo " 4. 协议错: 实际只有 http 但填了 https (或反过来)"
echo ""
echo " 下一步: 先在 AutoDL 上 curl 本机 http://127.0.0.1:8000/v1/health"
echo " 确认服务在跑, 再来重试本工作流."
echo "═══════════════════════════════════════════════════════════"
echo "health_ok=false" >> "$GITHUB_OUTPUT"
exit 1
fi
echo "$BODY" > /tmp/health.json
GPU_NAME="$(echo "$BODY" | jq -r '.gpu.name // "unknown"')"
GPU_MEM_GB="$(echo "$BODY" | jq -r '.gpu.memory_gb // "0"')"
ACTIVE_MODEL="$(echo "$BODY" | jq -r '.model.name // "unknown"')"
TIER="$(echo "$BODY" | jq -r '.tier.size_tier // "unknown"')"
QUANT="$(echo "$BODY" | jq -r '.tier.quantization // "unknown"')"
{
echo "health_ok=true"
echo "gpu_name=$GPU_NAME"
echo "gpu_mem_gb=$GPU_MEM_GB"
echo "active_model=$ACTIVE_MODEL"
echo "tier=$TIER"
echo "quant=$QUANT"
} >> "$GITHUB_OUTPUT"
echo "✅ 推理服务在跑 · GPU=$GPU_NAME · 当前模型=$ACTIVE_MODEL · 档位=$TIER/$QUANT"
write-endpoint:
name: 写 inference-endpoint.json + 重载 portal
needs: preflight
if: needs.preflight.outputs.health_ok == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: 装载 SSH 凭据 (写到 2C2G 域名机)
id: target
env:
HOST: ${{ secrets.ZY_CN_SERVER_HOST }}
USER: ${{ secrets.ZY_CN_SERVER_USER }}
KEY: ${{ secrets.ZY_CN_SERVER_KEY }}
PORT: ${{ secrets.ZY_CN_SSH_PORT }}
DEPLOY_PATH: ${{ secrets.ZY_CN_SERVER_PATH }}
run: |
set -euo pipefail
if [ -z "$HOST" ] || [ -z "$KEY" ] || [ -z "$USER" ]; then
echo "❌ ZY_CN_SERVER_{HOST,USER,KEY} 任一为空" >&2; exit 1
fi
PORT="${PORT:-22}"
DEPLOY_PATH="${DEPLOY_PATH:-/data/guanghulab}"
echo "user=$USER" >> "$GITHUB_OUTPUT"
echo "port=$PORT" >> "$GITHUB_OUTPUT"
echo "deploy_path=$DEPLOY_PATH" >> "$GITHUB_OUTPUT"
umask 077
mkdir -p ~/.ssh
printf '%s\n' "$KEY" > ~/.ssh/refresh_key
chmod 600 ~/.ssh/refresh_key
ssh-keyscan -p "$PORT" -H "$HOST" >> ~/.ssh/known_hosts 2>/dev/null || true
printf '%s' "$HOST" > ~/.ssh/refresh_host
chmod 600 ~/.ssh/refresh_host
- name: 组装 inference-endpoint.json
env:
URL: ${{ needs.preflight.outputs.endpoint_url }}
GPU_NAME: ${{ needs.preflight.outputs.gpu_name }}
GPU_MEM_GB: ${{ needs.preflight.outputs.gpu_mem_gb }}
ACTIVE_MODEL: ${{ needs.preflight.outputs.active_model }}
TIER: ${{ needs.preflight.outputs.tier }}
QUANT: ${{ needs.preflight.outputs.quant }}
RUN_ID: ${{ github.run_id }}
run: |
set -euo pipefail
REFRESHED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
# jq 安全组装, 避免 shell 注入
jq -n \
--arg url "$URL" \
--arg gpu_name "$GPU_NAME" \
--arg gpu_mem_gb "$GPU_MEM_GB" \
--arg active_model "$ACTIVE_MODEL" \
--arg tier "$TIER" \
--arg quant "$QUANT" \
--arg refreshed_at "$REFRESHED_AT" \
--arg run_id "$RUN_ID" \
'{
_sovereign: "TCS-0002∞ · 国作登字-2026-A-00037559",
_守护: "铸渊 · ICE-GL-ZY001",
endpoint_url: $url,
refreshed_at: $refreshed_at,
refreshed_by_run_id: $run_id,
gpu: { name: $gpu_name, memory_gb: $gpu_mem_gb },
tier: { size_tier: $tier, quantization: $quant },
active_model: $active_model
}' > inference-endpoint.json
echo "组装完成:"
cat inference-endpoint.json
- name: Dry-run · 只打印计划
if: needs.preflight.outputs.effective_dry_run == 'true'
env:
USER: ${{ steps.target.outputs.user }}
PORT: ${{ steps.target.outputs.port }}
DEPLOY_PATH: ${{ steps.target.outputs.deploy_path }}
run: |
echo "═══════════════ DRY RUN ═══════════════"
echo "would scp inference-endpoint.json → $USER@<host>:$DEPLOY_PATH/portal/data/"
echo "would ssh $USER@<host> 'sudo -u $USER mkdir -p $DEPLOY_PATH/portal/data && pm2 reload portal || true'"
echo "(real host masked)"
- name: 真跑 · scp 写入 + pm2 reload
if: needs.preflight.outputs.effective_dry_run != 'true'
env:
USER: ${{ steps.target.outputs.user }}
PORT: ${{ steps.target.outputs.port }}
DEPLOY_PATH: ${{ steps.target.outputs.deploy_path }}
run: |
set -euo pipefail
HOST="$(cat ~/.ssh/refresh_host)"
# 1. 确保目录存在 (PR-4 portal 会用这个路径; PR-4 之前也先建好)
ssh -i ~/.ssh/refresh_key -p "$PORT" \
-o StrictHostKeyChecking=accept-new \
"$USER@$HOST" \
"sudo mkdir -p $DEPLOY_PATH/portal/data && sudo chown -R $USER:$USER $DEPLOY_PATH/portal"
# 2. scp 上去
scp -i ~/.ssh/refresh_key -P "$PORT" \
-o StrictHostKeyChecking=accept-new \
inference-endpoint.json \
"$USER@$HOST:$DEPLOY_PATH/portal/data/inference-endpoint.json"
# 3. pm2 reload portal (PR-4 之前 portal 不存在, 命令失败也算成功)
ssh -i ~/.ssh/refresh_key -p "$PORT" \
-o StrictHostKeyChecking=accept-new \
"$USER@$HOST" \
"if pm2 describe portal >/dev/null 2>&1; then \
pm2 reload portal && echo '✅ pm2 reload portal 成功'; \
else \
echo ' portal 还没起 (PR-4 之前正常), 已写入 endpoint.json 等 portal 上线后自动读'; \
fi"
- name: 中文回执到 GitHub Actions Summary
if: always()
env:
URL: ${{ needs.preflight.outputs.endpoint_url }}
GPU_NAME: ${{ needs.preflight.outputs.gpu_name }}
GPU_MEM_GB: ${{ needs.preflight.outputs.gpu_mem_gb }}
ACTIVE_MODEL: ${{ needs.preflight.outputs.active_model }}
TIER: ${{ needs.preflight.outputs.tier }}
QUANT: ${{ needs.preflight.outputs.quant }}
DRY: ${{ needs.preflight.outputs.effective_dry_run }}
run: |
{
echo "# 🔄 推理端点刷新回执"
echo ""
echo "> 给冰朔 / 霜砚 / Awen 看的中文回执"
echo ""
if [ "$DRY" = "true" ]; then
echo "## ⚠️ DRY RUN (没真写)"
echo ""
echo "误触锁口令没填对, 工作流只打印计划, 没真写 2C2G 域名机."
echo ""
else
echo "## ✅ 已切到新端点"
echo ""
fi
echo "| 项 | 值 |"
echo "|---|---|"
echo "| 推理端点 | \`$URL\` |"
echo "| GPU | $GPU_NAME ($GPU_MEM_GB GB) |"
echo "| 当前模型 | $ACTIVE_MODEL |"
echo "| 档位 | $TIER / $QUANT |"
echo "| 刷新时间 | $(date -u +%Y-%m-%dT%H:%M:%SZ) |"
echo ""
echo "下一步: 冰朔打开 \`https://guanghulab.com/portal\` 试一下, 看模型有没有响应."
echo "如果还断, 看 \`/v1/health\`: \`$URL/v1/health\`"
} >> "$GITHUB_STEP_SUMMARY"
- name: 清理 SSH 私钥
if: always()
run: |
rm -f ~/.ssh/refresh_key ~/.ssh/refresh_host