a flake module to ease creating and managing multiple hosts in your nix flake.

refactor: improve error handling

+173 -157
+47 -39
flake-module.nix
··· 6 6 ... 7 7 }: 8 8 let 9 + inherit (builtins) concatLists attrNames; 9 10 inherit (lib.options) mkOption literalExpression; 10 11 inherit (lib) types; 11 12 ··· 17 18 18 19 cfg = config.easyHosts; 19 20 20 - # we really expect a list of paths but i want to accept lists of lists of lists and so on 21 - # since they will be flattened in the final function that applies the settings 22 - modulesType = types.listOf types.anything; 21 + mkBasicParams = name: { 22 + modules = mkOption { 23 + # we really expect a list of paths but i want to accept lists of lists of lists and so on 24 + # since they will be flattened in the final function that applies the settings 25 + type = types.listOf types.anything; 26 + default = [ ]; 27 + description = "${name} modules to be included in the system"; 28 + example = literalExpression '' 29 + [ ./hardware-configuration.nix ./networking.nix ] 30 + ''; 31 + }; 23 32 24 - specialArgsType = types.lazyAttrsOf types.raw; 33 + specialArgs = mkOption { 34 + type = types.lazyAttrsOf types.raw; 35 + default = { }; 36 + description = "${name} special arguments to be passed to the system"; 37 + example = literalExpression '' 38 + { foo = "bar"; } 39 + ''; 40 + }; 41 + }; 25 42 in 26 43 { 27 44 options = { ··· 40 57 example = literalExpression "aarch64-darwin"; 41 58 }; 42 59 43 - shared = { 44 - modules = mkOption { 45 - type = modulesType; 46 - default = [ ]; 47 - }; 48 - 49 - specialArgs = mkOption { 50 - type = specialArgsType; 51 - default = { }; 52 - }; 53 - }; 60 + shared = mkBasicParams "Shared"; 54 61 55 62 perClass = mkOption { 56 63 default = _: { ··· 59 66 }; 60 67 type = types.functionTo ( 61 68 types.submodule { 62 - options = { 63 - modules = mkOption { 64 - type = modulesType; 65 - default = [ ]; 66 - }; 67 - 68 - specialArgs = mkOption { 69 - type = specialArgsType; 70 - default = { }; 71 - }; 72 - }; 69 + options = mkBasicParams "Per class"; 73 70 } 74 71 ); 75 72 }; ··· 97 94 in 98 95 { 99 96 options = { 97 + # keep this up to date with 98 + # https://github.com/NixOS/nixpkgs/blob/75a43236cfd40adbc6138029557583eb77920afd/lib/systems/flake-systems.nix#L1 100 99 arch = mkOption { 101 - type = types.str; 100 + type = types.enum [ 101 + "x86_64" 102 + "aarch64" 103 + "armv6l" 104 + "armv7l" 105 + "i686" 106 + "powerpc64le" 107 + "riscv64" 108 + ]; 102 109 default = "x86_64"; 110 + example = "aarch64"; 103 111 }; 104 112 105 113 class = mkOption { 106 - type = types.str; 114 + type = types.enum (concatLists [ 115 + [ 116 + "nixos" 117 + "darwin" 118 + "iso" 119 + ] 120 + 121 + (attrNames cfg.additionalClasses) 122 + ]); 107 123 default = "nixos"; 124 + example = "darwin"; 108 125 }; 109 126 110 127 system = mkOption { 111 128 type = types.str; 112 129 default = constructSystem cfg self.class self.arch; 130 + example = "aarch64-darwin"; 113 131 }; 114 132 115 133 path = mkOption { ··· 122 140 type = types.bool; 123 141 default = false; 124 142 }; 125 - 126 - modules = mkOption { 127 - type = modulesType; 128 - default = [ ]; 129 - }; 130 - 131 - specialArgs = mkOption { 132 - type = specialArgsType; 133 - default = { }; 134 - }; 135 - }; 143 + } // (mkBasicParams name); 136 144 } 137 145 ) 138 146 );
+126 -118
lib.nix
··· 23 23 filterAttrs 24 24 ; 25 25 inherit (lib.modules) mkDefault evalModules; 26 + inherit (lib.trivial) mergeAttrs; 26 27 27 28 classToOS = class: if (class == "darwin") then "darwin" else "linux"; 28 29 classToND = class: if (class == "darwin") then "darwin" else "nixos"; ··· 90 91 specialArgs ? { }, 91 92 ... 92 93 }: 93 - withSystem system ( 94 - { self', inputs', ... }: 95 - let 96 - darwinInput = 97 - if (inputs ? darwin) then 98 - inputs.darwin 99 - else if (inputs ? nix-darwin) then 100 - inputs.nix-darwin 101 - else 102 - throw "cannot find nix-darwin input"; 94 + let 95 + darwinInput = inputs.darwin or inputs.nix-darwin or (throw "cannot find nix-darwin input"); 96 + nixpkgs = inputs.nixpkgs or (throw "cannot find nixpkgs input"); 103 97 104 - # create the modulesPath based on the system, we need 105 - modulesPath = 106 - if class == "darwin" then "${darwinInput}/modules" else "${inputs.nixpkgs}/nixos/modules"; 98 + # create the modulesPath based on the system, we need 99 + modulesPath = if class == "darwin" then "${darwinInput}/modules" else "${nixpkgs}/nixos/modules"; 107 100 108 - # we need to import the module list for our system 109 - # this is either the nixos modules list provided by nixpkgs 110 - # or the darwin modules list provided by nix darwin 111 - baseModules = import "${modulesPath}/module-list.nix"; 101 + # we need to import the module list for our system 102 + # this is either the nixos modules list provided by nixpkgs 103 + # or the darwin modules list provided by nix darwin 104 + baseModules = import "${modulesPath}/module-list.nix"; 112 105 113 - eval = evalModules { 114 - # we use recursiveUpdate such that users can "override" the specialArgs 115 - # 116 - # This should only be used for special arguments that need to be evaluated 117 - # when resolving module structure (like in imports). 118 - specialArgs = recursiveUpdate { 119 - inherit 120 - # these are normal args that people expect to be passed 121 - lib 122 - self # even though self is just the same as `inputs.self` 123 - inputs 106 + eval = evalModules { 107 + # we use recursiveUpdate such that users can "override" the specialArgs 108 + # 109 + # This should only be used for special arguments that need to be evaluated 110 + # when resolving module structure (like in imports). 111 + specialArgs = recursiveUpdate { 112 + inherit 113 + # these are normal args that people expect to be passed, 114 + # but we expect to be evaulated when resolving module structure 115 + inputs 124 116 125 - # these come from flake-parts 126 - self' 127 - inputs' 117 + # even though self is just the same as `inputs.self` 118 + # we still pass this as some people will use this 119 + self 128 120 129 - # we need to set this beacuse some modules require it sadly 130 - # you may also recall `modulesPath + /installer/scan/not-detected.nix` 131 - modulesPath 132 - ; 133 - } specialArgs; 121 + # we need to set this beacuse some modules require it sadly 122 + # you may also recall `modulesPath + /installer/scan/not-detected.nix` 123 + modulesPath 124 + ; 125 + } specialArgs; 134 126 135 - # A nominal type for modules. When set and non-null, this adds a check to 136 - # make sure that only compatible modules are imported. 137 - class = classToND class; 127 + # A nominal type for modules. When set and non-null, this adds a check to 128 + # make sure that only compatible modules are imported. 129 + class = classToND class; 138 130 139 - modules = flatten [ 140 - # bring in all of our base modules 141 - baseModules 131 + modules = flatten [ 132 + # bring in all of our base modules 133 + baseModules 142 134 143 - # import our host system paths 144 - ( 145 - if path != null then 146 - path 147 - else 148 - (filter pathExists [ 149 - # if the previous path does not exist then we will try to import some paths with some assumptions 150 - "${self}/hosts/${name}/default.nix" 151 - "${self}/systems/${name}/default.nix" 152 - ]) 153 - ) 135 + # import our host system paths 136 + ( 137 + if path != null then 138 + path 139 + else 140 + (filter pathExists [ 141 + # if the previous path does not exist then we will try to import some paths with some assumptions 142 + "${self}/hosts/${name}/default.nix" 143 + "${self}/systems/${name}/default.nix" 144 + ]) 145 + ) 154 146 155 - # get an installer profile from nixpkgs to base the Isos off of 156 - # this is useful because it makes things alot easier 157 - (optionals (class == "iso") [ 158 - "${inputs.nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix" 159 - ]) 147 + # get an installer profile from nixpkgs to base the Isos off of 148 + # this is useful because it makes things alot easier 149 + (optionals (class == "iso") [ 150 + "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix" 151 + ]) 160 152 161 - (singleton { 162 - # some modules to have these arguments, like documentation.nix 163 - # <https://github.com/NixOS/nixpkgs/blob/9692553cb583e8dca46b66ab76c0eb2ada1a4098/nixos/modules/misc/documentation.nix> 164 - _module.args = { 165 - inherit baseModules; 153 + # the next 3 singleton's are split up to make it easier to understand as they do things diffrent things 166 154 167 - # this should in the future be the modules that the user added without baseModules 168 - modules = [ ]; 155 + # recall `specialArgs` would take be prefered when resolving module structure 156 + # well this is how we do it use it for all args that don't need to rosolve module structure 157 + (singleton { 158 + _module.args = withSystem system ( 159 + { self', inputs', ... }: 160 + { 161 + inherit self' inputs'; 162 + } 163 + ); 164 + }) 169 165 170 - # TODO: remove in 25.05 171 - # https://github.com/NixOS/nixpkgs/blob/9692553cb583e8dca46b66ab76c0eb2ada1a4098/nixos/lib/eval-config.nix#L38 172 - extraModules = [ ]; 173 - }; 166 + # some modules to have these arguments, like documentation.nix 167 + # <https://github.com/NixOS/nixpkgs/blob/9692553cb583e8dca46b66ab76c0eb2ada1a4098/nixos/modules/misc/documentation.nix> 168 + (singleton { 169 + _module.args = { 170 + inherit baseModules; 174 171 175 - # we set the systems hostname based on the host value 176 - # which should be a string that is the hostname of the system 177 - networking.hostName = name; 172 + # this should in the future be the modules that the user added without baseModules 173 + modules = [ ]; 178 174 179 - nixpkgs = { 180 - # you can also do this as `inherit system;` with the normal `lib.nixosSystem` 181 - # however for evalModules this will not work, so we do this instead 182 - hostPlatform = mkDefault system; 175 + # TODO: remove in 25.05 176 + # https://github.com/NixOS/nixpkgs/blob/9692553cb583e8dca46b66ab76c0eb2ada1a4098/nixos/lib/eval-config.nix#L38 177 + extraModules = [ ]; 178 + }; 179 + }) 183 180 184 - # The path to the nixpkgs sources used to build the system. 185 - # This is automatically set up to be the store path of the nixpkgs flake used to build 186 - # the system if using lib.nixosSystem, and is otherwise null by default. 187 - # so that means that we should set it to our nixpkgs flake output path 188 - flake.source = inputs.nixpkgs.outPath; 189 - }; 190 - }) 181 + # here we make some basic assumptions about the system the person is using 182 + # like the system type and the hostname 183 + (singleton { 184 + # we set the systems hostname based on the host value 185 + # which should be a string that is the hostname of the system 186 + networking.hostName = mkDefault name; 191 187 192 - # if we are on darwin we need to import the nixpkgs source, its used in some 193 - # modules, if this is not set then you will get an error 194 - (optionals (class == "darwin") (singleton { 195 - # without supplying an upstream nixpkgs source, nix-darwin will not be able to build 196 - # and will complain and log an error demanding that you must set this value 197 - nixpkgs.source = mkDefault inputs.nixpkgs; 188 + nixpkgs = { 189 + # you can also do this as `inherit system;` with the normal `lib.nixosSystem` 190 + # however for evalModules this will not work, so we do this instead 191 + hostPlatform = mkDefault system; 198 192 199 - system = { 200 - # i don't quite know why this is set but upstream does it so i will too 201 - checks.verifyNixPath = false; 193 + # The path to the nixpkgs sources used to build the system. 194 + # This is automatically set up to be the store path of the nixpkgs flake used to build 195 + # the system if using lib.nixosSystem, and is otherwise null by default. 196 + # so that means that we should set it to our nixpkgs flake output path 197 + flake.source = nixpkgs.outPath; 198 + }; 199 + }) 202 200 203 - # we use these values to keep track of what upstream revision we are on, this also 204 - # prevents us from recreating docs for the same configuration build if nothing has changed 205 - darwinVersionSuffix = ".${darwinInput.shortRev or darwinInput.dirtyShortRev or "dirty"}"; 206 - darwinRevision = darwinInput.rev or darwinInput.dirtyRev or "dirty"; 207 - }; 208 - })) 201 + # if we are on darwin we need to import the nixpkgs source, its used in some 202 + # modules, if this is not set then you will get an error 203 + (optionals (class == "darwin") (singleton { 204 + # without supplying an upstream nixpkgs source, nix-darwin will not be able to build 205 + # and will complain and log an error demanding that you must set this value 206 + nixpkgs.source = mkDefault nixpkgs; 207 + 208 + system = { 209 + # i don't quite know why this is set but upstream does it so i will too 210 + checks.verifyNixPath = false; 211 + 212 + # we use these values to keep track of what upstream revision we are on, this also 213 + # prevents us from recreating docs for the same configuration build if nothing has changed 214 + darwinVersionSuffix = ".${darwinInput.shortRev or darwinInput.dirtyShortRev or "dirty"}"; 215 + darwinRevision = darwinInput.rev or darwinInput.dirtyRev or "dirty"; 216 + }; 217 + })) 209 218 210 - # import any additional modules that the user has provided 211 - modules 212 - ]; 219 + # import any additional modules that the user has provided 220 + modules 221 + ]; 222 + }; 223 + in 224 + if ((classToND class) == "nixos") then 225 + { nixosConfigurations.${name} = eval; } 226 + else 227 + { 228 + darwinConfigurations.${name} = eval // { 229 + system = eval.config.system.build.toplevel; 213 230 }; 214 - in 215 - if ((classToND class) == "nixos") then 216 - { nixosConfigurations.${name} = eval; } 217 - else 218 - { 219 - darwinConfigurations.${name} = eval // { 220 - system = eval.config.system.build.toplevel; 221 - }; 222 - } 223 - ); 231 + }; 224 232 225 233 foldAttrsReccursive = foldl' (acc: attrs: recursiveUpdate acc attrs) { }; 226 234 227 235 mkHosts = 228 236 easyHostsConfig: 229 - foldAttrs (host: acc: host // acc) { } ( 237 + foldAttrs mergeAttrs { } ( 230 238 attrValues ( 231 239 mapAttrs ( 232 240 name: hostConfig: ··· 239 247 240 248 # merging is handled later 241 249 modules = [ 242 - (hostConfig.modules or [ ]) 243 - (easyHostsConfig.shared.modules or [ ]) 244 - ((easyHostsConfig.perClass hostConfig.class).modules or [ ]) 250 + hostConfig.modules 251 + easyHostsConfig.shared.modules 252 + (easyHostsConfig.perClass hostConfig.class).modules 245 253 ]; 246 254 247 255 specialArgs = foldAttrsReccursive [ 248 - (hostConfig.specialArgs or { }) 249 - (easyHostsConfig.shared.specialArgs or { }) 250 - ((easyHostsConfig.perClass hostConfig.class).specialArgs or { }) 256 + hostConfig.specialArgs 257 + easyHostsConfig.shared.specialArgs 258 + (easyHostsConfig.perClass hostConfig.class).specialArgs 251 259 ]; 252 260 } 253 261 ) easyHostsConfig.hosts