使用 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")
index.json
文件index.json
文件用 atomic-algolia 上传到 Algolia,所以我之前编写了如下的 shell 脚本(取名为 deploy.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 博客的部署
$ bash deploy.sh
GitHub Actions 中的概念
在 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 仓库里面和源代码一起管理,详细的语法可以在官网找到
编写 YAML 文件
接下来,我们要尝试将前面提到的 Shell 脚本变成 GitHub Actions 对应的 YAML 文件(取名为 build_and_deploy.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: true
,fetch-depth: 0
则会在 clone 的时候将所有的 commit 都 clone 下来
接下来我们要安装 Hugo,虽然官网上给出了 详细的步骤,但是我觉得较为繁琐,于是我找到了 actions-hugo,该 action 可以自动帮我们下载并安装好 Hugo,hugo-version: 'latest'
则是让它每次都用最新版本的 Hugo。因为 DoIt 使用了 admonition 等特性,所以还需要设置 extended: true
,也就是使用 extended 版本的 Hugo
安装 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-pages 的 README.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
# replace with your email here.
$ ssh-keygen -t ed25519 -C martinlwx@163.com -f "hugo"
在当前目录,你就可以看到 hugo
和 hugo.pub
这两个文件了,接下来
- 点开 public 仓库的
Settings -> Deploy keys -> Add deploey key
,将hugo.pub
的内容添加进去,这里 Title 取什么名字无关紧要,要紧的是设置Allow Write Access
- 点开 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 add
和 git commit
(不过这也是因为之前的 commit message 是调用命令生成的)。但不管怎么说,还是从中学习到了 GitHub Actions 的使用,自动化部署 Hugo 博客是一次有意思的 CI/CD 实践。希望本文能给屏幕前的你带来帮助 :)