Files
blog/.github/workflows/deploy.yml
T
Workflow config file is invalid. Please check your config file: go-yaml load error in scanner at L238.C29: mapping values are not allowed in this context
2026-06-24 23:08:35 +08:00

531 lines
18 KiB
YAML
Raw 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: Deploy to Production
on:
push:
branches:
- main
schedule:
- cron: '0 1 * * *' # UTC 01:00 = 北京时间 09:00
workflow_dispatch:
inputs:
force_font_subset:
description: 'Force font subset regeneration'
type: boolean
default: false
skip_font_subset:
description: 'Skip font subset step'
type: boolean
default: false
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
env:
TZ: Asia/Shanghai
jobs:
# ============================================================
# Job 0: Pre-Flight Checks (前置检查)
# ============================================================
pre-check:
runs-on: ubuntu-latest
timeout-minutes: 2
outputs:
can_deploy: ${{ steps.check.outputs.can_deploy }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Validate repository
id: repo-check
run: |
if [ "${{ github.repository }}" != "zqlit/blog" ]; then
echo "❌ 只能在主仓库执行部署"
echo "can_deploy=false" >> $GITHUB_OUTPUT
exit 1
fi
echo "✅ 仓库验证通过"
echo "can_deploy=true" >> $GITHUB_OUTPUT
- name: Validate environment variables
id: env-check
run: |
REQUIRED_SECRETS=(
"EDGEONE_API_TOKEN"
"UPYUN_BUCKET"
"UPYUN_OPERATOR"
"UPYUN_PASSWORD"
"DOGECLOUD_ACCESS_KEY"
"DOGECLOUD_SECRET_KEY"
"MAIL_USERNAME"
"MAIL_PASSWORD"
)
MISSING=""
for SECRET in "${REQUIRED_SECRETS[@]}"; do
if [ -z "${{ secrets[SECRET] }}" ]; then
MISSING="$MISSING $SECRET"
fi
done
if [ -n "$MISSING" ]; then
echo "❌ 缺少必要的环境变量: $MISSING"
exit 1
fi
echo "✅ 所有必要环境变量已配置"
- name: Validate commit (only for push events)
if: github.event_name == 'push'
id: commit-check
run: |
COMMIT_AUTHOR="${{ github.event.head_commit.author.name }}"
COMMIT_MESSAGE="${{ github.event.head_commit.message }}"
echo "📝 提交作者: $COMMIT_AUTHOR"
echo "📄 提交信息: $COMMIT_MESSAGE"
if echo "$COMMIT_MESSAGE" | grep -qE "^(feat|fix|chore|docs|style|refactor|test): "; then
echo "✅ 提交信息符合规范"
else
echo "⚠️ 提交信息格式建议: feat/fix/chore/docs/style/refactor/test: description"
fi
- name: Summary
run: |
echo "## 🔍 前置检查完成" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "- **仓库**: ${{ github.repository }}" >> $GITHUB_STEP_SUMMARY
echo "- **分支**: ${{ github.ref_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **触发方式**: ${{ github.event_name }}" >> $GITHUB_STEP_SUMMARY
echo "- **Commit SHA**: ${{ github.sha }}" >> $GITHUB_STEP_SUMMARY
# ============================================================
# Job 1: Font Subset (字体子集化 - 可选并行任务)
# ============================================================
subset-fonts:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
needs: pre-check
if: |
needs.pre-check.outputs.can_deploy == 'true' &&
inputs.skip_font_subset != 'true'
outputs:
font_updated: ${{ steps.update.outputs.font_updated }}
char_count: ${{ steps.stats.outputs.char_count }}
original_size: ${{ steps.stats.outputs.original_size }}
subset_size: ${{ steps.stats.outputs.subset_size }}
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: pip install fonttools brotli zopfli
- name: Generate font subset
id: update
run: |
# 运行字体子集化脚本
# 会自动扫描:
# 1. 静态文件 (content, layouts, public)
# 2. 本地 JSON 数据 (友链、朋友圈)
# 3. 远程 API (api.usj.cc 的友链、订阅源、朋友圈)
python scripts/subset-font-safe.py
# 检查是否有变化
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
git add themes/Ying/static/font/zql-v2-subset.woff2 themes/Ying/static/font/used_chars.txt
if ! git diff --staged --quiet; then
git commit -m "chore: update font subset [skip ci]"
git push origin HEAD:main
echo "font_updated=true" >> $GITHUB_OUTPUT
echo "✅ 字体子集已更新并推送"
else
echo "font_updated=false" >> $GITHUB_OUTPUT
echo "ℹ️ 字体子集无变化,跳过提交"
fi
- name: Collect stats
id: stats
if: always()
run: |
CHAR_COUNT=$(wc -m < themes/Ying/static/font/used_chars.txt | tr -d ' ')
ORIGINAL_SIZE=$(stat -c%s themes/Ying/static/font/zql-v2.woff2)
SUBSET_SIZE=$(stat -c%s themes/Ying/static/font/zql-v2-subset.woff2)
echo "char_count=$CHAR_COUNT" >> $GITHUB_OUTPUT
echo "original_size=$ORIGINAL_SIZE" >> $GITHUB_OUTPUT
echo "subset_size=$SUBSET_SIZE" >> $GITHUB_OUTPUT
- name: Create font subset summary
if: always()
run: |
ORIGINAL_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.original_size }} / 1024" | bc)
SUBSET_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.subset_size }} / 1024" | bc)
REDUCTION_KB=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) / 1024" | bc)
PERCENTAGE=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) * 100 / ${{ steps.stats.outputs.original_size }}" | bc)
echo "## 🔤 字体子集化" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ "${{ steps.update.outputs.font_updated }}" = "true" ]; then
echo "✅ **字体子集已更新**" >> $GITHUB_STEP_SUMMARY
else
echo "ℹ️ **字体子集无变化**" >> $GITHUB_STEP_SUMMARY
fi
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 📊 统计信息" >> $GITHUB_STEP_SUMMARY
echo "- **字符数:** ${{ steps.stats.outputs.char_count }}" >> $GITHUB_STEP_SUMMARY
echo "- **原始大小:** ${ORIGINAL_SIZE_KB} KB" >> $GITHUB_STEP_SUMMARY
echo "- **子集大小:** ${SUBSET_SIZE_KB} KB" >> $GITHUB_STEP_SUMMARY
echo "- **节省空间:** ${REDUCTION_KB} KB (${PERCENTAGE}%)" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "### 🌐 扫描的数据源" >> $GITHUB_STEP_SUMMARY
echo "- 静态文件: content, layouts, public" >> $GITHUB_STEP_SUMMARY
echo "- 本地 JSON: link_lite.json, friend_circle_data.json" >> $GITHUB_STEP_SUMMARY
echo "- 远程 API: api.usj.cc (links, feeds, articles)" >> $GITHUB_STEP_SUMMARY
- name: Send font subset notification
if: steps.update.outputs.font_updated == 'true'
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
run: |
ORIGINAL_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.original_size }} / 1024" | bc)
SUBSET_SIZE_KB=$(echo "scale=1; ${{ steps.stats.outputs.subset_size }} / 1024" | bc)
REDUCTION_KB=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) / 1024" | bc)
PERCENTAGE=$(echo "scale=1; (${{ steps.stats.outputs.original_size }} - ${{ steps.stats.outputs.subset_size }}) * 100 / ${{ steps.stats.outputs.original_size }}" | bc)
if [ -n "$TELEGRAM_BOT_TOKEN" ] && [ -n "$TELEGRAM_CHAT_ID" ]; then
MESSAGE="✅ **字体子集化完成**
📦 项目: ${{ github.repository }}
🌿 分支: ${{ github.ref_name }}
📊 优化统计:
- 字符数: ${{ steps.stats.outputs.char_count }}
- 原始大小: ${ORIGINAL_SIZE_KB} KB
- 子集大小: ${SUBSET_SIZE_KB} KB
- 节省空间: ${REDUCTION_KB} KB (${PERCENTAGE}%)
🌐 数据源:
- 静态文件 + 本地JSON + 远程API"
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d text="${MESSAGE}" \
-d parse_mode="Markdown"
fi
if [ -n "$FEISHU_WEBHOOK_URL" ]; then
curl -s -X POST "${FEISHU_WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d '{
"msg_type": "text",
"content": {
"text": "✅ **字体子集化完成**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n\n📊 优化统计:\n- 字符数: ${{ steps.stats.outputs.char_count }}\n- 原始大小: '"${ORIGINAL_SIZE_KB}"' KB\n- 子集大小: '"${SUBSET_SIZE_KB}"' KB\n- 节省空间: '"${REDUCTION_KB}"' KB ('"${PERCENTAGE}"'%)\n\n🌐 数据源: 静态文件 + 本地JSON + 远程API"
}
}'
fi
# ============================================================
# Job 2: Build (构建)
# ============================================================
build:
runs-on: ubuntu-latest
timeout-minutes: 15
permissions:
contents: write
outputs:
build_hash: ${{ steps.hash.outputs.build_hash }}
needs: pre-check
if: needs.pre-check.outputs.can_deploy == 'true'
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.128.2'
extended: true
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install Node.js dependencies
run: npm ci
- name: Cache Hugo resources
uses: actions/cache@v4
with:
path: |
resources/_gen
/home/runner/.cache/hugo_cache
key: ${{ runner.os }}-hugo-${{ hashFiles('go.sum', 'package-lock.json', 'hugo.toml', 'config/_default/*.toml') }}
restore-keys: |
${{ runner.os }}-hugo-
- name: Run Pre-Build Scripts
shell: pwsh
run: ./scripts/add_draft_to_hidden.ps1
- name: Optimize Images
continue-on-error: true
timeout-minutes: 5
run: node scripts/optimize_images.js
- name: Commit Optimized Images
continue-on-error: true
run: |
git config --global user.name "GitHub Actions"
git config --global user.email "actions@github.com"
git add content/posts static
if ! git diff --staged --quiet; then
git commit -m "chore: auto-optimize images [skip ci]"
else
echo "No changes to commit"
fi
- name: Build Hugo site
timeout-minutes: 10
run: rm -rf public && hugo --minify
- name: Generate build hash
id: hash
run: echo "build_hash=$(find public -type f -print0 | sort -z | xargs -0 sha256sum | sha256sum | awk '{print $1}')" >> $GITHUB_OUTPUT
- name: Upload build artifact
uses: actions/upload-artifact@v4
with:
name: hugo-public
path: public/
retention-days: 1
- name: Push Image Optimizations
continue-on-error: true
run: |
git fetch origin main
git stash push -m "temp stash for rebase" || true
git rebase origin/main || true
git stash pop || true
if git log --oneline -1 | grep -q "\[skip ci\]"; then
git push origin HEAD:main
else
echo "No auto-commits to push"
fi
# ============================================================
# Job 3: Deploy to EdgeOne Pages (并行)
# ============================================================
deploy-edgeone:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
needs: build
steps:
- name: Checkout code (for scripts)
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: hugo-public
path: public/
- name: Deploy to EdgeOne Pages
env:
EDGEONE_API_TOKEN: ${{ secrets.EDGEONE_API_TOKEN }}
run: |
chmod +x scripts/deploy_edgeone.sh
./scripts/deploy_edgeone.sh hugo-blog ./public overseas
# ============================================================
# Job 4: Deploy to UpYun (并行)
# ============================================================
deploy-upyun:
runs-on: ubuntu-latest
timeout-minutes: 10
permissions:
contents: read
needs: build
steps:
- name: Download build artifact
uses: actions/download-artifact@v4
with:
name: hugo-public
path: public/
- name: Deploy to UpYun
uses: her-cat/upyun-deployer@v1.0.3
with:
bucket: ${{ secrets.UPYUN_BUCKET }}
operator: ${{ secrets.UPYUN_OPERATOR }}
password: ${{ secrets.UPYUN_PASSWORD }}
dir: 'public'
publish_dir: '/'
# ============================================================
# Job 5: CDN Refresh & Notifications (在所有部署完成后)
# ============================================================
finalize:
runs-on: ubuntu-latest
timeout-minutes: 5
permissions:
contents: read
needs: [deploy-edgeone, deploy-upyun]
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'npm'
- name: Install Node.js dependencies
run: npm ci
- name: Refresh DogeCloud CDN
env:
DOGECLOUD_ACCESS_KEY: ${{ secrets.DOGECLOUD_ACCESS_KEY }}
DOGECLOUD_SECRET_KEY: ${{ secrets.DOGECLOUD_SECRET_KEY }}
CDN_URL_LIST: ${{ secrets.CDN_URL_LIST }}
run: node scripts/refresh_cdn.js
- name: Generate Notification Report
continue-on-error: true
run: node scripts/generate_notification_report.js
- name: Send Telegram notification
if: secrets.TELEGRAM_BOT_TOKEN && secrets.TELEGRAM_CHAT_ID
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: |
MESSAGE="✅ **部署成功**
📦 项目: ${{ github.repository }}
🌿 分支: ${{ github.ref_name }}
🔗 Commit: ${{ github.sha }}
🕐 时间: $(date +'%Y-%m-%d %H:%M:%S')
📊 部署目标:
- EdgeOne Pages ✓
- UpYun ✓
- CDN 刷新 ✓"
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d text="${MESSAGE}" \
-d parse_mode="Markdown"
- name: Send Feishu notification
if: secrets.FEISHU_WEBHOOK_URL
env:
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
run: |
curl -s -X POST "${FEISHU_WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d '{
"msg_type": "text",
"content": {
"text": "✅ **部署成功**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n🔗 Commit: ${{ github.sha }}\n🕐 时间: '$(date +'%Y-%m-%d %H:%M:%S')'\n\n📊 部署目标:\n- EdgeOne Pages ✓\n- UpYun ✓\n- CDN 刷新 ✓"
}
}'
- name: Get current date (CN)
id: date
run: echo "date=$(date +'%Y-%m-%d %H:%M:%S')" >> $GITHUB_OUTPUT
- name: Send deployment notification
uses: dawidd6/action-send-mail@v3
with:
server_address: smtp.qq.com
server_port: 465
secure: true
username: ${{ secrets.MAIL_USERNAME }}
password: ${{ secrets.MAIL_PASSWORD }}
subject: 部署报告 - ${{ steps.date.outputs.date }}
to: ${{ secrets.MAIL_USERNAME }}
from: GitHub Actions <${{ github.event_name }}>
body: |
${{ env.DEPLOY_REPORT }}
# ============================================================
# Job 6: Failure Notification (失败通知)
# ============================================================
notify-failure:
runs-on: ubuntu-latest
timeout-minutes: 3
permissions:
contents: read
needs: [build, deploy-edgeone, deploy-upyun, finalize]
if: failure()
steps:
- name: Send Telegram failure notification
if: secrets.TELEGRAM_BOT_TOKEN && secrets.TELEGRAM_CHAT_ID
env:
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
run: |
MESSAGE="❌ **部署失败**
📦 项目: ${{ github.repository }}
🌿 分支: ${{ github.ref_name }}
🔗 Commit: ${{ github.sha }}
🕐 时间: $(date +'%Y-%m-%d %H:%M:%S')
📝 工作流: ${{ github.workflow }}
🔍 查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \
-d chat_id="${TELEGRAM_CHAT_ID}" \
-d text="${MESSAGE}" \
-d parse_mode="Markdown"
- name: Send Feishu failure notification
if: secrets.FEISHU_WEBHOOK_URL
env:
FEISHU_WEBHOOK_URL: ${{ secrets.FEISHU_WEBHOOK_URL }}
run: |
curl -s -X POST "${FEISHU_WEBHOOK_URL}" \
-H "Content-Type: application/json" \
-d '{
"msg_type": "text",
"content": {
"text": "❌ **部署失败**\n\n📦 项目: ${{ github.repository }}\n🌿 分支: ${{ github.ref_name }}\n🔗 Commit: ${{ github.sha }}\n🕐 时间: '$(date +'%Y-%m-%d %H:%M:%S')'\n\n📝 工作流: ${{ github.workflow }}\n🔍 查看详情: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
}
}'