diff --git a/lib/fudo/dns.nix b/lib/fudo/dns.nix index 4fd4afc..6c446ee 100644 --- a/lib/fudo/dns.nix +++ b/lib/fudo/dns.nix @@ -27,64 +27,11 @@ let }; }; - # networkHostOpts = import ../types/network-host.nix { inherit lib; }; - - # hostRecords = hostname: nethost-data: let - # # FIXME: RP doesn't work. - # # generic-host-records = let - # # host-data = if (hasAttr hostname config.fudo.hosts) then config.fudo.hosts.${hostname} else null; - # # in - # # if (host-data == null) then [] else ( - # # (map (sshfp: "${hostname} IN SSHFP ${sshfp}") host-data.ssh-fingerprints) ++ (optional (host-data.rp != null) "${hostname} IN RP ${host-data.rp}") - # # ); - # sshfp-records = if (hasAttr hostname config.fudo.hosts) then - # (map (sshfp: "${hostname} IN SSHFP ${sshfp}") - # config.fudo.hosts.${hostname}.ssh-fingerprints) - # else []; - # a-record = optional (nethost-data.ipv4-address != null) "${hostname} IN A ${nethost-data.ipv4-address}"; - # aaaa-record = optional (nethost-data.ipv6-address != null) "${hostname} IN AAAA ${nethost-data.ipv6-address}"; - # description-record = optional (nethost-data.description != null) "${hostname} IN TXT \"${nethost-data.description}\""; - # in - # join-lines (a-record ++ aaaa-record ++ description-record ++ sshfp-records); - - # makeSrvRecords = protocol: type: records: - # join-lines (map (record: - # "_${type}._${protocol} IN SRV ${toString record.priority} ${ - # toString record.weight - # } ${toString record.port} ${toString record.host}.") records); - - # makeSrvProtocolRecords = protocol: types: - # join-lines (mapAttrsToList (makeSrvRecords protocol) types); - - # cnameRecord = alias: host: "${alias} IN CNAME ${host}"; - - # mxRecords = mxs: concatStringsSep "\n" (map (mx: "@ IN MX 10 ${mx}.") mxs); - - # dmarcRecord = dmarc-email: - # optionalString (dmarc-email != null) '' - # _dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"''; - - # nsRecords = domain: ns-hosts: - # join-lines - # (mapAttrsToList (host: _: "@ IN NS ${host}.${domain}.") ns-hosts); - in { options.fudo.dns = with types; { enable = mkEnableOption "Enable master DNS services."; - # FIXME: This should allow for AAAA addresses too... - # nameservers = mkOption { - # type = attrsOf (submodule networkHostOpts); - # description = "Map of domain nameserver FQDNs to IP."; - # example = { - # "ns1.domain.com" = { - # ipv4-address = "1.1.1.1"; - # description = "my fancy dns server"; - # }; - # }; - # }; - identity = mkOption { type = str; description = "The identity (CH TXT ID.SERVER) of this host."; @@ -131,38 +78,6 @@ in { dom dom-cfg.zone-definition; - # data = '' - # $ORIGIN ${dom}. - # $TTL 12h - - # @ IN SOA ns1.${dom}. hostmaster.${dom}. ( - # ${toString config.instance.build-timestamp} - # 30m - # 2m - # 3w - # 5m) - - # ${optionalString (dom-cfg.default-host != null) - # "@ IN A ${dom-cfg.default-host}"} - - # ${mxRecords dom-cfg.mx} - - # $TTL 6h - - # ${optionalString (dom-cfg.gssapi-realm != null) - # ''_kerberos IN TXT "${dom-cfg.gssapi-realm}"''} - - # ${nsRecords dom cfg.nameservers} - # ${join-lines (mapAttrsToList hostRecords cfg.nameservers)} - - # ${dmarcRecord dom-cfg.dmarc-report-address} - - # ${join-lines - # (mapAttrsToList makeSrvProtocolRecords net-cfg.srv-records)} - # ${join-lines (mapAttrsToList hostRecords net-cfg.hosts)} - # ${join-lines (mapAttrsToList cnameRecord net-cfg.aliases)} - # ${join-lines net-cfg.verbatim-dns-records} - # ''; }) cfg.domains; }; }; diff --git a/lib/fudo/node-exporter.nix b/lib/fudo/node-exporter.nix index 927df44..b2a6e20 100644 --- a/lib/fudo/node-exporter.nix +++ b/lib/fudo/node-exporter.nix @@ -5,22 +5,31 @@ let inherit (lib.strings) concatStringsSep; cfg = config.fudo.node-exporter; - fudo-cfg = config.fudo.common; allow-network = network: "allow ${network};"; + local-networks = config.instance.local-networks; + in { - options.fudo.node-exporter = { + options.fudo.node-exporter = with types; { enable = mkEnableOption "Enable a Prometheus node exporter with some reasonable settings."; hostname = mkOption { - type = types.str; + type = str; description = "Hostname from which to export statistics."; }; + + user = mkOption { + type = str; + description = "User as which to run the node exporter job."; + default = "node-exporter"; + }; }; config = mkIf cfg.enable { - security.acme.certs.${cfg.hostname}.email = fudo-cfg.admin-email; + users.users.${cfg.user} = { + isSystemUser = true; + }; services = { # This'll run an exporter at localhost:9100 @@ -29,7 +38,7 @@ in { enabledCollectors = [ "systemd" ]; listenAddress = "127.0.0.1"; port = 9100; - user = "node"; + user = cfg.user; }; # ...And this'll expose the above to the outside world, or at least the @@ -43,7 +52,7 @@ in { locations."/metrics/node" = { extraConfig = '' - ${concatStringsSep "\n" (map allow-network fudo-cfg.local-networks)} + ${concatStringsSep "\n" (map allow-network local-networks)} allow 127.0.0.0/16; deny all; diff --git a/lib/lib/dns.nix b/lib/lib/dns.nix index c712bfc..983fd77 100644 --- a/lib/lib/dns.nix +++ b/lib/lib/dns.nix @@ -4,16 +4,60 @@ with pkgs.lib; let join-lines = concatStringsSep "\n"; - dump = obj: builtins.trace obj obj; + pthru = obj: builtins.trace obj obj; + + remove-blank-lines = str: + concatStringsSep "\n\n" + (filter builtins.isString + (builtins.split "\n\n\n+" str)); + + n-spaces = n: + concatStringsSep "" (builtins.genList (_: " ") n); + + pad-to-length = strlen: str: let + spaces = n-spaces (strlen - (stringLength str)); + in str + spaces; + + record-matcher = builtins.match "^([^;].*) IN ([A-Z][A-Z0-9]*) (.+)$"; + + is-record = str: (record-matcher str) != null; + + max-int = foldr (a: b: if (a < b) then b else a) 0; + + make-zone-formatter = zonedata: let + lines = splitString "\n" zonedata; + records = filter is-record lines; + split-records = map record-matcher records; + index-strlen = i: record: stringLength (elemAt record i); + record-index-maxlen = i: max-int (map (index-strlen i) split-records); + in record-formatter (record-index-maxlen 0) (record-index-maxlen 1); + + record-formatter = name-max: type-max: let + name-padder = pad-to-length name-max; + type-padder = pad-to-length type-max; + in record-line: let + record-parts = record-matcher record-line; + in + if (record-parts == null) then + record-line + else (let + name = elemAt record-parts 0; + type = elemAt record-parts 1; + data = elemAt record-parts 2; + in "${name-padder name} IN ${type-padder type} ${data}"); + + format-zone = zonedata: let + formatter = make-zone-formatter zonedata; + lines = splitString "\n" zonedata; + in concatStringsSep "\n" (map formatter lines); makeSrvRecords = protocol: service: records: let - service-blah = (dump service); - record-blah = (dump records); - in - join-lines (map (record: - "_${service}._${protocol} IN SRV ${toString record.priority} ${ - toString record.weight - } ${toString record.port} ${record.host}.") records); + service-blah = service; + record-blah = records; + in join-lines (map (record: + "_${service}._${protocol} IN SRV ${toString record.priority} ${ + toString record.weight + } ${toString record.port} ${record.host}.") records); makeSrvProtocolRecords = protocol: services: join-lines (mapAttrsToList (makeSrvRecords protocol) services); @@ -52,10 +96,15 @@ let "${hostname} IN A ${nethost-data.ipv4-address}"; aaaa-record = optional (nethost-data.ipv6-address != null) "${hostname} IN AAAA ${nethost-data.ipv6-address}"; + cname-record = optional (nethost-data.authoritative-hostname != null) + "${hostname} IN CNAME ${nethost-data.authoritative-hostname}"; description-record = optional (nethost-data.description != null) ''${hostname} IN TXT "${nethost-data.description}"''; - in - join-lines (a-record ++ aaaa-record ++ description-record ++ sshfp-records); + in join-lines (a-record ++ + aaaa-record ++ + cname-record ++ + sshfp-records ++ + description-record); cnameRecord = alias: host: "${alias} IN CNAME ${host}"; @@ -71,19 +120,19 @@ let flatmapAttrsToList = f: attrs: foldr (a: b: a ++ b) [] (mapAttrsToList f attrs); - nsARecords = _: ns-hosts: let - a-record = host: hostOpts: optional (hostOpts.ipv4-address != null) - "${host} IN A ${hostOpts.ipv4-address}"; - aaaa-record = host: hostOpts: optional (hostOpts.ipv6-address != null) - "${host} IN AAAA ${hostOpts.ipv6-address}"; - description-record = host: hostOpts: optional (hostOpts.description != null) - ''${host} IN TXT "${hostOpts.description}"''; - in flatmapAttrsToList - (host: hostOpts: - (a-record host hostOpts) ++ - (aaaa-record host hostOpts) ++ - (description-record host hostOpts)) - ns-hosts; + # nsARecords = _: ns-hosts: let + # a-record = host: hostOpts: optional (hostOpts.ipv4-address != null) + # "${host} IN A ${hostOpts.ipv4-address}"; + # aaaa-record = host: hostOpts: optional (hostOpts.ipv6-address != null) + # "${host} IN AAAA ${hostOpts.ipv6-address}"; + # description-record = host: hostOpts: optional (hostOpts.description != null) + # ''${host} IN TXT "${hostOpts.description}"''; + # in flatmapAttrsToList + # (host: hostOpts: + # (a-record host hostOpts) ++ + # (aaaa-record host hostOpts) ++ + # (description-record host hostOpts)) + # ns-hosts; srvRecordPair = domain: protocol: service: record: { @@ -97,63 +146,32 @@ let $ORIGIN ${dom}. $TTL ${zone.default-ttl} - ### - # Default Host - ### ${optionalString (zone.default-host != null) "@ IN A ${zone.default-host}"} - ### - # MX Records - ### ${join-lines (mxRecords zone.mx)} - # dmarc ${dmarcRecord zone.dmarc-report-address} - ### - # Kerberos Realm - ### ${optionalString (zone.gssapi-realm != null) ''_kerberos IN TXT "${zone.gssapi-realm}"''} - ### - # Nameservers - ### ${join-lines (nsRecords dom zone.nameservers)} - ${join-lines (nsARecords dom zone.nameservers)} + ${join-lines (mapAttrsToList hostRecords zone.nameservers)} - ### - # General Records - ### - $TTL ${zone.host-record-ttl} - - ### - # SRV Records - ### ${join-lines (mapAttrsToList makeSrvProtocolRecords zone.srv-records)} - ### - # Host Records - ### + $TTL ${zone.host-record-ttl} + ${join-lines (mapAttrsToList hostRecords zone.hosts)} - ### - # CNAMEs - ### ${join-lines (mapAttrsToList cnameRecord zone.aliases)} - ### - # Verbatim Records - ### ${join-lines zone.verbatim-dns-records} - ### - # Subdomains of ${dom} - ### ${join-lines (mapAttrsToList - (subdom: subdomCfg: subdomain-record "${subdom}.${dom}" subdomCfg) + (subdom: subdomCfg: domain-record "${subdom}.${dom}" subdomCfg) zone.subdomains)} ''; @@ -173,17 +191,18 @@ in rec { (service: records: map (srvRecordPair domain protocol service) records) services) srvRecords); - zoneToZonefile = timestamp: dom: zone: '' - $ORIGIN ${dom}. - $TTL ${zone.default-ttl} + zoneToZonefile = timestamp: dom: zone: + remove-blank-lines (format-zone '' + $ORIGIN ${dom}. + $TTL ${zone.default-ttl} - @ IN SOA ns1.${dom}. hostmaster.${dom}. ( - ${toString timestamp} - 30m - 2m - 3w - 5m) + @ IN SOA ns1.${dom}. hostmaster.${dom}. ( + ${toString timestamp} + 30m + 2m + 3w + 5m) - ${domain-record dom zone} - ''; + ${domain-record dom zone} + ''); } diff --git a/lib/lib/filesystem.nix b/lib/lib/filesystem.nix index 4009918..ab7b20a 100644 --- a/lib/lib/filesystem.nix +++ b/lib/lib/filesystem.nix @@ -27,6 +27,8 @@ let import-by-basename = path: mapAttrs (attr: attr-file: import attr-file) (basename-to-file-map path); + + list-nix-files = nix-files; in { - inherit basename-to-file-map import-by-basename; + inherit basename-to-file-map import-by-basename list-nix-files; } diff --git a/lib/types/network-host.nix b/lib/types/network-host.nix index 0284e7f..78b4400 100644 --- a/lib/types/network-host.nix +++ b/lib/types/network-host.nix @@ -1,6 +1,5 @@ { lib, ... }: -{ hostname, ... }: with lib; { options = with types; { @@ -23,6 +22,13 @@ with lib; default = null; }; + authoritative-hostname = mkOption { + type = nullOr str; + description = + "The 'real' hostname of this host, i.e. CNAME. Prefer aliases!"; + default = null; + }; + description = mkOption { type = nullOr str; description = "Description of the host.";