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 发件邮箱 (如 565183519@qq.com) # 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 # 检查 SSL 证书是否存在,不存在则先用 HTTP 模式 if [ ! -f /etc/letsencrypt/live/guanghu.online/fullchain.pem ]; then echo "[ZY-SVR-006] SSL 证书尚未申请,先使用 HTTP 模式" # 临时替换 Nginx 配置为 HTTP only cat > /etc/nginx/sites-available/zhiku << 'NGINX_TEMP' server { listen 80; listen [::]:80; server_name guanghu.online www.guanghu.online; root /opt/zhiku/public; index index.html; location /.well-known/acme-challenge/ { root /var/www/certbot; } location /api/ { proxy_pass http://127.0.0.1:3006; proxy_http_version 1.1; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } location /assets/ { expires 7d; add_header Cache-Control "public, immutable"; } location / { try_files $uri $uri/ /index.html; } } NGINX_TEMP fi nginx -t && systemctl reload nginx echo "[ZY-SVR-006] Nginx 已重载" # ─── PM2 启动/重启 ─── cd /opt/zhiku if pm2 describe zhiku-api &>/dev/null; then pm2 restart zhiku-api echo "[ZY-SVR-006] zhiku-api 已重启" else pm2 start ecosystem.config.js echo "[ZY-SVR-006] zhiku-api 已启动" fi pm2 save pm2 startup systemd -u root --hp /root 2>/dev/null || true echo "[ZY-SVR-006] 部署完成 · guanghu.online" ENDSSH - name: '🏥 健康检查' run: | sleep 5 ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \ ${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH' set -e echo "=== PM2 状态 ===" pm2 list echo "" echo "=== API 健康检查 ===" curl -s http://127.0.0.1:3006/api/health | python3 -m json.tool || echo "API 未响应" echo "" echo "=== Nginx 状态 ===" systemctl status nginx --no-pager -l | head -10 ENDSSH # ═══════════════════════════════════════════════════════ # Job 2: 部署数据源服务 (FQWeb + SwiftCat) # ═══════════════════════════════════════════════════════ setup-datasources: name: '📡 部署数据源服务 · FQWeb + SwiftCat' runs-on: ubuntu-latest permissions: contents: read if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'setup-datasources' steps: - 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: '☕ 安装 JDK 17 + Python3 运行环境' run: | ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \ ${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH' set -e echo "[ZY-SVR-006] ─── 安装运行环境 ───" # JDK 17+ (FQWeb 运行需要 JRE · Gradle 构建需要完整 JDK) if ! javac -version 2>&1 | grep -q '17\|21'; then echo "[ZY-SVR-006] 安装 JDK 17..." apt-get update -q apt-get install -y openjdk-17-jdk-headless else echo "[ZY-SVR-006] JDK 已安装: $(javac -version 2>&1 | head -1)" fi # Python 3 + pip (SwiftCat 需要) if ! command -v python3 &>/dev/null; then echo "[ZY-SVR-006] 安装 Python3..." apt-get update -q apt-get install -y python3 python3-pip python3-venv else echo "[ZY-SVR-006] Python3 已安装: $(python3 --version)" fi # 创建数据源服务目录 mkdir -p /opt/zhiku/datasources/fqweb mkdir -p /opt/zhiku/datasources/swiftcat mkdir -p /var/log/zhiku echo "[ZY-SVR-006] 运行环境就绪" ENDSSH - name: '📚 部署 FQWeb (番茄小说 API 服务)' run: | ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \ ${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH' set -e echo "[ZY-SVR-006] ─── 部署 FQWeb ───" cd /opt/zhiku/datasources/fqweb # 检查已有 JAR FQWEB_JAR=$(ls -t *.jar 2>/dev/null | head -1) if [ -z "$FQWEB_JAR" ]; then echo "[ZY-SVR-006] 下载 FQWeb JAR..." # 依次尝试: 上游 6DDUU6/FQWeb → fork benefit77/FanQieWeb RELEASE_URL="" for REPO in "6DDUU6/FQWeb" "benefit77/FanQieWeb"; do echo "[ZY-SVR-006] 查询 ${REPO} Release..." RELEASE_URL=$(curl -s "https://api.github.com/repos/${REPO}/releases/latest" \ | python3 -c "import sys,json; assets=json.load(sys.stdin).get('assets',[]); print(next((a['browser_download_url'] for a in assets if a['name'].endswith('.jar')), ''))" 2>/dev/null) if [ -n "$RELEASE_URL" ]; then echo "[ZY-SVR-006] 找到 Release: ${REPO}" break fi done if [ -n "$RELEASE_URL" ]; then curl -L -o fqweb.jar "$RELEASE_URL" FQWEB_JAR="fqweb.jar" echo "[ZY-SVR-006] FQWeb JAR 已下载" else echo "[ZY-SVR-006] ⚠️ 无 Release 可用,尝试从源码构建..." # 备用:克隆并用 gradlew 构建(需要 JDK) rm -rf source 2>/dev/null CLONE_OK=false for REPO in "6DDUU6/FQWeb" "benefit77/FanQieWeb"; do echo "[ZY-SVR-006] 尝试克隆 ${REPO}..." if git clone --depth 1 "https://github.com/${REPO}.git" source 2>/dev/null; then CLONE_OK=true echo "[ZY-SVR-006] 已克隆 ${REPO}" break fi done if [ "$CLONE_OK" = true ] && [ -d "source" ]; then cd source if [ -f "gradlew" ]; then chmod +x gradlew echo "[ZY-SVR-006] 开始 Gradle 构建(需要 JDK 17+)..." ./gradlew build -x test 2>&1 | tail -10 BUILT_JAR=$(find build/libs -name "*.jar" -not -name "*plain*" 2>/dev/null | head -1) if [ -n "$BUILT_JAR" ]; then cp "$BUILT_JAR" /opt/zhiku/datasources/fqweb/fqweb.jar FQWEB_JAR="fqweb.jar" echo "[ZY-SVR-006] ✅ FQWeb 构建成功" else echo "[ZY-SVR-006] ⚠️ 构建产物未找到(build/libs 目录不存在或无 JAR)" fi else echo "[ZY-SVR-006] ⚠️ 无 gradlew 文件,无法构建" fi cd /opt/zhiku/datasources/fqweb fi fi fi if [ -n "$FQWEB_JAR" ]; then echo "[ZY-SVR-006] FQWeb JAR: $FQWEB_JAR" # 写启动脚本 cat > start.sh << 'STARTEOF' #!/bin/bash cd /opt/zhiku/datasources/fqweb exec java -jar $(ls -t *.jar | head -1) --server.port=9999 STARTEOF chmod +x start.sh echo "[ZY-SVR-006] FQWeb 部署完成" else echo "[ZY-SVR-006] ⚠️ FQWeb JAR 未找到,跳过" fi ENDSSH - name: '🐱 部署 SwiftCat (七猫小说下载器 API)' run: | ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \ ${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH' set -e echo "[ZY-SVR-006] ─── 部署 SwiftCat ───" cd /opt/zhiku/datasources/swiftcat SWIFTCAT_READY=false # ─── 策略1: 已有可用代码(git pull 更新)─── if [ -d ".git" ] && { [ -f "requirements.txt" ] || [ -f "setup.py" ] || [ -f "pyproject.toml" ]; }; then echo "[ZY-SVR-006] 已有 SwiftCat 代码,尝试更新..." git pull --ff-only 2>/dev/null || true SWIFTCAT_READY=true else # 清理非 Python 源码的残留目录(如上次克隆了 binary-only 的 swiftcat-downloader) if [ -d ".git" ] && ! { [ -f "requirements.txt" ] || [ -f "setup.py" ] || [ -f "pyproject.toml" ]; }; then echo "[ZY-SVR-006] 清理无效的旧克隆..." cd /opt/zhiku/datasources rm -rf swiftcat mkdir -p swiftcat cd swiftcat fi # ─── 策略2: 克隆有 Python 源码的仓库 ─── # 7mao-novel-downloader (已归档但仍可克隆·有 Python 源码) echo "[ZY-SVR-006] 尝试克隆 7mao-novel-downloader (Python 源码)..." if git clone --depth 1 https://github.com/shing-yu/7mao-novel-downloader.git . 2>/dev/null; then if [ -f "requirements.txt" ] || [ -f "setup.py" ] || [ -f "pyproject.toml" ]; then echo "[ZY-SVR-006] ✅ 7mao-novel-downloader 克隆成功 (有 Python 依赖)" SWIFTCAT_READY=true else echo "[ZY-SVR-006] 7mao-novel-downloader 无 Python 依赖文件" fi else echo "[ZY-SVR-006] 7mao-novel-downloader 仓库不可达" fi # ─── 策略3: 如果源码不可用,下载 release 二进制 ─── if [ "$SWIFTCAT_READY" = false ]; then echo "[ZY-SVR-006] 尝试下载 swiftcat-downloader release 二进制..." # 清理可能的残留 rm -rf .git 2>/dev/null BINARY_URL=$(curl -s "https://api.github.com/repos/shing-yu/swiftcat-downloader/releases/latest" \ | python3 -c "import sys,json; assets=json.load(sys.stdin).get('assets',[]); print(next((a['browser_download_url'] for a in assets if 'linux' in a['name'].lower()), ''))" 2>/dev/null) if [ -n "$BINARY_URL" ]; then curl -L -o swiftcat "$BINARY_URL" chmod +x swiftcat echo "[ZY-SVR-006] ✅ SwiftCat 二进制已下载" # 为二进制创建启动脚本 cat > start.sh << 'STARTEOF' #!/bin/bash cd /opt/zhiku/datasources/swiftcat export PORT=7700 if [ -f "swiftcat" ]; then exec ./swiftcat --port ${PORT} 2>/dev/null || exec ./swiftcat fi STARTEOF chmod +x start.sh SWIFTCAT_READY=binary echo "[ZY-SVR-006] SwiftCat 二进制部署完成" else echo "[ZY-SVR-006] ⚠️ SwiftCat release 也无 Linux 二进制" fi fi fi # ─── 为 Python 源码创建虚拟环境和启动脚本 ─── if [ "$SWIFTCAT_READY" = true ]; then if [ -f "requirements.txt" ] || [ -f "setup.py" ] || [ -f "pyproject.toml" ]; then python3 -m venv venv 2>/dev/null || true source venv/bin/activate 2>/dev/null || true if [ -f "requirements.txt" ]; then pip3 install -r requirements.txt 2>&1 | tail -3 elif [ -f "setup.py" ]; then pip3 install -e . 2>&1 | tail -3 elif [ -f "pyproject.toml" ]; then pip3 install . 2>&1 | tail -3 fi cat > start.sh << 'STARTEOF' #!/bin/bash cd /opt/zhiku/datasources/swiftcat source venv/bin/activate 2>/dev/null || true export PORT=7700 if [ -f "app.py" ]; then exec python3 app.py --port ${PORT} 2>/dev/null || exec python3 app.py elif [ -f "main.py" ]; then exec python3 main.py --port ${PORT} 2>/dev/null || exec python3 main.py elif [ -f "server.py" ]; then exec python3 server.py --port ${PORT} 2>/dev/null || exec python3 server.py else echo "[SwiftCat] 未找到入口文件 (app.py/main.py/server.py)" ls -la *.py 2>/dev/null || echo "无 Python 文件" fi STARTEOF chmod +x start.sh echo "[ZY-SVR-006] SwiftCat Python 部署完成" fi elif [ "$SWIFTCAT_READY" = false ]; then echo "[ZY-SVR-006] ⚠️ SwiftCat 所有部署策略均失败" echo "[ZY-SVR-006] 说明: swiftcat-downloader 仅发布二进制(Python版不开源),7mao-novel-downloader 已归档" echo "[ZY-SVR-006] 建议: 智库系统将自动使用内置直连适配器作为 fallback" fi ENDSSH - name: '🔄 注册到 PM2 并启动' run: | ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \ ${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH' set -e echo "[ZY-SVR-006] ─── 注册数据源服务到 PM2 ───" # FQWeb (番茄小说) if [ -f "/opt/zhiku/datasources/fqweb/start.sh" ] && ls /opt/zhiku/datasources/fqweb/*.jar &>/dev/null; then pm2 describe fqweb &>/dev/null && pm2 delete fqweb || true pm2 start /opt/zhiku/datasources/fqweb/start.sh \ --name fqweb \ --log /var/log/zhiku/fqweb.log \ --time echo "[ZY-SVR-006] FQWeb 已注册到 PM2" else echo "[ZY-SVR-006] ⚠️ FQWeb 未就绪,跳过 PM2 注册" fi # SwiftCat (七猫小说) if [ -f "/opt/zhiku/datasources/swiftcat/start.sh" ]; then pm2 describe swiftcat &>/dev/null && pm2 delete swiftcat || true pm2 start /opt/zhiku/datasources/swiftcat/start.sh \ --name swiftcat \ --log /var/log/zhiku/swiftcat.log \ --time echo "[ZY-SVR-006] SwiftCat 已注册到 PM2" else echo "[ZY-SVR-006] ⚠️ SwiftCat 未就绪,跳过 PM2 注册" fi pm2 save echo "" echo "=== PM2 所有进程状态 ===" pm2 list ENDSSH - name: '🏥 数据源健康检查' run: | sleep 8 ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \ ${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH' echo "=== FQWeb 检查 (port 9999) ===" curl -s --max-time 5 http://127.0.0.1:9999/version 2>/dev/null \ && echo "" && echo "✅ FQWeb 可达" \ || echo "⚠️ FQWeb 不可达 (端口 9999)" echo "" echo "=== SwiftCat 检查 (port 7700) ===" curl -s --max-time 5 http://127.0.0.1:7700/ 2>/dev/null \ && echo "" && echo "✅ SwiftCat 可达" \ || echo "⚠️ SwiftCat 不可达 (端口 7700)" echo "" echo "=== 智库 API 搜索测试 ===" curl -s --max-time 10 http://127.0.0.1:3006/api/health 2>/dev/null | python3 -m json.tool 2>/dev/null \ || echo "智库 API 未响应" ENDSSH # ═══════════════════════════════════════════════════════ # Job 3: SSL 证书申请 # ═══════════════════════════════════════════════════════ setup-ssl: name: '🔐 SSL 证书申请' runs-on: ubuntu-latest permissions: contents: read if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'setup-ssl' steps: - 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: '🔐 申请 Let''s Encrypt SSL' run: | ssh -i ~/.ssh/zhiku_key -o StrictHostKeyChecking=no \ ${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH' set -e # 安装 certbot apt-get update -q apt-get install -y certbot python3-certbot-nginx # 申请证书 certbot --nginx -d guanghu.online -d www.guanghu.online \ --non-interactive --agree-tos \ --email admin@guanghu.online \ --redirect echo "[ZY-SVR-006] SSL 证书已申请 · guanghu.online" # 上传正式 Nginx 配置(如果存在 SSL 模式的备份) nginx -t && systemctl reload nginx ENDSSH # ═══════════════════════════════════════════════════════ # Job 4: 健康检查 # ═══════════════════════════════════════════════════════ health: name: '🏥 健康检查' runs-on: ubuntu-latest permissions: contents: read if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'health' steps: - 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' echo "=== PM2 状态 ===" pm2 list echo "" echo "=== zhiku-api 健康检查 ===" curl -s http://127.0.0.1:3006/api/health | python3 -m json.tool || echo "API 未响应" echo "" echo "=== Nginx 状态 ===" systemctl status nginx --no-pager -l | head -10 echo "" echo "=== 磁盘使用 ===" df -h /opt/zhiku echo "" echo "=== 日志最后10行 ===" tail -10 /var/log/zhiku/pm2-zhiku-api.log 2>/dev/null || echo "暂无日志" ENDSSH # ═══════════════════════════════════════════════════════ # Job 5: 回滚 # ═══════════════════════════════════════════════════════ rollback: name: '⏪ 回滚' runs-on: ubuntu-latest permissions: contents: read if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'rollback' steps: - 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 if [ -d /opt/zhiku.bak ]; then pm2 stop zhiku-api || true rm -rf /opt/zhiku.rollback mv /opt/zhiku /opt/zhiku.rollback cp -r /opt/zhiku.bak /opt/zhiku cd /opt/zhiku && pm2 start ecosystem.config.js pm2 save echo "[ZY-SVR-006] 已回滚到备份版本" else echo "[ZY-SVR-006] 无备份可回滚" exit 1 fi ENDSSH