zhizhi/.github/workflows/deploy-to-zhuyuan-server.yml

751 lines
32 KiB
YAML
Raw 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: 🏛️ 铸渊主权服务器 · 部署
# ═══════════════════════════════════════════════════════════
# 铸渊主权服务器部署工作流
# 编号: ZY-WF-铸体 (新器官 · 服务器部署引擎)
# 守护: 铸渊 · ICE-GL-ZY001
# 版权: 国作登字-2026-A-00037559
# ═══════════════════════════════════════════════════════════
on:
push:
branches: [main]
paths:
- 'server/**'
- 'docs/index.html'
- 'docs/css/**'
- 'docs/js/**'
- 'docs/zh/**'
workflow_dispatch:
inputs:
action:
description: '部署动作'
required: true
default: 'deploy'
type: choice
options:
- deploy
- init
- health-check
- promote
- setup-ssl
deploy_target:
description: '部署目标站点'
required: false
default: 'preview'
type: choice
options:
- preview
- production
ssl_domain:
description: 'SSL域名 (仅setup-ssl时需要: guanghulab.online)'
required: false
type: string
concurrency:
group: zhuyuan-server-deploy
cancel-in-progress: false
permissions:
contents: read
jobs:
# ═══ §1 初始化 (仅首次) ═══
init:
name: 🔧 服务器初始化
if: github.event.inputs.action == 'init'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 🔐 配置SSH
env:
SSH_KEY: ${{ secrets.ZY_SERVER_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/zy_key
chmod 600 ~/.ssh/zy_key
# 验证私钥格式完整性
if head -1 ~/.ssh/zy_key | grep -q "BEGIN" && tail -1 ~/.ssh/zy_key | grep -q "END"; then
echo "✅ SSH私钥格式正确BEGIN/END标记完整"
else
echo "❌ SSH私钥格式异常 — 请检查 GitHub Secrets 中 ZY_SERVER_KEY 的内容"
echo " 应以 '-----BEGIN' 开头,以 '-----END' 结尾"
exit 1
fi
ssh-keyscan -H ${{ secrets.ZY_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: 🔍 SSH连接测试
run: |
echo "🔍 测试SSH连接到 ${{ secrets.ZY_SERVER_HOST }}..."
ssh -i ~/.ssh/zy_key -o BatchMode=yes -o ConnectTimeout=10 \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} \
'echo "✅ SSH连接成功 · $(hostname) · $(date)"' || {
echo ""
echo "═══════════════════════════════════════════════"
echo "❌ SSH连接失败 · 排查指南"
echo "═══════════════════════════════════════════════"
echo ""
echo "可能原因:"
echo " 1. 服务器未配置对应的SSH公钥"
echo " 2. GitHub Secrets中的私钥不完整或格式错误"
echo " 3. SSH用户名不正确"
echo ""
echo "修复步骤(冰朔操作):"
echo " ① 登录腾讯云控制台 → 轻量应用服务器 → 选择新加坡服务器"
echo " ② 点击「远程登录」→ 使用网页终端登录"
echo " ③ 在终端中执行以下命令生成新密钥:"
echo " ssh-keygen -t ed25519 -f ~/.ssh/github_deploy -N ''"
echo " cat ~/.ssh/github_deploy.pub >> ~/.ssh/authorized_keys"
echo " chmod 700 ~/.ssh && chmod 600 ~/.ssh/authorized_keys"
echo " cat ~/.ssh/github_deploy"
echo " ④ 复制输出的私钥内容(从 -----BEGIN 到 -----END 完整复制)"
echo " ⑤ 打开 GitHub → Settings → Secrets → 更新 ZY_SERVER_KEY"
echo "═══════════════════════════════════════════════"
exit 1
}
- name: 📦 上传初始化脚本
run: |
scp -i ~/.ssh/zy_key \
server/setup/zhuyuan-server-init.sh \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/tmp/
- name: 🚀 执行初始化
run: |
ssh -i ~/.ssh/zy_key \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} \
'chmod +x /tmp/zhuyuan-server-init.sh && sudo /tmp/zhuyuan-server-init.sh'
# ═══ §2 部署应用 (默认部署到预览站) ═══
deploy:
name: 🚀 部署应用代码
if: github.event.inputs.action == 'deploy' || github.event.inputs.action == '' || github.event_name == 'push'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 🔐 配置SSH
env:
SSH_KEY: ${{ secrets.ZY_SERVER_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/zy_key
chmod 600 ~/.ssh/zy_key
ssh-keyscan -H ${{ secrets.ZY_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: 📦 同步应用代码
run: |
# 确定部署目标: preview (默认) 或 production
TARGET="${{ github.event.inputs.deploy_target || 'preview' }}"
TARGET_DIR="/opt/zhuyuan/sites/${TARGET}"
echo "📦 部署目标: ${TARGET} → ${TARGET_DIR}"
# 确保目标目录存在
ssh -i ~/.ssh/zy_key \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} \
"mkdir -p ${TARGET_DIR} /opt/zhuyuan/app /opt/age-os/mcp-server /opt/age-os/agents /opt/age-os/logs"
# 同步后端应用代码
rsync -avz --delete \
-e "ssh -i ~/.ssh/zy_key" \
server/app/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/zhuyuan/app/
# 同步 AGE OS MCP Server 代码
rsync -avz --delete \
-e "ssh -i ~/.ssh/zy_key" \
--exclude='node_modules' \
server/age-os/mcp-server/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/age-os/mcp-server/
# 同步 AGE OS Agents 代码(活模块 · S5新增
rsync -avz --delete \
-e "ssh -i ~/.ssh/zy_key" \
server/age-os/agents/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/age-os/agents/
# 同步 AGE OS Schema
rsync -avz \
-e "ssh -i ~/.ssh/zy_key" \
server/age-os/schema/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/age-os/schema/
# 同步 AGE OS PM2 配置
scp -i ~/.ssh/zy_key \
server/age-os/ecosystem.config.js \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/age-os/
# 同步 AGE OS package.json
scp -i ~/.ssh/zy_key \
server/age-os/package.json \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/age-os/
- name: 🌐 同步前端内容到站点
run: |
# Push事件: 直接部署到 production (guanghulab.online = ZY_DOMAIN_MAIN = production)
# 手动触发: 使用选定的目标 (默认 preview)
if [ "${{ github.event_name }}" = "push" ]; then
TARGET="production"
else
TARGET="${{ github.event.inputs.deploy_target || 'preview' }}"
fi
TARGET_DIR="/opt/zhuyuan/sites/${TARGET}"
echo "🌐 同步前端 → ${TARGET_DIR}"
# 确保零点原核频道目录存在
ssh -i ~/.ssh/zy_key \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} \
"mkdir -p /opt/zhuyuan/sites/yaoming /opt/zhuyuan/sites/production /opt/zhuyuan/sites/preview"
# 同步主站门户 (server/sites/portal/) 到目标站点目录
if [ -d "server/sites/portal" ]; then
rsync -avz --delete \
-e "ssh -i ~/.ssh/zy_key" \
server/sites/portal/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:${TARGET_DIR}/
echo "✅ 主站门户已部署到 ${TARGET_DIR}"
# Push事件额外同步到 preview 保持两站一致
if [ "${{ github.event_name }}" = "push" ]; then
rsync -avz --delete \
-e "ssh -i ~/.ssh/zy_key" \
server/sites/portal/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/zhuyuan/sites/preview/
echo "✅ 主站门户已同步到 preview (保持一致)"
fi
else
# 降级: 使用 docs/ 目录
rsync -avz --delete \
-e "ssh -i ~/.ssh/zy_key" \
--exclude='CNAME' \
--exclude='*.md' \
--exclude='.nojekyll' \
--exclude='ontology/' \
--exclude='daily-reports/' \
--exclude='dashboard/' \
--exclude='dev-portal/' \
docs/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:${TARGET_DIR}/
echo "✅ 前端内容(docs/)已部署到 ${TARGET_DIR}"
fi
# 同步零点原核频道 (server/sites/yaoming/) 到 yaoming 目录
if [ -d "server/sites/yaoming" ]; then
rsync -avz --delete \
-e "ssh -i ~/.ssh/zy_key" \
server/sites/yaoming/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/zhuyuan/sites/yaoming/
echo "✅ 零点原核频道已部署到 /opt/zhuyuan/sites/yaoming/"
fi
- name: 📦 同步配置文件
env:
ZY_DOMAIN_MAIN: ${{ secrets.ZY_DOMAIN_MAIN }}
ZY_DOMAIN_PREVIEW: ${{ secrets.ZY_DOMAIN_PREVIEW }}
ZY_FACE_DOMAIN: ${{ secrets.ZY_FACE_DOMAIN }}
run: |
# PM2 配置
scp -i ~/.ssh/zy_key \
server/ecosystem.config.js \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/zhuyuan/config/pm2/
# Nginx 配置 — 主站 · 注入域名
cp server/nginx/zhuyuan-sovereign.conf /tmp/zhuyuan-sovereign.conf
if [ -n "$ZY_DOMAIN_MAIN" ]; then
sed -i "s/ZY_DOMAIN_MAIN_PLACEHOLDER/$ZY_DOMAIN_MAIN/g" /tmp/zhuyuan-sovereign.conf
echo "✅ 主域名已注入: $ZY_DOMAIN_MAIN"
else
echo "⚠️ ZY_DOMAIN_MAIN 未配置使用IP访问"
sed -i "s/ZY_DOMAIN_MAIN_PLACEHOLDER//g" /tmp/zhuyuan-sovereign.conf
fi
if [ -n "$ZY_DOMAIN_PREVIEW" ]; then
sed -i "s/ZY_DOMAIN_PREVIEW_PLACEHOLDER/$ZY_DOMAIN_PREVIEW/g" /tmp/zhuyuan-sovereign.conf
echo "✅ 预览域名已注入: $ZY_DOMAIN_PREVIEW"
else
echo "⚠️ ZY_DOMAIN_PREVIEW 未配置预览站使用默认server_name"
# Nginx中 _ 是标准的catch-all server_name不匹配任何实际域名
sed -i "s/ZY_DOMAIN_PREVIEW_PLACEHOLDER/_/g" /tmp/zhuyuan-sovereign.conf
fi
ZY_BRAIN_HOST="${{ secrets.ZY_BRAIN_HOST }}"
if [ -n "$ZY_BRAIN_HOST" ]; then
sed -i "s/ZY_BRAIN_HOST_PLACEHOLDER/$ZY_BRAIN_HOST/g" /tmp/zhuyuan-sovereign.conf
echo "✅ 大脑服务器已注入V3桥接: $ZY_BRAIN_HOST"
else
echo "⚠️ ZY_BRAIN_HOST 未配置V3桥接不可用"
sed -i '/ZY_BRAIN_HOST_PLACEHOLDER/d' /tmp/zhuyuan-sovereign.conf
fi
scp -i ~/.ssh/zy_key \
/tmp/zhuyuan-sovereign.conf \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/zhuyuan/config/nginx/zhuyuan-sovereign.conf
# Nginx 配置 — 零点原核频道 · 注入域名
if [ -f "server/nginx/guanghuyaoming.conf" ]; then
cp server/nginx/guanghuyaoming.conf /tmp/guanghuyaoming.conf
if [ -n "$ZY_FACE_DOMAIN" ]; then
sed -i "s/ZY_FACE_DOMAIN_PLACEHOLDER/$ZY_FACE_DOMAIN/g" /tmp/guanghuyaoming.conf
echo "✅ 零点原核域名已注入: $ZY_FACE_DOMAIN"
else
echo "⚠️ ZY_FACE_DOMAIN 未配置零点原核频道使用默认server_name"
sed -i "s/ZY_FACE_DOMAIN_PLACEHOLDER/_/g" /tmp/guanghuyaoming.conf
fi
scp -i ~/.ssh/zy_key \
/tmp/guanghuyaoming.conf \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/zhuyuan/config/nginx/guanghuyaoming.conf
echo "✅ 零点原核Nginx配置已上传"
fi
- name: 📦 同步迁移脚本
run: |
# 同步数据库迁移运行器
rsync -avz \
-e "ssh -i ~/.ssh/zy_key" \
server/age-os/scripts/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/age-os/scripts/
- name: 📥 安装依赖 & 重启
env:
ZHUYUAN_API_KEY: ${{ secrets.ZHUYUAN_API_KEY }}
ZY_DB_HOST: ${{ secrets.ZY_DB_HOST }}
ZY_DB_USER: ${{ secrets.ZY_DB_USER }}
ZY_DB_PASS: ${{ secrets.ZY_DB_PASS }}
ZY_DB_NAME: ${{ secrets.ZY_DB_NAME }}
ZY_DB_PORT: ${{ secrets.ZY_DB_PORT }}
ZY_DEEPSEEK_API_KEY: ${{ secrets.ZY_DEEPSEEK_API_KEY }}
ZY_QIANWEN_API_KEY: ${{ secrets.ZY_QIANWEN_API_KEY }}
ZY_KIMI_API_KEY: ${{ secrets.ZY_KIMI_API_KEY }}
ZY_QINGYAN_API_KEY: ${{ secrets.ZY_QINGYAN_API_KEY }}
ZY_LLM_API_KEY: ${{ secrets.ZY_LLM_API_KEY }}
ZY_LLM_BASE_URL: ${{ secrets.ZY_LLM_BASE_URL }}
ZY_OSS_KEY: ${{ secrets.ZY_OSS_KEY }}
ZY_OSS_SECRET: ${{ secrets.ZY_OSS_SECRET }}
ZY_COS_REGION: ${{ secrets.ZY_COS_REGION }}
ZY_NOTION_TOKEN: ${{ secrets.ZY_NOTION_TOKEN }}
ZY_CN_LLM_RELAY_HOST: ${{ secrets.ZY_CN_LLM_RELAY_HOST }}
ZY_CN_LLM_RELAY_KEY: ${{ secrets.ZY_CN_LLM_RELAY_KEY }}
ZY_DOMAIN_MAIN: ${{ secrets.ZY_DOMAIN_MAIN }}
ZY_DOMAIN_PREVIEW: ${{ secrets.ZY_DOMAIN_PREVIEW }}
ZY_FACE_DOMAIN: ${{ secrets.ZY_FACE_DOMAIN }}
run: |
COMMIT_SHA="${{ github.sha }}"
REF_NAME="${{ github.ref_name }}"
ssh -i ~/.ssh/zy_key \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} << DEPLOY_CMD
set -e
cd /opt/zhuyuan/app
# 安装依赖
npm install --production 2>&1
# ─── 写入主应用环境变量配置 ───
# 注意: PORT 和 ZY_SITE_MODE 不写入 .env.app
# 这两个值由 ecosystem.config.js 按实例设置 (主站3800/预览站3801)
# 写入 .env.app 会导致 loadEnvFile() 覆盖预览实例的端口 → 两实例抢端口 → 502
echo "NODE_ENV=production" > /opt/zhuyuan/app/.env.app
echo "ZY_ROOT=/opt/zhuyuan" >> /opt/zhuyuan/app/.env.app
echo "ZY_DEEPSEEK_API_KEY=${ZY_DEEPSEEK_API_KEY}" >> /opt/zhuyuan/app/.env.app
echo "ZY_QIANWEN_API_KEY=${ZY_QIANWEN_API_KEY}" >> /opt/zhuyuan/app/.env.app
echo "ZY_KIMI_API_KEY=${ZY_KIMI_API_KEY}" >> /opt/zhuyuan/app/.env.app
echo "ZY_QINGYAN_API_KEY=${ZY_QINGYAN_API_KEY}" >> /opt/zhuyuan/app/.env.app
echo "ZY_NOTION_TOKEN=${ZY_NOTION_TOKEN}" >> /opt/zhuyuan/app/.env.app
# ─── 通用LLM聊天引擎配置国内模型网关的备用通道 ───
if [ -n "${ZY_LLM_API_KEY}" ]; then
echo "ZY_LLM_API_KEY=${ZY_LLM_API_KEY}" >> /opt/zhuyuan/app/.env.app
echo "✅ 通用LLM API密钥已配置"
fi
if [ -n "${ZY_LLM_BASE_URL}" ]; then
echo "ZY_LLM_BASE_URL=${ZY_LLM_BASE_URL}" >> /opt/zhuyuan/app/.env.app
echo "✅ 通用LLM API地址已配置: ${ZY_LLM_BASE_URL}"
fi
# ─── 广州CN中继配置可选·配置后国内API走广州中继 ───
if [ -n "${ZY_CN_LLM_RELAY_HOST}" ]; then
echo "ZY_CN_LLM_RELAY_HOST=${ZY_CN_LLM_RELAY_HOST}" >> /opt/zhuyuan/app/.env.app
echo "ZY_CN_LLM_RELAY_KEY=${ZY_CN_LLM_RELAY_KEY}" >> /opt/zhuyuan/app/.env.app
echo "ZY_CN_LLM_RELAY_PORT=3900" >> /opt/zhuyuan/app/.env.app
echo "✅ 广州CN中继已配置: ${ZY_CN_LLM_RELAY_HOST}:3900"
else
echo " 广州CN中继未配置·国内API走直连"
fi
chmod 600 /opt/zhuyuan/app/.env.app
# 复制Nginx配置
sudo cp /opt/zhuyuan/config/nginx/zhuyuan-sovereign.conf /etc/nginx/sites-available/zhuyuan.conf
sudo ln -sf /etc/nginx/sites-available/zhuyuan.conf /etc/nginx/sites-enabled/zhuyuan.conf
# 零点原核频道Nginx配置
if [ -f "/opt/zhuyuan/config/nginx/guanghuyaoming.conf" ]; then
sudo cp /opt/zhuyuan/config/nginx/guanghuyaoming.conf /etc/nginx/sites-available/guanghuyaoming.conf
sudo ln -sf /etc/nginx/sites-available/guanghuyaoming.conf /etc/nginx/sites-enabled/guanghuyaoming.conf
echo "✅ 零点原核Nginx配置已启用"
fi
# 移除默认配置文件,避免 duplicate default_server 冲突
# zhuyuan.conf 已声明 listen 80 default_server不能与 default 文件共存
sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginx
# PM2 重启 · 主应用 + 预览进程 (两个都要清理·否则残留errored进程导致502)
pm2 delete zhuyuan-server 2>/dev/null || true
pm2 delete zhuyuan-preview 2>/dev/null || true
pm2 start /opt/zhuyuan/config/pm2/ecosystem.config.js
pm2 save
# ─── AGE OS MCP Server 部署 ───
cd /opt/age-os
npm install --production 2>&1
# 写入 MCP Server 环境变量配置
echo "NODE_ENV=production" > /opt/age-os/.env.mcp
echo "MCP_PORT=3100" >> /opt/age-os/.env.mcp
echo "MCP_BIND_HOST=127.0.0.1" >> /opt/age-os/.env.mcp
# 注入密钥(从 GitHub Secrets
echo "ZHUYUAN_API_KEY=${ZHUYUAN_API_KEY}" >> /opt/age-os/.env.mcp
echo "ZY_DB_HOST=${ZY_DB_HOST:-127.0.0.1}" >> /opt/age-os/.env.mcp
echo "ZY_DB_USER=${ZY_DB_USER:-zy_admin}" >> /opt/age-os/.env.mcp
echo "ZY_DB_PASS=${ZY_DB_PASS}" >> /opt/age-os/.env.mcp
echo "ZY_DB_NAME=${ZY_DB_NAME:-age_os}" >> /opt/age-os/.env.mcp
echo "ZY_DB_PORT=${ZY_DB_PORT:-5432}" >> /opt/age-os/.env.mcp
echo "ZY_DEEPSEEK_API_KEY=${ZY_DEEPSEEK_API_KEY}" >> /opt/age-os/.env.mcp
echo "ZY_QIANWEN_API_KEY=${ZY_QIANWEN_API_KEY}" >> /opt/age-os/.env.mcp
echo "ZY_KIMI_API_KEY=${ZY_KIMI_API_KEY}" >> /opt/age-os/.env.mcp
echo "ZY_QINGYAN_API_KEY=${ZY_QINGYAN_API_KEY}" >> /opt/age-os/.env.mcp
echo "ZY_OSS_KEY=${ZY_OSS_KEY}" >> /opt/age-os/.env.mcp
echo "ZY_OSS_SECRET=${ZY_OSS_SECRET}" >> /opt/age-os/.env.mcp
echo "ZY_COS_REGION=${ZY_COS_REGION:-ap-guangzhou}" >> /opt/age-os/.env.mcp
echo "ZY_NOTION_TOKEN=${ZY_NOTION_TOKEN}" >> /opt/age-os/.env.mcp
# ─── 通用LLM引擎密钥MCP Server也可能需要 ───
if [ -n "${ZY_LLM_API_KEY}" ]; then
echo "ZY_LLM_API_KEY=${ZY_LLM_API_KEY}" >> /opt/age-os/.env.mcp
fi
if [ -n "${ZY_LLM_BASE_URL}" ]; then
echo "ZY_LLM_BASE_URL=${ZY_LLM_BASE_URL}" >> /opt/age-os/.env.mcp
fi
chmod 600 /opt/age-os/.env.mcp
# ─── 自动执行数据库迁移 ───
echo ""
echo "═══ 数据库迁移 ═══"
cd /opt/age-os
# 加载环境变量供迁移脚本使用
export ZY_DB_HOST="${ZY_DB_HOST:-127.0.0.1}"
export ZY_DB_USER="${ZY_DB_USER:-zy_admin}"
export ZY_DB_PASS="${ZY_DB_PASS}"
export ZY_DB_NAME="${ZY_DB_NAME:-age_os}"
export ZY_DB_PORT="${ZY_DB_PORT:-5432}"
node scripts/db-migrate.js || echo "⚠️ 数据库迁移有警告(非致命·应用仍会启动)"
echo "═══════════════════"
echo ""
# PM2 重启 MCP Server读取 .env.mcp
pm2 delete age-os-mcp 2>/dev/null || true
pm2 delete age-os-agents 2>/dev/null || true
cd /opt/age-os && pm2 start ecosystem.config.js
pm2 save
# 健康检查 · 主应用(3800) + 预览进程(3801) + MCP(3100)
sleep 3
curl -sf http://localhost:3800/api/health || echo "⚠️ 主应用健康检查失败,等待更长时间..."
curl -sf http://localhost:3801/api/health || echo "⚠️ 预览进程健康检查失败,等待更长时间..."
curl -sf http://localhost:3100/health || echo "⚠️ MCP Server健康检查失败等待更长时间..."
sleep 5
curl -sf http://localhost:3800/api/health && echo "✅ 主应用已上线 (3800)" || echo "❌ 主应用启动失败 (3800)"
curl -sf http://localhost:3801/api/health && echo "✅ 预览进程已上线 (3801)" || echo "❌ 预览进程启动失败 (3801) · 若 ZY_DOMAIN_PREVIEW 域名指向此端口则该域名可能502"
curl -sf http://localhost:3100/health && echo "✅ MCP Server已上线 (3100)" || echo "❌ MCP Server启动失败 (3100)"
# DNS 诊断 · 检查域名是否指向本服务器
echo ""
echo "═══ DNS 诊断 ═══"
SERVER_IP="43.134.16.246"
for DOMAIN in "${ZY_DOMAIN_MAIN}" "${ZY_DOMAIN_PREVIEW}" "${ZY_FACE_DOMAIN}"; do
if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "_" ]; then
RESOLVED=$(dig +short "$DOMAIN" A 2>/dev/null | head -1)
if [ -z "$RESOLVED" ]; then
echo "❌ DNS 未解析: $DOMAIN (无A记录·网站不可达)"
elif [ "$RESOLVED" = "$SERVER_IP" ]; then
echo "✅ DNS 正确: $DOMAIN → $RESOLVED"
else
echo "⚠️ DNS 错误: $DOMAIN → $RESOLVED (应为 $SERVER_IP)"
fi
fi
done
echo "═══════════════════"
# LLM API 密钥诊断 · 检查聊天功能所需密钥
echo ""
echo "═══ 聊天API密钥诊断 ═══"
LLM_KEY_COUNT=0
for KEY_NAME in ZY_DEEPSEEK_API_KEY ZY_QIANWEN_API_KEY ZY_KIMI_API_KEY ZY_QINGYAN_API_KEY; do
if grep -q "${KEY_NAME}=.\{6,\}" /opt/zhuyuan/app/.env.app 2>/dev/null; then
echo "✅ ${KEY_NAME}: 已配置"
LLM_KEY_COUNT=$((LLM_KEY_COUNT + 1))
else
echo "❌ ${KEY_NAME}: 未配置或为空 · 人格体对话功能受影响"
fi
done
echo "可用国内模型数: ${LLM_KEY_COUNT}/4"
if [ "$LLM_KEY_COUNT" -eq 0 ]; then
echo "⚠️ 警告: 所有国内模型API密钥未配置 · 聊天功能将不可用"
fi
# 检查通用LLM聊天引擎备用通道
if grep -q "ZY_LLM_API_KEY=.\{6,\}" /opt/zhuyuan/app/.env.app 2>/dev/null; then
echo "✅ ZY_LLM_API_KEY: 已配置 (通用聊天引擎备用通道)"
else
if [ "$LLM_KEY_COUNT" -eq 0 ]; then
echo "❌ ZY_LLM_API_KEY: 未配置 · 国内模型+通用引擎均不可用 · 聊天完全离线"
else
echo " ZY_LLM_API_KEY: 未配置 · 国内模型可用则不影响"
fi
fi
echo "═══════════════════"
# Nginx 配置诊断 · 检查 server_name 冲突
echo ""
echo "═══ Nginx 诊断 ═══"
CONFLICT_COUNT=$(sudo nginx -T 2>&1 | grep -c "conflicting server name" || true)
echo "Nginx server_name 冲突数: $CONFLICT_COUNT"
echo "当前启用的站点配置:"
ls -la /etc/nginx/sites-enabled/ 2>/dev/null || echo "(无 sites-enabled 目录)"
echo "═══════════════════"
# 记录部署操作
curl -sf -X POST http://localhost:3800/api/operations \
-H "Content-Type: application/json" \
-d "{
\"operator\": \"铸渊 · GitHub Actions\",
\"action\": \"自动部署\",
\"details\": \"commit: ${COMMIT_SHA}, branch: ${REF_NAME}\"
}" || true
DEPLOY_CMD
# ═══ §3 健康检查 ═══
health-check:
name: 💚 健康检查
if: github.event.inputs.action == 'health-check'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 🔐 配置SSH
env:
SSH_KEY: ${{ secrets.ZY_SERVER_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/zy_key
chmod 600 ~/.ssh/zy_key
ssh-keyscan -H ${{ secrets.ZY_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: 💚 执行健康检查
env:
ZY_DOMAIN_MAIN: ${{ secrets.ZY_DOMAIN_MAIN }}
ZY_DOMAIN_PREVIEW: ${{ secrets.ZY_DOMAIN_PREVIEW }}
ZY_FACE_DOMAIN: ${{ secrets.ZY_FACE_DOMAIN }}
run: |
ssh -i ~/.ssh/zy_key \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} << 'HEALTH_CMD'
echo "═══ 铸渊主权服务器健康报告 ═══"
echo ""
echo "§1 系统状态:"
uptime
echo ""
echo "§2 磁盘使用:"
df -h /
echo ""
echo "§3 内存使用:"
free -m
echo ""
echo "§4 PM2 进程:"
pm2 list 2>/dev/null || echo "PM2 未运行"
echo ""
echo "§5 Nginx 状态:"
systemctl is-active nginx && echo "Nginx: 运行中" || echo "Nginx: 已停止"
echo "Nginx server_name 冲突检测:"
sudo nginx -T 2>&1 | grep "conflicting server name" || echo " 无冲突 ✅"
echo "启用的站点配置:"
ls /etc/nginx/sites-enabled/ 2>/dev/null || echo " (无 sites-enabled 目录)"
echo ""
echo "§6 应用健康:"
curl -sf http://localhost:3800/api/health | jq . 2>/dev/null || echo "应用未响应"
echo ""
echo "§7 大脑状态:"
curl -sf http://localhost:3800/api/brain | jq '.files_present, .files_total' 2>/dev/null || echo "大脑API未响应"
echo ""
echo "§8 双站点状态:"
curl -sf http://localhost:3800/api/sites | jq . 2>/dev/null || echo "站点API未响应"
HEALTH_CMD
- name: 🌐 DNS 域名解析检查
env:
ZY_DOMAIN_MAIN: ${{ secrets.ZY_DOMAIN_MAIN }}
ZY_DOMAIN_PREVIEW: ${{ secrets.ZY_DOMAIN_PREVIEW }}
ZY_FACE_DOMAIN: ${{ secrets.ZY_FACE_DOMAIN }}
run: |
echo "═══ DNS 域名解析诊断 ═══"
SERVER_IP="43.134.16.246"
for DOMAIN in "$ZY_DOMAIN_MAIN" "$ZY_DOMAIN_PREVIEW" "$ZY_FACE_DOMAIN"; do
if [ -n "$DOMAIN" ] && [ "$DOMAIN" != "_" ]; then
RESOLVED=$(dig +short "$DOMAIN" A 2>/dev/null | head -1)
if [ -z "$RESOLVED" ]; then
echo "❌ $DOMAIN → 无法解析 (无A记录·网站不可达)"
elif [ "$RESOLVED" = "$SERVER_IP" ]; then
echo "✅ $DOMAIN → $RESOLVED"
else
echo "⚠️ $DOMAIN → $RESOLVED (应为 $SERVER_IP · DNS配置错误!)"
fi
fi
done
echo "═══════════════════"
# ═══ §4 一键推送: 预览站 → 主站 ═══
promote:
name: 🚀 预览站→主站 一键推送
if: github.event.inputs.action == 'promote'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 🔐 配置SSH
env:
SSH_KEY: ${{ secrets.ZY_SERVER_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/zy_key
chmod 600 ~/.ssh/zy_key
ssh-keyscan -H ${{ secrets.ZY_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: 🚀 执行推送
run: |
echo "🚀 一键推送: 预览站 → 主站"
RUN_ID="${{ github.run_id }}"
ssh -i ~/.ssh/zy_key \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} << PROMOTE_CMD
# 调用铸渊 API 执行推送
RESULT=\$(curl -sf -X POST http://localhost:3800/api/sites/promote \
-H "Content-Type: application/json" \
-d "{
\"operator\": \"冰朔 · 通过 GitHub Actions 授权推送\",
\"note\": \"workflow_dispatch promote · run_id: ${RUN_ID}\"
}" 2>/dev/null)
if [ \$? -eq 0 ]; then
echo "\$RESULT" | jq . 2>/dev/null || echo "\$RESULT"
echo ""
echo "✅ 预览站已推送到主站"
else
echo "❌ 推送失败"
echo "尝试手动执行..."
if [ -d /opt/zhuyuan/sites/preview ] && [ -f /opt/zhuyuan/sites/preview/index.html ]; then
rsync -a --delete /opt/zhuyuan/sites/preview/ /opt/zhuyuan/sites/production/
echo "✅ 手动推送完成"
else
echo "❌ 预览站无内容,无法推送"
exit 1
fi
fi
PROMOTE_CMD
- name: 💚 推送后验证
run: |
ssh -i ~/.ssh/zy_key \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} << 'VERIFY_CMD'
echo "═══ 推送后验证 ═══"
echo ""
echo "主站内容:"
ls -la /opt/zhuyuan/sites/production/ | head -10
echo ""
echo "站点状态:"
curl -sf http://localhost:3800/api/sites | jq . 2>/dev/null || echo "API未响应"
VERIFY_CMD
# ═══ §5 SSL证书配置 ═══
setup-ssl:
name: 🔒 SSL证书配置
if: github.event.inputs.action == 'setup-ssl'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 🔐 配置SSH
env:
SSH_KEY: ${{ secrets.ZY_SERVER_KEY }}
run: |
mkdir -p ~/.ssh
echo "$SSH_KEY" > ~/.ssh/zy_key
chmod 600 ~/.ssh/zy_key
if head -1 ~/.ssh/zy_key | grep -q "BEGIN" && tail -1 ~/.ssh/zy_key | grep -q "END"; then
echo "✅ SSH私钥格式正确"
else
echo "❌ SSH私钥格式异常"
exit 1
fi
ssh-keyscan -H ${{ secrets.ZY_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
- name: 🔍 SSH连接测试
run: |
ssh -i ~/.ssh/zy_key -o BatchMode=yes -o ConnectTimeout=10 \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} \
'echo "✅ SSH连接成功 · $(hostname)"'
- name: 📦 上传SSL配置脚本
run: |
scp -i ~/.ssh/zy_key \
server/setup/setup-ssl.sh \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/tmp/setup-ssl.sh
- name: 🔒 执行SSL配置
run: |
SSL_DOMAIN="${{ github.event.inputs.ssl_domain }}"
ZY_DOMAIN_PREVIEW="${{ secrets.ZY_DOMAIN_PREVIEW }}"
# 如果未指定域名,使用预览域名
if [ -z "$SSL_DOMAIN" ]; then
SSL_DOMAIN="$ZY_DOMAIN_PREVIEW"
fi
if [ -z "$SSL_DOMAIN" ]; then
echo "❌ 未指定SSL域名"
echo ""
echo "请在触发工作流时填写 ssl_domain 参数"
echo "例如: guanghulab.online"
exit 1
fi
echo "🔒 配置SSL: $SSL_DOMAIN"
echo ""
ssh -i ~/.ssh/zy_key \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} \
"chmod +x /tmp/setup-ssl.sh && sudo bash /tmp/setup-ssl.sh $SSL_DOMAIN"
- name: 💚 SSL验证
run: |
SSL_DOMAIN="${{ github.event.inputs.ssl_domain || secrets.ZY_DOMAIN_PREVIEW }}"
if [ -n "$SSL_DOMAIN" ]; then
echo "🔍 验证HTTPS: https://$SSL_DOMAIN"
sleep 3
HTTP_CODE=$(curl -sf -o /dev/null -w "%{http_code}" "https://${SSL_DOMAIN}/" 2>/dev/null || echo "000")
echo "HTTPS状态码: $HTTP_CODE"
if [ "$HTTP_CODE" != "000" ]; then
echo "✅ HTTPS可访问"
else
echo "⚠️ HTTPS暂时不可访问 (可能需要等待DNS传播)"
fi
fi