利用 GitHub Actions 自动部署 Hugo 博客到自建 VPS

更新历史

  • 2020-11-29: webfactory/ssh-agent 须使用 v0.4.1 或以上版本,以解决「The set-env command is deprecated」问题。
  • 2021-06-19: 添加 VPS 上 rsync 命令的安装。

工作流

GitHub Actions 提供了直接在 GitHub 仓库中创建软件生命周期工作流(workflow)的功能,极大地方便了持续集成、持续部署的实现。

本文将使用 GitHub Actions 实现 Hugo 博客自动部署到自建 VPS。基本思路是在每次 push 到 GitHub 之后触发 Actions 将最新代码 checkout 到其虚拟环境中并开始构建,构建成功后使用 rsync 自动部署到 VPS:

在 GitHub 仓库的 Actions 页面可看到大量的基于各种服务的模板,也可点击 set up a workflow yourself 生成一个通用模板,并在此模板基础上修改。修改保存后的工作流将以 commit 的形式提交到 .github/workflows 目录下。本文所述工作流的完整配置详见 main.yml,下面对此工作流逐一拆解。

触发时机

GitHub 生成的通用模板是在 master 分支的 push 和 pull request 事件时触发,按需修改为仅在 push 时触发:

on:
  push:
    branches: [ master ]

git checkout

工作流的第一步是检出最新代码到 GitHub Actions 的虚拟环境中,通过 actions/checkout 实现。由于我的博客主题是以子模块的形式存在于博客主仓库中的,因此需要使用 submodules: 'recursive' 将子模块也递归检出。

    - uses: actions/checkout@v2
      with:
        submodules: 'recursive'

生成 SSH 密钥

在最后的部署环节,需要使用 rsync(基于 SSH)连接到 VPS,因此提前使用 ssh-keygen 命令生成一对密钥(推荐使用 ed25519 算法)。注意此对密钥不能加密码保护(passphrase),以便在工作流中无人值守。此步骤可在任意一台支持 ssh-keygen 命令的设备上完成,但注意密钥不要泄露,因为没有密码保护!

ssh-keygen -t ed25519 -f ~/.ssh/blog_deploy_key

再将公钥 ~/.ssh/blog_deploy_key.pub 的内容添加到 VPS 的 ~/.ssh/authorized_keys 中,将私钥 ~/.ssh/blog_deploy_key 的内容添加到 GitHub 仓库的 Settings -> Secrets 中并命名为 BLOG_DEPLOY_KEY。此后就可以在工作流中以环境变量 ${{secrets.BLOG_DEPLOY_KEY}} 的形式使用私钥,而不需要将私钥内容直接贴在工作流中啦。

这里使用 webfactory/ssh-agent 实现私钥的缓存:

    - uses: webfactory/[email protected]
      with:
        ssh-private-key: |
          ${{ secrets.BLOG_DEPLOY_KEY }}

另外,工作流中还使用 ssh-keyscan 命令扫描 VPS 的公钥并保存到虚拟环境的 ~/.ssh/known_hosts 中,同样是为了实现工作流的无人值守。

    - name: Scan public keys
      run: |
        ssh-keyscan lancitou.net >> ~/.ssh/known_hosts

安装 Hugo

Hugo 官方推荐的 Docker 镜像附带了 klakegg/actions-hugo,但在工作流中使用 uses 语法添加之后就直接开始构建,而不是提供各种子命令,于是找到了更方便的 peaceiris/actions-hugo

    - name: Setup Hugo
      uses: peaceiris/actions-hugo@v2

这里不指定版本号,默认使用最新版本,避免了工作流的频繁修改。工作流执行时会输出详细日志,其中有 Hugo 版本号,详见效果一节

构建

安装了 Hugo 之后,就可以像往常一样使用 Hugo 的各种子命令了。直接在使用 hugo --minify 构建即可。

    - name: Build
      run: |
        hugo --minify

部署

虽然 Hugo 提供了 hugo deploy 命令实现部署操作,但此命令仅支持部署到 Google Cloud Storage (GCS)、Amazon S3 和 Azure Storage 服务器,对于小众 VPS 无能为力。

由于 Hugo 生成的是静态网站(默认在 public 目录下),对静态网站的部署其实就是将 public 目录同步到 VPS 上并作为 Web 服务器的根目录,实现此功能当然是用万能的 rsync 命令啦。rsync 命令在同步远程主机的文件或目录时使用 SSH 连接实现,此时就用到了上文生成的 SSH 密钥(由 rsync 命令自动完成)。

这里在使用 rsync 命令时,同时使用 --delete 选项将 VPS 对应目录下不需要的文件或目录删除,而 VPS 上 Web 服务器的根目录是 /var/www/lancitou-on-hugo/public,注意各个参数的正确使用:

    - name: Deploy
      run: |
        rsync -av --delete public [email protected]:/var/www/lancitou-on-hugo

注意:为了成功使用 rsync 命令,源主机(GitHub Actions 服务器)和目标主机(VPS)都需要安装 rsync,参见 rsync(1):

Note that rsync must be installed on both the source and destination machines.

GitHub Actions 服务器默认已有 rsync,VPS 上若未安装(如 minimal Ubuntu)则需要提前安装。

此方法适用于任何静态网站的部署而不仅限于 Hugo。

顺便说一句,安装 Hugo 一节提到的 peaceiris/actions-hugo 作者还提供了 peaceiris/actions-gh-pages 用于部署到 GitHub Pages,如需使用,请自行探索。

效果

工作流提交到 GitHub 仓库的 .github/workflows 目录后即可生效。在 GitHub 仓库的 Actions 页面可查看工作流的执行情况:

点开每个触发的工作流后,还可查看工作流的各个步骤及其详细日志:

commit history 中,每个 commit 后面也会有一个图标提示工作流是否执行成功:

从此,再也不需要每次更新博客之后手动登录 VPS 部署啦,甚至连 VPS 上的 git 仓库都可以删除,仅保留 public 目录即可。

参考资料

  1. 利用GitHub Actions实现Blog自动部署与发布
  2. Using GitHub Actions and Hugo Deploy to Deploy a Static Site to AWS

以上。

comments powered by Disqus