blog.rhelmot.io/posts/hello/body.md

6.6 KiB

As you know if you're part of my life, I have been going pretty hard on nixing my workstations and servers lately. This post is to commemorate the creation of this blog being run through nix, but also the workflow I've established to get here.

Since the year is 2025 and attention span is in short supply, here's the punchline:

  • I ran nix run .#sunflower.deploy to update my server, including deploying this blog
  • I will run nix run .#sunflower.deploy.blog-rhelmot-io to update the blog without updating the system profile
  • ...but I can still roll the blog forward and back without rolling the system forward and back, because the blog is its own nix profile!

Let's break it down.

Part 1: Static Site Generation

What a hotly contentious topic. I could probably stand to care a little bit less, but the caring I do care isn't much. I started this journey trying to use Jekyll, but quickly found that the Ruby-isms were too much for me. I did at one point succeed at getting a flake output which could build a whole Jekyll site, but it was too much of a hack for me to deign to put it upon my domain.

I eventually found Coricamu, which is exactly what I want, though it seems to be abandoned. It's small enough that I feel comfortable carrying it on my shoulders, so I forked it and did the one small fix necessary to get it to work with current nixpkgs. I may mess around with theming later, but it provides exactly what I want and not much more.

The source for this blog can be found here.

Coricamu lets you build a site through a NixOS-style module system. I constructed my flake.nix such that it runs module evaluation with two modules:

  • the blog.nix file, defining the site metadata
  • a module constructed automatically by slapping each entry of /posts/*/post.nix into a post directive
let
    posts = let 
      listingMap = builtins.readDir ./posts;
      listing = builtins.attrNames listingMap;
      getPostFile = post: (import ./posts/${post}/post.nix) // { slug = post; };
    in builtins.map getPostFile listing;
  in
    coricamu.lib.generateFlakeOutputs {
      outputName = "blog";
      modules = [ ./blog.nix { inherit posts; }];
    };

Then I just write some posts in markdown, put some quick metadata into a .nix file which references the markdown file, and build: nix build .#blog. I can also nix run .#blog-preview if I want a fancy server. Truly, we stand on the shoulders of giants.

Part 2: Managing Multiple Machines

I have several machines I manage with a central NixOS configuration repository - some workstations and some servers. This flake.nix also does a directory scan in order to populate its outputs, this time scanning sites in order to populate packages.${buildSystem}.nixosConfigurations.${site}.

Yes, nixos-rebuild will automatically search packages.${buildSystem} in order to build a system, allowing for cross compilation. This fact is particularly useful seeing as I work on NixBSD and am constantly cross compiling entire systems. There are various accoutrements in this repository which make it reasonable for me to use it for both NixOS and NixBSD systems, but that's a story for another time.

Now, I can deploy any of these systems with nixos-rebuild .#$HOST --remote-target $HOST --use-remote-sudo switch, ideally with --use-substitutes since my home internet uplink is dogshit.

There is a problem though - if I want to have my blog as a nix derivation, this means that I have to run a full system rebuild every time I publish a new post. It is very easy to simply drop the derivation output into the nginx configuration, but suddenly rollbacks are tied together with both the system and blog. Can we do better?

Part 3: Profiles and Deployment

Yes, we can, with the power of Nix Profiles! We won't be linking the typical kind of derivation output you would usually be putting in a user profile, with the system path and applications and such. Instead, our profile will simply be the static site build output derivation!

I may in the future decide to standardize some sort of "nginx site derivation" layout so I can link non-static sites this way, but for now I just point the root of the site at /nix/var/nix/profiles/blog-rhelmot-io, and deploy as follows:

nix-copy-closure --to $SITE $DRV
ssh $SITE sudo nix-env --set -p /nix/var/nix/profiles/blog-rhelmot-io $DRV

This can be automated! Check deploy.nix in my NixOS configuration for the final product. I define a list of deployments, each of which sets a profile name, a site to deploy on, and the package to deploy:

deployments = builtins.map mkDeploy [
  {
    profileName = "blog-rhelmot-io";
    site = "sunflower";
    targetPkg = flakeInputs."blog-rhelmot-io".packages.${platform}.blog;
  }
];

mkDeploy simply templates the previously-mentioned script with these parameters. We can then combine each script for a given site into a unified deploy script, along with a system profile rebuild for good measure:

filteredDeployments = builtins.filter (deployment: deployment.site == site) deployments;
targetSystem = flakeInputs.self.packages.${platform}.${site}.system;
deployAll = pkgs.writeShellScriptBin "deploy-all-${site}" (''
  set -ex
  # TODO take advantage of the nixos-rebuild infrastructure
  nix-copy-closure --to ${site} ${targetSystem}
  ssh ${site} 'sudo nix-env --set -p /nix/var/nix/profiles/system ${targetSystem} && sudo ${targetSystem}/bin/switch-to-configuration switch'
  set +e
'' + lib.concatStringsSep "\n" filteredDeployments);

It is annoying that nixos-rebuild proper doesn't support this use-case - that is, deploying a pre-built system profile. I have made the requisite change to enable this behavior in the NixBSD fork of nixos-rebuild, but this script is obviously only appropriate for building BSD targets.

Finally, we can deploy just the blog with a sub-attribute, nix run .#sunflower.deploy.blog-rhelmot-io:

filteredDeploymentsAttrs = builtins.listToAttrs (builtins.map (value: { name = value.profileName; inherit value; }) filteredDeployments);
final = deployAll // filteredDeploymentsAttrs;

I believe this is the best of both worlds.

Conclusion

Yippee woo hoo ya ha ha

You too can take control of your systems like this. Go forth and nixify!