guanghulab/.github/workflows/deploy-zhiku-guanghu-online.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

414 lines
17 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: '📚 光湖智库 · guanghu.online 部署'
# ═══════════════════════════════════════════════════════════
# 光湖智库节点独立部署工作流
# 项目编号: ZY-PROJ-006
# 域名: guanghu.online
# 服务器: ZY-SVR-006 (43.153.203.105 · 新加坡)
# 守护: 铸渊 · ICE-GL-ZY001
# 版权: 国作登字-2026-A-00037559
#
# 功能:
# 1. deploy — 部署前端 + 后端 + Nginx + PM2
# 2. setup-ssl — 申请/续签 SSL 证书
# 3. health — 检查 API + 前端运行状态
# 4. rollback — 回滚到上一份配置
#
# 所需 GitHub Secrets:
# ZY_NOVEL_HOST — ZY-SVR-006 IP (43.153.203.105)
# ZY_NOVEL_KEY — ZY-SVR-006 SSH 私钥
# ZY_NOVEL_USER — ZY-SVR-006 SSH 用户名 (root)
# ZY_NOVEL_JWT_SECRET — JWT 签名密钥
# ZY_OSS_KEY — 腾讯云 COS SecretId仓库已有·映射为 ZY_COS_SECRET_ID
# ZY_OSS_SECRET — 腾讯云 COS SecretKey仓库已有·映射为 ZY_COS_SECRET_KEY
# ZY_NOVEL_COS_BUCKET — COS 桶名(仓库已有·映射为 ZY_COS_BUCKET
# ZY_COS_REGION 不使用 Secret · 硬编码 ap-singapore · 避免覆盖广州桶配置)
# ZY_ZHIKU_DOMAIN — 域名 (guanghu.online)
# ZY_SMTP_USER — SMTP 发件邮箱 (如 <<@committer_handle:base64>>)
# ZY_SMTP_PASS — SMTP 应用专用密码 (非邮箱登录密码)
# ZY_DEEPSEEK_API_KEY — DeepSeek LLM API 密钥 (Agent对话)
# ═══════════════════════════════════════════════════════════
on:
push:
branches: [main]
paths:
- 'server/zhiku-node/**'
- 'server/nginx/zhiku-guanghu-online.conf'
workflow_dispatch:
inputs:
action:
description: '执行动作'
required: true
default: 'deploy'
type: choice
options:
- deploy
- setup-datasources
- setup-ssl
- health
- rollback
jobs:
# ═══════════════════════════════════════════════════════
# Job 1: 完整部署
# ═══════════════════════════════════════════════════════
deploy:
name: '🚀 智库节点部署 · guanghu.online'
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/zhiku_key
chmod 600 ~/.ssh/zhiku_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: '📦 安装服务器基础依赖'
run: |
ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
# 安装 Node.js 20 (如果还没有)
if ! command -v node &>/dev/null || [ "$(node -v | cut -d. -f1 | tr -d v)" -lt 20 ]; then
curl -fsSL https://deb.nodesource.com/setup_20.x | bash -
apt-get install -y nodejs
fi
# 安装 PM2
npm list -g pm2 &>/dev/null || npm install -g pm2
# 创建目录结构
mkdir -p /opt/zhiku/public/assets/{css,js,img}
mkdir -p /opt/zhiku/public/modules
mkdir -p /opt/zhiku/server
mkdir -p /opt/zhiku/data/index
mkdir -p /opt/zhiku/data/books
mkdir -p /opt/zhiku/data/guardian
mkdir -p /opt/zhiku/datasources/fqweb
mkdir -p /opt/zhiku/datasources/swiftcat
mkdir -p /var/log/zhiku
mkdir -p /var/www/certbot
echo "[ZY-SVR-006] 基础目录已就绪"
ENDSSH
- name: '📤 上传前端文件'
run: |
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no -r \
server/zhiku-node/public/* \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/zhiku/public/
- name: '📤 上传后端文件'
run: |
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no -r \
server/zhiku-node/server/package.json \
server/zhiku-node/server/package-lock.json \
server/zhiku-node/server/server.js \
server/zhiku-node/server/.env.example \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/zhiku/server/
- name: '📤 上传镜面 Agent + 镜防模块 + 书岚 Agent + 内置数据源'
run: |
# 创建远程目录(含铸渊哨兵 + 永久记忆存储)
ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} \
'mkdir -p /opt/zhiku/server/mirror-agent/data/{snapshots,tickets,memory} /opt/zhiku/server/mirror-shield /opt/zhiku/server/shulan-agent /opt/zhiku/server/builtin-source /opt/zhiku/server/zhuyuan-sentinel /opt/zhiku/data/guardian /opt/zhiku/data/sentinel'
# 上传 mirror-agent
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no -r \
server/zhiku-node/server/mirror-agent/*.js \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/zhiku/server/mirror-agent/
# 上传 mirror-shield
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no -r \
server/zhiku-node/server/mirror-shield/*.js \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/zhiku/server/mirror-shield/
# 上传 shulan-agent书岚人格体系统
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no -r \
server/zhiku-node/server/shulan-agent/*.js \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/zhiku/server/shulan-agent/
# 上传 builtin-source内置数据源直连 · 搜索下载fallback
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no -r \
server/zhiku-node/server/builtin-source/*.js \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/zhiku/server/builtin-source/
# 上传 zhuyuan-sentinel铸渊哨兵 · 永久记忆 + 书源自动运维)
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no -r \
server/zhiku-node/server/zhuyuan-sentinel/*.js \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/zhiku/server/zhuyuan-sentinel/
- name: '📤 上传 PM2 配置'
run: |
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
server/zhiku-node/ecosystem.config.js \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/zhiku/
- name: '📤 上传 Nginx 配置'
run: |
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
server/nginx/zhiku-guanghu-online.conf \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/etc/nginx/sites-available/zhiku
- name: '⚙️ 配置环境变量 + 安装依赖 + 启动服务'
run: |
ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
# ─── 写入 .env ───
cat > /opt/zhiku/server/.env << 'ENVEOF'
PORT=3006
NODE_ENV=production
ZY_ZHIKU_JWT_SECRET=${{ secrets.ZY_NOVEL_JWT_SECRET }}
ZY_ZHIKU_TOKEN_TTL=86400
ZY_COS_SECRET_ID=${{ secrets.ZY_OSS_KEY }}
ZY_COS_SECRET_KEY=${{ secrets.ZY_OSS_SECRET }}
ZY_COS_BUCKET=${{ secrets.ZY_NOVEL_COS_BUCKET }}
ZY_COS_REGION=ap-singapore
ZY_ZHIKU_DATA_DIR=/opt/zhiku/data
ZY_ZHIKU_LOG_DIR=/var/log/zhiku
ZY_ZHIKU_DOMAIN=${{ secrets.ZY_ZHIKU_DOMAIN }}
ZY_FANQIE_API_URL=http://127.0.0.1:9999
ZY_QIMAO_API_URL=http://127.0.0.1:7700
ZY_SMTP_PORT=465
ZY_SMTP_USER=${{ secrets.ZY_SMTP_USER }}
ZY_SMTP_PASS=${{ secrets.ZY_SMTP_PASS }}
ZY_DEEPSEEK_API_URL=https://api.deepseek.com/v1/chat/completions
ZY_DEEPSEEK_API_KEY=${{ secrets.ZY_DEEPSEEK_API_KEY }}
ENVEOF
# 去除可能的前导空格
sed -i 's/^[[:space:]]*//' /opt/zhiku/server/.env
# ─── 安装 Node.js 依赖 ───
cd /opt/zhiku/server
npm install --production 2>&1 | tail -5
# ─── Nginx 配置 ───
ln -sf /etc/nginx/sites-available/zhiku /etc/nginx/sites-enabled/zhiku
# ─── 启动服务 ───
cd /opt/zhiku
pm2 start ecosystem.config.js
pm2 save
# ─── 安装 COS CLI 工具 ───
if ! command -v coscli &>/dev/null; then
wget https://github.com/tencentyun/coscli/releases/download/v0.11.0-beta/coscli-linux
chmod +x coscli-linux
mv coscli-linux /usr/local/bin/coscli
fi
# ─── 配置 COS CLI ───
mkdir -p ~/.cos.yaml
cat > ~/.cos.yaml << 'COSEOF'
configs:
default:
secretid: $ZY_COS_SECRET_ID
secretkey: $ZY_COS_SECRET_KEY
region: $ZY_COS_REGION
bucket: $ZY_COS_BUCKET
COSEOF
# ─── 上传备份脚本 ───
mkdir -p /opt/zhiku/scripts
# ─── 设置定时备份任务 ───
(crontab -l 2>/dev/null; echo "0 4 * * * /opt/zhiku/scripts/backup-data-to-cos.sh >> /var/log/zhiku/backup.log 2>&1") | crontab -
# ─── 重启 cron 服务 ───
systemctl restart cron
echo "[ZY-SVR-006] 服务启动完成"
ENDSSH
- name: '📤 上传备份脚本'
run: |
scp -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
server/zhiku-node/scripts/backup-data-to-cos.sh \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/opt/zhiku/scripts/
ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} \
'chmod +x /opt/zhiku/scripts/backup-data-to-cos.sh'
- name: '✅ 验证服务状态'
run: |
ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
# 检查 PM2 状态
if ! pm2 list | grep -q "online"; then
echo "[ERROR] PM2 服务未正常运行"
exit 1
fi
# 检查 API 健康状态
API_HEALTH=$(curl -s http://localhost:3006/api/health | jq -r '.status')
if [ "$API_HEALTH" != "running" ]; then
echo "[ERROR] API 健康检查失败: $API_HEALTH"
exit 1
fi
# 检查备份脚本
if [ ! -x "/opt/zhiku/scripts/backup-data-to-cos.sh" ]; then
echo "[ERROR] 备份脚本未正确安装"
exit 1
fi
# 检查定时任务
if ! crontab -l | grep -q "backup-data-to-cos"; then
echo "[ERROR] 定时备份任务未设置"
exit 1
fi
echo "[ZY-SVR-006] 所有服务验证通过"
ENDSSH
# ═══════════════════════════════════════════════════════
# Job 2: 申请 SSL 证书
# ═══════════════════════════════════════════════════════
setup-ssl:
name: '🔒 申请 SSL 证书 · guanghu.online'
runs-on: ubuntu-latest
permissions:
contents: read
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'setup-ssl'
steps:
- name: '📥 检出代码'
uses: actions/checkout@v4
- name: '🔑 配置 SSH'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ZY_NOVEL_KEY }}" > ~/.ssh/zhiku_key
chmod 600 ~/.ssh/zhiku_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: '📦 安装 certbot'
run: |
ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
# 安装 certbot
apt-get update
apt-get install -y certbot python3-certbot-nginx
# 申请证书 (webroot 模式)
certbot certonly --webroot -w /var/www/certbot \
-d ${{ secrets.ZY_ZHIKU_DOMAIN }} \
-d www.${{ secrets.ZY_ZHIKU_DOMAIN }} \
--non-interactive \
--agree-tos \
--email ${{ secrets.ZY_SMTP_USER }} \
--expand
# 设置自动续期
(crontab -l 2>/dev/null; echo "0 3 * * * certbot renew --quiet --post-hook 'systemctl reload nginx'" ) | crontab -
# 重启 Nginx
systemctl reload nginx
echo "[ZY-SVR-006] SSL 证书申请完成"
ENDSSH
# ═══════════════════════════════════════════════════════
# Job 3: 健康检查
# ═══════════════════════════════════════════════════════
health:
name: '🩺 健康检查 · guanghu.online'
runs-on: ubuntu-latest
permissions:
contents: read
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'health'
steps:
- name: '📥 检出代码'
uses: actions/checkout@v4
- name: '🔑 配置 SSH'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ZY_NOVEL_KEY }}" > ~/.ssh/zhiku_key
chmod 600 ~/.ssh/zhiku_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: '🔄 检查服务状态'
run: |
ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
# 检查 PM2
echo "=== PM2 状态 ==="
pm2 list
# 检查 API 健康
echo "\n=== API 健康状态 ==="
curl -s http://localhost:3006/api/health | jq
# 检查 Nginx
echo "\n=== Nginx 状态 ==="
systemctl status nginx --no-pager
# 检查备份日志
echo "\n=== 最近备份日志 ==="
tail -n 20 /var/log/zhiku/backup.log 2>/dev/null || echo "无备份日志"
# 检查定时任务
echo "\n=== 定时任务 ==="
crontab -l
echo "[ZY-SVR-006] 健康检查完成"
ENDSSH
# ═══════════════════════════════════════════════════════
# Job 4: 回滚
# ═══════════════════════════════════════════════════════
rollback:
name: '⏪ 回滚到上一版本 · guanghu.online'
runs-on: ubuntu-latest
permissions:
contents: read
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'rollback'
steps:
- name: '📥 检出代码'
uses: actions/checkout@v4
- name: '🔑 配置 SSH'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ZY_NOVEL_KEY }}" > ~/.ssh/zhiku_key
chmod 600 ~/.ssh/zhiku_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: '⏪ 执行回滚'
run: |
ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
# 回滚 PM2
cd /opt/zhiku
pm2 restart ecosystem.config.js
echo "[ZY-SVR-006] 回滚完成"
ENDSSH