计算机技术学习札记

用 Hugo 配合 GitHub Actions 和 GitHub Pages 搭建博客

说明:目前本网站已经不再使用 Hugo,转而使用自有后端。

另注:本文写于我对 GitHub Actions 还不熟悉之时,其中可能有许多不「优雅」的表述。

之前利用 GitHub Student Developer Pack 白嫖的一年的 .me 域名和英国 PHP 主机最近就要到期了。由于一个众所周知的原因(穷),未来暂时没有长期购买主机的计划,因此决定将 Blog 重新迁回 GitHub Pages。这篇文章将记述一下自己用 Hugo 重构自己博客的历程。

开始之前

Jekyll, Hexo 还是 Hugo?

静态博客生成器就是用来把一组 Markdown 文档渲染成 HTML 页面的程序。

一般提到静态博客生成器,人们都会想到 JekyllHexo。这俩在静态博客这个领域相当成熟,前者更是 GitHub Pages 的官方选择。

相比之下,Hugo 作为一个版本号甚至没有突破 1 的后起之秀,似乎显得没有那么出名而广为人知。但事实上,Hugo 与前二者相比,虽然可能在功能上,以及主题、插件的丰富度上不如它们,但也有着不少的优点,尤其是它的快速(Go 的性能优势,以及 Hugo 本身定位“轻量级”)和干净(Hexo:node_modules 警告)。

结合考虑多方面的因素,我选择了 Hugo。

GitHub Actions!

虽然是静态博客,但配合 GitHub Actions 这种 Serverless 的服务也可以让静态博客的更新变得稍微方便一些。原有的静态博客的更新流程,是在文章写好之后,在本地机器上编译成静态的 HTML 文件,再 push 到 GitHub Pages 的仓库里。这样需要在写文章之后至少本地跑一次 Hugo。而利用 GitHub Actions,这一次“跑 Hugo”的过程可以挪到远端进行,并且自动触发。

具体地,我们需要两个仓库:一个是公开的页面仓库 username.github.io,另一个是公开或者私有的源码仓库 blog-source-code。前一个仓库配置为 GitHub Pages 仓库。

另注:也可以只用一个仓库 username.github.io 并使用两个不同的分支。

我们需要配置 GitHub Actions,使得当源码仓库更新的时候(例如,push 了一篇新的 .md 文件),远端自动运行一次 Hugo,生成好新的网页文件并存入 username.github.io 仓库。这样,我们只需要直接 push 新的文章文件,就能在 GitHub Actions 执行结束之后得到一个更新完成的网站了。

幸运的是,现在已经有项目 peaceiris/actions-gh-pages 帮我们模板化了这一系列过程。下面我们将详细介绍过程。

配置 GitHub Pages 和 GitHub Actions

准备两个仓库

如上文所言,需要准备两个 GitHub 仓库:username.github.ioblog-source-code。前者必须是公开(Public)的(如果你有 GitHub PRO 的话,也可以为私有仓库配置 Pages),后者可以是私有(Private)的。

生成一对 SSH 秘钥

接下来需要配置一对 SSH 秘钥。在上面的叙述中提到,blog-source-code 这个仓库会将编译好的页面放入 username.github.io,这意味着前者运行的 Actions 必须要有权限向后者进行 push。有三种方式可以实现这个过程的鉴权,这里我们选择传统的 SSH 秘钥。在 Shell 中运行

$ ssh-keygen -t rsa -b 4096 -C "$(git config user.email)" -f gh-pages -N ""

我们会得到两个文件:gh-pages.pub(公钥,给 username.github.io)以及 gh-pages(私钥,给 blog-source-code)。

为两个仓库部署秘钥

  • 部署源码仓库的秘钥。首先打开源码仓库 blog-source-code,找到 Settings → Secrets。点击 New repository secret 来添加一条秘钥,秘钥的名字设置为 ACTIONS_DEPLOY_KEY,秘钥内容为 gh-pages 文件的内容。

  • 部署页面仓库的秘钥。打开页面仓库 username.github.io,找到 Settings → Deploy Keys。点击 Add deploy key 来添加一条秘钥,秘钥的名字任意,内容为 gh-pages.pub 文件的内容。

安装 Hugo 并配置源码仓库

Hugo 的安装请参见官方文档。作为一个 Go 语言程序,它不需要借助其他包管理器(比如 Node.JS 的 npm)来安装。

将空的源码仓库克隆到本地:

git clone git@github.com:username/blog-source-code.git

初始化 Hugo 站点。执行下面的语句,可能会提示需要 --force 参数,加上即可(这是因为那个目录并非是空的,内有 .git 文件夹)

hugo new site ./blog-source-code

这样这个站点就配置好了。安装主题(注意主题如果是从 GitHub 克隆来的,请使用 submodule)、调整页面、添加文章等步骤不再赘述。现在我们将这个站点源码 push 到远端:

git add .
git commit -m "New site!"
git push -u origin master

配置 GitHub Actions

上一步完成之后,在 GitHub 打开源码仓库,点击 Actions → set up a workflow yourself。将下面的代码保存为 main.yml 文件,然后 commit。

name: GitHub Pages

on:
  push:
    branches:
      - master

jobs:
  deploy:
    runs-on: ubuntu-20.04
    steps:
      - name: Checkout repositories
        uses: actions/checkout@v2
        with:
          submodules: true  # Fetch Hugo themes (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'

      - name: Build
        run: hugo

      - name: Deploy
        uses: peaceiris/actions-gh-pages@v3
        with:
          deploy_key: ${{ secrets.ACTIONS_DEPLOY_KEY }}
          external_repository: criwits/criwits.github.io
          publish_branch: main
          publish_dir: ./public
          cname: criwits.top

需要进行手动修改的部分集中在代码最后几行。分别是

  • external_repository:设置成页面仓库 username.github.io 的地址。

  • publish_branch:设置成页面仓库的主分支,比如 master 或者 main

  • cname:设置成网站的域名。如果使用 .github.io 域名就不需要这一行。

提交(commit)之后,回到本地 blog-source-code 的 git 命令行,输入

git pull origin master

来拉取更改。

现在,源码仓库的 GitHub Actions 已经配置完成了。当每一次对 blog-source-code 仓库进行 push 的时候,都会触发 GitHub Pages 这个 Action,进而将编译好的页面推送至 username.github.io 仓库,实现博客的自动化编译。

MathJax 和 Hugo 的冲突与解决

用于渲染数学公式的 MathJax 和 Hugo 所使用的 BlackFriday 引擎有冲突。冲突体现在,后者会先于前者转义 Markdown 文件中的 \_ 等字符,当这些字符出现在

公式中时,就会被提前转义导致公式失效。

对于这种情况有以下几种解决方案:

  • 对于需要使用数学公式的文章,换用 Hugo 内置的其他渲染引擎。

  • 将数学公式用 <div></div> 包裹。

  • \\ 代替 \,用 \_ 代替 _ 等。

这里我们选择第一种,只需要在含有数学公式的 Markdown 文件开头的 YAML 区,加上一句

markup: mmark

就可以了。这会告知 Hugo 在渲染这个页面时使用 MMark 代替 BlackFriday 进行渲染,而前者并没有后者所存在的那种冲突。

但是,这种方法有一个巨大的问题:如果你的主题支持文章目录(TOC),MMark 无法生成 Markdown 文件的目录结构,因此文章目录失效。 在不使用 TOC 的时候可以使用这种方法。由于我使用的主题的 TOC 并不好看,因此我没有使用 TOC,故未深究其他更好的这个问题的解决方案。

Hugo 主题和 submodule

如果你的 Hugo 主题是从 GitHub 上直接克隆到 /path/to/your/blog/themes/ 的,那么你可能会遇到一个大问题:GitHub Actions 在执行第一步的时候报错。这是因为主题是一个 git 仓库,而这个 git 仓库被套在了一个大的 git 仓库(就是你的博客)里面。这是不能直接提交的,若强行提交会出一些奇怪的问题。

我解决这个问题的方法是:

  • 将他人的主题 fork 一份到自己的 GitHub 下,然后用下面的命令(而不是直接 clone)克隆。

    git submodule add git@github.com:foo/bar themes/foobar
    
  • 在对主题做出修改后,在主题的那个目录下进行 commit。

  • 在进行文章的更新后,在整个博客的目录下进行 commit。

这样,本质上来说,其实主题部分被存在了另外的一个 GitHub 仓库里,与文章等其他部分的源码是分开的。

关于 git 的 submodule 功能,我还不是十分了解,可能未来会去了解更多的用法吧(逃)。


这次重构博客应该是我第四次搭自己的博客了(第一次在 2016 年,第二次在 2020 年初,第三次在今年年初)。每次重新搭博客都是斗志昂扬,结果后面文章越写越少,最后荒凉闭站。这次下了狠心买了两年的腾讯云域名,并且选择了不需要更多服务器成本的 GitHub Pages,也是想让这个「新」博客长久一点,不要重蹈覆辙。

然后现在又换到服务器上了。

GitHub Actions 作为一种 Serverless 的服务,免费提供属实良心。大概以后会深入学学 CI 什么的怎么使用吧。