brettbrewer.com

Programming + Design

Home
How to get a Github post-recieve URL hook working on a non-public site PDF Print E-mail
Written by Brett Brewer   
Tuesday, 24 May 2011

I've been getting cozy with Github lately and I thought I should share my experiences of getting a post-receive URL hook working so that when someone does a git push to my main development repository it will force my dev site to do a git pull to deploy all the latest changes from the repository to the staging server. In my case, I have a fairly secure development server running on a domain that has no public DNS records. This means that to view the site, someone must have the ip-hostname mapping in their computer's host file. This also means that Github has no way of finding the site to call any post-recieve url scripts to trigger the git pull on the remote site. What follows is a list of everything that needed to be done to make this work.

First off, if you're using PHP like me, and your server has the slightest modicum of security, there's no way you're going to be able to successfully issue a shell_exec('git pull') command via php and have it work because php will only have the priviliges of your web server and chances are your repos and site files are owned by a different user. Enter a nice little apache module called "suphp". I can't get into the details of how to prperly set up suphp because I had my dedicated server experts do that for me, but assuming you can get that set up so that your staging server serves your development site under the same user that owns the site files and git repos, you can then set up a php script that can successfully do a shell_exec('git pull') and have it work. So, assuming you got suphp set up, or you're running a really insecure setup that allows your webserver user to run shell commands, you can create a php script on your server such as this:

<?php
$payload = json_decode(stripslashes(@$_POST['payload']));
$message = print_r(@$payload,true);
$message .= shell_exec('/usr/bin/git pull');
mail('me@mydomain.com','gihub post receive hook fired',$message);
echo $message;
exit;
?>

That was my first post-recieve URL hook script. I saved it on my staging domain's webroot folder and then in the Github admin for my repos, I just entered something like http://my.staging.server.com/Github.php. This worked great so long as my staging domain had a public dns record that allowed Github to find it, but for security reasons I didn't really want most people having access to the staging server, so I removed the public dns records and just added a hostfile mapping for my domain to my computers so I could browse it like it had a normal dns record. Of course, this broke the Github post-receive url hook. The first thing I thought to do was to ask Github support for a way to map my ip to my staging server in their system so I fired off an email to them to see if they would comply and then immediately thought of a rather simple solution. Github can't find a site with no DNS records, but it could easily find my server via its ip address. Since my staging server serves some other domains, I couldn't just give Github the server ip and expect it to find the right domain to call the script on, so I created a new apache config file to handle all requests that don't map to a specific server. For example if your staging server is on ip address 123.123.123.123, you could create an apache VirtualHost container such as this:

<VirtualHost 123.123.123.123:80>
        ServerAdmin me@mydomain.com
        ServerName 123.123.123.123
        ErrorLog /var/log/default.error
        CustomLog /var/log/default.access combined
        DocumentRoot /var/www/html
 
       #turn on suphp for this site
        suPHP_Engine on
        AddHandler x-httpd-php .php .php3 .php4 .php5
        suPHP_AddHandler x-httpd-php
 
</VirtualHost>

This makes any requests to your main ip address look for files in the defaut apache docroot at /var/www/html. The suPHP_Engine directives force the requests for php files to be handled by suPHP so it uses whatever user you've got configured for suPHP. There are ways to get suPHP to use different users based on directive you put in the VirtualHost containers, but that's another story. We only needed one user so we've got suPHP configured to always use that user and restricted to only work in certain directories. Securing your system is up to you. So anyway, I threw my post-receive URL php script in /var/www/html and add some things to allow it to update the proper dev site....

 

<?php 
 //put your own email address here if you want to receive email updates
 //when commits happen otherwise leave it blank or set it to false
 $email = "gitadmin@mydomain.com";
 
 //set $output_message to true if you want to be able to
 //view the output of the script in a browser
 $output_message = true;
 
 $message = "Github script called on staging server<br/>";
 
 //look for a request var named 'site'. This way you could theoretically
 //have the same script update different sites depending on the site
 //you pass in when you call the script.
 $site = isset($_GET['site'])?$_GET['site']:isset($_POST['site'])?$_POST['site']:"";
 
 if($site=='my.staging.domain.com'){
   $payload = json_decode(stripslashes(@$_POST['payload']));
   $message .= print_r(@$payload,true);
   $message .= shell_exec('cd /path/to/staging/site/dir;/usr/bin/git pull');
 }else{
   $message .= "No valid site specified";
 }
 
 if(!empty($email)){
   mail($email,'gihub post receive hook fired',$message);
 }
if($output_message){
   echo $message;
 }
exit;
 ?>

Then for the post-recieve-url hook in Github I used something like this:

http://123.123.123.123/?site=my.staging.domain.com

There is an apparently undocumented feature in the Github post-receive-url hook that converts any $_GET vars passed in the url into $_POST vars, so in addition to the "payload" post var you will get any other vars you passed as $_GET vars in your $_POST array. I just included the check for a $_GET['site'] var in my script so that I can also call the script from a normal request in my browser. The above script obviously provides very little security, but since the domain I'm using it for has no public dns records, there's not much chance of someone stumbling across it without knowing the site's IP. Since the script restricts the "git pull" command to running only on the domains specified in the $_GET['site'] request var, the worst someone could do is update my staging server with the latest code in my repos if they called it with the right site in the request, which is sort of the whole point of the script anyway so that's not much of a worry. Having the script email me the results every time it runs keeps me informed of what is going on, so if anyone starts messing around with the script it will be hard to miss in my inbox.

Now all is well in my Github world. 

Last Updated ( Tuesday, 24 May 2011 )
 
< Prev   Next >

Search

Who's Online


© 2017 www.brettbrewer.com
Joomla! is Free Software released under the GNU/GPL License.