Wow you pushed it even further, nice !!!
That makes a lot of sense; since a flake-parts module can define any number of flake outputs, it can also address things that link two kinds of outputs that aren’t of the same kind. This is not so easy with plain nixosModules.
I don’t wish to complicate my system configuration(s) with this pattern for now.
I think that a good configuration is modular in the sense that someone can take any one file of mine, and move it into their configuration, and it will more or less work. That only works if their configuration is made of the same kind of modules as mine, i.e. nixosModules.
In short: flake-parts modules blows the complexity budget for newcomers, and I’d like if my NixOS configuration is readable and adaptable by newcomers, since this is the most valuable personal project you can have for getting onboarded into the ecosystem.
Still, this pattern is very cool. I’d definitely consider using it for Nix-heavy projects, and it is very nice to know there’s an example of it here, so thanks a lot for it.
Just a suggestion for anyone who wants to collaborate on the dendritic pattern to set up a “watch” for any of PRs, discussion and issues.
back when I tried to use flake-parts in my multi-host config flake (wasn’t aware of the dendritic pattern then though), I eventually gave up because I failed to figure any way to configure nixpkgs differently per-host (basically I want to enable CUDA only where there is hardware that needs it, because enabling it universally causes too many pointless rebuilds). either I misunderstand the perSystem mechanism, or I missed a way to bypass it, or it’s just a fundamental limitation?
NixOS configurations are not typically defined under perSystem. So their pkgs don’t come from perSystem. So I’m pretty sure the dendritic pattern can support arbitrary pkgs per host, although I’ve always used the defaults.
I think the main problem is that it is not clear when modular configuration of nixpkgs works and when it doesn’t (for me it never did).
I actually do configure Nixpkgs within my NixOS configs. For the sake of unfree packages. Seems trivial:
apparently it was residual trauma from bug: Flake config cannot use unfree packages despite nixpkgs.config.allowUnfree = true · Issue #2942 · nix-community/home-manager · GitHub, sorry for the noise!
Just wanted to share that I’ve seen more people adopting the Dendritic pattern :).
I currently have a list of Dendritic Repositories as part of Dennix documentation.
If you want yours listed there, send a PR. Dennix will try to generate documentation on which flake modules and nix classes you use. (see previous link).
I also added support for dendritic repos using the Unify library. And I see at least a couple of them working now.
Anyways, I’m quite happy to find more people adopting this pattern.
![]()
@vic a cool example on the dendritic example would be how to take a 2 host small example (I like your SSH + some other demo) → turn it into denditric.
yep, I still have (still on my TODO) to contribute a complete dendritic example - or perhaps a basic template - to the dendritic repo. I agree that having something that shows how to setup up a couple of hosts sharing features between them can shine some light on using the pattern. I’ve been -sadly dealing with loss/health issues past month- busy mainly documenting things on vic/dendrix repo (added some documentation there about why I believe, the dendritic pattern is particularly well suited for community-driven configs).
But yes, I’ll try to remember to contribute a better example to the dendritic repo.
Today I added a flakeModules.allfollow after a request to make allfollow opt-in in flake-file.
And how cool it is that including a flake module now also adds features to your own flake. In this case, importing flakeModules.allfollow enables automatic pruning of flake.lock whenever it gets created.
If you are using dendritic setups, give flake-file a try. dependencies get closer to where they are used. for example, my doom emacs config defines dependencies right there where they are being used.
As I’m beginning to adopt flake-parts, I’m gravitating towards a similar idea.
I’m not making every file a flake-parts module, though. The key, in my mind, is to make flake-parts reach the leaves of the source tree everywhere, so that anything that needs to be done can be done sufficiently locally to the code that needs it.
For me, this means it’s good enough to have most nix files be flake-parts modules, and, crucially, the entry point to every directory be a flake-parts module. I do make a few exceptions, which I name consistently with their format, and always have in their own directory, accessing nothing outside that directory, with a flake-parts module as an entry point dedicated solely to that object:
package.nix: AcallPackage-able fileoverlay.nix: A raw overlay function in a file
This makes comparatively isolated components of your flake more understandable and re-useable, both for non-flake users and non-flake-parts users, and doesn’t really hurt co-location ability, in my opinion.
In a way, you could consider the entire directory these files are in essentially “a flake parts module with attachments.”
Also justifiable exceptions:
default.nix:nix-buildentry points, if you still want to be able to use that.shell.nix:nix-shellentry points, if you still want to be able to use that.flake.nix: if you use subflakes for some reason (usually a bad idea, but there are reasons), or you include a flake template.
sounds good!, having flake-parts modules be the entry-point of leaf directories, makes sense, and also allows you to have other nix files that not necessarily are flake-parts.
<offtopic rel=“importing”>
how do you load these flake-parts ? if, you have for example a flake-parts module /module.nix as entrypoint on some directories, you can use import-tree to load them with something like this:
import-tree.filter (lib.hasSuffix "/module.nix") ./root
and only those module.nix are auto-loaded, everything else can be callPackage’d or loaded in a different context.
for example if you had many packages each having a package.nix file, you can callPackage all of them at once (if you wanted) with the following function:
root: callArgs: lib.pipe import-tree [
(i: i.filter (lib.hasSuffix "/package.nix"))
(i: i.map (file: pkgs.callPackage file callArgs))
(i: i.withLib lib)
(i: i.leafs root)
]
or use something like @drupol’s pkgs-by-name-for-flake-parts from the parent module.nix
</offtopic>
be sure to share how things worked better for you.
I have a flake-parts entry point in every directory, and it’s responsible for importing the modules in that directory (other than itself), and the entry points of any subdirectories. I pass a version of my lib in as a specialArg to make it available to do this easily. My flake’s a refactoring mess right now, but you can see the core implementation here:
I have some more variants that do things like filterAttrs the result to exclude certain files (like the entry point), and call attrValues on it to get a list for imports.
Designing it this way means I still have the option to cut off flake-parts from some subdirectory if I want to handle it differently for some reason, and I can do this cut-off locally.
Currently my entry points are named default.nix, though I think I’ll change that soon, since it conflicts with the nix-build entry point, which I might still want to use, and I don’t need the notational convenience of importing directories directly when my importing is all managed by lib functions anyway.
I just defined a flake-parts option called pkgs-overlays, which get accumulated into _module.args.pkgs so that all of my flake outputs use it automatically. Usage example: my workarounds directory
I’d like to add to that: hardware-configuration.nix (though I name them by hostname). I have a directory with hardware configs for each machine I have a NixOS config for. Most recently I’ve just renamed that to modules/hardware/_generated (so that import-tree ignores it).
I want to be able to do nixos-generate-config --show-hardware-config > some-hostname.nix and not need to wrap it in flake.modules.nixos.hardware-for-some-hostname or something.
And also for my usecase (generating an ISO which runs disko automatically) I have a file called _disko.nix next to the NixOS config loading it. This file takes an argument device and thus cannot be used with flake-parts I don’t think.
I usually just write:
{
perSystem = { pkgs, lib, ... }: {
legacyPackages = lib.packagesFromDirectoryRecursive {
inherit (pkgs) callPackage newScope;
directory = ./pkgs/by-name;
};
};
}
This works for both flake-parts and non-flake parts (if you remove the perSystem and add something that maps it to multiple systems in some other way). If you wrap it with { pkgs ? import <nixpkgs> {}, ... }: let inherit (pkgs) lib; in instead, you get a default.nix. It’s the simplest/most vanilla way I think.
There will be a two hours dendritic pattern workshop in NixCon! If you want to get and or give support, get started, improve, refine or solve a problem, consider showing up! I’ll be there to help and probably learn a few things from you, as well.