summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authortv <tv@krebsco.de>2016-10-20 20:21:59 +0200
committertv <tv@krebsco.de>2016-10-20 20:21:59 +0200
commit844d347ce7cf0b7646e9ecba3fbdc0b90e608501 (patch)
tree32827cb69265c117abee1654ae6cf581a9a5c87c /lib
parent91d6bd66f4d50d47692f55c16bfb14bdf4837520 (diff)
lib: import bulk of krebs/4lib
Diffstat (limited to 'lib')
-rw-r--r--lib/default.nix36
-rw-r--r--lib/genid.nix37
-rw-r--r--lib/git.nix47
-rw-r--r--lib/types.nix459
4 files changed, 578 insertions, 1 deletions
diff --git a/lib/default.nix b/lib/default.nix
index 1f501085..2b12fa4b 100644
--- a/lib/default.nix
+++ b/lib/default.nix
@@ -1,10 +1,44 @@
let
- lib = import <nixpkgs/lib> // builtins // {
+ nixpkgs-lib = import <nixpkgs/lib>;
+ lib = with lib; nixpkgs-lib // builtins // {
+ git = import ./git.nix { inherit lib; };
shell = import ./shell.nix { inherit lib; };
+ types = nixpkgs-lib.types // import ./types.nix { inherit lib; };
eq = x: y: x == y;
ne = x: y: x != y;
mod = x: y: x - y * (x / y);
+
+ genid = import ./genid.nix { inherit lib; };
+ genid_signed = x: ((lib.genid x) + 16777216) / 2;
+
+ lpad = n: c: s:
+ if lib.stringLength s < n
+ then lib.lpad n c (c + s)
+ else s;
+
+ subdirsOf = path:
+ lib.mapAttrs (name: _: path + "/${name}")
+ (filterAttrs (_: eq "directory") (readDir path));
+
+ genAttrs' = names: f: listToAttrs (map f names);
+
+ getAttrs = names: set:
+ listToAttrs (map (name: nameValuePair name set.${name})
+ (filter (flip hasAttr set) names));
+
+ setAttr = name: value: set: set // { ${name} = value; };
+
+ toC = x: let
+ type = typeOf x;
+ reject = throw "cannot convert ${type}";
+ in {
+ list = "{ ${concatStringsSep ", " (map toC x)} }";
+ null = "NULL";
+ set = if isDerivation x then toJSON x else reject;
+ string = toJSON x; # close enough
+ }.${type} or reject;
+
};
in
diff --git a/lib/genid.nix b/lib/genid.nix
new file mode 100644
index 00000000..0aed1d35
--- /dev/null
+++ b/lib/genid.nix
@@ -0,0 +1,37 @@
+{ lib, ... }:
+with lib;
+with builtins;
+let out = genid;
+
+ # id = genid s = (hash s + min) % max
+ # min <= genid s < max
+ #
+ # min = 2^24 = 16777216 = 0x001000000
+ # max = 2^32 = 4294967296 = 0x100000000
+ #
+ # id is bigger than UID of nobody and GID of nogroup
+ # see <nixos/modules/misc/ids.nix> and some spare for stuff like lxd.
+ #
+ # :: str -> uint32
+ genid = s: sum16 (addmod16_16777216 (hash s));
+
+ # :: str -> list8 uint4
+ hash = s:
+ map hexint (stringToCharacters (substring 32 8 (hashString "sha1" s)));
+
+ # :: list uint -> uint
+ sum16 = foldl (a: i: a * 16 + i) 0;
+
+ # :: list8 uint4 -> list1 uint8 ++ list6 uint4
+ addmod16_16777216 = x: let
+ a = 16 * head x + head (tail x);
+ d = tail (tail x);
+ in [(mod (a + 1) 256)] ++ d;
+
+ # :: char -> uint4
+ hexint = x: hexvals.${toLower x};
+
+ # :: attrset char uint4
+ hexvals = listToAttrs (imap (i: c: { name = c; value = i - 1; })
+ (stringToCharacters "0123456789abcdef"));
+in out
diff --git a/lib/git.nix b/lib/git.nix
new file mode 100644
index 00000000..005c017a
--- /dev/null
+++ b/lib/git.nix
@@ -0,0 +1,47 @@
+{ lib, ... }:
+
+with lib;
+
+let
+ addName = name: set:
+ set // { inherit name; };
+
+ addNames = mapAttrs addName;
+
+ commands = addNames {
+ git-receive-pack = {};
+ git-upload-pack = {};
+ };
+
+ receive-modes = addNames {
+ fast-forward = {};
+ non-fast-forward = {};
+ create = {};
+ delete = {};
+ merge = {}; # TODO implement in git.nix
+ };
+
+ permissions = {
+ fetch = {
+ allow-commands = [
+ commands.git-upload-pack
+ ];
+ };
+
+ push = ref: extra-modes: {
+ allow-commands = [
+ commands.git-receive-pack
+ commands.git-upload-pack
+ ];
+ allow-receive-ref = ref;
+ allow-receive-modes = [ receive-modes.fast-forward ] ++ extra-modes;
+ };
+ };
+
+ refs = {
+ master = "refs/heads/master";
+ all-heads = "refs/heads/*";
+ };
+
+in
+commands // receive-modes // permissions // refs
diff --git a/lib/types.nix b/lib/types.nix
new file mode 100644
index 00000000..edd48c35
--- /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;
+ };
+}