So I think I know how to flakes! Sort of...

After much pain over a week of learning, and a lot of failing, I've started to understand some of why people use flakes. I don't believe that this is something that any sane person should learn, but that applies to most of Nix. Having nix around should be like having docker around for many people. You don't know how it works, but you can use a docker compose file and just do light edits to have most of the gain for knowing nearly nothing. With that said, if you are dumb enough to fall down the rabbit hole like I was, here's what I learned about flakes. First some highlights of what they even are.

  • Nix channels become inputs. No need to manage these anymore if that bothers you.
  • Thanks to version locking, it's great for keeping systems in sync with each other.
  • Solves what I was doing with linking home manager configs "for free"

So far, I've been managing NixOS on 2 computers, home-manager is managed on many machines, but with 3 definitions. This lead me to deciding to pull all of my configs into one flake. While I don't let NixOS or Nix-Darwin manage the home-manager bits themselves, I can put it all in a single flake to keep it all in one place. If you want to see the entire flake as it was at the time of posting there ya go, but I'm going to only pull out snippits to talk about.

So that's all it does? Why bother now?

Pretty much, yes. I managed to break my Gentoo install on my M1 Mac mini server completely by my own stupidity and not knowing how the hardware worked while compiling the kernel, and decided to just give NixOS a toy with on it instead. I was already on NixOS on my rarely used laptop, and quickly noticed that a lot of my configs were the same, so I started to integrate them into reusable parts, much like I did in home-manager. I got about half way through combining them when I realized that I would need to run git as root as the directories were root owned, and decided that maybe I should stop doing things my way, and learn the right way.

Where is the best place to start?

At the inputs of course! These are the things that I pull software from. They are software repos with their own flakes that I can grab and use as I need.

  inputs = {
    # Nixpkgs
    nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";

    # Home manager
    home-manager.url = "github:nix-community/home-manager";
    home-manager.inputs.nixpkgs.follows = "nixpkgs";

    darwin = {
      url = "github:lnl7/nix-darwin";
      inputs.nixpkgs.follows = "nixpkgs";
    };

    apple-silicon.url = "github:tpwrules/nixos-apple-silicon";
    
    hyprland.url = "github:hyprwm/Hyprland";
    
    emacs = {
      url = "github:nix-community/emacs-overlay";
      inputs.nixpkgs.follows = "nixpkgs";
    };
  };

Because I ran an M1 mac in the setup, I already had to take the apple silicone remote, so I looked around for others. I'm using hyprland on my laptop as something different, and even grabbed the emacs overlay to get some more optimized versions as I use Doom Emacs for most of my code editing.

Voodoo magic

The code below is code I took from another repo. It handles making systems in a format that's clean and easy to read, and handles the voodoo for me. Unless I end up needing a system not defined in the forAllSystems section, I can simply not touch this and let it do it's magic.

  outputs = {
    self,
    nixpkgs,
    home-manager,
    ...
  } @ inputs: let
    inherit (self) outputs;
    forAllSystems = nixpkgs.lib.genAttrs [
      "aarch64-linux"
      "i686-linux"
      "x86_64-linux"
      "aarch64-darwin"
      "x86_64-darwin"
    ];

    mkNixos = modules:
      nixpkgs.lib.nixosSystem {
        inherit modules;
        specialArgs = {inherit inputs outputs;};
      };

    mkDarwin = system: modules:
      inputs.darwin.lib.darwinSystem {
        inherit modules system inputs;
        specialArgs = {inherit inputs outputs;};
      };

    mkHome = modules: pkgs:
      home-manager.lib.homeManagerConfiguration {
        inherit modules pkgs;
        extraSpecialArgs = {inherit inputs outputs;};
      };


Bonus, it even includes a devshell to pull in deps to build everything initially.

    # Devshell for bootstrapping
    # Acessible through 'nix develop' or 'nix-shell' (legacy)
    devShells = forAllSystems (
      system: let
        pkgs = nixpkgs.legacyPackages.${system};
      in
        import ./shell.nix {inherit pkgs;}
    );

The important bits. System management!

Thankfully, once we get to this point, the voodoo above makes it a lot easier. You just define systems, and point them to a nix file and do the normal, not flake stuff mostly from there. My NixOS/Nix-Darwin machines get their name and files pointed, then home manager needs a username as well, as it's for, well, me the user, and not the system.

    nixosConfigurations = {
      # M1 mac mini
      farnsworth = mkNixos [./hosts/farnsworth];

      # Laptop
      amy = mkNixos [./hosts/amy];
    };

    darwinConfigurations = {
      # M2 Mac mini
      cubert = mkDarwin "aarch64-darwin" [./hosts/cubert];
    };
    homeConfigurations = {
      "kdb424@amy" = mkHome [./home-manager/machines/amy.nix] nixpkgs.legacyPackages.x86_64-linux;
      "kdb424@cubert" = mkHome [./home-manager/machines/cubert.nix] nixpkgs.legacyPackages.aarch64-darwin;
      "kdb424@farnsworth" = mkHome [./home-manager/machines/headless.nix] nixpkgs.legacyPackages.aarch64-linux;
      "kdb424@planex" = mkHome [./home-manager/machines/headless.nix] nixpkgs.legacyPackages.x86_64-linux;
      "kdb424@zapp" = mkHome [./home-manager/machines/headless.nix] nixpkgs.legacyPackages.x86_64-linux;
    };

With all of that defined, it basically just automates detection of the system and user, and will build things for me. I yoinked and modified a justfile from someone to make it even easier, so I can run just switch or just hm-switch to easily update the respective system and home manager stuff. It was a lot of learning, and a lot of yoinking, but it's starting to manage much of itself. Finally...

What's the point?

I've already started porting more and more out of homebrew on the Mac, and starting to feed my configs into it as I still have a full backup in git with yadm. Things like yabai and skhd have been pretty easy to migrate once I got my head wrapped around it. Getting things out of homebrew is quite nice, as homebrew, while it usually works, has not been an amazing tool for me to use, and breaks more than I would like. I've also gotten tired of managing my hosts file on my systems as I don't have, or want, a DNS resolver inside of my Zerotier network, so being able to update that in one place and all systems get it has been great. As I was writing this, I was fixing issues on my VPS that could have been much faster with Nix to resolve as I'm more familiar with Nix at this point than Nginx. There's still things I could do, but Nix will have to be something that I understand to the same level or greater than what I already know. For now, I'll stick to just using docker in nix to manage my services as I can't see a ton of reason on why that can't continue to work and be relatively invisible to me. Nix can at least automate making my compose files easy to access over NFS.