branch14log

A custom wiki with Gollum

11 May 2013

I've had a personal wiki for some time. Until recently I went with Mediawiki, simply because I've been too lazy to evaluate any alternatives. But keeping it uptodate, upgrading on a regular basis to cope with emerging vulnerabilities wasn't really worth it. I quess I was hardly using 5% of Mediawiki's features anyway. So when I heard about Gollum (the wiki which is also at work on github.com) I decided to give it a shot. I very much liked the idea that I would never again have to migrate or setup a database just for my wiki, since Gollum stores its content on disk in a git respository. Which in turn gives you additional benefits, like being able to edit any file on the disk with your favorite editor. Even extending Gollum's functionality with some custom JavaScript, which I had hacked for Mediawiki, turned out to be a surisingly pleasant experience, due to Rack's concept of middlwares, but more about that shortly. So here is how I set my new wiki up:

A step by step guide

Step 1: Create a directory, change into it and initialize a git repository. This is not only the repo our project will be stored in, but at the same time the repo Gollum will store its content. (In a more sophisticated setup you might want to seperate these, but for my personal wiki it comes quite handy.)
mkdir wiki
cd wiki
git init .
Step 2: Create two files. A file called `Gemfile` will hold the dependencies of our project. For now this is only Gollum itself. But we might add to that later as we customize our wiki. The Gemfile is consumed by `bundler`, a Ruby dependency management tool, which we will install and run in the next step. A file called `config.ru` contains the setup to run our wiki. config.ru is consumed by any application server which adheres to the principles introduced by `Rack`. Rack can be seen as an interface to web applications, although this is more of a simplification than a good description. Although Gollum ships with its own command to fire up a webserver, on a server you might want to take a more robust approach and run Gollum within an Apache/Nginx webserver via passenger (aka. mod_rails), luckily these know what to do with a config.ru, so deployment is pretty straight forward.
# Gemfile
source 'http://rubygems.org'
gem 'gollum'
# config.ru
#!/usr/bin/env ruby
require 'pathname'
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../Gemfile", Pathname.new(__FILE__).realpath)
require 'rubygems'
require 'bundler/setup'
require 'gollum/frontend/app'
# setup of gollum
gollum_path = File.expand_path(File.dirname(__FILE__))
Precious::App.set(:gollum_path, gollum_path)
Precious::App.set(:default_markup, :markdown)
Precious::App.set(:wiki_options, {:universal_toc => false})
run Precious::App
Step 3: Install and run Bundler. Bundler will install Gollum and its dependencies. After bundler has done its job we should be able to start our wiki with the command `gollum`. This will fire up Sinatra which is the web application framework Gollum is built on. Sinatra by default starts a webserver on port 4567. So if you direct you browser to http://localhost:4567/ you should see your newly created wiki.
gem install bundler
bundle
gollum
Step 4: Customize the wiki with injecting a custom JavaScript via Rack. We'll create two more files. The file `rack-injector.rb` will hold a Rack middleware which will inject some markup into every html which is served by gollum. The file `amazon.js` will hold the payload, which will be injected by our middleware. Finally we have to add our middleware to the Rack setup in config.ru.
# rack-injector.rb
module Rack
  class Injector < Struct.new :app, :options
    def call(env)
      status, headers, response = app.call(env)
      if headers["Content-Type"] =~ /text\/html/
        body = ""
        response.each { |part| body << part }
        index = body.rindex "</body>"
        if index
          body.insert index, options[:body]
          headers["Content-Length"] = body.length.to_s
          response = [ body ]
        end
      end
      [ status, headers, response ]
    end
  end
end
// amazon.js
(function() {
  var tmpl = '<iframe src="http://rcm-de.amazon.de/e/cm?lt1=_blank&bc1=FFFFFF&IS2=1&bg1=FFFFFF&fc1=000000&lc1=0000FF&t=branch14org-21&o=3&p=8&l=as1&m=amazon&f=ifr&ref=qf_sp_asin_til&asins=:asins" style="width:120px;height:240px;" scrolling="no" marginwidth="0" marginheight="0" frameborder="0"></iframe>';
  var amazon = $(".amazon");
  amazon.each(function(i, e) {
    e = $(e);
    var ids = e.html().split("\n");
    e.empty();
    $(ids).each(function (index, id) {
      if(id != "") {
        e.append(tmpl.replace(':asins', id));
      }
    });
  });
})();
# in config.ru, before the setup of gollum
require File.expand_path('../rack-injector', __FILE__)
custom = File.read(File.expand_path('../amazon.js', __FILE__))
use Rack::Injector, :body => "<script type='text/javascript'>#{custom}</script>"
With a restart of gollum, every tag on your pages with the class `amazon` will be transformed into nicely looking bookshelves. Within the tag you'll only need to list the ISBNs, one per line. For the content of the iframes I used Amazon affiliate links. So be aware if you copy and paste these examples you'll support me in writing this blog. Thanks man.