Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Automatic GitHub deployments
//
// README:
// - Listens for PUSH events
// - Fetches the ref pushed via the given remote
// - Sets the repositories HEAD to latest ref
// - Checks out the new HEAD (--force)
// - Install dependencies from package.json
// - Calls `npm run reload` (My app uses this)
// - Calls `nginx -s reload` (My app also uses this)
//
// Usage:
// export SECRET=YOUR_GITHUB_WEBHOOK_SECRET
// export CWD=/path/to/your/project
// export REMOTE=origin
// export SOCKET_PRIV="your_user:your_group"
// export GIT_AUTHOR="Your Name"
// export GIT_EMAIL="Your Email"
//
// As root:
// node auto-deploy
//
['SECRET', 'CWD', 'REMOTE', 'SOCKET_PRIV', 'GIT_AUTHOR', 'GIT_EMAIL']
.forEach(function(ENV) {
if (!(ENV in process.env)) {
console.error('Missing environment variable: ' + ENV);
process.exit();
}
});
var express = require('express');
var Git = require('nodegit');
var exec = require('child_process').execSync;
var createHandler = require('github-webhook-handler');
var GHWebHook = createHandler({ path: '/', secret: process.env.SECRET });
var me = Git.Signature.now(process.env.GIT_AUTHOR, process.env.GIT_EMAIL);
GHWebHook.on('push', function (event) {
var payload = event.payload;
if (payload.ref !== 'refs/heads/master') {
return;
}
function fetchRemote(repo) {
console.log('Fetching remote', process.env.REMOTE);
return repo.getRemote(process.env.REMOTE).then(function(remote) {
remote.setCallbacks({
credentials: function(url, userName) {
return Git.Cred.sshKeyFromAgent(userName);
}
});
return remote.fetch([payload.ref], me, 'Fetched latest deployment');
}).then(function() {
return repo;
});
}
function resetAndCheckout(repo) {
return repo.getReference(process.env.REMOTE).then(function(ref) {
console.log('Changing HEAD to', ref.target());
return repo.setHead(ref.name(), me, 'Set head to ' + ref.target());
}).then(function() {
console.log('Checking out HEAD');
return Git.Checkout.head(repo, {
checkoutStrategy: Git.Checkout.STRATEGY.FORCE
});
});
}
function reloadAndComplete() {
console.log('Ensure Node modules are up-to-date');
exec('npm install', { cwd: process.env.CWD });
console.log('Reloading application');
exec('npm run reload', { cwd: process.env.CWD });
console.log('Resetting NGINX');
exec('nginx -s reload');
console.log('Completed deployment');
}
// Async-flow.
Promise.resolve(process.env.CWD)
.then(function(path) {
console.log('Opened repository');
return Git.Repository.open(path);
})
.then(fetchRemote)
.then(resetAndCheckout)
.then(reloadAndComplete)
.catch(function(ex) {
console.log(ex.stack);
});
});
// Attempt to clean up previous socket and re-bind.
try { exec('rm ./socket'); } catch (ex) {}
express().use(GHWebHook).listen('./socket');
exec('chown ' + process.env.SOCKET_PRIV + ' ./socket');
@thejmazz
Copy link

thejmazz commented Jun 28, 2015

Cool! Was planning on figuring out how to do this myself after just playing with nodegit. Then I saw your tweet retweeted by @nodegit and came across this! Will save me some time haha. Is it normal practice to export environment variables for something like this in your .bashrc (or relevant) or to set them temporarily when starting up the script? Server should be always on so its not really temporary but still lol.

@tbranyen
Copy link
Author

tbranyen commented Jun 30, 2015

@thejmazz, it's super common to use env variables, but I wouldn't set these in the .bashrc. Maybe in a Makefile or bash script. That way it's easier to run.

@thejmazz
Copy link

thejmazz commented Jul 1, 2015

@tbranyen Oh yeah good idea, thanks!

@andeersg
Copy link

andeersg commented Jul 7, 2015

Hi, I try to do something like this, and have copied line 69 - 80. Where you have "process.env.REMOTE" I have a name for a tag, and that works, "ref.isValid()" says it's true. But when I get to the next thing:

return repo.setHead(ref.name(), repo.defaultSignature, 'Set head to ' + ref.target());

Then I get this error:
Assertion failed: (handle->InternalFieldCount() > 0), function Unwrap, file /Users/johnh/.node-gyp/0.10.38/src/node_object_wrap.h, line 61.

Am I doing something wrong?

Edit: It worked when creating a signature.

@frederikbrinck
Copy link

frederikbrinck commented Aug 6, 2016

@tbranyen Thanks for sharing this. A late question: I was wondering if there is any reason behind using execSync and not making asynchronous calls instead on line 76-87?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment