git.delta.rocks / jrsonnet / refs/commits / d9fb30d36ead

difftreelog

feat lenient nixosModules type

Yaroslav Bolyukin2024-07-11parent: #e9ac172.patch.diff
in: trunk

8 files changed

modifiedREADME.adocdiffbeforeafterboth
--- a/README.adoc
+++ b/README.adoc
@@ -63,18 +63,14 @@
       # nixosModules section of fleet config declares modules, which are used for all configured nixos hosts.
       nixosModules = [
         lanzaboote.nixosModules.lanzaboote
-        ({
-          config,
-          lib,
-          ...
-        }: {
+        {
           # Make `nix shell nixpkgs#thing` use the same nixpkgs, as used to build the system.
           nix.registry.nixpkgs = {
             from = { id = "nixpkgs"; type = "indirect"; };
             flake = nixpkgs;
             exact = false;
           };
-        })
+        }
       ];
 
       # Those modules are used to configure all the machines in cluster at the same time, good example of global modules
@@ -97,12 +93,12 @@
           ./controlplane-1/hardware-configuration.nix
           ./controlplane-1/configuration.nix
           # Configuration may also be specified inline, as in any nixos config.
-          ({...}: {
+          {
             services.ray = {
               gpus = 4;
               cpus = 128;
             };
-          })
+          }
         ];
       };
     };
modifiedflake.nixdiffbeforeafterboth
--- a/flake.nix
+++ b/flake.nix
@@ -16,19 +16,18 @@
       inputs.nixpkgs.follows = "nixpkgs";
     };
   };
-  outputs = {
+  outputs = inputs @ {
     self,
-    rust-overlay,
     flake-parts,
-    nixpkgs,
-    nixpkgs-stable-for-tests,
     crane,
+    ...
   }:
     flake-parts.lib.mkFlake {
-      # Not passing inputs through inputs for better visibility.
-      inputs = {};
+      inherit inputs;
     } {
-      flake = {
+      flake = let
+        inherit (inputs.nixpkgs.lib) mapAttrs;
+      in {
         lib = import ./lib {
           fleetPkgsForPkgs = pkgs:
             import ./pkgs {
@@ -45,11 +44,11 @@
             '';
             inventory = output: {
               children =
-                builtins.mapAttrs (configName: cluster: {
+                mapAttrs (configName: cluster: {
                   what = "fleet cluster configuration";
 
                   children =
-                    builtins.mapAttrs (hostName: host: {
+                    mapAttrs (hostName: host: {
                       what = "host [${host.system}]";
                     })
                     cluster.config.hosts;
@@ -70,19 +69,20 @@
         pkgs,
         ...
       }: let
+        inherit (lib) mapAttrs' elem;
         # Can also be built for darwin, through it is not usual to deploy nixos systems from macos machines.
         # I have no hardware for such testing, thus only adding machines I actually have and use.
         #
         # It is not possible to deploy any host from armv6/armv7 hardware, and I don't think it even makes sense.
         deployerSystems = ["aarch64-linux" "x86_64-linux"];
-        deployerSystem = builtins.elem system deployerSystems;
+        deployerSystem = elem system deployerSystems;
         lib = pkgs.lib;
         rust = pkgs.rust-bin.fromRustupToolchainFile ./rust-toolchain.toml;
         craneLib = (crane.mkLib pkgs).overrideToolchain rust;
       in {
-        _module.args.pkgs = import nixpkgs {
+        _module.args.pkgs = import inputs.nixpkgs {
           inherit system;
-          overlays = [(rust-overlay.overlays.default)];
+          overlays = [(inputs.rust-overlay.overlays.default)];
         };
         # Reference fleet package should be built with nightly rust, specified in rust-toolchain.toml.
         packages = lib.mkIf deployerSystem (let
@@ -116,14 +116,14 @@
         checks = let
           packages = import ./pkgs {
             inherit (pkgs) callPackage;
-            craneLib = crane.mkLib (import nixpkgs {inherit system;});
+            craneLib = crane.mkLib pkgs;
           };
           packages-with-nixpkgs-stable = import ./pkgs {
             inherit (pkgs) callPackage;
-            craneLib = crane.mkLib (import nixpkgs-stable-for-tests {inherit system;});
+            craneLib = crane.mkLib (import inputs.nixpkgs-stable-for-tests {inherit system;});
           };
           prefixAttrs = prefix: attrs:
-            nixpkgs.lib.attrsets.mapAttrs' (name: value: {
+            mapAttrs' (name: value: {
               name = "${prefix}${name}";
               value = value.overrideAttrs (prev: {
                 pname = "${prefix}${prev.pname}";
modifiedlib/fleetLib.nixdiffbeforeafterboth
--- a/lib/fleetLib.nix
+++ b/lib/fleetLib.nix
@@ -2,8 +2,11 @@
 {
   nixpkgs,
   hostNames,
-}:
-with nixpkgs.lib; rec {
+}: let
+  inherit (nixpkgs) lib;
+  inherit (lib) listToAttrs remove unique crossLists sort elemAt mkOptionType mkOverride optionalString;
+  inherit (lib.types) listOf coercedTo oneOf submodule;
+in rec {
   hostsToAttrs = f:
     listToAttrs (
       map (name: {
@@ -34,6 +37,27 @@
     then "${this}-${other}"
     else "${other}-${this}";
 
+  types = rec {
+    anyModule = mkOptionType {
+      name = "submodule";
+      inherit (submodule {}) check;
+      merge = lib.options.mergeOneOption;
+      description = "Nixos module";
+    };
+    listOfAnyModuleStrict =
+      listOf anyModule;
+    listOfAnyModule =
+      coercedTo (oneOf [listOfAnyModuleStrict anyModule]) (
+        v:
+          if builtins.isAttrs v
+          then [v]
+          else if builtins.isFunction v
+          then [v]
+          else v
+      )
+      listOfAnyModuleStrict;
+  };
+
   # mkDefault = mkOverride 1000
   # For places, where fleet knows better than nixpkgs defaults.
   mkFleetDefault = mkOverride 999;
modifiedmodules/fleet/assertions.nixdiffbeforeafterboth
--- a/modules/fleet/assertions.nix
+++ b/modules/fleet/assertions.nix
@@ -1,8 +1,10 @@
-{lib, ...}:
-with lib; {
+{lib, ...}: let
+  inherit (lib) mkOption;
+  inherit (lib.types) listOf unspecified str;
+in {
   options = {
     assertions = mkOption {
-      type = types.listOf types.unspecified;
+      type = listOf unspecified;
       internal = true;
       default = [];
       example = [
@@ -21,7 +23,7 @@
     warnings = mkOption {
       internal = true;
       default = [];
-      type = types.listOf types.str;
+      type = listOf str;
       example = ["The `foo' service is deprecated and will go away soon!"];
       description = ''
         This option allows modules to show warnings to users during
modifiedmodules/fleet/meta.nixdiffbeforeafterboth
--- a/modules/fleet/meta.nix
+++ b/modules/fleet/meta.nix
@@ -4,58 +4,53 @@
   config,
   nixpkgs,
   ...
-}:
-with lib;
-with fleetLib; let
-  hostModule = with types;
-    {...} @ hostConfig: let
-      hostName = hostConfig.config._module.args.name;
-    in {
-      options = {
-        nixosModules = mkOption {
-          type = listOf (mkOptionType {
-            name = "submodule";
-            inherit (submodule {}) check;
-            merge = lib.options.mergeOneOption;
-            description = "Nixos module";
-          });
-          description = "List of nixos modules";
-          default = [];
-        };
-        system = mkOption {
-          type = str;
-          description = "Type of system";
-        };
-        encryptionKey = mkOption {
-          type = str;
-          description = "Encryption key";
-        };
-        nixosSystem = mkOption {
-          type = unspecified;
-          description = "Nixos configuration";
-        };
-        nixpkgs = mkOption {
-          type = unspecified;
-          description = "Nixpkgs override";
-          default = nixpkgs;
-        };
+}: let
+  inherit (fleetLib) hostsToAttrs mkFleetGeneratorDefault;
+  inherit (fleetLib.types) listOfAnyModule;
+  inherit (lib) mkOption mkOptionType;
+  inherit (lib.types) str unspecified attrsOf listOf submodule;
+  hostModule = {...} @ hostConfig: let
+    hostName = hostConfig.config._module.args.name;
+  in {
+    options = {
+      nixosModules = mkOption {
+        # Not too strict, but nixos module system will fix everything.
+        type =
+          listOfAnyModule;
+
+        description = "List of nixos modules";
+        default = [];
+      };
+      system = mkOption {
+        type = str;
+        description = "Type of system";
+      };
+      encryptionKey = mkOption {
+        type = str;
+        description = "Encryption key";
+      };
+      nixosSystem = mkOption {
+        type = unspecified;
+        description = "Nixos configuration";
+      };
+      nixpkgs = mkOption {
+        type = unspecified;
+        description = "Nixpkgs override";
+        default = nixpkgs;
       };
-      config = {
-        nixosSystem = hostConfig.config.nixpkgs.lib.nixosSystem {
-          inherit (hostConfig.config) system;
-          modules = hostConfig.config.nixosModules;
-          specialArgs = {
-            inherit fleetLib;
-            fleet = hostsToAttrs (host: config.hosts.${host}.nixosSystem.config);
-          };
+    };
+    config = {
+      nixosSystem = hostConfig.config.nixpkgs.lib.nixosSystem {
+        inherit (hostConfig.config) system;
+        modules = hostConfig.config.nixosModules;
+        specialArgs = {
+          inherit fleetLib;
+          fleet = hostsToAttrs (host: config.hosts.${host}.nixosSystem.config);
         };
-        nixosModules = [
-          ({...}: {
-            networking.hostName = mkFleetGeneratorDefault hostName;
-          })
-        ];
       };
+      nixosModules.networking.hostName = mkFleetGeneratorDefault hostName;
     };
+  };
   overlayType = mkOptionType {
     name = "nixpkgs-overlay";
     description = "nixpkgs overlay";
@@ -63,19 +58,14 @@
     merge = lib.mergeOneOption;
   };
 in {
-  options = with types; {
+  options = {
     hosts = mkOption {
       type = attrsOf (submodule hostModule);
       default = {};
       description = "Configurations of individual hosts";
     };
     nixosModules = mkOption {
-      type = listOf (mkOptionType {
-        name = "submodule";
-        inherit (submodule {}) check;
-        merge = lib.options.mergeOneOption;
-        description = "Nixos modules";
-      });
+      type = listOfAnyModule;
       description = "Modules, which should be added to every system";
       default = [];
     };
@@ -89,9 +79,9 @@
       nixosModules =
         config.nixosModules
         ++ [
-          ({...}: {
+          {
             nixpkgs.overlays = config.overlays;
-          })
+          }
         ];
     });
     nixosModules = import ../../nixos/modules/module-list.nix;
modifiedmodules/fleet/secrets.nixdiffbeforeafterboth
--- a/modules/fleet/secrets.nix
+++ b/modules/fleet/secrets.nix
@@ -3,11 +3,13 @@
   fleetLib,
   config,
   ...
-}:
-with lib;
-with fleetLib; let
-  sharedSecret = with types; ({config, ...}: {
-    freeformType = types.lazyAttrsOf unspecified;
+}: let
+  inherit (fleetLib) hostsToAttrs;
+  inherit (lib) mkOption mapAttrsToList mapAttrs filterAttrs concatStringsSep;
+  inherit (lib.types) lazyAttrsOf unspecified nullOr listOf str bool attrsOf submodule;
+
+  sharedSecret = {config, ...}: {
+    freeformType = lazyAttrsOf unspecified;
     options = {
       expectedOwners = mkOption {
         type = nullOr (listOf str);
@@ -66,9 +68,9 @@
         default = [];
       };
     };
-  });
-  hostSecret = with types; {
-    freeformType = types.lazyAttrsOf unspecified;
+  };
+  hostSecret = {
+    freeformType = lazyAttrsOf unspecified;
     options = {
       createdAt = mkOption {
         type = nullOr str;
@@ -81,7 +83,7 @@
     };
   };
 in {
-  options = with types; {
+  options = {
     version = mkOption {
       type = str;
       default = "";
@@ -128,11 +130,7 @@
     });
     # TODO: Should this attribute be moved to `nixpkgs.overlays`?
     overlays = [
-      (final: prev: let
-        lib = final.lib;
-        inherit (lib) strings;
-        inherit (strings) concatStringsSep;
-      in {
+      (final: prev: {
         mkSecretGenerators = {recipients}: rec {
           # TODO: Merge both generators to one with consistent options syntax?
           # Impure generator is built on local machine, then built closure is copied to remote machine,
modifiednixos/meta.nixdiffbeforeafterboth
--- a/nixos/meta.nix
+++ b/nixos/meta.nix
@@ -2,11 +2,13 @@
   lib,
   pkgs,
   ...
-}:
-with lib; {
-  options = with types; {
+}: let
+  inherit (lib) mkOption;
+  inherit (lib.types) listOf str submodule;
+in {
+  options = {
     nixpkgs.resolvedPkgs = mkOption {
-      type = types.pkgs // {description = "nixpkgs.pkgs";};
+      type = lib.types.pkgs // {description = "nixpkgs.pkgs";};
       description = "Value of pkgs";
     };
     tags = mkOption {
@@ -30,9 +32,6 @@
         };
       };
       description = "Network definition of host";
-    };
-    buildTarget = mkOption {
-      type = enum ["toplevel" "sd-image" "installation-cd"];
     };
   };
   config = {
modifiednixos/secrets.nixdiffbeforeafterboth
before · nixos/secrets.nix
1{2  lib,3  config,4  pkgs,5  ...6}:7with lib; let8  inherit (lib.strings) hasPrefix removePrefix;9  plaintextPrefix = "<PLAINTEXT>";10  plaintextNewlinePrefix = "<PLAINTEXT-NL>";1112  sysConfig = config;13  secretPartType = secretName:14    types.submodule ({config, ...}: {15      options = with types; {16        raw = mkOption {17          description = "Secret in fleet-specific undocumented format, do not use. Import from fleet.nix";18          internal = true;19        };20        hash = mkOption {21          type = str;22          description = "Hash of secret in encoded format";23        };24        path = mkOption {25          type = str;26          description = "Path to secret part, incorporating data hash (thus it will be updated on secret change)";27        };28        stablePath = mkOption {29          type = str;30          description = "Path to secret part, incorporating data hash (thus it will be updated on secret change)";31        };32        data = mkOption {33          type = str;34          description = "Secret public data (only available for plaintext)";35        };36      };37      config = let38        partName = config._module.args.name;39      in {40        hash = mkOptionDefault (builtins.hashString "sha1" config.raw);41        data = mkOptionDefault (42          if hasPrefix plaintextPrefix config.raw43          then removePrefix plaintextPrefix config.raw44          else if hasPrefix plaintextNewlinePrefix config.raw45          then removePrefix plaintextNewlinePrefix config.raw46          else throw "secret.part.data attribute only works for public plaintext secret parts, got ${config.raw}"47        );48        path = mkOptionDefault "/run/secrets/${secretName}/${config.hash}-${partName}";49        stablePath = mkOptionDefault "/run/secrets/${secretName}/${partName}";50      };51    });52  secretType = types.submodule ({config, ...}: let53    secretName = config._module.args.name;54  in {55    freeformType = types.lazyAttrsOf (secretPartType secretName);56    options = with types; {57      shared = mkOption {58        description = "Is this secret owned by this machine, or propagated from shared secrets";59        default = false;60      };61      expectedOwners = mkOption {62        type = nullOr unspecified;63        default = null;64        internal = true;65      };6667      generator = mkOption {68        type = nullOr unspecified;69        description = "Derivation to evaluate for secret generation";70        default = null;71      };72      mode = mkOption {73        type = str;74        description = "Secret mode";75        default = "0440";76      };77      owner = mkOption {78        type = str;79        description = "Owner of the secret";80        default = "root";81      };82      group = mkOption {83        type = str;84        description = "Group of the secret";85        default = sysConfig.users.users.${config.owner}.group;86      };87    };88  });89  processPart = part: {90    inherit (part) raw path stablePath;91  };92  processSecret = secret:93    {94      inherit (secret) group mode owner;95    }96    // (mapAttrs (_: processPart) (removeAttrs secret [97      "shared"98      "generator"99      "mode"100      "group"101      "owner"102103      # FIXME: Some of those removed attributes shouldn't be here, but there is some error in passing shared secrets from fleet to nixos.104      "expectedOwners"105    ]));106  secretsFile = pkgs.writeTextFile {107    name = "secrets.json";108    text =109      builtins.toJSON (mapAttrs (_: processSecret)110        config.secrets);111  };112in {113  options = {114    secrets = mkOption {115      type = types.attrsOf secretType;116      default = {};117      description = "Host-local secrets";118    };119  };120  config = {121    environment.systemPackages = [pkgs.fleet-install-secrets];122    system.activationScripts.decryptSecrets =123      stringAfter (124        [125          # secrets are owned by user/group, thus we need to refer to those126          "users"127          "groups"128          "specialfs"129        ]130        # nixos-impermanence compatibility: secrets are encrypted by host-key,131        # but with impermanence we expect that the host-key is installed by132        # persist-file activation script.133        ++ (lib.optional (config.system.activationScripts ? "persist-files") "persist-files")134      ) ''135        1>&2 echo "setting up secrets"136        ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}137      '';138  };139}
after · nixos/secrets.nix
1{2  lib,3  config,4  pkgs,5  ...6}: let7  inherit (lib.strings) hasPrefix removePrefix;8  inherit (lib) mkOption mkOptionDefault mapAttrs stringAfter;9  inherit (lib.types) submodule str attrsOf nullOr unspecified lazyAttrsOf;10  plaintextPrefix = "<PLAINTEXT>";11  plaintextNewlinePrefix = "<PLAINTEXT-NL>";1213  sysConfig = config;14  secretPartType = secretName:15    submodule ({config, ...}: {16      options = {17        raw = mkOption {18          description = "Secret in fleet-specific undocumented format, do not use. Import from fleet.nix";19          internal = true;20        };21        hash = mkOption {22          type = str;23          description = "Hash of secret in encoded format";24        };25        path = mkOption {26          type = str;27          description = "Path to secret part, incorporating data hash (thus it will be updated on secret change)";28        };29        stablePath = mkOption {30          type = str;31          description = "Path to secret part, incorporating data hash (thus it will be updated on secret change)";32        };33        data = mkOption {34          type = str;35          description = "Secret public data (only available for plaintext)";36        };37      };38      config = let39        partName = config._module.args.name;40      in {41        hash = mkOptionDefault (builtins.hashString "sha1" config.raw);42        data = mkOptionDefault (43          if hasPrefix plaintextPrefix config.raw44          then removePrefix plaintextPrefix config.raw45          else if hasPrefix plaintextNewlinePrefix config.raw46          then removePrefix plaintextNewlinePrefix config.raw47          else throw "secret.part.data attribute only works for public plaintext secret parts, got ${config.raw}"48        );49        path = mkOptionDefault "/run/secrets/${secretName}/${config.hash}-${partName}";50        stablePath = mkOptionDefault "/run/secrets/${secretName}/${partName}";51      };52    });53  secretType = submodule ({config, ...}: let54    secretName = config._module.args.name;55  in {56    freeformType = lazyAttrsOf (secretPartType secretName);57    options = {58      shared = mkOption {59        description = "Is this secret owned by this machine, or propagated from shared secrets";60        default = false;61      };62      expectedOwners = mkOption {63        type = nullOr unspecified;64        default = null;65        internal = true;66      };6768      generator = mkOption {69        type = nullOr unspecified;70        description = "Derivation to evaluate for secret generation";71        default = null;72      };73      mode = mkOption {74        type = str;75        description = "Secret mode";76        default = "0440";77      };78      owner = mkOption {79        type = str;80        description = "Owner of the secret";81        default = "root";82      };83      group = mkOption {84        type = str;85        description = "Group of the secret";86        default = sysConfig.users.users.${config.owner}.group;87      };88    };89  });90  processPart = part: {91    inherit (part) raw path stablePath;92  };93  processSecret = secret:94    {95      inherit (secret) group mode owner;96    }97    // (mapAttrs (_: processPart) (removeAttrs secret [98      "shared"99      "generator"100      "mode"101      "group"102      "owner"103104      # FIXME: Some of those removed attributes shouldn't be here, but there is some error in passing shared secrets from fleet to nixos.105      "expectedOwners"106    ]));107  secretsFile = pkgs.writeTextFile {108    name = "secrets.json";109    text =110      builtins.toJSON (mapAttrs (_: processSecret)111        config.secrets);112  };113in {114  options = {115    secrets = mkOption {116      type = attrsOf secretType;117      default = {};118      description = "Host-local secrets";119    };120  };121  config = {122    environment.systemPackages = [pkgs.fleet-install-secrets];123    system.activationScripts.decryptSecrets =124      stringAfter (125        [126          # secrets are owned by user/group, thus we need to refer to those127          "users"128          "groups"129          "specialfs"130        ]131        # nixos-impermanence compatibility: secrets are encrypted by host-key,132        # but with impermanence we expect that the host-key is installed by133        # persist-file activation script.134        ++ (lib.optional (config.system.activationScripts ? "persist-files") "persist-files")135      ) ''136        1>&2 echo "setting up secrets"137        ${pkgs.fleet-install-secrets}/bin/fleet-install-secrets install ${secretsFile}138      '';139  };140}