guanghulab/.github/workflows/deploy-novel-mirror-shield.yml
2026-05-10 13:12:44 +08:00

481 lines
24 KiB
YAML
Raw Permalink 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: '🛡️ 语言保护罩 + Phase 1 · ZY-SVR-006 部署'
# ═══════════════════════════════════════════════════════════
# 光湖智库节点 Phase 1 部署工作流
# 编号: ZY-WF-NOVEL-SHIELD
# 守护: 铸渊 · ICE-GL-ZY001
# 版权: 国作登字-2026-A-00037559
#
# 目标服务器: ZY-SVR-006 (43.153.203.105 · 新加坡 · 智库节点)
#
# 功能:
# 1. deploy — 部署 Phase 1 完整栈Nginx保护罩+fail2ban+Node.js API+PM2+SSL
# 2. setup-ssl — 单独为 novel.guanghuyaoming.com 申请/续签 SSL 证书
# 3. health — 检查保护罩 + API 运行状态
# 4. rollback — 回滚到上一份配置
#
# 所需 GitHub Secrets (全部7个):
# ZY_NOVEL_HOST — ZY-SVR-006 新加坡服务器 IP (43.153.203.105)
# ZY_NOVEL_KEY — ZY-SVR-006 SSH 私钥 (PEM格式)
# ZY_NOVEL_USER — ZY-SVR-006 SSH 用户名 (root)
# ZY_NOVEL_PATH — 部署路径 (/opt/novel-db)
# ZY_NOVEL_JWT_SECRET — JWT token 签名密钥 (64位随机)
# ZY_NOVEL_COS_BUCKET — COS 桶名 (zy-team-hub-sg-1317346199)
# ZY_NOVEL_COS_REGION — COS 区域 (ap-singapore)
# ═══════════════════════════════════════════════════════════
on:
push:
branches: [main]
paths:
- 'server/nginx/novel-mirror-shield.conf'
- 'server/novel-db/**'
workflow_dispatch:
inputs:
action:
description: '执行动作'
required: true
default: 'deploy'
type: choice
options:
- deploy
- setup-ssl
- health
- rollback
jobs:
# ═══════════════════════════════════════════════════════
# Job 1: Phase 1 完整部署
# ═══════════════════════════════════════════════════════
deploy:
name: '🚀 Phase 1 完整部署'
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/novel_key
chmod 600 ~/.ssh/novel_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: '📦 安装服务器依赖 (nginx + fail2ban + certbot)'
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
apt-get update -q
apt-get install -y nginx fail2ban certbot python3-certbot-nginx
mkdir -p ${{ secrets.ZY_NOVEL_PATH }}/app/public
mkdir -p ${{ secrets.ZY_NOVEL_PATH }}/logs
mkdir -p /var/www/certbot
touch /var/log/novel-shield-bans.log
chmod 644 /var/log/novel-shield-bans.log
echo "=== 服务器依赖安装完成 ==="
ENDSSH
- name: '📤 上传应用代码'
run: |
# 上传 app 目录 (index.js, package.json, public/)
scp -r -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
server/novel-db/app/ \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:${{ secrets.ZY_NOVEL_PATH }}/
# 上传 PM2 生态配置
scp -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
server/novel-db/ecosystem.config.js \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:${{ secrets.ZY_NOVEL_PATH }}/ecosystem.config.js
- name: '⚙️ 生成 .env 并安装 Node.js 依赖'
env:
JWT_SECRET: ${{ secrets.ZY_NOVEL_JWT_SECRET }}
COS_BUCKET: ${{ secrets.ZY_NOVEL_COS_BUCKET }}
COS_REGION: ${{ secrets.ZY_NOVEL_COS_REGION }}
run: |
# 在 runner 上生成 .env 文件,再 scp 上传(避免 heredoc 展开 secrets
cat > /tmp/novel-db.env << EOF
NODE_ENV=production
PORT=4000
JWT_SECRET=${JWT_SECRET}
COS_BUCKET=${COS_BUCKET}
COS_REGION=${COS_REGION}
BAN_LOG_PATH=/var/log/novel-shield-bans.log
NGINX_LOG=/var/log/nginx/novel-access.log
EOF
scp -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
/tmp/novel-db.env \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:${{ secrets.ZY_NOVEL_PATH }}/.env
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} \
'chmod 600 ${{ secrets.ZY_NOVEL_PATH }}/.env && cd ${{ secrets.ZY_NOVEL_PATH }}/app && npm install --production --silent && echo "✓ Node.js 依赖安装完成"'
- name: '🚀 启动/重启 PM2 应用'
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
cd ${{ secrets.ZY_NOVEL_PATH }}
# 如果 PM2 进程已存在则重启,否则启动
pm2 describe novel-api > /dev/null 2>&1 \
&& pm2 restart novel-api \
|| pm2 start ecosystem.config.js
pm2 save
# 确保开机自启systemd幂等
pm2 startup systemd -u root --hp /root 2>/dev/null || true
systemctl enable pm2-root 2>/dev/null || true
echo "=== PM2 启动完成 ==="
pm2 list
ENDSSH
- name: '🗄️ 备份现有 Nginx 配置'
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
BACKUP_TS=$(date +%Y%m%d_%H%M%S)
if [ -f /etc/nginx/sites-available/novel-mirror-shield.conf ]; then
cp /etc/nginx/sites-available/novel-mirror-shield.conf \
/etc/nginx/sites-available/novel-mirror-shield.conf.bak.$BACKUP_TS
echo "已备份 → novel-mirror-shield.conf.bak.$BACKUP_TS"
fi
ENDSSH
- name: '📤 上传 Nginx 语言保护罩配置'
run: |
scp -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
server/nginx/novel-mirror-shield.conf \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/etc/nginx/sites-available/novel-mirror-shield.conf
- name: '🔧 配置 Nginx 全局速率限制'
run: |
scp -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
server/novel-db/security/nginx-rate-limit.conf \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/etc/nginx/conf.d/shield-rate-limit.conf
- name: '🔗 启用 Nginx 站点 + SSL 占位引导'
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
ln -sf /etc/nginx/sites-available/novel-mirror-shield.conf \
/etc/nginx/sites-enabled/novel-mirror-shield.conf
rm -f /etc/nginx/sites-enabled/default
mkdir -p /var/www/certbot /etc/letsencrypt/live/novel.guanghuyaoming.com
# ─── SSL 占位文件引导(解决鸡蛋问题) ───
# novel-mirror-shield.conf 的 HTTPS 块引用 letsencrypt 文件
# 全新服务器上这些文件不存在 → nginx -t 失败 → certbot 也无法运行
# 修复方案: 先创建临时占位文件让 nginx -t 通过certbot 申请成功后会替换为真实文件
if [ ! -f /etc/letsencrypt/options-ssl-nginx.conf ]; then
printf '%s\n' \
'ssl_session_cache shared:le_nginx_SSL:10m;' \
'ssl_session_timeout 1440m;' \
'ssl_session_tickets off;' \
'ssl_protocols TLSv1.2 TLSv1.3;' \
'ssl_prefer_server_ciphers off;' \
'ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256";' \
> /etc/letsencrypt/options-ssl-nginx.conf
echo "✓ 已创建 options-ssl-nginx.conf 占位文件"
fi
if [ ! -f /etc/letsencrypt/ssl-dhparams.pem ]; then
openssl dhparam -out /etc/letsencrypt/ssl-dhparams.pem 2048 2>/dev/null
echo "✓ 已生成 ssl-dhparams.pem 占位文件 (2048-bit)"
fi
if [ ! -f /etc/letsencrypt/live/novel.guanghuyaoming.com/fullchain.pem ]; then
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout /etc/letsencrypt/live/novel.guanghuyaoming.com/privkey.pem \
-out /etc/letsencrypt/live/novel.guanghuyaoming.com/fullchain.pem \
-days 30 -subj '/CN=novel.guanghuyaoming.com' 2>/dev/null
echo "✓ 已创建临时自签名证书占位30天有效·certbot 申请后替换)"
fi
nginx -t
systemctl reload nginx || systemctl restart nginx
echo "✓ Nginx 重载成功"
ENDSSH
- name: '🛡️ 部署 fail2ban L3 保护层'
run: |
scp -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
server/novel-db/security/fail2ban-novel.conf \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/etc/fail2ban/jail.d/novel-shield.conf
scp -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
server/novel-db/security/fail2ban-filter-novel-scan.conf \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/etc/fail2ban/filter.d/novel-scan.conf
scp -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
server/novel-db/security/fail2ban-filter-novel-4xx.conf \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/etc/fail2ban/filter.d/novel-4xx.conf
scp -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
server/novel-db/security/fail2ban-action-ban-log.conf \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }}:/etc/fail2ban/action.d/novel-ban-log.conf
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
systemctl enable fail2ban
systemctl restart fail2ban
echo "✓ fail2ban 启动成功"
# 等待 fail2ban socket 就绪异步启动socket 需要数秒创建)
for i in 1 2 3 4 5; do
if [ -S /var/run/fail2ban/fail2ban.sock ]; then
echo "✓ fail2ban socket 已就绪 (第${i}次检测)"
break
fi
echo "⏳ 等待 fail2ban socket 就绪... (${i}/5)"
sleep 2
done
fail2ban-client status || echo "⚠ fail2ban-client status 查询失败,服务可能仍在初始化"
ENDSSH
- name: '🔒 配置 iptables 扫描端口引流 (L3 增强)'
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
for PORT in 8080 8443 3000 3001 3002 8888 9090; do
iptables -t nat -D PREROUTING -p tcp --dport $PORT -j REDIRECT --to-port 80 2>/dev/null || true
iptables -t nat -A PREROUTING -p tcp --dport $PORT -j REDIRECT --to-port 80
done
apt-get install -y iptables-persistent -q || true
iptables-save > /etc/iptables/rules.v4 2>/dev/null || netfilter-persistent save 2>/dev/null || true
echo "✓ iptables 扫描端口引流配置完成"
ENDSSH
- name: "🔐 申请/续签 Let's Encrypt SSL 证书"
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
mkdir -p /var/www/certbot
# 检查是否是真实的 Let's Encrypt 证书,或只是上一步创建的占位自签名证书
CERT_ISSUER=$(openssl x509 -issuer -noout \
-in /etc/letsencrypt/live/novel.guanghuyaoming.com/fullchain.pem 2>/dev/null || echo "none")
if echo "$CERT_ISSUER" | grep -qi "Let's Encrypt\|letsencrypt\|ISRG"; then
echo "✓ Let's Encrypt 证书已存在,执行续签检查..."
certbot renew --quiet || true
else
echo "=== 申请 Let's Encrypt 证书 (webroot 方式,不依赖 nginx 插件) ==="
# certonly --webroot: 不会尝试修改 nginx 配置,不会触发 nginx -t
# nginx 已在上一步启动HTTP + 占位HTTPS/.well-known/acme-challenge/ 可正常响应
certbot certonly --webroot \
-w /var/www/certbot \
-d novel.guanghuyaoming.com \
--non-interactive \
--agree-tos \
--email admin@guanghuyaoming.com \
--preferred-challenges http \
--quiet
echo "✓ Let's Encrypt 证书申请成功,重载 Nginx 激活真实证书..."
systemctl reload nginx
fi
certbot certificates 2>/dev/null | grep -E "Domains|Expiry|Certificate" || true
echo "✓ HTTPS 已激活 · novel.guanghuyaoming.com"
ENDSSH
- name: '✅ 完整部署验证'
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
echo "═══ Phase 1 完整部署验证 · ZY-SVR-006 ═══"
echo ""
echo "─── Node.js API (PM2) ───"
pm2 describe novel-api | grep -E "status|pid|uptime|restarts" | head -5
HTTP_API=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://127.0.0.1:4000/api/health 2>/dev/null || echo "超时")
echo "API /api/health: $HTTP_API"
echo ""
echo "─── Nginx 蜜罐 (L2) ───"
systemctl is-active nginx && echo "✓ Nginx 在线" || echo "✗ Nginx 离线"
HTTP_80=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://127.0.0.1/ 2>/dev/null || echo "超时")
echo "蜜罐 HTTP 响应: $HTTP_80"
echo ""
echo "─── fail2ban (L3) ───"
systemctl is-active fail2ban && echo "✓ fail2ban 在线" || echo "✗ fail2ban 离线"
fail2ban-client status 2>/dev/null | grep "Jail list" || true
echo ""
echo "─── SSL 证书 ───"
if [ -f /etc/letsencrypt/live/novel.guanghuyaoming.com/fullchain.pem ]; then
echo "✓ SSL 证书已安装"
openssl x509 -enddate -noout -in /etc/letsencrypt/live/novel.guanghuyaoming.com/fullchain.pem 2>/dev/null || true
else
echo "⚠ SSL 证书待申请 (DNS 传播中?运行 setup-ssl 重试)"
fi
echo ""
echo "═══ Phase 1 部署完成 · 语言保护罩 L2+L3 已激活 ═══"
ENDSSH
# ═══════════════════════════════════════════════════════
# Job 2: 申请 SSL 证书
# ═══════════════════════════════════════════════════════
setup-ssl:
name: '🔐 申请 SSL 证书'
runs-on: ubuntu-latest
permissions: {}
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/novel_key
chmod 600 ~/.ssh/novel_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: "🔐 申请 Let's Encrypt SSL 证书 (webroot)"
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
# 安装 certbot (不安装 python3-certbot-nginx避免 nginx 插件干扰 webroot 方式)
apt-get update -q
apt-get install -y certbot
mkdir -p /var/www/certbot /etc/letsencrypt/live/novel.guanghuyaoming.com
# ─── SSL 占位文件引导(与 deploy job 相同逻辑) ───
# nginx 配置引用 letsencrypt 文件,全新服务器上不存在 → nginx -t 失败
# 先创建临时占位文件让 nginx 可以运行
if [ ! -f /etc/letsencrypt/options-ssl-nginx.conf ]; then
printf '%s\n' \
'ssl_session_cache shared:le_nginx_SSL:10m;' \
'ssl_session_timeout 1440m;' \
'ssl_session_tickets off;' \
'ssl_protocols TLSv1.2 TLSv1.3;' \
'ssl_prefer_server_ciphers off;' \
'ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256";' \
> /etc/letsencrypt/options-ssl-nginx.conf
echo "✓ 已创建 options-ssl-nginx.conf 占位文件"
fi
if [ ! -f /etc/letsencrypt/ssl-dhparams.pem ]; then
openssl dhparam -out /etc/letsencrypt/ssl-dhparams.pem 2048 2>/dev/null
echo "✓ 已生成 ssl-dhparams.pem 占位文件 (2048-bit)"
fi
if [ ! -f /etc/letsencrypt/live/novel.guanghuyaoming.com/fullchain.pem ]; then
openssl req -x509 -nodes -newkey rsa:2048 \
-keyout /etc/letsencrypt/live/novel.guanghuyaoming.com/privkey.pem \
-out /etc/letsencrypt/live/novel.guanghuyaoming.com/fullchain.pem \
-days 30 -subj '/CN=novel.guanghuyaoming.com' 2>/dev/null
echo "✓ 已创建临时自签名证书占位30天有效·certbot 申请后替换)"
fi
# 确保 nginx 能启动(占位文件就绪)
nginx -t && systemctl reload nginx || systemctl restart nginx
# 使用 webroot 方式申请证书
# --preferred-challenges http 确保只用 HTTP-01 验证,不触发 nginx 插件
certbot certonly --webroot \
-w /var/www/certbot \
-d novel.guanghuyaoming.com \
--non-interactive \
--agree-tos \
--email admin@guanghuyaoming.com \
--preferred-challenges http
echo "✓ Let's Encrypt 证书申请成功"
# 重载 nginx 使用真实证书
systemctl reload nginx
echo "✓ Nginx 重载 · HTTPS 已激活"
# 设置自动续期(幂等)
if ! crontab -l 2>/dev/null | grep -q "certbot renew"; then
(crontab -l 2>/dev/null; echo "0 3 1 * * certbot renew --quiet --post-hook 'systemctl reload nginx'") | crontab -
echo "✓ SSL 自动续期 cron 已配置"
fi
certbot certificates 2>/dev/null | grep -E "Domains|Expiry|Certificate" || true
echo "=== SSL 证书配置完成 ==="
ENDSSH
# ═══════════════════════════════════════════════════════
# Job 3: 健康检查
# ═══════════════════════════════════════════════════════
health:
name: '🔍 保护罩健康检查'
runs-on: ubuntu-latest
permissions: {}
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'health'
steps:
- name: '🔑 配置 SSH'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ZY_NOVEL_KEY }}" > ~/.ssh/novel_key
chmod 600 ~/.ssh/novel_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: '🔍 检查保护罩状态'
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
echo "═══ 语言保护罩健康检查 · ZY-SVR-006 ═══"
echo ""
echo "─── Nginx 状态 ───"
systemctl is-active nginx && echo "✓ Nginx 在线" || echo "✗ Nginx 离线"
nginx -t 2>&1 | tail -2
echo ""
echo "─── fail2ban 状态 ───"
systemctl is-active fail2ban && echo "✓ fail2ban 在线" || echo "✗ fail2ban 离线"
fail2ban-client status 2>/dev/null || echo "⚠ fail2ban-client 无法连接 socket"
echo ""
echo "─── 今日封禁记录 ───"
TODAY=$(date +%Y-%m-%d)
BANS=$(grep "$TODAY" /var/log/novel-shield-bans.log 2>/dev/null | wc -l)
echo "今日拦截: $BANS 次"
if [ "$BANS" -gt 0 ]; then
echo "最近10条:"
tail -10 /var/log/novel-shield-bans.log
fi
echo ""
echo "─── iptables 扫描引流规则 ───"
iptables -t nat -L PREROUTING --line-numbers 2>/dev/null | grep -E "REDIRECT|target" | head -15 || echo "无规则或无权限查看"
echo ""
echo "─── 蜜罐响应测试 ───"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --connect-timeout 5 http://127.0.0.1/ 2>/dev/null || echo "超时")
echo "蜜罐 HTTP 响应码: $HTTP_CODE"
echo ""
echo "─── SSL 证书状态 ───"
if [ -f /etc/letsencrypt/live/novel.guanghuyaoming.com/fullchain.pem ]; then
EXPIRY=$(openssl x509 -enddate -noout -in /etc/letsencrypt/live/novel.guanghuyaoming.com/fullchain.pem 2>/dev/null || echo "无法读取")
echo "SSL 证书到期: $EXPIRY"
else
echo "SSL 证书: 未申请 (运行 setup-ssl 动作)"
fi
echo ""
echo "═══ 检查完成 ═══"
ENDSSH
# ═══════════════════════════════════════════════════════
# Job 4: 回滚
# ═══════════════════════════════════════════════════════
rollback:
name: '⏪ 回滚配置'
runs-on: ubuntu-latest
permissions: {}
if: github.event_name == 'workflow_dispatch' && github.event.inputs.action == 'rollback'
steps:
- name: '🔑 配置 SSH'
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ZY_NOVEL_KEY }}" > ~/.ssh/novel_key
chmod 600 ~/.ssh/novel_key
ssh-keyscan -H ${{ secrets.ZY_NOVEL_HOST }} >> ~/.ssh/known_hosts
- name: '⏪ 回滚到上一份配置'
run: |
ssh -i ~/.ssh/novel_key -o StrictHostKeyChecking=no \
${{ secrets.ZY_NOVEL_USER }}@${{ secrets.ZY_NOVEL_HOST }} << 'ENDSSH'
set -e
LATEST_BAK=$(ls -t /etc/nginx/sites-available/novel-mirror-shield.conf.bak.* 2>/dev/null | head -1)
if [ -z "$LATEST_BAK" ]; then
echo "✗ 没有找到备份文件,无法回滚"
exit 1
fi
echo "回滚到: $LATEST_BAK"
cp "$LATEST_BAK" /etc/nginx/sites-available/novel-mirror-shield.conf
nginx -t
systemctl reload nginx
echo "✓ 回滚成功"
ENDSSH