受够了 Wordpress 的臃肿以及对 markdown 的水土不服,受不了 ghost 主题的匮乏与对中文圈的态度。Hexo 用过一段时间,但实在太讨厌 NPM 了,只想安安静静写个博客,结果竟然还要配“开发环境”?

于是 —— Go 真香!迁移到了 Hugo。和 Hexo 一样作为一个静态网站生成器,集成 Github Pages 几乎是标配了,但是 GH 近期在大陆似乎不太如意,眼看着就差彻底断联了。所以还是更想放在自己的香港 VPS 上。作为踩坑记录,这篇文章出现了。

上传文件到 VPS

因为 hugo 生成本身非常容易,就一行命令的事,所以我们先来解决主要矛盾,那就是怎么把生成的文件放到 VPS 上。

rsync

当然,你大可以配一下 Hook,GH 生成完毕后打包传到 Release 或其他什么鬼地方,通知服务器,然后服务器再去拉取回来解压。显然,这不是一个「优雅」的方案。

我希望这一步尽可能地简单,简单到无序通知 VPS 进行额外操作,GH 可以单方面部署完成,这样就少了许多维护成本与不稳定因素。既然是文件传输,自然想到 FTP SFTP/SCP。这两个其实都行啦,不过这里都不用 😂,而且另一个更加适合此场景的东西——rsync。这是一个文件同步服务,可以进行差异比较只传输必要部分,和其他一堆现代化的特性,有兴趣自己查查看吧

标准的服务器系统应该预置了 rsync(不要告诉我你使用 windows),这东西用起来和 scp 很像——都是借用 ssh 进行传输,也使用系统账户进行认证。一条命令轻松实现需求

  • -a 表示递归同步,并且同步元数据
  • -v 打印一些信息
  • --delete 删除目标文件夹存在但本地不存在的玩意,不加这个参数就是增量同步。你一定不希望删掉某个不可描述的东西后实际上文件还在那吧 😄
rsync -av --delete source/ username@host:destination

(才怪)难道你的服务器不要身份验证了?

你可以直接把 VPS root 的用户名密码,或者密钥拿过来使,又不是不能用。但是我 觉得 肯定这不是个好主意。新建个系统用户?然后 balabala…

关于 rsync 的中文使用总结可以看看阮一峰的博客

其实除了走 ssh 协议,rsync 还有自己的协议,采用的也是独立的用户系统。这种模式需要服务器开一个守护进程,并且做一丢丢配置。

rsync 服务端守护进程

我的系统的 Ubuntu 20 LTS,Cent OS 死忠粉自己转换命令

系统预装了 rsync,先把示例配置文件拷贝一份到标准目录:

sudo cp /usr/share/doc/rsync/examples/rsyncd.conf /etc   

然后编辑一下,全局参数没怎么改,只是把 log 取消注释了好记录日志。重点是下面的模块配置,这里只列出我改动的部分:

[cheblog] # 模块名,后面传输文件的时候用到
        comment = chenhe blog web root # 注释
        path = /www/wwwroot/xxxxx # 模块的根路径
        read only = no # 取消只读
        uid = 1002 # 进程使用的uid根据情况填写,0是root,我用的是www
        gid = 1002 # 进程使用的gid根据情况填写,0是root,我用的是www
        auth users = myblog # 允许的用户名(与系统用户无关)
        secrets file = /etc/rsyncd.secrets # 用户数据库(我们一会创建)

然后在上面 secrets file 指定地方创建一个文件,里面写上用户名和密码,一行一个,用英文冒号隔开,注意用户名得和 auth users 一样,否则将无法连接此模块。比如这样:

myblog:123123

⚠️ 必须设置用户数据文件权限为只有所属用户可读写,所属用户必须和启用 rsync 的用户一致 。默认是 root 启动的,也是 root 创建的,所以我们修改一下权限就行了:

sudo chmod 600 /etc/rsyncd.secrets

最后手动启动一下:

sudo rsync --daemon

ps -aux | grep rsync # 确认启动成功

客户端设置

其实客户端已经可以直接使用了:

rsync -av --delete source/ [username]@[host]::[moduleName]

但是这样需要我们手动输入密码,不适合自动化执行。可以创建一个文件,里面写上密码,不要写用户名。同样,这个文件的权限也必须是只有执行 rsync 的用户可以读写。

然后执行:

rsync -av --delete source/ [username]@[host]::[moduleName] --password-file=[密码文件]

大功告成!

Github Actions 编写

这个就非常简单了,捋一下思路:

  1. 克隆博客源码
  2. 安装 hugo
  3. 生成网站
  4. 同步 vps

前两步已经有了非常完善的轮子,生成只要执行一下hugo就行,稍微动手写一下最后一步:

- name: Deploy to VPS
  env:
    RSYNC_PWD: ${{ secrets.RSYNC_PASSWORD }}
  run: |
    echo $RSYNC_PWD > rsync.pwd
    sudo chmod 600 rsync.pwd
    rsync -av --delete public/ username@[ip]::module --password-file=rsync.pwd    

唯一注意的就是,为了安全(很多朋友喜欢把博客源码仓库一并公开),上传用的密钥万万不可硬编码到 actions 文件里,也不应该通过命令行直接传递,因为后者可能会暴露在 log 中。

最佳实践是填写到仓库设置中专用的 secrtes 区域,然后通过环境变量从上下文中注入,最后使用环境变量传递。

下面附上完整的 actions 配置文件:

name: Deploy

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

  workflow_dispatch:

# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
      - uses: actions/checkout@v2
      
      - name: Setup Hugo
        uses: peaceiris/actions-hugo@v2

      - name: Build
        run: hugo --minify

      - name: Deploy to VPS
        env:
          RSYNC_PWD: ${{ secrets.RSYNC_PASSWORD }}
        run: |
          echo $RSYNC_PWD > rsync.pwd
          sudo chmod 600 rsync.pwd
          rsync -av --delete public/ [username]@[ip]::[module] --password-file=rsync.pwd          

现在我们更新文章后只要一句 git push,后面的就不用操心啦。