Learning Hakyll and Setting Up

Hello all,

I’ve now successfully migrated over to a static blog, and I couldn’t be happier with the outcome. In the process, I learned how to:

Let me tell you how I got up and running!

Hakyll and I

I knew I wanted a static blog. I also knew that I wanted to play with Haskell while setting up the blog. I’d been watching Hakyll for some time. I knew there’d be some work involved, but that was okay - more to learn! So I dove in.

Hakyll was fairly easy to install, and it installed just fine on both GHC 7.6.3 and GHC 7.8.1. All I had to do was:

$ cabal sandbox init
$ cabal install hakyll

…and I was good to go on that front! Be warned: this will take some time. Hakyll depends on many other sizable Haskell projects, so the first build takes at least 10 minutes, more if you bring in documentation and profiled libraries.

After that, I let Hakyll work its magic.

$ hakyll-init blog

This gave me the following:

allele@rainbow-generator:~/haskell/tmp2:$ hakyll-init blog
Creating blog/posts/2012-10-07-rosa-rosa-rosam.markdown
Creating blog/posts/2012-08-12-spqr.markdown
Creating blog/posts/2012-12-07-tu-quoque.markdown
Creating blog/posts/2012-11-28-carpe-diem.markdown
Creating blog/about.rst
Creating blog/contact.markdown
Creating blog/css/default.css
Creating blog/images/haskell-logo.png
Creating blog/site.hs
Creating blog/templates/default.html
Creating blog/templates/archive.html
Creating blog/templates/post-list.html
Creating blog/templates/post.html
Creating blog/index.html

This is the default configuration, and gives you plenty to work with already! Most of the trouble I encountered was working with the site.hs, which is where we define what kinds of files we’d like Hakyll to manage for us.

To ease further development for my blog, I created a new sandbox that took advantage of read-review-refactor.cabal.

$ cd blog
$ cabal sandbox init
$ cabal install --dependencies-only
$ cabal build

I now also had access to the REPL, which is really nice when trying to introspect Hakyll types:

$ cabal repl

Regarding actually working with Hakyll - some of the rules were easier to work with: exposing images/css/js as relative URLs, serving a few static files (about, contact, index), creating the posts and the archive. However, I also wanted to add a tag system, which was not available in the initial site.hs. Herein was where I really had to learn how Hakyll worked.

As far as I understand at this time, there are a few key concepts available in Hakyll that compose fairly well:

There’s also a notion of a Compiler monad that seems to be used throughout. My understanding at the moment is that the Compiler monad hides the details of how a particular Item was generated. There’s also snapshots, which appear to preserve a certain data for Items for later use. I used snapshots as per the tutorial to add Atom support.

Let’s take a look at one such rule and some type signatures.

match "posts/*" $ do
    route $ setExtension "html"
    compile $ pandocCompiler
        >>= saveSnapshot "content"
        >>= loadAndApplyTemplate "templates/post.html" (postCtx tags)
        >>= loadAndApplyTemplate "templates/default.html" defaultContext
        >>= relativizeUrls

This is what one Hakyll rule section looks like. The first portion, match, specifies that when we match files with the given pattern, posts/*, we’ll do the following to them. In this case, that following consists of:

Let’s inspect a few type signatures to get a feel for the underlying machinery:

match :: Pattern -> Rules () -> Rules ()
route :: Routes -> Rules ()
compile :: (Binary a, Writable a, Typeable a) =>
  Compiler (Item a) -> Rules ()

Alright, nothing too surprising. The type names indicate we’re working with a DSL, and they match up pretty intuitively to what I expect. I was actually surprised in the writing of this blog post that Pattern was a rather involved type. Let me show you:

data Pattern
  = Everything
  | Complement Pattern
  | And Pattern Pattern
  | Glob [GlobComponent]
  | List (Set Identifier)
  | Regex String
  | Version (Maybe String)

I’ll avoid introspecting this any deeper at the moment. Everything sounds like it could get pretty deep (or perhaps it maps to the notion of ’*’?).

There’s more to look at, but in the interest of brevity, I’ll leave it up to you to check it out.

Overall, it was a bit of a Haskell-in-action learning experience. I recommend it to new Haskellers after completing LYAH or a related text to put some basics to practice. It was also a good time to read others’ site.hs. I referenced Chromatic Leaves and jaspervdj for most of my efforts. The latest on my blog sources is available here.

The key things that proved tricky for me were:

The hardest of these was adding Tags support, but I was able to overcome this hurdle by leveraging prior art.

Serving the Blog

I considered both nginx and mighttpd2 for serving content. I’m currently serving using nginx, but may migrate to mighttpd2 at some time in the future, especially since GHC 7.8.1 was released today.

Here’s my nginx configuration:

# /etc/nginx/nginx.conf
user root;
worker_processes 2;
pid /run/nginx.pid;

events {
  worker_connections 768;
}

http {
  sendfile on;
  tcp_nopush on;
  tcp_nodelay on;
  keepalive_timeout 65;
  types_hash_max_size 2048;

  include /etc/nginx/mime.types;
  default_type application/octet-stream;

  access_log off;
  error_log off;

  gzip on;
  gzip_http_version 1.1;
  gzip_vary on;
  gzip_comp_level 6;
  gzip_disable "msie6";
  gzip_types ...;  # lots of static file types

  include /etc/nginx/sites-enabled/*;
}

Nothing too interesting going on here. A little gzip, no logging, running as root (not the most secure). Then, there’s the particular blog setup:

# /etc/nginx/sites-enabled/default
server {
    listen 80;
    return 301 https://$host$request_uri;
}

server {
  listen 443 ssl spdy;
  listen [::]:443 ssl spdy ipv6only=on;

  root /root/blog;
  index index.html;

  location ~* \.html {
    expires 1h;
  }

  location ~* \.(css|png|jpe?g|gif|js) {
    expires 24h;
  }

  ssl on;
  ssl_certificate /root/.tls/unified.crt;
  ssl_certificate_key /root/.tls/blog.key;

  ssl_session_timeout 5m;

  ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers ...;  # see [1]
  ssl_prefer_server_ciphers on;

  ssl_session_cache shared:SSL:10m;
  keepalive_timeout 70;

  spdy_headers_comp 6;
}

I followed configuration advice given here, which leads to my next section.

Some tricky points I encountered:

Setting up HTTPS

I followed this guide through and through. It took me a few hours to go through all of it from start to finish. I spent $0 and have a valid certificate to show for it. Highly recommended!

The only part that tripped me up a bit was the generation of the key. Initially, I tried to generate it on my cloud server. However, this ended up taking way too long, since generating “randomness” on said server was tricky.

Develop Locally and Deploy Remotely

This was pretty cool, and probably one of my favorite Hakyll features. While I was still getting used to working with Hakyll and figuring out how I wanted my site to look, I was able to do so in a very interactive fashion. The trick to doing this is to use site watch, or using the cabal sandbox, Blog watch.

$ cabal build
$ ./dist/build/Blog/Blog watch

site watch will launch a web server (Warp, I believe – correct me if I’m wrong!) that allows you to browse your site locally. Further, it also watches files for changes and rebuilds the portions of your site that change! As soon as I hit C-x C-s in my emacs terminal on a new post, it was available on the site index as well as its own page. This made my first few hours with Hakyll very productive.

That’s the develop locally cycle:

  1. launch site watch
  2. make some changes to html, css, images, or post files
  3. view them in the browser
  4. go back to (2) until satisfied

To deploy is just as easy, and requires zero downtime. Here’s my command:

$ site rebuild
$ rsync -arvz _site blog:~/blog

This will regenerate anything that may have gone stale, and then copy over only the bits that changed. nginx continues to chug along over at https://queertypes.com/, and all is well in blog land.

Finally, for reference’s sake, I also copy changes over to github:

$ git ci -am 'posts: added learning hakyll'
$ git push origin master

Conclusion

That’s it! I feel like I learned a lot over the week that it took me to migrate my blog. Ah yes, migrating. Ha, about that…

I actually migrated manually. I had less than 20 posts, so it didn’t take much more than an hour to copy/paste and re-tag.

I recommend Hakyll for those that need a static blogging solution and want to practice some Haskell. It’s an efficient tool, and serves as a decent, high-level environment for working with common Haskell concepts (Monoids, Monads). Not to mention, it leverages the amazing pandoc for HTML generation, so you’ve got syntax highlighting out of the box.

’til next time!

  1. https://gist.github.com/konklone/6532544