This website is statically generated via Nix

2022-10-03

My recipe to use a Static Site Generator via Nix.
5 minutes

🔗Hosted on Clever-Cloud

This blog is (currently) hosted on Clever-Cloud, a PaaS.

This website is static so I went to the easier option: a static website which is actually served by good ol’ Apache httpd.

The first versions of this website were a sole index.html file, pushed via git+ssh to the Clever-Cloud’s application, as it’s meant to be.

As the urge to blog was building, I looked into a solution to handle its content in a saner way. I’m used to writing html by hand but we all know that it’s not the proper way to produce content. When writing your ideas down you don’t want to mess with the syntax.

As a long time user of pandoc for the vast majority of my personal or professional documentation over the last 5 years, I went to a markdown-based solution. And I discovered Zola when reading about biscuits. By the way you should learn about biscuits, but that’s a story for another time.

Also I love Nix.

🔗Zola

Zola is a static site generator. I won’t plagiarize their own documentation so if you want to know more, go read it. I’ll wait.

Done reading it? Good.

So the whole tool is built around a CLI, and the main task provided by it is the build task.

nix-shell -p zola --command "zola build"

Its job is to produce a comprehensive files tree in the public/ directory, with an index.html at root. Deploying this directory to a web server or reverse proxy should do the trick.

You could for instance test it via python3’s own little webserver:

nix-shell -p python38 --command \
  "python3 -m http.server --directory public/"

🔗The derivation wrapping it

{ pkgs ? import <nixpkgs> {}
, name ? "homepage-statically-generated"
, baseUrl ? "https://frederic.menou.me"
}:

let
  gitignoreSource = pkgs.callPackage ./gitignore.nix {};

in
  pkgs.stdenv.mkDerivation {
    inherit name;

    nativeBuildInputs = with pkgs; [
      zola
    ];

    src = gitignoreSource ./.;

    buildPhase = ''
      zola build -u ${baseUrl}
    '';

    installPhase = ''
      mkdir $out
      cp -r public/. $out/
    '';
  }

It’s a pretty naive derivation:

The gitignoreSource helper is provided by the following fixed derivation:

{ fetchFromGitHub, lib }:

let
  gitignoreSrc = fetchFromGitHub {
    owner = "hercules-ci";
    repo = "gitignore.nix";
    rev = "a20de23b925fd8264fd7fad6454652e142fd7f73";
    sha256 =
      "sha256-8DFJjXG8zqoONA1vXtgeKXy68KdJL5UaXR8NtVMUbx8=";
  };
  inherit (import gitignoreSrc { inherit lib; })
    gitignoreSource;
in gitignoreSource

This produces a result/ directory containing the result of zola build. Relaunching it will do nothing as sources havent changed.

The last step is to deploy it on your hosting provider.

🔗Deploying to Clever-Cloud

As I mentioned in the beginning, I use Clever-Cloud. It’s probably not the cheaper option for this usecase, but in exchange of a fee I don’t worry about system maintenance, uptime, and other boring stuff in that vein.

This is the step where you need to do some side effect via a git push. And this is where I fall back to a good ol’ bash script.

Reader is getting nervous.

First let’s summarize the plan:

  1. build it via nix
  2. sync the result to the git clone
  3. commit the changes
  4. if something has been committed, push to the remote pointing at Clever-Cloud’s deployment machine

The whole script looks like this:

#! /usr/bin/env nix-shell
# shellcheck shell=bash
#! nix-shell -i bash -p git

set -e

sourceRevision=$(git rev-parse --short HEAD)
nix-build

result="$(pwd)/result"
cd /path/to/git-clone

pendingChanges=$(git status -s | wc -l)

if [ "$pendingChanges" -gt 0 ]
then
  echo "Some changes weren't committed in the target repository."
  echo "Aborting"
  exit 1

else
  git ls-files -z | xargs -0 rm -f
  cp --recursive --dereference --no-preserve=all "${result}"/. ./
  git add --all
  git commit -m "Automated deployment from ${sourceRevision}"
  git push clever master
fi

Some comments in no specific order:

🔗Flakes

I know, I know, this is the future. I’m not used to it (yet).


Thanks to Céline, Ismaël, Clément and Björn for their reviews.