Commit f6cefc0

HPCesia <me@hpcesia.com>
2026-06-10 04:53:42
Add settings for each aspects
1 parent 57bf622
Changed files (2)
modules
modules/core/openssh.nix
@@ -26,9 +26,18 @@
       })
     ];
 
+    settings = {
+      port = lib.mkOption {
+        description = "Port to open for SSH access.";
+        type = lib.types.port;
+        default = 22;
+      };
+    };
+
     nixos = {host, ...}: {
       services.openssh = {
         enable = true;
+        ports = [host.settings.core.openssh.access.port];
         settings = {
           PermitRootLogin = "prohibit-password";
           PasswordAuthentication = false;
modules/hosts/schema.nix
@@ -5,6 +5,85 @@
 }: {
   den.schema.host = {
     options = let
+      # NOTE: Copied from https://github.com/sini/nix-config/blob/1a9f6a8f6ec5f6c324f9f4ce01630703dd6c1eaa/modules/den/schema/host.nix#L121
+      # TODO: This feature may be merge into den in furture. Check https://github.com/denful/den/pull/563
+      # Dynamic settings type — recursively discovers aspects that declare .settings.
+      # Mirrors the aspect tree: den.aspects.disk.zfs-disk-single.settings →
+      # host.settings.disk.zfs-disk-single.*
+      settingsType = let
+        inherit (den.lib.aspects.fx.keyClassification) structuralKeysSet;
+        classKeys = den.classes or {};
+        quirkKeys = den.quirks or {};
+        skipKey = k: k == "settings" || structuralKeysSet ? ${k} || classKeys ? ${k} || quirkKeys ? ${k};
+
+        # Settings declarations may be plain option attrsets
+        # (`{ foo = mkOption {...}; }`) or module-shaped with explicit
+        # imports/config. Default the module keys so plain attrsets work.
+        #
+        # imports'/config' are bound under distinct names on purpose: writing
+        # `imports = raw.imports or [ ]` here gets rewritten by statix (W04) to
+        # `inherit (raw) imports`, which DROPS the `or` default and throws when
+        # raw is a plain options attrset with no imports/config key.
+        reshapeSettings = raw: let
+          imports' = raw.imports or [];
+          config' = raw.config or {};
+        in {
+          imports = imports';
+          config = config';
+          options = removeAttrs raw [
+            "imports"
+            "config"
+          ];
+        };
+
+        # True if a node has .settings anywhere in its aspect subtree.
+        hasSettingsDeep = node:
+          builtins.isAttrs node
+          && (
+            (node ? settings)
+            || lib.any (k: !(skipKey k) && hasSettingsDeep (node.${k} or null)) (builtins.attrNames node)
+          );
+
+        # Build the submodule for one aspect-tree node, mirroring the tree.
+        # A node may be BOTH an aspect with .settings AND a parent of child
+        # aspects that have settings (e.g. services.bgp has localAsn settings and
+        # also parents services.bgp.cilium-bgp). Merge the node's own settings
+        # options with recursion into its settings-bearing children.
+        nodeModule = node: let
+          ownSettings =
+            if node ? settings
+            then reshapeSettings node.settings
+            else {
+              imports = [];
+              config = {};
+              options = {};
+            };
+          settingChildren =
+            lib.filterAttrs (
+              k: v: !(skipKey k) && builtins.isAttrs v && hasSettingsDeep v
+            )
+            node;
+          childOptions =
+            lib.mapAttrs (
+              name: child:
+                lib.mkOption {
+                  type = lib.types.submodule (nodeModule child);
+                  default = {};
+                  description = "Settings under ${name}";
+                }
+            )
+            settingChildren;
+          # Distinct names so statix (W04) can't rewrite to
+          # `inherit (ownSettings) imports`, which would drop the `or` default.
+          ownImports = ownSettings.imports or [];
+          ownConfig = ownSettings.config or {};
+        in {
+          imports = ownImports;
+          config = ownConfig;
+          options = (ownSettings.options or {}) // childOptions;
+        };
+      in
+        lib.types.submodule (nodeModule (den.aspects or {}));
     in {
       description = lib.mkOption {
         description = "Description of the host.";
@@ -12,6 +91,12 @@
         default = "";
       };
 
+      settings = lib.mkOption {
+        description = "Per-aspect typed settings";
+        type = settingsType;
+        default = {};
+      };
+
       pubKey = lib.mkOption {
         description = "Public SSH key for the host.";
         type = lib.types.nullOr lib.types.str;