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

Configure Feed

Select the types of activity you want to include in your feed.

init

+688
+43
README.md
··· 1 + # Easy Hosts 2 + 3 + This is a nix flake module, this means that it is intended to be used alongside [flake-parts](https://flake.parts). 4 + 5 + You can find some examples of how to use this module in the [examples](./examples) directory. 6 + 7 + ## Why use this? 8 + 9 + We provide you with the following attributes `self'` and `inputs'` that can be used to make your configuration shorter going from writing `inputs.input-name.packages.${pkgs.system}.package-name` to `inputs'.input-name.packages.package-name`. 10 + 11 + We also can auto construct your hosts based on your file structure. Whilst providing you with a nice api which will allow you to add more settings to your hosts at a later date or consume another flake-module that can work alongside this flake. 12 + 13 + ## Explaination of the module 14 + 15 + - `easyHosts.autoConstruct`: If set to true, the module will automatically construct the hosts for you from the directory structure of `easyHosts.path`. 16 + 17 + - `easyHosts.path`: The directory to where the hosts are stored, this *must* be set. 18 + 19 + - `easyHosts.onlySystem`: If you only have 1 system type like `aarch64-darwin` then you can use this setting to prevent nesting your directories. 20 + 21 + - `easyHosts.shared`: The shared options for all the hosts. 22 + - `modules`: A list of modules that will be included in all the hosts. 23 + - `specialArgs`: A list of special arguments that will be passed to all the hosts. 24 + 25 + - `easyHosts.perClass`: This provides you with the `class` argument such that you can specify what classes get which modules. 26 + - `modules`: A list of modules that will be included in all the hosts of the given class. 27 + - `specialArgs`: A list of special arguments that will be passed to all the hosts of the given class. 28 + 29 + - `easyHosts.hosts.<host>`: The options for the given host. 30 + - `path`: the path to the host, this is not strictly needed if you have a flat directory called `hosts` or `systems`. 31 + - `arch`: The architecture of the host. 32 + - `modules`: A list of modules that will be included in the host. 33 + - `class`: the class of the host, this can be one of [ "nixos", "darwin", "iso" ]. 34 + - `specialArgs`: A list of special arguments that will be passed to the host. 35 + - `deployable`: this was added for people who may want to consume a deploy-rs or colonma flakeModule. 36 + 37 + ## Similar projects 38 + 39 + - [ez-configs](https://github.com/ehllie/ez-configs) 40 + 41 + ## Real world examples 42 + 43 + - [isabelroses/dotfiles](https://github.com/isabelroses/dotfiles)
+48
examples/multi-specialised/flake.lock
··· 1 + { 2 + "nodes": { 3 + "flake-parts": { 4 + "inputs": { 5 + "nixpkgs-lib": [ 6 + "nixpkgs" 7 + ] 8 + }, 9 + "locked": { 10 + "lastModified": 1735774679, 11 + "narHash": "sha256-soePLBazJk0qQdDVhdbM98vYdssfs3WFedcq+raipRI=", 12 + "owner": "hercules-ci", 13 + "repo": "flake-parts", 14 + "rev": "f2f7418ce0ab4a5309a4596161d154cfc877af66", 15 + "type": "github" 16 + }, 17 + "original": { 18 + "owner": "hercules-ci", 19 + "repo": "flake-parts", 20 + "type": "github" 21 + } 22 + }, 23 + "nixpkgs": { 24 + "locked": { 25 + "lastModified": 1735471104, 26 + "narHash": "sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs=", 27 + "owner": "NixOS", 28 + "repo": "nixpkgs", 29 + "rev": "88195a94f390381c6afcdaa933c2f6ff93959cb4", 30 + "type": "github" 31 + }, 32 + "original": { 33 + "owner": "NixOS", 34 + "ref": "nixos-unstable", 35 + "repo": "nixpkgs", 36 + "type": "github" 37 + } 38 + }, 39 + "root": { 40 + "inputs": { 41 + "flake-parts": "flake-parts", 42 + "nixpkgs": "nixpkgs" 43 + } 44 + } 45 + }, 46 + "root": "root", 47 + "version": 7 48 + }
+35
examples/multi-specialised/flake.nix
··· 1 + { 2 + inputs = { 3 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 + 5 + flake-parts = { 6 + url = "github:hercules-ci/flake-parts"; 7 + inputs.nixpkgs-lib.follows = "nixpkgs"; 8 + }; 9 + 10 + easy-hosts.url = "github:isabelroses/easy-hosts"; 11 + }; 12 + 13 + outputs = 14 + inputs: 15 + inputs.flake-parts.lib.mkFlake { inherit inputs; } { 16 + imports = [ inputs.easy-hosts.flakeModule ]; 17 + 18 + systems = [ 19 + "x86_64-linux" 20 + "aarch64-darwin" 21 + ]; 22 + 23 + easyHosts = { 24 + autoConstruct = true; 25 + path = ./hosts; 26 + 27 + perClass = class: { 28 + # only nixos systems will get this module 29 + modules = inputs.nixpkgs.lib.optionals (class == "nixos") [ 30 + ./modules/nixos.nix 31 + ]; 32 + }; 33 + }; 34 + }; 35 + }
+1
examples/multi-specialised/hosts/aarch64-darwin/default.nix
··· 1 + { }
+1
examples/multi-specialised/hosts/x86_64-iso/test2/default.nix
··· 1 + { }
+1
examples/multi-specialised/hosts/x86_64-nixos/test3/default.nix
··· 1 + { }
+1
examples/multi-specialised/hosts/x86_64-nixos/test4/default.nix
··· 1 + { }
+1
examples/multi-specialised/modules/nixos.nix
··· 1 + { }
+48
examples/multi/flake.lock
··· 1 + { 2 + "nodes": { 3 + "flake-parts": { 4 + "inputs": { 5 + "nixpkgs-lib": [ 6 + "nixpkgs" 7 + ] 8 + }, 9 + "locked": { 10 + "lastModified": 1735774679, 11 + "narHash": "sha256-soePLBazJk0qQdDVhdbM98vYdssfs3WFedcq+raipRI=", 12 + "owner": "hercules-ci", 13 + "repo": "flake-parts", 14 + "rev": "f2f7418ce0ab4a5309a4596161d154cfc877af66", 15 + "type": "github" 16 + }, 17 + "original": { 18 + "owner": "hercules-ci", 19 + "repo": "flake-parts", 20 + "type": "github" 21 + } 22 + }, 23 + "nixpkgs": { 24 + "locked": { 25 + "lastModified": 1735471104, 26 + "narHash": "sha256-0q9NGQySwDQc7RhAV2ukfnu7Gxa5/ybJ2ANT8DQrQrs=", 27 + "owner": "NixOS", 28 + "repo": "nixpkgs", 29 + "rev": "88195a94f390381c6afcdaa933c2f6ff93959cb4", 30 + "type": "github" 31 + }, 32 + "original": { 33 + "owner": "NixOS", 34 + "ref": "nixos-unstable", 35 + "repo": "nixpkgs", 36 + "type": "github" 37 + } 38 + }, 39 + "root": { 40 + "inputs": { 41 + "flake-parts": "flake-parts", 42 + "nixpkgs": "nixpkgs" 43 + } 44 + } 45 + }, 46 + "root": "root", 47 + "version": 7 48 + }
+25
examples/multi/flake.nix
··· 1 + { 2 + inputs = { 3 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 + 5 + flake-parts = { 6 + url = "github:hercules-ci/flake-parts"; 7 + inputs.nixpkgs-lib.follows = "nixpkgs"; 8 + }; 9 + 10 + easy-hosts.url = "github:isabelroses/easy-hosts"; 11 + }; 12 + 13 + outputs = 14 + inputs: 15 + inputs.flake-parts.lib.mkFlake { inherit inputs; } { 16 + imports = [ inputs.easy-hosts.flakeModule ]; 17 + 18 + systems = [ "x86_64-linux" ]; 19 + 20 + easyHosts = { 21 + autoConstruct = true; 22 + path = ./hosts; 23 + }; 24 + }; 25 + }
+1
examples/multi/hosts/aarch64-darwin/test/default.nix
··· 1 + { }
+1
examples/multi/hosts/aarch64-linux/test2/default.nix
··· 1 + { }
+1
examples/multi/hosts/x86_64-linux/test3/default.nix
··· 1 + { }
+1
examples/multi/hosts/x86_64-linux/test4/default.nix
··· 1 + { }
+35
examples/not-auto/flake.nix
··· 1 + { 2 + inputs = { 3 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 + 5 + flake-parts = { 6 + url = "github:hercules-ci/flake-parts"; 7 + inputs.nixpkgs-lib.follows = "nixpkgs"; 8 + }; 9 + 10 + easy-hosts.url = "github:isabelroses/easy-hosts"; 11 + }; 12 + 13 + outputs = 14 + inputs: 15 + inputs.flake-parts.lib.mkFlake { inherit inputs; } { 16 + imports = [ inputs.easy-hosts.flakeModule ]; 17 + 18 + systems = [ 19 + "x86_64-nixos" 20 + "aarch64-darwin" 21 + ]; 22 + 23 + easyHosts.hosts = { 24 + test = { 25 + arch = "x86_64"; 26 + class = "nixos"; 27 + }; 28 + 29 + test2 = { 30 + arch = "x86_64"; 31 + class = "darwin"; 32 + }; 33 + }; 34 + }; 35 + }
+1
examples/not-auto/hosts/test/default.nix
··· 1 + { }
+1
examples/not-auto/hosts/test2/default.nix
··· 1 + { }
+26
examples/only/flake.nix
··· 1 + { 2 + inputs = { 3 + nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; 4 + 5 + flake-parts = { 6 + url = "github:hercules-ci/flake-parts"; 7 + inputs.nixpkgs-lib.follows = "nixpkgs"; 8 + }; 9 + 10 + easy-hosts.url = "github:isabelroses/easy-hosts"; 11 + }; 12 + 13 + outputs = 14 + inputs: 15 + inputs.flake-parts.lib.mkFlake { inherit inputs; } { 16 + imports = [ inputs.easy-hosts.flakeModule ]; 17 + 18 + systems = [ "aarch64-darwin" ]; 19 + 20 + easyHosts = { 21 + autoConstruct = true; 22 + path = ./hosts; 23 + onlySystem = "aarch64-darwin"; 24 + }; 25 + }; 26 + }
+1
examples/only/hosts/test1/default.nix
··· 1 + { }
+1
examples/only/hosts/test2/default.nix
··· 1 + { }
+134
flake-module.nix
··· 1 + { 2 + lib, 3 + inputs, 4 + config, 5 + withSystem, 6 + ... 7 + }: 8 + let 9 + inherit (lib.options) mkOption literalExpression; 10 + inherit (lib) types; 11 + 12 + inherit (import ./lib.nix { inherit lib inputs withSystem; }) 13 + constructSystem 14 + mkHosts 15 + buildHosts 16 + ; 17 + 18 + cfg = config.easyHosts; 19 + in 20 + { 21 + options = { 22 + easyHosts = { 23 + autoConstruct = lib.mkEnableOption "Automatically construct hosts"; 24 + 25 + path = mkOption { 26 + type = types.nullOr types.path; 27 + default = null; 28 + example = literalExpression "./hosts"; 29 + }; 30 + 31 + onlySystem = mkOption { 32 + type = types.nullOr types.str; 33 + default = null; 34 + example = literalExpression "aarch64-darwin"; 35 + }; 36 + 37 + shared = { 38 + modules = mkOption { 39 + # we really expect a list of paths but i want to accept lists of lists of lists and so on 40 + # since they will be flattened in the final function that applies the settings 41 + type = types.listOf types.anything; 42 + default = [ ]; 43 + }; 44 + 45 + specialArgs = mkOption { 46 + type = types.attrs; 47 + default = { }; 48 + }; 49 + }; 50 + 51 + perClass = mkOption { 52 + default = _: { 53 + modules = [ ]; 54 + specialArgs = { }; 55 + }; 56 + 57 + type = types.functionTo ( 58 + types.submodule { 59 + options = { 60 + modules = mkOption { 61 + type = types.listOf types.anything; 62 + default = [ ]; 63 + }; 64 + 65 + specialArgs = mkOption { 66 + type = types.attrs; 67 + default = { }; 68 + }; 69 + }; 70 + } 71 + ); 72 + }; 73 + 74 + hosts = mkOption { 75 + default = { }; 76 + type = types.attrsOf ( 77 + types.submodule ( 78 + { name, ... }: 79 + let 80 + self = cfg.hosts.${name}; 81 + in 82 + { 83 + options = { 84 + arch = mkOption { 85 + type = types.str; 86 + default = "x86_64"; 87 + }; 88 + 89 + class = mkOption { 90 + type = types.str; 91 + default = "nixos"; 92 + }; 93 + 94 + system = mkOption { 95 + type = types.str; 96 + default = constructSystem self.class self.arch; 97 + }; 98 + 99 + path = mkOption { 100 + type = types.nullOr types.path; 101 + default = null; 102 + example = literalExpression "./hosts/myhost"; 103 + }; 104 + 105 + deployable = mkOption { 106 + type = types.bool; 107 + default = false; 108 + }; 109 + 110 + modules = mkOption { 111 + type = types.listOf types.anything; 112 + default = [ ]; 113 + }; 114 + 115 + specialArgs = mkOption { 116 + type = types.attrs; 117 + default = { }; 118 + }; 119 + }; 120 + } 121 + ) 122 + ); 123 + }; 124 + }; 125 + }; 126 + 127 + config = { 128 + # if the user has made it such that they want the hosts to be constructed automatically 129 + # i.e. from the file paths then we will do that 130 + easyHosts.hosts = lib.mkIf cfg.autoConstruct (buildHosts cfg); 131 + 132 + flake = mkHosts cfg; 133 + }; 134 + }
+29
flake.nix
··· 1 + { 2 + inputs = { }; 3 + 4 + outputs = _: { 5 + flakeModule = ./flake-module.nix; 6 + 7 + templates = { 8 + multi = { 9 + path = ./templates/multi; 10 + description = "A multi-system flake with auto construction enabled, but only using x86_64-linux."; 11 + }; 12 + 13 + multi-specialised = { 14 + path = ./templates/multi-specialised; 15 + description = "A multi-system flake with auto construction enabled, using the custom class system of easy-hosts"; 16 + }; 17 + 18 + not-auto = { 19 + path = ./templates/not-auto; 20 + description = "A flake with auto construction disabled, using only the `easyHosts.hosts` attribute."; 21 + }; 22 + 23 + only = { 24 + path = ./templates/only; 25 + description = "A flake with auto construction enabled, with only one class and a more 'flat' structure."; 26 + }; 27 + }; 28 + }; 29 + }
+252
lib.nix
··· 1 + { 2 + lib, 3 + inputs, 4 + withSystem, 5 + ... 6 + }: 7 + let 8 + inherit (inputs) self; 9 + 10 + constructSystem = 11 + target: arch: 12 + if (target == "iso" || target == "nixos") then "${arch}-linux" else "${arch}-${target}"; 13 + 14 + inherit (builtins) 15 + readDir 16 + elemAt 17 + filter 18 + pathExists 19 + ; 20 + inherit (lib.lists) optionals singleton flatten; 21 + inherit (lib.attrsets) 22 + recursiveUpdate 23 + foldAttrs 24 + attrValues 25 + mapAttrs 26 + filterAttrs 27 + ; 28 + inherit (lib.modules) mkDefault evalModules; 29 + 30 + /** 31 + mkHost is a function that uses withSystem to give us inputs' and self' 32 + it also assumes the the system type either nixos or darwin and uses the appropriate 33 + 34 + # Type 35 + 36 + ``` 37 + mkHost :: AttrSet -> AttrSet 38 + ``` 39 + 40 + # Example 41 + 42 + ```nix 43 + mkHost { 44 + name = "myhost"; 45 + path = "/path/to/host"; 46 + system = "x86_64-linux"; 47 + class = "nixos"; 48 + modules = [ ./module.nix ]; 49 + specialArgs = { foo = "bar"; }; 50 + } 51 + ``` 52 + */ 53 + mkHost = 54 + { 55 + name, 56 + path, 57 + class ? "nixos", 58 + system ? "x86_64-linux", 59 + modules ? [ ], 60 + specialArgs ? { }, 61 + ... 62 + }: 63 + withSystem system ( 64 + { self', inputs', ... }: 65 + let 66 + eval = evalModules { 67 + # we use recursiveUpdate such that users can "override" the specialArgs 68 + # 69 + # This should only be used for special arguments that need to be evaluated 70 + # when resolving module structure (like in imports). 71 + specialArgs = recursiveUpdate { 72 + # create the modulesPath based on the system, we need 73 + modulesPath = 74 + if class == "darwin" then "${inputs.darwin}/modules" else "${inputs.nixpkgs}/nixos/modules"; 75 + 76 + # laying it out this way is completely arbitrary, however it looks nice i guess 77 + inherit lib; 78 + inherit self self'; 79 + inherit inputs inputs'; 80 + } specialArgs; 81 + 82 + # A nominal type for modules. When set and non-null, this adds a check to 83 + # make sure that only compatible modules are imported. 84 + class = if class == "iso" then "nixos" else class; 85 + 86 + modules = flatten [ 87 + # import our host system paths 88 + ( 89 + if path != null then 90 + path 91 + else 92 + (filter pathExists [ 93 + # if the previous path does not exist then we will try to import some paths with some assumptions 94 + "${self}/hosts/${name}/default.nix" 95 + "${self}/systems/${name}/default.nix" 96 + ]) 97 + ) 98 + 99 + # get an installer profile from nixpkgs to base the Isos off of 100 + # this is useful because it makes things alot easier 101 + (optionals (class == "iso") [ 102 + "${inputs.nixpkgs}/nixos/modules/installer/cd-dvd/installation-cd-minimal-new-kernel.nix" 103 + ]) 104 + 105 + # we need to import the module list for our system 106 + # this is either the nixos modules list provided by nixpkgs 107 + # or the darwin modules list provided by nix darwin 108 + (import ( 109 + if class == "darwin" then 110 + "${inputs.darwin}/modules/module-list.nix" 111 + else 112 + "${inputs.nixpkgs}/nixos/modules/module-list.nix" 113 + )) 114 + 115 + (singleton { 116 + # TODO: learn what this means and why its needed to build the iso 117 + _module.args.modules = [ ]; 118 + 119 + # we set the systems hostname based on the host value 120 + # which should be a string that is the hostname of the system 121 + networking.hostName = name; 122 + 123 + nixpkgs = { 124 + # you can also do this as `inherit system;` with the normal `lib.nixosSystem` 125 + # however for evalModules this will not work, so we do this instead 126 + hostPlatform = mkDefault system; 127 + 128 + # The path to the nixpkgs sources used to build the system. 129 + # This is automatically set up to be the store path of the nixpkgs flake used to build 130 + # the system if using lib.nixosSystem, and is otherwise null by default. 131 + # so that means that we should set it to our nixpkgs flake output path 132 + flake.source = inputs.nixpkgs.outPath; 133 + }; 134 + }) 135 + 136 + # if we are on darwin we need to import the nixpkgs source, its used in some 137 + # modules, if this is not set then you will get an error 138 + (optionals (class == "darwin") (singleton { 139 + # without supplying an upstream nixpkgs source, nix-darwin will not be able to build 140 + # and will complain and log an error demanding that you must set this value 141 + nixpkgs.source = mkDefault inputs.nixpkgs; 142 + 143 + system = { 144 + # i don't quite know why this is set but upstream does it so i will too 145 + checks.verifyNixPath = false; 146 + 147 + # we use these values to keep track of what upstream revision we are on, this also 148 + # prevents us from recreating docs for the same configuration build if nothing has changed 149 + darwinVersionSuffix = ".${inputs.darwin.shortRev or inputs.darwin.dirtyShortRev or "dirty"}"; 150 + darwinRevision = inputs.darwin.rev or inputs.darwin.dirtyRev or "dirty"; 151 + }; 152 + })) 153 + 154 + # import any additional modules that the user has provided 155 + modules 156 + ]; 157 + }; 158 + in 159 + if (class == "nixos" || class == "iso") then 160 + { nixosConfigurations.${name} = eval; } 161 + else 162 + { 163 + darwinConfigurations.${name} = eval // { 164 + system = eval.config.system.build.toplevel; 165 + }; 166 + } 167 + ); 168 + 169 + foldAttrsReccursive = builtins.foldl' (acc: attrs: recursiveUpdate acc attrs) { }; 170 + 171 + mkHosts = 172 + makeHostsConfig: 173 + foldAttrs (host: acc: host // acc) { } ( 174 + attrValues ( 175 + mapAttrs ( 176 + name: cfg: 177 + mkHost { 178 + inherit name; 179 + 180 + inherit (cfg) class system path; 181 + 182 + # merging is handled later 183 + modules = [ 184 + (cfg.modules or [ ]) 185 + (makeHostsConfig.shared.modules or [ ]) 186 + ((makeHostsConfig.perClass cfg.class).modules or [ ]) 187 + ]; 188 + 189 + specialArgs = foldAttrsReccursive [ 190 + (cfg.specialArgs or { }) 191 + (makeHostsConfig.shared.specialArgs or { }) 192 + ((makeHostsConfig.perClass cfg.class).specialArgs or { }) 193 + ]; 194 + } 195 + ) makeHostsConfig.hosts 196 + ) 197 + ); 198 + 199 + onlyDirs = filterAttrs (_: type: type == "directory"); 200 + 201 + splitSystem = 202 + system: 203 + let 204 + sp = builtins.split "-" system; 205 + arch = elemAt sp 0; 206 + class = if ((elemAt sp 2) == "linux") then "nixos" else elemAt sp 2; 207 + in 208 + { 209 + inherit arch class; 210 + }; 211 + 212 + normaliseHosts = 213 + cfg: hosts: 214 + if (cfg.onlySystem == null) then 215 + foldAttrs (acc: host: acc // host) { } ( 216 + attrValues ( 217 + mapAttrs ( 218 + system: hosts': 219 + mapAttrs (name: _: { 220 + inherit (splitSystem system) arch class; 221 + path = "${cfg.path}/${system}/${name}"; 222 + }) hosts' 223 + ) hosts 224 + ) 225 + ) 226 + else 227 + mapAttrs (host: _: { 228 + inherit (splitSystem cfg.onlySystem) arch class; 229 + path = "${cfg.path}/${host}"; 230 + }) hosts; 231 + 232 + buildHosts = 233 + cfg: 234 + let 235 + hostsDir = readDir cfg.path; 236 + 237 + hosts = 238 + if (cfg.onlySystem != null) then 239 + hostsDir 240 + else 241 + mapAttrs (path: _: readDir "${cfg.path}/${path}") (onlyDirs hostsDir); 242 + in 243 + normaliseHosts cfg hosts; 244 + in 245 + { 246 + inherit 247 + constructSystem 248 + mkHost 249 + mkHosts 250 + buildHosts 251 + ; 252 + }