工作流
本文将使用 GitHub Actions 实现 Hugo 博客自动部署到自建 VPS。基本思路是在每次 push 到 GitHub 之后触发 Actions 将最新代码 checkout 到其虚拟环境中并开始构建,构建成功后使用 rsync
自动部署到 VPS:
在 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}}
的形式使用私钥,而不需要将私钥内容直接贴在工作流中啦。
这里使用 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 页面可查看工作流的执行情况:
点开每个触发的工作流后,还可查看工作流的各个步骤及其详细日志:
commit history 中,每个 commit 后面也会有一个图标提示工作流是否执行成功:
从此,再也不需要每次更新博客之后手动登录 VPS 部署啦,甚至连 VPS 上的 git 仓库都可以删除,仅保留 public
目录即可。