zhizhi/.github/workflows/deploy-to-zhuyuan-server.yml
Workflow config file is invalid. Please check your config file: yaml: line 280: could not find expected ':'

515 lines
20 KiB
YAML
Raw 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: 🏛️ 铸渊主权服务器 · 部署
# ═══════════════════════════════════════════════════════════
# 铸渊主权服务器部署工作流
# 编号: 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/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 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: 📥 安装依赖 & 重启
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
# PM2 重启 MCP Server读取 .env.mcp
pm2 delete age-os-mcp 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