# service-evaluator.nix - Pure Nix evaluation logic for service documentation { nodes, pkgs, lib, ... }: let # Helper to recursively extract option information extractOptions = path: options: let pathStr = lib.concatStringsSep "." path; in lib.flatten (lib.mapAttrsToList ( name: value: let currentPath = path ++ [name]; currentPathStr = lib.concatStringsSep "." currentPath; in if (value._type or null) == "option" then # This is an actual NixOS option [ { name = currentPathStr; type = value.type.description or (builtins.typeOf (value.default or null)); default = value.default or null; defaultText = if value ? defaultText then value.defaultText.text or null else null; description = value.description or "No description available"; example = value.example or null; readOnly = value.readOnly or false; } ] else if lib.isAttrs value && !(lib.hasAttr "_type" value) then # This is a nested attribute set, recurse extractOptions currentPath value else # Skip other types [] ) options); # Get service options from the first node (they should be the same across nodes) firstNode = lib.head (lib.attrValues nodes); homelabServices = firstNode.options.homelab.services or {}; # Extract all services and their options serviceDefinitions = lib.mapAttrs (serviceName: serviceOptions: { inherit serviceName; options = extractOptions [] serviceOptions; features = let optionNames = map (opt: opt.name) (extractOptions [] serviceOptions); in { hasMonitoring = lib.any (name: lib.hasPrefix "monitoring" name) optionNames; hasLogging = lib.any (name: lib.hasPrefix "logging" name) optionNames; hasProxy = lib.any (name: lib.hasPrefix "proxy" name) optionNames; }; }) homelabServices; # Also get all services that exist in actual configurations (for deployment info) allConfiguredServices = lib.unique (lib.flatten (lib.mapAttrsToList ( nodeName: node: if (node.config.homelab.enable or false) then lib.attrNames (node.config.homelab.services or {}) else [] ) nodes)); # For each service, get deployment info serviceDeployments = lib.listToAttrs (map (serviceName: { name = serviceName; value = lib.foldl ( acc: nodeName: let node = nodes.${nodeName}; serviceConfig = node.config.homelab.services.${serviceName} or null; isEnabled = if serviceConfig != null then serviceConfig.enable or false else false; in if serviceConfig != null then acc // { totalNodes = acc.totalNodes + 1; enabledNodes = acc.enabledNodes + ( if isEnabled then 1 else 0 ); nodeNames = acc.nodeNames ++ [nodeName]; enabledNodeNames = acc.enabledNodeNames ++ ( if isEnabled then [nodeName] else [] ); } else acc ) { totalNodes = 0; enabledNodes = 0; nodeNames = []; enabledNodeNames = []; } (lib.attrNames nodes); }) allConfiguredServices); # Combine service definitions with deployment info servicesWithDeployment = lib.mapAttrs ( serviceName: serviceData: serviceData // { deployment = serviceDeployments.${ serviceName } or { totalNodes = 0; enabledNodes = 0; nodeNames = []; enabledNodeNames = []; }; } ) serviceDefinitions; in { services = servicesWithDeployment; totalServices = lib.length (lib.attrNames servicesWithDeployment); allConfiguredServices = allConfiguredServices; }