For the longest time I’ve wanted to have a small VM in the cloud to experiment with random web-deployed stuff (mainly Node.js work). I’ve been using Azure for a lot of that, because it’s free with my MSDN subscription, but I’ll need a plan for when that expires.

I’ve been hearing a lot about the guys at Digital Ocean - they have some pretty great offerings starting at $5/month - and their support documentation is perfect for non-native Linux people like myself.

So now that I have my VM, I’m keen to automatically generate my staticly generated Hexo blog on each checkin to master. But what are my options?

I can think of a few options:

  • Run some kind of CI server (maybe Jenkins?). Cool idea, but I only have 1Gb of RAM, so that’s not going to happen.
  • Write my own WebHook endpoint that can be invoked via a BitBucket WebHook on checkin. Cool. But overkill for now.
  • Keep another remote of my git repo on the vm, then write a Git Hook to generate the blog on push. Yeah. Just right!

Let’s do this!


flickr photo shared by CarbonNYC [in SF!] under a Creative Commons ( BY ) license

The magic Git post-receive hook

It’s easy to host a second git remote of my blog repo on the VM (just setup another remote via SSH), but then there’s the matter of finding the right git hook. In this case, the one I need is post-receive - which is the one that gets generated every time you push to the remote repo. Of course, you’ll have to edit the hook on the server. Bring on the vi kungfoo!

If you haven’t played with git hooks before, they live in your .git/hooks directory. Rename one of the samples in there to drop the .sample extension, and you’re good to play. The doco is kinda sketchy on these things, but the Atlassian coverage is pretty great. You’ll only find a subset of sample files in your hooks directory, but there’s plenty more events to choose from.

In the case of the post-receive hook, you are fed a few arguments on stdin that contain good value. The oldrev, newrev, and refname are three you’re actually fed. I’ve taken this pretty great example, and doctored it to my own ends.

Here’s my current hook:

#!/bin/sh
read oldrev newrev refname
echo We are moving from $oldrev to $newrev on $refname

if [ "$refname" != "refs/heads/master" ]; then
        echo Pushing to non-master branch $refname. No blog regen required.
        exit;
fi

echo We are on mater. Rolling working dir forward to latest master commit...

cd ..

GIT_DIR='.git'
git reset --hard

echo Regenerating hexo blog...
hexo generate

echo Regeneration complete

And with that hook in place, we are ready to roll: every fresh commit to master moves things forward to the latest commit, then regenerates the blog.

But there’s only one snag. In my case I’m running on a non-bare repo, so I’m going to get grief from git about pushing to a branch that’s already checked out. My git reset --hard is going to repair that damage, but git doesn’t know that :-)

Hence, you’ll need to run one final config step on the server:

git config receive.denyCurrentBranch ignore

And we’re in business.

That’s one very lightweight CI solution using only Git hooks!