Summary - I seem to be misunderstanding flakes. On to the rest:
I’ve been playing with NixOS the last few weeks and trying to get a feel for it. Love the concept and actually found it easier to do many of the things I was wanting to do (very minimal system with zfs root inside LUKS, unified kernel secure booting with no grub etc - basically a very minimal but very modern stack).
I have subsequently spent a lot of time chasing my tail trying to get a scalable config repo going. It’s quite possible that this is me overcomplicating things as I have sysadmin experience and have used other config management systems in the past (ansible/chef et al).
I had assumed that flakes was the preferred way to manage config fragments that you would then compose into a system, but that seems to be a really painful process. Perhaps naively I started pulling things out into seperate folders for things (eg one for zfs root related stuff, one for boot loader) one for home manager and then one for hosts - looks like this after a few evolutions:
.
├── README.md
├── configuration.nix
├── flake.lock
├── flake.nix
├── home
│ └── user
│ └── home.nix
├── hosts
│ └── test
│ └── default.nix
└── modules
├── default.nix
├── secure-boot
│ ├── default.nix
│ └── flake.nix
└── zfs-root
└── default.nix
Where the root flake is pretty minimal and just does this:
description = "Barebones NixOS on ZFS config";
inputs = {
nixpkgs.url = "nixpkgs/nixos-23.11";
#nixpkgs-unstable.url = "nixpkgs/master";
home-manager = {
url = "github:nix-community/home-manager/release-23.11";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = inputs@{ self, nixpkgs, home-manager }:
let
mkHost = hostName: system:
nixpkgs.lib.nixosSystem {
pkgs = import nixpkgs {
inherit system;
};
specialArgs = {
# make all inputs availabe in other nix files
inherit inputs;
};
modules = [
# Configuration shared by all hosts
./configuration.nix
# Configuration per host
./hosts/${hostName}
# make home-manager as a module of nixos
# so that home-manager configuration will be deployed automatically when executing `nixos-rebuild switch`
home-manager.nixosModules.home-manager
{
home-manager.useGlobalPkgs = true;
home-manager.useUserPackages = true;
home-manager.users.user = import ./home/user/home.nix;
}
];
};
in {
nixosConfigurations = {
test = mkHost "test" "x86_64-linux";
};
};
}
So the intent is that the host config then pulls in various config fragments for features that I want (zfs, boot, etc) along with host specific settings (network, system packages etc).
But that means that the host nix file has to do something like this, which seems like it’s unsupported:
# Import system config modules
imports = builtins.concatMap import [
"../../modules/zfs-root"
"../../modules/secure-boot"
];
I’ve seen the issues open about relative paths, but lots of the discussion in there leads me to believe this really isn’t a pattern that’s on the golden path so to speak. I’ve found a few interesting projects that seem to work around things in various ways from the simple to the framework and the libraries (flake-parts and flakelight I guess) but all in all I can’t help but feel like I’m swimming against the tide somehow.
Seems like all my problems go away if each flake is its own repo and maybe that’s more the design use case? That would be an unpleasant workflow while I’m developing a first pass, but maybe I’m just overcomplicating things?
So I guess I’m asking advice - for someone reasonably experienced with the problem space and a few programming languages but new to nix (and not a super huge FP nerd) but looking to really “get” the tool, how should I approach something like this idiomatically in nix? Or am I already on the right track and have just hit the rough edges of something experimental and either accept the pain or just use modules?