给php用户生成ssh-key

sudo -Hu  www ssh-keygen -t rsa
# 一路回车就完事了
# 然后保存公钥到github上
cat /home/www/.ssh/id_rsa.pub

检查php是否允许使用 shell_exec()

<?php

echo shell_exec('ls -la');

如果无法成功执行,则需要修改 php.ini 并重启 php-fpm 。


sudo权限

  • 编辑 /etc/sudoers 文件, 允许 www 用户使用 sudo 指令并且不需要密码
# /etc/sudoers
www ALL=(ALL) NOPASSWD:ALL
  • 这一步骤是为了允许php成功执行 git pull/composer install/npm run install 等命令。 我认为给 www用户赋予 sudo 权限是一种不安全的行为,目前有另一种方案,是把整个项目文件的权限都给到 www。 因为未进行过具体操作,所以这里不再赘述

github中设置webhook

  • github 的仓库主页setting -> setting -> Webhooks -> Add webhook
  • Payload URL 填入你的 webhook 地址

  • Content type 根据个人需要,建议是 application/json
  • Secret 填入你自定义的密码, 用于防止 webhook 泄漏后被恶意调用,鉴别非 github 的请求
  • 下一个选项,可以根据自己的需要,选择需要发送触发 webhook 的事件。 建议是只处理 Pull request ,选择 Let me select individual events. 并勾选 Pull requests 即可

安装 Laravel Shield 中间件保护 github webhook

# 引入package
composer require laravel-shield/github

# 输出配置文件
php artisan vendor:publish
# 然后选择 laravel-shield
# 之后就会生成 config/shield.php
# 务必把刚才 github 中设置的 Secret 写入 .env 文件,通过 env() 来读取使用,避免泄漏

# config/shield.php
<?php

return [

    'services' => [
        'github' => [
            'driver' => \Shield\GitHub\GitHub::class,
            'options' => [
                'token' => env('GITHUB_WEBHOOK_SECRET'),
            ]
        ]
    ]

];


# .env
...
GITHUB_WEBHOOK_SECRET=刚才在github中设置的密码
...

创建一个webhook路由

  • 以 laravel 为例:
  • 创建一个钩子 controller
php artisan make:controller HooksController
  • 编辑路由

    # routes/web.php
    ...
     // webhooks , middleware('shield:github') 为刚刚安装的 laravel shield package 中间件
        Route::post('hooks/github', 'HooksController@github')->middleware('shield:github');
    ...
  • 编辑 webhook 文件
<?php
namespace App\Http\Controllers;

use Symfony\Component\HttpFoundation\Response as HttpResponse;

class HooksController extends ApiController
{
    public function github()
    {
        $pullRequestInformation = \Request::json('pull_request');
        if (empty($pullRequestInformation)) {
            return $this->response->setStatusCode(HttpResponse::HTTP_OK)->json('not found pull request information');
        }

        // 只处理合并的 webhook , 其他开始/关闭都不进行操作
        if (
            $pullRequestInformation['state'] != 'closed'
            || $pullRequestInformation['merged'] != true
        ) {
            return $this->response->setStatusCode(HttpResponse::HTTP_OK)->json("it is not a merge action");
        }

        // 为适应多环境,可以自行设置限制分支
        if (empty(env('CURRENT_BRANCH'))) {
            return $this->response->setStatusCode(HttpResponse::HTTP_OK)->json("need to set a current branch.");
        }
        
        // 如果 pull request 不是更新本分支,则忽略
        if (
            $pullRequestInformation['base']['ref'] != env('CURRENT_BRANCH')
        ) {
            return $this->response->setStatusCode(HttpResponse::HTTP_OK)->json("current branch is incorrect");
        }

        // 执行 git pull , 并进行记录
        $result = shell_exec('cd '.base_path().' && sudo git pull 2>&1');
        \Log::info(__METHOD__." : $result");
        
        // 还可根据 $pullRequestInformation 中返回的更新文件array, 执行例如 composer install/migrate 等操作
    
        // 最后返回 http 200 给 github 即可
        return $this->response->setStatusCode(HttpResponse::HTTP_OK)->json($result);
    }
 }

测试

  • 一切准备之后,更新好线上机器代码,随便创建一个 pull request , 通过github 的仓库主页setting -> setting -> Webhooks -> 对应的webhook地址 ,在最下方会显示每一次 request 的状况和返回
  • 如果出现错误, 解决后可以直接重新执行 ( Rediliver ) 而不需要重复的新建/关闭 pull request

bingo!

恭喜你~ 完成了简易的 webhook 自动部署