stockholm/lib/types.nix

605 lines
15 KiB
Nix
Raw Normal View History

{ lib, ... }:
2015-07-24 10:23:52 +00:00
2016-10-20 18:21:59 +00:00
let
inherit (lib)
2018-02-28 13:30:11 +00:00
all any attrNames concatMapStringsSep concatStringsSep const filter flip
genid_uint31 hasSuffix head isInt isString length mergeOneOption mkOption
2016-10-20 18:21:59 +00:00
mkOptionType optional optionalAttrs optionals range splitString
2017-08-01 09:27:03 +00:00
stringLength substring test testString typeOf;
2016-10-20 18:21:59 +00:00
inherit (lib.types)
attrsOf bool either enum int listOf nullOr path str submodule;
2016-10-20 18:21:59 +00:00
in
2015-07-24 10:23:52 +00:00
2016-10-20 18:21:59 +00:00
rec {
2015-07-24 10:23:52 +00:00
host = submodule ({ config, ... }: {
2015-07-24 10:23:52 +00:00
options = {
name = mkOption {
type = label;
default = config._module.args.name;
2015-07-24 10:23:52 +00:00
};
cores = mkOption {
2018-12-05 15:52:32 +00:00
type = uint;
2015-07-24 10:23:52 +00:00
};
nets = mkOption {
type = attrsOf net;
default = {};
2015-07-24 10:23:52 +00:00
};
2015-08-13 10:03:59 +00:00
2016-10-20 18:21:59 +00:00
binary-cache.pubkey = mkOption {
type = nullOr binary-cache-pubkey;
default = null;
};
2017-08-31 17:15:09 +00:00
ci = mkOption {
description = ''
2017-08-31 17:15:09 +00:00
If true, then the host wants to be tested by some CI system.
See <stockholm/krebs/2configs/buildbot-all.nix>
'';
type = bool;
default = false;
};
2017-08-31 17:45:58 +00:00
external = mkOption {
description = ''
Whether the host is defined externally (in contrast to being defined
in <stockholm>). This is useful e.g. when legacy and/or adopted
hosts should be part of retiolum or some other component.
'';
type = bool;
default = false;
};
2018-04-27 13:14:59 +00:00
monitoring = mkOption {
description = ''
Whether the host should be monitored by monitoring tools like Prometheus.
'';
type = bool;
default = false;
};
2016-02-19 15:18:28 +00:00
owner = mkOption {
type = user;
};
2015-08-13 20:28:21 +00:00
extraZones = mkOption {
default = {};
# TODO: string is either MX, NS, A or AAAA
type = attrsOf str;
2015-08-13 20:28:21 +00:00
};
2015-09-26 22:22:50 +00:00
2015-07-24 16:36:16 +00:00
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 {
2016-03-16 00:54:49 +00:00
type = nullOr ssh-pubkey;
default = null;
};
ssh.privkey = mkOption {
2016-03-16 00:54:49 +00:00
type = nullOr ssh-privkey;
default = null;
};
2019-03-22 06:56:13 +00:00
syncthing.id = mkOption {
# TODO syncthing id type
2019-04-13 12:06:36 +00:00
type = nullOr str;
2019-03-22 06:56:13 +00:00
default = null;
};
2015-07-24 10:23:52 +00:00
};
});
2015-07-24 10:23:52 +00:00
net = submodule ({ config, ... }: {
options = {
name = mkOption {
type = label;
default = config._module.args.name;
};
2015-07-24 10:23:52 +00:00
via = mkOption {
type = nullOr net;
default = null;
};
addrs = mkOption {
2017-11-28 18:40:03 +00:00
type = listOf addr;
default =
optional (config.ip4 != null) config.ip4.addr ++
optional (config.ip6 != null) config.ip6.addr;
2015-07-24 10:23:52 +00:00
};
aliases = mkOption {
# TODO nonEmptyListOf hostname
type = listOf hostname;
default = [];
};
ip4 = mkOption {
type = nullOr (submodule {
options = {
addr = mkOption {
type = addr4;
};
prefix = mkOption ({
2017-10-11 16:12:31 +00:00
type = cidr4;
} // optionalAttrs (config.name == "retiolum") {
default = "10.243.0.0/16";
});
};
});
default = null;
};
ip6 = mkOption {
type = nullOr (submodule {
options = {
addr = mkOption {
type = addr6;
2017-04-13 09:13:59 +00:00
apply = lib.normalize-ip6-addr;
};
prefix = mkOption ({
2017-10-11 16:12:31 +00:00
type = cidr6;
} // optionalAttrs (config.name == "retiolum") {
default = "42::/16";
});
};
});
default = null;
};
ssh = mkOption {
type = submodule {
options = {
port = mkOption {
type = int;
default = 22;
};
};
};
default = {};
2015-07-24 10:23:52 +00:00
};
tinc = mkOption {
type = let net = config; in nullOr (submodule ({ config, ... }: {
2015-07-24 10:23:52 +00:00
options = {
config = mkOption {
type = str;
default = concatStringsSep "\n" (
(optionals (net.via != null)
2016-07-28 08:55:34 +00:00
(map (a: "Address = ${a} ${toString config.port}") net.via.addrs))
++
(map (a: "Subnet = ${a}") net.addrs)
++
2017-11-28 18:40:03 +00:00
(map (a: "Subnet = ${a}") config.subnets)
++
2016-07-28 11:02:41 +00:00
[config.extraConfig]
++
[config.pubkey]
);
2015-07-24 10:23:52 +00:00
};
pubkey = mkOption {
2016-03-16 01:04:22 +00:00
type = tinc-pubkey;
2015-07-24 10:23:52 +00:00
};
pubkey_ed25519 = mkOption {
type = nullOr tinc-pubkey;
default = null;
};
2016-07-28 11:02:41 +00:00
extraConfig = mkOption {
description = "Extra Configuration to be appended to the hosts file";
default = "";
type = str;
2016-07-28 11:02:41 +00:00
};
2016-07-28 08:55:34 +00:00
port = mkOption {
type = int;
description = "tinc port to use to connect to host";
default = 655;
};
2017-11-28 18:40:03 +00:00
subnets = mkOption {
type = listOf cidr;
description = "tinc subnets";
default = [];
};
2015-07-24 10:23:52 +00:00
};
2015-07-27 00:45:03 +00:00
}));
default = null;
2015-07-24 10:23:52 +00:00
};
2018-12-09 15:50:58 +00:00
wireguard = mkOption {
type = nullOr (submodule ({ config, ... }: {
options = {
port = mkOption {
type = int;
description = "tinc port to use to connect to host";
default = 51820;
};
pubkey = mkOption {
type = wireguard-pubkey;
};
subnets = mkOption {
type = listOf cidr;
description = ''
wireguard subnets,
this defines how routing behaves for hosts that can't reach each other.
'';
default = [];
};
};
}));
};
2015-07-24 10:23:52 +00:00
};
});
positive = mkOptionType {
name = "positive integer";
check = x: isInt x && x > 0;
merge = mergeOneOption;
};
2016-06-07 20:36:40 +00:00
uint = mkOptionType {
name = "unsigned integer";
check = x: isInt x && x >= 0;
merge = mergeOneOption;
};
2016-02-21 04:27:37 +00:00
secret-file = submodule ({ config, ... }: {
options = {
name = mkOption {
type = pathname;
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";
};
2020-08-04 18:28:04 +00:00
service = mkOption {
type = systemd.unit-name;
default = "secret-${lib.systemd.encodeName config.name}.service";
2020-08-04 18:28:04 +00:00
};
2016-02-21 04:27:37 +00:00
source-path = mkOption {
type = str;
default = toString <secrets> + "/${config.name}";
2016-02-21 04:27:37 +00:00
};
};
});
source = submodule ({ config, ... }: {
options = {
type = let
2018-02-28 13:30:11 +00:00
known-types = attrNames source-types;
type-candidates = filter (k: config.${k} != null) known-types;
in mkOption {
2018-02-28 13:30:11 +00:00
default = if length type-candidates == 1
then head type-candidates
else throw "cannot determine type";
type = enum known-types;
};
file = mkOption {
apply = x:
2018-02-28 13:30:11 +00:00
if absolute-pathname.check x
then { path = x; }
else x;
2018-02-28 13:30:11 +00:00
default = null;
type = nullOr (either absolute-pathname source-types.file);
};
git = mkOption {
default = null;
2018-02-28 13:30:11 +00:00
type = nullOr source-types.git;
};
2017-12-15 02:33:43 +00:00
pass = mkOption {
default = null;
2018-02-28 13:30:11 +00:00
type = nullOr source-types.pass;
2017-12-15 02:33:43 +00:00
};
2018-02-28 13:15:29 +00:00
pipe = mkOption {
apply = x:
if absolute-pathname.check x
then { command = x; }
else x;
default = null;
type = nullOr (either absolute-pathname source-types.pipe);
};
2018-02-28 13:30:11 +00:00
symlink = mkOption {
type = nullOr (either pathname source-types.symlink);
default = null;
apply = x:
2018-02-28 13:30:11 +00:00
if pathname.check x
then { target = x; }
else x;
};
};
});
2018-02-28 13:30:11 +00:00
source-types = {
file = submodule {
options = {
path = mkOption {
type = absolute-pathname;
};
};
};
2018-02-28 13:30:11 +00:00
git = submodule {
options = {
ref = mkOption {
type = str; # TODO types.git.ref
};
url = mkOption {
type = str; # TODO types.git.url
};
};
};
2018-02-28 13:30:11 +00:00
pass = submodule {
options = {
dir = mkOption {
type = absolute-pathname;
};
name = mkOption {
type = pathname; # TODO relative-pathname
};
2017-12-15 02:33:43 +00:00
};
};
2018-02-28 13:15:29 +00:00
pipe = submodule {
options = {
command = mkOption {
type = absolute-pathname;
};
};
};
2018-02-28 13:30:11 +00:00
symlink = submodule {
options = {
target = mkOption {
type = pathname; # TODO relative-pathname
};
};
};
2018-02-28 13:30:11 +00:00
};
2015-10-09 12:07:29 +00:00
suffixed-str = suffs:
mkOptionType {
name = "string suffixed by ${concatStringsSep ", " suffs}";
check = x: isString x && any (flip hasSuffix x) suffs;
merge = mergeOneOption;
};
user = submodule ({ config, ... }: {
2015-07-24 18:48:00 +00:00
options = {
home = mkOption {
type = absolute-pathname;
default = "/home/${config.name}";
};
2015-07-24 23:05:14 +00:00
mail = mkOption {
2017-04-27 17:43:12 +00:00
type = nullOr str;
default = null;
2015-07-24 23:05:14 +00:00
};
2015-07-24 18:48:00 +00:00
name = mkOption {
2016-02-06 18:37:14 +00:00
type = username;
default = config._module.args.name;
2015-07-24 18:48:00 +00:00
};
pgp.pubkeys = mkOption {
type = attrsOf pgp-pubkey;
default = {};
description = ''
Set of user's PGP public keys.
2016-06-07 00:15:58 +00:00
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.
'';
};
2015-07-24 18:48:00 +00:00
pubkey = mkOption {
2016-03-16 00:54:49 +00:00
type = nullOr ssh-pubkey;
2016-02-21 06:39:24 +00:00
default = null;
2015-07-24 18:48:00 +00:00
};
2016-02-21 05:56:57 +00:00
uid = mkOption {
type = int;
default = genid_uint31 config.name;
2016-02-21 05:56:57 +00:00
};
2015-07-24 18:48:00 +00:00
};
});
2016-04-26 23:33:39 +00:00
group = submodule ({ config, ... }: {
options = {
name = mkOption {
type = username;
default = config._module.args.name;
};
gid = mkOption {
type = int;
default = genid_uint31 config.name;
2016-04-26 23:33:39 +00:00
};
};
});
2015-07-24 18:48:00 +00:00
2016-04-07 18:29:07 +00:00
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);
2017-06-18 13:36:18 +00:00
in
test IPv4address;
2016-04-07 18:29:07 +00:00
merge = mergeOneOption;
};
addr6 = mkOptionType {
name = "IPv6 address";
check = let
# TODO check IPv6 address harder
IPv6address = "[0-9a-f.:]+";
2017-06-18 13:36:18 +00:00
in
test IPv6address;
merge = mergeOneOption;
};
2017-10-11 16:12:31 +00:00
cidr = either cidr4 cidr6;
cidr4 = mkOptionType {
name = "CIDRv4 address";
check = let
CIDRv4address = 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) + "(/([1-2]?[0-9]|3[0-2]))?";
in
test CIDRv4address;
merge = mergeOneOption;
};
cidr6 = mkOptionType {
name = "CIDRv6 address";
check = let
# TODO check IPv6 address harder
CIDRv6address = "[0-9a-f.:]+(/([0-9][0-9]?|1[0-2][0-8]))?";
in
test CIDRv6address;
merge = mergeOneOption;
};
2016-10-20 18:21:59 +00:00
binary-cache-pubkey = str;
pgp-pubkey = str;
2017-08-01 09:27:03 +00:00
sitemap.entry = submodule ({ config, ... }: {
options = {
desc = mkOption {
default = null;
type = nullOr str;
};
href = mkOption {
${if testString "https?://.*" config._module.args.name
then "default" else null} = config._module.args.name;
type = nullOr str; # TODO nullOr uri?
};
};
});
2016-03-16 00:54:49 +00:00
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";
};
};
};
2015-12-28 18:43:31 +00:00
2016-03-16 01:04:22 +00:00
tinc-pubkey = str;
2016-10-20 18:21:59 +00:00
krebs.file-location = submodule {
2015-12-28 18:43:31 +00:00
options = {
# TODO user
host = mkOption {
type = host;
};
# TODO merge with ssl.privkey.path
path = mkOption {
2016-10-20 18:21:59 +00:00
type = either path str;
2015-12-28 18:43:31 +00:00
apply = x: {
path = toString x;
string = x;
}.${typeOf x};
};
};
};
2016-02-06 18:37:14 +00:00
2016-06-12 23:37:51 +00:00
file-mode = mkOptionType {
name = "file mode";
2017-06-18 13:36:18 +00:00
check = test "[0-7]{4}";
2016-06-12 23:37:51 +00:00
merge = mergeOneOption;
};
2016-06-04 17:06:50 +00:00
haskell.conid = mkOptionType {
name = "Haskell constructor identifier";
2017-06-18 13:36:18 +00:00
check = test "[[:upper:]][[:lower:]_[:upper:]0-9']*";
2016-06-04 17:06:50 +00:00
merge = mergeOneOption;
};
haskell.modid = mkOptionType {
name = "Haskell module identifier";
check = x: isString x && all haskell.conid.check (splitString "." x);
merge = mergeOneOption;
};
2020-08-05 08:57:09 +00:00
systemd.unit-name = mkOptionType {
name = "systemd unit name";
check = x:
test "^[0-9A-Za-z:_.\\-]+@?\\.(service|socket|device|mount|automount|swap|target|path|timer|slice|scope)$" x &&
stringLength x <= 256;
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
2017-06-18 14:17:55 +00:00
check = test "[0-9A-Za-z]([0-9A-Za-z-]*[0-9A-Za-z])?";
merge = mergeOneOption;
};
# POSIX.12017, 3.190 Group Name
groupname = mkOptionType {
name = "POSIX group name";
check = filename.check;
merge = mergeOneOption;
};
2019-04-17 18:19:12 +00:00
# POSIX.12017, 3.281 Portable Filename
2016-02-06 18:37:14 +00:00
filename = mkOptionType {
2019-04-17 18:19:12 +00:00
name = "POSIX portable filename";
check = test "[0-9A-Za-z._][0-9A-Za-z._-]*";
2016-02-06 18:37:14 +00:00
merge = mergeOneOption;
};
2019-04-17 18:19:12 +00:00
# POSIX.12017, 3.2 Absolute Pathname
absolute-pathname = mkOptionType {
name = "POSIX absolute pathname";
2017-03-31 13:08:16 +00:00
check = x: isString x && substring 0 1 x == "/" && pathname.check x;
merge = mergeOneOption;
};
2019-04-17 18:19:12 +00:00
# POSIX.1-2017, 3.271 Pathname
pathname = mkOptionType {
name = "POSIX pathname";
2017-03-31 13:08:16 +00:00
check = x:
let
# The filter is used to normalize paths, i.e. to remove duplicated and
# trailing slashes. It also removes leading slashes, thus we have to
# check for "/" explicitly below.
xs = filter (s: stringLength s > 0) (splitString "/" x);
in
isString x && (x == "/" || (length xs > 0 && all filename.check xs));
merge = mergeOneOption;
};
2019-04-17 18:19:12 +00:00
# POSIX.1-2017, 3.216 Login Name
2016-02-06 18:37:14 +00:00
username = mkOptionType {
2019-04-17 18:19:12 +00:00
name = "POSIX login name";
check = filename.check;
merge = mergeOneOption;
2016-02-06 18:37:14 +00:00
};
2018-12-09 15:50:58 +00:00
wireguard-pubkey = str;
2015-07-24 10:23:52 +00:00
}