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