···34 _module.args.ci-os = "${{matrix.os}}";
35 }
36 EOF
37- nix run .#write-flake
38 nix flake update den
39 nix run .#write-flake
40 nix flake metadata
···34 _module.args.ci-os = "${{matrix.os}}";
35 }
36 EOF
37+ nix run .#write-flake --override-input den "github:$GITHUB_REPOSITORY/$GITHUB_SHA"
38 nix flake update den
39 nix run .#write-flake
40 nix flake metadata
+21-19
README.md
···1314<img width="400" height="400" alt="den" src="https://github.com/user-attachments/assets/af9c9bca-ab8b-4682-8678-31a70d510bbb" />
1516-- focused on host/home definitions.
17-- host/home configs via aspects.
18- multi-platform, multi-tenant hosts.
19- shareable-hm in os and standalone.
20- extensible for new host/home classes.
21-- stable/unstable input channels.
22- customizable os/home factories.
002324**❄️ Try it now! Launch our template VM:**
25···163164This library also provides `default` aspects to apply global configurations to all hosts, users, or homes of a certain class.
165166-- `den.aspects.default.host`: Applied to all hosts.
167-- `den.aspects.default.user`: Applied to all users within hosts.
168-- `den.aspects.default.home`: Applied to all standalone homes.
169170## Advanced Customization
171···262263You can define default settings that apply to all hosts, users, or homes. This is a powerful way to enforce global standards and reduce duplication.
264265-##### Class-Based Defaults (`den.aspects.default.<host|user|home>`)
266267You can apply settings to all systems of a specific *class* (e.g., `nixos`, `darwin`, `homeManager`) by adding them directly to the default aspect.
268269```nix
270# modules/aspects.nix
271{
272- den.aspects.default = {
273 host.nixos.system.stateVersion = "25.11";
274 host.darwin.system.stateVersion = 6;
275 user.homeManager.home.stateVersion = "25.11";
···278}
279```
280281-##### Parametric Defaults (`den.aspects.default.<host|user|home>.includes`)
282283For more dynamic configurations, you can add *functions* to the `includes` list of a default aspect. These functions are called for every host, user, or home, and receive the corresponding object (`host`, `user`, or `home`) as an argument. This allows you to generate configuration that is parameterized by the system's properties.
284···290{
291 # 1. Define a parametric aspect (a function) that takes a host and returns
292 # a configuration snippet.
0293 den.aspects.example.provides.hostName = { host }: { class, ... }: {
294 ${class}.networking.hostName = host.hostName;
295 };
296297 # 2. Include this function in the default host includes.
298 # This function will now be called for every host defined in `den.hosts`.
299- den.aspects.default.host.includes = [
300 den.aspects.example.provides.hostName
301 ];
302}
···304305###### How Parametric Defaults Work
306307-Under the hood, `aspects.default.host`, `aspects.default.user`, and `aspects.default.home` are not static aspects but **functors**. When `den` evaluates a system, it invokes the corresponding default functor, which in turn iterates over the functions in its `includes` list. It calls each function with a context-specific object and merges the resulting configuration snippets.
308309The parameters passed to the functions in each `includes` list are as follows:
310311-- `den.aspects.default.host.includes`: Each function receives the `host` object (`{ host }`).
312-- `den.aspects.default.user.includes`: Each function receives the `host` and `user` objects (`{ host, user }`). This applies to users defined within a host.
313-- `den.aspects.default.home.includes`: Each function receives the `home` object (`{ home }`). This applies to standalone home-manager configurations.
314315This mechanism allows you to create highly reusable and context-aware default configurations that adapt to each system's specific attributes.
316···323{
324 den.aspects.example.provides.user = { user, host }:
325 let
326- # Default configuration for a user
327- defaultConfig = {
328 nixos.users.users.${user.userName}.isNormalUser = true;
329 darwin.system.primaryUser = user.userName;
330 };
331332- # Special configuration for NixOS-on-WSL
333- hostSpecificConfig.adelie = {
334 nixos.defaultUser = user.userName;
335 };
336 in
337 # Use the host-specific config if it exists, otherwise use the default.
338- hostSpecificConfig.${host.name} or defaultConfig;
339}
340```
341
···1314<img width="400" height="400" alt="den" src="https://github.com/user-attachments/assets/af9c9bca-ab8b-4682-8678-31a70d510bbb" />
1516+- focused on host/home [definitions](#basic-usage).
17+- host/home configs via [aspects](#advanced-aspect-patterns).
18- multi-platform, multi-tenant hosts.
19- shareable-hm in os and standalone.
20- extensible for new host/home classes.
21+- stable/unstable input [channels](#custom-factories-instantiate).
22- customizable os/home factories.
23+- [batteries](modules/aspects/batteries) included and replaceable.
24+- features [tested](https://github.com/vic/den/actions) with [examples](templates/default/modules/_example).
2526**❄️ Try it now! Launch our template VM:**
27···165166This library also provides `default` aspects to apply global configurations to all hosts, users, or homes of a certain class.
167168+- `den.default.host`: Applied to all hosts.
169+- `den.default.user`: Applied to all users within hosts.
170+- `den.default.home`: Applied to all standalone homes.
171172## Advanced Customization
173···264265You can define default settings that apply to all hosts, users, or homes. This is a powerful way to enforce global standards and reduce duplication.
266267+##### Class-Based Defaults (`den.default.<host|user|home>`)
268269You can apply settings to all systems of a specific *class* (e.g., `nixos`, `darwin`, `homeManager`) by adding them directly to the default aspect.
270271```nix
272# modules/aspects.nix
273{
274+ den.default = {
275 host.nixos.system.stateVersion = "25.11";
276 host.darwin.system.stateVersion = 6;
277 user.homeManager.home.stateVersion = "25.11";
···280}
281```
282283+##### Parametric Defaults (`den.default.<host|user|home>.includes`)
284285For more dynamic configurations, you can add *functions* to the `includes` list of a default aspect. These functions are called for every host, user, or home, and receive the corresponding object (`host`, `user`, or `home`) as an argument. This allows you to generate configuration that is parameterized by the system's properties.
286···292{
293 # 1. Define a parametric aspect (a function) that takes a host and returns
294 # a configuration snippet.
295+ # re-usable aspects use `den.aspects` and private ones let bindings.
296 den.aspects.example.provides.hostName = { host }: { class, ... }: {
297 ${class}.networking.hostName = host.hostName;
298 };
299300 # 2. Include this function in the default host includes.
301 # This function will now be called for every host defined in `den.hosts`.
302+ den.default.host.includes = [
303 den.aspects.example.provides.hostName
304 ];
305}
···307308###### How Parametric Defaults Work
309310+Under the hood, `den.default.host`, `den.default.user`, and `den.default.home` are not static aspects but **functors**. When `den` evaluates a system, it invokes the corresponding default functor, which in turn iterates over the functions in its `includes` list. It calls each function with a context-specific object and merges the resulting configuration snippets.
311312The parameters passed to the functions in each `includes` list are as follows:
313314+- `den.default.host.includes`: Each function receives the `host` object (`{ host }`).
315+- `den.default.user.includes`: Each function receives the `host` and `user` objects (`{ host, user }`). This applies to users defined within a host.
316+- `den.default.home.includes`: Each function receives the `home` object (`{ home }`). This applies to standalone home-manager configurations.
317318This mechanism allows you to create highly reusable and context-aware default configurations that adapt to each system's specific attributes.
319···326{
327 den.aspects.example.provides.user = { user, host }:
328 let
329+ aspect = {
0330 nixos.users.users.${user.userName}.isNormalUser = true;
331 darwin.system.primaryUser = user.userName;
332 };
333334+ # Special aspect for NixOS-on-WSL
335+ per-host.adelie = {
336 nixos.defaultUser = user.userName;
337 };
338 in
339 # Use the host-specific config if it exists, otherwise use the default.
340+ per-host.${host.name} or aspect;
341}
342```
343
···1+{
2+ inputs,
3+ lib,
4+ den,
5+ ...
6+}:
7+let
8+ import-tree.description = ''
9+ an aspect that recursively imports non-dendritic .nix files from a `_''${class}` directory.
10+11+ this can be used to help migrating from huge existing setups,
12+ by having files: path/_nixos/*.nix, path/_darwin/*.nix, etc.
13+14+ requirements:
15+ - inputs.import-tree
16+17+ usage:
18+19+ this aspect can be included explicitly on any aspect:
20+21+ # example: my-host will import _nixos or _darwin nix files automatically.
22+ den.aspects.my_host.includes = [ (den.import-tree ./.) ];
23+24+ or it can be default imported per host/user/home:
25+26+27+ # each host will import-tree from ./hosts/''${host.name}/_{nixos,darwin}/*.nix
28+ den.default.host.includes = [ (den.import-tree._.host ./hosts) ];
29+30+ # each user will import-tree from ./users/''${user.name}@''${host.name}/_homeManager/*.nix
31+ den.default.user.includes = [ (den.import-tree._.user ./users) ];
32+33+ # each home will import-tree from ./homes/''${home.name}/_homeManager/*.nix
34+ den.default.home.includes = [ (den.import-tree._.home ./homes) ];
35+36+ you are also free to create your own auto-imports layout following the implementation of these.
37+ '';
38+39+ import-tree.__functor =
40+ _: root:
41+ { class, ... }:
42+ let
43+ path = "${toString root}/_${class}";
44+ aspect.${class}.imports = [
45+ (inputs.import-tree path)
46+ ];
47+ in
48+ if builtins.pathExists path then aspect else { };
49+50+ import-tree.provides = {
51+ host = root: { host }: import-tree "${toString root}/${host.name}";
52+ user = root: { host, user }: import-tree "${toString root}/${user.name}@${host.name}";
53+ home = root: { home }: import-tree "${toString root}/${home.name}";
54+ };
55+56+ aspect-option = import ../_aspect_option.nix { inherit inputs lib; };
57+58+in
59+{
60+ config.den = { inherit import-tree; };
61+ options.den.import-tree = aspect-option "import-tree aspects";
62+}
···1+# this is a non-dendritic darwin class module file.
2+# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
3+{ ... }:
4+{
5+ # see nix-darwin options.
6+}
···1+# this is a non-dendritic nix class module file.
2+# automatically discovered by `den.import-tree` as enabled in auto-imports.nix
3+#
4+# suppose this file was auto-generated by nixos-generate-config or some other hardware tooling.
5+{
6+7+}
+86-78
templates/default/modules/_example/aspects.nix
···1# example aspect dependencies for our hosts
2# Feel free to remove it, adapt or split into modules.
3-{ inputs, lib, ... }:
0000004{
0000000056- den.aspects =
7- { aspects, ... }:
8- {
9- # rockhopper.nixos = { }; # config for rockhopper host
10- # alice.homeManager = { }; # config for alice
11- developer = {
12- description = "aspect for bob's standalone home-manager";
13- homeManager = { };
14- };
1516- # aspect for adelie host using github:nix-community/NixOS-WSL
17- wsl.nixos = {
18- imports = [ inputs.nixos-wsl.nixosModules.default ];
19- wsl.enable = true;
20- };
21-22- # default.{host,user,home} can be used for global settings.
23- default.host.darwin.system.stateVersion = lib.mkDefault 6;
24- default.host.nixos.system.stateVersion = "25.11";
25- default.home.homeManager.home.stateVersion = lib.mkDefault "25.11";
2627- # parametric host and user default configs. see aspects-config.nix
28- default.host.includes = [ aspects.example.provides.host ];
29- default.user.includes = [ aspects.example.provides.user ];
30- default.home.includes = [ aspects.example.provides.home ];
03132- # aspect for each host that includes the user alice.
33- alice.provides.hostUser =
34- { user, ... }:
35- {
36- # administrator in all nixos hosts
37- nixos.users.users.${user.userName} = {
38- isNormalUser = true;
39- extraGroups = [ "wheel" ];
40- };
41 };
42-43- # subtree of aspects for demo purposes.
44- example.provides = {
4546- # in our example, we allow all nixos hosts to be vm-bootable.
47- vm-bootable = {
48- nixos =
49- { modulesPath, ... }:
50- {
51- imports = [ (modulesPath + "/installer/cd-dvd/installation-cd-minimal.nix") ];
52- };
53- };
5455- # parametric providers.
56- host =
57- { host }:
58- { class, ... }:
59 {
60- includes = [ aspects.example.provides.vm-bootable ];
61- ${class}.networking.hostName = host.hostName;
62 };
06364- user =
65- { user, host }:
66- let
67- aspect = {
68- name = "(example.user ${host.name} ${user.name})";
69- description = "user setup on different OS";
70- darwin.system.primaryUser = user.userName;
71- nixos.users.users.${user.userName}.isNormalUser = true;
72- };
73-74- # adelie is nixos-on-wsl, has special user setup
75- by-host.adelie = {
76- nixos.defaultUser = user.userName;
77- };
78- in
79- by-host.${host.name} or aspect;
8081- home =
82- { home }:
83- { class, ... }:
84- let
85- path = if lib.hasSuffix "darwin" home.system then "/Users" else "/home";
86- in
87- {
88- ${class}.home = {
89- username = home.userName;
90- homeDirectory = "${path}/${home.userName}";
91- };
92 };
9394- };
0000000000000000000009596 };
0097}
···1# example aspect dependencies for our hosts
2# Feel free to remove it, adapt or split into modules.
3+# see also: defaults.nix, compat-imports.nix, home-managed.nix
4+{
5+ inputs,
6+ den,
7+ lib,
8+ ...
9+}:
10{
11+ # see also defaults.nix where static settings are set.
12+ den.default = {
13+ # parametric defaults for host/user/home. see aspects/dependencies.nix
14+ # `_` is shorthand alias for `provides`.
15+ host.includes = [ den.aspects.example._.host ];
16+ user.includes = [ den.aspects.example._.user ];
17+ home.includes = [ den.aspects.example._.home ];
18+ };
1920+ # aspects for our example host/user/home definitions.
21+ # on a real setup you will split these over into multiple dendritic files.
22+ den.aspects = {
23+ rockhopper.nixos = { }; # config for rockhopper host
24+ # alice.homeManager = { }; # config for alice
00002526+ developer = {
27+ description = "aspect for bob's standalone home-manager";
28+ homeManager = { };
29+ };
0000003031+ # aspect for adelie host using github:nix-community/NixOS-WSL
32+ wsl.nixos = {
33+ imports = [ inputs.nixos-wsl.nixosModules.default ];
34+ wsl.enable = true;
35+ };
3637+ # aspect for each host that includes the user alice.
38+ alice.provides.hostUser =
39+ { user, ... }:
40+ {
41+ # administrator in all nixos hosts
42+ nixos.users.users.${user.userName} = {
43+ isNormalUser = true;
44+ extraGroups = [ "wheel" ];
045 };
46+ };
004748+ # subtree of aspects for demo purposes.
49+ example.provides = {
0000005051+ # in our example, we allow all nixos hosts to be vm-bootable.
52+ vm-bootable = {
53+ nixos =
54+ { modulesPath, ... }:
55 {
56+ imports = [ (modulesPath + "/installer/cd-dvd/installation-cd-minimal.nix") ];
057 };
58+ };
5960+ # parametric providers.
61+ host =
62+ { host }:
63+ { class, ... }:
64+ {
65+ # `_` is a shorthand alias for `provides`
66+ includes = [ den.aspects.example._.vm-bootable ];
67+ ${class}.networking.hostName = host.hostName;
68+ };
00000006970+ user =
71+ { user, host }:
72+ let
73+ by-class.nixos.users.users.${user.userName}.isNormalUser = true;
74+ by-class.darwin = {
75+ system.primaryUser = user.userName;
76+ users.users.${user.userName}.isNormalUser = true;
000077 };
7879+ # adelie is nixos-on-wsl, has special additional user setup
80+ by-host.adelie.nixos.defaultUser = user.userName;
81+ in
82+ {
83+ includes = [
84+ by-class
85+ (by-host.${host.name} or { })
86+ ];
87+ };
88+89+ home =
90+ { home }:
91+ { class, ... }:
92+ let
93+ homeDir = if lib.hasSuffix "darwin" home.system then "/Users" else "/home";
94+ in
95+ {
96+ ${class}.home = {
97+ username = lib.mkDefault home.userName;
98+ homeDirectory = lib.mkDefault "${homeDir}/${home.userName}";
99+ };
100+ };
101102 };
103+104+ };
105}