使用Github的WebHooks实现生产环境代码自动更新

一般公司的项目都会使用 Git 或者 SVN 进行版本控制,在本地开发之后push上去,然后再使用ssh连接线上服务器去手动拉取代码。甚至于有些公司还在使用传统的更新代码:使用ftp/sftp进行上传覆盖

现在的线上仓库比如 Github、Gitlab、Gitee 等都支持hook技术,可以很方便的实现代码的自动化管理

这里以我经常使用的 Github 为例,监听dev分支有push动作时,可以自动通过设置的hook通知生产环境中的脚本执行git pull拉取代码,自动更新,非常方便

关于WebHooks

让我们看看 官方 关于Github webhooks的解释:

Webhooks allow you to build or set up integrations which subscribe to certain events on GitHub.com.

总结出来几个点就是:

  • 必须是 Github 上面的项目
  • 订阅了确定的事件(包括 push/pull 等命令)
  • 自动触发

其他线上仓库也是一样的,我们要达到的目的是:当有新的本地commit push到线上仓库时,服务器仓库自动pull最线上仓库新的代码

WebHook的工作原理也是很简单的:

当我们push代码到线上仓库,线上仓库必然知道这个push操作,就会hook(可以理解为回调)我们预留的URL

而这个URL对应一段后台代码,这段代码执行了git pull,这样就实现自动更新的操作

准备工作

这里以 PHP 的代码为例,实际上用 Java、JavaScript 等都可以

我们需要在生产环境的服务器上装好 Git,这个应该是没有问题的

然后我们需要克隆代码下来,这里需要注意的是用户组和权限的问题

PHP 一般使用www或者nginx用户运行,PHP通过脚本执行系统命令也是用这个用户,所以必须确保在该用户家目录(一般是/home/www或/home/nginx)下有.ssh目录

我们生成SSH和克隆代码的时候,需要加上

sudo -Hu www ssh-keygen -t rsa -C "email@address.com"

sudo -Hu www git clone git@github.com:sy-records/WordPress-tools.git

我的服务器是www用户权限,所以我加的是www,你可以看一下你的服务器是那个用户在运行 PHP

SSH生成好之后用户家目录是有.ssh目录的

如果不确定的话,测试一下连接

sudo -Hu www ssh -T git@github.com

在哪里克隆的代码,查看一下你的目录,因为脚本里要用

PHP代码

Github、GitLab、Gitee 虽然都是Git仓库平台,但是发送的WebHooks请求的数据格式有些差别

  • Github支持application/jsonapplication/x-www-form-urlencoded两种格式,安全token需通过请求头X-Hub-Signature加密发给URL,服务器需要解密后验证。了解更多
  • GitLab支持application/json格式,安全token通过请求头HTTP_X_GITLAB_TOKEN明文发给URL。了解更多
  • Gitee也支持application/jsonapplication/x-www-form-urlencoded两种格式,安全token放在请求体明文发给URL,名称是password了解更多

请求头我们可以通过$_SERVER全局变量获得请求的值,比如$_SERVER['X-Hub-Signature']

然后看一下你的服务器支持不支持shell_exec这个 PHP 函数

确保PHP正常执行系统命令,写一个PHP文件,内容:

echo shell_exec('ls -la');

在通过浏览器访问这个文件,能够输出目录结构说明PHP可以运行系统命令

新建一个 PHP 文件

$target = '/www/wwwroot/WordPress-tools'; // 生产环境web目录
//密钥
$secret = "test6666";
//获取GitHub发送的内容
$json = file_get_contents('php://input');
$content = json_decode($json, true);
//github发送过来的签名
$signature = $_SERVER['HTTP_X_HUB_SIGNATURE'];
if (!$signature) {
   return http_response_code(404);
}
list($algo, $hash) = explode('=', $signature, 2);
//计算签名
$payloadHash = hash_hmac($algo, $json, $secret);
// 判断签名是否匹配
if ($hash === $payloadHash) {
    $cmd = "cd $target && git pull";
    $res = shell_exec($cmd);
    $res_log = 'Success:'.PHP_EOL;
    $res_log .= $content['head_commit']['author']['name'] . ' 在' . date('Y-m-d H:i:s') . '向' . $content['repository']['name'] . '项目的' . $content['ref'] . '分支push了' . count($content['commits']) . '个commit:' . PHP_EOL;
    $res_log .= $res.PHP_EOL;
    $res_log .= '======================================================================='.PHP_EOL;
    echo $res_log;
} else {
    $res_log  = 'Error:'.PHP_EOL;
    $res_log .= $content['head_commit']['author']['name'] . ' 在' . date('Y-m-d H:i:s') . '向' . $content['repository']['name'] . '项目的' . $content['ref'] . '分支push了' . count($content['commits']) . '个commit:' . PHP_EOL;
    $res_log .= '密钥不正确不能pull'.PHP_EOL;
    $res_log .= '======================================================================='.PHP_EOL;
    echo $res_log;
}

在执行的命令后面加上2>&1可以输出详细信息,确定错误位置,我这里没加,保存即可

打开你的 Github 仓库项目地址,进入Webhooks

webhooks列表

点击Add webhook,添加一个webhook

添加webhook

Payload URL 填写可以访问你刚才保存的那个文件的地址,建议放在一个可以访问的目录即可,不需要在你的项目目录中,放在项目目录中会提示你有新文件,很烦人的。当然你也可以把它当做项目的文件去提交上去

Content type 我们选择application/json

Secret 就是我们刚才的$secret变量给的值,我这里是test6666

下面一个不用改,选择Just the push event.,因为我们只需要push的时候进行回调,然后添加即可

然后 Github 会发送一个测试的请求,我们可以看一下Response是不是 200,然后看一下Body中有没有success

第一次有个 Warning 是因为count这个函数的问题,Github 发送的测试请求没有push条数

然后我们可以在本地push一下,再去测试一下,有什么问题评论讨论吧

2 条评论

发表评论

*