Pattern: every file is a flake-parts module

After nine moths of using flake-parts in several projects I’ve refactored my personal infrastructure repository to adhere to a pattern; every Nix file is a flake-parts module.

I have found this pattern liberating and therefore sharing. The readme has a few words on the matter.

17 Likes

Massive, very interesting!

1 Like

How is that some modules are a function that return an attribute set, others are a function that return an attribute set whose main value is also a function and others are plain attribute sets?

2 Likes

Modules are of the structure x or moduleArgs: x, where x is some conformant attrset.

In flake-parts there are nested module system evaluations, so you could have fpModuleArgs: { perSystem = perSystemModuleArgs: perSystemAttrs; ... }, and so on. I guess that’s why some find f-p confusing to use.

1 Like

I don’t find fp confusing at all. I’m more than used to high order functions (in either direction, taking or returning functions), recursion and all the usual jazz. But after reading the flakes on that repo, and reading probably 55% of flake-parts docs I was completely oblivious at what is making possible to return one or the other, and even worse, what to expect as input or what will be produced as output.

This black magic is probably very convenient, but it is very hard to understand for a newcomer

f-p as in flake-parts, not functional programming.

1 Like

Ah, ok my bad. Because nix is a functional language, and I am a functional programming fan I got triggered by my bias

Oh Cool.

We did the same thing here.:

But I wrote the omnibus.pops.nixosModules/nixosProfiles loader to load them, which is based on the fileSystems.

Also, I think omnibus.pops.nixosModules/Profiles can help you reorganize the modules to be a tree structure and export to any type.

 pops.nixosProfiles.exports.default.presets
{
  audio = { ... };
  boot = «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»;
  cloud = { ... };
  coding = { ... };
  display = { ... };
  fileSystems = { ... };
  fontconfig = «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»;
  gpu = { ... };
  mimalloc = «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»;
  networking = { ... };
  nix = «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»;
  nix-ld = «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»;
  opengl = «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»;
  openssh = «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»;
  scenarios = { ... };
  secrets = { ... };
  security = { ... };
  shell = { ... };
  virtualisation = { ... };
  zswap = «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»;
}

nix-repl> pops.nixosProfiles.exports.exportModulesRecursive
[
  «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»
  «lambda @ /nix/store/859yblp6911asw03i546awv2zkz0979g-source/src/haumea/importModule.nix:59:11»

That looks like a file that is a NixOS module, isn’t it? In the pattern I’m describing each file is a flake-parts module. All files are flake-parts modules.

Were did you get your flake-parts knowledge? Just from their docs? I don’t see a clear reference of what the structure a flake-module should be

1 Like

I had to make some effort and got some help. I agree that the flake-parts documentation needs significant improvement.

The docs at https://flake.parts technically have everything, but it didn’t click for me until I read through others’ code that use f-p.

Nice! I really like this pattern.

I guess I can see the advantages of importing all files instead of having to follow a predefined folder structure (like rails conventions). Reading your configs I can see the advantage of defining cross-cutting concerns independent of the file location.

I’ll be exploring your pattern on my own setup, and will use flake-parts a lot more on my published nix flakes :).

Now , -at the cost of being taken as a person who would publish two-lines isEven function as an npm package- I just published a flake inspired by yours here:

3 Likes

If you’re interested there’s a more fleshed-out version of your concept at GitHub - nix-community/haumea: Filesystem-based module system for Nix [maintainer=@figsoda]. But that’s quite the opposite of what’s presented in the “every file as module” idea.

But that’s quite the opposite of what’s presented in the “every file as module” idea.

I have specially written a module loader based on haumea, whether it is every file as module or importNixOS modules as a tree or other hierarchical requirements, it depends on the direct functions of the loader and transformer.

In my instance:

1 Like

Today, I have started the migration of my configuration repository to this pattern.

Follow the progress at refactor: rewrite using `flake-parts` module for better flexibility by drupol · Pull Request #83 · drupol/nixos-x260 · GitHub

I also migrated a project I use a lot at work to this pattern too: refactor: rewrite using flake-parts modules only for better flexibility (2a7223dc) · Commits · ECPHP / Devs profile · GitLab

Thanks for the inspiration, this makes everything cleaner!

I wish I could have used Haumea for loading the modules, but after hours of trying, I couldn’t get anywhere. I therefore used GitHub - vic/import-tree: Import all nix files in a directory tree. and in less than 5 minutes it was working. There’s a bit of frustration there, but I have something working and easy to reason about now.

Edit: I wrote a blog post about this at Refactoring My Infrastructure As Code Configurations | Not a Number

2 Likes

This week I finally ported my osConfigurations.nix from numtide/blueprint to this pattern, all files being flake-parts modules.

One thing I loved (already noted by @mightyiam on their post) is that with this pattern you think more in term of “features” and not on how/where they are applied (other frameworks/patterns focus on imposing a directory structure).

For example, my rdesk.nix (remote-desktop feature) defines a homeManager (input-leap mouse multiplexer app) and a nixos module (nixos firewall rules) in the same file since both configs are part of the very same feature but one applies settings at the os-level and another at the home-level.

Same for my user.nix that exposes and re-uses my home config on my nixos, darwin hosts and standalone home-manager.

@drupol, @mightyiam, I’m thinking about creating a tiny github repo dedicated to this pattern (and have links to existing configurations using it - ours three as far as I know), perhaps a couple of nix templates and documentation on things we have found useful about this pattern. Perhaps, we even could provide “collections” of flake-parts modules that people could import on their flake and get started with fully functioning configs, something akin to configuration frameworks for neovim, spacemacs/doom-emacs layers.

2 Likes

@drupol @vic everyone, I love the idea of a dedicated home for this pattern because I think it’s useful enough to justify it. If we can build such a resource I think that that will contribute to more sharing, more interest and more usage. And I think that that would translate into value for flake users. One use case I think of is hordes of Linux PC users newly adopting NixOS. It’s happening:

(source)

And we can spare these new users months worth of learning. We can increase the rate of adoption by decreasing the rate of giving up because “there are too many ways to do things and where do I start?”.

So here is a stub repository in which I hope we will collaborate in creating a refined demonstration of how to use flake-parts and possibly even collaborate on some related utilities farther down the line.

I lieu of other naming suggestions I thought about it disproportionally and named it The Dendritic Pattern.

If anyone wants to collaborate synchronously on this, I’m open to that as well.

7 Likes

I just switched my config to a flake-parts module I made to simplify the pattern a bit for me. I mainly made it to have a nicer syntax for using nixos and home-manager modules in the same file, and for defining the hosts and their feature tags. Kinda hacky atm and I was rushing to switch to it so a lot of modules need to be split up, but it works.

1 Like

For people following this thread, I’ve added an issue #1 at Dendritic repo (your input is welcome), about writing some example that shows how the Dendritic Pattern is not just about loading all files at once, it is more about defining cross-cutting concerns (or features) across different module classes, flake/nixos/darwin/homeManager/other-class levels, grouping same-purpose configurations together, but still giving you flexibility on where these concerns are defined and applied.

So its more about thinking in “features” than a fixed directory/osConfigurations definition structure.

4 Likes