hexo-deploy-pipeline

引言

架构设计

整体的发布流程简化为:本地 Push 源码 -> GitHub Actions 编译打包 -> 触发服务器 apiservice -> 解压并切换目录 -> 刷新 CDN。
在这个流程中,有几个需要处理的细节:

  1. 安全性: apiservice 接口直接暴露在公网上,需要通过 HMAC 算法对消息体进行签名。
  2. 平滑发布: 避免在部署期间出现 404,通过 Linux 软链接 (ln -sfn) 实现新老 Web 目录的原子替换。
  3. 缓存清理: 站点用了 EdgeOne,静态资源更新后如果不主动通知 EdgeOne 过期缓存会造成访问到旧资源。

实现步骤

服务器基础环境

为了遵循权限最小化原则,首先在服务器上为 apiservice 服务创建一个专用的非特权用户及目录:

1
2
3
4
# 创建 apiservice 用户及相关目录
sudo useradd -r -s /bin/sbin/nologin apiservice
sudo mkdir -p /opt/webapp/apiservice /opt/webapp/blog
sudo chown -R apiservice:apiservice /opt/webapp

Web 服务器使用 Caddy,配置反向代理将 /api/deploy 路由转发给后端的 Python 服务,同时托管通过软链接指向的静态页面:

1
2
3
4
5
6
7
api.sinkalex.ac.cn {
...

handle /api/deploy {
reverse_proxy 127.0.0.1:3000
}
}

Python 虚拟环境与依赖安装

为了不污染系统的全局 Python 环境,我们需要为 apiservice 服务单独创建一个虚拟环境(venv)。

1
2
3
4
5
6
7
8
9
10
# 切换到 apiservice 用户
sudo su - -s /bin/bash apiservice
cd /opt/webapp/apiservice

# 创建并激活虚拟环境
python3 -m venv venv
source venv/bin/activate

# 安装后端框架及腾讯云 SDK
pip install fastapi uvicorn python-multipart python-dotenv tencentcloud-sdk-python-teo

腾讯云 CAM 权限配置

由于部署完成后需要自动刷新 EdgeOne CDN 缓存,我们需要调用腾讯云 API。出于安全考虑,不要使用主账号的 API 密钥。

我们需要在腾讯云访问管理(CAM)中创建一个子账号:

  1. 新建用户,访问方式仅勾选 “编程访问”。
  2. 权限策略搜索并勾选 QcloudTEOFullAccess。
  3. 创建成功后,保存好生成的 SecretId 和 SecretKey。

[!NOTE]

在 /opt/webapp/apiservice/ 目录下创建 .env 文件,存入上述密钥以及自定义的 apiservice secret key。
同时将 .env 所有权转交给 apiservice,并将权限设为 600

1
2
3
nano /opt/webapp/apiservice/.env
chown -R apiservice:apiservice /opt/webapp/apiservice/.env
chmod 600 /opt/webapp/apiservice/.env
1
2
3
4
5
6
7
```

### apiservice 服务端实现 (FastAPI)

在同目录下创建 `index.py`,这是接收请求、校验签名并执行部署的核心逻辑:

```python

配置 Systemd 守护进程

为了让 apiservice 服务在后台稳定运行并支持开机自启,我们需要编写一个 systemd 服务单元。退出 apiservice 用户,回到拥有 sudo 权限的账号,创建文件 /etc/systemd/system/hexo-apiservice.service

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[Unit]
Description=Hexo apiservice Deployment Service
After=network.target

[Service]
User=apiservice
Group=apiservice
WorkingDirectory=/opt/webapp/apiservice
# 指定虚拟环境的 bin 目录,确保 uvicorn 能正确加载依赖
Environment="PATH=/opt/webapp/apiservice/venv/bin"
ExecStart=/opt/webapp/apiservice/venv/bin/uvicorn index:app --host 127.0.0.1 --port 3000
Restart=always

[Install]
WantedBy=multi-user.target

保存后,重载 systemd 并启动服务:

1
2
3
4
sudo systemctl daemon-reload
sudo systemctl enable --now hexo-apiservice
# 检查服务运行状态
sudo systemctl status hexo-apiservice

GitHub Actions 工作流配置

最后,在 Hexo 仓库根目录新建 .github/workflows/deploy.yml。这里的核心在于通过 Shell 脚本计算与服务端一致的 HMAC 签名,并将其放在 Header 中随文件一同发送。

需要在 GitHub 仓库的 Settings -> Secrets and variables -> Actions 中提前配好 DEPLOY_TOKEN(值与服务端的 SECRET_TOKEN 一致)。

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
name: Deploy Hexo

on:
push:
branches: [ master ]

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install and Build
run: |
npm ci || npm install
npx hexo generate

- name: Zip Files
run: |
cd public
zip -r -q ../blog.zip ./*
cd ..

- name: Upload to apiservice
run: |
TIMESTAMP=$(date +%s)
FILE_HASH=$(sha256sum blog.zip | awk '{print $1}')
PAYLOAD="${TIMESTAMP}.${FILE_HASH}"

# 计算 HMAC-SHA256 签名
SIGNATURE=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "${{ secrets.DEPLOY_TOKEN }}" | awk '{print $2}')

curl -X POST -F "file=@blog.zip" \
-H "X-Timestamp: $TIMESTAMP" \
-H "X-Signature: $SIGNATURE" \
https:///api/deploy

结语

test article

测试文章

测试一下

Hello World

Welcome to Hexo! This is your very first post. Check documentation for more info. If you get any problems when using Hexo, you can find the answer in troubleshooting or you can ask me on GitHub.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Run server

1
$ hexo server

More info: Server

Generate static files

1
$ hexo generate

More info: Generating

Deploy to remote sites

1
$ hexo deploy

More info: Deployment