120 lines
6.6 KiB
Markdown
120 lines
6.6 KiB
Markdown
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](https://git.lain.faith/rhelmot/blog.rhelmot.io/commit/5e24401e67b613c7d81abcec8aed14fdf04a4159), but it was too much of a hack for me to deign to put it upon my domain.
|
|
|
|
I eventually found [Coricamu](https://github.com/danth/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](https://git.lain.faith/rhelmot/blog.rhelmot.io).
|
|
|
|
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
|
|
|
|
```nix
|
|
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](https://git.lain.faith/rhelmot/nixos-config) - 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](https://github.com/nixos-bsd/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](https://nix.dev/manual/nix/2.24/command-ref/files/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:
|
|
|
|
```shell
|
|
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](https://git.lain.faith/rhelmot/nixos-config/src/branch/main/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:
|
|
|
|
```nix
|
|
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:
|
|
|
|
```nix
|
|
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](https://github.com/nixos-bsd/nixbsd/blob/main/modules/installer/tools/nixos-rebuild.sh), 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`:
|
|
|
|
```nix
|
|
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!
|