chaihongjun.me

github代码通过webhook自动同步部署到阿里云ECS完美解决没有实际效果

这篇文章的使用场景前提:本地开发,然后同步到github仓库,再在服务器上通过git pull从仓库拉取更新的内容。这里的github仓库相当于一个中转站,连接本地和远程服务器的一个桥梁。每次提交到远程仓github库的代码文件,还需要再进入服务器拉取一下,略显的麻烦,如果在本地更改的文件代码提交到github仓库之后,仓库自动的再下发到服务器那么不是非常爽?那就按照如下方式来操作:

首先,服务器环境是lnmp,所以创建一个webhook.php的文件,用来接收github仓库更新的通知,并且做出相应git pull操作。

<?php
//服务器本地仓库路径,填写仓库入口目录
$local = '/alidata1/www/web/';
//github远程仓库地址
$remote = 'https://github.com/example/examplegit';
//密钥,github是密钥,验证方式跟gitee不一样
//请注意,这里设置的密钥稍后在github网站设置的时候需要用到
$secret = 'password';
//获取请求参数
$request = file_get_contents('php://input');
if (empty($request)) {
    die('request is empty');
}
//获取http 头
$headers = getHeaders();
//github发送过来的签名
$hubSignature = $headers['X-Hub-Signature'];
list($algo, $hash) = explode('=', $hubSignature, 2);
// 计算签名
$payloadHash = hash_hmac($algo, $request, $secret);
// 判断签名是否匹配
if ($hash != $payloadHash) {
    die('secret is error');
}
// echo shell_exec("cd {$local} && /usr/bin/git pull {$remote} 2>&1");
// /root/github_synch.sh 是在服务器上面的一个拉取仓库文件的脚本。
echo shell_exec("/root/github_synch.sh");
die('done ' . date('Y-m-d H:i:s', time()));
/**
 * @todo 获取头信息
*/
function getHeaders()
{
    $headers = array();
    //Apache服务器才支持getallheaders函数
    if (!function_exists('getallheaders')) {
        foreach ($_SERVER as $name => $value) {
            if (substr($name, 0, 5) == 'HTTP_') {
                $headers[str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))))] = $value;
            }
        }
    } else {
        $headers = getallheaders();
    }
    return $headers;
}

请将以上文件放置到服务器的web目录内(比如/alidata1/www/web/webhook.php),由于在github网站稍后的配置中,github网站需要访问这个文件,所以给这个文件的访问配置一个URL,比如:

https://github.example.com/webhook.php

 因此,在服务器上就应当配置一个对应的&ldquo;主机&rdquo;,并做好DNS解析工作。

然后,前面提到的那个脚本文件(github_synch.sh):

#!/bin/bash
# github_synch.sh

# 进入服务器本地仓库目录
cd /alidata1/www/web/
# 从github拉取更新
git pull origin master
# 防止拉取更新之后目录权限变更
chown -R www.www /alidata1/www/web
# 防止 修正 webhook.php 可执行权限
chmod +x /alidata1/www/web/webhook.php

这个脚本文件的属主和属组是root,权限是755。

上面这个脚本相当于模拟真人在服务器操作连接github,所以需要配置必备的账户密码等信息:

请在root的家目录创建文件(.git-credentials):

https://用户名:密码@github.com  //github的配置

同样在shell下执行下面的操作:

 git config --global user.name "用户名"
 git config --global user.email 邮箱

上面两行操作是配置服务器访问github仓库之用的。如果已经配置过那么是可以跳过的。

接着:

git config --global credential.helper store

这个时候root的家目录下的.gitconfig文件是这样的:

github代码通过webhook自动同步部署到阿里云ECS完美解决没有实际效果

对root用户的配置就完成了,接着是对WWW用户的,因为php-fpm运行的是www用户,非常简单,将root用户家目录下面的.gitconfig和.git-credentials这两个文件复制到www用户的家目录/home/www/内,并且修改这两个文件的属性:

cp ~/.gitconfig /home/www/
cp ~/.git-credentials /home/www/
cd /home/www/
chown www.www .gitconfig
chown www.www .git-credentials

完成以上的内容之后,服务器端的配置就完成了。

然后,在进入github网站开启webhook:

进入仓库点击右侧的设置:

github代码通过webhook自动同步部署到阿里云ECS完美解决没有实际效果然后,再点击左侧的Webhooks:

github代码通过webhook自动同步部署到阿里云ECS完美解决没有实际效果

然后点击右侧的 Add webhook

github代码通过webhook自动同步部署到阿里云ECS完美解决没有实际效果

payload URL 填写的就是需要访问的钩子文件的URL

content type 选择 application/json

Secret 这里写的密钥和前面webhook.php脚本里面的要一致

如果前面设置的URL是https协议访问的,这里会出现SSL验证选项,如果你的HTTPS是免费的一定选择禁止SSL验证:

github代码通过webhook自动同步部署到阿里云ECS完美解决没有实际效果

然后后面的trigger 选择默认即可 Just the push event

以及最后的Active。

当全部配置完的时候,github会发送一条测试信息,根据Response的内容可以判断是否成功:

github代码通过webhook自动同步部署到阿里云ECS完美解决没有实际效果

虽然,前面是打了对勾,但是返回的信息提示有函数被禁止执行了,这个函数正好是服务器拉取更新需要的功能,所以前往php配置文件将这个被禁止的函数解除禁止,并且重启php服务器。
这个时候你以为就真的大功告成了?错!
最后还是拉取更新没成功,因为PHP脚本是通过php-fpm去执行的,而php-fpm的运行身份是www,www是没有权限去执行root可以执行的动作(git),这里必须让www有root的权限去执行它,而且不需要填写密码。于是,要这么做。编辑修改 /etc/sudoers 文件,配置这么一段:

## Same thing without a password

# %wheel        ALL=(ALL)       NOPASSWD: ALL

www  ALL=(ALL) NOPASSWD:ALL

由于环境和安装的差异这里的git路径请填写真确。到这里你可能以为这回真的没问题了,可惜你又错了。博主在调试和对比了几个场景发现,还有下面两个隐藏的坑:

1. webhook.php那个是由php-fpm(www)来执行运行的,但是它里面执行调用的shell脚本确是在root的家目录里面 

/root/github_synch.sh

www用户是没有权限进入root的家目录的,所以应该把github_synch.sh这脚本搬到www用户可以访问的目录内,比如:/home/www/。所以webhook.php里面执行的shell脚本的路径也需要相应修改。

2.一定确保仓库里面的.git目录下的config配置文件使用的是https访问的:(repo/.git repo是服务器上面的仓库目录)

[core]
        repositoryformatversion = 0
        filemode = true
        bare = false
        logallrefupdates = true
[remote "origin"]
        fetch = +refs/heads/*:refs/remotes/origin/*
        url = https://github.com/example/example.git
[branch "master"]
        remote = origin
        merge = refs/heads/master

上面的关于URL那里,后面必须是https的地址。如果是以SSH协议形式访问的

url = ssh:git@github.com:example/example.git

一定改成https的地址,不然报错

git error: unable to unlink old (Permission denied)

最后,完美收工,当然,最好实际进行测试一下为好。

参考资料:

https://blog.kinggui.com/archives/469

https://www.jianshu.com/p/50ea356152ac

知识共享许可协议本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可。作者:柴宏俊»