As a first little post and an introduction to the blog, I thought it might be cute to have a little overview of how it's made. This has been my first time setting up most of the surrounding architecture and I have learned more than a few things in the process (though there is, of course, lots left to learn). This post won't go into great detail about any particular points, but will serve as more of an overview of what 'the system' looks like at the time of writing.
That is to say: don't expect any brilliant insights from this post, but read on if you're interested in how I've organized things.
Let's start with the most important part of this whole thing, shall we? The truth is that without /Hakyll/, this blog would not be up and running now. I have been wanting to get into writing for a bit, but I have been lacking a platform that met my criteria:
- I wanted to be able to write my posts using Emacs' /Org mode/
- I wanted to be able to put them in a version controlled repo and have the blog auto-update whenever I pushed a new update.
- I wanted it to be low-ish effort---at the very least I didn't want to mess with servers and so on---but I also wanted to be able to host it myself, so that I would not be dependent on some other platform.
Maybe I didn't look hard enough, but I couldn't really find any alternatives that would let me tick all these boxes. The closest was GitHub pages, which would have been fine, except that I would have had to restrict myself to markdown. ... but then a friend of mine told me about Hakyll and I found this blog post on using it with Org mode. That was all I needed to set out.
Notable changes and additions
While Hakyll comes with a lot of great features out of the box, I found that I wanted just a little bit more out of it, and these things had to be configured:
- Feeds (RSS and atom)
- I've never used an RSS reader before, but in setting up this project I wanted to find out how the format works and ended up getting addicted myself. If you've not tried it out: you might never wanna go back. Overall, this was quite easy, though it did require some tricksy loading of posts and applying different url treatment.
- Another thing I've never looked twice at. This project provided me a reason to understand what it is, why you might want it, and how you could set it up. Oh, and how you'd link it in your
robots.txt. Pretty simple.
- For some reason, it was quite important for me to be able to have a paginated stream of all blog posts (yup, that's the blog page). Luckily, it was fairly easy to set up, though it did require me to change how I store post drafts.
Where I want to go
I think I've got it mostly where I want it now, but I'm still not happy with how I store drafts. At the moment, they're in a
drafts directory under the
posts directory. In an ideal world I'd be able to store them along with all other posts with the only way to tell whether they've been published or not is whether they have a
published tag in their metadata. However, this causes some issues with Hakyll's build process when it tries to process unpublished posts for different parts of the site. It should be as simple as loading the correct snapshot based on a filtered group of posts, but I haven't quite gotten around to that yet.
Overall I've been very happy with what Netlify has offered me thus far. Their documentation is great, their CLI tool is pretty nice, and they seem to have their stuff together.
External domain providers
Stupidly, I bought the domain from a different domain provider and then went to Netlify later. If I'd had the foresight to check with Netlify first, I wouldn't have had to deal with setting up name servers and so on. It hasn't been that big a deal, and at least now I know you can, but it would just have been easier if I'd gone all in on Netlify, I think. Maybe next time.
It might be worth pointing out that by changing DNS records to have it all managed by Netlify, I got
https support for 'free' (when the option would have been to pay for it, had I stayed with my domain provider).
Haskell support (or lack thereof)
However, even though Netlify does offer a pretty solid service, their CI/CD system does not, at the time of writing, offer Haskell support1. This means that for now, I'm stuck using an external service to build and then publish to their systems. While this isn't too bad, it does mean that I miss out on certain benefits that using their integrated system gives you, including minification and dynamic image serving.
As Netlify doesn't support Haskell static site generators out of the box, I have to work around them and use an external CI/CD system instead. This is actually a big part of the reason why I chose GitLab for hosting this site: I've used their systems a fair bit lately and find them rather nice to work with.
For my CI/CD setup, there where a couple of rules that I wanted to have in place:
- If I push an update to anything that would impact the site (styles, posts, templates), the system should deploy.
- If I push changes that include new posts, the system should add the appropriate timestamps to the relevant posts before deploying. The timestamp additions should be commited and pushed back to the main repo before deploying.
- I should be able to manually deploy, either by going through the gitlab UI, or by pushing a commit that includes a specific tag (
- I should be able to say that a commit should skip CI, even if it would normally trigger it, using a tag (
Most of this sounds pretty basic, but it was a surprising amount of effort to get things set up right. The first big snag was that GitLab doesn't have built-in support for pushing changes back to the repo from the CI pipeline. There are ways to do it, but it was surprisingly convoluted for something I would expect to be reasonably common.
The second thing, and this is probably the most pressing matter relating to this site at the moment, is that building the site for deployment takes almost an hour. From what I can tell, this is because stack (the Haskell build tool) has to download and compile Hakyll and its dependencies before compiling the site. However, there are solutions to this: The most promising one I found is the one documented in this blog post by Saksham Sharma. I did try using the provided docker image, but due to an issue with locale info being unset in pure nix shells2 (I suspect), the site won't build properly. I expect I'll be looking into this pretty soon.
Python helper scripts
As any good software developer, I want to automate as much of the process as possible, and the first obstacle I ran into was related to timestamping posts, both for first publish and for subsequent updates. Beyond that, I also wanted an easy way to move files from the drafts directory to the published posts, adding the required data while doing so.
A pretty simple script meant to be run in CI. It goes through all the posts with 'published' tags that have changed since last push and adds the current timestamp. If a file has no value for the 'published' tag, the current time is added, else, it adds or updates the 'modified' tag with the current time.
To automate moving files between directories and adding the 'published' tag to drafts, I wrote a simple script that does just that. Super easy. This led me to my first real revelation using
nix-shell (still just scratching the surface here), where I could package this script and use it as a command anywhere! I was super excited until I failed at packaging it correctly due to the dependencies on
hakyll.py ... This hasn't been resolved yet, but I'll figure it out at some point!
This is just some shared functions that deal specifically with Hakyll and my specific system. Most notably: lets you get metadata as a dict, update metadata values, sort tags, decide whether a post is new or updated and whether it is published at all. Also my first time using type annotations in Python (I don't actually know whether I did it right, but the system isn't complaining so far?), so that's a win.
In short, this has been a very enlightening and rewarding experience, and while I'm certainly glad that to have gone through it, I am looking forward to not having to worry about it for a while (hah, as if).
Now, let's get writing!