diff --git a/blog.nix b/blog.nix index f22b0cd..0fe0b07 100644 --- a/blog.nix +++ b/blog.nix @@ -12,10 +12,14 @@ path = "index.html"; title = "Home"; - head = '' - + body.markdown = '' + Webster's dictionary defines "blog" as "a website that contains online personal reflections, comments, and often hyperlinks, videos, and photographs provided by the writer". + This is mine. + + You will find mostly longform technical writing here. + + ''; - body.html = ""; } ]; } diff --git a/posts/hello/body.md b/posts/hello/body.md index 2e157df..0949ba7 100644 --- a/posts/hello/body.md +++ b/posts/hello/body.md @@ -1,3 +1,119 @@ -Hello world! This is a test post. +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. -There will be a real post here soon. +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! diff --git a/posts/hello/post.nix b/posts/hello/post.nix index 30bcc3f..9d61aef 100644 --- a/posts/hello/post.nix +++ b/posts/hello/post.nix @@ -1,6 +1,6 @@ { authors = [ "Audrey Dutcher" ]; - datetime = "2025-04-20 03:20-0700"; - title = "First post!!1!"; + datetime = "2025-04-20 00:00-0700"; + title = "Hello Blog - How I streamlined my application management with Nix"; body.markdownFile = ./body.md; }