Better than Revivify

Posted on September 24, 2024

I hadn’t really touched my blog/website for years. At the time, I used it to learn somethings about webhosting. I’d unfortunately lost much of the spark to do hobby programming after starting to work fulltime, leading to my last post being from 2020, prior to me even graduating university.

Update?

I have some free time right now and I’ve have some ideas for blog posts I hadn’t gone around to. So why not jot them down and update my blog a bit? Well… I didn’t set up any automated updates, nor had I touched my blog in years, then built with ghost. Even worse, it wasn’t just ghost that needed to be updated. I was looking at multiple updates that needed to be done, with Ubuntu, ghost and nodejs all sitting at incompatible outdated versions. I’d have to do the updates in some ordering I am way to unfamiliar to determine.

I tried for a bit, but after some hair pulling, I thought: “Why not just recreate this trash completely?”. I wasn’t too happy with ghost anyway, it was way too overkill for my purposes, I didn’t need subscribers or mailing lists. Just a static site with some words on it, perhaps with some styling and some files I could host.

But What if I Didn’t?

In comes Github Pages, a free way to host static websites! I’ve wanted to try using it for quite a while, and I’m already quite familiar with Github, having to use it for work.

To use it, I’d need a static site generator. The typical choice here is Jekyll, even being recommend on their own site. I’ve chosen a Haskell alternative called Hakyll, for the simple reason that I like to use Haskell. Getting it up and running was quick; the Hakyll tutorial ends with a nice working blog website with a few placeholder posts. My next step was to translate the ghost
Here’s an archive I could find of my website. I broke everything attempting to update my VPS, so getting a production example wasn’t exactly possible.
content I’d backed up, to the markdown files Hakyll builds into html using pandoc.

After that tedium came the fun part, getting everything exactly as I liked it. I’ll go over noteworthy things I changed or added to my setup, and how I did it. A lot of existing blogposts exist on integrating features into Hakyll. As always, I’m standing on the shoulders of giants.

Styling

After clunking around in CSS for a bit trying to get a consistent color scheme based on gruvbox, this very short post gave me the idea to use Clay for my styling instead. I didn’t know Clay existed prior, but I definitely liked the idea of using Haskell for everything.

Unfortunately, the way described in the blogpost limits it to a single module, which seemed counterproductive to the idea of using a preprocessor in the first place. So instead, I run the script as an executable via cabal. One thing to note, is to fix the dependencies of the created file using rulesExtraDependencies, so it is recreated on a change to any of the underlying modules. I’m using the following pattern quite often to avoid unnecessary/missed recompilation.
main = hakyll $ do
    ...
    match "css/**.hs" $ compile getResourceBody
    clayDependencies <- makePatternDependency "css/**.hs"
    rulesExtraDependencies [clayDependencies] $
        create ["css/main.css"] $ do
            route idRoute
            compile createCssUsingCabal

Syntax & Latex

Can’t have a techblog without snippets of code right? Luckily, Tony Zorman wrote a nice blogpost, integrating pygments into a Hakyll blog. I didn’t deviate much from that. I’m not too big of a fan of including the .css file manually, as I already have the above pipeline working nicely. But recreating the pygments syntax coloring files would require me understanding the way it tags tokens, and I can’t be bothered.

Similarly, I was able to follow this post to use KaTeX.

Sidenotes

I like to use side notes quite a bit instead of footnotes, as I find it often detracts from the point you’re making, if the reader is compelled to jump to a different section of the page. For sidenotes, there already exists a package I could reuse after flipping the html-sidenotes flag that isn’t on hackage yet.

Using nix, I just overrode the pandoc-sidenotes version in the overlay that’s used while creating my shell, and it’ll automatically be installed in the shell-provided ghc.
makeHaskellOverlay (prev: hfinal: hprev:
  {
    pandoc-sidenote = hprev.callCabal2nixWithOptions "pandoc-sidenote"
      (builtins.fetchGit {
        url = "https://github.com/jez/pandoc-sidenote";
        rev = "<insert hash here>";
      })
      "-f html-sidenotes"
      {};
  }
)

Fonts

I’m using Montserrat for regular ol’ text and Fira Code for typesetting code. I could just use Google’s CDN to host these fonts, but that’s no fun and implies any visitor getting unnecessarily tracked by big boi G. But if I’m self-hosting, I may as well go all out and do some font optimization.

I downloaded the variable font .ttf files from Google, and used fontimize to load in the html documents and optimize each variable font file individually
Note that optimise_fonts_for_html_contents circumvents css files. The implication being that fonts are only optimized based on rough information visible in the HTML, instead of full granularity.

This means that it is incompatible with the KaTeX font files as those are mainly styled using classes. Perhaps I can get it to work at a future point, but I’d have to toy with Hakyll quite a bit. I’d need to give fontimize the right version of the website, and to use the optimized font files afterwards in the final output.
. This implies wrangling my nix environment a bit and writing a python script, invoking it via unsafeCompiler.
# Optimizing is as simple as calling a single function.
optimise_fonts_for_html_contents(
    html_contents=[html.read() for html in html_files],
    fonts=font_file_paths,
    subsetname="subset",
    print_stats=False,
)

This makes my fonts dependent on the content of my blog, resizing to fit whatever nonsense I write down. Nifty!

Deployment

I was able to riff off of this blog post to deploy my blog, which is located in a private repository, to my public pages repo. I changed it to support a minimal build environment via nix as that’s what I use to maintain dependencies. I tried to optimize the Github Action time a bit by including nix caching, but also garbage collecting the nix store to keep the caches up to date, and creating a separate build profile from the default shell I use to write this blog.
jobs:
  build-deploy:
    steps:
      - run: |
          nix develop .#minimal -c cabal update
          nix develop .#minimal -c cabal build --only-dependencies site
          nix develop .#minimal -c cabal build site
          nix develop .#minimal -c cabal exec site build
      - run: |
          nix develop .#minimal --profile /tmp/build-profile
          nix-store --gc

Misc

There’re some more nice blogposts I used to add several niceties.
  • Drafts: I found this blogpost on being able to include drafts in Hakyll.
  • Feeds: I added both RSS and Atom feeds following Robert Pearce’s example.
  • Formatted Titles: In the base setup, titles are pulled from metadata and inserted into the templates without going to the pandoc transformation. Which means that every character is piped verbatim, no bold, italics, monospace, no anything. Just plain old characters. This post describes a nice solution, using the first header in the content as the title of the post instead, which does include any formatting conversion done by pandoc.
  • Resume: I’ve been impromptu maintaining my resume in a separate location and copying the built file over, but I may as well build it on the fly as part of this blog.

    I didn’t follow any instruction on doing this, but improvising it was simple enough. I just had to ad the latex dependencies to my nix environment and create my resume in a similar fashion to how the syntax highlighting is set up, calling out to latexmk to build my resume file.

All-in-all, this was quite fun. Hakyll is very nice to work with. It’s understandably peculiar when building things that have dependencies, such as using rulesExtraDependencies to handle dependencies of files that are created via IO. But getting it all into, what I think is, a nice result was pretty easy and painless.