从“手工作坊”到“自动化工厂”:我的 Hexo 博客 CI/CD 踩坑与实践实录

作为一个计算机科学与技术专业的大四学生,折腾个人博客似乎是大学生涯的“必修课”。在此之前,我的 Hexo 博客(搭载了好看的 Butterfly 主题)一直采用的是最原始的本地部署方式:每次写完文章,都要在本地终端敲下 hexo clean && hexo g -d

虽然命令不复杂,但这存在几个致命痛点:

  1. 重度依赖本地环境:万一电脑重装系统,或者换了台电脑,又得重新配置 Node.js 和一大堆依赖,简直是场灾难。
  2. 缺乏版本控制:只有生成的静态网页托管在云端,博客的 Markdown 源码如果忘记备份,一旦硬盘损坏,心血全部白费。
  3. 不够“运维”:作为一个未来想从事运维(DevOps)方向的人,怎么能忍受这种纯手工的低效操作呢?

为了解决这些问题,我决定引入现代软件工程的基础设施——CI/CD(持续集成与持续部署),把发布的脏活累活全部交给 GitHub Actions。这篇文章记录了我的整个折腾过程以及踩过的那些“大坑”。

一、 架构设计:双分支策略

在开始之前,我们需要对 GitHub 仓库的结构进行改造。采取“源码与产物分离”的原则:

  • hexo-source 分支:用来存放博客的所有原材料(Markdown 文章、主题文件、_config.ymlpackage.json 等)。这是我们平时打交道的分支。
  • main 分支:用来存放 Hexo 编译后生成的纯静态 HTML/CSS/JS 文件。GitHub Pages 直接读取这个分支对外展示。

二、 核心图纸:编写 GitHub Actions 工作流

在博客根目录下创建 .github/workflows/deploy.yml 文件。这就好比给 GitHub 机器人写了一份 SOP(标准作业程序)。

YAML

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
name: Hexo Blog CI/CD

# 触发条件:当向 hexo-source 分支 push 代码时触发
on:
push:
branches:
- hexo-source

permissions:
contents: write
pages: write
id-token: write

jobs:
build-and-deploy:
runs-on: ubuntu-latest # 分配一台最新的 Ubuntu 虚拟机

steps:
# 1. 拉取源码
- name: Checkout Source Code
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0 # 关键:拉取所有提交历史,防止文章时间全部变成今天

# 2. 配置 Node.js 环境
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

# 3. 依赖缓存(加速下次构建)
- name: Cache dependencies
uses: actions/cache@v3
with:
path: ~/.npm
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}

# 4. 安装依赖(根据 package.json)
- name: Install Dependencies
run: npm ci

# 5. 构建静态网页
- name: Build Hexo
run: npx hexo generate

# 6. 部署到 main 分支
- name: Deploy to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
publish_branch: main
commit_message: 'deploy: auto deploy by GitHub Actions'

当这套流程跑通后,我只需要执行 git push,剩下的机器会自动帮我完成。

三、 血泪踩坑记(重点)

你以为写完脚本就万事大吉了?实际上,我在跑这套流水线时经历了无数个报错和“白屏”,以下是排查和解决的过程:

坑 1:GitHub 机器人权限不足 (Failed in 10s)

一开始跑流水线,只用了 10 秒就亮起了红灯报错。

  • 原因:GitHub 默认的安全策略限制了 Action 的写权限,机器人没法把代码推送到 main 分支。
  • 解决:进入仓库 Settings -> Actions -> General,滚动到底部将 Workflow permissions 改为 Read and write permissions

坑 2:网页部署成功,但打开是白屏!

Actions 一路绿灯,打开博客却是一片空白,查看网页源码发现也是空的。

  • 原因 1:主题变成“空壳”。因为 Butterfly 主题本身是一个 Git 仓库,直接丢进源码里会被识别为 Submodule(子模块)。如果没有配置 .gitmodules,云端拉下来的主题文件夹就是空的。
    • 解决:进入本地 themes/Butterfly删掉隐藏的 .git 文件夹,将其降维成普通文件夹,然后重新 git add 并推送。
  • 原因 2:缺少渲染器插件。本地偷偷装过 Pug 和 Stylus 渲染器,但没写进配置里,导致云端机器人不会翻译主题模板。
    • 解决:执行 npm install hexo-renderer-pug hexo-renderer-stylus --save,强制将其写入 package.json,一起推送到云端。

坑 3:本地 Git 一直报错 Connection was reset

代码修好了,却死活推不上 GitHub。

  • 原因:本地网络环境问题,Git 客户端迷路了,没走本机的代理端口。

  • 解决:给 Git 配置本地代理。由于我的代理软件端口是 7897,执行以下命令精准放行:

    Bash

    1
    2
    git config --global http.proxy http://127.0.0.1:7897
    git config --global https.proxy http://127.0.0.1:7897

坑 4:所有文章的发布时间都变成了“今天”

好不容易看到界面了,结果发现所有历史文章的日期全变了。

  • 原因:由于 Hexo 默认读取文件的“创建时间”作为文章日期。在 GitHub Actions 启动的全新虚拟机里,源码文件都是“刚刚”拉取下载的,所以全变成了今天。
  • 解决:在前面的 deploy.yml 脚本中,给 checkout 步骤加上 fetch-depth: 0 拉取完整历史。当然,最稳妥的办法还是在每篇 Markdown 文件的头部(Front-matter)老老实实写上 date: YYYY-MM-DD

四、 总结

将博客从手动挡升级到自动挡,虽然中间踩了不少坑,但也让我真切体会到了自动化运维的魅力。现在,无论我是在图书馆的电脑上,还是用平板,只要能访问 GitHub,就能随时修改发布我的文章。

这也算是我迈向专业开发/运维道路上的一个小小里程碑。技术折腾不止,期待未来能在服务器、网络和架构上探索更多有趣的玩法!