使用Github的webhook完成自动部署网站

Hexo 静态站点自动部署:通过 GitHub WebHook 同步到国内服务器

你希望解决 Hexo 部署到 github.io 因境外服务器、域名被墙导致访问不稳定的问题,本文整理完整方案:通过配置 GitHub WebHook 触发国内服务器的 Koa 接口,自动拉取/更新 Hexo 静态仓库代码,实现站点部署到国内服务器,大幅提升访问稳定性。

一、核心思路

  1. 核心痛点:github.io 服务器在境外,域名易被墙,访问稳定性差;
  2. 解决方案:
    • GitHub 仓库配置 WebHook,当代码 push 时触发指定接口;
    • 国内服务器部署 Koa 服务,提供 WebHook 接收接口;
    • 接口逻辑:自动检查本地静态目录 → 存在则 git pull 更新 → 不存在则 git clone 拉取 → pull 失败则删除重克隆,最终将 Hexo 静态文件部署到国内服务器。

二、第一步:GitHub WebHook 配置(仓库端)

1. 操作步骤

  1. 登录 GitHub,打开你的 Hexo 静态仓库(*.github.io);
  2. 进入仓库 → Settings(设置)→ 左侧 Webhooks → 点击 Add webhook
  3. 核心配置项:
    配置项 说明 & 推荐值
    Payload URL 国内服务器的接口地址(如 https://你的域名/github/xxx/update),需确保服务器端口可访问
    Content type 选择 application/json(便于后续接口解析)
    Secret (可选但推荐)设置自定义密钥(如随机字符串),用于接口验证请求合法性
    Which events would you like to trigger this webhook? 选择 Just the push event(仅当代码 push 时触发,避免无效请求)
    Active 勾选(启用 WebHook)
  4. 点击 Add webhook 完成配置,GitHub 会自动发送测试请求,可在 Recent deliveries 中查看触发状态。

补充:若测试请求失败,需检查国内服务器的防火墙/安全组是否开放接口端口(如 80/443),确保接口可被 GitHub 访问。

三、第二步:国内服务器部署自动同步脚本(Koa 实现)

1. 环境准备

服务器需提前安装依赖:

1
2
3
4
5
6
7
8
# 安装 Git(用于拉取/更新仓库)
yum install git -y # CentOS
# 或 apt-get install git -y # Ubuntu

# 初始化 Node 项目(若未部署 Koa 服务)
mkdir hexo-deploy-server && cd hexo-deploy-server
npm init -y
npm install koa koa-router path fs child_process --save

2. 完整自动部署代码(Koa 路由)

优化原脚本,补充 pull 失败后删除重克隆错误处理日志记录 等关键逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
const Koa = require('koa');
const Router = require('koa-router');
const path = require('path');
const fs = require('fs');
const { exec, execSync } = require('child_process');

const app = new Koa();
const router = new Router();

// 配置项(根据实际情况修改)
const CONFIG = {
repoName: 'csorz', // 你的 GitHub 用户名
staticDir: path.resolve(__dirname, '../../static/csorzhub/'), // 静态文件存放目录
gitUrl: 'http://github.com/csorzhub/csorzhub.github.io.git', // Hexo 仓库地址
maxRetry: 3 // pull 失败后重试次数
};

/**
* 检查目录是否存在
* @param {string} dirPath - 目录路径
* @returns {boolean} 是否存在
*/
function checkDirExists(dirPath) {
try {
fs.statSync(dirPath);
return true;
} catch (err) {
return false;
}
}

/**
* 执行 Git Pull 操作
* @param {string} dirPath - 仓库目录
* @param {number} retry - 已重试次数
* @returns {Promise<boolean>} 是否成功
*/
async function execGitPull(dirPath, retry = 0) {
return new Promise((resolve) => {
exec('git pull', { cwd: dirPath }, (error, stdout, stderr) => {
if (error) {
console.error(`[${new Date()}] Git Pull 失败(重试${retry}次):`, error.message);
// 重试次数未达上限,再次尝试
if (retry < CONFIG.maxRetry) {
resolve(execGitPull(dirPath, retry + 1));
} else {
resolve(false); // 重试耗尽,返回失败
}
} else {
console.log(`[${new Date()}] Git Pull 成功:`, stdout);
resolve(true);
}
});
});
}

/**
* 删除目录并重新克隆仓库
* @param {string} dirPath - 仓库目录
* @param {string} gitUrl - 仓库地址
* @returns {boolean} 是否成功
*/
function deleteAndReClone(dirPath, gitUrl) {
try {
// 删除原有目录
fs.rmSync(dirPath, { recursive: true, force: true });
console.log(`[${new Date()}] 已删除目录:${dirPath}`);
// 重新克隆
execSync(`git clone ${gitUrl} ${dirPath}`, { stdio: [0, 1, 2] });
console.log(`[${new Date()}] 重新克隆仓库成功`);
return true;
} catch (err) {
console.error(`[${new Date()}] 删除重克隆失败:`, err.message);
return false;
}
}

// GitHub WebHook 触发接口
router.get('/github/:repo/update', async (ctx) => {
try {
const { staticDir, gitUrl } = CONFIG;
const dirExists = checkDirExists(staticDir);

if (dirExists) {
// 目录存在,先尝试 pull 更新
const pullSuccess = await execGitPull(staticDir);
if (!pullSuccess) {
// pull 失败,删除重克隆
const reCloneSuccess = deleteAndReClone(staticDir, gitUrl);
if (!reCloneSuccess) {
ctx.status = 500;
ctx.body = { code: 500, msg: '更新失败:Pull 重试耗尽且重克隆失败' };
return;
}
}
} else {
// 目录不存在,直接克隆
execSync(`git clone ${gitUrl} ${staticDir}`, { stdio: [0, 1, 2] });
console.log(`[${new Date()}] 首次克隆仓库成功:${staticDir}`);
}

ctx.body = { code: 200, msg: `更新 ${CONFIG.repoName}.github.io 成功!` };
} catch (err) {
console.error(`[${new Date()}] 接口执行异常:`, err.message);
ctx.status = 500;
ctx.body = { code: 500, msg: '更新失败:服务器内部错误', error: err.message };
}
});

// 注册路由并启动服务
app.use(router.routes()).use(router.allowedMethods());
const PORT = 3000; // 可修改为实际端口(需开放防火墙)
app.listen(PORT, () => {
console.log(`部署服务已启动:http://localhost:${PORT}`);
});

3. 代码关键优化说明

优化点 说明
配置抽离 将仓库地址、目录、重试次数等抽离为 CONFIG,便于维护
重试机制 Git Pull 失败后自动重试(默认3次),避免网络波动导致失败
失败降级 重试耗尽后自动删除目录并重新克隆,保证最终同步成功
日志记录 打印操作时间和结果,便于排查问题
异常捕获 全局 try/catch 包裹,避免接口崩溃,返回明确的错误信息

四、第三步:验证与上线

  1. 启动 Koa 服务:
    1
    2
    3
    4
    node app.js # 简易启动
    # 推荐使用 pm2 守护进程(避免服务中断)
    npm install pm2 -g
    pm2 start app.js --name hexo-deploy-server
  2. 测试触发:
    • 本地修改 Hexo 代码,git push 到 GitHub 仓库;
    • 查看 GitHub WebHook 的 Recent deliveries,状态应为 200
    • 查看服务器日志(pm2 logs hexo-deploy-server),确认「Pull/Clone 成功」;
    • 访问国内服务器的静态文件地址,验证站点是否更新。

五、关键注意事项

  1. 权限问题:确保运行 Koa 服务的用户有 git 执行权限、静态目录的读写权限;
  2. Secret 验证(可选):生产环境建议在 WebHook 配置 Secret,并在接口中验证请求头 X-Hub-Signature-256,防止恶意请求;
  3. HTTPS 配置:若接口地址用 HTTPS,需在服务器配置 SSL 证书(如 Let’s Encrypt),GitHub WebHook 优先推荐 HTTPS 地址;
  4. 定时检查(可选):可添加定时任务(如 crontab),定期检查仓库是否同步,避免 WebHook 触发失败导致站点未更新。

总结

  1. 核心流程:GitHub WebHook(push 触发)→ 国内服务器 Koa 接口 → 自动 Pull/Clone Hexo 仓库 → 部署静态文件;
  2. 关键优化:Pull 失败重试 + 重克隆降级,保证同步成功率;
  3. 稳定性保障:用 pm2 守护 Koa 服务,配合日志/权限配置,避免服务中断。

通过这套方案,可彻底解决 github.io 访问不稳定的问题,同时保留 GitHub 代码管理的便利性,实现 Hexo 站点的国内稳定部署。


使用Github的webhook完成自动部署网站
https://cszy.top/20201116-git-1/
作者
csorz
发布于
2020年11月16日
许可协议