a flake module to ease creating and managing multiple hosts in your nix flake.
at main 12 kB view raw
1{ 2 lib, 3 inputs, 4 withSystem, 5}: 6let 7 inherit (inputs) self; 8 9 inherit (builtins) readDir; 10 inherit (lib) 11 elemAt 12 filter 13 pathExists 14 foldAttrs 15 pipe 16 optionals 17 singleton 18 concatLists 19 recursiveUpdate 20 attrValues 21 mapAttrs 22 filterAttrs 23 mkDefault 24 mergeAttrs 25 ; 26 27 /** 28 classToOS 29 30 # Arguments 31 32 - [class]: The class of the system. This is usually one of `nixos`, `darwin`, or `iso`. 33 34 # Type 35 36 ``` 37 classToOS :: String -> String 38 ``` 39 40 # Example 41 42 ```nix 43 classToOS "darwin" 44 => "darwin" 45 ``` 46 47 ```nix 48 classToOS "nixos" 49 => "linux" 50 ``` 51 */ 52 classToOS = class: if (class == "darwin") then "darwin" else "linux"; 53 54 /** 55 classToND 56 57 # Arguments 58 59 - [class]: The class of the system. This is usually one of `nixos`, `darwin`, or `iso`. 60 61 # Type 62 63 ``` 64 classToND :: String -> String 65 ``` 66 67 # Example 68 69 ```nix 70 classToND "darwin" 71 => "darwin" 72 ``` 73 74 ```nix 75 classToND "iso" 76 => "nixos" 77 ``` 78 */ 79 classToND = class: if (class == "darwin") then "darwin" else "nixos"; 80 81 /** 82 redefineClass 83 84 # Arguments 85 86 - [additionalClasses]: A set of additional classes to be used for the system. 87 - [class]: The class of the system. This is usually one of `nixos`, `darwin`, or `iso`. 88 89 # Type 90 91 ``` 92 redefineClass :: AttrSet -> String -> String 93 ``` 94 95 # Example 96 97 ```nix 98 redefineClass { rpi = "nixos"; } "linux" 99 => "nixos" 100 ``` 101 102 ```nix 103 redefineClass { rpi = "nixos"; } "rpi" 104 => "nixos" 105 ``` 106 */ 107 redefineClass = 108 additionalClasses: class: ({ linux = "nixos"; } // additionalClasses).${class} or class; 109 110 /** 111 constructSystem 112 113 # Arguments 114 115 - [additionalClasses]: A set of additional classes to be used for the system. 116 - [arch]: The architecture of the system. This is usually one of `x86_64`, `aarch64`, or `armv7l`. 117 - [class]: The class of the system. This is usually one of `nixos`, `darwin`, or `iso`. 118 119 # Type 120 121 ``` 122 constructSystem :: AttrSet -> String -> String -> String 123 ``` 124 125 # Example 126 127 ```nix 128 constructSystem { rpi = "nixos"; } "x86_64" "rpi" 129 => "x86_64-linux" 130 ``` 131 132 ```nix 133 constructSystem { rpi = "nixos"; } "x86_64" "linux" 134 => "x86_64-linux" 135 ``` 136 */ 137 constructSystem = 138 additionalClasses: arch: class: 139 let 140 class' = redefineClass additionalClasses class; 141 os = classToOS class'; 142 in 143 "${arch}-${os}"; 144 145 /** 146 splitSystem 147 148 # Arguments 149 150 - [system]: The system to be split. This is usually one of `x86_64-linux`, `aarch64-darwin`, or `armv7l-linux`. 151 152 # Type 153 154 ``` 155 splitSystem :: String -> AttrSet 156 ``` 157 158 # Example 159 160 ```nix 161 splitSystem "x86_64-linux" 162 => { arch = "x86_64"; class = "linux"; } 163 ``` 164 165 ```nix 166 splitSystem "aarch64-darwin" 167 => { arch = "aarch64"; class = "darwin"; } 168 ``` 169 */ 170 splitSystem = 171 system: 172 let 173 sp = builtins.split "-" system; 174 arch = elemAt sp 0; 175 class = elemAt sp 2; 176 in 177 { 178 inherit arch class; 179 }; 180 181 /** 182 mkHost is a function that uses withSystem to give us inputs' and self' 183 it also assumes the the system type either nixos or darwin and uses the appropriate 184 185 # Type 186 187 ``` 188 mkHost :: AttrSet -> AttrSet 189 ``` 190 191 # Example 192 193 ```nix 194 mkHost { 195 name = "myhost"; 196 path = "/path/to/host"; 197 system = "x86_64-linux"; 198 class = "nixos"; 199 modules = [ ./module.nix ]; 200 specialArgs = { foo = "bar"; }; 201 } 202 ``` 203 */ 204 mkHost = 205 { 206 name, 207 path, 208 # by the time we receive the argument here it can only be one of 209 # nixos, darwin, or iso. The redefineClass function should be used prior 210 # nixos, darwin. The redefineClass function should be used prior 211 class, 212 system, 213 nixpkgs, 214 nix-darwin, 215 modules ? [ ], 216 specialArgs ? { }, 217 ... 218 }: 219 let 220 evalHost = if class == "darwin" then nix-darwin.lib.darwinSystem else nixpkgs.lib.nixosSystem; 221 in 222 evalHost { 223 # we use recursiveUpdate such that users can "override" the specialArgs 224 # 225 # This should only be used for special arguments that need to be evaluated 226 # when resolving module structure (like in imports). 227 specialArgs = recursiveUpdate { 228 inherit 229 # these are normal args that people expect to be passed, 230 # but we expect to be evaluated when resolving module structure 231 inputs 232 233 # even though self is just the same as `inputs.self` 234 # we still pass this as some people will use this 235 self 236 ; 237 } specialArgs; 238 239 modules = concatLists [ 240 # import our host system paths 241 ( 242 if path != null then 243 [ path ] 244 else 245 (filter pathExists [ 246 # if the previous path does not exist then we will try to import some paths with some assumptions 247 "${self}/hosts/${name}/default.nix" 248 "${self}/systems/${name}/default.nix" 249 ]) 250 ) 251 252 # get an installer profile from nixpkgs to base the Isos off of 253 # this is useful because it makes things alot easier 254 (optionals (class == "iso") [ 255 "${nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix" 256 ]) 257 258 # the next 3 singleton's are split up to make it easier to understand as they do things different things 259 260 # recall `specialArgs` would take be preferred when resolving module structure 261 # well this is how we do it use it for all args that don't need to rosolve module structure 262 (singleton { 263 key = "easy-hosts#specialArgs"; 264 _file = "${__curPos.file}"; 265 266 _module.args = withSystem system ( 267 { self', inputs', ... }: 268 { 269 inherit self' inputs'; 270 } 271 ); 272 }) 273 274 # here we make some basic assumptions about the system the person is using 275 # like the system type and the hostname 276 (singleton { 277 key = "easy-hosts#hostname"; 278 _file = "${__curPos.file}"; 279 280 # we set the systems hostname based on the host value 281 # which should be a string that is the hostname of the system 282 networking.hostName = mkDefault name; 283 }) 284 285 (singleton { 286 key = "easy-hosts#nixpkgs"; 287 _file = "${__curPos.file}"; 288 289 nixpkgs = { 290 # you can also do this as `inherit system;` with the normal `lib.nixosSystem` 291 # however for evalModules this will not work, so we do this instead 292 hostPlatform = mkDefault system; 293 294 # The path to the nixpkgs sources used to build the system. 295 # This is automatically set up to be the store path of the nixpkgs flake used to build 296 # the system if using lib.nixosSystem, and is otherwise null by default. 297 # so that means that we should set it to our nixpkgs flake output path 298 flake.source = nixpkgs.outPath; 299 }; 300 }) 301 302 # if we are on darwin we need to import the nixpkgs source, its used in some 303 # modules, if this is not set then you will get an error 304 (optionals (class == "darwin") (singleton { 305 key = "easy-hosts#nixpkgs-darwin"; 306 _file = "${__curPos.file}"; 307 308 # without supplying an upstream nixpkgs source, nix-darwin will not be able to build 309 # and will complain and log an error demanding that you must set this value 310 nixpkgs.source = mkDefault nixpkgs; 311 })) 312 313 # import any additional modules that the user has provided 314 modules 315 ]; 316 }; 317 318 /** 319 toHostOutput 320 321 # Arguments 322 323 - [name]: The name of the host. 324 - [class]: The class of the host. This is usually one of `nixos`, `darwin`, or `iso`. 325 - [output]: The output of the host. 326 327 # Type 328 329 ``` 330 toHostOutput :: AttrSet -> AttrSet 331 ``` 332 333 # Example 334 335 ```nix 336 toHostOutput { 337 name = "myhost"; 338 class = "nixos"; 339 output = { }; 340 } 341 => { nixosConfigurations.myhost = { }; } 342 ``` 343 */ 344 toHostOutput = 345 { 346 name, 347 class, 348 output, 349 }: 350 if ((classToND class) == "nixos") then 351 { nixosConfigurations.${name} = output; } 352 else 353 { darwinConfigurations.${name} = output; }; 354 355 foldAttrsMerge = foldAttrs mergeAttrs { }; 356 foldAttrsMergeRec = foldAttrs recursiveUpdate { }; 357 358 /** 359 mkHosts is a function that takes a set of hosts and returns a set of host outputs. 360 361 # Arguments 362 363 - [easyHostsConfig]: The easy-hosts configuration. 364 365 # Type 366 367 ``` 368 mkHosts :: AttrSet -> AttrSet 369 ``` 370 */ 371 mkHosts = 372 easyHostsConfig: 373 pipe easyHostsConfig.hosts [ 374 (mapAttrs ( 375 name: hostConfig: 376 let 377 # memoize the class and perClass values so we don't have to recompute them 378 sources = lib.flatten [ 379 # modules and specialArgs from different sources combined 380 easyHostsConfig.shared 381 hostConfig 382 (builtins.map easyHostsConfig.perTag hostConfig.tags) 383 (easyHostsConfig.perClass hostConfig.class) 384 (easyHostsConfig.perArch hostConfig.arch) 385 ]; 386 class = redefineClass easyHostsConfig.additionalClasses hostConfig.class; 387 in 388 toHostOutput { 389 inherit name class; 390 391 output = mkHost { 392 inherit name class; 393 394 inherit (hostConfig) 395 system 396 path 397 nixpkgs 398 nix-darwin 399 ; 400 401 modules = concatLists (builtins.map (x: x.modules) sources); 402 specialArgs = builtins.foldl' recursiveUpdate { } (builtins.map (x: x.specialArgs) sources); 403 }; 404 } 405 )) 406 407 attrValues 408 foldAttrsMerge 409 ]; 410 411 /** 412 normaliseHost 413 414 # Arguments 415 416 - [additionalClasses]: A set of additional classes to be used for the system. 417 - [system]: The system to be normalised. This is usually one of `x86_64-linux`, `aarch64-darwin`, or `armv7l-linux`. 418 - [path]: The path to the host. 419 420 # Type 421 422 ``` 423 normaliseHost :: AttrSet -> String -> String -> AttrSet 424 ``` 425 426 # Example 427 428 ```nix 429 normaliseHost { rpi = "nixos"; } "x86_64-linux" "/path/to/host" 430 => { arch = "x86_64"; class = "linux"; path = "/path/to/host"; system = "x86_64-linux"; } 431 ``` 432 */ 433 normaliseHost = 434 additionalClasses: system: path: 435 let 436 inherit (splitSystem system) arch class; 437 in 438 { 439 inherit arch class path; 440 system = constructSystem additionalClasses arch class; 441 }; 442 443 /** 444 normaliseHosts is a function that takes a set of hosts and returns a set of normalised hosts. 445 446 # Arguments 447 448 - [cfg]: The easy-hosts configuration. 449 - [hosts]: The hosts to be normalised. 450 451 # Type 452 453 ``` 454 normaliseHosts :: AttrSet -> AttrSet -> AttrSet 455 ``` 456 */ 457 normaliseHosts = 458 cfg: hosts: 459 if (cfg.onlySystem == null) then 460 pipe hosts [ 461 (mapAttrs ( 462 system: 463 mapAttrs (name: _: normaliseHost cfg.additionalClasses system "${cfg.path}/${system}/${name}") 464 )) 465 466 attrValues 467 foldAttrsMerge 468 ] 469 else 470 mapAttrs (name: _: normaliseHost cfg.additionalClasses cfg.onlySystem "${cfg.path}/${name}") hosts; 471 472 /** 473 buildHosts is a function that takes a configuration and returns a set of hosts. 474 It is used to build the hosts for the system. 475 476 # Arguments 477 478 - [cfg]: The easy-hosts configuration. 479 480 # Type 481 482 ``` 483 buildHosts :: AttrSet -> AttrSet 484 ``` 485 */ 486 buildHosts = 487 cfg: 488 let 489 hostsDir = readDir cfg.path; 490 491 hosts = 492 if (cfg.onlySystem != null) then 493 hostsDir 494 else 495 mapAttrs (path: _: readDir "${cfg.path}/${path}") ( 496 filterAttrs (_: type: type == "directory") hostsDir 497 ); 498 in 499 normaliseHosts cfg hosts; 500in 501{ 502 inherit 503 constructSystem 504 mkHost 505 mkHosts 506 buildHosts 507 ; 508}