SSL Certificate Management #10
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: SSL Certificate Management | |
| on: | |
| # 每月 1 号和 15 号凌晨 3 点执行 | |
| schedule: | |
| - cron: "0 19 1,15 * *" # UTC 19:00 = 北京时间 03:00 | |
| # 支持手动触发 | |
| workflow_dispatch: | |
| inputs: | |
| force_renew: | |
| description: "强制重新申请证书" | |
| required: false | |
| type: boolean | |
| default: false | |
| env: | |
| DOMAINS: "xkcoding.com www.xkcoding.com blog.xkcoding.com" | |
| EMAIL: "237497819@qq.com" | |
| SERVER_CERT_PATH: "/opt/myblog/certbot/etc/live/xkcoding.com" | |
| jobs: | |
| ssl-manage: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Install acme.sh | |
| run: | | |
| curl https://get.acme.sh | sh -s email=${{ env.EMAIL }} | |
| echo "$HOME/.acme.sh" >> $GITHUB_PATH | |
| - name: Configure Aliyun DNS API | |
| run: | | |
| ~/.acme.sh/acme.sh --set-default-ca --server letsencrypt | |
| # 写入环境变量供后续步骤使用 | |
| echo "Ali_Key=${{ secrets.ALIYUN_ACCESS_KEY_ID }}" >> $GITHUB_ENV | |
| echo "Ali_Secret=${{ secrets.ALIYUN_ACCESS_KEY_SECRET }}" >> $GITHUB_ENV | |
| - name: Issue/Renew Certificate | |
| run: | | |
| FORCE_RENEW="${{ github.event.inputs.force_renew }}" | |
| # 构建域名参数 | |
| DOMAIN_ARGS="" | |
| for domain in ${{ env.DOMAINS }}; do | |
| DOMAIN_ARGS="$DOMAIN_ARGS -d $domain" | |
| done | |
| # 检查是否需要强制续期 | |
| FORCE_FLAG="" | |
| if [ "$FORCE_RENEW" = "true" ]; then | |
| echo "🔄 强制重新申请证书..." | |
| FORCE_FLAG="--force" | |
| fi | |
| # 使用 DNS-01 验证申请/续期证书 | |
| echo "🔐 申请证书: ${{ env.DOMAINS }}" | |
| ~/.acme.sh/acme.sh --issue \ | |
| --dns dns_ali \ | |
| $DOMAIN_ARGS \ | |
| $FORCE_FLAG \ | |
| --debug || { | |
| # 如果证书已存在且未到期,--issue 会失败,这是正常的 | |
| echo "ℹ️ 证书可能已存在且未到期" | |
| } | |
| - name: Deploy Certificate to Server | |
| uses: appleboy/ssh-action@v1.2.0 | |
| with: | |
| host: ${{ secrets.ALIYUN_HOST }} | |
| username: ${{ secrets.ALIYUN_USER }} | |
| key: ${{ secrets.ALIYUN_SSH_KEY }} | |
| port: ${{ secrets.ALIYUN_PORT || 22 }} | |
| script: | | |
| echo "🚀 部署证书到服务器..." | |
| # 创建证书目录 | |
| CERT_DIR="${{ env.SERVER_CERT_PATH }}" | |
| mkdir -p "$CERT_DIR" | |
| # 证书将通过 SCP 传输(下一步) | |
| echo "✅ 证书目录已准备: $CERT_DIR" | |
| - name: Prepare Certificate Files | |
| run: | | |
| CERT_PATH="$HOME/.acme.sh/xkcoding.com_ecc" | |
| # 检查 ECC 证书路径 | |
| if [ ! -f "$CERT_PATH/xkcoding.com.cer" ]; then | |
| CERT_PATH="$HOME/.acme.sh/xkcoding.com" | |
| fi | |
| if [ ! -f "$CERT_PATH/xkcoding.com.cer" ]; then | |
| echo "❌ 证书文件不存在" | |
| ls -la $HOME/.acme.sh/ | |
| exit 1 | |
| fi | |
| echo "📜 证书路径: $CERT_PATH" | |
| # 创建工作区目录存放证书(SCP action 可以访问) | |
| mkdir -p $GITHUB_WORKSPACE/certs | |
| # 复制证书文件(使用 certbot 兼容的文件名) | |
| cp "$CERT_PATH/xkcoding.com.cer" $GITHUB_WORKSPACE/certs/cert.pem | |
| cp "$CERT_PATH/xkcoding.com.key" $GITHUB_WORKSPACE/certs/privkey.pem | |
| cp "$CERT_PATH/fullchain.cer" $GITHUB_WORKSPACE/certs/fullchain.pem | |
| cp "$CERT_PATH/ca.cer" $GITHUB_WORKSPACE/certs/chain.pem 2>/dev/null || cp "$CERT_PATH/fullchain.cer" $GITHUB_WORKSPACE/certs/chain.pem | |
| # 设置权限让 SCP action 可以读取 | |
| chmod 644 $GITHUB_WORKSPACE/certs/* | |
| echo "✅ 证书文件已准备" | |
| ls -la $GITHUB_WORKSPACE/certs/ | |
| - name: Upload Certificate to Server | |
| uses: appleboy/scp-action@v0.1.7 | |
| with: | |
| host: ${{ secrets.ALIYUN_HOST }} | |
| username: ${{ secrets.ALIYUN_USER }} | |
| key: ${{ secrets.ALIYUN_SSH_KEY }} | |
| port: ${{ secrets.ALIYUN_PORT || 22 }} | |
| source: "certs/*" | |
| target: "${{ env.SERVER_CERT_PATH }}/" | |
| strip_components: 1 | |
| - name: Reload Nginx | |
| uses: appleboy/ssh-action@v1.2.0 | |
| with: | |
| host: ${{ secrets.ALIYUN_HOST }} | |
| username: ${{ secrets.ALIYUN_USER }} | |
| key: ${{ secrets.ALIYUN_SSH_KEY }} | |
| port: ${{ secrets.ALIYUN_PORT || 22 }} | |
| script: | | |
| echo "🔄 重新加载 Nginx 配置..." | |
| # 检查证书文件 | |
| CERT_DIR="${{ env.SERVER_CERT_PATH }}" | |
| if [ -f "$CERT_DIR/fullchain.pem" ] && [ -f "$CERT_DIR/privkey.pem" ]; then | |
| echo "✅ 证书文件已就位" | |
| ls -la "$CERT_DIR/" | |
| # 重新加载 nginx(无需停止服务) | |
| docker exec myblog-nginx nginx -s reload 2>/dev/null || { | |
| echo "⚠️ Nginx reload 失败,尝试重启容器..." | |
| docker restart myblog-nginx 2>/dev/null || true | |
| } | |
| echo "✅ Nginx 已重新加载证书" | |
| else | |
| echo "❌ 证书文件不完整" | |
| ls -la "$CERT_DIR/" || true | |
| exit 1 | |
| fi | |
| - name: Verify HTTPS | |
| run: | | |
| echo "🔍 等待配置生效..." | |
| sleep 10 | |
| echo "🔍 验证 HTTPS 访问..." | |
| for domain in xkcoding.com www.xkcoding.com blog.xkcoding.com; do | |
| HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://$domain/" --max-time 10 || echo "000") | |
| if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "301" ] || [ "$HTTP_CODE" = "302" ]; then | |
| echo "✅ $domain: HTTPS 正常 (HTTP $HTTP_CODE)" | |
| else | |
| echo "⚠️ $domain: HTTP $HTTP_CODE" | |
| fi | |
| done | |
| - name: Summary | |
| if: always() | |
| run: | | |
| echo "## 🔐 SSL 证书管理" >> $GITHUB_STEP_SUMMARY | |
| echo "" >> $GITHUB_STEP_SUMMARY | |
| echo "- **时间**: $(date +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY | |
| echo "- **验证方式**: DNS-01 (Aliyun DNS)" >> $GITHUB_STEP_SUMMARY | |
| echo "- **域名**: xkcoding.com, www.xkcoding.com, blog.xkcoding.com" >> $GITHUB_STEP_SUMMARY | |
| echo "- **强制续期**: ${{ github.event.inputs.force_renew || 'false' }}" >> $GITHUB_STEP_SUMMARY |