背景

之前这个博客的自动化部署一直是用自建 Drone CI 来跑的,相关文章可以翻翻:Blog All ProcessDrone自动化部署Java应用

用了两三年,功能上没问题,push 代码后自动构建部署一条龙。但最近越来越觉得维护这套东西的成本太高了:

  1. 需要一台服务器专门跑 Drone——drone-server + drone-runner-docker 两个容器,还得配 NGINX 反代、HTTPS 证书
  2. GitHub OAuth 应用要维护——Drone 登录依赖 GitHub OAuth,得在 GitHub 上建 OAuth App,配回调地址
  3. 服务器迁移就全得重来——之前从宝塔换到 1Panel,Drone 的 docker socket 映射、目录映射全部得重新搞
  4. 资源占用不划算——就为了跑个博客构建,长期跑两个容器,内存白白占着

说白了,就我一个用户,建一个博客项目,为了这点需求长期维护一套 CI 系统,性价比太低了。

于是就想,能不能把这套东西干掉,找个现成的、不用自己维护的方案。

方案选择

目前主流的 CI 服务就这几个:

  1. GitHub Actions——GitHub 自带的,免费额度够用,仓库就在 GitHub 上,天然集成
  2. GitLab CI——如果项目放 GitLab 上可以用,但我项目已经在 GitHub 了
  3. Jenkins——更重了,跟 Drone 一个性质,需要自己维护服务器
  4. Drone Cloud——官方托管版,但免费额度有限

没怎么犹豫就选了 GitHub Actions,原因很简单:

  • 仓库本身就在 GitHub 上,push 就触发,零延迟
  • 免费额度:每月 2000 分钟,博客构建一次不到 2 分钟,根本用不完
  • 不用维护任何服务器,GitHub 帮你跑
  • 配置就是一个 .yml 文件,放项目里就行

之前的部署流程

先回顾一下之前 Drone 的流程长什么样:

GitHub push → Drone webhook → drone-runner 拉代码 → 
hugo 构建 → scp 静态文件到博客服务器 → 完成

看着挺顺的,但实际上里面有几个坑:

  1. 换行符问题——Windows 下 git 默认 CRLF,Linux 下 LF,PaperMod 主题的 CSS integrity 校验会失败,导致样式全丢。之前在 Drone 服务器上还得专门配 git config --global core.autocrlf input 来解决
  2. submodule 问题——PaperMod 主题用 git submodule 方式引入的,Drone 拉代码时候不会自动拉 submodule,导致 hugo 构建出来内容不全
  3. scp 全量传输——每次部署都是全量上传,博客大了之后越来越慢

这些坑其实都能解,但每次服务器迁移或者换环境,都得重新踩一遍。

GitHub Actions 实现

最终跑通的配置如下,直接看文件 .github/workflows/deploy.yml

name: Deploy Hugo Blog by SCP

on:
  push:
    branches:
      - main

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: 检出代码
        uses: actions/checkout@v3

      - name: Install custom hugo withdeploy
        run: |
          wget https://github.com/gohugoio/hugo/releases/download/v0.145.0/hugo_extended_withdeploy_0.145.0_Linux-64bit.tar.gz -O hugo.tar.gz
          tar -zxf hugo.tar.gz
          sudo mv hugo /usr/local/bin/hugo
          hugo version

      - name: 构建网站
        run: hugo --minify

      - name: 上传到blog.chunxiao.site
        uses: appleboy/scp-action@v0.1.7
        with:
          host: ${{ secrets.SERVER_HOST }}
          username: ${{ secrets.SERVER_USER }}
          port: 1313
          password: ${{ secrets.SERVER_PASSWORD }}
          source: "public/."
          target: "/opt/1panel/apps/openresty/openresty/www/sites/blog.chunxiao.site/index"

下面一步一步解释为什么这么写。

代码检出

- name: 检出代码
  uses: actions/checkout@v3

这一步 GitHub 会拉取仓库代码到 runner 上。默认就会拉取所有 submodule,所以之前 Drone 遇到的主题缺失问题在 Actions 上天然不存在。

Hugo 构建

这里有个坑,重点说。

最开始我试了 GitHub 官方的 peaceiris/actions-hugo@v3 这个 action,看着挺方便的:

- name: Setup Hugo
  uses: peaceiris/actions-hugo@v3
  with:
    hugo-version: "0.145.0"
    extended: true

但实际跑下来发现问题了——普通的 hugo extended 版本缺少 withdeploy 功能,PaperMod 主题有些特性依赖这个。之前自建 Drone 时候用的也是自定义的 hugo_extended_withdeploy 版本。

所以这里改成了手动下载 Hugo 官方提供的 extended_withdeploy 版本:

- name: Install custom hugo withdeploy
  run: |
    wget https://github.com/gohugoio/hugo/releases/download/v0.145.0/hugo_extended_withdeploy_0.145.0_Linux-64bit.tar.gz -O hugo.tar.gz
    tar -zxf hugo.tar.gz
    sudo mv hugo /usr/local/bin/hugo
    hugo version

这个版本在 Hugo 的 release 页面有提供,专门带了 deploy 扩展功能。版本号锁定 0.145.0,避免 Hugo 升级后 PaperMod 出现兼容问题——这个教训之前踩过,Hugo 大版本升级后主题的 partial 路径可能变,直接导致构建出来的页面结构不对。

构建命令很简单:

- name: 构建网站
  run: hugo --minify

--minify 会对 HTML/CSS/JS 进行压缩,减少传输体积。

部署到服务器

这一步是核心,也是我之前踩了大坑的地方。

最开始的方案是用 rsync

- name: Deploy via rsync
  uses: burnett01/rsync-deployments@7.0.2
  with:
    switches: -avz --delete
    path: public/
    remote_path: /opt/1panel/...
    ...

结果跑完之后网站直接 404 了。

排查发现,rsync 在 GitHub Actions 环境下,--delete 参数会把远程目录里不属于本次同步的文件全部删掉。1Panel 管理网站的时候会在网站目录下放一些配置文件(比如 .user.ini),rsync 一跑全给清了,OpenResty 找不到正确的 index 文件,直接 404。

后来试了自己写原生 rsync 脚本,把 .user.ini 排除掉:

rsync -avz --delete --exclude '.user.ini' ...

结果还是 404。进一步排查发现,rsync 的 public/ 路径行为跟 scp 不一样——rsync 会把 public 这个目录本身也同步过去,而不是只同步目录里的内容。

所以最终回到了之前 Drone 用的方案:appleboy/scp-action

- name: 上传到blog.chunxiao.site
  uses: appleboy/scp-action@v0.1.7
  with:
    host: ${{ secrets.SERVER_HOST }}
    username: ${{ secrets.SERVER_USER }}
    port: 1313
    password: ${{ secrets.SERVER_PASSWORD }}
    source: "public/."
    target: "/opt/1panel/apps/openresty/openresty/www/sites/blog.chunxiao.site/index"

关键点:

  1. source: "public/."——末尾的 . 很重要,意思是同步 public 目录里的内容,而不是把 public 这个目录本身也带过去。如果写成 public/,传到服务器上会变成 index/public/xxx.html,路径就错了
  2. password 认证——不用 SSH key,直接用密码。GitHub Secrets 里存的是密码,安全级别对于博客部署足够了
  3. port: 1313——我的服务器 SSH 端口不是默认 22,改成了 1313

Secrets 配置

需要在 GitHub 仓库的 Settings → Secrets and variables → Actions 中添加:

Secret 名称
SERVER_HOST博客服务器 IP(填入你云服务商提供的公网 IP)
SERVER_USERSSH 用户名
SERVER_PASSWORDSSH 密码

注意:SERVER_PORT 我直接写死在 yml 里了,因为端口不会变,没必要多一个 secret。

实际运行效果

push 代码后,大约 1 分 30 秒完成部署:

  • 检出代码:~5 秒
  • 下载 Hugo:~10 秒
  • 构建网站:~30 秒
  • SCP 上传:~20 秒

每次部署完可以在 Actions 页面看到完整的运行日志,哪一步出了问题一目了然。

踩坑记录

坑1:rsync 导致 404

这个坑在上面已经详细说了,核心就是 rsync 的路径行为和 --delete 参数跟 1Panel 的网站目录管理冲突。最终用 scp-action 解决。

教训:能跑的配置别随便改。 之前 Drone 的 scp 方案跑了两年没出过问题,换成 rsync 反而把网站搞挂了。

坑2:Hugo 版本选错

peaceiris/actions-hugo 装的是普通 extended 版,缺少 withdeploy 功能,导致部分 PaperMod 特性不正常。必须用 hugo_extended_withdeploy 版本。

坑3:server 地址搞混

配置 secret 时候把服务器地址写错了,部署半天连不上。这个坑提醒大家:多个服务器的话一定要核对清楚 IP,别像我一样张冠李戴。

跟 Drone 的对比

维度Drone CIGitHub Actions
需要自建服务器
免费额度自建无限制2000 分钟/月
维护成本高(容器+NGINX+OAuth)
配置位置项目根目录 .drone.yml.github/workflows/deploy.yml
日志查看Drone 后台页面GitHub Actions 页面
构建速度~2 分钟~1.5 分钟
换行符问题需要额外配置无需处理
submodule需要手动处理自动处理

总结

从自建 Drone 切换到 GitHub Actions,最大的感受就是:省事了

不用再维护 Drone 服务器、不用管 OAuth 应用、不用担心服务器迁移后 CI 全挂。push 代码后 GitHub 自动跑,跑完自动部署,日志也在 GitHub 上能看到。

对于个人博客这种低频、轻量的场景,GitHub Actions 完全够用。如果你的项目本身就在 GitHub 上,基本没有理由再自建一套 CI。

当然,如果你的团队有合规要求、代码不能放 GitHub,那另当别论。但对于个人开发者来说,能用现成的就别自己搭。


最后,如果你对 WSL2、Docker、本地大模型部署这些感兴趣,可以关注我的 B 站(UID: 483468180 - 晓若寒轻),后面还会出更多实用教程。

有问题的话,可以在文章下面留言,或者 B 站私信我。