Modular, context-aware and aspect-oriented dendritic Nix configurations. Discussions: https://oeiuwq.zulipchat.com/join/nqp26cd4kngon6mo3ncgnuap/ den.oeiuwq.com
configurations den dendritic nix aspect oriented

feat(battery): New `den._.hostname` and .. (#236)

Convert two well-known patterns from the examples into built-in
batteries:

- Bidirectional provider
- Automatically setting `networking.hostName`

I feel that these two use-cases are common enough that they are worth
being lifted up into the built-in set of batteries.

What do you think?

Also, I would like to hear if you have any better ideas for the names:

- `den._.set-hostname`: Maybe too specific? In the future, will there be
any other use-cases for auto-configuring hosts that could be included in
this battery?
- `den._.bidirectional-provider`: Feels descriptive, but maybe too long?

---------

Co-authored-by: Victor Borja <vborja@apache.org>

authored by

musjj
Victor Borja
and committed by
GitHub
a63a4c3b 8acd14ae

+274 -97
+50
docs/src/content/docs/guides/batteries.mdx
··· 28 28 Sets `users.users.<name>` on NixOS/Darwin and `home.username`/`home.homeDirectory` 29 29 for Home Manager. Works in both host-user and standalone home contexts. 30 30 31 + ### `den.provides.hostname` 32 + 33 + Sets the system hostname as defined in `den.hosts.<name>.hostName`: 34 + 35 + ```nix 36 + den.default.includes = [ den.provides.hostname ]; 37 + ``` 38 + 39 + ### `den.provides.mutual-provider` 40 + 41 + Allows hosts and users to contribute configuration **to each other** through `provides`: 42 + 43 + ```nix 44 + den.hosts.x86_64-linux.igloo.users.tux = { }; 45 + den.default.includes = [ den._.mutual-provider ]; 46 + ``` 47 + 48 + This is not the same as the built-in bidirectionality: 49 + 50 + ```nix 51 + # contributes to ALL users of this host 52 + den.aspects.my-host.homeManager = { ... } 53 + 54 + # contributes to ALL hosts of where my-user exist 55 + den.aspects.my-user.nixos = { ... } 56 + ``` 57 + 58 + The difference is that this allows you to wire bidirectionality between 59 + explictly-named hosts/users pairs. 60 + 61 + A user providing config TO the host: 62 + 63 + ```nix 64 + den.aspects.tux = { 65 + provides.igloo = { host, ... }: { 66 + nixos.programs.nh.enable = host.name == "igloo"; 67 + }; 68 + }; 69 + ``` 70 + 71 + A host providing config TO the user: 72 + 73 + ```nix 74 + den.aspects.igloo = { 75 + provides.tux = { user, ... }: { 76 + homeManager.programs.helix.enable = user.name == "alice"; 77 + }; 78 + }; 79 + ``` 80 + 31 81 ### `den.provides.primary-user` 32 82 33 83 Marks a user as the primary user of the system:
+11
docs/src/content/docs/reference/batteries.mdx
··· 20 20 Creates OS-level user accounts (`users.users.<name>`) with `isNormalUser`, 21 21 and `home` directory. Works on NixOS/Darwin/WSL/HomeManager. 22 22 23 + ### `den._.hostname` 24 + 25 + Automatically sets the host's name to the one defined in `den.hosts.<name>.hostName`. Works on NixOS/Darwin/WSL. 26 + 23 27 ### `den._.os-user` 24 28 25 29 A `user` class automatically enabled by Den to forward settings into the host's ··· 33 37 34 38 Sets the user's login shell. 35 39 `users.users.<name>.shell` enabling the shell at host, and the home-manager `programs.<shell>.enable`. 40 + 41 + ### `den._.mutual-provider` 42 + 43 + Allows the user and host to contribute configuration to each other via 44 + `provides`. This is not the same as the built-in bidirectionality. The 45 + difference is that this allows you to wire bidirectionality between 46 + explictly-named hosts/users pairs. 36 47 37 48 ### `den._.tty-autologin` 38 49
+3 -22
docs/src/content/docs/tutorials/example.md
··· 3 3 description: Feature showcase with namespaces, angle brackets, cross-platform aspects, and providers. 4 4 --- 5 5 6 - The example template demonstrates Den's advanced features: namespaces, angle brackets, cross-platform hosts, bidirectional providers, and custom aspect libraries. 6 + The example template demonstrates Den's advanced features: namespaces, angle brackets, cross-platform hosts, mutual providers, and custom aspect libraries. 7 7 8 8 ## Initialize 9 9 ··· 97 97 98 98 The `<name>` syntax is shorthand for aspect lookup. `<den/define-user>` resolves to `den.provides.define-user`. See [Angle Brackets](/guides/angle-brackets/). 99 99 100 - ### Bidirectional Providers 100 + ### Mutual Providers 101 101 102 102 ```nix 103 103 # modules/aspects/alice.nix — user provides config TO the host ··· 117 117 118 118 Hosts and users can contribute configuration **to each other** through `provides`. Alice enables `nh` on igloo, and igloo enables `helix` for alice. These are cross-providers activated during context transformation. 119 119 120 - ### Custom Routes 121 - 122 - ```nix 123 - # modules/aspects/eg/routes.nix 124 - { den, ... }: 125 - { 126 - eg.routes = let 127 - inherit (den.lib) parametric; 128 - mutual = from: to: den.aspects.${from.aspect}._.${to.aspect} or { }; 129 - routes = { host, user, ... }@ctx: 130 - parametric.fixedTo ctx { 131 - includes = [ (mutual user host) (mutual host user) ]; 132 - }; 133 - in routes; 134 - } 135 - ``` 136 - 137 - This aspect **wires bidirectional providers** between hosts and users automatically. Including `<eg/routes>` in `den.default` activates all `provides` declarations. 138 - 139 120 ### Tests 140 121 141 122 ```nix ··· 172 153 | Standalone HM config | ✓ | 173 154 | Namespaces (`eg`) | ✓ | 174 155 | Angle brackets | ✓ | 175 - | Bidirectional providers | ✓ | 156 + | Mutual providers | ✓ | 176 157 | VM testing | ✓ | 177 158 | CI checks | ✓ | 178 159
+24
modules/aspects/provides/hostname.nix
··· 1 + { den, ... }: 2 + let 3 + description = '' 4 + Sets the system hostname as defined in `den.hosts.<name>.hostName`: 5 + 6 + Works on NixOS/Darwin/WSL. 7 + 8 + ## Usage 9 + 10 + den.defaults.includes = [ den._.hostname ]; 11 + ''; 12 + 13 + setHostname = 14 + { host }: 15 + { 16 + ${host.class}.networking.hostName = host.hostName; 17 + }; 18 + in 19 + { 20 + den.provides.hostname = den.lib.parametric.exactly { 21 + inherit description; 22 + includes = [ setHostname ]; 23 + }; 24 + }
+67
modules/aspects/provides/mutual-provider.nix
··· 1 + # See usage at: templates/example/modules/aspects/{defaults.nix,alice.nix,igloo.nix} 2 + { den, ... }: 3 + let 4 + description = '' 5 + Allows specifically-chosen hosts and users to contribute configuration **to 6 + each other** through `provides`. 7 + 8 + This is not the same as the built-in bidirectionality: 9 + 10 + # contributes to ALL users of this host 11 + den.aspects.my-host.homeManager = { ... } 12 + 13 + # contributes to ALL hosts of where my-user exist 14 + den.aspects.my-user.nixos = { ... } 15 + 16 + The difference is that this allows you to wire bidirectionality between 17 + explictly-named hosts/users pairs (see the usage below). 18 + 19 + This battery implements an aspect "routing" pattern. 20 + 21 + Unlike `den.default` which is `parametric.atLeast` we use 22 + `parametric.fixedTo` here, which help us propagate an already computed 23 + context to all includes. 24 + 25 + This battery, when installed in a `parametric.atLeast` will just forward 26 + the same context. The `mutual` helper returns an static configuration 27 + which is ignored by parametric aspects, thus allowing non-existing 28 + aspects to be just ignored. 29 + 30 + Be sure to read the Host Context section on: 31 + https://den.oeiuwq.com/explanation/context-pipeline 32 + 33 + ## Usage 34 + 35 + den.hosts.x86_64-linux.igloo.users.tux = { }; 36 + den.default.includes = [ den._.mutual-provider ]; 37 + 38 + A user providing config TO the host: 39 + 40 + den.aspects.tux = { 41 + provides.igloo = { host, ... }: { 42 + nixos.programs.nh.enable = host.name == "igloo"; 43 + }; 44 + }; 45 + 46 + A host providing config TO the user: 47 + 48 + den.aspects.igloo = { 49 + provides.tux = { user, ... }: { 50 + homeManager.programs.helix.enable = user.name == "alice"; 51 + }; 52 + }; 53 + ''; 54 + 55 + mutual = from: to: den.aspects.${from.aspect}._.${to.aspect} or { }; 56 + in 57 + { 58 + den.provides.mutual-provider = 59 + { host, user }@ctx: 60 + den.lib.parametric.fixedTo ctx { 61 + inherit description; 62 + includes = [ 63 + (mutual user host) 64 + (mutual host user) 65 + ]; 66 + }; 67 + }
+33
templates/ci/modules/features/batteries/hostname.nix
··· 1 + { denTest, ... }: 2 + { 3 + flake.tests.hostname = { 4 + 5 + test-sets-hostname = denTest ( 6 + { den, igloo, ... }: 7 + { 8 + den.hosts.x86_64-linux.igloo.users.tux = { }; 9 + 10 + den.default.includes = [ den._.hostname ]; 11 + 12 + expr = igloo.networking.hostName; 13 + expected = "igloo"; 14 + } 15 + ); 16 + 17 + test-sets-custom-hostname = denTest ( 18 + { den, igloo, ... }: 19 + { 20 + den.hosts.x86_64-linux.igloo = { 21 + hostName = "sahara"; 22 + users.tux = { }; 23 + }; 24 + 25 + den.default.includes = [ den._.hostname ]; 26 + 27 + expr = igloo.networking.hostName; 28 + expected = "sahara"; 29 + } 30 + ); 31 + 32 + }; 33 + }
+76
templates/ci/modules/features/batteries/mutual-provider.nix
··· 1 + { denTest, ... }: 2 + { 3 + flake.tests.mutual-provider = { 4 + 5 + test-host-provide-user = denTest ( 6 + { den, igloo, ... }: 7 + { 8 + den.hosts.x86_64-linux.igloo.users.tux = { }; 9 + 10 + den.default.nixos.system.stateVersion = "25.11"; 11 + den.default.homeManager.home.stateVersion = "25.11"; 12 + 13 + den.default.includes = [ den._.mutual-provider ]; 14 + 15 + den.aspects.igloo.provides.tux = den.lib.parametric { 16 + homeManager.home.shellAliases.g = "git"; 17 + }; 18 + 19 + expr = igloo.home-manager.users.tux.home.shellAliases; 20 + 21 + expected.g = "git"; 22 + } 23 + ); 24 + 25 + test-user-provide-host = denTest ( 26 + { den, igloo, ... }: 27 + { 28 + den.hosts.x86_64-linux.igloo.users.tux = { }; 29 + 30 + den.default.nixos.system.stateVersion = "25.11"; 31 + den.default.homeManager.home.stateVersion = "25.11"; 32 + 33 + den.default.includes = [ den._.mutual-provider ]; 34 + 35 + den.aspects.tux.provides.igloo = den.lib.parametric { 36 + nixos.boot.crashDump.reservedMemory = "99999M"; 37 + }; 38 + 39 + expr = igloo.boot.crashDump.reservedMemory; 40 + 41 + expected = "99999M"; 42 + } 43 + ); 44 + 45 + test-provide-each-other = denTest ( 46 + { den, igloo, ... }: 47 + { 48 + den.hosts.x86_64-linux.igloo.users.tux = { }; 49 + 50 + den.default.nixos.system.stateVersion = "25.11"; 51 + den.default.homeManager.home.stateVersion = "25.11"; 52 + 53 + den.default.includes = [ den._.mutual-provider ]; 54 + 55 + den.aspects.igloo.provides.tux = den.lib.parametric { 56 + homeManager.home.keyboard.model = "denboard"; 57 + }; 58 + 59 + den.aspects.tux.provides.igloo = den.lib.parametric { 60 + nixos.boot.kernel.randstructSeed = "denseed"; 61 + }; 62 + 63 + expr = [ 64 + igloo.boot.kernel.randstructSeed 65 + igloo.home-manager.users.tux.home.keyboard.model 66 + ]; 67 + 68 + expected = [ 69 + "denseed" 70 + "denboard" 71 + ]; 72 + } 73 + ); 74 + 75 + }; 76 + }
+1 -8
templates/ci/modules/features/default-includes.nix
··· 27 27 { den, igloo, ... }: 28 28 { 29 29 den.hosts.x86_64-linux.igloo.users.tux = { }; 30 - den.default.includes = [ 31 - ( 32 - { host, ... }: 33 - { 34 - ${host.class}.networking.hostName = host.name; 35 - } 36 - ) 37 - ]; 30 + den.default.includes = [ den._.hostname ]; 38 31 39 32 expr = igloo.networking.hostName; 40 33 expected = "igloo";
+1 -8
templates/ci/modules/features/host-options.nix
··· 20 20 { 21 21 den.hosts.x86_64-linux.igloo.users.tux = { }; 22 22 den.default.homeManager.home.stateVersion = "25.11"; 23 - den.default.includes = [ 24 - ( 25 - { host, ... }: 26 - { 27 - ${host.class}.networking.hostName = host.hostName; 28 - } 29 - ) 30 - ]; 23 + den.default.includes = [ den._.hostname ]; 31 24 32 25 expr = igloo.networking.hostName; 33 26 expected = "igloo";
+1 -6
templates/ci/modules/features/parametric.nix
··· 98 98 { 99 99 nixos.networking.hostName = "NEVER"; 100 100 }; 101 - sets-hostname = 102 - { host, ... }: 103 - { 104 - nixos.networking.hostName = host.name; 105 - }; 106 101 in 107 102 { 108 103 den.hosts.x86_64-linux.igloo.users.tux = { }; 109 104 den.aspects.igloo = den.lib.parametric { 110 105 includes = [ 111 - sets-hostname 106 + den._.hostname 112 107 never-matches 113 108 ]; 114 109 };
+1 -8
templates/ci/modules/features/top-level-parametric.nix
··· 22 22 23 23 test-host-aspect-with-context = denTest ( 24 24 { den, igloo, ... }: 25 - let 26 - custom-host-config = 27 - { host, ... }: 28 - { 29 - nixos.networking.hostName = host.name; 30 - }; 31 - in 32 25 { 33 26 den.hosts.x86_64-linux.igloo.users.tux = { }; 34 - den.aspects.igloo.includes = [ custom-host-config ]; 27 + den.aspects.igloo.includes = [ den._.hostname ]; 35 28 36 29 expr = igloo.networking.hostName; 37 30 expected = "igloo";
+6 -9
templates/example/modules/aspects/defaults.nix
··· 17 17 # These are functions that produce configs 18 18 den.default.includes = [ 19 19 # ${user}.provides.${host} and ${host}.provides.${user} 20 - <eg/routes> 20 + <den/mutual-provider> 21 + 22 + # Automatically set hostname 23 + <den/hostname> 21 24 22 25 # Automatically create the user on host. 23 26 <den/define-user> ··· 32 35 # # This will append 42 into foo option for the {host} and for EVERY {host,user} 33 36 # ({ host, ... }: { nixos.foo = [ 42 ]; }) # DO-NOT-DO-THIS. 34 37 # 35 - # # Instead try to be explicit if a function is intended for ONLY { host }. 36 - (den.lib.take.exactly ( 37 - { host }: 38 - { 39 - nixos.networking.hostName = host.hostName; 40 - } 41 - )) 42 - 38 + # # Instead try to be explicit if a function is intended for ONLY { host } 39 + # den.lib.take.exactly ({ host }: { nixos.foo = [ 42 ]; }) 43 40 ]; 44 41 }
-36
templates/example/modules/aspects/eg/routes.nix
··· 1 - # This example implements an aspect "routing" pattern. 2 - # 3 - # Unlike `den.default` which is `parametric.atLeast` 4 - # we use `parametric.fixedTo` here, which help us 5 - # propagate an already computed context to all includes. 6 - # 7 - # This aspect, when installed in a `parametric.atLeast` 8 - # will just forward the same context. 9 - # The `mutual` helper returns an static configuration which 10 - # is ignored by parametric aspects, thus allowing 11 - # non-existing aspects to be just ignored. 12 - # 13 - # Be sure to read: https://vic.github.io/den/dependencies.html 14 - # See usage at: defaults.nix, alice.nix, igloo.nix 15 - # 16 - { den, ... }: 17 - { 18 - # Usage: `den.default.includes [ eg.routes ]` 19 - eg.routes = 20 - let 21 - inherit (den.lib) parametric; 22 - 23 - # eg, `<user>._.<host>` and `<host>._.<user>` 24 - mutual = from: to: den.aspects.${from.aspect}._.${to.aspect} or { }; 25 - 26 - routes = 27 - { host, user, ... }@ctx: 28 - parametric.fixedTo ctx { 29 - includes = [ 30 - (mutual user host) 31 - (mutual host user) 32 - ]; 33 - }; 34 - in 35 - routes; 36 - }