{ lib, inputs, config, ... }: let inherit (lib) mapAttrs flatten unique optionals optional ; allRoles = config.dendritic.roles; allFeatures = config.dendritic.features; resolveRoles = let go = seen: roleName: if builtins.elem roleName seen then throw "Circular dendritic role reference detected: ${ builtins.concatStringsSep " -> " (seen ++ [ roleName ]) }" else let role = allRoles.${roleName} or null; in if role == null then throw "Unknown dendritic role: ${roleName}" else [ roleName ] ++ flatten (map (go (seen ++ [ roleName ])) role.roles); in roleNames: unique (flatten (map (go [ ]) roleNames)); resolveFeatures = let go = seen: featureName: if builtins.elem featureName seen then throw "Circular dendritic feature reference detected: ${ builtins.concatStringsSep " -> " (seen ++ [ featureName ]) }" else let feature = allFeatures.${featureName} or null; in if feature == null then throw "Unknown dendritic feature: ${featureName}" else [ featureName ] ++ flatten (map (go (seen ++ [ featureName ])) feature.features); in featureNames: unique (flatten (map (go [ ]) featureNames)); mkHost = hostName: host: let username = if host.primaryUser == null then null else host.primaryUser; user = if username == null then null else config.dendritic.users.${username}; userHome = if user == null then null else user.homes.${hostName} or null; resolvedRoleNames = resolveRoles host.roles; roleFeatureNames = flatten (map (roleName: allRoles.${roleName}.features) resolvedRoleNames); resolvedFeatureNames = resolveFeatures roleFeatureNames; featureNixosModules = flatten ( map (featureName: allFeatures.${featureName}.nixosModules) resolvedFeatureNames ); featureHomeModules = if userHome == null then [ ] else let resolvedHomeRoleNames = resolveRoles userHome.roles; homeRoleFeatureNames = flatten ( map (roleName: allRoles.${roleName}.features) resolvedHomeRoleNames ); resolvedHomeFeatureNames = resolveFeatures homeRoleFeatureNames; embeddedHomeFeatureNames = builtins.filter ( featureName: allFeatures.${featureName}.includeInEmbeddedHomeManager ) resolvedHomeFeatureNames; in flatten (map (featureName: allFeatures.${featureName}.homeModules) embeddedHomeFeatureNames); in inputs.nixpkgs.lib.nixosSystem { system = host.system; specialArgs = { inherit inputs; }; modules = [ { nixpkgs.overlays = [ inputs.self.overlays.default ]; } ../inventory/current.nix ../inventory/features.nix ../inventory/hosts.nix ../inventory/users.nix ../../aspects/roles.nix { dendritic.current = { inherit hostName; primaryUser = username; }; networking.hostName = hostName; } ] ++ host.nixosModules ++ featureNixosModules ++ optional (username != null) { users.users.${username} = { isNormalUser = true; extraGroups = host.primaryUserExtraGroups; }; } ++ optionals (user != null) user.nixosModules ++ optional (userHome != null) inputs.home-manager.nixosModules.home-manager ++ optional (userHome != null) { home-manager.useGlobalPkgs = true; home-manager.useUserPackages = true; home-manager.extraSpecialArgs = { inherit inputs; }; home-manager.sharedModules = [ ../inventory/current.nix ../inventory/features.nix ../inventory/hosts.nix ../inventory/users.nix ../../aspects/roles.nix ]; home-manager.users.${username}.imports = [ { dendritic.current = { inherit hostName; primaryUser = username; }; } ] ++ user.homeModules ++ featureHomeModules ++ userHome.extraModules ++ [ { home.username = user.username; home.homeDirectory = "/home/${user.username}"; home.stateVersion = userHome.stateVersion; } ]; }; }; in { config.flake.nixosConfigurations = mapAttrs mkHost config.dendritic.hosts; }