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: | TARGET="${{ github.event.inputs.deploy_target || 'preview' }}" TARGET_DIR="/opt/zhuyuan/sites/${TARGET}" echo "🌐 同步前端 → ${TARGET_DIR}" # 同步 docs/ 目录下的前端文件到站点目录 # 排除不需要的文件(markdown文档、CNAME等) 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 "✅ 前端内容已部署到 ${TARGET_DIR}" - name: 📦 同步配置文件 env: ZY_DOMAIN_MAIN: ${{ secrets.ZY_DOMAIN_MAIN }} ZY_DOMAIN_PREVIEW: ${{ secrets.ZY_DOMAIN_PREVIEW }} 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 - 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 }} 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 # 复制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 # 移除默认配置文件,避免 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 重启 · 主应用 pm2 delete zhuyuan-server 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 环境变量配置 cat > /opt/age-os/.env.mcp << 'ENV_EOF' NODE_ENV=production MCP_PORT=3100 MCP_BIND_HOST=127.0.0.1 ENV_EOF # 注入密钥(从 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 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 # 健康检查 sleep 3 curl -sf http://localhost:3800/api/health || echo "⚠️ 主应用健康检查失败,等待更长时间..." curl -sf http://localhost:3100/health || echo "⚠️ MCP Server健康检查失败,等待更长时间..." sleep 5 curl -sf http://localhost:3800/api/health && echo "✅ 主应用已上线" || echo "❌ 主应用启动失败" curl -sf http://localhost:3100/health && echo "✅ MCP Server已上线" || echo "❌ MCP Server启动失败" # 记录部署操作 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: 💚 执行健康检查 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 "" 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 # ═══ §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