guanghulab/.github/workflows/deploy-ftchat-guanghu-online.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

267 lines
11 KiB
YAML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

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: '✦ FTCHAT 部署 · guanghu.online (微调模型内测)'
# ═══════════════════════════════════════════════════════════
# 光湖 · 微调模型团队内测站 部署工作流
# 编号: ZY-WF-FTCHAT
# 守护: 铸渊 · ICE-GL-ZY001
# 版权: 国作登字-2026-A-00037559
#
# 目标服务器: ZY-SVR-006 (43.153.203.105 · 新加坡)
# 域名: guanghu.online
# 部署路径: /opt/guanghu/ftchat
# 后端端口: 3010 (内部, Nginx 反代)
# 进程名: ftchat-api (PM2)
#
# 复用 Secrets (与 novel-mirror-shield 同服务器):
# ZY_NOVEL_HOST — 43.153.203.105
# ZY_NOVEL_USER — root
# ZY_NOVEL_KEY — SSH 私钥
#
# 新增 Secrets:
# FT_NOTION_API_TOKEN — Notion Internal Integration token
# FT_NOTION_PROMPT_PAGE_ID — Notion 系统提示词页面 ID
# FT_DASHSCOPE_API_KEY — 阿里云百炼 API Key (区分商业模型)
# ZY_SMTP_USER / ZY_SMTP_PASS — QQ 邮箱 SMTP (验证码发件)
# ═══════════════════════════════════════════════════════════
on:
push:
branches: [main]
paths:
- 'src/**'
- 'server/ftchat/**'
- 'server/nginx/ftchat-guanghu-online.conf'
- 'frontend/ftchat/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/deploy-ftchat-guanghu-online.yml'
workflow_dispatch:
inputs:
action:
description: '执行动作'
required: true
default: 'deploy'
type: choice
options:
- deploy
- retire-zhiku
- health
- rollback
jobs:
deploy:
name: '🚀 部署 FTCHAT'
runs-on: ubuntu-latest
permissions:
contents: read
if: |
github.event_name == 'push' ||
(github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'deploy')
steps:
- name: '📥 检出代码'
uses: actions/checkout@v4
- name: '🔑 配置 SSH'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ZY_NOVEL_KEY }}" > ~/.ssh/ftchat_key
chmod 600 ~/.ssh/ftchat_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: '📦 服务器准备目录'
run: |
ssh -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
mkdir -p /opt/guanghu/ftchat/{src,server/ftchat,scripts,data,public}
mkdir -p /var/log/ftchat
# 数据目录权限
chmod 700 /opt/guanghu/ftchat/data
echo "✓ 目录就绪"
ENDSSH
- name: '📤 上传后端代码 (src/ + server/ftchat/)'
run: |
# 排除 node_modules / data / public 等
tar --exclude='node_modules' --exclude='.git' --exclude='data' \
-czf /tmp/ftchat-backend.tar.gz \
src/ server/ftchat/services server/ftchat/middleware \
server/ftchat/ecosystem.config.js \
package.json package-lock.json \
scripts/hli-contract-check.js
scp -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
/tmp/ftchat-backend.tar.gz \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/guanghu/ftchat/_payload.tar.gz
ssh -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} \
'cd /opt/guanghu/ftchat && tar -xzf _payload.tar.gz && rm -f _payload.tar.gz && echo "✓ 后端代码已解压"'
- name: '🏷️ 注入前端构建版本号 (cache-busting)'
run: |
BUILD_ID="${GITHUB_SHA::7}-$(date -u +%Y%m%d%H%M)"
echo "BUILD_ID=${BUILD_ID}"
# 替换 index.html 中的 __BUILD_ID__ 占位 → 触发用户端 CSS/JS 强制刷新
sed -i "s/__BUILD_ID__/${BUILD_ID}/g" frontend/ftchat/index.html
grep -E 'style\.css|chat\.js|theme-toggle\.js' frontend/ftchat/index.html || true
- name: '📤 上传前端静态文件'
run: |
scp -r -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
frontend/ftchat/* \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/guanghu/ftchat/public/
ssh -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} \
'echo "✓ 前端 $(ls /opt/guanghu/ftchat/public/ | wc -l) 个文件"'
- name: '⚙️ 生成服务器 .env (在 runner 上拼接, 避免 heredoc 暴露 secrets)'
env:
NOTION_TOKEN: ${{ secrets.FT_NOTION_API_TOKEN }}
NOTION_PAGE: ${{ secrets.FT_NOTION_PROMPT_PAGE_ID }}
DASHSCOPE_KEY: ${{ secrets.FT_DASHSCOPE_API_KEY }}
# 微调模型 ID 必须填 DashScope 控制台真实 ID (例: ft:qwen-...:xxxxx).
# 没配则保持空, ft-dashscope.js 默认值会被命中后由 model_not_found 自动降级到 FT_MODEL_FALLBACK (qwen-turbo).
MODEL_SYSTEM: ${{ secrets.FT_MODEL_SYSTEM }}
MODEL_NAIPPING: ${{ secrets.FT_MODEL_NAIPPING }}
MODEL_FALLBACK: ${{ secrets.FT_MODEL_FALLBACK }}
SMTP_USER: ${{ secrets.ZY_SMTP_USER }}
SMTP_PASS: ${{ secrets.ZY_SMTP_PASS }}
run: |
cat > /tmp/ftchat.env << EOF
NODE_ENV=production
PORT=3010
FTCHAT_DATA_DIR=/opt/guanghu/ftchat/data
FT_NOTION_API_TOKEN=${NOTION_TOKEN}
FT_NOTION_PROMPT_PAGE_ID=${NOTION_PAGE}
FT_DASHSCOPE_API_KEY=${DASHSCOPE_KEY}
FT_MODEL_SYSTEM=${MODEL_SYSTEM:-qwen3-8b-ft-202604281809-9f30}
FT_MODEL_NAIPPING=${MODEL_NAIPPING:-qwen3-8b-ft-202604281809-9f30}
FT_MODEL_FALLBACK=${MODEL_FALLBACK:-qwen-turbo}
ZY_SMTP_USER=${SMTP_USER}
ZY_SMTP_PASS=${SMTP_PASS}
EOF
scp -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
/tmp/ftchat.env \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/guanghu/ftchat/.env
ssh -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} \
'chmod 600 /opt/guanghu/ftchat/.env'
rm -f /tmp/ftchat.env
- name: '📦 安装依赖'
run: |
ssh -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
cd /opt/guanghu/ftchat
# Node 与 PM2 假定已在服务器上 (与 novel-api 复用)
npm ci --production --silent || npm install --production --silent
# 契约自检 (确保上传的 schema 与代码一致)
if [ -f scripts/hli-contract-check.js ]; then
node scripts/hli-contract-check.js || echo "⚠ 契约自检发现问题, 继续部署"
fi
echo "✓ 依赖安装完成"
ENDSSH
- name: '🚀 启动/重启 PM2'
run: |
ssh -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
cd /opt/guanghu/ftchat
# 加载 .env 到当前 shell, PM2 reload --update-env 会继承
set -a
# shellcheck disable=SC1091
. ./.env
set +a
pm2 describe ftchat-api > /dev/null 2>&1 \
&& pm2 reload server/ftchat/ecosystem.config.js --update-env \
|| pm2 start server/ftchat/ecosystem.config.js
pm2 save
pm2 startup systemd -u root --hp /root 2>/dev/null || true
systemctl enable pm2-root 2>/dev/null || true
echo "=== PM2 状态 ==="
pm2 list
ENDSSH
- name: '🌐 部署 Nginx 配置'
run: |
scp -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
server/nginx/ftchat-guanghu-online.conf \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/etc/nginx/sites-available/ftchat-guanghu-online.conf
ssh -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
# 备份并禁用旧的 zhiku 站点 (域名复用)
if [ -L /etc/nginx/sites-enabled/zhiku ] || [ -L /etc/nginx/sites-enabled/zhiku-guanghu-online.conf ]; then
rm -f /etc/nginx/sites-enabled/zhiku /etc/nginx/sites-enabled/zhiku-guanghu-online.conf
echo "✓ 已禁用旧 zhiku 站点"
fi
ln -sf /etc/nginx/sites-available/ftchat-guanghu-online.conf \
/etc/nginx/sites-enabled/ftchat-guanghu-online.conf
nginx -t
systemctl reload nginx
echo "✓ Nginx 已重载"
ENDSSH
- name: '✅ 健康检查'
run: |
sleep 3
ssh -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
echo "── /health (本地直连 3010) ──"
curl -fsS http://127.0.0.1:3010/health || (echo '后端未就绪' && exit 1)
echo
echo "── /hli/ftchat/status (本地) ──"
curl -fsS http://127.0.0.1:3010/hli/ftchat/status
echo
echo "── /hli/ftchat/status (公网 HTTPS) ──"
curl -fsS https://guanghu.online/hli/ftchat/status || echo '⚠ 公网检查失败 (可能 DNS/SSL 还没就绪)'
echo
echo "✓ 健康检查通过"
ENDSSH
retire-zhiku:
name: '🗄️ 下架旧 zhiku 站点'
runs-on: ubuntu-latest
permissions:
contents: read
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'retire-zhiku'
steps:
- name: '📥 检出代码'
uses: actions/checkout@v4
- name: '🔑 配置 SSH'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ZY_NOVEL_KEY }}" > ~/.ssh/ftchat_key
chmod 600 ~/.ssh/ftchat_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: '🛑 停止 zhiku-api + 禁用 nginx 站点'
run: |
ssh -i ~/.ssh/ftchat_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set +e
pm2 describe zhiku-api > /dev/null 2>&1 && {
pm2 stop zhiku-api
pm2 delete zhiku-api
pm2 save
echo "✓ zhiku-api PM2 进程已停止"
} || echo " zhiku-api 不在 PM2 中"
# 禁用 nginx
rm -f /etc/nginx/sites-enabled/zhiku /etc/nginx/sites-enabled/zhiku-guanghu-online.conf
nginx -t && systemctl reload nginx && echo "✓ Nginx 已重载"
ENDSSH
health:
name: '🩺 健康检查'
runs-on: ubuntu-latest
permissions:
contents: read
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'health'
steps:
- run: |
curl -fsS https://guanghu.online/health
echo
curl -fsS https://guanghu.online/hli/ftchat/status