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

feat: Context for host with home-manager users. (#123)

See [#den > Set user unspecific home-manager
options](https://oeiuwq.zulipchat.com/#narrow/channel/548534-den/topic/Set.20user.20unspecific.20home-manager.20options/with/563739770)

This changeset introduces a new Den core context: `{ HM-OS-HOST }`
invoked on host aspects. This context is used by our home-manager
integration but can also be used by any other host aspect to detect when
HM is enabled, for example to set `useGlobalPkgs`.

This context is produced by `hm-os-host.nix` when it detects any host
that has an OS supported by home-manager AND has at least one user with
`homeManager` class.

The aspect from `hm-os-host.nix` is internal, always included in
`den.default`.

When `den._.home-manager` integration is enabled, it reacts to the
`host-aspect { HM-OS-HOST }` context and setups home-manager
integration. For each user it calls `user-aspect { HM-OS-USER }` for
hm-dependencies.nix to include the correct dependencies.

home-manager related code has been moved to ./provides/home-manager/.

authored by oeiuwq.com and committed by

GitHub 64497b43 9e5bb4be

+138 -63
+2 -45
modules/aspects/dependencies.nix
··· 6 6 inherit (den.lib) 7 7 owned 8 8 statics 9 - parametric 9 + take 10 10 ; 11 - 12 - inherit (den.lib.take) exactly; 13 11 14 12 dependencies = [ 15 - (exactly osDependencies) 16 - (exactly hmUserDependencies) 17 - (exactly hmStandaloneDependencies) 13 + (take.exactly osDependencies) 18 14 ]; 19 15 20 16 osDependencies = ··· 47 43 (owned USR) 48 44 (statics USR) 49 45 (USR { inherit OS host user; }) 50 - ]; 51 - }; 52 - 53 - # from OS home-managed integration. 54 - hmUserDependencies = 55 - { 56 - OS-HM, 57 - host, 58 - user, 59 - }: 60 - let 61 - inherit (OS-HM) OS HM; 62 - context = { 63 - inherit 64 - OS 65 - HM 66 - user 67 - host 68 - ; 69 - }; 70 - in 71 - { 72 - includes = [ 73 - (owned den.default) 74 - (statics den.default) 75 - (owned HM) 76 - (statics HM) 77 - (parametric.fixedTo context OS) 78 - ]; 79 - }; 80 - 81 - hmStandaloneDependencies = 82 - { HM, home }: 83 - den.lib.take.unused home { 84 - includes = [ 85 - (owned den.default) 86 - (statics den.default) 87 - (owned HM) 88 - (statics HM) 89 46 ]; 90 47 }; 91 48
+15 -17
modules/aspects/provides/home-manager.nix modules/aspects/provides/home-manager/hm-integration.nix
··· 26 26 ''; 27 27 28 28 homeManager = 29 - { OS, host }: 30 - { class, aspect-chain }: 29 + { HM-OS-HOST }: 31 30 let 31 + inherit (HM-OS-HOST) OS host; 32 + 32 33 hmClass = "homeManager"; 33 34 hmUsers = builtins.filter (u: u.class == hmClass) (lib.attrValues host.users); 34 35 35 36 hmUserModule = 36 37 user: 37 38 let 38 - ctx = { 39 - inherit aspect-chain; 40 - class = hmClass; 41 - }; 42 39 HM = den.aspects.${user.aspect}; 43 40 aspect = HM { 44 - inherit host user; 45 - OS-HM = { inherit OS HM; }; 41 + HM-OS-USER = { 42 + inherit 43 + OS 44 + HM 45 + host 46 + user 47 + ; 48 + }; 46 49 }; 47 - module = aspect.resolve ctx; 50 + module = aspect.resolve { class = hmClass; }; 48 51 in 49 52 module; 50 53 ··· 53 56 value.imports = [ (hmUserModule user) ]; 54 57 }) hmUsers; 55 58 56 - hmModule = host.hm-module or inputs.home-manager."${class}Modules".home-manager; 57 - aspect.${class} = { 59 + hmModule = host.hm-module or inputs.home-manager."${host.class}Modules".home-manager; 60 + aspect.${host.class} = { 58 61 imports = [ hmModule ]; 59 62 home-manager.users = lib.listToAttrs users; 60 63 }; 61 64 62 - supportedOS = builtins.elem class [ 63 - "nixos" 64 - "darwin" 65 - ]; 66 - enabled = supportedOS && builtins.length hmUsers > 0; 67 65 in 68 - if enabled then aspect else { }; 66 + aspect; 69 67 70 68 in 71 69 {
+45
modules/aspects/provides/home-manager/hm-dependencies.nix
··· 1 + { 2 + den, 3 + ... 4 + }: 5 + let 6 + inherit (den.lib) 7 + owned 8 + statics 9 + parametric 10 + take 11 + ; 12 + 13 + dependencies = [ 14 + (take.exactly hmUserDependencies) 15 + (take.exactly hmStandaloneDependencies) 16 + ]; 17 + 18 + # from OS home-managed integration. 19 + hmUserDependencies = 20 + { HM-OS-USER }: 21 + { 22 + includes = [ 23 + (owned den.default) 24 + (statics den.default) 25 + (owned HM-OS-USER.HM) 26 + (statics HM-OS-USER.HM) 27 + (parametric.fixedTo HM-OS-USER HM-OS-USER.OS) 28 + ]; 29 + }; 30 + 31 + hmStandaloneDependencies = 32 + { HM, home }: 33 + take.unused home { 34 + includes = [ 35 + (owned den.default) 36 + (statics den.default) 37 + (owned HM) 38 + (statics HM) 39 + ]; 40 + }; 41 + 42 + in 43 + { 44 + den.default.includes = dependencies; 45 + }
+43
modules/aspects/provides/home-manager/hm-os-host.nix
··· 1 + { den, lib, ... }: 2 + let 3 + description = '' 4 + This is a private aspect always included in den.default. 5 + 6 + This aspect detects hosts that have an HM supported OS and 7 + that have at least one user with ${hm-class} class. 8 + 9 + When this occurs it produces a context `HM-OS-HOST` 10 + that other host aspects can use. 11 + 12 + When the `den._.home-manager` aspect is enabled by the user, 13 + it reacts to this context and configures HM on the host. 14 + 15 + This same context can be used by other aspects to configure 16 + OS settings ONLY for hosts having HM enabled. 17 + ''; 18 + 19 + hm-class = "homeManager"; 20 + hm-os-classes = [ 21 + "nixos" 22 + "darwin" 23 + ]; 24 + 25 + hm-detect = 26 + { OS, host }: 27 + let 28 + is-os-supported = builtins.elem host.class hm-os-classes; 29 + hm-users = builtins.filter (u: u.class == hm-class) (lib.attrValues host.users); 30 + has-hm-users = builtins.length hm-users > 0; 31 + is-hm-host = is-os-supported && has-hm-users; 32 + ctx.HM-OS-HOST = { inherit OS host; }; 33 + in 34 + if is-hm-host then OS ctx else { }; 35 + 36 + aspect = { 37 + inherit description; 38 + __functor = _: den.lib.take.exactly hm-detect; 39 + }; 40 + in 41 + { 42 + den.default.includes = [ aspect ]; 43 + }
+29
templates/examples/modules/_example/ci/hm-enabled-host.nix
··· 1 + { den, inputs, ... }: 2 + { 3 + # The `{ HM-OS-HOST }` context is activated ONLY for hosts that have 4 + # a HM supported OS and at least one user with homeManager class. 5 + den.aspects.hm-global-pkgs = 6 + { HM-OS-HOST }: 7 + den.lib.take.unused [ HM-OS-HOST.host ] # access host from context if needed 8 + { 9 + nixos.home-manager.useGlobalPkgs = true; 10 + }; 11 + 12 + den.default.includes = [ den.aspects.hm-global-pkgs ]; 13 + 14 + den.hosts.x86_64-linux.no-homes = { }; 15 + 16 + perSystem = 17 + { checkCond, rockhopper, ... }: 18 + { 19 + checks.rockhopper-hm-global-pkgs = checkCond "rockhopper-hm-global-pkgs" ( 20 + rockhopper.config.home-manager.useGlobalPkgs 21 + ); 22 + 23 + checks.no-homes-hm-global-pkgs = checkCond "no-homes-hm-global-pkgs" ( 24 + # no home-manager enabled nor useGlobalPkgs 25 + !inputs.self.nixosConfigurations.no-homes.config ? home-manager.useGlobalPkgs 26 + ); 27 + }; 28 + 29 + }
+4 -1
templates/examples/modules/_example/ci/top-level-parametric.nix
··· 22 22 homeManager.imports = [ (topLevel host.name) ]; 23 23 }; 24 24 25 + den.aspects.rockhopper.includes = [ 26 + den.aspects.toplevel-host 27 + ]; 28 + 25 29 den.aspects.alice.includes = [ 26 - den.aspects.toplevel-host 27 30 den.aspects.toplevel-user 28 31 ]; 29 32