Hexo博客在Gitea Action 自动打包发布流程

每次推送代码到 master 分支时,Gitea Action 会自动完成:构建 → 打包 → 发布 Release → 同步到服务器

工作流流程

各步骤说明

1. 触发条件

1
2
3
4
on:
push:
branches:
- master

仅监听 master 分支的推送事件。

2. 防护机制

跳过自动发布提交: 含有 Auto release 关键词的提交不会触发,防止 Release 创建事件产生死循环。

Gatekeeper 标签检查: 从远端同步最新标签后,检测当前 commit 是否已有 v- 开头的标签。如果存在,说明本次触发是 Release API 产生的二次调用,直接安全退出。

1
2
3
4
if: |
github.ref == 'refs/heads/master' &&
github.ref_type == 'branch' &&
!contains(github.event.head_commit.message, 'Auto release')

3. 环境配置

设置 Node.js 22 和 npm 缓存,通过代理访问外部网络:

配置项
Node.js 22
npm 缓存 启用
HTTP 代理 Clash (7890)

4. 构建与打包

1
2
npx hexo generate
tar -czf hexo-blog-master.tar.gz public/
  • hexo generate:生成静态文件到 public/
  • tar -czf:将 public/ 压缩为 tar.gz

5. 创建 Release

通过 Gitea API 创建 Release,自动附带本次 commit 的提交信息:

1
2
3
4
5
6
7
8
9
10
## 🤖 Auto Release

**Commit:** a1b2c3d
**Branch:** master

---

### 📝 提交标题

提交正文内容

6. 上传到公网服务器

使用 rsync 将 public/ 目录同步到公网服务器,支持:

  • 增量同步(-avz
  • 删除服务端多余文件(--delete
  • 通过 SSH 密钥认证
1
rsync -avz --delete public/ user@server:/path/

变量与密钥

名称 类型 用途
HEXO_RELEASE_TOKEN Secrets Gitea API 访问令牌,用于创建 Release
SSH_PRIVATE_KEY Secrets 服务器 SSH 私钥,用于 rsync 同步
SERVER_HOST Variables 目标服务器地址
SERVER_PORT Variables SSH 端口,默认 22
SERVER_USER Variables SSH 登录用户
SERVER_PATH Variables 部署路径

完整工作流

参考流程:hexo-release.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
name: 📦 Hexo Release
run-name: ${{ github.actor }} 🚀 Hexo Release 🎯

on:
push:
branches:
- master

jobs:
release:
runs-on: ubuntu-latest
if: |
github.ref == 'refs/heads/master' &&
github.ref_type == 'branch' &&
!contains(github.event.head_commit.message, 'Auto release')

steps:
- name: 📥 Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0

# 🌟 Gatekeeper: local Git tag check to prevent re-trigger loops
- name: 🛡️ Anti-Loop Gatekeeper
run: |
# Force sync tags from remote to avoid Gitea cache staleness
git fetch --tags --force

CURRENT_COMMIT=$(git rev-parse --short HEAD)
echo "Current Commit ID: $CURRENT_COMMIT"

# Get all tags pointing to the current commit
ALL_TAGS=$(git tag --points-at HEAD)
echo "Tags on this commit: $ALL_TAGS"

# Check if any auto-release tag (v-*) exists on this commit
if echo "$ALL_TAGS" | grep -q "v-"; then
echo "========================================================="
echo "🚨 [Blocked] Auto-release tag already exists on this commit."
echo "🚨 This run is likely a re-trigger caused by the Release API. Exiting safely."
echo "========================================================="
exit 0 # 🌟 Exit cleanly to cancel all subsequent steps
fi

- name: ⚙️ Setup Node.js
uses: actions/setup-node@v6
env:
# HTTP_PROXY: "http://192.168.0.2:7890" # 可以添加代理
# HTTPS_PROXY: "http://192.168.0.2:7890" # 可以添加代理
RUNNER_TOOL_CACHE: ${{ github.workspace }}/tool_cache
with:
node-version: 22
cache: 'npm'

- name: 📦 Install dependencies
run: npm ci

- name: 🔨 Generate static files & Package
run: |
npx hexo generate
tar -czf hexo-blog-${GITHUB_REF_NAME}.tar.gz public/

- name: 🚀 Create Release & Upload Artifact
run: |
ZIP_FILE=hexo-blog-${GITHUB_REF_NAME}.tar.gz
SHORT_SHA=$(git rev-parse --short HEAD)

export PAYLOAD_SHA="$SHORT_SHA"
export PAYLOAD_BRANCH="$GITHUB_REF_NAME"

python3 << 'PYEOF' > release_payload.json
import json, subprocess, os

sha = os.environ.get('PAYLOAD_SHA', '')
branch = os.environ.get('PAYLOAD_BRANCH', '')
subject = subprocess.run(
['git', 'log', '-1', '--pretty=format:%s'],
capture_output=True, text=True).stdout.strip()
body = subprocess.run(
['git', 'log', '-1', '--pretty=format:%b'],
capture_output=True, text=True).stdout.strip()
body_text = ('## 🤖 Auto Release\n\n'
f'**Commit:** {sha}\n**Branch:** {branch}\n\n'
'---\n\n'
f'### 📝 {subject}\n\n{body}')
payload = {
'tag_name': f'v-{sha}',
'name': f'Hexo Blog {branch} ({sha})',
'body': body_text.strip(),
'draft': False,
'prerelease': False
}
print(json.dumps(payload))
PYEOF

curl -s -X POST "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases" \
-H "Authorization: token ${{ secrets.HEXO_RELEASE_TOKEN }}" \
-H "Content-Type: application/json" \
-d @release_payload.json > release.json

RELEASE_ID=$(python3 -c "import sys,json; print(json.load(sys.stdin)['id'])" < release.json)

curl -s -X POST "${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}/releases/${RELEASE_ID}/assets" \
-H "Authorization: token ${{ secrets.HEXO_RELEASE_TOKEN }}" \
-F "attachment=@${ZIP_FILE}"

echo "✅ Release created: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/releases/tag/v-${SHORT_SHA}"

- name: 🌐 Sync public/ to public server
uses: burnett01/rsync-deployments@v8
env:
# HTTP_PROXY: "http://192.168.0.2:7890" # 可以添加代理
# HTTPS_PROXY: "http://192.168.0.2:7890" # 可以添加代理
with:
switches: -avzr --delete
path: public/
remote_path: ${{ vars.SERVER_PATH }}
remote_host: ${{ vars.SERVER_HOST }}
remote_port: ${{ vars.SERVER_PORT || '22' }}
remote_user: ${{ vars.SERVER_USER }}
remote_key: ${{ secrets.SSH_PRIVATE_KEY }}