diff --git a/1systems/tv/cd.nix b/1systems/tv/cd.nix
index 42e4ecf1a..d3cae6f4a 100644
--- a/1systems/tv/cd.nix
+++ b/1systems/tv/cd.nix
@@ -35,11 +35,8 @@ in
         singleton config.tv.github-hosts-sync.port;
     }
     {
-      imports = [ ../../3modules/tv/identity.nix ];
-      tv.identity = {
-        enable = true;
-        self = config.tv.identity.hosts.cd;
-      };
+      imports = [ ../../2configs/tv/identity.nix ];
+      tv.identity.self = config.tv.identity.hosts.cd;
     }
     {
       imports = [ ../../3modules/tv/iptables.nix ];
diff --git a/1systems/tv/mkdir.nix b/1systems/tv/mkdir.nix
index e4e89872e..e0e057d63 100644
--- a/1systems/tv/mkdir.nix
+++ b/1systems/tv/mkdir.nix
@@ -11,11 +11,8 @@ with lib;
     ../../2configs/tv/exim-smarthost.nix
     ../../2configs/tv/git-public.nix
     {
-      imports = [ ../../3modules/tv/identity.nix ];
-      tv.identity = {
-        enable = true;
-        self = config.tv.identity.hosts.mkdir;
-      };
+      imports = [ ../../2configs/tv/identity.nix ];
+      tv.identity.self = config.tv.identity.hosts.mkdir;
     }
     {
       imports = [ ../../3modules/tv/iptables.nix ];
@@ -49,7 +46,7 @@ with lib;
   networking.hostName = "mkdir";
   networking.interfaces.enp2s1.ip4 = [
     {
-      address = "162.248.167.241";
+      address = "162.248.167.241"; # TODO
       prefixLength = 24;
     }
   ];
diff --git a/1systems/tv/nomic.nix b/1systems/tv/nomic.nix
index b6235297f..6f984c44d 100644
--- a/1systems/tv/nomic.nix
+++ b/1systems/tv/nomic.nix
@@ -10,11 +10,8 @@ with lib;
     ../../2configs/tv/exim-retiolum.nix
     ../../2configs/tv/git-public.nix
     {
-      imports = [ ../../3modules/tv/identity.nix ];
-      tv.identity = {
-        enable = true;
-        self = config.tv.identity.hosts.nomic;
-      };
+      imports = [ ../../2configs/tv/identity.nix ];
+      tv.identity.self = config.tv.identity.hosts.nomic;
     }
     {
       imports = [ ../../3modules/tv/iptables.nix ];
diff --git a/1systems/tv/rmdir.nix b/1systems/tv/rmdir.nix
index 14817c9bc..b77a1c39e 100644
--- a/1systems/tv/rmdir.nix
+++ b/1systems/tv/rmdir.nix
@@ -11,11 +11,8 @@ with lib;
     ../../2configs/tv/exim-smarthost.nix
     ../../2configs/tv/git-public.nix
     {
-      imports = [ ../../3modules/tv/identity.nix ];
-      tv.identity = {
-        enable = true;
-        self = config.tv.identity.hosts.rmdir;
-      };
+      imports = [ ../../2configs/tv/identity.nix ];
+      tv.identity.self = config.tv.identity.hosts.rmdir;
     }
     {
       imports = [ ../../3modules/tv/iptables.nix ];
diff --git a/1systems/tv/wu.nix b/1systems/tv/wu.nix
index 866386698..400005cb7 100644
--- a/1systems/tv/wu.nix
+++ b/1systems/tv/wu.nix
@@ -18,11 +18,8 @@ in
     ../../2configs/tv/xserver.nix
     ../../2configs/tv/synaptics.nix # TODO w110er if xserver is enabled
     {
-      imports = [ ../../3modules/tv/identity.nix ];
-      tv.identity = {
-        enable = true;
-        self = config.tv.identity.hosts.wu;
-      };
+      imports = [ ../../2configs/tv/identity.nix ];
+      tv.identity.self = config.tv.identity.hosts.wu;
     }
     {
       environment.systemPackages = with pkgs; [
diff --git a/2configs/tv/charybdis.nix b/2configs/tv/charybdis.nix
index f4b7c1012..a45c7aebe 100644
--- a/2configs/tv/charybdis.nix
+++ b/2configs/tv/charybdis.nix
@@ -74,7 +74,7 @@ let
 
   user = {
     name = "charybdis";
-    uid = 3731512864; # genid charybdis
+    uid = 3748224544; # genid charybdis
   };
 
   configFile = toFile "charybdis-ircd.conf" ''
@@ -123,7 +123,7 @@ let
     #loadmodule "extensions/ip_cloaking.so";
     
     serverinfo {
-      name = ${toJSON config.tv.identity.self.fqdn};
+      name = ${toJSON (head config.tv.identity.self.nets.retiolum.aliases)};
       sid = "4z3";
       description = "miep!";
       network_name = "irc.retiolum";
@@ -133,9 +133,9 @@ let
       /* On multi-homed hosts you may need the following. These define
        * the addresses we connect from to other servers. */
       /* for IPv4 */
-      vhost = ${toJSON config.tv.identity.self.addr};
+      vhost = ${concatMapStringsSep ", " toJSON config.tv.identity.self.nets.retiolum.addrs4};
       /* for IPv6 */
-      vhost6 = ${toJSON config.tv.identity.self.addr6};
+      vhost6 = ${concatMapStringsSep ", " toJSON config.tv.identity.self.nets.retiolum.addrs6};
       
       /* ssl_private_key: our ssl private key */
       ssl_private_key = "/tmp/ssl.key";
@@ -238,12 +238,10 @@ let
       /* If you want to listen on a specific IP only, specify host.
        * host definitions apply only to the following port line.
        */
-      host = ${toJSON config.tv.identity.self.addr};
-      port = 6667;
-      sslport = 6697;
-    
-      /* Listen on IPv6 (if you used host= above). */
-      host = ${toJSON config.tv.identity.self.addr6};
+      # XXX This is stupid because only one host is allowed[?]
+      #host = ''${concatMapStringsSep ", " toJSON (
+      #  config.tv.identity.self.nets.retiolum.addrs
+      #)};
       port = 6667;
       sslport = 6697;
     };
diff --git a/2configs/tv/identity.nix b/2configs/tv/identity.nix
new file mode 100644
index 000000000..de99f507f
--- /dev/null
+++ b/2configs/tv/identity.nix
@@ -0,0 +1,154 @@
+{ config, ... }:
+
+{
+  imports = [ ../../3modules/tv/identity.nix ];
+  tv.identity = {
+    enable = true;
+    search = "retiolum";
+    hosts = {
+      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"];
+          };
+          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-----
+            '';
+          };
+        };
+      };
+      rmdir = {
+        cores = 1;
+        dc = "tv"; #dc = "cac";
+        nets = rec {
+          internet = {
+            addrs4 = ["167.88.44.94"];
+          };
+          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-----
+            '';
+          };
+        };
+      };
+    };
+  };
+}
diff --git a/3modules/tv/consul.nix b/3modules/tv/consul.nix
index 480198456..312faa02f 100644
--- a/3modules/tv/consul.nix
+++ b/3modules/tv/consul.nix
@@ -5,8 +5,7 @@
 # TODO consul-bootstrap HOST  that actually does is
 # TODO tools to inspect state of a cluster in outage state
 
-with builtins;
-with lib;
+with import ../../4lib/tv { inherit lib pkgs; };
 let
   cfg = config.tv.consul;
 
@@ -24,10 +23,10 @@ let
     enable = mkEnableOption "tv.consul";
 
     dc = mkOption {
-      type = types.unspecified;
+      type = types.label;
     };
     hosts = mkOption {
-      type = with types; listOf unspecified;
+      type = with types; listOf host;
     };
     encrypt-file = mkOption {
       type = types.str; # TODO path (but not just into store)
@@ -38,7 +37,7 @@ let
       default = "/var/lib/consul";
     };
     self = mkOption {
-      type = types.unspecified;
+      type = types.host;
     };
     server = mkOption {
       type = types.bool;
@@ -56,9 +55,11 @@ let
     log_level = "INFO";
     #node_name =
     server = cfg.server;
-    bind_addr = cfg.self.addr; # TODO cfg.addr
     enable_syslog = true;
-    retry_join = map (getAttr "addr") (filter (host: host.fqdn != cfg.self.fqdn) cfg.hosts);
+    retry_join =
+      # TODO allow consul in other nets than retiolum [maybe]
+      concatMap (host: host.nets.retiolum.addrs)
+                (filter (host: host.name != cfg.self.name) cfg.hosts);
     leave_on_terminate = true;
   } // optionalAttrs cfg.server {
     bootstrap_expect = length cfg.hosts;
@@ -88,7 +89,7 @@ let
         ExecStartPre = pkgs.writeScript "consul-init" ''
           #! /bin/sh
           mkdir -p ${cfg.data-dir}
-          chown consul: ${cfg.data-dir}
+          chown ${user.name}: ${cfg.data-dir}
           install -o ${user.name} -m 0400 ${cfg.encrypt-file} /tmp/encrypt.json
         '';
         ExecStart = pkgs.writeScript "consul-service" ''
@@ -111,7 +112,7 @@ let
 
   user = {
     name = "consul";
-    uid = 2983239726; # genid consul
+    uid = 2999951406; # genid consul
   };
 
 in
diff --git a/3modules/tv/ejabberd.nix b/3modules/tv/ejabberd.nix
index b694d05d2..2910a9a69 100644
--- a/3modules/tv/ejabberd.nix
+++ b/3modules/tv/ejabberd.nix
@@ -55,8 +55,7 @@ let
 
   user = {
     name = "ejabberd";
-    uid = 405222; 
-    # TODO uid = 3483034447; # genid ejabberd
+    uid = 3499746127; # genid ejabberd
   };
 
   my-ejabberdctl = pkgs.writeScriptBin "ejabberdctl" ''
diff --git a/3modules/tv/git.nix b/3modules/tv/git.nix
index 8d2ab482d..8c73d0354 100644
--- a/3modules/tv/git.nix
+++ b/3modules/tv/git.nix
@@ -149,7 +149,7 @@ let
       shell = "/bin/sh";
       openssh.authorizedKeys.keys =
         mapAttrsToList (_: makeAuthorizedKey git-ssh-command) cfg.users;
-      uid = 112606723; # genid git
+      uid = 129318403; # genid git
     };
   };
 
@@ -237,13 +237,13 @@ let
 
   fcgitwrap-user = {
     name = "fcgiwrap";
-    uid = 2851179180; # genid fcgiwrap
+    uid = 2867890860; # genid fcgiwrap
     group = "fcgiwrap";
   };
 
   fcgitwrap-group = {
-    name = "fcgiwrap";
-    gid = 2851179180; # genid fcgiwrap
+    name = fcgitwrap-user.name;
+    gid = fcgitwrap-user.uid;
   };
 
 
diff --git a/3modules/tv/github-hosts-sync.nix b/3modules/tv/github-hosts-sync.nix
index 3da1064a1..f50bf2b1b 100644
--- a/3modules/tv/github-hosts-sync.nix
+++ b/3modules/tv/github-hosts-sync.nix
@@ -75,7 +75,7 @@ let
 
   user = {
     name = "github-hosts-sync";
-    uid = 3203842966; # genid github-hosts-sync
+    uid = 3220554646; # genid github-hosts-sync
   };
 
   Zpkgs = import ../../Zpkgs/tv { inherit pkgs; };
diff --git a/3modules/tv/identity.nix b/3modules/tv/identity.nix
index d3ac91393..584b27165 100644
--- a/3modules/tv/identity.nix
+++ b/3modules/tv/identity.nix
@@ -1,6 +1,6 @@
-{ config, lib, ... }:
+{ config, lib, pkgs, ... }:
 
-with lib;
+with import ../../4lib/tv { inherit lib pkgs; };
 let
   cfg = config.tv.identity;
 
@@ -13,77 +13,70 @@ let
     enable = mkEnableOption "tv.identity";
 
     self = mkOption {
-      type = types.unspecified;
+      type = types.host;
     };
+
+    #others = mkOption {
+    #  type = types.host;
+    #  default = filterAttrs (name: _host: name != cfg.self.name) cfg.hosts;
+    #};
+
     hosts = mkOption {
-      type = with types; attrsOf unspecified;
-      default = {
-        cd = {
-          #dc = "cac";
-          dc = "tv";
-          fqdn = "cd.retiolum";
-          subdomains = [
-            "cgit"
-          ];
-          addr = "10.243.113.222";
-          addr6 = "42:4522:25f8:36bb:8ccb:0150:231a:2af3";
-          #internet-addr = "162.219.5.183";
-          cores = 2;
-        };
-        mkdir = {
-          #dc = "cac";
-          dc = "tv";
-          fqdn = "mkdir.retiolum";
-          subdomains = [
-            "cgit"
-          ];
-          addr = "10.243.113.223";
-          cores = 1;
-        };
-        nomic = {
-          #dc = "gg";
-          dc = "tv";
-          fqdn = "nomic.retiolum";
-          subdomains = [
-            "cgit"
-          ];
-          addr = "10.243.0.110";
-          cores = 2;
-        };
-        rmdir = {
-          #dc = "cac";
-          dc = "tv";
-          fqdn = "rmdir.retiolum";
-          subdomains = [
-            "cgit"
-          ];
-          addr = "10.243.113.224";
-          #addr = "42:4522:25f8:36bb:8ccb:0150:231a:2af5";
-          cores = 1;
-        };
-        wu = {
-          #dc = "gg";
-          dc = "tv";
-          fqdn = "wu.retiolum";
-          subdomains = [
-            "cgit"
-          ];
-          addr = "10.243.13.37";
-          cores = 8;
-        };
-      };
+      type = with types; attrsOf host;
+      apply = mapAttrs (name: value: value // { inherit name; });
+    };
+
+    search = mkOption {
+      type = types.hostname;
     };
   };
 
   imp = {
     networking.extraHosts =
-      let
-        f = name: { addr, fqdn, subdomains, ... }: ''
-          ${addr} ${toString (map (s: "${s}.${name} ${s}.${fqdn}") subdomains)}
-        '';
-      in
-      concatStringsSep "\n" (mapAttrsToList f cfg.hosts);
+      concatStringsSep "\n" (flatten (
+        # TODO deepMap ["hosts" "nets"] (hostname: host: netname: net:
+        mapAttrsToList (hostname: host:
+          mapAttrsToList (netname: net:
+            let
+              aliases = toString (unique (longs ++ shorts));
+              longs = (splitByProvider net.aliases).hosts;
+              shorts = map (removeSuffix ".${cfg.search}") longs;
+            in
+            map (addr: "${addr} ${aliases}") net.addrs
+          ) host.nets
+        ) cfg.hosts
+      ));
   };
 
+  # TODO move domain name providers to a dedicated module
+  # providers : tree label providername
+  providers = {
+    internet = "hosts";
+    retiolum = "hosts";
+    de.viljetic = "regfish";
+    de.krebsco = "ovh";
+  };
+
+  # splitByProvider : [alias] -> set providername [alias]
+  splitByProvider = foldl (acc: alias: insert (providerOf alias) alias acc) {};
+
+  # providerOf : alias -> providername
+  providerOf = alias:
+    tree-get (splitString "." alias) providers;
+
+  # insert : k -> v -> set k [v] -> set k [v]
+  insert = name: value: set:
+    set // { ${name} = set.${name} or [] ++ [value]; };
+
+  # tree k v = set k (either v (tree k v))
+
+  # tree-get : [k] -> tree k v -> v
+  tree-get = path: x:
+    let
+      y = x.${last path};
+    in
+    if typeOf y != "set"
+      then y
+      else tree-get (init path) y;
 in
 out
diff --git a/3modules/tv/retiolum.nix b/3modules/tv/retiolum.nix
index 8dc4197aa..ca1418c32 100644
--- a/3modules/tv/retiolum.nix
+++ b/3modules/tv/retiolum.nix
@@ -46,7 +46,6 @@ let
       description = ''
         The tinc network name.
         It is used to generate long host entries,
-        derive the name of the user account under which tincd runs,
         and name the TUN device.
       '';
     };
@@ -106,20 +105,22 @@ let
         #      and the private key.
         ExecStartPre = pkgs.writeScript "retiolum-init" ''
           #! /bin/sh
-          install -o ${user} -m 0400 ${cfg.privateKeyFile} /tmp/retiolum-rsa_key.priv
+          install -o ${user.name} -m 0400 ${cfg.privateKeyFile} /tmp/retiolum-rsa_key.priv
         '';
-        ExecStart = "${tinc}/sbin/tincd -c ${confDir} -d 0 -U ${user} -D";
+        ExecStart = "${tinc}/sbin/tincd -c ${confDir} -d 0 -U ${user.name} -D";
         SyslogIdentifier = "retiolum";
       };
     };
 
-    # TODO user.name = "retiolum"
     users.extraUsers = singleton {
-      name = user;
-      uid = 2961822815; # bin/genid retiolum-tinc
+      inherit (user) name uid;
     };
   };
 
+  user = {
+    name = "retiolum";
+    uid = 301281149; # genid retiolum
+  };
 
   tinc = cfg.tincPackage;
   hostsType = builtins.typeOf cfg.hosts;
@@ -217,21 +218,5 @@ let
 
     chmod +x $out/tinc-up
   '';
-
-
-  user = cfg.network + "-tinc";
-
 in
 out
-
-
-
-#let
-#  cfg = config.tv.retiolum;
-#  arg' = arg // { inherit cfg; };
-#in
-#
-#{
-#  options.tv.retiolum = import ./options.nix arg';
-#  config = lib.mkIf cfg.enable (import ./config.nix arg');
-#}
diff --git a/3modules/tv/urlwatch.nix b/3modules/tv/urlwatch.nix
index 05a0b0faf..a659fc74f 100644
--- a/3modules/tv/urlwatch.nix
+++ b/3modules/tv/urlwatch.nix
@@ -28,7 +28,7 @@ let
     };
     from = mkOption {
       type = types.str;
-      default = "${cfg.user}@${config.networking.hostName}.retiolum";
+      default = "${user.name}@${config.networking.hostName}.retiolum";
       description = ''
         Content of the From: header of the generated mails.
       '';
@@ -54,11 +54,6 @@ let
         https://nixos.org/channels/nixos-unstable/git-revision
       ];
     };
-    user = mkOption {
-      type = types.str;
-      default = "urlwatch";
-      description = "User under which urlwatch runs.";
-    };
   };
 
   urlsFile = toFile "urls" (concatStringsSep "\n" cfg.urls);
@@ -84,7 +79,7 @@ let
         SSL_CERT_FILE = "${pkgs.cacert}/etc/ssl/certs/ca-bundle.crt";
       };
       serviceConfig = {
-        User = cfg.user;
+        User = user.name;
         PermissionsStartOnly = "true";
         PrivateTmp = "true";
         Type = "oneshot";
@@ -94,11 +89,10 @@ let
             set -euf
 
             dataDir=$HOME
-            user=${escapeShellArg cfg.user}
 
             if ! test -e "$dataDir"; then
               mkdir -m 0700 -p "$dataDir"
-              chown "$user": "$dataDir"
+              chown ${user.name}: "$dataDir"
             fi
           '';
         ExecStart = pkgs.writeScript "urlwatch" ''
@@ -108,7 +102,6 @@ let
           from=${escapeShellArg cfg.from}
           mailto=${escapeShellArg cfg.mailto}
           urlsFile=${escapeShellArg urlsFile}
-          user=${escapeShellArg cfg.user}
 
           cd /tmp
 
@@ -130,11 +123,14 @@ let
         '';
       };
     };
-    users.extraUsers = optionals (cfg.user == "urlwatch") (singleton {
-      name = "urlwatch";
-      uid = 3450919516; # bin/genid urlwatch
-    });
+    users.extraUsers = singleton {
+      inherit (user) name uid;
+    };
   };
 
+  user = {
+    name = "urlwatch";
+    uid = 3467631196; # genid urlwatch
+  };
 in
 out
diff --git a/4lib/tv/default.nix b/4lib/tv/default.nix
index 164a6a1aa..092a9626c 100644
--- a/4lib/tv/default.nix
+++ b/4lib/tv/default.nix
@@ -1,12 +1,10 @@
 { lib, pkgs, ... }:
 
 with builtins;
+with lib;
 
-let
-  inherit (lib) mapAttrs stringAsChars;
-in
+builtins // lib // rec {
 
-rec {
   git = import ./git.nix {
     lib = lib // {
       inherit addNames;
@@ -41,9 +39,6 @@ rec {
       in
         xsn >= sn && substring (xsn - sn) sn xs == s ;
 
-  removeSuffix =
-    s : xs : substring 0 (stringLength xs - stringLength s) xs;
-
   # setMap :: (String -> a -> b) -> Set String a -> [b]
   #setMap = f: xs: map (k : f k (getAttr k xs)) (attrNames xs);
 
@@ -59,4 +54,81 @@ rec {
       else if c == "\n" then "'\n'"
       else "\\${c}");
 
+  types = lib.types // (with lib.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;
+        };
+      };
+    };
+
+    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 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;
+              };
+            };
+          });
+        };
+      };
+    });
+
+    positive = mkOptionType {
+      name = "positive integer";
+      check = x: isInt x && x > 0;
+      merge = mergeOneOption;
+    };
+
+    # TODO
+    addr = str;
+    addr4 = str;
+    addr6 = str;
+    hostname = str;
+    label = str;
+  });
+
 }
diff --git a/Makefile b/Makefile
index 7225a5d6b..da234677b 100644
--- a/Makefile
+++ b/Makefile
@@ -2,6 +2,7 @@
 # usage:
 #		make system=foo
 #		make systems='foo bar'
+#		make eval system=foo get=config.networking.extraHosts
 #
 
 .ONESHELL:
@@ -82,6 +83,20 @@ deploy:;@
 
 		result/bin/switch-to-configuration switch
 	EOF
+
+.PHONY: eval
+eval:
+	@nix-instantiate \
+		--json \
+		--eval \
+		--strict \
+		-A "$$get" \
+		-E '
+			import <nixpkgs/nixos/lib/eval-config.nix> {
+				system = builtins.currentSystem;
+				modules = [ ./1systems/$(LOGNAME)/$(system).nix ];
+			}
+		' | jq -r .
 else
 $(error unbound variable: system[s])
 endif
diff --git a/Zpkgs/tv/genid.nix b/Zpkgs/tv/genid.nix
index 22ef6d29b..c75bec317 100644
--- a/Zpkgs/tv/genid.nix
+++ b/Zpkgs/tv/genid.nix
@@ -13,7 +13,8 @@ pkgs.writeScriptBin "genid" ''
   name=$1
   hash=$(printf %s "$name" | sha1sum | cut -d\  -f1 | tr a-f A-F)
   echo "
-    min=2^16  # bigger than nobody and nogroup, see <nixos/modules/misc/ids.nix>
+    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