hexo的deploy部署命令部署法

本文最后更新于 2026年10月15日 凌晨

之前写过两篇相关的:Hexo的部署(使用git) 讲的是 hexo deploy 用 git 部署的基本原理,服务器自部署Hexo网站实现自动化构建 讲的是在服务器上建裸仓库、用钩子在服务器端构建的方案。这篇算第三个阶段:本地构建,用 hexo deploy 把产物推到服务器,由服务器的钩子负责落地。等于把前两篇的机制各取一半拼起来。

一、为什么要换

服务器端构建那套(push 源码 → 服务器钩子 → 服务器跑 hexo g → rsync 到站点目录)能跑通,但有几个不舒服的地方:

  1. 服务器上必须装 node / pnpm / hexo,依赖也装在服务器上;
  2. 每次推送都要在服务器上跑一遍依赖安装和构建,慢;
  3. 构建报错只能去服务器上看日志,本地复现不了;
  4. 服务器上多了一套完整的 node 工具链要维护。

而 hexo deploy 的机制其实很简单——它就是把生成的静态文件用 git 推到某个远端。把”推送”和”落地”拆开看,服务端只负责把文件搬到 Web 目录,这件事一个 git 钩子就够,根本不需要 node。

所以就变成了:构建留在本地(环境熟悉、报错看得见),服务器只当一个”收件 + 搬运”的角色。

二、hexo deploy(Git 方式)到底做了什么

它靠 hexo-deployer-git 插件完成,幕后是四步:

  1. 在项目根目录维护一个隐藏的独立仓库 .deploy_git;
  2. 把 public/ 里的内容同步进这个仓库;
  3. 在里面 git add -A + git commit -m "Site updated: <时间>";
  4. 执行 git push -f,推到你配置的远端仓库和分支。

关键认识:它不关心对端是什么。 对端是 GitHub Pages、是 Gitea、还是你自己在服务器上 git init --bare 出来的一个空仓库,对它来说没有区别——它只负责”把生成结果用 git 推过去”。

所以只要对端能接住这个 push 并把文件放到正确的位置,部署就成立了。而”接住 push”这件事,在裸仓库里就是 git 钩子。

三、接收端:裸仓库 + post-update 钩子

为什么是裸仓库

裸仓库没有工作区,它的存在意义就是”接收推送”,正好符合中转站的角色:

1
git init --bare /srv/blog/deploy.git

钩子选哪个

git 提供了 post-receive 和 post-update 两个推送后钩子,区别在参数传递方式:

  • post-receive:从标准输入读 旧值 新值 引用名 的三元组;
  • post-update:把引用名作为命令行参数传进来。

单分支、单一目标的部署,post-update 就够了,脚本更简单。

钩子做三件事

  1. 从裸仓库把本次推送的内容检出到一个暂存目录;
  2. 用 rsync -a --delete 把暂存目录同步到 Web 根目录;
  3. 删掉暂存目录。

为什么不直接 checkout 到 Web 根目录?

因为 git checkout -f 只写不删。本站的永久链接是 /:year/:month/:hash/ 这种带 hash 的形式,只要改一次文章标题,hash 就变了,会多出一个新目录,而旧目录会一直留在站点目录里,变成死链。rsync --delete 才能让目标目录精确等于本次推送的内容。

为什么可以放心用 push -f?

接收端那个仓库只存构建产物,它自己的历史没有任何意义(源码的历史在另一个仓库里)。所以 -f 把之前的提交覆盖掉是正常的——跟 GitHub Pages 的 gh-pages 分支是同一个套路。

四、完整链路

1
2
3
4
5
6
7
8
本地写文章
└─ hexo clean && hexo g 生成 public/
└─ hexo d 在 .deploy_git 里提交,然后 git push -f
└─► <服务器>:/srv/blog/deploy.git 裸仓库,只存产物
└─ hooks/post-update
├─ git checkout -f → /srv/blog/.deploy-stage 暂存
└─ rsync -a --delete → /srv/blog/www
└─ Nginx 直接读

最后一环要说明一下:这里的 Nginx 是 root 直接读磁盘上的静态文件,不是反向代理。所以文件一落进 Web 目录,线上立刻就是新的,不需要重载 Nginx,也不需要重启任何服务。

五、实践:服务端

以下命令在 Web 服务器上执行,假设站点目录是 /srv/blog/www,部署账号是 deployer。

1
2
# 1. 建裸仓库
git init --bare /srv/blog/deploy.git

先确认账号对站点目录有写权限,别等推送失败了再回来查:

1
test -w /srv/blog/www && echo WRITABLE || echo NOT-WRITABLE

然后写钩子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 2. 写钩子
cat > /srv/blog/deploy.git/hooks/post-update <<'EOF'
#!/bin/sh
set -e

GIT_DIR=/srv/blog/deploy.git
STAGE=/srv/blog/.deploy-stage
WWW=/srv/blog/www

echo ">>>>>> [Deploy] 开始同步静态文件"

rm -rf "$STAGE"
mkdir -p "$STAGE"
git --work-tree="$STAGE" --git-dir="$GIT_DIR" checkout -f master

rsync -a --delete "$STAGE"/ "$WWW"/
rm -rf "$STAGE"

echo ">>>>>> [Deploy] 已同步到 $WWW"
EOF

# 3. 加执行权限
chmod +x /srv/blog/deploy.git/hooks/post-update

三个要点:

  • <<'EOF' 的单引号不能省。省掉的话 $STAGE、$WWW 会在写入文件时就被当前 shell 展开成空字符串,钩子里就变成 rsync -a --delete "/" / 了——这是能造成严重事故的。
  • set -e 让脚本中途出错立刻停止,避免半成品上线。
  • 钩子脚本的换行符要用 LF。从 Windows 传过去的 CRLF 会让 /bin/sh 报 bad interpreter 之类的怪错。

Nginx 侧(站点目录就是上面那个 www):

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 443 ssl;
server_name example.com;

ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

location / {
root /srv/blog/www;
index index.html index.htm;
}
}

注意这里 root 的路径必须和钩子里 WWW 指向的路径完全一致,这是最容易出错、也最难察觉的地方(见下面的坑 3)。

六、实践:本地

第一步,装插件:

1
2
pnpm add hexo-deployer-git
# 用 npm 的话:npm install --save hexo-deployer-git

这个插件不在 Hexo 的默认依赖里,不装的话 hexo d 会直接报:

1
ERROR Deployer not found: git

第二步,配置 _config.yml:

1
2
3
4
deploy:
- type: git
repo: deployer@<服务器IP>:/srv/blog/deploy.git
branch: master

第三步,推送:

1
2
rm -rf .deploy_git        # 换过仓库地址的话必须清掉
hexo clean && hexo g -d

rm -rf .deploy_git 这步容易被忽略:hexo-deployer-git 会把远程地址缓存在 .deploy_git/.git 里,不清掉的话即使 _config.yml 改了,它可能还是往旧地址推。

七、怎么确认真的成功了

hexo g -d 的输出里有三个信号:

  1. 目标地址正确,且第一次推送时是新建分支而不是强推:

    1
    2
    To <服务器IP>:/srv/blog/deploy.git
    * [new branch] HEAD -> master
  2. 钩子的标准输出会以 remote: 前缀透传回来——这是最有价值的一条,等于在本地就能看到服务端脚本的执行结果:

    1
    2
    remote: >>>>>> [Deploy] 开始同步静态文件
    remote: >>>>>> [Deploy] 已同步到 /srv/blog/www
  3. 最后一行:INFO Deploy done: git

然后到服务器上交叉验证:

1
2
3
4
5
6
7
8
9
# 站点文件是不是刚刚被写过
stat -c '%n %y' /srv/blog/www/index.html

# 暂存目录应该已经被删掉(说明脚本跑到了最后一行)
ls -ld /srv/blog/.deploy-stage

# 文件数应该和仓库里的完全一致,不一致就是有残留
git --git-dir=/srv/blog/deploy.git ls-tree -r --name-only master | wc -l
find /srv/blog/www -type f | wc -l

八、踩过的坑

  1. hexo-deployer-git 没装。_config.yml 里写了 deploy 段也没用,hexo d 直接报 Deployer not found: git。

  2. 部署账号对站点目录没有写权限。钩子里 rsync 报 Permission denied。先用 test -w 测,别靠猜。注意账号属主和目录属主是两回事——目录是 775 且属主是另一个用户时,非属主账号就只有 r-x。

  3. 钩子里的目标目录写错了。这个最坑:推送成功、钩子也执行成功、Deploy done 也打印了,但线上毫无变化——因为文件被搬到了另一个没人读的目录。这种”静默成功”比直接报错难查得多。务必核对 Nginx 的 root 和钩子里的 $WWW 是不是同一个路径。

  4. 只 checkout 不 delete。改标题后旧永久链接变成死链,而且会一直堆。所以钩子里必须有 rsync --delete。

  5. 换了仓库地址但没清 .deploy_git。它会继续往旧地址推,而你会以为配置没生效。

  6. heredoc 没加单引号。变量被提前展开,钩子里的路径全变空字符串。

  7. 钩子脚本带了 CRLF。在 Windows 上编辑再传上去很容易发生,/bin/sh 会报莫名其妙的解释器错误。用 file <钩子> 确认是 “POSIX shell script” 而不是 “with CRLF line terminators”。

  8. 构建环境自身的版本漂移。比如 pnpm 大版本升级后,原先写在 package.json 里 pnpm 字段的设置(如 overrides)不再被读取,pnpm 会拒绝执行任何 script,报 ERR_PNPM_VERIFY_DEPS_BEFORE_RUN。这时要把这些设置迁到 pnpm-workspace.yaml。这类问题和部署无关,但会在部署前一秒拦住你,所以单独记一笔。

  9. 配置文件的换行符混用。在 Windows 上编辑 _config.yml,如果编辑器只给新行写 CRLF,就会出现同文件内混用。YAML 一般能吃下去(CR 会被当作行尾),但 git diff 会变得没法读。要根治可以在仓库根放一个 .gitattributes:

    1
    * text=auto eol=lf

    它跟着仓库走,比在每台机器上设 core.autocrlf 可靠。

九、和”服务器端构建”方案的对比

维度 服务器端构建 本地构建 + hexo deploy
触发方式 git push 源码,自动 手动执行 hexo g -d
服务器依赖 node + pnpm + hexo 只需要 git 和 rsync
构建速度 每次都要装依赖、生成 用本地缓存,通常更快
出错在哪看 服务器日志 本地终端
源码与产物 同一个仓库 两个仓库,各管各的
站点目录残留 取决于钩子是否用了 --delete 同上
适合场景 完全不想碰命令行 想掌控构建过程、服务器不想装 node

两者并不冲突,也可以并存:源码照常 push 到自己的代码仓库做版本管理,发布时再单独跑一次 hexo g -d。相当于把”保存”和”发布”两个动作分开了。

十、小结

  • hexo deploy(Git 方式)的本质就是:把 public/ 内容提交进 .deploy_git,然后 git push -f 到配置的远端。它不关心对端是谁。
  • 对端的最小实现是:一个裸仓库 + 一个把文件搬到 Web 目录的钩子。
  • 钩子的关键细节有两个:检出到暂存目录再用 rsync --delete 同步(解决旧文件残留),用 push -f(产物仓库的历史无意义)。
  • 这样服务器完全不需要 node 环境,构建留在本地,报错看得见,链路短、每一环都能单独验证。
  • 代价是发布从”push 即发布”变成了”手动执行一条命令”——对个人博客来说,这个代价换来的是可控性和简单性,是划算的。

hexo的deploy部署命令部署法
https://www.xiebingyuan.cn/2026/10/637d2089e0ab/
作者
bingyuan
发布于
2026年10月15日
更新于
2026年10月15日
许可协议