本篇不是长篇大论服务器的架设,只是谈与svn或者git相关的内容。

首先说下自己的思路,每个开发人员将代码提交到svn服务器的时候,触发post-commit钩子,使得代码自动更新到测试服务器,在测试服务器上查看代码上线后是否OK。如果没问题,则对与测试服务器环境完全一致的线上服务器的代码进行手动更新。手动更新的方式为通过web访问,web访问一个php脚本,php脚本去执行shell命令来更新。

如果我们每次都是对线上服务器做ssh连接,然后执行svn up的命令实在有点儿浪费时间。而且不是所有人都权限去访问线上服务器,如果所有人更新代码都要一个人去更新,更是麻烦。而且,笔者现在的实际开发环境中,线上服务器只对外开放了一个80端口,想连ssh,还得先连自己搭建的vpn。所以我们最好是能直接通过web端进行更新。

代码github地址 https://github.com/zhoumengkang/svn-hook-to-remote-server

下面说下实现的代码的详细解释:

首先是svn服务器上的钩子post-commit代码

#!/bin/sh
export LANG="zh_CN.UTF-8"
#更新到远程的服务器
#/home/wwwroot/xxx/svn.php 为svn.php在svn服务器上的存放地址
/usr/local/php/bin/php /home/wwwroot/xxx/svn.php

这里是在post-commit里面执行一个php脚本。下面看这个脚本(svn.php)的代码:

/**
 * svn钩子post-commit里执行的文件
 * 存放在svn服务器上
 */
$updateUrl = "http://test.mengkang.net/update.php";//远程测试服务器上的update.php的地址
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $updateUrl);
curl_setopt($ch, CURLOPT_HEADER, 0);
curl_exec($ch);
curl_close($ch);

在这个脚本里是使用的curl去模拟访问了测试服务器上的URLhttp://test.mengkang.net/update.php

/**
 * svn服务器上的钩子需要模拟访问的文件,必须是外网可以访问的
 * 存放在远程服务器上
 * @author  zhoumengkang <i@zhoumengkang.com>
 */
error_reporting(E_ALL);

//"/home/wwwroot/test/" 为代码更新到的指定目录路径
putenv("LC_CTYPE=zh_CN.UTF-8");//有时候字符集会很坑爹,所以设置环境变量还是很有必要的
$handle = popen('svn up --username zmk --password 123456 /home/wwwroot/test/ 2>&1','r');
//echo "'$handle'; " . gettype($handle) . "\n";
$read = stream_get_contents($handle);
//TODO 如果在$read中可以匹配到“error/conflict”,就应该发送邮件到管理员的邮箱了!
echo $read;
pclose($handle);

稍微做点安全判断扩展版

<?php
header("Cache-Control:no-cache,must-revalidate");
//首先判断ip是否合法
if(!preg_match('/123\.123\.123\.[0-9]+/', $_SERVER['REMOTE_ADDR']){
  //可以给管理员发送邮件
  die('ip不合法');
}
?>
<form action="" method="post">
  <input type="password" name="password" id="password" value="">
  <input type="submit" value="提交" onclick="storePassword();">
  <script type="text/javascript">
    //自动填充密码
    function setValue(){
      var passwordInput = document.getElementById('password');
      passwordInput.value = getcookie('svnupdate');
    }
    window.onload=setValue;

    //存储密码
    function storePassword(){
      var passwordInputValue = document.getElementById('password').value;
      if(getcookie('svnupdate') != passwordInputValue){
        setcookie('svnupdate',passwordInputValue,365);
      }
    }

    //Cookie操作函数
    function setcookie(name,value,days){
        if("undefined" == typeof(days)){
            days = 30;
        }else{
            days = parseInt(days);
        }
        var exp  = new Date();
        exp.setTime(exp.getTime() + days*24*60*60*1000);
        document.cookie = name + "="+ value + ";expires=" + exp.toGMTString();
    }
    function getcookie(name){
        var arr = document.cookie.match(new RegExp("(^| )"+name+"=([^;]*)(;|$)"));
        if(arr != null){
            return (arr[2]);
        }else{
            return "";
        }
    }
  </script>
</form>
<?php
if($_POST['password'] !='123456'){
  die('密码错误');
}
putenv("LC_CTYPE=zh_CN.UTF-8");
$handle = popen('svn up /test --username zmk --password 123456 2>&1', 'r');
$read = stream_get_contents($handle);
echo "<pre>";
printf($read);
echo "</pre>"; 
pclose($handle);
?>

这样就实现了测试服务器的自动更新,线上服务器则通过访问http://mengkang.net/update.php来手动更新代码。

代码以演示为主,大家可以在脚本中添加svn更新出错发送邮件等扩展功能。

别忘了这些代码是建立在svn搭建完毕之后,测试服务器和线上服务器都做了一次检出之上的。

线上服务器的update.php代码与测试服务器上的代码相似,可以做一些安全防护措施,比如加一个限定和密码输入。

在上面这些代码都部署完毕之后,可能会因为各种权限问题,导致不能正常自动更新,你可以update.php页面,那上面输出各种错误的原因。

有一个坑需要提醒下:

在svn 1.6 以后的版本之后,当然执行svn up的时候会默认提示是否需要保存密码,需要修改下配置文件才可能跳过这步。

假设你的svn的配置文件在这/home/www/.subversion/servers请编辑下它,全局密码保存配置修改为如下规则

[global]
store-passwords = yes
store-plaintext-passwords = no

不管是测试服务器还是线上服务器都应该拒绝对所有.svn目录的访问。

location ~ ^(.*)\/\.svn\/{
        deny all;
}

类似的,如果测试服务器在外网必须设置为只有开发人员的ip才能访问。


周梦康
9k 声望6.7k 粉丝

退隐江湖