From 5af563d70f4f98d28cdd9bf0b851cd246a9b1c2f Mon Sep 17 00:00:00 2001 From: alexion Date: Sun, 27 Sep 2026 23:31:44 -0400 Subject: [PATCH] refactor(dotfiles): split registry responsibilities --- configs/nixos/registry/address.nix | 4 +- configs/nixos/registry/route.nix | 6 +- docs/TODO.md | 6 + src/dotfiles/registry.nix | 204 +------------------------- src/dotfiles/registry/collection.nix | 82 +++++++++++ src/dotfiles/registry/definitions.nix | 26 ++++ src/dotfiles/registry/options.nix | 22 +++ src/dotfiles/registry/schema.nix | 46 ++++++ src/dotfiles/registry/validation.nix | 44 ++++++ src/dotfiles/util.nix | 3 + 10 files changed, 242 insertions(+), 201 deletions(-) create mode 100644 src/dotfiles/registry/collection.nix create mode 100644 src/dotfiles/registry/definitions.nix create mode 100644 src/dotfiles/registry/options.nix create mode 100644 src/dotfiles/registry/schema.nix create mode 100644 src/dotfiles/registry/validation.nix diff --git a/configs/nixos/registry/address.nix b/configs/nixos/registry/address.nix index 3f4eb65..a338dee 100644 --- a/configs/nixos/registry/address.nix +++ b/configs/nixos/registry/address.nix @@ -1,10 +1,10 @@ -{ lib, ... }: +{ lib, util, ... }: { type = lib.types.str; checks = [ { - assertion = lib.registry.predicates.uniqueValue (entry: entry.value); + assertion = util.registry.predicates.uniqueValue (entry: entry.value); message = "Address registry values must be unique."; } ]; diff --git a/configs/nixos/registry/route.nix b/configs/nixos/registry/route.nix index 65e7857..fc69b87 100644 --- a/configs/nixos/registry/route.nix +++ b/configs/nixos/registry/route.nix @@ -1,14 +1,14 @@ -{ lib, ... }: +{ lib, util, ... }: { checks = [ { - assertion = lib.registry.predicates.uniqueValue (entry: entry.value.domain); + assertion = util.registry.predicates.uniqueValue (entry: entry.value.domain); message = "Route domains must be unique."; } { assertion = entries: - lib.registry.predicates.uniqueValue (entry: entry.value) ( + util.registry.predicates.uniqueValue (entry: entry.value) ( lib.concatMap (entry: map (port: entry // { value = port; }) entry.value.udp.ports) entries ); message = "Route UDP ports must be unique."; diff --git a/docs/TODO.md b/docs/TODO.md index 853b804..33b0b72 100644 --- a/docs/TODO.md +++ b/docs/TODO.md @@ -128,6 +128,11 @@ Review Nix files in entrypoint order: - [x] `src/dotfiles/default.nix` - [x] `src/dotfiles/util.nix` - [ ] `src/dotfiles/registry.nix` +- [ ] `src/dotfiles/registry/options.nix` +- [ ] `src/dotfiles/registry/definitions.nix` +- [ ] `src/dotfiles/registry/schema.nix` +- [ ] `src/dotfiles/registry/collection.nix` +- [ ] `src/dotfiles/registry/validation.nix` - [ ] `configs/nixos/registry/address.nix` - [ ] `configs/nixos/registry/env-var.nix` - [ ] `configs/nixos/registry/homepage.nix` @@ -148,6 +153,7 @@ Review Nix files in entrypoint order: - [ ] `configs/nixos/modules/desktop/apps/firefox.nix` - [ ] `configs/nixos/modules/desktop/apps/ghostty.nix` - [ ] `configs/nixos/modules/desktop/apps/obsidian.nix` +- [ ] `configs/nixos/modules/desktop/apps/r2modman.nix` - [ ] `configs/nixos/modules/desktop/apps/signal.nix` - [ ] `configs/nixos/modules/desktop/apps/steam.nix` - [ ] `configs/nixos/modules/desktop/apps/vencord.nix` diff --git a/src/dotfiles/registry.nix b/src/dotfiles/registry.nix index c1ac884..91df9c5 100644 --- a/src/dotfiles/registry.nix +++ b/src/dotfiles/registry.nix @@ -5,208 +5,20 @@ util, }: let - definitions = lib.genAttrs' (util.collectNixFiles root) ( - path: lib.nameValuePair (util.removeNixSuffix path) (mkRegistry path) - ); - - mkRegistry = - path: - let - evaluated = lib.evalModules { - specialArgs.lib = registryLib; - modules = [ - registryDefinitionModule - path - ]; - }; - in - { - inherit (evaluated.config) type replicate checks; - }; - - registryLib = lib // { - registry = (lib.registry or { }) // { - inherit predicates; - }; + options = import ./registry/options.nix { + inherit definitions lib; }; - predicates = { - uniqueValue = - value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries; + definitions = import ./registry/definitions.nix { + inherit lib root util; }; - registryDefinitionModule = { - options = { - type = lib.mkOption { - type = lib.types.raw; - description = '' - NixOS option type for one registry entry. - ''; - }; - - replicate = lib.mkOption { - type = lib.types.bool; - default = false; - description = '' - Whether collected entries are merged and published under every host key. - ''; - }; - - checks = lib.mkOption { - type = lib.types.listOf checkType; - default = [ ]; - description = '' - Validation checks for this registry's flattened collected entries. - ''; - }; - }; - }; - - checkType = lib.types.submodule { - options = { - assertion = lib.mkOption { - type = lib.types.functionTo lib.types.bool; - description = '' - Predicate run against this registry's flattened collected entries. - ''; - }; - - message = lib.mkOption { - type = lib.types.str; - description = '' - Error message shown when this registry validation check fails. - ''; - }; - }; - }; - - registryOption = - registry: - lib.mkOption { - type = lib.types.attrsOf (lib.types.attrsOf registry.type); - default = { }; - description = '' - Lazily collected fleet-visible registry entries, nested by host and entry name. - ''; - }; - withOperations = - registry: - registry - // { - join = host: entry: join registry host entry; - }; - join = - registry: host: entry: - lib.filterAttrs (_name: value: value != null) ( - lib.mapAttrs ( - registryName: _registry: (registry.${registryName}.${host} or { }).${entry} or null - ) definitions - ); - - collectData = - { hosts }: - let - data = lib.mapAttrs ( - registryName: registry: - if registry.replicate or false then - replicatedData registryName hosts raw.${registryName} - else - lib.mapAttrs (hostName: host: host.config.registry.${registryName}.${hostName} or { }) hosts - ) definitions; - raw = lib.mapAttrs (registryName: _registry: rawDeclarations registryName hosts) definitions; - in - assertValid data; - - replicatedData = - registryName: hosts: raw: - let - entries = lib.mapAttrs (_entryName: entry: entry.value) (replicatedEntries registryName raw); - in - lib.mapAttrs (_hostName: _host: entries) hosts; - - replicatedEntries = - registryName: raw: - let - entries = lib.concatLists ( - lib.concatLists ( - lib.mapAttrsToList ( - _evalHost: hostDeclarations: - lib.mapAttrsToList ( - hostName: declarations: - lib.mapAttrsToList (entryName: value: { - path = "registry.${registryName}.${hostName}.${entryName}"; - inherit entryName value; - }) declarations - ) hostDeclarations - ) raw - ) - ); - in - lib.foldl' ( - acc: entry: - if lib.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; - - rawDeclarations = - registryName: hosts: - lib.mapAttrs (_hostName: host: host.config.registry.${registryName} or { }) hosts; - - assertValid = - data: - let - assertions = validationAssertions data; - failures = lib.filter (assertion: !assertion.assertion) assertions; - in - assert lib.assertMsg (lib.all (assertion: assertion.assertion) assertions) ( - lib.concatMapStringsSep "\n" (assertion: assertion.message) failures - ); - data; - - validationAssertions = - data: - lib.concatLists ( - lib.mapAttrsToList ( - registryName: registry: - let - entries = flattenRegistryEntries registryName data.${registryName}; - in - map (check: { - assertion = check.assertion entries; - inherit (check) message; - }) registry.checks - ) definitions - ); - - flattenRegistryEntries = - registryName: data: - lib.concatLists ( - lib.mapAttrsToList ( - host: entries: - lib.mapAttrsToList (entry: value: { - path = "registry.${registryName}.${host}.${entry}"; - inherit host entry value; - }) entries - ) data - ); - in { - types = lib.mapAttrs (_name: definition: definition.type) definitions; + inherit (options) types module; - module = { - options.registry = lib.mapAttrs (_name: registryOption) definitions; + collected = import ./registry/collection.nix { + inherit definitions hosts lib; + validate = import ./registry/validation.nix { inherit definitions lib; }; }; - - collected = withOperations (collectData { - inherit hosts; - }); } diff --git a/src/dotfiles/registry/collection.nix b/src/dotfiles/registry/collection.nix new file mode 100644 index 0000000..57897c2 --- /dev/null +++ b/src/dotfiles/registry/collection.nix @@ -0,0 +1,82 @@ +{ + definitions, + hosts, + lib, + validate, +}: +let + withOperations = + registry: + registry + // { + join = host: entry: join registry host entry; + }; + + join = + registry: host: entry: + lib.filterAttrs (_name: value: value != null) ( + lib.mapAttrs ( + registryName: _registry: (registry.${registryName}.${host} or { }).${entry} or null + ) definitions + ); + + collectData = + { hosts }: + let + data = lib.mapAttrs ( + registryName: registry: + if registry.replicate or false then + replicatedData registryName hosts raw.${registryName} + else + lib.mapAttrs (hostName: host: host.config.registry.${registryName}.${hostName} or { }) hosts + ) definitions; + raw = lib.mapAttrs (registryName: _registry: rawDeclarations registryName hosts) definitions; + in + validate data; + + replicatedData = + registryName: hosts: raw: + let + entries = lib.mapAttrs (_entryName: entry: entry.value) (replicatedEntries registryName raw); + in + lib.mapAttrs (_hostName: _host: entries) hosts; + + replicatedEntries = + registryName: raw: + let + entries = lib.concatLists ( + lib.concatLists ( + lib.mapAttrsToList ( + _evalHost: hostDeclarations: + lib.mapAttrsToList ( + hostName: declarations: + lib.mapAttrsToList (entryName: value: { + path = "registry.${registryName}.${hostName}.${entryName}"; + inherit entryName value; + }) declarations + ) hostDeclarations + ) raw + ) + ); + in + lib.foldl' ( + acc: entry: + if lib.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; + + rawDeclarations = + registryName: hosts: + lib.mapAttrs (_hostName: host: host.config.registry.${registryName} or { }) hosts; +in +withOperations (collectData { + inherit hosts; +}) diff --git a/src/dotfiles/registry/definitions.nix b/src/dotfiles/registry/definitions.nix new file mode 100644 index 0000000..f2b2c40 --- /dev/null +++ b/src/dotfiles/registry/definitions.nix @@ -0,0 +1,26 @@ +{ + lib, + root, + util, +}: +let + definitions = lib.genAttrs' (util.collectNixFiles root) ( + path: lib.nameValuePair (util.removeNixSuffix path) (mkRegistry path) + ); + + mkRegistry = + path: + let + evaluated = lib.evalModules { + specialArgs = { inherit util; }; + modules = [ + (import ./schema.nix { inherit lib; }) + path + ]; + }; + in + { + inherit (evaluated.config) type replicate checks; + }; +in +definitions diff --git a/src/dotfiles/registry/options.nix b/src/dotfiles/registry/options.nix new file mode 100644 index 0000000..021b13f --- /dev/null +++ b/src/dotfiles/registry/options.nix @@ -0,0 +1,22 @@ +{ + definitions, + lib, +}: +let + registryOption = + registry: + lib.mkOption { + type = lib.types.attrsOf (lib.types.attrsOf registry.type); + default = { }; + description = '' + Lazily collected fleet-visible registry entries, nested by host and entry name. + ''; + }; +in +{ + types = lib.mapAttrs (_name: definition: definition.type) definitions; + + module = { + options.registry = lib.mapAttrs (_name: registryOption) definitions; + }; +} diff --git a/src/dotfiles/registry/schema.nix b/src/dotfiles/registry/schema.nix new file mode 100644 index 0000000..fdd9a0a --- /dev/null +++ b/src/dotfiles/registry/schema.nix @@ -0,0 +1,46 @@ +{ lib }: +let + checkType = lib.types.submodule { + options = { + assertion = lib.mkOption { + type = lib.types.functionTo lib.types.bool; + description = '' + Predicate run against this registry's flattened collected entries. + ''; + }; + + message = lib.mkOption { + type = lib.types.str; + description = '' + Error message shown when this registry validation check fails. + ''; + }; + }; + }; +in +{ + options = { + type = lib.mkOption { + type = lib.types.raw; + description = '' + NixOS option type for one registry entry. + ''; + }; + + replicate = lib.mkOption { + type = lib.types.bool; + default = false; + description = '' + Whether collected entries are merged and published under every host key. + ''; + }; + + checks = lib.mkOption { + type = lib.types.listOf checkType; + default = [ ]; + description = '' + Validation checks for this registry's flattened collected entries. + ''; + }; + }; +} diff --git a/src/dotfiles/registry/validation.nix b/src/dotfiles/registry/validation.nix new file mode 100644 index 0000000..1840ff5 --- /dev/null +++ b/src/dotfiles/registry/validation.nix @@ -0,0 +1,44 @@ +{ + definitions, + lib, +}: +let + assertValid = + data: + let + assertions = validationAssertions data; + failures = lib.filter (assertion: !assertion.assertion) assertions; + in + assert lib.assertMsg (lib.all (assertion: assertion.assertion) assertions) ( + lib.concatMapStringsSep "\n" (assertion: assertion.message) failures + ); + data; + + validationAssertions = + data: + lib.concatLists ( + lib.mapAttrsToList ( + registryName: registry: + let + entries = flattenRegistryEntries registryName data.${registryName}; + in + map (check: { + assertion = check.assertion entries; + inherit (check) message; + }) registry.checks + ) definitions + ); + + flattenRegistryEntries = + registryName: data: + lib.concatLists ( + lib.mapAttrsToList ( + host: entries: + lib.mapAttrsToList (entry: value: { + path = "registry.${registryName}.${host}.${entry}"; + inherit host entry value; + }) entries + ) data + ); +in +assertValid diff --git a/src/dotfiles/util.nix b/src/dotfiles/util.nix index 56c5e99..a38ca34 100644 --- a/src/dotfiles/util.nix +++ b/src/dotfiles/util.nix @@ -22,6 +22,9 @@ in removeNixSuffix = path: lib.removeSuffix ".nix" (builtins.baseNameOf path); + registry.predicates.uniqueValue = + value: entries: builtins.length (lib.unique (map value entries)) == builtins.length entries; + bridgeName = id: "br-vlan${toString id}"; deriveMac =