diff options
| author | tv <tv@krebsco.de> | 2016-10-20 20:21:59 +0200 | 
|---|---|---|
| committer | tv <tv@krebsco.de> | 2016-10-20 20:21:59 +0200 | 
| commit | c8718ceb9f6d5fa4d01bf04fdfa7e775980b143c (patch) | |
| tree | 3bc11da90e5c413480610524d8de5d5bb28536e9 /lib | |
| parent | 0f45156aae3fa69b9a75ea03862f15fe4aff10cb (diff) | |
lib: import bulk of krebs/4lib
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/default.nix | 36 | ||||
| -rw-r--r-- | lib/genid.nix | 37 | ||||
| -rw-r--r-- | lib/git.nix | 47 | ||||
| -rw-r--r-- | lib/types.nix | 459 | 
4 files changed, 578 insertions, 1 deletions
| diff --git a/lib/default.nix b/lib/default.nix index 1f50108..2b12fa4 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 0000000..0aed1d3 --- /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 0000000..005c017 --- /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 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; +  }; +} | 
