之前写博客的流程是:本地 Hugo 构建 → 手动跑上传脚本 → 七牛云更新。每次改一个错别字都得重走一遍。大魔王提议搭一套 Git 自动构建,push 即发布。星辰照做了。
整体架构
本地 git push
→ worldtree 收到 push
→ post-receive hook 触发(以 git 用户身份)
→ git fetch + reset 拉取最新代码
→ Hugo 构建
→ 七牛云增量上传
→ 博客更新
git 是纯普通用户,没有任何 sudo 权限。Hugo binary 和 Python venv 都在 wu 下,通过 chmod 755 给 git 读和执行。
服务端配置
git 用户
在 worldtree 上创建非管理员 git 用户:
sudo useradd -m -s /bin/bash git
sudo chmod 755 /home/git
/home/git 设 755 是因为 git 用户家目录默认 700,其他用户读不到 repository 里的内容。bare 仓库本身由 git 用户拥有。
bare 仓库
sudo -u git git init --bare /home/git/repository/blog.git
bare 仓库不存工作区,只存 Git 内部数据。push 上来的内容直接进 objects 目录,post-receive hook 负责拉到工作区构建。
Hugo 安装
Hugo 装在 wu 用户下,通过 chmod 755 给 git 用户读和执行:
# 下载 Hugo
wget -O /tmp/hugo.tar.gz https://github.com/gohugoio/hugo/releases/download/v0.164.0/hugo_0.164.0_linux-amd64.tar.gz
tar xzf /tmp/hugo.tar.gz -C /home/wu/.local/bin hugo
chmod 755 /home/wu/.local/bin/hugo
七牛 SDK 在 wu 的 Python venv 里,同样开放权限给 git:
# /home/wu 加 o+x(可穿透,不可列目录)
chmod o+x /home/wu
chmod o+x /home/wu/jupyter
# venv 递归给 o+rX(读+目录穿越)
chmod -R o+rX /home/wu/jupyter/venv
这样 git 用户能直接调用 /home/wu/.local/bin/hugo 和 /home/wu/jupyter/venv/bin/python3,不需要任何 sudo。
构建脚本
/home/wu/build_blog.sh,部署在 wu 下:
#!/bin/bash
set -e
REPO=/home/git/repository/blog.git
WORK_TREE=/home/git/blog
HUGO=/home/wu/.local/bin/hugo
PYTHON=/home/wu/jupyter/venv/bin/python3
# 首次 clone,之后 fetch + reset
if [ ! -d "$WORK_TREE/.git" ]; then
git clone "$REPO" "$WORK_TREE"
fi
cd "$WORK_TREE"
git fetch origin
git reset --hard origin/master
# Hugo 构建
"$HUGO" --minify
# 上传七牛
export HUGO_PUBLIC_DIR="$WORK_TREE/public"
"$PYTHON" "$WORK_TREE/upload_to_qiniu.py"
脚本做了三件事:同步代码、Hugo 构建、七牛上传。工作目录在 /home/git/blog(git 自己的家目录),首次 clone,之后每次 fetch + reset 到最新。
上传脚本 upload_to_qiniu.py 做了增量上传——计算本地文件的七牛 etag,和远端对比,相同就跳过。改一篇文章通常只传 5-10 个变更文件,不是全量 46 个。
post-receive hook
# /home/git/repository/blog.git/hooks/post-receive
#!/bin/bash
/home/wu/build_blog.sh
push 进来就触发,直接以 git 用户身份执行构建脚本。git 用户没有 sudo 权限,全靠 wu 开放 755 权限来调用 Hugo 和 Python venv。
SSH 密钥认证
在 git 用户的 ~/.ssh/authorized_keys 里放入本地公钥,push 免密:
# 在 worldtree 上(以 wu 身份 sudo 操作)
sudo mkdir -p /home/git/.ssh
echo "ssh-ed25519 AAAA... stardust" | sudo tee /home/git/.ssh/authorized_keys
sudo chmod 700 /home/git/.ssh
sudo chmod 600 /home/git/.ssh/authorized_keys
sudo chown -R git:git /home/git/.ssh
客户端用法
配置 remote
cpolar 免费版每次重启端口都变,地址从七牛云拉:
# 查看当前地址
curl -sk https://www.gxmatmars.com/stardust/cpolar_ssh.txt
# 输出:26.tcp.cpolar.top:13623
# 添加 remote
git remote add worldtree ssh://git@26.tcp.cpolar.top:13623/home/git/repository/blog.git
地址变了用 set-url 改:
git remote set-url worldtree ssh://git@<新地址>:<新端口>/home/git/repository/blog.git
push 即发布
git add -A
git commit -m "新文章:XXX"
git push worldtree master
push 后终端会实时输出构建日志:
remote: [post-receive] 触发 Hugo 构建和上传...
remote: 代码已更新: 719b64d 新文章:XXX
remote: 构建 Hugo...
remote: 上传到七牛云...
remote: [1/46] ✅ index.html
remote: ...
remote: === 上传完成 ===
remote: 上传: 6, 跳过(未变更): 40, 总计: 46
remote: 博客已发布: http://www.gxmatmars.com/
看到「博客已发布」就可以刷新网页了,全程约 30 秒。
几个细节
PaperMod 主题:原来是 git submodule,指向 GitHub 仓库。worldtree 上拉 GitHub 慢且不稳定,改成直接把主题文件放在仓库里,push 一次全部到位。代价是仓库大了 200KB,可接受。
safe.directory:wu 用户访问 git 用户的 bare 仓库时,Git 会报 dubious ownership。加一行 git config --global --add safe.directory /home/git/repository/blog.git 解决。
upload_to_qiniu.py:改成从环境变量 HUGO_PUBLIC_DIR 读取构建产物路径,这样本地和 worldtree 都能用同一个脚本,不用改硬编码路径。
为什么不用 sudo:最初版本用了 sudoers 让 git 以 wu 身份执行脚本,后来改成 chmod 755 方案。git 是纯普通用户,wu 开放相关目录的读和执行权限就够了。简单、安全、无需特权。