Github push webhook implementation
- edit - share
GitHub and Bitbucket provide webhooks support to notify external services when certain events happen with a repository. The most commonly used webhook event is push
.
The following code is a PHP implementation of GitHub webhook that will update a repository clone and execute required deployment code when a new commit was pushed.
<?php | |
// Webhook code to update repo clone and execute required deployement code when new commit was pushed | |
// Webhook content-type should be set to application/json and a random secret code should be set too. | |
// Secret Random Code You set on github webhook settings | |
const SECRET_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'; | |
if(strcasecmp($_SERVER['REQUEST_METHOD'], 'POST') != 0){ | |
throw new Exception('Request method must be POST!'); | |
} | |
if (! in_array($_SERVER['HTTP_X_GITHUB_EVENT'], ['push', 'ping'])) { | |
throw new Exception('Request event should be either "push" or "ping"!'); | |
} | |
$contentType = isset($_SERVER["CONTENT_TYPE"]) ? trim($_SERVER["CONTENT_TYPE"]) : ''; | |
if(strcasecmp($contentType, 'application/json') != 0){ | |
throw new Exception('Content type must be: application/json'); | |
} | |
$hash = hash_hmac('sha1', file_get_contents('php://input'), SECRET_TOKEN, false); | |
if ($_SERVER['HTTP_X_HUB_SIGNATURE'] == 'sha1=' . $hash) { | |
$json = json_decode(file_get_contents('php://input'), true); | |
if(!is_array($json)){ | |
throw new Exception('Received content contained invalid JSON!'); | |
} | |
$commands = array(); | |
if ($json['repository']['full_name'] == 'USERNAME/REPO1-DJANGO-APP') { | |
$commands = array( | |
'whoami', | |
'cd /PATH/TO/SITE1 && git pull', | |
'cd /PATH/TO/SITE1 && git status', | |
// 'cd /PATH/TO/SITE && git submodule sync', | |
// 'cd /PATH/TO/SITE1 && git submodule update', | |
// 'cd /PATH/TO/SITE1 && git submodule status', | |
'cd /PATH/TO/SITE1 && bash -c "source env/bin/activate && pip3 install -r requirements.txt"', | |
'cd /PATH/TO/SITE1 && bash -c "source env/bin/activate && python3 manage.py migrate --noinput"', | |
'cd /PATH/TO/SITE1 && bash -c "source env/bin/activate && python3 manage.py collectstatic --clear --link --noinput"', | |
'kill -HUP $(pidof uwsgi) && echo "Server reloaded" || echo "Can not reload the server"', | |
); | |
} else if ($json['repository']['full_name'] == 'USERNAME/REPO2-STATIC-WEBSITE') { | |
$commands = array( | |
'whoami', | |
'cd /PATH/TO/SITE2 && git pull', | |
'cd /PATH/TO/SITE2 && git status', | |
); | |
} else { | |
throw new Exception('Repo is not in the list'); | |
} | |
// Run the commands for output | |
foreach($commands AS $command){ | |
// Run it | |
try { | |
$tmp = shell_exec($command); | |
} catch (Exception $e) { | |
$tmp = "ERROR!\n"; | |
$tmp = 'Caught exception: '. $e->getMessage(). "\n"; | |
} | |
// Output | |
echo "sh> $command\n"; | |
echo htmlentities(trim($tmp)) . "\n"; | |
} | |
} else { | |
throw new Exception('Wrong signature hash!'); | |
} | |
?> |
After adding this file to your server, you need to make the following changes before adding the webhook to GitHub:
- Sett
SECRET_TOKEN
to a randomly generated token. You can use this command to generate a truly random secure token on Linux:
head /dev/urandom | tr -dc A-Za-z0-9 | head -c 40
- Change
$commands
list to matches your need for every repository. An example of updating both a static website repository and a Django based application repository is provided on the code above as a reference. - Add write access to the process running PHP (FastCGI, mod_php, …) which is mostly either running within the group
www-data
orwww
depending on the Linux distribution running on your server. - If your repository is private, generate an ssh key and add the pubkey as a read-only deployment key to your repository settings. This key should be generated using the same user as the PHP process.
Finally, add a webhook to your repository settings. Make sure to set the content type to application/json
and the secret to the secret token you have already generated.