The Scenario
You work on your website locally and you use Github to source control your site. After pushing changes to Github, you login to your server and pull the changes. In this article we will discuss how to automate the pulling of changes from the Github repository on your server.
Prerequisites
This article assumes that you are running a server with PHP where running shell_exec('whoami')
returns www-data
and that all files and folders in the git respository are owned by www-data
.
The Webhook
In your repository settings, create a webhook that points to https://yoursite/github-webhook-handler.php
.
Set the content type to application/x-www-form-urlencoded
and set a secret.
The Server Files
The github-webhook-handler.php
script is given by:
<?php
/**
* GitHub webhook handler template.
*
* @see https://developer.github.com/webhooks/
* @orignal author Miloslav Hůla (https://github.com/milo)
*/
$hookSecret = 's.e.c.r.e.t';
set_error_handler(function($severity, $message, $file, $line) {
throw new \ErrorException($message, 0, $severity, $file, $line);
});
set_exception_handler(function($e) {
header('HTTP/1.1 500 Internal Server Error');
echo "Error on line {$e->getLine()}: " . htmlSpecialChars($e->getMessage());
die();
});
$rawPost = NULL;
if ($hookSecret !== NULL) {
if (!isset($_SERVER['HTTP_X_HUB_SIGNATURE'])) {
throw new \Exception("HTTP header 'X-Hub-Signature' is missing.");
} elseif (!extension_loaded('hash')) {
throw new \Exception("Missing 'hash' extension to check the secret code validity.");
}
list($algo, $hash) = explode('=', $_SERVER['HTTP_X_HUB_SIGNATURE'], 2) + array('', '');
if (!in_array($algo, hash_algos(), TRUE)) {
throw new \Exception("Hash algorithm '$algo' is not supported.");
}
$rawPost = file_get_contents('php://input');
if ($hash !== hash_hmac($algo, $rawPost, $hookSecret)) {
throw new \Exception('Hook secret does not match.');
}
};
if (!isset($_SERVER['CONTENT_TYPE'])) {
throw new \Exception("Missing HTTP 'Content-Type' header.");
} elseif (!isset($_SERVER['HTTP_X_GITHUB_EVENT'])) {
throw new \Exception("Missing HTTP 'X-Github-Event' header.");
}
switch ($_SERVER['CONTENT_TYPE']) {
case 'application/json':
$json = $rawPost ?: file_get_contents('php://input');
break;
case 'application/x-www-form-urlencoded':
$json = $_POST['payload'];
break;
default:
throw new \Exception("Unsupported content type: $_SERVER[CONTENT_TYPE]");
}
# Payload structure depends on triggered event
# https://developer.github.com/v3/activity/events/types/
$payload = json_decode($json);
switch (strtolower($_SERVER['HTTP_X_GITHUB_EVENT'])) {
case 'ping':
echo 'pong';
break;
default:
header('HTTP/1.0 404 Not Found');
$output = shell_exec('./git-pull.sh 2>&1');
echo $output; // For debug only. Can be found in GitHub hook log.
die();
}
The git-pull.sh
in the same directory is given by:
#!/bin/bash
sudo -u www-data -H git fetch origin
reslog=$(sudo -u www-data -H git log HEAD..origin/master --oneline)
if [[ "${reslog}" != "" ]] ; then
sudo -u www-data -H git merge origin/master # completing the pull
echo "pulled repository"
fi
This script resets the your local git repository to the state of the master branch on your Github repository. Therefore any untracked or unpushed changes on the server will be lost.
Giving www-data Access to git
On the server run
sudo -u www-data ssh-keygen -t rsa
and add /var/www/.ssh/id_rsa.pub
to your github account. Do not add a passphrase.
Next run visudo
and add the line
www-data ALL = (www-data) NOPASSWD: /usr/bin/git
to the end of the file.
To test whether www-data
has permission to run git commands, cd
to your repository and run sudo -u www-data -H git pull
to try and pull any changes. If this works then you are done and whenever you push to Github the changes should be automatically pulled on the server.