使用 GitHub Actions 自动化 Hugo 博客部署

最近在学习 GitHub Actions,GitHub Actions 是 GitHub 提供的一个特性,可以用来自动化执行一些步骤。在软件开发中,最常见的需要自动化的场景可能就是构建了。对于编译型的编程语言(比如 C/C++)编写的软件,通常需要编写对应的构建的脚本,软件构建的过程涉及到:环境准备、依赖下载、启动构建等。不过,利用 GitHub Actions 来自动化软件构建过程并不是本文的主题。在我思考我可以将 GitHub Actions 用于何处的时候,我想到了:利用 GitHub Actions 来自动化 Hugo 博客的部署。因为 Hugo 博客的部署也涉及到不少一系列固定的步骤 :)

我的 Hugo 博客包含了 2 个仓库

  • public 仓库 - martinlwx.github.io,存放了生成的 Hugo 静态网站
  • private 仓库,该仓库存放了 Hugo 的配置文件,以及博客文章的原始 Markdown 文件。private 仓库下存在一个 ./public 目录,是一个 git submodule,指向了前面提到的 public 仓库

大概长下面这个样子

flowchart LR
  private("Private repo") -->|git push| public("Public repo: martinlwx.github.io")

警告
如果你没有使用 Algolia 的话,你就没有必要上传 index.json 文件
部署 Hugo 博客的流程是这样子的:先写好文章,然后用 Hugo 生成静态博客,然后将生成的 index.json 文件用 atomic-algolia 上传到 Algolia,所以我之前编写了如下的 shell 脚本(取名为 deploy.sh

sh

#!/bin/bash

echo "Deploying hugo website..."
# delete all the files in public folder
rm -rf public/*

# build the website
hugo -t DoIt

# upload index.json
node push_algolia_json.js

# deploy the website
msg="rebuild site on $(date)"
echo "$msg"
cd public || exit
git add .
git commit -m  "$msg"
git push

# backup private repo
cd .. || exit
git add .
git commit -m "backup hugo on $(date)"
git push

echo "Git push successfully!"

那么,我就可以用如下的命令完成 Hugo 博客的部署

sh

$ bash deploy.sh

在 GitHub Actions 里面,涉及到许多概念,包括 Workflow、Event、Job、Action、Step

Workflow 里面可以包含一到多个 Job,每个 Job 里面则包含了多个 Step,每个 Step 可以运行 Shell 命令、Shell 脚本或者是执行 action,这里的 action 可以看成是别人写好的脚本,通常是一些复杂而重复的操作。通过复用别人的 action 我们就极大程度减轻了编写 Workflow 的工作量

Workflow 通常被我们设置好的 Event 触发,Event 包括跟 git 有关的事件,比如 git push、收到 PR 请求等。触发之后 workflow 就会开始运行了。用图表示就是

timeline
   Event: push
        : pull request
        : ...
   Job1 : Step 1. Run action
        : Step 2. Run action
        : Step 3. Run action
        : Step 4. Run action
        : ...
   Job2: Step 1. Run action
       : Step 2. Run action
       : Step 3. Run action
       : Step 4. Run action
       : ...

我们可以用一个 YAML 文件来描述 Workflow,这个 YAML 文件会放到 git 仓库里面和源代码一起管理,详细的语法可以在官网找到

信息
下面假定你已经对 GitHub Actions 的基本语法有一定的了解,这不是本文的重点 :)

接下来,我们要尝试将前面提到的 Shell 脚本变成 GitHub Actions 对应的 YAML 文件(取名为 build_and_deploy.yaml 文件)

yaml

name: hugo-deploy
on:
  push:
    branches:
      - master

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Check out this repo
        uses: actions/checkout@v4
        with:
          submodules: 'recursive'
          fetch-depth: 0
      - name: Install Hugo CLI
        uses: peaceiris/actions-hugo@v3
        with:
          hugo-version: 'latest'
          extended: true
      - name: Install node.js
        uses: actions/setup-node@v4
        with:
          node-version: 'latest'
      - name: Install atomic-algolia
        run: npm install atomic-algolia
      - name: Build the website with the DoIt theme
        run: hugo -t DoIt
      - name: Upload index.json for search
        run: node push_algolia_json.js
      - name: Deploy
        uses: peaceiris/actions-gh-pages@v4
        with:
          deploy_key: ${{ secrets.HUGO_PRIVATE_KEY }}
          external_repository: Martinlwx/martinlwx.github.io
          publish_branch: main
          publish_dir: ./public

其中 actions/checkout 是一个别人写好的 action,可以用来将 git 仓库下载到本地,我的 private 仓库涉及到 2 个 git submodule

这里需要把他们都 clone 到本地,所以设置 submodules: truefetch-depth: 0 则会在 clone 的时候将所有的 commit 都 clone 下来


接下来我们要安装 Hugo,虽然官网上给出了 详细的步骤,但是我觉得较为繁琐,于是我找到了 actions-hugo,该 action 可以自动帮我们下载并安装好 Hugo,hugo-version: 'latest' 则是让它每次都用最新版本的 Hugo。因为 DoIt 使用了 admonition 等特性,所以还需要设置 extended: true,也就是使用 extended 版本的 Hugo


警告
如果你没有使用 Algolia 的话,你就没有必要安装 node.js,也没有必要安装 atomic-algolia,也没有必要进行 index.json 文件的上传

安装 node.js 也是同样的道理,已经有别人写好的了,这里不再赘述

在安装完 node.js 之后,用 npm 安装 atomic-algolia,这样后面就可以上传 index.json 文件了


在上传 index.json 文件之前,我们需要先用 Hugo 生成静态博客,这样才能保证 index.json 文件是最新的。跟在本地生成 Hugo 静态博客一样,用 hugo -t DoIt 指定主题并生成即可


生成 index.json 文件之后就可以用 node push_algolia_json.js 上传了,push_algolia_json.js 文件怎么写可以看 atomic-algolia 文档


最后,就涉及到 Hugo 博客的部署了,actions-hugo 的同一个作者还写了另外一个 action actions-gh-pages 可以用来快速部署。这里情况稍微复杂一些,前面提到,我有 1 个 public 仓库和 1 个 private 仓库,这里要做的事情其实是要从 private 仓库部署到 public 仓库。根据 actions-gh-pagesREADME.md 文档,需要设置 external_repository,将其指向我的 public 仓库的路径,格式也很好懂。另外因为部署要跨 git 仓操作,所以需要设置 deploy_key,这意味着我们需要生成 ssh-key 并分别添加到不同的 git 仓库里,这是相对比较麻烦的一步

flowchart LR
  private("`
  Private repo
  (use the *private* ssh-key as a *secret*)
  `") -->|peaceiris/actions-gh-pages@v4| public("`
  Public repo
  (use the *public* ssh-key as a *deploy key*)
  `")

首先,用自己的电脑生成配对的 ssh-key

sh

# replace with your email here.
$ ssh-keygen -t ed25519 -C martinlwx@163.com -f "hugo"

当前目录,你就可以看到 hugohugo.pub 这两个文件了,接下来

  1. 点开 public 仓库Settings -> Deploy keys -> Add deploey key,将 hugo.pub 的内容添加进去,这里 Title 取什么名字无关紧要,要紧的是设置 Allow Write Access
  2. 点开 private 仓库Settings -> Secrets and variables -> Actions -> Manage repository secret,将 hugo 的内容添加进去,Title 可以取作 HUGO_PRIVATE_KEY,就可以在 deploy_key 字段里面引用 ${{ secrets.HUGO_PRIVATE_KEY }}

剩下没有解释的几个配置项

  • publish_branch: main,看名字也很好懂,这里指的是部署的时候要部署到哪个分支,我的 public 仓库 只有 main 分支,所以就这么设置了
  • publish_dir: ./public,这里指的是你的 private 仓库生成 Hugo 静态博客的时候输出到什么文件夹,我的就是输出到 ./public 文件夹

上面的步骤都完成之后,在 private 仓库的根目录新建 .github/workflows 目录,并将如上的 YAML 保存在里面,git add & git push 一下就好了。之后点开仓库的 Actions 就可以看到该 workflow 了,如下所示

在搭建完流水线之后,我发现我的工作量似乎变多了,本来 bash deploy.sh 就能搞定的事情,现在需要自己 git addgit commit(不过这也是因为之前的 commit message 是调用命令生成的)。但不管怎么说,还是从中学习到了 GitHub Actions 的使用,自动化部署 Hugo 博客是一次有意思的 CI/CD 实践。希望本文能给屏幕前的你带来帮助 :)