星河避难所

返回

写博客写代码都适用:推完就自动部署的 GitHub Webhook 工具模糊背景图

前阵子,我看到有人在吐槽自己写博客很麻烦——写麻烦、托管麻烦,推完 GitHub 之后还得去服务器上手动拉代码部署。虽然我以前也在做类似的东西,但他这么一提让我想起这件事,我就想着,不如把自己一直随便用的小玩意整理一下,顺便分享给大家。

于是就有了这个小项目:HookExec,一个用 Go 写的 GitHub Webhook 脚本执行器。推完代码,服务器自己去拉取、构建、重启,你只管写。


什么是 Webhook#

Webhook 本质上就是 GitHub 在某些操作发生时,向你指定的地址发送一个网络通知。最常见的事件包括 push、pull_request、release 等。收到通知之后,你就可以根据事件类型做一些操作,比如自动部署代码、触发测试、更新文档等等。

设置 Webhook 其实也很简单:在 GitHub 仓库里找到 Settings → Webhooks → Add webhook,填入你服务器的地址和一个 Secret,选中你想监听的事件,然后 GitHub 就会在这些事件发生时发送 POST 请求到你的服务器。只要你的服务端能接收请求、校验 Secret,再执行对应操作,就完成了。

我以前一直随便把 Webhook 放在某个在跑的服务里处理,所以一直没有好好整理过。Webhook 的核心其实不复杂,它需要的只是一个接口去接收请求,这也是之前一直随手搞的原因。


为什么用 Go 写#

Go 写这种小工具特别合适:

  • 编译后就是一个二进制文件,直接丢到服务器上运行就行,不依赖其他环境。
  • 运行开销小,启动快,非常适合长期驻留监听请求。
  • 写起来简单,核心逻辑就是解析 HTTP 请求、验证签名、匹配事件和仓库,然后执行脚本。

它长什么样#

一个二进制、一个 SQLite 文件、一个脚本目录:

/opt/hookexec/
├── hook-exec              ← 单个可执行文件
├── hook-exec.db           ← SQLite,存配置和执行记录
└── shell/                 ← 你的脚本放这里
    └── blog.sh
plaintext

没有外部依赖,不需要 Node.js,也不会从外网拉任何东西。服务器在内网也照样跑得起来。

管理页在 /web,打开是这样:

HookExec 项目列表

每个项目一张卡片,关联的仓库、脚本文件、工作目录和超时时间都写在上面,右下角是最近一次执行的结果。要加一个站,点「新建项目」把仓库和脚本关联起来就行:

新建项目

执行记录在另一个标签页,点开任意一条,能看到这次投递的完整信息,以及脚本跑出来的完整输出:

执行记录详情

我手上有一堆博客、文档站和小服务,分散在不同仓库里。让每个服务自己监听自己的 webhook 并不是做不到,但每加一个站就要重复一遍「起个小 HTTP 服务 + 验签 + 解析 payload + 调部署命令」,而且这些逻辑散在各处,出没出问题得一个个去看。

HookExec 把这件事收敛成一个常驻服务:所有仓库的 webhook 都打到它这里,它按 owner/repo 找到对应的脚本执行。加一个新站就是「新建一个项目 + 传一个脚本」,不用再动服务本身。


一个刻意的设计:脚本不由它创建#

这里要说一下我这次特意坚持的一点:网页上不能编写或修改脚本。

可执行的内容全部来自 <数据目录>/shell/,只能由你自己通过 scp / ansible / git 传上去。网页端能做的只是「从服务器上已有的脚本里挑一个执行」。

这样一来,就算管理页的密码泄露了,对方也只能在你放上去的脚本里挑一个跑,而不是能在你的服务器上执行任意命令。

脚本路径的校验我收在了同一个包里:拼出来的路径必须落在脚本目录内(软链解掉之后再判一次)、必须是普通文件,而且每次执行前都重新校验,不信任数据库里存的值——即使有人直接改库、或者导入一份恶意配置,也逃不出脚本目录。

当然,管理页的凭证仍然是服务器级别的机密,记得通过 HTTPS 反代暴露,不要直接裸奔在公网上。


跑起来#

git clone https://github.com/zxc7563598/hook-exec && cd hook-exec
make build
./hook-exec --listen 127.0.0.1:9000 --data-dir /opt/hookexec
bash

第一次启动会把数据目录和 shell/ 建好。没给密码的话会随机生成一个打印出来,只显示这一次,记得存下来。

然后:

  1. 打开 http://127.0.0.1:9000/web 登录管理页
  2. 把脚本放进 <数据目录>/shell/ 并 chmod +x,点「重新扫描」
  3. 新建项目,把仓库和脚本关联起来
  4. 到 GitHub 仓库的 Settings → Webhooks 填上你的地址,Secret 填和项目里一致的那个

配好之后 GitHub 会立刻发一个 ping 做连通性检查,HookExec 回 200 并且什么都不做,就说明通了。


我的部署脚本#

我博客的部署脚本大概是这样,放在 /opt/hookexec/shell/blog.sh:

注意脚本是直接执行的,靠 shebang 决定解释器,不需要(也不能)在配置里写 sh 前缀。所以要记得 chmod +x,没有可执行位是跑不起来的,页面上会标黄提醒你,而不是等执行的时候抛一个晦涩的 permission denied。

脚本需要的信息全部通过环境变量传进来,常用的有这些:

  • HOOK_REPO —— owner/repo
  • HOOK_EVENT —— push / pull_request / release …
  • HOOK_BRANCH、HOOK_REF —— 分支名、refs/heads/main
  • HOOK_SHA —— 提交 SHA
  • HOOK_ACTOR —— 触发者
  • HOOK_PAYLOAD_FILE —— 完整 payload 的临时文件路径,要取深层字段直接 jq 它就行

另外也提供了一套 GitHub Actions 风格的同名变量(GITHUB_REPOSITORY、GITHUB_SHA、GITHUB_REF_NAME……),现成的部署脚本改一下就能用。

有了这个流程,我写博客只管写,编辑器里 push 完就完事了。


如果你也想试试#

项目在 GitHub 上:HookExec ↗。它轻量、零依赖,上手也快。如果你也想搞个自动部署,可以直接拿去试试,不用在意具体实现,看看 README,或者直接在 releases 里下载对应平台的包,解压就能用。

有问题或者想法,欢迎直接在 GitHub 上交流。

写博客写代码都适用:推完就自动部署的 GitHub Webhook 工具
https://hejunjie.life/blog/a9ee8nt5
作者 何俊杰
发布于 2025年11月20日
评论区好像卡住了,试试刷新?✨