前言:我们在进行git代码维护时,每次本地push代码,都要去服务器再进行pull代码的拉取,以保证代码本地和线上的一致性,但是频繁的拉取是非常繁琐的,特别是大项目要进行多个服务器代码同步时,这时候就用到了webhooks(网络钩子)这个东西。
部署使用例子
ps:注意我这里全程用到的是码云进行测试举例,其他的都大同小异
- 去git仓库创建webhooks
- 添加钩子文件 执行shell命令
以我自己项目为例,框架使用的是tp5,在public/下创建一个名为webhooks.php的文件,目的在于当代码推送至服务器上时,github可以访问到并且可以执行的一个文件。该文件用于执行shell 命令,在触发该链接时自动执行该文件里的命令,从而实现git 自动拉取。
具体代码
<?php // 本地仓库路径 项目目录 $local = '/www/wwwroot/wurenji'; // 密码 gitee项目管理webhook中设置 $password = '123'; //如果请求体内容为空,返回错误 $payload = file_get_contents('php://input'); if (!$payload) { header('HTTP/1.1 400 Bad Request'); die('HTTP HEADER or POST is missing.'); } // 如果启用验证,并且验证买QQ平台失败,返回错误 // gitee默认返回json,解析json后验证密码 $data = json_decode($payload, true); if(empty($data) || $data['password'] != $password) { header('HTTP/1.1 403 Permission Denied'); die('Permission denied.'); } // 如果仓库目录不存在,返回错误 if (!is_dir($local)) { header('HTTP/1.1 500 Internal Server Error'); die('Local directory is missing'); } //输出执行结果 包括错误信息,在gitee webhook中可以查看和测试 $res = shell_exec("cd {$local} && git pull 2>&1"); echo $res; $res_log = '-------------------------'.PHP_EOL; $res_log .= ' 在' . date('Y-m-d H:i:s') . '向' . $content['repository']['name'] . '项目的' . $content['ref'] . '分支push'.$res; file_put_contents("git-webhook.txt", $res_log, FILE_APPEND);//将每次拉取信息追加写入到日志里 die("done " . date('Y-m-d H:i:s', time()));
- 修改.git/config文件url
url = https://账号:密码@gitee.com/xiangyuphp/项目名.git
- php.ini 文件里shell 没有禁用
处理方法 将php.ini 里的shell_exec删除,重启服务即可
(重启php:systemctl restart php-fpm)
- 修改.git和FETCH_HEAD的文件权限
chmod -R 777 .git
chmod -R 777 .git/FETCH_HEAD
测试
点击测试就可以看到返回信息,是否成功
日志
每次本地提交代码时,会在public/下生成git-webhook.txt日志文件,记录webhooks运行日志
原理:
每次你push代码到git仓库后,git代码管理平台就会检测push事件,然后发送一个post请求到你的绑定https://xxx.webhooks.php,在该访问的post请求中处理git拉取代码功能。
多服务器例子
SSH 连接公钥登入
其他登入方法:Linux 系统实现 SSH 连接的 3 种 方式
原理:注入公钥到服务端,表示拥有该公钥的客户端可以免密登入 将客户端 A 的公钥~/.ssh/id_rsa.pub复制到服务端 B 的授权Key文件~/.ssh/authorized_keys中。 可采用手动方式,也可以在客户端 A 执行命令 ssh-copy-id root@172.19.0.2来实现 也可以使用ssh -p 16222 root@xxxx,手动在服务器上连接一遍ssh 在客户端 A 执行命令 ssh 172.19.0.2 即可实现免密登入 输入 exit 退出
index.php
<?php define('ROOT', __DIR__); include './common.php'; $json = file_get_contents('php://input'); //fileLog($json, date('Y-m-d')); $input = json_decode($json, true); //如果没数据 不做处理 if (!$input) { header('HTTP/1.1 400 Bad Request'); die('HTTP HEADER or POST is missing.'); } //如果不是合并, 推送事件 不做处理 if(!in_array($input['object_kind'], ['merge_request', 'push'])) return true; //分支(只有合并事件才有这个变量) $branch = $input['object_attributes']['target_branch'] ?? null; //分支(只有推送事件才有这个变量) $branch_ref = $input['ref'] ?? null; //项目绝对路径 $path = '/data/wwwroot/'; //获取项目名称 $projectName = strtolower($input['repository']['name']); if($projectName == 'guildapi'){ $projectName = 'guildApi'; //大小写转义 } //后台 $ips_guild = [ '43.13xxxx', //洛谷 ]; //api $ips_guildapi = [ //洛谷 '43.138.xxx', //sdk1 '43.138.1xxx', //sdk2 ]; //测试服 $ips_test = [ '101.3xxx', ]; $msg = ''; $ips = []; //调用shell脚本拉取代码 if($branch == 'master' || $branch_ref == "refs/heads/master"){ //正式服 if($projectName == 'guild'){ $ips = $ips_guild; }else{ $ips = $ips_guildapi; } foreach($ips as $ip){ $res = shell_exec("sudo ./gitlab_config.sh {$path}{$projectName} {$ip} 2>&1"); echo $res; echo "\n"; echo '-------------------------'; echo "\n"; } }else{ //测试服 foreach($ips_test as $ip){ $res = shell_exec("sudo ./gitlab_config.sh {$path}{$projectName} {$ip} 2>&1"); echo $res; echo "\n"; echo '-------------------------'.PHP_EOL; echo "\n"; } } //$res = json_encode($out); //file_put_contents("log.txt", $res . "\n", FILE_APPEND);//将每次拉取信息追加写入到日志里 die("done " . date('Y-m-d H:i:s', time()));
gitlab_config.sh文件shell脚本代码
#!/usr/bin/expect -f #保持交互式命令 #interact set projectPath [lindex $argv 0] set ip [lindex $argv 1] # 设置ssh连接的超时时间 set timeout -1 set password "h87xxxxx" #ssh -p 16222 root@42.1.xxxx ssh每连接一个新的服务器,需要手动在服务器上连接一遍ssh,保存 spawn ssh -p 16222 root@$ip # set host "192.168.1.14" # set passwd "123456" # spawn ssh root@$host # expect { # "yes/no" { send "yes\r";exp_continue } # "assword:" { send "$passwd\r" } # } # interact expect "]*" # 通配符 send "cd $projectPath\r" expect "]*" send "sudo git pull\r" expect "]*" exit expect eof # 退出此expect交互程序
shell脚本扩展
执行
./gitlab_config.sh x y
定义一个变量
set username "leo" set name "xx.domain"
获取命令行参数(与bash不同的是第一个参数index为0)
set hostname [lindex $argv 0] //获取第一个参数,及x值,存入hostname变量中 set password [lindex $argv 1] //获取第二个参数,及y值,存入password变量中
定义数组
// 定义一个数组 set host_list(0) {host1 127.0.0.1} set host_list(1) {host2 192.168.1.1} set host_list(3) {host3 8.8.8.8} //获取数组长度 set len [array size host_list] send $len
if条件
[string compare $host "123"] //判断变量是否相等,相等返回0,不相等返回1 set host [lindex $argv 0] //获取第一个参数 if ![string compare $host ""] { //此处花括号前必须有一个空格,具体请参考TCL语言规范 //如果host变量为空字符串 } if {[string compare [string toupper $choose] "N"] == 0} { //如果choose变量为"N" } elseif {[scan $choose {%[0-9]} choose] == 0} { //如果choose不是数字,scan用户匹配,详情参考TCL语法 } elseif { $choose < 0 || $choose >= $len } { exit //退出 }
for循环
set len [array size host_list] for {set index 0} {$index < $len} {incr index} { puts "$index -> $host_list($index)" } // incr为自增关键字 // puts用于输出到用户,类似于send_user
注意:
配置gitlab合并dev分支触发钩子,测试服自动拉取dev分支代码: shell脚本最低要655权限,并且需要unix格式, 可以: vi filename 然后用命令 :set ff 可以看到dos或unix的字样. 如果的确是dos格式的, 那么你可以用set ff=unix把它强制为unix格式的, 然后存盘退出. 再运行即可
gitlab设置:
1、点击项目里的设置,选择Webhooks 2、填写服务器URL、勾选合并请求事件(只处理合并到dev分支),如果URL是本地域名,需要点击管理中心、 点击设置、选择网络、点击外发请求 勾选允许Webhook和服务对本地网络的请求 填写本地域名 如:gitlab.script.com 点击保存, 如果gitlab使用外部的nginx 则需要在/etc/hosts文件将域名指向本地