diff options
Diffstat (limited to 'lib/types.nix')
-rw-r--r-- | lib/types.nix | 459 |
1 files changed, 459 insertions, 0 deletions
diff --git a/lib/types.nix b/lib/types.nix new file mode 100644 index 0000000..edd48c3 --- /dev/null +++ b/lib/types.nix @@ -0,0 +1,459 @@ +{ lib, ... }: + +let + inherit (lib) + all any concatMapStringsSep concatStringsSep const filter flip genid + hasSuffix head isInt isString length match mergeOneOption mkOption + mkOptionType optional optionalAttrs optionals range splitString + stringLength tail typeOf; + inherit (lib.types) + attrsOf bool either enum int listOf nullOr path str string submodule; +in + +rec { + + host = submodule ({ config, ... }: { + options = { + name = mkOption { + type = label; + default = config._module.args.name; + }; + cores = mkOption { + type = positive; + }; + nets = mkOption { + type = attrsOf net; + default = {}; + }; + + binary-cache.pubkey = mkOption { + type = nullOr binary-cache-pubkey; + default = null; + }; + + owner = mkOption { + type = user; + }; + + extraZones = mkOption { + default = {}; + # TODO: string is either MX, NS, A or AAAA + type = attrsOf string; + }; + + secure = mkOption { + type = bool; + default = false; + description = '' + If true, then the host is capable of keeping secret information. + + TODO define minimum requirements for secure hosts + ''; + }; + + ssh.pubkey = mkOption { + type = nullOr ssh-pubkey; + default = null; + }; + ssh.privkey = mkOption { + type = nullOr ssh-privkey; + default = null; + }; + }; + }); + + net = submodule ({ config, ... }: { + options = { + name = mkOption { + type = label; + default = config._module.args.name; + }; + via = mkOption { + type = nullOr net; + default = null; + }; + addrs = mkOption { + type = listOf addr; + default = + optional (config.ip4 != null) config.ip4.addr ++ + optional (config.ip6 != null) config.ip6.addr; + }; + aliases = mkOption { + # TODO nonEmptyListOf hostname + type = listOf hostname; + default = []; + }; + ip4 = mkOption { + type = nullOr (submodule { + options = { + addr = mkOption { + type = addr4; + }; + prefix = mkOption ({ + type = str; # TODO routing prefix (CIDR) + } // optionalAttrs (config.name == "retiolum") { + default = "10.243.0.0/16"; + }); + }; + }); + default = null; + }; + ip6 = mkOption { + type = nullOr (submodule { + options = { + addr = mkOption { + type = addr6; + }; + prefix = mkOption ({ + type = str; # TODO routing prefix (CIDR) + } // optionalAttrs (config.name == "retiolum") { + default = "42::/16"; + }); + }; + }); + default = null; + }; + ssh = mkOption { + type = submodule { + options = { + port = mkOption { + type = int; + default = 22; + }; + }; + }; + default = {}; + }; + tinc = mkOption { + type = let net = config; in nullOr (submodule ({ config, ... }: { + options = { + config = mkOption { + type = str; + default = concatStringsSep "\n" ( + (optionals (net.via != null) + (map (a: "Address = ${a} ${toString config.port}") net.via.addrs)) + ++ + (map (a: "Subnet = ${a}") net.addrs) + ++ + [config.extraConfig] + ++ + [config.pubkey] + ); + }; + pubkey = mkOption { + type = tinc-pubkey; + }; + extraConfig = mkOption { + description = "Extra Configuration to be appended to the hosts file"; + default = ""; + type = string; + }; + port = mkOption { + type = int; + description = "tinc port to use to connect to host"; + default = 655; + }; + }; + })); + default = null; + }; + }; + }); + + positive = mkOptionType { + name = "positive integer"; + check = x: isInt x && x > 0; + merge = mergeOneOption; + }; + + uint = mkOptionType { + name = "unsigned integer"; + check = x: isInt x && x >= 0; + merge = mergeOneOption; + }; + + secret-file = submodule ({ config, ... }: { + options = { + name = mkOption { + type = filename; + default = config._module.args.name; + }; + path = mkOption { + type = absolute-pathname; + default = "/run/keys/${config.name}"; + }; + mode = mkOption { + type = file-mode; + default = "0400"; + }; + owner = mkOption { + type = user; + }; + group-name = mkOption { + type = str; + default = "root"; + }; + source-path = mkOption { + type = str; + default = toString <secrets> + "/${config.name}"; + }; + }; + }); + + + source = submodule ({ config, ... }: { + options = { + type = let + types = ["file" "git" "symlink"]; + in mkOption { + type = enum types; + default = let + cands = filter (k: config.${k} != null) types; + in + if length cands == 1 + then head cands + else throw "cannot determine type"; + }; + file = let + file-path = (file-source.getSubOptions "FIXME").path.type; + in mkOption { + type = nullOr (either file-source file-path); + default = null; + apply = x: + if file-path.check x + then { path = x; } + else x; + }; + git = mkOption { + type = nullOr git-source; + default = null; + }; + symlink = let + symlink-target = (symlink-source.getSubOptions "FIXME").target.type; + in mkOption { + type = nullOr (either symlink-source symlink-target); + default = null; + apply = x: + if symlink-target.check x + then { target = x; } + else x; + }; + }; + }); + + file-source = submodule { + options = { + path = mkOption { + type = absolute-pathname; + }; + }; + }; + + git-source = submodule { + options = { + ref = mkOption { + type = str; # TODO types.git.ref + }; + url = mkOption { + type = str; # TODO types.git.url + }; + }; + }; + + symlink-source = submodule { + options = { + target = mkOption { + type = pathname; # TODO relative-pathname + }; + }; + }; + + + suffixed-str = suffs: + mkOptionType { + name = "string suffixed by ${concatStringsSep ", " suffs}"; + check = x: isString x && any (flip hasSuffix x) suffs; + merge = mergeOneOption; + }; + + user = submodule ({ config, ... }: { + options = { + home = mkOption { + type = absolute-pathname; + default = "/home/${config.name}"; + }; + mail = mkOption { + type = str; # TODO retiolum mail address + default = "${config._module.args.name}@${config.networking.hostName}.r"; + }; + name = mkOption { + type = username; + default = config._module.args.name; + }; + pgp.pubkeys = mkOption { + type = attrsOf pgp-pubkey; + default = {}; + description = '' + Set of user's PGP public keys. + + Modules supporting PGP may use well-known key names to define + default values for options, in which case the well-known name + should be documented in the respective option's description. + ''; + }; + pubkey = mkOption { + type = nullOr ssh-pubkey; + default = null; + }; + uid = mkOption { + type = int; + default = genid config.name; + }; + }; + }); + group = submodule ({ config, ... }: { + options = { + name = mkOption { + type = username; + default = config._module.args.name; + }; + gid = mkOption { + type = int; + default = genid config.name; + }; + }; + }); + + addr = either addr4 addr6; + addr4 = mkOptionType { + name = "IPv4 address"; + check = let + IPv4address = let d = "([1-9]?[0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])"; in + concatMapStringsSep "." (const d) (range 1 4); + in x: isString x && match IPv4address x != null; + merge = mergeOneOption; + }; + addr6 = mkOptionType { + name = "IPv6 address"; + check = let + # TODO check IPv6 address harder + IPv6address = "[0-9a-f.:]+"; + in x: isString x && match IPv6address x != null; + merge = mergeOneOption; + }; + + binary-cache-pubkey = str; + + pgp-pubkey = str; + + ssh-pubkey = str; + ssh-privkey = submodule { + options = { + bits = mkOption { + type = nullOr (enum ["4096"]); + default = null; + }; + path = mkOption { + type = either path str; + apply = x: { + path = toString x; + string = x; + }.${typeOf x}; + }; + type = mkOption { + type = enum ["rsa" "ed25519"]; + default = "ed25519"; + }; + }; + }; + + tinc-pubkey = str; + + krebs.file-location = submodule { + options = { + # TODO user + host = mkOption { + type = host; + }; + # TODO merge with ssl.privkey.path + path = mkOption { + type = either path str; + apply = x: { + path = toString x; + string = x; + }.${typeOf x}; + }; + }; + }; + + file-mode = mkOptionType { + name = "file mode"; + check = x: isString x && match "[0-7]{4}" x != null; + merge = mergeOneOption; + }; + + haskell.conid = mkOptionType { + name = "Haskell constructor identifier"; + check = x: + isString x && match "[[:upper:]][[:lower:]_[:upper:]0-9']*" x != null; + merge = mergeOneOption; + }; + + haskell.modid = mkOptionType { + name = "Haskell module identifier"; + check = x: isString x && all haskell.conid.check (splitString "." x); + merge = mergeOneOption; + }; + + # RFC952, B. Lexical grammar, <hname> + hostname = mkOptionType { + name = "hostname"; + check = x: isString x && all label.check (splitString "." x); + merge = mergeOneOption; + }; + + # RFC952, B. Lexical grammar, <name> + # RFC1123, 2.1 Host Names and Numbers + label = mkOptionType { + name = "label"; + # TODO case-insensitive labels + check = x: isString x + && match "[0-9A-Za-z]([0-9A-Za-z-]*[0-9A-Za-z])?" x != null; + merge = mergeOneOption; + }; + + # POSIX.1‐2013, 3.278 Portable Filename Character Set + filename = mkOptionType { + name = "POSIX filename"; + check = x: isString x && match "([0-9A-Za-z._])[0-9A-Za-z._-]*" x != null; + merge = mergeOneOption; + }; + + # POSIX.1‐2013, 3.2 Absolute Pathname + # TODO normalize slashes + # TODO two slashes + absolute-pathname = mkOptionType { + name = "POSIX absolute pathname"; + check = x: let xs = splitString "/" x; xa = head xs; in + isString x + && stringLength x > 0 + && (xa == "/" || (xa == "" && all filename.check (tail xs))); + merge = mergeOneOption; + }; + + # POSIX.1‐2013, 3.267 Pathname + # TODO normalize slashes + pathname = mkOptionType { + name = "POSIX pathname"; + check = x: let xs = splitString "/" x; in + isString x && all filename.check (if head xs == "" then tail xs else xs); + merge = mergeOneOption; + }; + + # POSIX.1-2013, 3.431 User Name + username = mkOptionType { + name = "POSIX username"; + check = filename.check; + merge = mergeOneOption; + }; +} |