Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions .github/workflows/cdn-ssl-renew.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
name: CDN SSL Certificate Management

on:
# 每月 1 号和 15 号凌晨 4 点执行(错开博客 SSL 续期时间)
schedule:
- cron: "0 20 1,15 * *" # UTC 20:00 = 北京时间 04:00
# 支持手动触发
workflow_dispatch:
inputs:
force_renew:
description: "强制重新申请证书"
required: false
type: boolean
default: false

env:
DOMAIN: "cdn.xkcoding.com"

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new CDN domain is cdn.xkcoding.com according to the PR title and description, but in the GitHub Actions workflow, the DOMAIN environment variable is set to cdn.xkcoding.com at line 17, while the design documentation and actual markdown replacements use static.xkcoding.com. This inconsistency needs to be resolved. Either update the workflow to use static.xkcoding.com or update all markdown files and documentation to use cdn.xkcoding.com.

Suggested change
DOMAIN: "cdn.xkcoding.com"
DOMAIN: "static.xkcoding.com"

Copilot uses AI. Check for mistakes.
EMAIL: "237497819@qq.com"

jobs:
cdn-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: |
export Ali_Key="${{ secrets.ALI_ACCESS_KEY_ID }}"
export Ali_Secret="${{ secrets.ALI_ACCESS_KEY_SECRET }}"

# 保存到 acme.sh 账户配置
~/.acme.sh/acme.sh --set-default-ca --server letsencrypt

# 写入环境变量供后续步骤使用
echo "Ali_Key=${{ secrets.ALI_ACCESS_KEY_ID }}" >> $GITHUB_ENV
echo "Ali_Secret=${{ secrets.ALI_ACCESS_KEY_SECRET }}" >> $GITHUB_ENV

- name: Issue/Renew Certificate
run: |
FORCE_RENEW="${{ github.event.inputs.force_renew }}"

# 检查是否需要强制续期
FORCE_FLAG=""
if [ "$FORCE_RENEW" = "true" ]; then
echo "🔄 强制重新申请证书..."
FORCE_FLAG="--force"
fi

# 使用 DNS-01 验证申请/续期证书
echo "🔐 申请证书: ${{ env.DOMAIN }}"
~/.acme.sh/acme.sh --issue \
--dns dns_ali \
-d ${{ env.DOMAIN }} \
$FORCE_FLAG \
--debug || {
# 如果证书已存在且未到期,--issue 会失败,这是正常的
echo "ℹ️ 证书可能已存在且未到期"
}
Comment on lines +30 to +62

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The exported environment variables Ali_Key and Ali_Secret are set using echo in step "Configure Aliyun DNS API" (lines 39-40), but they will not be available in the subsequent "Issue/Renew Certificate" step because environment variables set with echo >> $GITHUB_ENV are only available in subsequent steps, not in the same step. The acme.sh command in the next step needs these variables. Consider using the env context directly in the run command for the "Issue/Renew Certificate" step.

Copilot uses AI. Check for mistakes.
Comment on lines +55 to +62

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling logic at lines 59-62 catches failures from the acme.sh --issue command and outputs an informational message. However, this will cause the workflow to appear successful even when certificate issuance actually fails (not just when it's already valid). Consider checking the actual exit code or error message to distinguish between "certificate already valid" and genuine failures.

Suggested change
~/.acme.sh/acme.sh --issue \
--dns dns_ali \
-d ${{ env.DOMAIN }} \
$FORCE_FLAG \
--debug || {
# 如果证书已存在且未到期,--issue 会失败,这是正常的
echo "ℹ️ 证书可能已存在且未到期"
}
if ! ~/.acme.sh/acme.sh --issue \
--dns dns_ali \
-d ${{ env.DOMAIN }} \
$FORCE_FLAG \
--debug; then
ACME_EXIT_CODE=$?
# acme.sh 约定:退出码 2 通常表示证书已存在且未到期,无需续期
if [ "$ACME_EXIT_CODE" -eq 2 ]; then
echo "ℹ️ 证书已存在且未到期,无需续期"
else
echo "❌ 证书申请/续期失败,退出码: $ACME_EXIT_CODE"
exit "$ACME_EXIT_CODE"
fi
fi

Copilot uses AI. Check for mistakes.

- name: Install Aliyun CLI
run: |
# 下载并安装阿里云 CLI
curl -Lo aliyun-cli.tgz https://aliyuncli.alicdn.com/aliyun-cli-linux-latest-amd64.tgz
tar -xzf aliyun-cli.tgz
sudo mv aliyun /usr/local/bin/

# 配置 CLI
aliyun configure set \
--profile default \
--mode AK \
--region cn-hangzhou \
--access-key-id ${{ secrets.ALI_ACCESS_KEY_ID }} \
--access-key-secret ${{ secrets.ALI_ACCESS_KEY_SECRET }}
Comment on lines +76 to +77

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The secret name uses ALI_ACCESS_KEY_ID and ALI_ACCESS_KEY_SECRET (lines 32-33, 76-77), but according to the proposal.md documentation, the expected secret names are ALI_KEY and ALI_SECRET. This inconsistency could cause the workflow to fail if secrets are configured using the documented names. Either update the workflow to use ALI_KEY and ALI_SECRET, or update all documentation to specify ALI_ACCESS_KEY_ID and ALI_ACCESS_KEY_SECRET.

Suggested change
--access-key-id ${{ secrets.ALI_ACCESS_KEY_ID }} \
--access-key-secret ${{ secrets.ALI_ACCESS_KEY_SECRET }}
--access-key-id ${{ secrets.ALI_KEY }} \
--access-key-secret ${{ secrets.ALI_SECRET }}

Copilot uses AI. Check for mistakes.

- name: Deploy Certificate to CDN
run: |
CERT_PATH="$HOME/.acme.sh/${{ env.DOMAIN }}_ecc"

# 检查证书是否存在
if [ ! -f "$CERT_PATH/${{ env.DOMAIN }}.cer" ]; then
# 尝试非 ECC 路径
CERT_PATH="$HOME/.acme.sh/${{ env.DOMAIN }}"
fi

if [ ! -f "$CERT_PATH/${{ env.DOMAIN }}.cer" ]; then
echo "❌ 证书文件不存在"
ls -la $HOME/.acme.sh/
exit 1
fi

echo "📜 证书路径: $CERT_PATH"

# 读取证书内容
CERT_CONTENT=$(cat "$CERT_PATH/${{ env.DOMAIN }}.cer" "$CERT_PATH/ca.cer" 2>/dev/null || cat "$CERT_PATH/fullchain.cer")
KEY_CONTENT=$(cat "$CERT_PATH/${{ env.DOMAIN }}.key")

# 生成证书名称(包含时间戳)
CERT_NAME="cdn-xkcoding-$(date +%Y%m%d%H%M%S)"

echo "🚀 部署证书到 CDN..."
echo " 证书名称: $CERT_NAME"
echo " 域名: ${{ env.DOMAIN }}"

# 使用阿里云 CDN API 设置域名证书
aliyun cdn SetDomainServerCertificate \
--DomainName ${{ env.DOMAIN }} \
--CertName "$CERT_NAME" \
--CertType upload \
--ServerCertificateStatus on \
--ServerCertificate "$CERT_CONTENT" \
--PrivateKey "$KEY_CONTENT" \
--ForceSet 1

echo "✅ 证书部署成功!"

- name: Verify HTTPS
run: |
echo "🔍 等待 CDN 配置生效..."
sleep 30

echo "🔍 验证 HTTPS 访问..."
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://${{ env.DOMAIN }}/2017-07-12-14998277900006.jpg" || echo "000")

Copilot AI Jan 19, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The verification step at line 126 tests HTTPS with a specific hardcoded image URL "2017-07-12-14998277900006.jpg". This creates a dependency on that specific image existing in the OSS bucket. If that image is missing or the path changes, the verification will fail even if the SSL certificate is correctly deployed. Consider using a more generic verification approach, such as checking the SSL certificate itself or testing the domain root URL.

Suggested change
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://${{ env.DOMAIN }}/2017-07-12-14998277900006.jpg" || echo "000")
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "https://${{ env.DOMAIN }}/" || echo "000")

Copilot uses AI. Check for mistakes.

if [ "$HTTP_CODE" = "200" ]; then
echo "✅ HTTPS 验证成功!"
else
echo "⚠️ HTTPS 返回状态码: $HTTP_CODE"
echo " CDN 可能需要更多时间生效,请稍后手动验证"
fi

- name: Summary
if: always()
run: |
echo "## 🔐 CDN SSL 证书管理" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **时间**: $(date +'%Y-%m-%d %H:%M:%S UTC')" >> $GITHUB_STEP_SUMMARY
echo "- **域名**: \`${{ env.DOMAIN }}\`" >> $GITHUB_STEP_SUMMARY
echo "- **强制续期**: ${{ github.event.inputs.force_renew || 'false' }}" >> $GITHUB_STEP_SUMMARY
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,10 @@ hexo-backup/
.specstory/
.cursorindexingignore
resources/

# image migration temp files
scripts/migrate-images/temp/
scripts/migrate-images/reports/

# ossutil output
ossutil_output/
Loading
Loading