Keyen's Wiki

Keyen's Wiki

基于 Git Hook 的 Hexo 自动化部署指南

12
2025-09-07

基于 Git Hook 的 Hexo 自动化部署指南

前言

为什么采用Git Hook这种方式来实现自动化部署

笔者前期是采用本地生成静态html(即 public/)然后通过scp传到远程服务器的方式来实现网站部署。

如下脚本可以实现该功能:

@echo off
chcp 65001 >nul
echo ===== 开始部署流程 =====

echo.
echo 1. 执行 hexo clean...
call hexo clean
if %ERRORLEVEL% NEQ 0 (
    echo [错误] hexo clean 执行失败!错误代码: %ERRORLEVEL%
    pause
    exit /b 1
)
echo hexo clean 完成.

echo.
echo 2. 执行 hexo generate...
call hexo g
if %ERRORLEVEL% NEQ 0 (
    echo [错误] hexo g 执行失败!错误代码: %ERRORLEVEL%
    pause
    exit /b 1
)
echo hexo generate 完成.

echo.
echo 3. 删除远端服务器上的 /home/keyenzhou/wiki/ 内容...
ssh keyenzhou@8.153.167.18 "rm -rf /home/keyenzhou/wiki/*"
if %ERRORLEVEL% NEQ 0 (
    echo [警告] 删除远端文件时可能出现了问题。继续执行上传...
) else (
    echo 远端文件删除完成.
)

echo.
echo 4. 上传新的 public 内容到远端服务器...
scp -r ./public/* keyenzhou@8.153.167.18:/home/keyenzhou/wiki/
if %ERRORLEVEL% NEQ 0 (
    echo [错误] SCP 上传失败!
    pause
    exit /b 1
)
echo SCP 上传成功!

echo.
echo ===== 所有操作已完成! =====
pause

该方式有以下几种问题:

  1. 本地生成静态html占用本地资源(笔者希望本地只进行文章编写,将耗费CPU资源的功能转移到远程服务器上)

  2. scp传输文件量过大,每次生成完html都是直接全量传给服务器。显然从本地只将文章的修改部分传到服务器更为合理。(利用 Git 的增量提交和智能压缩传输机制可以完美将传输过程优化)

  3. 操作较为复杂,需要记住的过程较多。

最终效果

通过git hook 实现自动化部署后,我们只需要在本地进行编写,通过git操作即可在远程实现自动生成自动部署。

  • git status && git add .

git status && git add .
  • git commit -s

commit message模板
  • git push

通过git push命令即可自动执行在远程服务器的hook,来部署服务

触发钩子的过程
钩子触发结束

该方案的优势:

  1. 解放本地资源,将计算密集型任务卸载到远程服务器

  2. 优化带宽利用率,仅传输增量源码而非全量静态资源

  3. 简化部署流程,实现“一次配置,一键发布”的自动化体验

  4. 本地与服务器职责分离,本地专注于内容创作,服务器负责构建与托管

核心原理

利用 Git 的“裸仓库”与“钩子”功能,将一次普通的 git push 指令转化为在远程服务器上自动触发静态站点生成的信号。

1.信号触发器 Git Hook

Git Hook 是 Git 在特定操作(如提交、推送等)前后自动执行的脚本。本方案使用的是 post-receive Hook,它仅在推送操作完全成功后被触发一次。这确保了部署操作的稳定性和可靠性。

2.执行载体:Git 裸仓库 (Bare Repository)

普通 Git 仓库包含工作目录(您的文件)和版本历史。而裸仓库仅包含版本历史,它不包含文件的工作副本,因此无法在其内部直接进行文件修改或生成操作。它作为一个纯净的“中转站”或“调度中心”,专门用于接收推送并触发 Hook 脚本。

执行流程:

执行流程图

准备工作

本地环境 windows11

1.Node.js

Node.js 22.19.0

2.Hexo

  • 全局安装

npm install -g hexo-cli
  • 项目安装(package.json)

{
  "name": "hexo-site",
  "version": "0.0.0",
  "private": true,
  "scripts": {
    "build": "hexo generate",
    "clean": "hexo clean",
    "deploy": "hexo deploy",
    "server": "hexo server"
  },
  "hexo": {
    "version": "7.3.0"
  },
  "dependencies": {
    "hexo": "^7.3.0",
    "hexo-filter-github-emojis": "^3.0.5",
    "hexo-generator-archive": "^2.0.0",
    "hexo-generator-category": "^2.0.0",
    "hexo-generator-index": "^4.0.0",
    "hexo-generator-search": "^2.4.3",
    "hexo-generator-tag": "^2.0.0",
    "hexo-image-link": "^0.0.6",
    "hexo-permalink-pinyin": "^1.1.0",
    "hexo-renderer-ejs": "^2.0.0",
    "hexo-renderer-marked": "^7.0.1",
    "hexo-renderer-stylus": "^3.0.1",
    "hexo-server": "^3.0.0",
    "hexo-theme-landscape": "^1.0.0",
    "hexo-wordcount": "^6.0.1"
  }
}

3.Git

Git

远程环境 Ubuntu2404

1.Node.js

sudo apt update
sudo apt install nodejs npm

2.Hexo

  • 全局安装

npm install -g hexo-cli
  • 局部安装

使用package.json (项目目录下)

npm install --production

3.Git

sudo apt update
sudo apt install git

4.Nginx

sudo apt-get update
sudo apt-get install nginx -y # -y表示自动对所有交互选项回答yes

详细步骤

1. 服务器端 - 初始化 Git 裸仓库

  1. 登录服务器

ssh keyenzhou@ip # 已经配置免密登录
  1. 创建并初始化裸仓库

# 1. 创建存放仓库的目录
mkdir -p /home/keyenzhou/wiki
cd /home/keyenzhou/wiki

# 2. 初始化一个裸仓库(注意 --bare 参数)
git init --bare wiki.git

2. 服务器端 - 创建工作目录并设置权限

  1. 创建hexo工作目录

# 1. 创建目录(用于存放源码和生成静态文件)
sudo mkdir -p /home/keyenzhou/wiki/wiki

# 2. 将此目录的所有权赋予自己,避免权限问题
sudo chown -R keyenzhou:keyenzhou /home/keyenzhou/wiki/wiki
  1. 验证权限

ls -ld /home/keyenzhou/wiki/wiki
# 应显示所有者是 keyenzhou

3. 服务器端 - 创建并配置 Git Hook

自动化的核心

  1. 创建post-receive钩子文件

# 进入裸仓库的 hooks 目录
cd /home/keyenzhou/repo/wiki/wiki.git/hooks

# 使用 vim 创建 post-receive 文件(无后缀)
vim post-receive
  1. 编辑脚本内容(post-receive)

#!/bin/bash
# 当有推送时,这个脚本会被触发

# 设置目录
TARGET="/home/keyenzhou/wiki/wiki"
GIT_DIR="/home/keyenzhou/wiki/wiki.git"

# 循环读取所有被更新的引用(虽然你可能只推了一个分支)
while read oldrev newrev refname
do
    # 只监听 master 分支的推送
    if [[ $refname = "refs/heads/master" ]] ; then
        echo "🎉 收到对 master 分支的推送,开始部署..."
        # 强制将仓库内容检出到目标目录
        git --work-tree="$TARGET" --git-dir="$GIT_DIR" checkout -f master
        # 进入目录,执行部署命令
        cd "$TARGET"
        npm install --production
        npx hexo clean && npx hexo generate
        echo "✅ 部署完成!"
    fi
done
# 循环结束
  1. 赋予脚本执行权限(关键步骤!)

chmod +x post-receive

4. 本地 - 配置 Git 远程仓库并推送

  1. 在本地Hexo博客根目录下,添加远程仓库地址

cd /d/wiki/  # 切换到你的本地Hexo项目目录

# 添加远程仓库的别名
git remote add origin keyenzhou@ip:/home/keyenzhou/wiki/wiki.git
  1. 将代码首次推动到服务器

# 添加所有文件到暂存区
git add .

# 提交更改
git commit --signoff

# 推送到服务器的远程仓库
# -u 等同于 git branch --set-upstream-to=origin/<branch> <mybranch>
# 将本地分支与远程分支相关联,同时推送
git push -u deploy master

建议:

设置git的默认commit message

  1. 设置全局提交模板

# 在用户根目录创建模板文件
mkdir -p ~/.git-templates
vim ~/.git-templates/git-commit-template.txt
# 设置全局提交模板
git config --global commit.template ~/.git-templates/git-commit-template.txt
  1. 设置项目提交模板 (建议)

cd /d/wiki/
vim .git-commit-template.txt
git config commit.template .git-commit-template.txt

笔者设置的模板为:

# 博客项目提交信息模板

类型:
post:     new_post.md
design:   
feature:  
fix:      
optimize: 
config:   

# 文章相关请注明文章标题或slug

# 示例:
# post: 添加《Git使用技巧》文章
# fix: 修复首页样式错位问题

# 详细描述:
# 
#  Breaking changes:
# 
# 关联Issue: #

# 类型:
# post:     新文章
# design:   设计更新
# feature:  新功能
# fix:      修复问题
# optimize: 性能优化
# config:   配置变更

5. 验证与测试

  1. 观察推送过程

    • 执行git push之后,观察命令行输出。如果配置成功能看到来自服务器的Hook脚本的输出信息。

      hook脚本输出
  2. 登录服务器检查

# 检查工作目录是否有文件
ls -la /home/keyenzhou/wiki/wiki

# 检查静态文件是否已生成
ls -la /home/keyenzhou/wiki/wiki/public
  1. 后续日常使用

git add .
git commit --signoff
git push  # 一键触发自动化部署

6. 服务器端 - 配置 Nginx Web 服务

完成环境准备后,使用sudo systemctl status nginx检查nginx状态

  1. 编辑配置文件

sudo vim /etc/nginx/sites-available/default
server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /home/keyenzhou/wiki/wiki/public;

        # Add index.php to the list if you are using PHP
        index index.html index.htm index.nginx-debian.html;

        server_name _;

        location ~* \.(css|js)$ {
                add_header Cache-Control no-cache;
        }

        location / {
                # First attempt to serve request as file, then
                # as directory, then fall back to displaying a 404.
                try_files $uri $uri/ =404;
        }
}
  1. 重启nginx服务

sudo service nginx restart

7. 访问博客

浏览器地址栏输入 http://ip 即可尝试访问,如果一切配置正确,应该能看到 Hexo 博客页面了!

常见问题

1. 403 forbidden

可能是nginx权限不足,可以将nginx与用户放在一个用户组里。

笔者是直接修改了用户目录的权限,使得nginx能够读取文件

sudo chmod -r 755 /home/keyenzhou

2. 为什么用post-reveive而不是post-update钩子

1. post-receive 钩子 (推荐)

  • 时机:当整个 git push 操作已经成功完成,所有引用(分支、标签)都已被更新之后。

  • 行为:它只运行一次。它是“推送后”的最终通知。

  • 数据:它通过标准输入(stdin) 接收数据,每行包含三个参数:<old-value> <new-value> <ref-name>

2. post-update 钩子 (不推荐用于部署)

  • 时机:在推送过程中,每成功更新一个引用(比如一个分支或一个标签)之后,就会立即触发。

  • 行为:一次推送如果更新了 3 个分支,它就会被调用3 次

  • 数据:它通过命令行参数接收被更新的引用名称列表。

潜在问题:
如果你在 post-update 里写部署脚本,一次推送多个分支或标签会导致部署脚本被重复执行多次,这通常不是你想要的行为。

为什么必须用 post-receive

  1. 效率:我们只关心代码是否推送成功,然后执行一次部署任务。post-receive 完美匹配。

  2. 避免重复执行:如果不小心同时推送了分支和标签(git push --all --tags),post-update 会触发两次并尝试部署两次,可能造成冲突。而 post-receive 仍然只触发一次。

  3. 信息更丰富post-receive 可以通过标准输入知道每个引用的旧版本和新版本哈希值,这在做精细化的部署或通知时非常有用。

3.钩子脚本中为什么不使用git pull而是git checkout

特性

git --work-tree=... --git-dir=... checkout -f

git pull

本质

强制覆盖工作目录

合并/拉取并整合远程变更

使用场景

部署脚本、CI/CD、Hook

日常开发协作

仓库关系

通常在同一机器或文件系统上

通常涉及远程仓库(如GitHub)

工作流程

单向强制同步(无视本地修改)

双向协作同步(尝试整合修改)

对本地修改

直接丢弃,强制与仓库一致

尝试保留,并与远程变更合并

安全性

危险(会丢失未提交的工作)

相对安全(有冲突会提示)

git维护的目录不是一个开发环境,而是一个纯粹的部署目标。它不应该有自己的提交历史或需要合并的更改。使用 git pull 在这里是错误且危险的,我们很难处理自动化脚本执行过程中git pull产生的问题。

4. 关于LF的警告

warning: in the working copy of '.git-commit-template.txt', LF will be replaced by CRLF the next time Git to
  • LF (Line Feed):Linux/macOS 系统的换行符

  • CRLF (Carriage Return + Line Feed):Windows 系统的换行符

  • Git 检测到文件中的换行符与您的系统设置不匹配

git config --global core.autocrlf false

总结与展望