{ lib, self }: let inherit (lib) all concatLists concatMapStringsSep filter filterAttrs foldl' hasAttr hasSuffix mapAttrs mapAttrs' mapAttrsToList mkOption nameValuePair removeSuffix types ; assertionType = types.submodule { options = { assertion = mkOption { type = types.bool; description = '' Whether this registry validation assertion passed. ''; }; message = mkOption { type = types.str; description = '' Error message shown when this registry validation assertion fails. ''; }; }; }; registryDefinitionModule = { options = { type = mkOption { type = types.raw; description = '' NixOS option type for one registry entry. ''; }; replicate = mkOption { type = types.bool; default = false; description = '' Whether collected entries are merged and published under every host key. ''; }; validate = mkOption { type = types.functionTo (types.listOf assertionType); default = _entries: [ ]; description = '' Validate this registry's flattened collected entries. ''; }; }; }; registryName = fileName: removeSuffix ".nix" fileName; registryOption = registry: mkOption { type = types.attrsOf (types.attrsOf registry.type); default = { }; description = '' Lazily collected fleet-visible registry entries, nested by host and entry name. ''; }; mkRegistry = path: let evaluated = lib.evalModules { specialArgs = { inherit lib; }; modules = [ registryDefinitionModule path ]; }; in { inherit (evaluated.config) type replicate validate; }; definitions = mapAttrs' ( name: type: let path = self + "/registry/${name}"; in nameValuePair (registryName name) (mkRegistry path) ) (filterAttrs (name: type: type == "regular" && hasSuffix ".nix" name) (builtins.readDir (self + "/registry"))); flattenRegistryEntries = registryName: data: concatLists ( mapAttrsToList ( host: entries: mapAttrsToList (entry: value: { path = "registry.${registryName}.${host}.${entry}"; inherit host entry value; }) entries ) data ); rawDeclarations = registryName: hosts: mapAttrs (_hostName: host: host.config.registry.${registryName} or { }) hosts; replicatedEntries = registryName: raw: let entries = concatLists ( concatLists ( mapAttrsToList ( _evalHost: hostDeclarations: mapAttrsToList ( hostName: declarations: mapAttrsToList (entryName: value: { path = "registry.${registryName}.${hostName}.${entryName}"; inherit entryName value; }) declarations ) hostDeclarations ) raw ) ); in foldl' ( acc: entry: if hasAttr entry.entryName acc then if acc.${entry.entryName}.value == entry.value then acc else throw "conflicting replicated registry entry ${entry.path} does not match ${acc.${entry.entryName}.path}" else acc // { ${entry.entryName} = entry; } ) { } entries; replicatedData = registryName: hosts: raw: let entries = mapAttrs (_entryName: entry: entry.value) (replicatedEntries registryName raw); in mapAttrs (_hostName: _host: entries) hosts; validationAssertions = data: concatLists ( mapAttrsToList ( registryName: registry: registry.validate (flattenRegistryEntries registryName data.${registryName}) ) definitions ); assertValid = data: let assertions = validationAssertions data; failures = filter (assertion: !assertion.assertion) assertions; in assert lib.assertMsg (all (assertion: assertion.assertion) assertions) ( concatMapStringsSep "\n" (assertion: assertion.message) failures ); data; join = registry: host: entry: filterAttrs (_name: value: value != null) ( mapAttrs (registryName: _registry: (registry.${registryName}.${host} or { }).${entry} or null) definitions ); collectData = { hosts }: let raw = mapAttrs (registryName: _registry: rawDeclarations registryName hosts) definitions; data = mapAttrs ( registryName: registry: if registry.replicate or false then replicatedData registryName hosts raw.${registryName} else mapAttrs (hostName: host: host.config.registry.${registryName}.${hostName} or { }) hosts ) definitions; in assertValid data; withOperations = registry: registry // { join = host: entry: join registry host entry; }; in { inherit definitions; module = { options.registry = mapAttrs (_name: registry: registryOption registry) definitions; }; collect = args: withOperations (collectData args); }