468 lines
11 KiB
Nix
468 lines
11 KiB
Nix
{ lib, ... }:
|
||
|
||
let
|
||
inherit (lib)
|
||
all any concatMapStringsSep concatStringsSep const filter flip
|
||
genid hasSuffix head isInt isString length mergeOneOption mkOption
|
||
mkOptionType optional optionalAttrs optionals range splitString
|
||
stringLength substring test 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;
|
||
};
|
||
|
||
managed = mkOption {
|
||
description = ''
|
||
If true, then the host's configuration is defined in stockholm.
|
||
'';
|
||
type = bool;
|
||
};
|
||
|
||
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;
|
||
apply = lib.normalize-ip6-addr;
|
||
};
|
||
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 = nullOr str;
|
||
default = null;
|
||
};
|
||
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
|
||
test IPv4address;
|
||
merge = mergeOneOption;
|
||
};
|
||
addr6 = mkOptionType {
|
||
name = "IPv6 address";
|
||
check = let
|
||
# TODO check IPv6 address harder
|
||
IPv6address = "[0-9a-f.:]+";
|
||
in
|
||
test IPv6address;
|
||
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 = test "[0-7]{4}";
|
||
merge = mergeOneOption;
|
||
};
|
||
|
||
haskell.conid = mkOptionType {
|
||
name = "Haskell constructor identifier";
|
||
check = test "[[:upper:]][[:lower:]_[:upper:]0-9']*";
|
||
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 = test "[0-9A-Za-z]([0-9A-Za-z-]*[0-9A-Za-z])?";
|
||
merge = mergeOneOption;
|
||
};
|
||
|
||
# POSIX.1‐2013, 3.278 Portable Filename Character Set
|
||
filename = mkOptionType {
|
||
name = "POSIX filename";
|
||
check = test "([0-9A-Za-z._])[0-9A-Za-z._-]*";
|
||
merge = mergeOneOption;
|
||
};
|
||
|
||
# POSIX.1‐2013, 3.2 Absolute Pathname
|
||
absolute-pathname = mkOptionType {
|
||
name = "POSIX absolute pathname";
|
||
check = x: isString x && substring 0 1 x == "/" && pathname.check x;
|
||
merge = mergeOneOption;
|
||
};
|
||
|
||
# POSIX.1‐2013, 3.267 Pathname
|
||
pathname = mkOptionType {
|
||
name = "POSIX pathname";
|
||
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;
|
||
};
|
||
|
||
# POSIX.1-2013, 3.431 User Name
|
||
username = mkOptionType {
|
||
name = "POSIX username";
|
||
check = filename.check;
|
||
merge = mergeOneOption;
|
||
};
|
||
}
|