diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | app.js | 116 | ||||
-rw-r--r-- | build.sh | 26 | ||||
-rw-r--r-- | default | 122 | ||||
-rw-r--r-- | package.json | 15 | ||||
-rw-r--r-- | readme.md | 48 |
6 files changed, 329 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..36420af --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +config.json
\ No newline at end of file @@ -0,0 +1,116 @@ +var _ = require('underscore'); +var fs = require('fs'); +var express = require('express'); +var app = express(); +var queue = require('queue-async'); +var exec = require('child_process').exec, + child, + cmd; +var email = require('emailjs/email'); +var config = require('./config.json'); +var mailer = email.server.connect(config.email); +var data; + +app.use(express.bodyParser()); + +// Receive webhook post +app.post('/hooks/jekyll', function(req, res){ + var q = queue(1); + data = JSON.parse(req.body.payload); + + // Close connection + res.send(200); + + // Process webhook data + data.repo = data.repository.name; + data.branch = data.ref.split('/')[2]; + data.owner = data.repository.owner.name; + data.url = 'git@' + config.gh_server + ':' + data.owner + '/' + data.repo + '.git'; + data.base = config.temp_directory + '/' + data.owner + '/' + data.repo + '/' + data.branch; + data.source = data.base + '/' + 'code'; + data.dest = data.base + '/' + 'site'; + data.baseurl = '/' + data.repo; + data.site = config.site_directory + '/' + data.repo; + + // End early if not master branch + if (data.branch !== config.branch) { + console.log('Not ' + config.branch + ' branch.'); + return; + } + + // If repo doesn't exist locally, clone it + if (!fs.existsSync(data.source)) { + // Git clone repo from GitHub + cmd = 'git clone ' + data.url + ' ' + data.source; + q.defer(run, cmd); + } + + // Git checkout appropriate branch, pull latest code + cmd = 'cd ' + data.source + + ' && git checkout ' + data.branch + + ' && git pull origin ' + data.branch + + ' && cd -'; + q.defer(run, cmd); + + // Run jekyll + cmd = 'cd ' + data.source + + ' && jekyll ' + data.source + ' ' + data.dest + + ' --no-server --no-auto --base-url="' + data.baseurl + '"' + + ' && cd -'; + q.defer(run, cmd); + + // Sync files (remove old files, copy new ones) + cmd = 'rm -rf ' + data.site + ' && mv ' + data.dest + ' ' + data.site; + q.defer(run, cmd); + + // Done processing + q.await(function() { + // Log success message + console.log( + 'Successfully rendered: ' + data.owner + '/' + data.repo + + '@' + data.after + ' to ' + data.site + ); + + // Send success email + if (config.email && data.pusher.email) { + var message = { + text: 'Successfully rendered: ' + data.owner + '/' + data.repo + + '@' + data.after + ' to ' + data.site, + from: config.email.user, + to: data.pusher.email, + subject: 'Successfully built ' + data.repo + ' site' + }; + mailer.send(message, function(err, message) { console.log(err || message); }); + } + }); +}); + +// Start server +app.listen(8080); +console.log('Listening on port 8080'); + +function run(cmd, cb) { + console.log('Running: ' + cmd); + child = exec(cmd, function (error, stdout, stderr) { + console.log('stdout: ' + stdout); + console.log('stderr: ' + stderr); + + if (error !== null) { + console.log('exec error: ' + error); + + // Send error email + if (config.email && data.pusher.email) { + var message = { + text: 'exec error: ' + error, + from: config.email.user, + to: data.pusher.email, + subject: '!! Failed to build ' + data.repo + ' site' + }; + mailer.send(message, function(err, message) { console.log(err || message); }); + } + + } else { + if (typeof cb === 'function') cb(); + } + }); +} diff --git a/build.sh b/build.sh new file mode 100644 index 0000000..9e267a7 --- /dev/null +++ b/build.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# Install node and depencencies +sudo apt-get update -y +sudo apt-get install python-software-properties python g++ make -y +sudo add-apt-repository ppa:chris-lea/node.js -y +sudo apt-get update -y +sudo apt-get install nodejs npm -y + +# Forever to keep server running +sudo npm install -g forever + +# Git +sudo apt-get install git -y + +# Ruby +sudo apt-get install ruby1.8 -y +sudo apt-get install rubygems -y + +# Jekyll +sudo gem install jekyll --version "0.11.2" +sudo gem install rdiscount -- version "1.6.8" +sudo gem install json --version "1.6.1" + +# Nginx for static content +sudo apt-get install nginx -y
\ No newline at end of file @@ -0,0 +1,122 @@ +# You may add here your +# server { +# ... +# } +# statements for each of your virtual hosts to this file + +## +# You should look at the following URL's in order to grasp a solid understanding +# of Nginx configuration files in order to fully unleash the power of Nginx. +# http://wiki.nginx.org/Pitfalls +# http://wiki.nginx.org/QuickStart +# http://wiki.nginx.org/Configuration +# +# Generally, you will want to move this file somewhere, and start with a clean +# file but keep this around for reference. Or just disable in sites-enabled. +# +# Please see /usr/share/doc/nginx-doc/examples/ for more detailed examples. +## + +server { + #listen 80; ## listen for ipv4; this line is default and implied + #listen [::]:80 default ipv6only=on; ## listen for ipv6 + + root /usr/share/nginx/www; + index index.html index.htm; + + # Make site accessible from http://localhost/ + server_name localhost; + + location / { + # First attempt to serve request as file, then + # as directory, then fall back to index.html + try_files $uri $uri/ /index.html; + # Uncomment to enable naxsi on this location + # include /etc/nginx/naxsi.rules + auth_basic "Restricted"; + auth_basic_user_file /etc/nginx/.htpasswd; + } + + # location /doc/ { + # alias /usr/share/doc/; + # autoindex on; + # allow 127.0.0.1; + # deny all; + # } + + # Only for nginx-naxsi : process denied requests + #location /RequestDenied { + # For example, return an error code + #return 418; + #} + + #error_page 404 /404.html; + + # redirect server error pages to the static page /50x.html + # + #error_page 500 502 503 504 /50x.html; + #location = /50x.html { + # root /usr/share/nginx/www; + #} + + # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000 + # + #location ~ \.php$ { + # fastcgi_split_path_info ^(.+\.php)(/.+)$; + # # NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini + # + # # With php5-cgi alone: + # fastcgi_pass 127.0.0.1:9000; + # # With php5-fpm: + # fastcgi_pass unix:/var/run/php5-fpm.sock; + # fastcgi_index index.php; + # include fastcgi_params; + #} + + # deny access to .htaccess files, if Apache's document root + # concurs with nginx's one + # + #location ~ /\.ht { + # deny all; + #} +} + + +# another virtual host using mix of IP-, name-, and port-based configuration +# +#server { +# listen 8000; +# listen somename:8080; +# server_name somename alias another.alias; +# root html; +# index index.html index.htm; +# +# location / { +# try_files $uri $uri/ /index.html; +# } +#} + + +# HTTPS server +# +#server { +# listen 443; +# server_name localhost; +# +# root html; +# index index.html index.htm; +# +# ssl on; +# ssl_certificate cert.pem; +# ssl_certificate_key cert.key; +# +# ssl_session_timeout 5m; +# +# ssl_protocols SSLv3 TLSv1; +# ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv3:+EXP; +# ssl_prefer_server_ciphers on; +# +# location / { +# try_files $uri $uri/ /index.html; +# } +#} diff --git a/package.json b/package.json new file mode 100644 index 0000000..6faa85e --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "jekyll-hook", + "description": "A server that listens for GitHub webhook posts and renders a Jekyll site", + "author": "dhcole", + "version": "0.0.1", + "dependencies": { + "express": "3.x", + "queue-async": "1.0.1", + "underscore": "1.4.4", + "emailjs": "0.3.4", + "xml2js": "0.2.6", + "js-yaml": "2.0.3" + }, + "private": true +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..26a8960 --- /dev/null +++ b/readme.md @@ -0,0 +1,48 @@ +# jekyll-hook + +A server that listens for webhook posts from GitHub, generates a site with Jekyll, and moves it somewhere to be served. Use this to run your own GitHub Pages-style web server. Great for when you need to serve your websites behind a firewall, need extra server-level features like HTTP basic auth (see `default` file for Nginx config with basic auth), or want to host your site directly on a CDN or file host like S3. + +## Installation + +- `build.sh` installs server dependencies for Ubuntu Linux +- run `$ npm install` to install app dependencies + +## Configuration + +Copy the following JSON to `config.json` in the application's root directory. + +```json +{ + "gh_server": "github.com", + "branch": "master", + "temp_directory": "/home/ubuntu/jekyll-hook", + "site_directory": "/usr/share/nginx/www", + "email": { + "user": "", + "password": "", + "host": "", + "ssl": true + } +} +``` + +Configuration attributes: + +- `gh_server` The GitHub server from which to pull code +- `branch` The branch to watch for changes +- `temp_directory` A directory to store code and site files +- `site_directory` A directory to publish the site +- `email` Optional. Settings for sending email alerts + - `user` Sending email account's user name (e.g. `example@gmail.com`) + - `password` Sending email account's password + - `host` SMTP host for sending email account (e.g. `smtp.gmail.com`) + - `ssl` `true` or `false` for SSL + +## Usage + +- run once: `$ node app.js` +- use [forever](https://github.com/nodejitsu/forever) to run as server: `$ forever app.js` + +## Web server + +Serve content from a simple webserver link Nginx (the `default` file is a sample Nginx configuration with HTTP basic auth) or use s3cmd or rsync to mirror files on S3 or a CDN. |