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

contexts are not strict now - preparation for incremental contexts. (#52)

This is in preparation for contexts being incremental,

an aspect can add (merge) additional data to an existing context.

for example, a host configuration starts with { host } but later for each of it users it merges currentContext // { user } leading to { host, user } augmented context.

This allows contexts to be incremental, so parametric-aspects (functions) can specify what is the context they need:

{ user, ...} - At least user data in context.
{ user, host, gaming } - Need the three of them.

Will invalidate and close #51.
Breaking Change

Your existing functions taking context like { host } will likely need to be { host, ... } now. See this PR diff for how batteries were updated.

authored by oeiuwq.com and committed by

GitHub e4a7c7c8 891c5c58

+25 -23
+5 -1
checkmate/tests/aspect-functor.nix
··· 40 nixos.user-only = user; 41 } 42 else 43 - { } 44 ) 45 ( 46 { home, ... }: ··· 100 ); 101 expected = { 102 includes = [ 103 { nixos.any = 10; } 104 ]; 105 }; ··· 129 ); 130 expected = { 131 includes = [ 132 { 133 nixos.host-user = [ 134 1 135 2 136 ]; 137 } # host user 138 { nixos.any = 10; } 139 ]; 140 };
··· 40 nixos.user-only = user; 41 } 42 else 43 + { nixos.user-only = false; } 44 ) 45 ( 46 { home, ... }: ··· 100 ); 101 expected = { 102 includes = [ 103 + { nixos.home = 2; } 104 { nixos.any = 10; } 105 ]; 106 }; ··· 130 ); 131 expected = { 132 includes = [ 133 + { nixos.host = 1; } 134 { 135 nixos.host-user = [ 136 1 137 2 138 ]; 139 } # host user 140 + { nixos.user = 2; } 141 + { nixos.user-only = false; } 142 { nixos.any = 10; } 143 ]; 144 };
+1 -1
checkmate/tests/function_can_take.nix
··· 62 user 63 ] 64 ); 65 - expected = false; 66 }; 67 68 in
··· 62 user 63 ] 64 ); 65 + expected = true; 66 }; 67 68 in
+10 -10
modules/aspects/dependencies.nix
··· 7 8 dependencies = [ 9 # owned attributes: <aspect>.<class> 10 - ({ home }: owned home.class den.aspects.${home.aspect}) 11 - ({ host }: owned host.class den.aspects.${host.aspect}) 12 - ({ user, host }: owned user.class den.aspects.${user.aspect}) 13 14 # defaults: owned from den.default.<class> 15 - ({ home }: owned home.class den.default) 16 - ({ host }: owned host.class den.default) 17 - ({ user, host }: owned user.class den.default) 18 19 # static (non-parametric) from <aspect>.includes 20 - ({ home }: statics den.aspects.${home.aspect}) 21 - ({ host }: statics den.aspects.${host.aspect}) 22 - ({ user, host }: statics den.aspects.${user.aspect}) 23 24 # user-to-host context 25 ({ fromUser, toHost }: owned toHost.class den.aspects.${fromUser.aspect}) ··· 34 ]; 35 36 hostIncludesFromUsers = 37 - { host }: 38 { 39 includes = 40 let
··· 7 8 dependencies = [ 9 # owned attributes: <aspect>.<class> 10 + ({ home, ... }: owned home.class den.aspects.${home.aspect}) 11 + ({ host, ... }: owned host.class den.aspects.${host.aspect}) 12 + ({ user, ... }: owned user.class den.aspects.${user.aspect}) 13 14 # defaults: owned from den.default.<class> 15 + ({ home, ... }: owned home.class den.default) 16 + ({ host, ... }: owned host.class den.default) 17 + ({ user, ... }: owned user.class den.default) 18 19 # static (non-parametric) from <aspect>.includes 20 + ({ home, ... }: statics den.aspects.${home.aspect}) 21 + ({ host, ... }: statics den.aspects.${host.aspect}) 22 + ({ user, ... }: statics den.aspects.${user.aspect}) 23 24 # user-to-host context 25 ({ fromUser, toHost }: owned toHost.class den.aspects.${fromUser.aspect}) ··· 34 ]; 35 36 hostIncludesFromUsers = 37 + { host, ... }: 38 { 39 includes = 40 let
+1 -1
modules/aspects/provides/home-manager.nix
··· 28 29 __functor = 30 _: 31 - { host }: 32 { class, aspect-chain }: 33 let 34 hmUsers = builtins.filter (u: u.class == "homeManager") (lib.attrValues host.users);
··· 28 29 __functor = 30 _: 31 + { host, ... }: 32 { class, aspect-chain }: 33 let 34 hmUsers = builtins.filter (u: u.class == "homeManager") (lib.attrValues host.users);
+5 -4
modules/aspects/provides/import-tree.nix
··· 61 62 den._.import-tree.__functor = 63 _: root: 64 - { class, ... }: 65 let 66 path = "${toString root}/_${class}"; 67 aspect.${class}.imports = [ (inputs.import-tree path) ]; ··· 69 if builtins.pathExists path then aspect else { }; 70 71 den._.import-tree.provides = { 72 - host = root: { host }: den._.import-tree "${toString root}/${host.name}"; 73 - user = root: { host, user }: den._.import-tree "${toString root}/${user.name}@${host.name}"; 74 - home = root: { home }: den._.import-tree "${toString root}/${home.name}"; 75 }; 76 }
··· 61 62 den._.import-tree.__functor = 63 _: root: 64 + # deadnix: skip 65 + { class, aspect-chain }: 66 let 67 path = "${toString root}/_${class}"; 68 aspect.${class}.imports = [ (inputs.import-tree path) ]; ··· 70 if builtins.pathExists path then aspect else { }; 71 72 den._.import-tree.provides = { 73 + host = root: { host, ... }: den._.import-tree "${toString root}/${host.name}"; 74 + home = root: { home, ... }: den._.import-tree "${toString root}/${home.name}"; 75 + user = root: { host, user, ... }: den._.import-tree "${toString root}/${user.name}@${host.name}"; 76 }; 77 }
+1 -4
nix/fn-can-take.nix
··· 1 lib: param: f: 2 let 3 args = lib.mapAttrsToList (name: optional: { inherit name optional; }) (lib.functionArgs f); 4 - empty = lib.length args == 0; 5 6 givenAttrs = (builtins.isAttrs param) && !param ? __functor; 7 ··· 10 11 intersection = lib.intersectLists required provided; 12 satisfied = lib.length required == lib.length intersection; 13 - 14 - noExtas = lib.length required == lib.length provided; 15 in 16 - empty || (givenAttrs && satisfied && noExtas)
··· 1 lib: param: f: 2 let 3 args = lib.mapAttrsToList (name: optional: { inherit name optional; }) (lib.functionArgs f); 4 5 givenAttrs = (builtins.isAttrs param) && !param ? __functor; 6 ··· 9 10 intersection = lib.intersectLists required provided; 11 satisfied = lib.length required == lib.length intersection; 12 in 13 + givenAttrs && satisfied
+1 -1
templates/default/modules/_example/aspects.nix
··· 26 27 # Example: parametric host aspect to automatically set hostName on any host. 28 set-host-name = 29 - { host }: 30 { 31 ${host.class}.networking.hostName = host.name; 32 };
··· 26 27 # Example: parametric host aspect to automatically set hostName on any host. 28 set-host-name = 29 + { host, ... }: 30 { 31 ${host.class}.networking.hostName = host.name; 32 };
+1 -1
templates/default/modules/_profile/profiles.nix
··· 11 pro.profiles = { 12 __functor = den.lib.parametric true; 13 includes = [ 14 - ({ host }: pro.${host.system} or { }) 15 # add other routes according to context. 16 ]; 17 };
··· 11 pro.profiles = { 12 __functor = den.lib.parametric true; 13 includes = [ 14 + ({ host, ... }: pro.${host.system} or { }) 15 # add other routes according to context. 16 ]; 17 };