zhizhi/.github/workflows/deploy-cn-landing.yml

530 lines
19 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: 🇨🇳 国内域名落地页 · 部署
# ═══════════════════════════════════════════════════════════
# 国内域名ICP备案落地页部署工作流
# 编号: ZY-WF-CN-LANDING
# 服务器: ZY-SVR-004 · 43.139.217.141 · 广州七区
# 守护: 铸渊 · ICE-GL-ZY001
# 版权: 国作登字-2026-A-00037559
# ═══════════════════════════════════════════════════════════
#
# 部署内容:
# 1. 国内落地页 (server/sites/cn-landing/) → /opt/zhuyuan-cn/sites/cn-landing/
# 2. Nginx 配置 (server/nginx/cn-domain.conf) → Nginx sites-enabled
# 3. LLM API 密钥注入 (四大国内模型)
#
# 架构: GitHub Actions(美国) → SG(新加坡·跳板) → CN(广州·目标)
on:
push:
branches: [main]
paths:
- 'server/sites/cn-landing/**'
- 'server/nginx/cn-domain.conf'
workflow_dispatch:
inputs:
action:
description: '部署动作'
required: true
default: 'deploy'
type: choice
options:
- deploy
- health-check
- setup-ssl
concurrency:
group: cn-landing-deploy
cancel-in-progress: false
permissions:
contents: read
jobs:
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跳板 (SG→CN)
env:
SG_SSH_KEY: ${{ secrets.ZY_SERVER_KEY }}
CN_SSH_KEY: ${{ secrets.ZY_CN_SERVER_KEY }}
CN_SSH_PORT: ${{ secrets.ZY_CN_SSH_PORT }}
run: |
CN_PORT="${CN_SSH_PORT:-22}"
echo "🔧 SSH跳板: GitHub Actions → SG → CN (端口 ${CN_PORT})"
mkdir -p ~/.ssh
echo "$SG_SSH_KEY" > ~/.ssh/id_sg
echo "$CN_SSH_KEY" > ~/.ssh/id_cn
chmod 600 ~/.ssh/id_sg ~/.ssh/id_cn
for key_file in id_sg id_cn; do
if ! head -1 ~/.ssh/${key_file} | grep -q "BEGIN"; then
echo "❌ SSH密钥格式异常: ${key_file}"
exit 1
fi
done
echo "✅ SG + CN 密钥格式正确"
ssh-keyscan -H ${{ secrets.ZY_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
cat > ~/.ssh/config << EOF
Host sg-jump
HostName ${{ secrets.ZY_SERVER_HOST }}
User ${{ secrets.ZY_SERVER_USER }}
IdentityFile ~/.ssh/id_sg
StrictHostKeyChecking accept-new
BatchMode yes
Host cn-target
HostName ${{ secrets.ZY_CN_SERVER_HOST }}
User ${{ secrets.ZY_CN_SERVER_USER }}
Port ${CN_PORT}
IdentityFile ~/.ssh/id_cn
ProxyJump sg-jump
StrictHostKeyChecking accept-new
BatchMode yes
ConnectTimeout 30
EOF
chmod 600 ~/.ssh/config
- name: 🔍 SSH连接测试
run: |
echo "🔍 测试SSH跳板连接到广州服务器..."
ssh cn-target 'echo "✅ SSH连接成功 · $(hostname) · $(date)"' || {
echo "❌ SSH跳板连接失败"
exit 1
}
- name: 📂 准备目标目录
run: |
ssh cn-target << 'PREP_CMD'
set -e
mkdir -p /opt/zhuyuan-cn/sites/cn-landing
mkdir -p /opt/zhuyuan-cn/data/logs
mkdir -p /opt/zhuyuan-cn/config/nginx
echo "✅ 目录结构就绪"
PREP_CMD
- name: 📦 同步落地页文件
run: |
rsync -avz --delete \
server/sites/cn-landing/ \
cn-target:/opt/zhuyuan-cn/sites/cn-landing/
echo "✅ 落地页已同步到 /opt/zhuyuan-cn/sites/cn-landing/"
- name: 📦 同步Nginx配置
env:
ZY_CN_DOMAIN: ${{ secrets.ZY_CN_DOMAIN }}
run: |
cp server/nginx/cn-domain.conf /tmp/cn-domain.conf
if [ -n "$ZY_CN_DOMAIN" ]; then
# 去除可能的 www. 前缀,获取裸域名
BARE_DOMAIN="${ZY_CN_DOMAIN#www.}"
sed -i "s/ZY_CN_BARE_DOMAIN_PLACEHOLDER/${BARE_DOMAIN}/g" /tmp/cn-domain.conf
echo "✅ 国内域名已注入: ${BARE_DOMAIN} + www.${BARE_DOMAIN}"
else
echo "⚠️ ZY_CN_DOMAIN 未配置使用IP直接访问"
sed -i "s/ZY_CN_BARE_DOMAIN_PLACEHOLDER www.ZY_CN_BARE_DOMAIN_PLACEHOLDER/_/g" /tmp/cn-domain.conf
fi
scp -F ~/.ssh/config \
/tmp/cn-domain.conf \
cn-target:/opt/zhuyuan-cn/config/nginx/cn-domain.conf
echo "✅ Nginx配置已上传"
- name: 🔑 注入LLM API密钥
env:
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 }}
run: |
# 在本地生成密钥文件(变量在此展开)
cat > /tmp/.env.llm << ENVEOF
ZY_DEEPSEEK_API_KEY=${ZY_DEEPSEEK_API_KEY}
ZY_QIANWEN_API_KEY=${ZY_QIANWEN_API_KEY}
ZY_KIMI_API_KEY=${ZY_KIMI_API_KEY}
ZY_QINGYAN_API_KEY=${ZY_QINGYAN_API_KEY}
ENVEOF
# 上传到目标服务器
scp -F ~/.ssh/config /tmp/.env.llm cn-target:/opt/zhuyuan-cn/config/.env.llm
ssh cn-target 'chmod 600 /opt/zhuyuan-cn/config/.env.llm'
rm -f /tmp/.env.llm
echo "✅ 四大国内模型API密钥已注入"
- name: 🔧 启用Nginx配置 & 重载
env:
ZY_CN_DOMAIN: ${{ secrets.ZY_CN_DOMAIN }}
run: |
BARE_DOMAIN="${ZY_CN_DOMAIN#www.}"
ssh cn-target << NGINX_CMD
set -e
echo "═══ Nginx 配置诊断 ═══"
# 诊断: 列出当前所有启用的Nginx站点配置
echo "§1 当前sites-enabled目录:"
ls -la /etc/nginx/sites-enabled/ 2>/dev/null || echo " (目录不存在)"
echo ""
echo "§1.1 当前conf.d目录:"
ls -la /etc/nginx/conf.d/ 2>/dev/null || echo " (目录不存在)"
# 清理所有冲突配置 — cn-domain.conf 必须是唯一的站点配置
# 此服务器仅服务国内落地页不应有其他Nginx站点配置
echo ""
echo "§2 清理冲突配置..."
# 清理 sites-enabled 中的冲突
for conf in /etc/nginx/sites-enabled/*; do
if [ -f "\$conf" ] && [ "\$(basename "\$conf")" != "cn-domain.conf" ]; then
echo " 🗑️ 移除sites-enabled冲突: \$(basename "\$conf")"
sudo rm -f "\$conf"
fi
done
# 清理 conf.d 中可能的冲突 server 块配置
# 保留 conf.d 中的全局配置(如 gzip, ssl_params 等)
# 只移除包含 server { 或 proxy_pass 的自定义站点配置
for conf in /etc/nginx/conf.d/*.conf; do
if [ -f "\$conf" ] && grep -qE "server[[:space:]]*\{" "\$conf" 2>/dev/null; then
echo " 🗑️ 移除conf.d冲突站点配置: \$(basename "\$conf")"
sudo rm -f "\$conf"
fi
done
# 复制配置到Nginx目录
sudo cp /opt/zhuyuan-cn/config/nginx/cn-domain.conf /etc/nginx/sites-available/cn-domain.conf
sudo ln -sf /etc/nginx/sites-available/cn-domain.conf /etc/nginx/sites-enabled/cn-domain.conf
# ─── SSL自动注入: 检测已有Let's Encrypt证书 ───
# 如果之前运行过 setup-ssl证书文件会存在
# 每次deploy都重新注入SSL配置避免模板覆盖导致SSL丢失
if [ -d "/etc/letsencrypt/live/${BARE_DOMAIN}" ] && [ -f "/etc/letsencrypt/live/${BARE_DOMAIN}/fullchain.pem" ]; then
echo ""
echo "§2.5 🔐 检测到SSL证书注入HTTPS配置..."
# 在 listen 80 后添加 listen 443 ssl 和证书配置
sudo sed -i '/listen 80 default_server;/a\\n listen 443 ssl default_server;\n ssl_certificate /etc/letsencrypt/live/'"${BARE_DOMAIN}"'/fullchain.pem;\n ssl_certificate_key /etc/letsencrypt/live/'"${BARE_DOMAIN}"'/privkey.pem;\n include /etc/letsencrypt/options-ssl-nginx.conf;\n ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;' \
/etc/nginx/sites-available/cn-domain.conf
echo "✅ SSL配置已注入 · HTTPS已启用"
else
echo ""
echo "§2.5 未检测到SSL证书 · 仅HTTP模式"
echo " 运行 setup-ssl 动作安装Let's Encrypt证书"
fi
# 确认最终状态
echo ""
echo "§3 清理后sites-enabled目录:"
ls -la /etc/nginx/sites-enabled/
echo ""
echo "§3.1 清理后conf.d目录:"
ls -la /etc/nginx/conf.d/ 2>/dev/null || echo " (空)"
# 测试并重载Nginx
echo ""
sudo nginx -t && sudo systemctl reload nginx
echo "✅ Nginx配置已生效 · 仅cn-domain.conf生效"
NGINX_CMD
- name: 💚 健康检查
run: |
ssh cn-target << 'HEALTH_CMD'
set -e
echo "═══ 国内落地页部署验证 ═══"
# 检查Nginx
if systemctl is-active --quiet nginx; then
echo "✅ Nginx: 运行中"
else
echo "❌ Nginx: 已停止"
exit 1
fi
# 诊断: 确认Nginx活跃配置
echo ""
echo "§ Nginx活跃配置:"
ls -la /etc/nginx/sites-enabled/ 2>/dev/null
# 检查页面可访问
HTTP_CODE=$(curl -sf -o /dev/null -w '%{http_code}' http://localhost/ 2>/dev/null || echo "000")
if [ "$HTTP_CODE" = "200" ]; then
echo "✅ 落地页: HTTP 200 OK"
else
echo "❌ 落地页: HTTP ${HTTP_CODE}"
# 不立即退出 · 继续诊断
fi
# 检查ICP备案号 · 确认是正确的落地页(非代理页面)
if curl -sf http://localhost/ | grep -q "陕ICP备2025071211号"; then
echo "✅ ICP备案号: 已显示"
else
echo "❌ ICP备案号: 未找到 — 可能有冲突配置覆盖了落地页"
echo " 诊断: 页面前200字符:"
curl -sf http://localhost/ 2>/dev/null | head -c 200 || echo "(无响应)"
fi
# 检查备案链接
if curl -sf http://localhost/ | grep -q "beian.miit.gov.cn"; then
echo "✅ 工信部链接: 已配置"
else
echo "⚠️ 工信部链接: 未找到"
fi
# 健康端点 · 验证返回的是cn-landing而不是其他服务
HEALTH=$(curl -sf http://localhost/health 2>/dev/null || echo '{}')
echo "✅ 健康探针: ${HEALTH}"
# 验证健康端点来自cn-domain.conf而非其他服务
if echo "$HEALTH" | grep -q "cn-landing"; then
echo "✅ 健康端点: 来自cn-domain.conf正确"
elif echo "$HEALTH" | grep -qE "relay|proxy|mcp"; then
echo "❌ 健康端点: 来自其他服务 — Nginx配置冲突未完全解决"
fi
# 确认没有端口冲突: 检查是否有其他服务监听80端口
echo ""
echo "§ 端口80监听进程:"
sudo ss -tlnp | grep ':80 ' || echo " (无)"
echo ""
echo "═══ 部署完成 ═══"
HEALTH_CMD
- name: 🧹 清理SSH密钥
if: always()
run: rm -f ~/.ssh/id_sg ~/.ssh/id_cn ~/.ssh/config
health-check:
name: 💚 国内落地页健康检查
if: github.event.inputs.action == 'health-check'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 🔐 配置SSH跳板 (SG→CN)
env:
SG_SSH_KEY: ${{ secrets.ZY_SERVER_KEY }}
CN_SSH_KEY: ${{ secrets.ZY_CN_SERVER_KEY }}
CN_SSH_PORT: ${{ secrets.ZY_CN_SSH_PORT }}
run: |
CN_PORT="${CN_SSH_PORT:-22}"
mkdir -p ~/.ssh
echo "$SG_SSH_KEY" > ~/.ssh/id_sg
echo "$CN_SSH_KEY" > ~/.ssh/id_cn
chmod 600 ~/.ssh/id_sg ~/.ssh/id_cn
ssh-keyscan -H ${{ secrets.ZY_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
cat > ~/.ssh/config << EOF
Host sg-jump
HostName ${{ secrets.ZY_SERVER_HOST }}
User ${{ secrets.ZY_SERVER_USER }}
IdentityFile ~/.ssh/id_sg
StrictHostKeyChecking accept-new
BatchMode yes
Host cn-target
HostName ${{ secrets.ZY_CN_SERVER_HOST }}
User ${{ secrets.ZY_CN_SERVER_USER }}
Port ${CN_PORT}
IdentityFile ~/.ssh/id_cn
ProxyJump sg-jump
StrictHostKeyChecking accept-new
BatchMode yes
ConnectTimeout 30
EOF
chmod 600 ~/.ssh/config
- name: 💚 执行健康检查
run: |
ssh cn-target << 'HEALTH_CMD'
echo "═══ 国内落地页健康报告 ═══"
echo ""
echo "§1 系统状态:"
uptime
echo ""
echo "§2 Nginx 状态:"
systemctl is-active nginx && echo "Nginx: 运行中" || echo "Nginx: 已停止"
echo ""
echo "§3 Nginx sites-enabled 诊断:"
ls -la /etc/nginx/sites-enabled/ 2>/dev/null || echo " (目录不存在)"
echo ""
echo "§4 落地页文件:"
ls -la /opt/zhuyuan-cn/sites/cn-landing/ 2>/dev/null || echo "目录不存在"
echo ""
echo "§5 页面访问测试:"
HTTP_CODE=$(curl -sf -o /dev/null -w '%{http_code}' http://localhost/ 2>/dev/null || echo "000")
echo "HTTP状态码: ${HTTP_CODE}"
echo ""
echo "§6 ICP备案号检查:"
if curl -sf http://localhost/ | grep -q "陕ICP备2025071211号"; then
echo "✅ ICP备案号已显示"
else
echo "❌ ICP备案号未找到"
echo " 页面前200字符:"
curl -sf http://localhost/ 2>/dev/null | head -c 200 || echo "(无响应)"
fi
echo ""
echo "§7 健康端点验证:"
HEALTH=$(curl -sf http://localhost/health 2>/dev/null || echo '{}')
echo "响应: ${HEALTH}"
if echo "$HEALTH" | grep -q "cn-landing"; then
echo "✅ 来自cn-domain.conf正确"
elif echo "$HEALTH" | grep -qE "relay|proxy|mcp"; then
echo "❌ 来自其他服务 — 存在Nginx配置冲突"
fi
echo ""
echo "§8 LLM API密钥状态:"
if [ -f /opt/zhuyuan-cn/config/.env.llm ]; then
KEY_COUNT=$(grep -c "API_KEY" /opt/zhuyuan-cn/config/.env.llm 2>/dev/null || echo "0")
echo "已配置密钥数: ${KEY_COUNT}"
else
echo "⚠️ LLM密钥文件不存在"
fi
echo ""
echo "§9 端口80监听进程:"
sudo ss -tlnp | grep ':80 ' 2>/dev/null || echo " (无)"
# SSL状态检查
echo ""
echo "§10 SSL证书状态:"
sudo certbot certificates 2>/dev/null || echo " certbot未安装 · 运行setup-ssl动作安装"
echo ""
echo "§11 HTTPS访问测试:"
HTTPS_CODE=$(curl -sf -o /dev/null -w '%{http_code}' https://localhost/ -k 2>/dev/null || echo "000")
echo "HTTPS状态码: ${HTTPS_CODE}"
HEALTH_CMD
- name: 🧹 清理SSH密钥
if: always()
run: rm -f ~/.ssh/id_sg ~/.ssh/id_cn ~/.ssh/config
setup-ssl:
name: 🔐 安装SSL证书 (Let's Encrypt)
if: github.event.inputs.action == 'setup-ssl'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: 🔐 配置SSH跳板 (SG→CN)
env:
SG_SSH_KEY: ${{ secrets.ZY_SERVER_KEY }}
CN_SSH_KEY: ${{ secrets.ZY_CN_SERVER_KEY }}
CN_SSH_PORT: ${{ secrets.ZY_CN_SSH_PORT }}
run: |
CN_PORT="${CN_SSH_PORT:-22}"
mkdir -p ~/.ssh
echo "$SG_SSH_KEY" > ~/.ssh/id_sg
echo "$CN_SSH_KEY" > ~/.ssh/id_cn
chmod 600 ~/.ssh/id_sg ~/.ssh/id_cn
ssh-keyscan -H ${{ secrets.ZY_SERVER_HOST }} >> ~/.ssh/known_hosts 2>/dev/null
cat > ~/.ssh/config << EOF
Host sg-jump
HostName ${{ secrets.ZY_SERVER_HOST }}
User ${{ secrets.ZY_SERVER_USER }}
IdentityFile ~/.ssh/id_sg
StrictHostKeyChecking accept-new
BatchMode yes
Host cn-target
HostName ${{ secrets.ZY_CN_SERVER_HOST }}
User ${{ secrets.ZY_CN_SERVER_USER }}
Port ${CN_PORT}
IdentityFile ~/.ssh/id_cn
ProxyJump sg-jump
StrictHostKeyChecking accept-new
BatchMode yes
ConnectTimeout 30
EOF
chmod 600 ~/.ssh/config
- name: 🔐 安装certbot并申请证书
env:
ZY_CN_DOMAIN: ${{ secrets.ZY_CN_DOMAIN }}
run: |
BARE_DOMAIN="${ZY_CN_DOMAIN#www.}"
echo "🔐 为 ${BARE_DOMAIN} + www.${BARE_DOMAIN} 申请SSL证书..."
ssh cn-target << SSLCMD
set -e
echo "═══ SSL证书安装 ═══"
# 安装certbot (如果未安装)
if ! command -v certbot &> /dev/null; then
echo "📦 安装certbot..."
sudo apt-get update -qq
sudo apt-get install -y -qq certbot python3-certbot-nginx
else
echo "✅ certbot已安装: \$(certbot --version 2>&1)"
fi
# 确认Nginx正在运行
if ! systemctl is-active --quiet nginx; then
echo "❌ Nginx未运行先启动Nginx"
sudo systemctl start nginx
fi
# 申请证书 (--nginx模式会自动修改Nginx配置)
echo ""
echo "§1 申请Let's Encrypt证书..."
sudo certbot --nginx \
-d ${BARE_DOMAIN} \
-d www.${BARE_DOMAIN} \
--non-interactive \
--agree-tos \
--email admin@${BARE_DOMAIN} \
--redirect \
--no-eff-email
# 验证证书
echo ""
echo "§2 验证证书..."
sudo certbot certificates
# 验证HTTPS可访问
echo ""
echo "§3 HTTPS访问测试..."
HTTP_CODE=\$(curl -sf -o /dev/null -w '%{http_code}' https://localhost/ -k 2>/dev/null || echo "000")
echo "HTTPS状态码: \${HTTP_CODE}"
# 验证HTTP→HTTPS重定向
echo ""
echo "§4 HTTP→HTTPS重定向测试..."
REDIR_CODE=\$(curl -sf -o /dev/null -w '%{http_code}' http://localhost/ 2>/dev/null || echo "000")
echo "HTTP重定向码: \${REDIR_CODE} (301=正确重定向)"
echo ""
echo "═══ SSL安装完成 ═══"
echo "🌐 现在可以通过 https://${BARE_DOMAIN} 访问"
SSLCMD
- name: 🧹 清理SSH密钥
if: always()
run: rm -f ~/.ssh/id_sg ~/.ssh/id_cn ~/.ssh/config