上一篇文章中我们已经把博客跑起来了。但这里留下几个问题:
- 服务器配置可能比较低,构建静态网页的过程可能会让服务器资源耗尽。
- 目前工作流是其他设备写文章,然后使用
git推送到GitHub上。然后 SSH 登陆服务器再进行git pull && pnpm build,太笨重了。
这一篇搭建一套自动化流程:本地写完文章 git push,GitHub Actions 在云端构建,构建完成后 Webhook 通知服务器自动拉取。之后更新博客只需将文章推送到 GitHub 即可。
思路
服务器只负责展示构建结果,不负责构建过程。把构建拆到 GitHub Actions 上,构建完产物推到独立的 build 分支,触发 GitHub Webhook 主动推送消息。
GitHub Actions:免费、配置高、速度快的 CI 机器
整体链路:
git push
│
▼
GitHub Actions: checkout → install → build → push dist/ 到 build 分支
│
▼
GitHub Webhook 发 POST 到服务器
│
▼
服务器 webhook 服务:校验 HMAC → git pull → Nginx 自动生效
构建 GitHub Actions
要使用 GitHub Actions 要在项目根目录下的 .github/workflows 目录编写部署脚本,用于构建博客静态内容后推送到 build 分支。
# .github/workflows/deploy.yml
name: Build and Push to build branch
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
jobs:
build-and-push:
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install pnpm
uses: pnpm/action-setup@v4
with:
version: "11.3.0"
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "24"
cache: "pnpm"
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: pnpm run build
- name: Push dist to build branch
run: |
cd dist
git init
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add -A
git commit -m "Build: $(date -u +'%Y-%m-%d %H:%M:%S UTC')" || exit 0
git push --force \
"https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git" \
HEAD:build
关键点:
permissions: contents: write允许 Action 推送代码。- 最后一步进入
dist/目录,初始化独立 git 仓库,force push 到build分支。每次只保留一个 commit,不膨胀 git 历史。 GITHUB_TOKEN是 GitHub 自动提供的,不需要额外配置 secrets。
服务器 Webhook 配置
接下来让服务器感知到 build 分支的更新。
可以使用 webhook 这个轻量工具来接收 Github Webhooks 的请求。GitHub 收到 push 后向服务器发一个带签名的 POST 请求,服务器校验后执行 git pull。
安装 webhook
sudo apt install -y webhook
编写 webhook 配置文件
// -> /etc/webhook/hooks.json
[
{
"id": "blog-deploy",
"execute-command": "/etc/webhook/deploy.sh", // <---- 触发脚本
"command-working-directory": "/var/www/blog/dist", // <---- 目标文件夹
"trigger-rule": {
"match": {
"type": "payload-hmac-sha256",
"secret": "替换成你自己的随机密钥",
"parameter": {
"source": "header",
"name": "X-Hub-Signature-256"
}
}
}
}
]
HMAC 签名校验保证只有 GitHub(持有同一个 secret)才能触发脚本。使用下面命令可以生成密钥:
openssl rand -hex 32
编写执行脚本
这是 webhook 触发后实际执行的脚本。逻辑很简单:
- 写日志:每次触发都记录时间戳到
/tmp/webhook-deploy.log,方便事后排查这次 webhook 到底有没有执行、什么时候执行的。 - 进入工作目录
git fetch+git reset --hard:之所以不用git pull,是因为 GitHub Actions 对build分支是 force push,本地和远端历史完全不重合,git pull会报冲突。fetch拿到最新远端状态后reset --hard到origin/build,干净利落地覆盖本地所有文件。 - 拉取成功写入日志
#!/bin/bash
set -euo pipefail
LOG_FILE="/tmp/webhook-deploy.log"
log() {
echo "[$(date '+%F %T')] $*" >> "$LOG_FILE"
}
log "Deploy triggered"
cd /var/www/blog/dist
git fetch origin build
git reset --hard origin/build
git pull
log "Deploy successful"
sudo chmod +x /etc/webhook/deploy.sh
systemd 服务
webhook 需要以守护进程方式一直在后台运行,用 systemd 管理最合适。几个关键配置:
Type=simple:webhook 是一个长期运行的前台进程,不会自己 fork 到后台,simple是 systemd 对这种进程的标准处理方式。ExecStart:-hooks指定配置文件路径,-port 9000让 webhook 只监听本地回环(等会儿用 Nginx 反向代理对外暴露),-verbose把执行日志打到 journald,方便journalctl -u webhook查看。Restart=always:进程崩溃或意外退出时自动重启,保证 webhook 始终可用。User=<your_username>:用你自己的用户名运行 webhook,这样/var/www/blog/dist的权限和你手动 clone 时的用户一致,不需要额外chown。如果用www-data,clone 仓库后还得把目录所有者改过去,多一步操作,还容易忘。
编写 systemd 单元文件:
sudo vim /etc/systemd/system/webhook.service
# /etc/systemd/system/webhook.service
[Unit]
Description=Webhook server
After=network.target
[Service]
Type=simple
ExecStart=/usr/bin/webhook -hooks /etc/webhook/hooks.json -port 9000 -verbose
Restart=always
User=<your_username> # <---- 修改成自己的用户名
[Install]
WantedBy=multi-user.target
重载 systemd 并设置开机自启:
sudo systemctl daemon-reload # 让 systemd 识别新写的 service 文件
sudo systemctl enable --now webhook
Nginx 反向代理
在 blog.conf 的 443 server 块中加一个 location,把 /hooks/ 代理到 webhook:
location /hooks/ {
proxy_pass http://127.0.0.1:9000/hooks/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
重载 Nginx:
sudo nginx -t && sudo systemctl reload nginx
我们也可以设置单独的子域名 hooks.huazaiki.com,只要和 Github Webhooks 对照上即可。
GitHub Webhook
去 GitHub 仓库 Settings → Webhooks → Add webhook:
| 字段 | 值 |
|---|---|
| Payload URL | https://hooks.huazaiki.com/blog-deploy |
| Content type | application/json |
| Secret | 用 openssl rand -hex 32 生成的那个 |
| Events | Just the push event |
添加后 GitHub 发一个 ping 测试连通性,显示绿勾就算通了。
踩坑记录
实际配置中踩了几个坑,记下来免得下次再掉进去。
webhook 用哪个用户运行。 一开始我照着教程把 User 设成了 www-data,结果 webhook 触发后 git fetch 因为权限不足静默失败——仓库是我自己的用户 clone 的,www-data 读不了。后来直接把 service 文件里的 User 改成自己的用户名(比如 blogadmin),就绕过了权限问题。比 chown -R www-data:www-data /var/www/blog 更直接,也不用担心以后自己手动进去 git pull 调试时又把文件所有者改回去。
日常使用
配置完成后的日常流程:
-
在本地机器上编写文章
-
git 推送,剩下的全自动
git add . && git commit -m "新文章" && git push origin main
1-2 分钟后访问博客,文章已经在上面了。一次配置,长期省心。