summaryrefslogtreecommitdiffstats
path: root/lib/types.nix
diff options
context:
space:
mode:
Diffstat (limited to 'lib/types.nix')
-rw-r--r--lib/types.nix459
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;
+ };
+}