导航
导航
文章目录
  1. 简介
  2. 过程
    1. 设置回调 URL,选择事件
    2. 添加 Deploy keys
    3. 准备钩子文件
    4. 克隆项目文件
    5. 测试
    6. 验证

利用 Webhooks 实现代码的自动拉取

很长时间没有更新博客了,前段时间一直都在忙着找工作,最近才稳定下来,后面有机会的话再说说找工作的经历。

以前做完一个项目,要放到服务器上运行,最开始使用 Xftp 将代码传上去,中间如果需要修改代码,都是在本地调试完再传到服务器,覆盖原来的文件生效,非常麻烦。

后来使用 Git 做版本管理,配合代码托管平台,就不用一个个的传文件了,每次修改完代码 push 到远程版本库以后,只需要登陆到服务器上,pull 一下,最新的代码就被拉下来了,比之前方便了许多。

进了公司在发现,之前的方法还是太笨了,Github 提供了一个功能 Webhooks,利用它可以实现代码的自动拉取,每次 push 完代码,再也不用登陆服务器手动拉代码了,非常方便。

简介

Webhooks 是代码托管平台提供的一个功能,对于任意一个项目,可以设置一个 URL,同时选择一些事件,当触发了这些事件时,代码托管平台会自动请求该 URL,并带上一些必要的参数。简单点说,Webhooks 就是一个项目的钩子函数,当你执行一些事件时,会自动调用这个钩子函数,事件就是 push、pull request、fork 等等这些操作。

代码托管平台有很多,常见的第三方的 GitHub、码云、Coding,或者自己搭建的 GitLab 都提供了 Webhooks 功能,设置方法基本都差不多,本文以 GitHub 为例讲解。

过程

设置回调 URL,选择事件

登陆 GitHub,新建一个版本库,命名为 Webhooks,依次点击 Settings -> Webhooks -> Add webhook,之后如下图

三个参数:

  • Payload URL:必填,回调的 URL,每次检测到事件时,都会向该 URL 发送一个 POST 请求;
  • Content type:POST 携带数据的方式,GitHub 上提供 application/json 和 application/x-www-form-urlencoded 两种,如果只是实现简单的拉代码,选哪一种无所谓,其它的代码托管平台可能只提供 json 方式;
  • Secret:密钥,用于验证,现在先不填。

下面就是事件选择,默认 push 的时候触发,一般这样就可以了,如果想选择其它事件,点第三个按钮就可以看到所有的事件,选择合适的事件后,点 Add webhook 按钮即可。

添加 Deploy keys

两个公钥

  • 用户公钥:SSH keys,认证用户身份,添加用户公钥后,对该用户的所有项目拥有读写权限,用于开发机;
  • 部署公钥:Deploy keys,对项目进行授权,拥有只读权限,一般用于生产或测试服务器。

详情:Coding 中部署公匙和 SSH 公匙区别

知道两个公钥的区别后,通过 SSH 登陆服务器,执行以下命令

1
2
3
sudo mkdir /var/www/.ssh
sudo chown -R www-data:www-data /var/www/.ssh
sudo -Hu www-data ssh-keygen -t rsa -C "your_name@example.com"

第二条命令表示将目录 .ssh 的拥有者、所属组修改为 www-data,第三条命令表示在 /var/www/.ssh 目录下生成密钥,同时将该密钥的拥有者、所属组修改为 www-data,为什么这么做呢?因为在 Ubuntu 中,PHP 运行时的用户为 www-data,如果不修改,PHP 运行时的用户是读不到这个部署公钥的,也就拉不了代码了。

如果不确定系统的 PHP 运行时使用的哪个用户,可以随便新建一个 PHP 文件,写下面的代码,访问一下就知道了。

1
2
3
<?php
system("whoami");
?>

部署公钥生成后,执行下面的代码查看公钥,复制

1
sudo cat /var/www/.ssh/id_rsa.pub

依次点击项目的 Setting -> Deploy keys -> Add deploy key,将公钥粘进去,点击 Add key 添加完成。

准备钩子文件

在 GitHub 上操作完成后,下面的操作都在服务器上。

在刚才设置的 URL 指向的目录下,新建一个 index.php 文件,代码如下

1
2
3
<?php
shell_exec("cd /var/www/html/Webhooks && git pull 2>&1"); // 切换到项目目录,执行 git pull,加上 2>&1 会输出一些错误信息,便于调试
?>

需要说明的是,我上面设置的回调地址 webhooks.mrzhouxiaofei.com 指向了服务器上 /var/www/html/webhooks 这个地址,所以新建的 index.php 文件也在这个目录下,GitHub 回调地址时,自然就访问到了这个文件。

如果你设置的回调地址不一样,可以根据地址访问的目录新建文件。

同样的,也需要为目录和文件设置拥有者、所属组,命令如下

1
sudo chown -R www-data:www-data /var/www/html/webhooks

克隆项目文件

克隆项目,同时修改项目的拥有者、所属组,如下

1
sudo -Hu www-data git clone git@github.com:mrzhouxiaofei/Webhooks.git

执行完后,服务器的 /var/www/ 目录结构如下

1
2
3
4
5
----/var/www
------------/.ssh 部署公钥,拥有者、所属组为 www-data
------------/html
----------------/webhooks/index.php 钩子文件,拥有者、所属组为 www-data
----------------/Webhooks 项目目录,拥有者、所属组为 www-data

到此为止,服务器应该就能自动拉取项目文件了,可以在 GitHub 上的 Webhooks 项目里新建一个文件,然后看下服务器的项目目录里有没有拉取新文件。

测试

打开项目,点击 Setting -> Webhooks,可以看到刚刚设置的回调 URL,点进去拉到最下面 Recent Deliveries,可是看到所有的被触发事件的请求,任意点击一个查看,如下图所示

  • Request
    • Headers:请求头,包含一些基本信息;
    • Payload:触发这次事件的所有信息都包含在这里面,包括项目名、commit、用户名等等,如果服务器上的钩子文件要做一些高级操作,就可以解析这个字段。
  • Response
    • Headers:响应头
    • Body:服务器钩子文件的返回信息,调试输出信息可以写到钩子文件里,请求后在这里查看。

每一种事件发送的 Payload 格式是不一样的,关于 Payload 更多信息,请看官方文档: Event Types & Payloads

验证

对于基本的项目拉取代码,以上的内容已经足够了。

但是很容易就能想到一个问题,如果别人知道了你回调的 URL,然后他在自己项目的 Webhooks 里填上你的 URL,然后疯狂 push 怎么办?尽管没办法获取你的代码,但是你的服务器会执行一些不必要的 git pull,显然这是我们不能容忍的。

这个时候,Secret 就派上用场了。添加 Webhooks 的时候,Secret 列填上一个随机字符串,在服务器上的钩子文件里再写个验证就可以了,这样就可以挡住恶意的攻击了。

具体来说,在 Secret 里填上一个字符串,比如说填上 mrzhouxiaofei,登陆服务器,编辑 /var/www/html/webhooks 目录下的钩子文件 index.php,内容如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
$secret = "mrzhouxiaofei"; //密钥,和 GitHub 上对应
$path = "/var/www/html/Webhooks"; //服务器上的项目文件目录
$signature = $_SERVER["HTTP_X_HUB_SIGNATURE"]; //获取散列字符串
if($signature) {
$rawPost = file_get_contents("php://input"); //获取收到的数据
list($algo, $hash) = explode("=", $signature, 2); //获取散列算法、散列值
if ($hash === hash_hmac($algo, $rawPost, $secret)) { //验证
shell_exec("cd /var/www/html/Webhooks && git pull 2>&1");
echo "代码拉取成功";
} else {
echo "Secret 验证失败";
}
} else {
echo "请输入 Secret";
}

这样,每次触发事件,GitHub 会使用 SHA-1 将发送的数据和 Secret 一起散列,生成一个散列字符串,在钩子文件中需要对这个散列字符串进行验证。

对于其它代码托管平台,有的不提供 Secret 字段,有的 Secret 在钩子文件中直接验证,至于使用哪一种,看对应的官方文档就行了。