about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--machines/profpatsch/base-server-options.toml8
-rw-r--r--machines/profpatsch/base-server.nix51
-rw-r--r--machines/profpatsch/base-server.toml26
-rw-r--r--pkgs/profpatsch/nixos-toml-modules.nix119
4 files changed, 182 insertions, 22 deletions
diff --git a/machines/profpatsch/base-server-options.toml b/machines/profpatsch/base-server-options.toml
new file mode 100644
index 00000000..3cfc6e51
--- /dev/null
+++ b/machines/profpatsch/base-server-options.toml
@@ -0,0 +1,8 @@
+[module]
+type = "nixos-options"
+version = "0.0.1"
+
+[vuizvui.user.profpatsch.server.sshPort]
+description = "ssh port"
+type = "port"
+default = 6879
diff --git a/machines/profpatsch/base-server.nix b/machines/profpatsch/base-server.nix
index 921e5d8d..7b0a714a 100644
--- a/machines/profpatsch/base-server.nix
+++ b/machines/profpatsch/base-server.nix
@@ -1,36 +1,43 @@
 { config, pkgs, lib, ... }:
 
 let
-  cfg = config.vuizvui.user.profpatsch.server;
+  cfgImports = (import ../../pkgs/profpatsch/nixos-toml-modules.nix { inherit lib; }).readAnyToml ./base-server.toml
+    config;
 
 in
 {
-  imports = [
-    ./base.nix
-  ];
+  inherit (cfgImports) imports;
 
-  options.vuizvui.user.profpatsch.server.sshPort = lib.mkOption {
-    description = "ssh port";
-    # TODO: replace with types.intBetween https://github.com/NixOS/nixpkgs/pull/27239
-    type = with lib.types; addCheck int (x: x >= 0 && x <= 65535);
-    default = 6879;
-  };
+  # TODO: cannot read options from pkgs because it would lead to an infinite recursion
+  # in the module system, since the pkgs passed into this module already requires all options.
+  options = ((import ../../pkgs/profpatsch/nixos-toml-modules.nix { inherit lib; }).readAnyToml ./base-server-options.toml).options
+    ;
 
-  config = {
+  config = cfgImports.config;
 
-    programs.mosh.enable = true;
 
-    services.openssh = {
-      enable = true;
-      listenAddresses = [ { addr = "0.0.0.0"; port = cfg.sshPort; } ];
-    };
+  # options.vuizvui.user.profpatsch.server.sshPort = lib.traceValSeqN 3 (lib.mkOption {
+  #   description = "ssh port";
+  #   # TODO: replace with types.intBetween https://github.com/NixOS/nixpkgs/pull/27239
+  #   type = with lib.types; addCheck int (x: x >= 0 && x <= 65535);
+  #   default = 6879;
+  # });
 
-    networking.firewall = {
-      enable = true;
-      allowPing = true;
-      allowedTCPPorts = [ cfg.sshPort ];
-    };
+  # config = {
 
-  };
+  #   programs.mosh.enable = true;
+
+  #   services.openssh = {
+  #     enable = true;
+  #     listenAddresses = [ { addr = "0.0.0.0"; port = cfg.sshPort; } ];
+  #   };
+
+  #   networking.firewall = {
+  #     enable = true;
+  #     allowPing = true;
+  #     allowedTCPPorts = [ cfg.sshPort ];
+  #   };
+
+  # };
 
 }
diff --git a/machines/profpatsch/base-server.toml b/machines/profpatsch/base-server.toml
new file mode 100644
index 00000000..b2b4a88a
--- /dev/null
+++ b/machines/profpatsch/base-server.toml
@@ -0,0 +1,26 @@
+[module]
+type = "nixos-config"
+version = "0.0.1"
+
+[[imports]]
+module = "./base.nix"
+
+[configVariables]
+server = [ "vuizvui", "user", "profpatsch", "server" ]
+
+[programs.mosh]
+enable = true
+
+[services.openssh]
+enable = true
+
+  [[services.openssh.listenAddresses]]
+  addr = "0.0.0.0"
+  port._configVariable.server = "sshPort"
+
+[networking.firewall]
+enable = true
+allowPing = true
+
+[[networking.firewall.allowedTCPPorts]]
+_configVariable.server = "sshPort"
diff --git a/pkgs/profpatsch/nixos-toml-modules.nix b/pkgs/profpatsch/nixos-toml-modules.nix
new file mode 100644
index 00000000..1424292a
--- /dev/null
+++ b/pkgs/profpatsch/nixos-toml-modules.nix
@@ -0,0 +1,119 @@
+{ lib }:
+
+let
+  tv = lib.traceValSeqN 3;
+  parseSemver = s:
+    let els = lib.splitString "." s;
+    in assert (lib.assertMsg (builtins.length els == 3) "semver must be of the form maj.min.patch, was ${s}"); {
+        major = lib.head els;
+        minor = lib.head (lib.tail els);
+        patch = lib.head (lib.tail (lib.tail els));
+      };
+  semver = major: minor: patch: { inherit major minor patch; };
+
+  nixos-options-0_0_1 = {toml, tomlFileDir}:
+    let
+      toOption = { description, type, default }: lib.mkOption {
+        inherit description default;
+        # TODO: this only works for types which are not functions obviously
+        type = lib.types.${type};
+      };
+
+      transformOptions =
+        let isOptionAttrset = a: a ? description && a ? type && a ? default;
+        in lib.mapAttrsRecursiveCond
+          # TODO: if an option set had these fields for any reason,
+          # we wouldn’t recognize it as an option.
+          # Maybe there should be an escape hatch in the toml description?
+          # We wouldn’t want the toml description to need an extra annotation for an option definition
+          (attrs: !isOptionAttrset attrs)
+          (_path: attrs:
+            if isOptionAttrset attrs
+            then toOption attrs
+            else lib.id);
+
+    in {
+      options = transformOptions toml;
+    };
+
+  nixos-config-0_0_1 = {toml, tomlFileDir } : config:
+    let
+      configVariables = toml.configVariables or {};
+      getSingleAttr = attrs: errWrongLength:
+        let keys = lib.attrNames attrs;
+            len = lib.length keys;
+        in
+        assert lib.assertMsg (len == 1)
+          (errWrongLength { num = len; keys = keys; });
+        let key = lib.head keys;
+        in { name = key; value = attrs.${key}; };
+      mapIfAttrs = attrKeys: f: attrs:
+        # lib.attrNames is always sorted like (lib.sort lib.lessThan)
+        if (lib.sort lib.lessThan attrKeys) == (lib.attrNames attrs)
+        then { mapped = true; val = f attrs; }
+        else { mapped = false; val = attrs; };
+
+      transformConfigVariable = { _configVariable }:
+        let var = getSingleAttr _configVariable
+          ({ num, keys }: "_configVariable must have exactly one attribute, the config variable to match, but you gave ${toString num}: ${lib.generators.toPretty {} keys}");
+        in
+        assert lib.assertMsg (configVariables ? ${var.name})
+          "_configVariable referenced ${var.name}, but you haven’t specified ${var.name} in [configVariables], only: ${lib.generators.toPretty {} toml.configVariables}";
+        let
+          initialPath = configVariables.${var.name};
+          lastPathElem = var.value;
+        in lib.getAttrFromPath (initialPath ++ [lastPathElem]) config;
+
+      relativeStringToPath = parentPath: relString: parentPath + ("/" + relString);
+      transformImports = map ({module}: relativeStringToPath tomlFileDir module);
+
+      # recurse a structure of attrsets and lists recursively.
+      # f takes the current value and returns a { recurse : bool, val : a}
+      # where if recurse is true the result of f (val) will be recursed into further
+      # and val is the returned value.
+      mapValRecursive = f: val:
+        let mapped = f val;
+        in if !mapped.recurse then mapped.val
+        else if lib.isAttrs mapped.val
+          then lib.mapAttrs (_: mapValRecursive f) mapped.val
+        else if lib.isList mapped.val
+          then map (mapValRecursive f) mapped.val
+        else mapped.val;
+
+      transformConfig =
+        { imports = transformImports toml.imports;
+          config =
+            mapValRecursive
+              (val:
+                 if lib.isAttrs val
+                 then
+                   let m = mapIfAttrs [ "_configVariable" ] transformConfigVariable val;
+                   in { recurse = !m.mapped; val = m.val; }
+                 else { recurse = true; inherit val; })
+              (removeAttrs toml [ "imports" "configVariables" ]);
+        };
+
+    in transformConfig;
+
+  readAnyToml = filePath:
+    let
+      toml = builtins.fromTOML (builtins.readFile filePath);
+      parsers = {
+        "nixos-options" = {
+          "0.0.1" = nixos-options-0_0_1;
+        };
+        "nixos-config" = {
+          "0.0.1" = nixos-config-0_0_1;
+        };
+      };
+       # TODO: these errors are gonna be horrible …
+    in parsers.${toml.module.type}.${toml.module.version} {
+        toml = (removeAttrs toml [ "module" ]);
+        tomlFileDir = builtins.dirOf filePath;
+    };
+
+in {
+  inherit
+    readAnyToml
+    ;
+}