summaryrefslogtreecommitdiffstats
path: root/krebs
diff options
context:
space:
mode:
Diffstat (limited to 'krebs')
-rw-r--r--krebs/3modules/default.nix407
-rw-r--r--krebs/3modules/git.nix486
-rw-r--r--krebs/3modules/github-hosts-sync.nix83
-rw-r--r--krebs/3modules/nginx.nix72
-rw-r--r--krebs/3modules/retiolum.nix226
-rw-r--r--krebs/3modules/urlwatch.nix138
-rw-r--r--krebs/4lib/default.nix18
-rw-r--r--krebs/4lib/dns.nix31
-rw-r--r--krebs/4lib/listset.nix11
-rw-r--r--krebs/4lib/tree.nix13
-rw-r--r--krebs/4lib/types.nix109
-rw-r--r--krebs/5pkgs/default.nix14
-rw-r--r--krebs/5pkgs/dic.nix36
-rw-r--r--krebs/5pkgs/genid.nix22
-rw-r--r--krebs/5pkgs/github-hosts-sync.nix40
-rw-r--r--krebs/5pkgs/github-known_hosts.nix13
-rw-r--r--krebs/5pkgs/hashPassword.nix16
17 files changed, 1735 insertions, 0 deletions
diff --git a/krebs/3modules/default.nix b/krebs/3modules/default.nix
new file mode 100644
index 000000000..cd9cd732b
--- /dev/null
+++ b/krebs/3modules/default.nix
@@ -0,0 +1,407 @@
+{ config, lib, ... }:
+
+with import ../4lib { inherit lib; };
+let
+ cfg = config.krebs;
+
+ out = {
+ imports = [
+ ./github-hosts-sync.nix
+ ./git.nix
+ ./nginx.nix
+ ./retiolum.nix
+ ./urlwatch.nix
+ ];
+ options.krebs = api;
+ config = mkIf cfg.enable imp;
+ };
+
+ api = {
+ enable = mkEnableOption "krebs";
+
+ build = mkOption {
+ type = types.submodule ({ config, ... }: {
+ options = {
+ target = mkOption {
+ type = with types; nullOr str;
+ default = null;
+ };
+ deps = mkOption {
+ type = with types; attrsOf (submodule {
+ options = {
+ url = mkOption {
+ type = str;
+ };
+ rev = mkOption {
+ type = nullOr str;
+ default = null;
+ };
+ };
+ });
+ default = {};
+ };
+ script = mkOption {
+ type = types.str;
+ default = ''
+ #! /bin/sh
+ set -efux
+
+ target=${escapeShellArg cfg.build.target}
+
+ push(){(
+ src=$1/
+ dst=$target:$2
+ rsync \
+ --exclude .git \
+ --exclude .graveyard \
+ --exclude old \
+ --rsync-path="mkdir -p \"$dst\" && rsync" \
+ --usermap=\*:0 \
+ --groupmap=\*:0 \
+ --delete-excluded \
+ -vrLptgoD \
+ "$src" "$dst"
+ )}
+
+ ${concatStrings (mapAttrsToList (name: { url, rev, ... }:
+ optionalString (rev == null) ''
+ push ${toString (map escapeShellArg [
+ "${url}"
+ "/root/src/${name}"
+ ])}
+ '') config.deps)}
+
+ exec ssh -S none "$target" /bin/sh <<\EOF
+ set -efux
+ fetch(){(
+ url=$1
+ rev=$2
+ dst=$3
+ mkdir -p "$dst"
+ cd "$dst"
+ if ! test -e .git; then
+ git init
+ fi
+ if ! cur_url=$(git config remote.origin.url 2>/dev/null); then
+ git remote add origin "$url"
+ elif test "$cur_url" != "$url"; then
+ git remote set-url origin "$url"
+ fi
+ if test "$(git rev-parse --verify HEAD 2>/dev/null)" != "$rev"; then
+ git fetch origin
+ git checkout "$rev" -- .
+ git checkout -q "$rev"
+ git submodule init
+ git submodule update
+ fi
+ git clean -dxf
+ )}
+
+ ${concatStrings (mapAttrsToList (name: { url, rev, ... }:
+ optionalString (rev != null) ''
+ fetch ${toString (map escapeShellArg [
+ url
+ rev
+ "/root/src/${name}"
+ ])}
+ '') config.deps)}
+
+ echo build system...
+ profile=/nix/var/nix/profiles/system
+ NIX_PATH=/root/src \
+ nix-env \
+ -Q \
+ -p "$profile" \
+ -f '<stockholm>' \
+ --set \
+ -A system \
+ --argstr user-name ${escapeShellArg cfg.build.user.name} \
+ --argstr system-name ${escapeShellArg cfg.build.host.name}
+
+ exec "$profile"/bin/switch-to-configuration switch
+ EOF
+ '';
+ };
+ host = mkOption {
+ type = types.host;
+ };
+ user = mkOption {
+ type = types.user;
+ };
+ };
+ });
+ # Define defaul value, so unset values of the submodule get reported.
+ default = {};
+ };
+
+ dns = {
+ providers = mkOption {
+ # TODO with types; tree dns.label dns.provider, so we can merge.
+ # Currently providers can only be merged if aliases occur just once.
+ type = with types; attrsOf unspecified;
+ };
+ };
+
+ hosts = mkOption {
+ type = with types; attrsOf host;
+ };
+
+ users = mkOption {
+ type = with types; attrsOf user;
+ };
+
+ # XXX is there a better place to define search-domain?
+ # TODO search-domains :: listOf hostname
+ search-domain = mkOption {
+ type = types.hostname;
+ default = "retiolum";
+ };
+ };
+
+ imp = mkMerge [
+ { krebs = lass-imp; }
+ { krebs = makefu-imp; }
+ { krebs = tv-imp; }
+ {
+ krebs.dns.providers = {
+ de.krebsco = "ovh";
+ internet = "hosts";
+ retiolum = "hosts";
+ };
+
+ # XXX This overlaps with krebs.retiolum
+ networking.extraHosts = concatStringsSep "\n" (flatten (
+ mapAttrsToList (hostname: host:
+ mapAttrsToList (netname: net:
+ let
+ aliases = toString (unique (longs ++ shorts));
+ providers = dns.split-by-provider net.aliases cfg.dns.providers;
+ longs = providers.hosts;
+ shorts = map (removeSuffix ".${cfg.search-domain}") longs;
+ in
+ map (addr: "${addr} ${aliases}") net.addrs
+ ) host.nets
+ ) cfg.hosts
+ ));
+ }
+ ];
+
+ lass-imp = {
+ hosts = addNames {
+ };
+ users = addNames {
+ lass = {
+ pubkey = readFile ../../Zpubkeys/lass.ssh.pub;
+ };
+ uriel = {
+ pubkey = readFile ../../Zpubkeys/uriel.ssh.pub;
+ };
+ };
+ };
+
+ makefu-imp = {
+ hosts = addNames {
+ pnp = {
+ cores = 1;
+ dc = "makefu"; #vm on 'omo'
+ nets = {
+ retiolum = {
+ addrs4 = ["10.243.0.210"];
+ addrs6 = ["42:f9f1:0000:0000:0000:0000:0000:0001"];
+ aliases = [
+ "pnp.retiolum"
+ "cgit.pnp.retiolum"
+ ];
+ tinc.pubkey = ''
+ -----BEGIN RSA PUBLIC KEY-----
+ MIIBCgKCAQEAugkgEK4iy2C5+VZHwhjj/q3IOhhazE3TYHuipz37KxHWX8ZbjH+g
+ Ewtm79dVysujAOX8ZqV8nD8JgDAvkIZDp8FCIK0/rgckhpTsy1HVlHxa7ECrOS8V
+ pGz4xOxgcPFRbv5H2coHtbnfQc4GdA5fcNedQ3BP3T2Tn7n/dbbVs30bOP5V0EMR
+ SqZwNmtqaDQxOvjpPg9EoHvAYTevrpbbIst9UzCyvmNli9R+SsiDrzEPgB7zOc4T
+ TG12MT+XQr6JUu4jPpzdhb6H/36V6ADCIkBjzWh0iSfWGiFDQFinD+YSWbA1NOTr
+ Qtd1I3Ov+He7uc2Z719mb0Og2kCGnCnPIwIDAQAB
+ -----END RSA PUBLIC KEY-----
+ '';
+ };
+ };
+ };
+ };
+ users = addNames {
+ makefu = {
+ mail = "root@euer.krebsco.de";
+ pubkey = readFile ../../Zpubkeys/makefu_arch.ssh.pub;
+ };
+ };
+ };
+
+ tv-imp = {
+ dns.providers = {
+ de.viljetic = "regfish";
+ };
+ hosts = addNames {
+ cd = {
+ cores = 2;
+ dc = "tv"; #dc = "cac";
+ nets = rec {
+ internet = {
+ addrs4 = ["162.219.7.216"];
+ aliases = [
+ "cd.internet"
+ "cd.viljetic.de"
+ "cgit.cd.viljetic.de"
+ "cd.krebsco.de"
+ ];
+ };
+ retiolum = {
+ via = internet;
+ addrs4 = ["10.243.113.222"];
+ addrs6 = ["42:4522:25f8:36bb:8ccb:0150:231a:2af3"];
+ aliases = [
+ "cd.retiolum"
+ "cgit.cd.retiolum"
+ ];
+ tinc.pubkey = ''
+ -----BEGIN RSA PUBLIC KEY-----
+ MIICCgKCAgEAvmCBVNKT/Su4v9nl/Nm3STPo5QxWPg7xEkzIs3Oh39BS8+r6/7UQ
+ rebib7mczb+ebZd+Rg2yFoGrWO8cmM0VcLy5bYRMK7in8XroLEjWecNNM4TRfNR4
+ e53+LhcPdkxo0A3/D+yiut+A2Mkqe+4VXDm/JhAiAYkZTn7jUtj00Atrc7CWW1gN
+ sP3jIgv4+CGftdSYOB4dm699B7OD9XDLci2kOaFqFl4cjDYUok03G0AduUlRx10v
+ CKbKOTIdm8C36A902/3ms+Hyzkruu+VagGIZuPSwqXHJPCu7Ju+jarKQstMmpQi0
+ PubweWDL0o/Dfz2qT3DuL4xDecIvGE6kv3m41hHJYiK+2/azTSehyPFbsVbL7w0V
+ LgKN3usnZNcpTsBWxRGT7nMFSnX2FLDu7d9OfCuaXYxHVFLZaNrpccOq8NF/7Hbk
+ DDW81W7CvLyJDlp0WLnAawSOGTUTPoYv/2wAapJ89i8QGCueGvEc6o2EcnBVMFEW
+ ejWTQzyD816f4RsplnrRqLVlIMbr9Q/n5TvlgjjhX7IMEfMy4+7qLGRQkNbFzgwK
+ jxNG2fFSCjOEQitm0gAtx7QRIyvYr6c7/xiHz4AwxYzBmvQsL/OK57NO4+Krwgj5
+ Vk8TQ2jGO7J4bB38zaxK+Lrtfl8i1AK1171JqFMhOc34JSJ7T4LWDMECAwEAAQ==
+ -----END RSA PUBLIC KEY-----
+ '';
+ };
+ };
+ };
+ mkdir = {
+ cores = 1;
+ dc = "tv"; #dc = "cac";
+ nets = rec {
+ internet = {
+ addrs4 = ["162.248.167.241"];
+ aliases = [
+ "mkdir.internet"
+ ];
+ };
+ retiolum = {
+ via = internet;
+ addrs4 = ["10.243.113.223"];
+ addrs6 = ["42:4522:25f8:36bb:8ccb:0150:231a:2af4"];
+ aliases = [
+ "mkdir.retiolum"
+ "cgit.mkdir.retiolum"
+ ];
+ tinc.pubkey = ''
+ -----BEGIN RSA PUBLIC KEY-----
+ MIIBCgKCAQEAuyfM+3od75zOYXqnqRMAt+yp/4z/vC3vSWdjUvEmCuM23c5BOBw+
+ dKqbWoSPTzOuaQ0szdL7a6YxT+poSUXd/i3pPz59KgCl192rd1pZoJKgvoluITev
+ voYSP9rFQOUrustfDb9qKW/ZY95cwdCvypo7Vf4ghxwDCnlmyCGz7qXTJMLydNKF
+ 2PH9KiY4suv15sCg/zisu+q0ZYQXUc1TcgpoIYBOftDunOJoNdbti+XjwWdjGmJZ
+ Bn4GelsrrpwJFvfDmouHUe8GsD7nTgbZFtiJbKfCEiK16N0Q0d0ZFHhAV2nPjsk2
+ 3JhG4n9vxATBkO82f7RLrcrhkx9cbLfN3wIDAQAB
+ -----END RSA PUBLIC KEY-----
+ '';
+ };
+ };
+ };
+ nomic = {
+ cores = 2;
+ dc = "tv"; #dc = "gg23";
+ nets = rec {
+ retiolum = {
+ addrs4 = ["10.243.0.110"];
+ addrs6 = ["42:02d5:733f:d6da:c0f5:2bb7:2b18:09ec"];
+ aliases = [
+ "nomic.retiolum"
+ "cgit.nomic.retiolum"
+ ];
+ tinc.pubkey = ''
+ -----BEGIN RSA PUBLIC KEY-----
+ MIIBCgKCAQEAwb8Yk/YRc17g2J9n960p6j4W/l559OPyuMPdGJ4DmCm3WNQtxoa+
+ qTFUiDiI85BcmfqnSeddLG8zTC2XnSlIvCRMJ9oKzppFM4PX4OTAaJZVE5WyCQhw
+ Kd4tHVdoQgJW5yFepmT9IUmHqkxXJ0R2W93l2eSZNOcnFvFn0ooiAlRi4zAiHClu
+ 5Mz80Sc2rvez+n9wtC2D06aYjP23pHYld2xighHR9SUqX1dFzgSXNSoWWCcgNp2a
+ OKcM8LzxLV7MTMZFOJCJndZ77e4LsUvxhQFP6nyKZWg30PC0zufZsuN5o2xsWSlA
+ Wi9sMB1AUR6mZrxgcgTFpUjbjbLQf+36CwIDAQAB
+ -----END RSA PUBLIC KEY-----
+ '';
+ };
+ };
+ secure = true;
+ };
+ rmdir = {
+ cores = 1;
+ dc = "tv"; #dc = "cac";
+ nets = rec {
+ internet = {
+ addrs4 = ["167.88.44.94"];
+ aliases = [
+ "rmdir.internet"
+ ];
+ };
+ retiolum = {
+ via = internet;
+ addrs4 = ["10.243.113.224"];
+ addrs6 = ["42:4522:25f8:36bb:8ccb:0150:231a:2af5"];
+ aliases = [
+ "rmdir.retiolum"
+ "cgit.rmdir.retiolum"
+ ];
+ tinc.pubkey = ''
+ -----BEGIN RSA PUBLIC KEY-----
+ MIIBCgKCAQEA+twy4obSbJdmZLfBoe9YYeyoDnXkO/WPa2D6Eh6jXrWk5fbhBjRf
+ i3EAQfLiXXFJX3E8V8YvJyazXklI19jJtCLDiu/F5kgJJfyAkWHH+a/hcg7qllDM
+ Xx2CvS/nCbs+p48/VLO6zLC7b1oHu3K/ob5M5bwPK6j9NEDIL5qYiM5PQzV6zryz
+ hS9E/+l8Z+UUpYcfS3bRovXJAerB4txc/gD3Xmptq1zk53yn1kJFYfVlwyyz+NEF
+ 59JZj2PDrvWoG0kx/QjiNurs6XfdnyHe/gP3rmSTrihKFVuA3cZM62sDR4FcaeWH
+ SnKSp02pqjBOjC/dOK97nXpKLJgNH046owIDAQAB
+ -----END RSA PUBLIC KEY-----
+ '';
+ };
+ };
+ };
+ wu = {
+ cores = 4;
+ # TODO wu is mobile, so dc means "home data center"
+ dc = "tv"; #dc = "gg23";
+ nets = {
+ retiolum = {
+ addrs4 = ["10.243.13.37"];
+ addrs6 = ["42:0:0:0:0:0:0:1337"];
+ aliases = [
+ "wu.retiolum"
+ ];
+ tinc.pubkey = ''
+ -----BEGIN RSA PUBLIC KEY-----
+ MIIBCgKCAQEArDvU0cuBsVqTjCX2TlWL4XHSy4qSjUhjrDvUPZSKTVN7x6OENCUn
+ M27g9H7j4/Jw/8IHoJLiKnXHavOoc9UJM+P9Fla/4TTVADr69UDSnLgH+wGiHcEg
+ GxPkb2jt0Z8zcpD6Fusj1ATs3sssaLHTHvg1D0LylEWA3cI4WPP13v23PkyUENQT
+ KpSWfR+obqDl38Q7LuFi6dH9ruyvqK+4syddrBwjPXrcNxcGL9QbDn7+foRNiWw4
+ 4CE5z25oGG2iWMShI7fe3ji/fMUAl7DSOOrHVVG9eMtpzy+uI8veOHrdTax4oKik
+ AFGCrMIov3F0GIeu3nDlrTIZPZDTodbFKQIDAQAB
+ -----END RSA PUBLIC KEY-----
+ '';
+ };
+ };
+ secure = true;
+ };
+ };
+ users = addNames {
+ mv = {
+ mail = "mv@cd.retiolum";
+ pubkey = readFile ../../Zpubkeys/mv_vod.ssh.pub;
+ };
+ tv = {
+ mail = "tv@wu.retiolum";
+ pubkey = readFile ../../Zpubkeys/tv_wu.ssh.pub;
+ };
+ };
+ };
+
+in
+out
diff --git a/krebs/3modules/git.nix b/krebs/3modules/git.nix
new file mode 100644
index 000000000..64b7820b2
--- /dev/null
+++ b/krebs/3modules/git.nix
@@ -0,0 +1,486 @@
+{ config, pkgs, lib, ... }:
+
+# TODO unify logging of shell scripts to user and journal
+# TODO move all scripts to ${etcDir}, so ControlMaster connections
+# immediately pick up new authenticators
+# TODO when authorized_keys changes, then restart ssh
+# (or kill already connected users somehow)
+
+with import ../4lib { inherit lib; };
+let
+ cfg = config.krebs.git;
+
+ out = {
+ options.krebs.git = api;
+ config = mkIf cfg.enable (mkMerge [
+ (mkIf cfg.cgit cgit-imp)
+ git-imp
+ ]);
+ };
+
+ api = {
+ enable = mkEnableOption "krebs.git";
+
+ cgit = mkOption {
+ type = types.bool;
+ default = true;
+ description = ''
+ Enable cgit.
+ Cgit is an attempt to create a fast web interface for the git version
+ control system, using a built in cache to decrease pressure on the
+ git server.
+ cgit in this module is being served via fastcgi nginx.This module
+ deploys a http://cgit.<hostname> nginx configuration and enables nginx
+ if not yet enabled.
+ '';
+ };
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/git";
+ description = "Directory used to store repositories.";
+ };
+ etcDir = mkOption {
+ type = types.str;
+ default = "/etc/git";
+ };
+ repos = mkOption {
+ type = types.attrsOf (types.submodule ({
+ options = {
+ desc = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Repository description.
+ '';
+ };
+ section = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Repository section.
+ '';
+ };
+ name = mkOption {
+ type = types.str;
+ description = ''
+ Repository name.
+ '';
+ };
+ hooks = mkOption {
+ type = types.attrsOf types.str;
+ default = {};
+ description = ''
+ Repository-specific hooks.
+ '';
+ };
+ public = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''
+ Allow everybody to read the repository via HTTP if cgit enabled.
+ '';
+ # TODO allow every configured user to fetch the repository via SSH.
+ };
+ };
+ }));
+
+ default = {};
+
+ example = literalExample ''
+ {
+ testing = {
+ name = "testing";
+ hooks.post-update = '''
+ #! /bin/sh
+ set -euf
+ echo post-update hook: $* >&2
+ ''';
+ };
+ testing2 = { name = "testing2"; };
+ }
+ '';
+
+ description = ''
+ Repositories.
+ '';
+ };
+ root-desc = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Text printed below the heading on the repository index page.
+ Default value: "a fast webinterface for the git dscm".
+ '';
+ };
+ root-title = mkOption {
+ type = types.nullOr types.str;
+ default = null;
+ description = ''
+ Text printed as heading on the repository index page.
+ Default value: "Git Repository Browser".
+ '';
+ };
+ rules = mkOption {
+ type = types.unspecified;
+ };
+ };
+
+ git-imp = {
+ system.activationScripts.git-init = "${init-script}";
+
+ # TODO maybe put all scripts here and then use PATH?
+ environment.etc."${etc-base}".source =
+ scriptFarm "git-ssh-authorizers" {
+ authorize-command = makeAuthorizeScript (map ({ repo, user, perm }: [
+ (map getName (ensureList user))
+ (map getName (ensureList repo))
+ (map getName perm.allow-commands)
+ ]) cfg.rules);
+
+ authorize-push = makeAuthorizeScript (map ({ repo, user, perm }: [
+ (map getName (ensureList user))
+ (map getName (ensureList repo))
+ (ensureList perm.allow-receive-ref)
+ (map getName perm.allow-receive-modes)
+ ]) (filter (x: hasAttr "allow-receive-ref" x.perm) cfg.rules));
+ };
+
+ users.extraUsers = singleton {
+ description = "Git repository hosting user";
+ name = "git";
+ shell = "/bin/sh";
+ openssh.authorizedKeys.keys =
+ mapAttrsToList (_: makeAuthorizedKey git-ssh-command)
+ config.krebs.users;
+ uid = 129318403; # genid git
+ };
+ };
+
+ cgit-imp = {
+ users.extraUsers = lib.singleton {
+ inherit (fcgitwrap-user) group name uid;
+ home = toString (pkgs.runCommand "empty" {} "mkdir -p $out");
+ };
+
+ users.extraGroups = lib.singleton {
+ inherit (fcgitwrap-group) gid name;
+ };
+
+ services.fcgiwrap = {
+ enable = true;
+ user = fcgitwrap-user.name;
+ group = fcgitwrap-user.group;
+ # socketAddress = "/run/fcgiwrap.sock" (default)
+ # socketType = "unix" (default)
+ };
+
+ environment.etc."cgitrc".text = ''
+ css=/static/cgit.css
+ logo=/static/cgit.png
+
+ # if you do not want that webcrawler (like google) index your site
+ robots=noindex, nofollow
+
+ virtual-root=/
+
+ # TODO make this nicer (and/or somewhere else)
+ cache-root=/tmp/cgit
+
+ cache-size=1000
+ enable-commit-graph=1
+ enable-index-links=1
+ enable-index-owner=0
+ enable-log-filecount=1
+ enable-log-linecount=1
+ enable-remote-branches=1
+
+ ${optionalString (cfg.root-title != null) "root-title=${cfg.root-title}"}
+ ${optionalString (cfg.root-desc != null) "root-desc=${cfg.root-desc}"}
+
+ snapshots=0
+ max-stats=year
+
+ ${concatMapStringsSep "\n" (repo: ''
+ repo.url=${repo.name}
+ repo.path=${cfg.dataDir}/${repo.name}
+ ${optionalString (repo.section != null) "repo.section=${repo.section}"}
+ ${optionalString (repo.desc != null) "repo.desc=${repo.desc}"}
+ '') (filter isPublicRepo (attrValues cfg.repos))}
+ '';
+
+ system.activationScripts.cgit = ''
+ mkdir -m 0700 -p /tmp/cgit
+ chown ${toString fcgitwrap-user.uid}:${toString fcgitwrap-group.gid} /tmp/cgit
+ '';
+
+ krebs.nginx = {
+ enable = true;
+ servers.cgit = {
+ server-names = [
+ "cgit.${config.networking.hostName}"
+ "cgit.${config.networking.hostName}.retiolum"
+ ];
+ locations = [
+ (nameValuePair "/" ''
+ include ${pkgs.nginx}/conf/fastcgi_params;
+ fastcgi_param SCRIPT_FILENAME ${pkgs.cgit}/cgit/cgit.cgi;
+ fastcgi_param PATH_INFO $uri;
+ fastcgi_param QUERY_STRING $args;
+ fastcgi_param HTTP_HOST $server_name;
+ fastcgi_pass unix:${config.services.fcgiwrap.socketAddress};
+ '')
+ (nameValuePair "/static/" ''
+ root ${pkgs.cgit}/cgit;
+ rewrite ^/static(/.*)$ $1 break;
+ '')
+ ];
+ };
+ };
+ };
+
+ fcgitwrap-user = {
+ name = "fcgiwrap";
+ uid = 2867890860; # genid fcgiwrap
+ group = "fcgiwrap";
+ };
+
+ fcgitwrap-group = {
+ name = fcgitwrap-user.name;
+ gid = fcgitwrap-user.uid;
+ };
+
+
+ ensureList = x:
+ if typeOf x == "list" then x else [x];
+
+ getName = x: x.name;
+
+ isPublicRepo = getAttr "public"; # TODO this is also in ./cgit.nix
+
+ makeAuthorizedKey = git-ssh-command: user@{ name, pubkey, ... }:
+ # TODO assert name
+ # TODO assert pubkey
+ let
+ options = concatStringsSep "," [
+ ''command="exec ${git-ssh-command} ${name}"''
+ "no-agent-forwarding"
+ "no-port-forwarding"
+ "no-pty"
+ "no-X11-forwarding"
+ ];
+ in
+ "${options} ${pubkey}";
+
+ # [case-pattern] -> shell-script
+ # Create a shell script that succeeds (exit 0) when all its arguments
+ # match the case patterns (in the given order).
+ makeAuthorizeScript =
+ let
+ # TODO escape
+ to-pattern = x: concatStringsSep "|" (ensureList x);
+ go = i: ps:
+ if ps == []
+ then "exit 0"
+ else ''
+ case ''$${toString i} in ${to-pattern (head ps)})
+ ${go (i + 1) (tail ps)}
+ esac'';
+ in
+ patterns: ''
+ #! /bin/sh
+ set -euf
+ ${concatStringsSep "\n" (map (go 1) patterns)}
+ exit -1
+ '';
+
+ reponames = rules: sort lessThan (unique (map (x: x.repo.name) rules));
+
+ # TODO makeGitHooks that uses runCommand instead of scriptFarm?
+ scriptFarm =
+ farm-name: scripts:
+ let
+ makeScript = script-name: script-string: {
+ name = script-name;
+ path = pkgs.writeScript "${farm-name}_${script-name}" script-string;
+ };
+ in
+ pkgs.linkFarm farm-name (mapAttrsToList makeScript scripts);
+
+
+ git-ssh-command = pkgs.writeScript "git-ssh-command" ''
+ #! /bin/sh
+ set -euf
+
+ PATH=${makeSearchPath "bin" (with pkgs; [
+ coreutils
+ git
+ gnugrep
+ gnused
+ systemd
+ ])}
+
+ abort() {
+ echo "error: $1" >&2
+ systemd-cat -p err -t git echo "error: $1"
+ exit -1
+ }
+
+ GIT_SSH_USER=$1
+
+ systemd-cat -p info -t git echo \
+ "authorizing $GIT_SSH_USER $SSH_CONNECTION $SSH_ORIGINAL_COMMAND"
+
+ # References: The Base Definitions volume of
+ # POSIX.1‐2013, Section 3.278, Portable Filename Character Set
+ portable_filename_bre="^[A-Za-z0-9._-]\\+$"
+
+ command=$(echo "$SSH_ORIGINAL_COMMAND" \
+ | sed -n 's/^\([^ ]*\) '"'"'\(.*\)'"'"'/\1/p' \
+ | grep "$portable_filename_bre" \
+ || abort 'cannot read command')
+
+ GIT_SSH_REPO=$(echo "$SSH_ORIGINAL_COMMAND" \
+ | sed -n 's/^\([^ ]*\) '"'"'\(.*\)'"'"'/\2/p' \
+ | grep "$portable_filename_bre" \
+ || abort 'cannot read reponame')
+
+ ${cfg.etcDir}/authorize-command \
+ "$GIT_SSH_USER" "$GIT_SSH_REPO" "$command" \
+ || abort 'access denied'
+
+ repodir=${escapeShellArg cfg.dataDir}/$GIT_SSH_REPO
+
+ systemd-cat -p info -t git \
+ echo "authorized exec $command $repodir"
+
+ export GIT_SSH_USER
+ export GIT_SSH_REPO
+ exec "$command" "$repodir"
+ '';
+
+ init-script = pkgs.writeScript "git-init" ''
+ #! /bin/sh
+ set -euf
+
+ PATH=${makeSearchPath "bin" (with pkgs; [
+ coreutils
+ findutils
+ gawk
+ git
+ gnugrep
+ gnused
+ ])}
+
+ dataDir=${escapeShellArg cfg.dataDir}
+ mkdir -p "$dataDir"
+
+ # Notice how the presence of hooks symlinks determine whether
+ # we manage a repositry or not.
+
+ # Make sure that no existing repository has hooks. We can delete
+ # symlinks because we assume we created them.
+ find "$dataDir" -mindepth 2 -maxdepth 2 -name hooks -type l -delete
+ bad_hooks=$(find "$dataDir" -mindepth 2 -maxdepth 2 -name hooks)
+ if echo "$bad_hooks" | grep -q .; then
+ printf 'error: unknown hooks:\n%s\n' \
+ "$(echo "$bad_hooks" | sed 's/^/ /')" \
+ >&2
+ exit -1
+ fi
+
+ # Initialize repositories.
+ ${concatMapStringsSep "\n" (repo:
+ let
+ hooks = scriptFarm "git-hooks" (makeHooks repo);
+ in
+ ''
+ reponame=${escapeShellArg repo.name}
+ repodir=$dataDir/$reponame
+ mode=${toString (if isPublicRepo repo then 0711 else 0700)}
+ if ! test -d "$repodir"; then
+ mkdir -m "$mode" "$repodir"
+ git init --bare --template=/var/empty "$repodir"
+ chown -R git:nogroup "$repodir"
+ fi
+ ln -s ${hooks} "$repodir/hooks"
+ ''
+ ) (attrValues cfg.repos)}
+
+ # Warn about repositories that exist but aren't mentioned in the
+ # current configuration (and thus didn't receive a hooks symlink).
+ unknown_repos=$(find "$dataDir" -mindepth 1 -maxdepth 1 \
+ -type d \! -exec test -e '{}/hooks' \; -print)
+ if echo "$unknown_repos" | grep -q .; then
+ printf 'warning: stale repositories:\n%s\n' \
+ "$(echo "$unknown_repos" | sed 's/^/ /')" \
+ >&2
+ fi
+ '';
+
+ makeHooks = repo: removeAttrs repo.hooks [ "pre-receive" ] // {
+ pre-receive = ''
+ #! /bin/sh
+ set -euf
+
+ PATH=${makeSearchPath "bin" (with pkgs; [
+ coreutils # env
+ git
+ systemd
+ ])}
+
+ accept() {
+ #systemd-cat -p info -t git echo "authorized $1"
+ accept_string="''${accept_string+$accept_string
+ }authorized $1"
+ }
+ reject() {
+ #systemd-cat -p err -t git echo "denied $1"
+ #echo 'access denied' >&2
+ #exit_code=-1
+ reject_string="''${reject_string+$reject_string
+ }access denied: $1"
+ }
+
+ empty=0000000000000000000000000000000000000000
+
+ accept_string=
+ reject_string=
+ while read oldrev newrev ref; do
+
+ if [ $oldrev = $empty ]; then
+ receive_mode=create
+ elif [ $newrev = $empty ]; then
+ receive_mode=delete
+ elif [ "$(git merge-base $oldrev $newrev)" = $oldrev ]; then
+ receive_mode=fast-forward
+ else
+ receive_mode=non-fast-forward
+ fi
+
+ if ${cfg.etcDir}/authorize-push \
+ "$GIT_SSH_USER" "$GIT_SSH_REPO" "$ref" "$receive_mode"; then
+ accept "$receive_mode $ref"
+ else
+ reject "$receive_mode $ref"
+ fi
+ done
+
+ if [ -n "$reject_string" ]; then
+ systemd-cat -p err -t git echo "$reject_string"
+ exit -1
+ fi
+
+ systemd-cat -p info -t git echo "$accept_string"
+
+ ${optionalString (hasAttr "post-receive" repo.hooks) ''
+ # custom post-receive hook
+ ${repo.hooks.post-receive}''}
+ '';
+ };
+
+ etc-base =
+ assert (hasPrefix "/etc/" cfg.etcDir);
+ removePrefix "/etc/" cfg.etcDir;
+
+in
+out
diff --git a/krebs/3modules/github-hosts-sync.nix b/krebs/3modules/github-hosts-sync.nix
new file mode 100644
index 000000000..0274b9d15
--- /dev/null
+++ b/krebs/3modules/github-hosts-sync.nix
@@ -0,0 +1,83 @@
+{ config, lib, pkgs, ... }:
+
+with builtins;
+with lib;
+let
+ cfg = config.krebs.github-hosts-sync;
+
+ out = {
+ options.krebs.github-hosts-sync = api;
+ config = mkIf cfg.enable imp;
+ };
+
+ api = {
+ enable = mkEnableOption "krebs.github-hosts-sync";
+ port = mkOption {
+ type = types.int; # TODO port type
+ default = 1028;
+ };
+ dataDir = mkOption {
+ type = types.str; # TODO path (but not just into store)
+ default = "/var/lib/github-hosts-sync";
+ };
+ ssh-identity-file = mkOption {
+ type = types.str; # TODO must be named *.ssh.{id_rsa,id_ed25519}
+ default = "/root/src/secrets/github-hosts-sync.ssh.id_rsa";
+ };
+ };
+
+ imp = {
+ systemd.services.github-hosts-sync = {
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ environment = {
+ port = toString cfg.port;
+ };
+ serviceConfig = {
+ PermissionsStartOnly = "true";
+ SyslogIdentifier = "github-hosts-sync";
+ User = user.name;
+ Restart = "always";
+ ExecStartPre = pkgs.writeScript "github-hosts-sync-init" ''
+ #! /bin/sh
+ set -euf
+
+ ssh_identity_file_target=$(
+ case ${cfg.ssh-identity-file} in
+ *.ssh.id_rsa|*.ssh.id_ed25519) echo ${cfg.dataDir}/.ssh/id_rsa;;
+ *.ssh.id_ed25519) echo ${cfg.dataDir}/.ssh/id_ed25519;;
+ *)
+ echo "bad identity file name: ${cfg.ssh-identity-file}" >&2
+ exit 1
+ esac
+ )
+
+ mkdir -p ${cfg.dataDir}
+ chown ${user.name}: ${cfg.dataDir}
+
+ install \
+ -o ${user.name} \
+ -m 0400 \
+ ${cfg.ssh-identity-file} \
+ "$ssh_identity_file_target"
+
+ ln -snf ${kpkgs.github-known_hosts} ${cfg.dataDir}/.ssh/known_hosts
+ '';
+ ExecStart = "${kpkgs.github-hosts-sync}/bin/github-hosts-sync";
+ };
+ };
+
+ users.extraUsers = singleton {
+ inherit (user) name uid;
+ home = cfg.dataDir;
+ };
+ };
+
+ user = {
+ name = "github-hosts-sync";
+ uid = 3220554646; # genid github-hosts-sync
+ };
+
+ kpkgs = import ../../krebs/5pkgs { inherit pkgs; };
+in
+out
diff --git a/krebs/3modules/nginx.nix b/krebs/3modules/nginx.nix
new file mode 100644
index 000000000..702e8a7f6
--- /dev/null
+++ b/krebs/3modules/nginx.nix
@@ -0,0 +1,72 @@
+{ config, pkgs, lib, ... }:
+
+with builtins;
+with lib;
+let
+ cfg = config.krebs.nginx;
+
+ out = {
+ options.krebs.nginx = api;
+ config = mkIf cfg.enable imp;
+ };
+
+ api = {
+ enable = mkEnableOption "krebs.nginx";
+
+ servers = mkOption {
+ type = with types; attrsOf optionSet;
+ options = singleton {
+ server-names = mkOption {
+ type = with types; listOf str;
+ # TODO use identity
+ default = [
+ "${config.networking.hostName}"
+ "${config.networking.hostName}.retiolum"
+ ];
+ };
+ locations = mkOption {
+ type = with types; listOf (attrsOf str);
+ };
+ };
+ default = {};
+ };
+ };
+
+ imp = {
+ services.nginx = {
+ enable = true;
+ httpConfig = ''
+ include ${pkgs.nginx}/conf/mime.types;
+ default_type application/octet-stream;
+ sendfile on;
+ keepalive_timeout 65;
+ gzip on;
+ server {
+ listen 80 default_server;
+ server_name _;
+ return 404;
+ }
+ ${concatStrings (mapAttrsToList (_: to-server) cfg.servers)}
+ '';
+ };
+ };
+
+
+ indent = replaceChars ["\n"] ["\n "];
+
+ to-location = { name, value }: ''
+ location ${name} {
+ ${indent value}
+ }
+ '';
+
+ to-server = { server-names, locations, ... }: ''
+ server {
+ listen 80;
+ server_name ${toString server-names};
+ ${indent (concatStrings (map to-location locations))}
+ }
+ '';
+
+in
+out
diff --git a/krebs/3modules/retiolum.nix b/krebs/3modules/retiolum.nix
new file mode 100644
index 000000000..481d6565c
--- /dev/null
+++ b/krebs/3modules/retiolum.nix
@@ -0,0 +1,226 @@
+{ config, pkgs, lib, ... }:
+
+with builtins;
+with lib;
+let
+ cfg = config.krebs.retiolum;
+
+ out = {
+ options.krebs.retiolum = api;
+ config = mkIf cfg.enable imp;
+ };
+
+ api = {
+ enable = mkEnableOption "krebs.retiolum";
+
+ name = mkOption {
+ type = types.str;
+ default = config.networking.hostName;
+ # Description stolen from tinc.conf(5).
+ description = ''
+ This is the name which identifies this tinc daemon. It must
+ be unique for the virtual private network this daemon will
+ connect to. The Name may only consist of alphanumeric and
+ underscore characters. If Name starts with a $, then the
+ contents of the environment variable that follows will be
+ used. In that case, invalid characters will be converted to
+ underscores. If Name is $HOST, but no such environment
+ variable exist, the hostname will be read using the
+ gethostnname() system call This is the name which identifies
+ the this tinc daemon.
+ '';
+ };
+
+ generateEtcHosts = mkOption {
+ type = types.str;
+ default = "both";
+ description = ''
+ If set to <literal>short</literal>, <literal>long</literal>, or <literal>both</literal>,
+ then generate entries in <filename>/etc/hosts</filename> from subnets.
+ '';
+ };
+
+ network = mkOption {
+ type = types.str;
+ default = "retiolum";
+ description = ''
+ The tinc network name.
+ It is used to generate long host entries,
+ and name the TUN device.
+ '';
+ };
+
+ tincPackage = mkOption {
+ type = types.package;
+ default = pkgs.tinc;
+ description = "Tincd package to use.";
+ };
+
+ hosts = mkOption {
+ type = with types; either package path;
+ default = ../../Zhosts;
+ description = ''
+ If a path is given, then it will be used to generate an ad-hoc package.
+ '';
+ };
+
+ iproutePackage = mkOption {
+ type = types.package;
+ default = pkgs.iproute;
+ description = "Iproute2 package to use.";
+ };
+
+
+ privateKeyFile = mkOption {
+ # TODO if it's types.path then it gets copied to /nix/store with
+ # bad unsafe permissions...
+ type = types.str;
+ default = "/root/src/secrets/retiolum.rsa_key.priv";
+ description = ''
+ Generate file with <literal>tincd -K</literal>.
+ This file must exist on the local system. The default points to
+ <secrets/retiolum.rsa_key.priv>.
+ '';
+ };
+
+ connectTo = mkOption {
+ type = types.listOf types.str;
+ default = [ "fastpoke" "pigstarter" "gum" ];
+ description = ''
+ The list of hosts in the network which the client will try to connect
+ to. These hosts should have an 'Address' configured which points to a
+ routeable IPv4 or IPv6 address.
+ '';
+ };
+
+ };
+
+ imp = {
+ environment.systemPackages = [ tinc hosts iproute ];
+
+ networking.extraHosts = retiolumExtraHosts;
+
+ systemd.services.retiolum = {
+ description = "Tinc daemon for Retiolum";
+ after = [ "network.target" ];
+ wantedBy = [ "multi-user.target" ];
+ path = [ tinc iproute ];
+ serviceConfig = {
+ PermissionsStartOnly = "true";
+ PrivateTmp = "true";
+ Restart = "always";
+ # TODO we cannot chroot (-R) b/c we use symlinks to hosts
+ # and the private key.
+ ExecStartPre = pkgs.writeScript "retiolum-init" ''
+ #! /bin/sh
+ install -o ${user.name} -m 0400 ${cfg.privateKeyFile} /tmp/retiolum-rsa_key.priv
+ '';
+ ExecStart = "${tinc}/sbin/tincd -c ${confDir} -d 0 -U ${user.name} -D";
+ SyslogIdentifier = "retiolum";
+ };
+ };
+
+ users.extraUsers = singleton {
+ inherit (user) name uid;
+ };
+ };
+
+ user = {
+ name = "retiolum";
+ uid = 301281149; # genid retiolum
+ };
+
+ tinc = cfg.tincPackage;
+
+ hosts = getAttr (typeOf cfg.hosts) {
+ package = cfg.hosts;
+ path = pkgs.stdenv.mkDerivation {
+ name = "custom-retiolum-hosts";
+ src = cfg.hosts;
+ installPhase = ''
+ mkdir $out
+ find . -name .git -prune -o -type f -print0 \
+ | xargs -0 cp --target-directory $out
+ '';
+ };
+ };
+
+ iproute = cfg.iproutePackage;
+
+ retiolumExtraHosts = import (pkgs.runCommand "retiolum-etc-hosts"
+ { }
+ ''
+ generate() {
+ (cd ${hosts}
+ printf \'\'
+ for i in `ls`; do
+ names=$(hostnames $i)
+ for j in `sed -En 's|^ *Aliases *= *(.+)|\1|p' $i`; do
+ names="$names $(hostnames $j)"
+ done
+ sed -En '
+ s|^ *Subnet *= *([^ /]*)(/[0-9]*)? *$|\1 '"$names"'|p
+ ' $i
+ done | sort
+ printf \'\'
+ )
+ }
+
+ case ${cfg.generateEtcHosts} in
+ short)
+ hostnames() { echo "$1"; }
+ generate
+ ;;
+ long)
+ hostnames() { echo "$1.${cfg.network}"; }
+ generate
+ ;;
+ both)
+ hostnames() { echo "$1.${cfg.network} $1"; }
+ generate
+ ;;
+ *)
+ echo '""'
+ ;;
+ esac > $out
+ '');
+
+
+ confDir = pkgs.runCommand "retiolum" {
+ # TODO text
+ executable = true;
+ preferLocalBuild = true;
+ } ''
+ set -euf
+
+ mkdir -p $out
+
+ ln -s ${hosts} $out/hosts
+
+ cat > $out/tinc.conf <<EOF
+ Name = ${cfg.name}
+ Device = /dev/net/tun
+ Interface = ${cfg.network}
+ ${concatStrings (map (c : "ConnectTo = " + c + "\n") cfg.connectTo)}
+ PrivateKeyFile = /tmp/retiolum-rsa_key.priv
+ EOF
+
+ # source: krebscode/painload/retiolum/scripts/tinc_setup/tinc-up
+ cat > $out/tinc-up <<EOF
+ host=$out/hosts/${cfg.name}
+ ${iproute}/sbin/ip link set \$INTERFACE up
+
+ addr4=\$(sed -n 's|^ *Subnet *= *\(10[.][^ ]*\) *$|\1|p' \$host)
+ if [ -n "\$addr4" ];then
+ ${iproute}/sbin/ip -4 addr add \$addr4 dev \$INTERFACE
+ ${iproute}/sbin/ip -4 route add 10.243.0.0/16 dev \$INTERFACE
+ fi
+ addr6=\$(sed -n 's|^ *Subnet *= *\(42[:][^ ]*\) *$|\1|p' \$host)
+ ${iproute}/sbin/ip -6 addr add \$addr6 dev \$INTERFACE
+ ${iproute}/sbin/ip -6 route add 42::/16 dev \$INTERFACE
+ EOF
+
+ chmod +x $out/tinc-up
+ '';
+
+in out
diff --git a/krebs/3modules/urlwatch.nix b/krebs/3modules/urlwatch.nix
new file mode 100644
index 000000000..39d9fec54
--- /dev/null
+++ b/krebs/3modules/urlwatch.nix
@@ -0,0 +1,138 @@
+{ config, lib, pkgs, ... }:
+
+# TODO multiple users
+# TODO inform about unused caches
+# cache = url: "${cfg.dataDir}/.urlwatch/cache/${hashString "sha1" url}"
+# TODO hooks.py
+
+with builtins;
+with lib;
+let
+ cfg = config.krebs.urlwatch;
+
+ # TODO assert sendmail's existence
+ out = {
+ options.krebs.urlwatch = api;
+ config = mkIf cfg.enable imp;
+ };
+
+ api = {
+ enable = mkEnableOption "krebs.urlwatch";
+
+ dataDir = mkOption {
+ type = types.str;
+ default = "/var/lib/urlwatch";
+ description = ''
+ Directory where the urlwatch service should store its state.
+ '';
+ };
+ from = mkOption {
+ type = types.str;
+ default = "${user.name}@${config.networking.hostName}.retiolum";
+ description = ''
+ Content of the From: header of the generated mails.
+ '';
+ };
+ mailto = mkOption {
+ type = types.str;
+ default = config.krebs.build.user.mail;
+ description = ''
+ Content of the To: header of the generated mails. [AKA recipient :)]
+ '';
+ };
+ onCalendar = mkOption {
+ type = types.str;
+ default = "04:23";
+ description = ''
+ Run urlwatch at this interval.
+ The format is described in systemd.time(7), CALENDAR EVENTS.
+ '';
+ };
+ urls = mkOption {
+ type = with types; listOf str;
+ default = [];
+ description = "URL to watch.";
+ example = [
+ https://nixos.org/channels/nixos-unstable/git-revision
+ ];
+ };
+ };
+
+ urlsFile = toFile "urls" (concatStringsSep "\n" cfg.urls);
+
+ imp = {
+ systemd.timers.urlwatch = {
+ wantedBy = [ "timers.target" ];
+ timerConfig = {
+ OnCalendar = cfg.onCalendar;
+ Persistent = "true";
+ };
+ };
+ systemd.services.urlwatch = {
+ path = with pkgs; [
+ coreutils
+ gnused
+ urlwatch
+ ];
+ environment = {
+ HOME = cfg.dataDir;
+ LC_ALL = "en_US.UTF-8";
+ LOCALE_ARCHIVE = "${pkgs.glibcLocales}/lib/locale/locale-archive";
+ SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
+ };
+ serviceConfig = {
+ User = user.name;
+ PermissionsStartOnly = "true";
+ PrivateTmp = "true";
+ Type = "oneshot";
+ ExecStartPre =
+ pkgs.writeScript "urlwatch-prestart" ''
+ #! /bin/sh
+ set -euf
+
+ dataDir=$HOME
+
+ if ! test -e "$dataDir"; then
+ mkdir -m 0700 -p "$dataDir"
+ chown ${user.name}: "$dataDir"
+ fi
+ '';
+ ExecStart = pkgs.writeScript "urlwatch" ''
+ #! /bin/sh
+ set -euf
+
+ from=${escapeShellArg cfg.from}
+ mailto=${escapeShellArg cfg.mailto}
+ urlsFile=${escapeShellArg urlsFile}
+
+ cd /tmp
+
+ urlwatch -e --urls="$urlsFile" > changes 2>&1 || :
+
+ if test -s changes; then
+ date=$(date -R)
+ subject=$(sed -n 's/^\(CHANGED\|ERROR\|NEW\): //p' changes \
+ | tr \\n \ )
+ {
+ echo "Date: $date"
+ echo "From: $from"
+ echo "Subject: $subject"
+ echo "To: $mailto"
+ echo
+ cat changes
+ } | /var/setuid-wrappers/sendmail -t
+ fi
+ '';
+ };
+ };
+ users.extraUsers = singleton {
+ inherit (user) name uid;
+ };
+ };
+
+ user = {
+ name = "urlwatch";
+ uid = 3467631196; # genid urlwatch
+ };
+in
+out
diff --git a/krebs/4lib/default.nix b/krebs/4lib/default.nix
new file mode 100644
index 000000000..b67585335
--- /dev/null
+++ b/krebs/4lib/default.nix
@@ -0,0 +1,18 @@
+{ lib, ... }:
+
+with builtins;
+with lib;
+
+builtins // lib // rec {
+
+ addName = name: set:
+ set // { inherit name; };
+
+ addNames = mapAttrs addName;
+
+ types = import ./types.nix { inherit lib; };
+
+ dns = import ./dns.nix { inherit lib; };
+ listset = import ./listset.nix { inherit lib; };
+ tree = import ./tree.nix { inherit lib; };
+}
diff --git a/krebs/4lib/dns.nix b/krebs/4lib/dns.nix
new file mode 100644
index 000000000..b2cf3c24c
--- /dev/null
+++ b/krebs/4lib/dns.nix
@@ -0,0 +1,31 @@
+{ lib, ... }:
+
+let
+ listset = import ./listset.nix { inherit lib; };
+in
+
+with builtins;
+with lib;
+
+rec {
+ # label = string
+
+ # TODO does it make sense to have alias = list label?
+
+ # split-by-provider :
+ # [[label]] -> tree label provider -> listset provider alias
+ split-by-provider = as: providers:
+ foldl (m: a: listset.insert (provider-of a providers) a m) {} as;
+
+ # provider-of : alias -> tree label provider -> provider
+ # Note that we cannot use tree.get here, because path can be longer
+ # than the tree depth.
+ provider-of = a:
+ let
+ go = path: tree:
+ if typeOf tree == "string"
+ then tree
+ else go (tail path) tree.${head path};
+ in
+ go (reverseList (splitString "." a));
+}
diff --git a/krebs/4lib/listset.nix b/krebs/4lib/listset.nix
new file mode 100644
index 000000000..3aae22f20
--- /dev/null
+++ b/krebs/4lib/listset.nix
@@ -0,0 +1,11 @@
+{ lib, ... }:
+
+with lib;
+
+rec {
+ # listset k v = set k [v]
+
+ # insert : k -> v -> listset k v -> listset k v
+ insert = name: value: set:
+ set // { ${name} = set.${name} or [] ++ [value]; };
+}
diff --git a/krebs/4lib/tree.nix b/krebs/4lib/tree.nix
new file mode 100644
index 000000000..1cd83b3f6
--- /dev/null
+++ b/krebs/4lib/tree.nix
@@ -0,0 +1,13 @@
+{ lib, ... }:
+
+with lib;
+
+rec {
+ # tree k v = set k (either v (tree k v))
+
+ # get : [k] -> tree k v -> v
+ get = path: tree:
+ if length path > 0
+ then get (tail path) tree.${head path} # TODO check if elem exists
+ else tree;
+}
diff --git a/krebs/4lib/types.nix b/krebs/4lib/types.nix
new file mode 100644
index 000000000..92410dd58
--- /dev/null
+++ b/krebs/4lib/types.nix
@@ -0,0 +1,109 @@
+{ lib, ... }:
+
+with lib;
+with types;
+
+types // rec {
+
+ host = submodule {
+ options = {
+ name = mkOption {
+ type = label;
+ };
+ dc = mkOption {
+ type = label;
+ };
+ cores = mkOption {
+ type = positive;
+ };
+ nets = mkOption {
+ type = attrsOf net;
+ apply = x: assert hasAttr "retiolum" x; x;
+ };
+ 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
+ '';
+ };
+ };
+ };
+
+ net = submodule ({ config, ... }: {
+ options = {
+ via = mkOption {
+ type = nullOr net;
+ default = null;
+ };
+ addrs = mkOption {
+ type = listOf addr;
+ apply = _: config.addrs4 ++ config.addrs6;
+ };
+ addrs4 = mkOption {
+ type = listOf addr4;
+ default = [];
+ };
+ addrs6 = mkOption {
+ type = listOf addr6;
+ default = [];
+ };
+ aliases = mkOption {
+ # TODO nonEmptyListOf hostname
+ type = listOf hostname;
+ };
+ tinc = mkOption {
+ type = let net-config = config; in nullOr (submodule ({ config, ... }: {
+ options = {
+ config = mkOption {
+ type = str;
+ apply = _: ''
+ ${optionalString (net-config.via != null)
+ (concatMapStringsSep "\n" (a: "Address = ${a}") net-config.via.addrs)}
+ ${concatMapStringsSep "\n" (a: "Subnet = ${a}") net-config.addrs}
+ ${config.pubkey}
+ '';
+ };
+ pubkey = mkOption {
+ type = str;
+ };
+ };
+ }));
+ default = null;
+ };
+ };
+ });
+
+ positive = mkOptionType {
+ name = "positive integer";
+ check = x: isInt x && x > 0;
+ merge = mergeOneOption;
+ };
+
+ user = submodule {
+ options = {
+ mail = mkOption {
+ type = str; # TODO retiolum mail address
+ };
+ name = mkOption {
+ type = str; # TODO
+ };
+ pubkey = mkOption {
+ type = str;
+ };
+ pubkeys = mkOption {
+ type = attrsOf str;
+ default = {};
+ };
+ };
+ };
+
+ # TODO
+ addr = str;
+ addr4 = str;
+ addr6 = str;
+ hostname = str;
+ label = str;
+}
diff --git a/krebs/5pkgs/default.nix b/krebs/5pkgs/default.nix
new file mode 100644
index 000000000..231fda797
--- /dev/null
+++ b/krebs/5pkgs/default.nix
@@ -0,0 +1,14 @@
+{ pkgs, ... }:
+
+let
+ inherit (pkgs) callPackage;
+in
+
+pkgs //
+{
+ dic = callPackage ./dic.nix {};
+ genid = callPackage ./genid.nix {};
+ github-hosts-sync = callPackage ./github-hosts-sync.nix {};
+ github-known_hosts = callPackage ./github-known_hosts.nix {};
+ hashPassword = callPackage ./hashPassword.nix {};
+}
diff --git a/krebs/5pkgs/dic.nix b/krebs/5pkgs/dic.nix
new file mode 100644
index 000000000..571773d22
--- /dev/null
+++ b/krebs/5pkgs/dic.nix
@@ -0,0 +1,36 @@
+{ stdenv, fetchgit, coreutils, curl, gnused, gnugrep, ... }:
+
+stdenv.mkDerivation {
+ name = "dic";
+
+ src = fetchgit {
+ url = https://github.com/krebscode/painload;
+ rev = "35ccac73d563ad30d2851b9aeed4cfef69ff74e3";
+ sha256 = "1y1fs2p3xj2yrqpw0h5kd0f3c5p1y70xk1hjnw99sr33r67s9c35";
+ };
+
+ phases = [
+ "unpackPhase"
+ "installPhase"
+ ];
+
+ installPhase =
+ let
+ path = stdenv.lib.makeSearchPath "bin" [
+ coreutils
+ curl
+ gnused
+ gnugrep
+ ];
+ in
+ ''
+ mkdir -p $out/bin
+
+ sed \
+ 's,^main() {$,&\n PATH=${path}; export PATH,' \
+ < ./util/bin/dic \
+ > $out/bin/dic
+
+ chmod +x $out/bin/dic
+ '';
+}
diff --git a/krebs/5pkgs/genid.nix b/krebs/5pkgs/genid.nix
new file mode 100644
index 000000000..c75bec317
--- /dev/null
+++ b/krebs/5pkgs/genid.nix
@@ -0,0 +1,22 @@
+{ lib, pkgs, ... }:
+
+pkgs.writeScriptBin "genid" ''
+ #! /bin/sh
+ # usage: genid NAME
+ set -euf
+
+ export PATH=${lib.makeSearchPath "bin" (with pkgs; [
+ bc
+ coreutils
+ ])}
+
+ name=$1
+ hash=$(printf %s "$name" | sha1sum | cut -d\ -f1 | tr a-f A-F)
+ echo "
+ min=2^24 # bigger than nobody and nogroup, see <nixos/modules/misc/ids.nix>
+ # and some spare for stuff like lxd.
+ max=2^32 # see 2^(8*sizeof(uid_t))
+ ibase=16
+ ($hash + min) % max
+ " | bc
+''
diff --git a/krebs/5pkgs/github-hosts-sync.nix b/krebs/5pkgs/github-hosts-sync.nix
new file mode 100644
index 000000000..d69b2b12b
--- /dev/null
+++ b/krebs/5pkgs/github-hosts-sync.nix
@@ -0,0 +1,40 @@
+{ stdenv, fetchgit, pkgs, ... }:
+
+stdenv.mkDerivation {
+ name = "github-hosts-sync";
+
+ src = fetchgit {
+ url = https://github.com/krebscode/painload;
+ rev = "35ccac73d563ad30d2851b9aeed4cfef69ff74e3";
+ sha256 = "1y1fs2p3xj2yrqpw0h5kd0f3c5p1y70xk1hjnw99sr33r67s9c35";
+ };
+
+ phases = [
+ "unpackPhase"
+ "installPhase"
+ ];
+
+ installPhase =
+ let
+ ca-bundle = "${pkgs.cacert}/etc/ca-bundle.crt";
+ path = stdenv.lib.makeSearchPath "bin" (with pkgs; [
+ coreutils
+ findutils
+ git
+ gnugrep
+ gnused
+ openssh
+ socat
+ ]);
+ in
+ ''
+ mkdir -p $out/bin
+
+ sed \
+ 's,^main() {$,&\n export PATH=${path} GIT_SSL_CAINFO=${ca-bundle},' \
+ < ./retiolum/scripts/github_hosts_sync/hosts-sync \
+ > $out/bin/github-hosts-sync
+
+ chmod +x $out/bin/github-hosts-sync
+ '';
+}
diff --git a/krebs/5pkgs/github-known_hosts.nix b/krebs/5pkgs/github-known_hosts.nix
new file mode 100644
index 000000000..302fdd8d5
--- /dev/null
+++ b/krebs/5pkgs/github-known_hosts.nix
@@ -0,0 +1,13 @@
+{ lib, ... }:
+
+with builtins;
+with lib;
+
+let
+ github-pubkey = removeSuffix "\n" (readFile ../../Zpubkeys/github.ssh.pub);
+in
+
+toFile "github-known_hosts"
+ (concatMapStrings
+ (i: "github.com,192.30.252.${toString i} ${github-pubkey}\n")
+ (range 0 255))
diff --git a/krebs/5pkgs/hashPassword.nix b/krebs/5pkgs/hashPassword.nix
new file mode 100644
index 000000000..a10340cc4
--- /dev/null
+++ b/krebs/5pkgs/hashPassword.nix
@@ -0,0 +1,16 @@
+{ lib, pkgs, ... }:
+
+pkgs.writeScriptBin "hashPassword" ''
+ #! /bin/sh
+ # usage: hashPassword
+ set -euf
+
+ export PATH=${lib.makeSearchPath "bin" (with pkgs; [
+ coreutils
+ mkpasswd
+ openssl
+ ])}
+
+ salt=$(openssl rand -base64 16 | tr -d '+=' | head -c 16)
+ exec mkpasswd -m sha-512 -S "$salt"
+''
[cgit] Unable to lock slot /tmp/cgit/19200000.lock: No such file or directory (2)