guanghulab/.github/workflows/deploy-to-zhuyuan-server.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

952 lines
44 KiB
YAML
Raw Permalink 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/**'
- 'glada/**'
- 'ops-agent/**'
- '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/schema /opt/age-os/scripts /opt/age-os/logs /opt/glada /opt/zhuyuan/ops-agent /opt/zhuyuan/ops-agent/data /opt/zhuyuan/data/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/
# 同步 GLADA 自主开发Agent 代码
rsync -avz --delete \
-e "ssh -i ~/.ssh/zy_key" \
--exclude='node_modules' \
--exclude='queue/*.json' \
--exclude='logs/' \
--exclude='receipts/' \
--exclude='.env.glada' \
glada/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/glada/
# 同步 OPS Agent 运维守卫代码
rsync -avz --delete \
-e "ssh -i ~/.ssh/zy_key" \
--exclude='node_modules' \
--exclude='data/' \
--exclude='.env.ops-agent' \
ops-agent/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/zhuyuan/ops-agent/
- name: 🔄 同步 docs → portal (单一数据源保障)
run: |
# docs/index.html 是 GitHub Pages 的数据源
# server/sites/portal/ 是服务器部署的数据源
# 自动同步确保两者一致,防止修改 docs/ 后服务器未更新
if [ -f "docs/index.html" ] && [ -d "server/sites/portal" ]; then
cp docs/index.html server/sites/portal/index.html
echo "✅ docs/index.html → server/sites/portal/index.html 已同步"
fi
- 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
# 同步智库节点 novel-db (server/novel-db/) 到服务器
# 与主站同服务器部署 · PM2 管理 · 端口 4000
if [ -d "server/novel-db" ]; then
ssh -i ~/.ssh/zy_key \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }} \
"mkdir -p /opt/zhuyuan/novel-db/app /opt/zhuyuan/novel-db/data/books /opt/zhuyuan/novel-db/data/chapters"
rsync -avz \
-e "ssh -i ~/.ssh/zy_key" \
--exclude='node_modules' \
--exclude='.env' \
server/novel-db/ \
${{ secrets.ZY_SERVER_USER }}@${{ secrets.ZY_SERVER_HOST }}:/opt/zhuyuan/novel-db/
echo "✅ 智库节点 novel-db 已同步到 /opt/zhuyuan/novel-db/"
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_SMTP_USER: ${{ secrets.ZY_SMTP_USER }}
ZY_SMTP_PASS: ${{ secrets.ZY_SMTP_PASS }}
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
#
# ─── SMTP 备份:在清空 .env.app 之前先备份已有 SMTP 行 ───
# 注意: 此命令在服务器上执行(不含 $ 变量展开),正确地从服务器读取现有 SMTP 凭据
# 若 GitHub Secret 未配置,后续会从此备份中恢复,避免每次部署把已配置的 SMTP 清空
grep "^ZY_SMTP" /opt/zhuyuan/app/.env.app > /tmp/.zy_smtp_bak 2>/dev/null || true
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
# ─── QQ邮箱SMTP配置零点原核频道邮箱验证码登录 ───
# 优先使用 CI/CD 注入的 SecretSecret 未配置时从部署前备份中恢复
# 修复说明: 原来用 $(grep ...) 读取服务器文件,但 unquoted heredoc 中命令替换在本地
# runner 执行,/opt/zhuyuan/app/.env.app 本地不存在,导致 PREV_SMTP 始终为空
# 现在改为:清空前先 grep 备份(服务器端执行),清空后从备份恢复
if [ -n "${ZY_SMTP_USER}" ] && [ -n "${ZY_SMTP_PASS}" ]; then
echo "ZY_SMTP_USER=${ZY_SMTP_USER}" >> /opt/zhuyuan/app/.env.app
echo "ZY_SMTP_PASS=${ZY_SMTP_PASS}" >> /opt/zhuyuan/app/.env.app
echo "✅ QQ邮箱SMTP凭据已配置 (来自 GitHub Secret)"
rm -f /tmp/.zy_smtp_bak
elif [ -s /tmp/.zy_smtp_bak ]; then
cat /tmp/.zy_smtp_bak >> /opt/zhuyuan/app/.env.app
rm -f /tmp/.zy_smtp_bak
echo "✅ QQ邮箱SMTP凭据已恢复 (来自服务器现有值·Secret未设置)"
else
echo "⚠️ ZY_SMTP_USER/ZY_SMTP_PASS未配置·邮箱验证码功能不可用·请在 GitHub Settings → Secrets 中设置"
fi
# ─── 通用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
# ─── 服务器区域标识Phase A3·决定中继路由策略 ───
echo "ZY_SERVER_REGION=sg" >> /opt/zhuyuan/app/.env.app
# ─── 数据库连接配置Phase A2·记忆桥接需要 ───
echo "ZY_DB_HOST=${ZY_DB_HOST:-127.0.0.1}" >> /opt/zhuyuan/app/.env.app
echo "ZY_DB_USER=${ZY_DB_USER:-zy_admin}" >> /opt/zhuyuan/app/.env.app
echo "ZY_DB_PASS=${ZY_DB_PASS}" >> /opt/zhuyuan/app/.env.app
echo "ZY_DB_NAME=${ZY_DB_NAME:-age_os}" >> /opt/zhuyuan/app/.env.app
echo "ZY_DB_PORT=${ZY_DB_PORT:-5432}" >> /opt/zhuyuan/app/.env.app
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 delete novel-api 2>/dev/null || true
# ─── 智库节点 novel-db 依赖安装 + 环境变量 ───
if [ -d "/opt/zhuyuan/novel-db/app" ]; then
cd /opt/zhuyuan/novel-db/app
npm install --production
# 写入 novel-db 环境变量(复用主站 LLM API 密钥)
echo "NODE_ENV=production" > /opt/zhuyuan/novel-db/.env
echo "PORT=4000" >> /opt/zhuyuan/novel-db/.env
echo "BOOKS_DIR=/opt/zhuyuan/novel-db/data/books" >> /opt/zhuyuan/novel-db/.env
echo "CHAPTERS_DIR=/opt/zhuyuan/novel-db/data/chapters" >> /opt/zhuyuan/novel-db/.env
# AI 模型密钥(智库 Agent 对话 + AI 辅助写作)
echo "DEEPSEEK_API_KEY=${ZY_DEEPSEEK_API_KEY}" >> /opt/zhuyuan/novel-db/.env
echo "ZY_DEEPSEEK_API_KEY=${ZY_DEEPSEEK_API_KEY}" >> /opt/zhuyuan/novel-db/.env
echo "KIMI_API_KEY=${ZY_KIMI_API_KEY}" >> /opt/zhuyuan/novel-db/.env
echo "ZY_KIMI_API_KEY=${ZY_KIMI_API_KEY}" >> /opt/zhuyuan/novel-db/.env
echo "DASHSCOPE_API_KEY=${ZY_QIANWEN_API_KEY}" >> /opt/zhuyuan/novel-db/.env
echo "ZY_QIANWEN_API_KEY=${ZY_QIANWEN_API_KEY}" >> /opt/zhuyuan/novel-db/.env
if [ -n "${ZY_LLM_API_KEY}" ]; then
echo "LLM_API_KEY=${ZY_LLM_API_KEY}" >> /opt/zhuyuan/novel-db/.env
fi
if [ -n "${ZY_LLM_BASE_URL}" ]; then
echo "LLM_BASE_URL=${ZY_LLM_BASE_URL}" >> /opt/zhuyuan/novel-db/.env
fi
chmod 600 /opt/zhuyuan/novel-db/.env
echo "✅ 智库节点 novel-db 依赖已安装 + 环境变量已写入"
fi
cd /opt/zhuyuan/app
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
# ─── GLADA 自主开发Agent 部署 ───
echo ""
echo "═══ GLADA 自主开发Agent 部署 ═══"
mkdir -p /opt/glada/queue /opt/glada/logs/executions /opt/glada/logs/notifications /opt/glada/receipts /var/log/glada
# 安装 GLADA 依赖
cd /opt/glada
npm install --production 2>&1
# 写入 GLADA 环境变量
echo "NODE_ENV=production" > /opt/glada/.env.glada
echo "GLADA_PORT=3900" >> /opt/glada/.env.glada
echo "GLADA_POLL_INTERVAL=30000" >> /opt/glada/.env.glada
echo "GLADA_MODEL=deepseek-chat" >> /opt/glada/.env.glada
echo "GLADA_MODEL_PREFERENCE=" >> /opt/glada/.env.glada
echo "GLADA_MODEL_REFRESH_MS=600000" >> /opt/glada/.env.glada
echo "GLADA_STOP_ON_FAILURE=true" >> /opt/glada/.env.glada
if [ -n "${ZY_LLM_API_KEY}" ]; then
echo "ZY_LLM_API_KEY=${ZY_LLM_API_KEY}" >> /opt/glada/.env.glada
fi
if [ -n "${ZY_LLM_BASE_URL}" ]; then
echo "ZY_LLM_BASE_URL=${ZY_LLM_BASE_URL}" >> /opt/glada/.env.glada
fi
# ─── GLADA 邮件通知(复用零点原核 QQ 邮箱 SMTP 凭据) ───
if [ -n "${ZY_SMTP_USER}" ] && [ -n "${ZY_SMTP_PASS}" ]; then
echo "ZY_SMTP_USER=${ZY_SMTP_USER}" >> /opt/glada/.env.glada
echo "ZY_SMTP_PASS=${ZY_SMTP_PASS}" >> /opt/glada/.env.glada
echo "✅ GLADA 邮件通知 SMTP 已配置"
else
echo "⚠️ GLADA 邮件通知未配置ZY_SMTP_USER/ZY_SMTP_PASS 缺失)"
fi
chmod 600 /opt/glada/.env.glada
# 预飞检查
cd /opt/glada && node install-check.js || echo "⚠️ GLADA 预飞检查有警告(非致命·仍尝试启动)"
# PM2 重启 GLADA Agent
pm2 delete glada-agent 2>/dev/null || true
cd /opt/glada && pm2 start ecosystem.config.js
pm2 save
echo "═══════════════════"
echo ""
# ─── OPS Agent 运维守卫部署 ───
echo ""
echo "═══ OPS Agent 运维守卫部署 ═══"
mkdir -p /opt/zhuyuan/ops-agent/data /opt/zhuyuan/data/logs
# 安装 OPS Agent 依赖
cd /opt/zhuyuan/ops-agent
npm install --production 2>&1
# 写入 OPS Agent 环境变量
echo "NODE_ENV=production" > /opt/zhuyuan/ops-agent/.env.ops-agent
echo "OPS_AGENT_PORT=3950" >> /opt/zhuyuan/ops-agent/.env.ops-agent
if [ -n "${ZY_LLM_API_KEY}" ]; then
echo "ZY_LLM_API_KEY=${ZY_LLM_API_KEY}" >> /opt/zhuyuan/ops-agent/.env.ops-agent
fi
if [ -n "${ZY_LLM_BASE_URL}" ]; then
echo "ZY_LLM_BASE_URL=${ZY_LLM_BASE_URL}" >> /opt/zhuyuan/ops-agent/.env.ops-agent
fi
if [ -n "${ZY_DEEPSEEK_API_KEY}" ]; then
echo "ZY_DEEPSEEK_API_KEY=${ZY_DEEPSEEK_API_KEY}" >> /opt/zhuyuan/ops-agent/.env.ops-agent
fi
# OPS Agent 邮件通知(复用零点原核 QQ 邮箱 SMTP 凭据)
if [ -n "${ZY_SMTP_USER}" ] && [ -n "${ZY_SMTP_PASS}" ]; then
echo "ZY_SMTP_USER=${ZY_SMTP_USER}" >> /opt/zhuyuan/ops-agent/.env.ops-agent
echo "ZY_SMTP_PASS=${ZY_SMTP_PASS}" >> /opt/zhuyuan/ops-agent/.env.ops-agent
echo "✅ OPS Agent 邮件通知 SMTP 已配置"
else
echo "⚠️ OPS Agent 邮件通知未配置ZY_SMTP_USER/ZY_SMTP_PASS 缺失)"
fi
chmod 600 /opt/zhuyuan/ops-agent/.env.ops-agent
# 预飞检查
cd /opt/zhuyuan/ops-agent && node install-check.js || echo "⚠️ OPS Agent 预飞检查有警告(非致命·仍尝试启动)"
# PM2 重启 OPS Agent
pm2 delete ops-agent 2>/dev/null || true
cd /opt/zhuyuan/ops-agent && pm2 start ecosystem.config.js
pm2 save
echo "═══════════════════"
echo ""
# 健康检查 · 主应用(3800) + 预览进程(3801) + MCP(3100) + 智库节点(4000) + GLADA(3900) + OPS(3950)
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健康检查失败等待更长时间..."
curl -sf http://localhost:4000/api/health || echo "⚠️ 智库节点健康检查失败,等待更长时间..."
curl -sf http://localhost:3900/api/glada/health || echo "⚠️ GLADA Agent健康检查失败等待更长时间..."
curl -sf http://localhost:3950/health || echo "⚠️ OPS Agent健康检查失败等待更长时间..."
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)"
curl -sf http://localhost:4000/api/health && echo "✅ 智库节点已上线 (4000)" || echo "❌ 智库节点启动失败 (4000) · zhiku.html API将不可用"
curl -sf http://localhost:3900/api/glada/health && echo "✅ GLADA Agent已上线 (3900)" || echo "❌ GLADA Agent启动失败 (3900) · 自主开发Agent离线"
curl -sf http://localhost:3950/health && echo "✅ OPS Agent已上线 (3950)" || echo "❌ OPS Agent启动失败 (3950) · 运维守卫离线"
# 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