工作流

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

Overview

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

点击查看
name: Auto Deploy Hugo
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-20.04

    steps:
      - uses: actions/checkout@v3
        with:
          submodules: 'recursive'
          fetch-depth: 0
      
      - uses: webfactory/ssh-agent@v0.7.0
        with:
          ssh-private-key: |
            ${{ secrets.BLOG_DEPLOY_KEY }}
                        
      - name: Scan public keys
        run: |
          ssh-keyscan -p ${YOUR_SERVER_SSH_PORT} ${YOUR_SERVER_IP} >> ~/.ssh/known_hosts
                    
      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2
        with:
          hugo-version: 'latest'

      - name: Build
        run: hugo --minify

      - name: Deploy
        run: | 
          rsync -avuz -e 'ssh -p ${YOUR_SERVER_SSH_PORT}' --progress --delete public/ ${YOUR_SERVER_IP}:${YOUR_BLOG_PATH}

触发机制

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

on:
  push:
    branches: [main]

虚拟环境

这里将虚拟环境设置为 ubuntu-20.04 版本,可以根据自己的需求进行修改:

deploy:
  runs-on: ubuntu-20.04

git checkout

工作流的第一步是检出最新代码到 GitHub Actions 的虚拟环境中,通过 actions/checkout 实现。由于我的博客主题是以子模块的形式存在于博客主仓库中的,因此需要使用 submodules: 'recursive' 将子模块也递归检出。检出时,可以根据需求设置检出 git 的提交数,使用 fetch-depth: 0 则将所有历史提交全部检出。

- uses: actions/checkout@v3
  with:
  submodules: 'recursive'
  fetch-depth: 0

生成 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}} 的形式使用私钥,而不需要将私钥内容直接贴在工作流中啦。

Add repository secret

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

- uses: webfactory/ssh-agent@v0.7.0
  with:
    ssh-private-key: | 
      ${{ secrets.BLOG_DEPLOY_KEY }}

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

- name: Scan public keys
  run: | 
    ssh-keyscan -p ${YOUR_SERVER_SSH_PORT} ${YOUR_SERVER_IP} >> ~/.ssh/known_hosts

需要注意的是这里的 VPS 上 SSH 开放的端口 ${YOUR_SERVER_SSH_PORT} ,以及 Web 服务器的 IP 地址 ${YOUR_SERVER_IP} 需要根据自建 VPS 的情况进行修改。

该方式为采用最为原始的指令代码来实现,如果觉得麻烦可以使用 easingthemes/ssh-deploy

安装 Hugo

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

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

这里指定使用最新版本,避免了工作流的频繁修改。工作流执行时会输出详细日志,其中有 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 上 SSH 开放的端口 ${YOUR_SERVER_SSH_PORT} ,以及 Web 服务器的 IP 地址 ${YOUR_SERVER_IP} 与根目录是 ${YOUR_BLOG_PATH} 需根据自建情况而定,注意各个参数的正确使用:

- name: Deploy
  run: | 
    rsync -avuz -e 'ssh -p ${YOUR_SERVER_SSH_PORT}' --progress --delete public/ ${YOUR_SERVER_IP}:${YOUR_BLOG_PATH}

注意:为了成功使用 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 页面可查看工作流的执行情况:

workflow

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

step & log

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

commit flag

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




Reference12