---
layout: post
title:  "Build a Jekyll blog with Nix using flakes"
date:   2023-01-12 13:06:03 +0200
categories: nix
tags: nix jekyll github-pages
---

This blog is powered by **Github pages** and **jekyll**. I started it when I had
never ever heard of Nix, but since then I've moved to NixOS and wanted
to be able to write again here.
*Unfortunately it wasn't that easy...*

Indeed the documentation out here about building this setup with Nix is **very outdated**
and a lot of it just doesn't work anymore because package evolved, **purity** is
now enforced using flakes, etc ...

I've managed to get something to work and I'll write it down for you:

## Base flake structure

The base structure of the flake is fairly simple:

``` nix
{
  description = "Build the blog";
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs/22.11";
  };
  outputs = inputs: with inputs; let
    system = "x86_64-linux";
    pkgs = import nixpkgs { inherit system; };

    # Utility to run a script easily in the flakes app
    simple_script = name: add_deps: text: let
      exec = pkgs.writeShellApplication {
        inherit name text;
        runtimeInputs = with pkgs; [
            gnumake     # Required by some dependencies in order to build
            bundler
        ] ++ add_deps;
      };
    in {
      type = "app";
      program = "${exec}/bin/${name}";
    };

  in {
    apps.${system} = {
      default = simple_script "serve_blog" [] ''
        # nix run -> Serve the website locally
      '';

      generate = simple_script "generate_blog_env" [] ''
          # nix run .#generate
          #   Will be used to re-generate the Ruby environment after modifying
          #     a dependency, the version number, etc ...
      '';
    };
  };
}
```

### The workflow

At initialisation, and after that every time you modify a dependency,
bump a version, or modify something in the Ruby environment,
call `nix run .#generate`

It will use `bundler` and `bundix` to *generate* the `Gemfile`, `Gemfile.lock`,
`gemset.nix` and even `.bundle/config`. \\
This way, we only need to configure variables inside the flake, the rest will magically
work.

Once this is done, you can serve the website locally by calling `nix run`

It will use the Ruby environment we created using the `generate` script, and call
`jekyll serve --trace`

## Generating the Ruby environment

First for the environment generation, we will generate the `Gemfile` file using
good ol' nix variables:

``` nix
github-pages-version = 227;

inc_gems = {
  minima = "2.5";
  webrick = "1.7";          # Required for the jekyll serve to work
};

inc_plugins = {
  jekyll-feed = "0.12";
};

# Generation of the Gemfile
generate_gemfile = let
  gems = builtins.concatStringsSep "\n" (pkgs.lib.attrsets.mapAttrsToList (name: version:
    "gem \"${name}\", \"~> ${version}\""
  ) inc_gems);

  plugins = builtins.concatStringsSep "\n" (pkgs.lib.attrsets.mapAttrsToList (name: version:
    "  gem \"${name}\", \"~> ${version}\""
  ) inc_plugins);
in
  ''
    # This file is autogenerated by the flake.nix file, do not edit
    source "https://rubygems.org"

    # Gems dependencies to be installed
    ${gems}
    gem "github-pages", "~> ${builtins.toString github-pages-version}", group: :jekyll_plugins

    # Github Pages plugins
    group :jekyll_plugins do
    ${plugins}
    end
  '';
```

This `Gemfile` will be used by `bundler` to create a `Gemfile.lock` to pin the
dependencies, which in turn will be used by `bundix` to generate a `gemset.nix`,
the nixified ruby gems.

All these files are used by the nix `bundlerEnv` function like this:

``` nix
# The build environment
env = pkgs.bundlerEnv {
  name = "blog";
  ruby = pkgs.ruby_3_1;
  gemdir = ./.;
};
```

Now, let's tie the two pieces together in the `generate_blog_env` script:

``` nix
generate = simple_script "generate_blog_env" [ pkgs.bundix ] ''
    set -e

    rm -f gemset.nix Gemfile Gemfile.lock .bundle/config

    cat << EOF > Gemfile
    ${generate_gemfile}
    EOF

    bundler update
    bundler lock
    bundler package
    bundix --magic -d -l
    rm -rf vendor
'';
```

So far so good.

## Bundle configuration and platform issues

Nowadays, `bundler` doesn't like command line flags to be used, and it prefers
to read from either the `.bundle/config` file, or directly from environment variables.

In the `generate_blog_env` script, I adapted the flags passed to generate the
configuration before generating the `gemset.nix` file like so:

``` bash
export BUNDLE_PATH=vendor
export BUNDLE_CACHE_ALL=true
export BUNDLE_NO_INSTALL=true
export BUNDLE_FORCE_RUBY_PLATFORM=true
```

Notice the `BUNDLE_FORCE_RUBY_PLATFORM`, it is needed because some dependencies
(at least `nokogiri`) are platform-specific and will raise an issue if it detects
the machine platform.
This configuration will ensure that it won't raise any compatibility issue.

## Using the environment

Yay ! The environment builds without any trouble !

Now let's serve the website locally using `jekyll`. In the `serve_blog` script,
we simply have to write:

``` nix
''
    echo "Bundler env: ${env}"      # For debugging / exploration of the files
    ${env}/bin/bundler exec ${env}/bin/jekyll serve --trace
''
```

And now we can serve the whole thing locally without any troubles !

## It finally builds

The full flake source can be seen [here][flakesrc]

All this setup is the result of hours or research and debugging, and I
unfortunately wasn't able to find all the links and pages that I used,
so I cannot credit them the way I'd like to.
However a big **Thank you** is in order for all these people

This setup builds perfectly for my (weird) setup, but it's far from clean,
optimized, and could maybe not work for you. \\
However *feel free to reach me* if you have some trouble, or if you wish
to make this setup better, for now it only lies in my blog repo, but
why not having a repository specially made for building a
Github pages + jerkyll setup using Nix.

[flakesrc]: https://raw.githubusercontent.com/litchipi/litchipi.github.io/6488b3ac73e4a5ae264e442ad4c32007c6533b06/flake.nix
