利用Github action构建Hexo博客

自从GitHub action可以免费使用后,我就开始想利用它解决博客的构建和部署的问题。直到今天,终于实现了自己比较满意的GitHub action。

Hexo博客在使用时,存在几点不方便的地方:

  1. Hexo的初始化需要安装各种依赖库,这点本文暂时也没能实现开箱即用。
  2. 当更换电脑后,又需要重新配置一遍本地的Hexo环境
  3. Hexo deploy命令在实际使用中并不方便

我想要的是,配置一次,之后就无需关心hexo的问题,可以让我专注于写文章。而GitHub action就提供了这样功能,我们可以把hexo的配置放在GitHub仓库里,然后每次更新文章(Markdown文件)后,GitHub action就自动根据仓库里的hexo配置去构建博客并且发布到相应的博客仓库。

我的总体思路就是,仓库A有两个分支,分别是masterhexo-settingsmaster放的是Markdown文件,也就是hexo博客中的source/_posts里的文件,hexo-settings放的是hexo的配置信息,也就是,hexo博客源码中,除了node_modulessource之外的所有文件。同时,master分支作为hexo-settings的一个submodule,路径就是source/_posts,主题也作为hexo-settings的一个submodule,路径当然就是themes/theme_name了。这样,当git clone hexo-settings后,只需要同时拉取submodules,就可以获得完整的hexo博客源码了。Github action就是这样,先拉取完整的源码,然后再根据packages.json安装相关依赖(node_modules),就可以生成html文件了。

生成html文件之后,还需要部署到博客仓库里(name.github.io)。这里,我采用的是在本地新建一个空的git仓库,把生成的文件(public文件夹里的文件)都放进去,然后强制把这个本地仓库push到博客仓库,覆盖原有的内容。这样的好处是,博客仓库永远都只有一个commit,别人没办法获得我的博客的历史版本。

经过一番折腾,最后的成品在本文文末。下面是对该action代码的解释,如果只想要结果,可以直接拉到文末获取代码。

第一块代码是on。表示action被触发的条件,我写了两个,一个是push事件发生时,一个是watch。watch这个是抄来的,我在网上看到有人说加这个watch代码就可以实现监控star事件,也就是当我点击项目的star按钮时,action就会执行,是个很有趣的功能。

第二块是job,是action的任务内容。env是环境变量,这里的HEXO_BRANCH表示的是hexo配置信息所在的分支,MD_BRANCH是Markdown文件所在分支(即该分支内容就是_posts文件夹的内容)。需要根据实际分支名进行修改。接下来steps里是任务的步骤。

步骤Get repo name and username是从GitHub提供的环境变量$GITHUB_REPOSITORY中获取当前的仓库A的名字和用户名作为环境变量,用于后续步骤。其中,用户名是用来拼接出最后要部署到的仓库名USER_NAME/user_name.github.io。步骤Init Env也是设置环境变量。其中,HEXO_ENV_PATH是hexo配置的绝对路径,BLOG_PATH是本地新建的空的git仓库的绝对路径。

步骤Cache pandoc and npm实现了npmpandoc安装包的缓存,GitHub就会帮我们保存这些内容,这样下一次执行该action时,就可以从GitHub的服务器获取保存的内容,无需花时间去网络上下载。这里我缓存的是~/.npm目录,然后我发现似乎只能有一个缓存,所以我把pandoc的安装包也放在这个目录里,一起保存了。如果缓存不命中,那就只能重新下载pandoc安装包了。

步骤Prepare hexoUpdate content分别是安装博客的依赖和构建博客,并且把生成的文件都移动到上面说的空的git仓库。注意这里用的是npm run build,这个命令会执行packages.jsonscripts下的build命令。因为我的build命令比较复杂,所以都写在那里了。如果只想要hexo generate,那可以直接把npm run build替换成hexo generate

最后,步骤Add all to the empty git就是把新的仓库里的所有文件commit了。再用github-push-action把这个新的仓库强制push到博客仓库。这里的变量secrets.ADMIN也是要根据实际进行修改。需要在GitHub上的博客仓库的设置里新建一个personal access token,然后把这个token放到仓库A的secrets里,也就是仓库A设置里的Secrets选项。Secrets选项设置的就是一个键值对,我设置的键就叫做ADMIN,值就是前面那个token。详细的token设置可以参考GitHub官方文档

至此,我的GitHub博客基本已经不需要去打理hexo部分的内容,只需要专心写md文件,写完push上去,就会自动发布到博客里。我也尝试过把这个action代码只放在hexo-settings分支里,这样master分支就没有action代码了,所以当有文章更新时,并不会马上发布,而是需要我点击star按钮才会发布,可以理解为:star按钮就是“发布”按钮。

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
name: update

on:
push:
branches:
- master
- hexo-settings
watch:
type: [started]

jobs:
build:
runs-on: ubuntu-latest
env:
HEXO_BRANCH: hexo-settings
MD_BRANCH: master

HEXO_FOLDER: _hexo_env
BLOG_FOLDER: _blog

steps:

- name: Get repo name and username
run: |
echo "USER_NAME=$(echo $GITHUB_REPOSITORY | awk -F / '{print $1}')" >> $GITHUB_ENV
echo "REPO_NAME=$(echo $GITHUB_REPOSITORY | awk -F / '{print $2}')" >> $GITHUB_ENV
- name: Init Env
run: |
echo "MD_PATH=$(dirname $GITHUB_WORKSPACE)/$REPO_NAME" >> $GITHUB_ENV
echo "HEXO_ENV_PATH=$(dirname $GITHUB_WORKSPACE)/${{ env.REPO_NAME }}/$HEXO_FOLDER" >> $GITHUB_ENV
echo "BLOG_PATH=$(dirname $GITHUB_WORKSPACE)/${{ env.REPO_NAME }}/$BLOG_FOLDER" >> $GITHUB_ENV
echo "BLOG_REPO_NAME=${{ env.USER_NAME }}/$(echo ${{ env.USER_NAME }} | awk '{print tolower($0)}').github.io" >> $GITHUB_ENV
- uses: actions/checkout@v2
with:
persist-credentials: false
fetch-depth: 0
path: ${{ env.MD_PATH }}
ref: ${{ env.MD_BRANCH }}

- uses: actions/checkout@v2
with:
persist-credentials: false
fetch-depth: 0
path: ${{ env.HEXO_ENV_PATH }}
ref: ${{ env.HEXO_BRANCH }}

- uses: actions/setup-node@v2
with:
node-version: '14'

- name: Init an empty git for blog deploying
run: |
mkdir $BLOG_PATH
cd $BLOG_PATH
git init
- name: Cache pandoc and npm
id: cache-pn
uses: actions/cache@v1
env:
cache-name: cache-pandoc-npm
with:
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package.json') }}
restore-keys: ${{ runner.os }}-build-${{ env.cache-name }}

- name: Download pandoc
if: steps.cache-pn.outputs.cache-hit != 'true'
run: |
cd ~
mkdir -p .npm
cd .npm
sudo apt install -y wget
wget -O pandoc.deb https://github.com/jgm/pandoc/releases/download/2.9.2/pandoc-2.9.2-1-amd64.deb
- name: Install pandoc
run: sudo dpkg -i ~/.npm/pandoc.deb

- name: Prepare hexo
run: |
cd $HEXO_ENV_PATH
npm install -g hexo-cli
npm install
git clone https://github.com/next-theme/hexo-theme-next themes/next
cat ./package.json
npm --version
hexo version
- name: Update content
run: |
cd $MD_PATH
shopt -s extglob
mkdir -p $HEXO_ENV_PATH/source/_posts
cp -r !(.gitignore|.git|.github|.|..|$HEXO_FOLDER|$BLOG_FOLDER) -t $HEXO_ENV_PATH/source/_posts
cd $HEXO_ENV_PATH
npm run build
cp -r public/* -t $BLOG_PATH
- name: Add all to the empty git
run: |
cd $BLOG_PATH
git status
git config --local user.email "blog-bot@bot.bot"
git config --local user.name "blog-bot"
git add .
git commit -m "Update blog"
- name: Deploy
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.ADMIN }}
directory: ${{ env.BLOG_PATH }}
repository: ${{ env.BLOG_REPO_NAME }}
force: true