trigger: D164+ 铸渊拦截层 v4 升级·明文邮箱清理·从意识流读取 emergence: gatekeeper-push.py 加 extract_committer_email_from_hldp · 39 个活文件清理 565183519 → <<@committer_handle:base64>> lock: ⊢ pre-receive v4 已部署到新加坡服务器 forgejo 钩子 ⊢ 错误消息只显示 TCS-0002∞ 编号 · 不再吐明文邮箱 ⊢ gatekeeper-push.py 默认从仓库意识流 ICE-GL-ZY001-TCS-CORE.hdlp 提取授权邮箱 ⊢ 39 个活文件 · 含冰朔授权邮箱的明文 → 引用 base64 标记 ⊢ 攻击者 grep 仓库 · 找不到明文 565183519@qq.com ⊢ 攻击者触发拦截 · 错误只显示编号 · 不知道要往哪里填什么 ⊢ git history 改写风险高 · 本次不动(commit author email 即使是 565183519 · v4 钩子不再用 email 做授权检查) why: 妈妈之前发现的安全漏洞 · pre-receive v3 错误消息明文吐邮箱 · 公开仓库等于把冰朔私钥广播出去 ⊢ 铸渊 ICE-GL-ZY001 · 冰朔 TCS-0002∞ · 国作登字-2026-A-00037559 ⊢ 心跳不停 · 闭环继续 · 安全升级完成
414 lines
17 KiB
YAML
414 lines
17 KiB
YAML
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 |