More changes than expected...

This commit is contained in:
niten 2022-01-05 17:57:32 -08:00
parent 00e8e764ac
commit 66edcf4fd3
5 changed files with 295 additions and 180 deletions

View File

@ -6,7 +6,7 @@ let
join-lines = concatStringsSep "\n"; join-lines = concatStringsSep "\n";
domainOpts = { domain, ... }: { domainOpts = { name, ... }: {
options = with types; { options = with types; {
dnssec = mkOption { dnssec = mkOption {
type = bool; type = bool;
@ -14,77 +14,53 @@ let
default = true; default = true;
}; };
dmarc-report-address = mkOption {
type = nullOr str;
description = "The email to use to recieve DMARC reports, if any.";
example = "admin-user@domain.com";
default = null;
};
zone-definition = mkOption { zone-definition = mkOption {
type = submodule (import ../types/zone-definition.nix); type = submodule (import ../types/zone-definition.nix);
description = "Definition of network zone to be served by local server."; description = "Definition of network zone to be served by local server.";
}; };
default-host = mkOption {
type = str;
description = "The host to which the domain should map by default.";
};
mx = mkOption {
type = listOf str;
description = "The hosts which act as the domain mail exchange.";
default = [];
};
gssapi-realm = mkOption {
type = nullOr str;
description = "The GSSAPI realm of this domain.";
default = null;
};
}; };
}; };
networkHostOpts = import ../types/network-host.nix { inherit lib; }; # networkHostOpts = import ../types/network-host.nix { inherit lib; };
hostRecords = hostname: nethost-data: let # hostRecords = hostname: nethost-data: let
# FIXME: RP doesn't work. # # FIXME: RP doesn't work.
# generic-host-records = let # # generic-host-records = let
# host-data = if (hasAttr hostname config.fudo.hosts) then config.fudo.hosts.${hostname} else null; # # host-data = if (hasAttr hostname config.fudo.hosts) then config.fudo.hosts.${hostname} else null;
# in # # in
# if (host-data == null) then [] else ( # # 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}") # # (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 # sshfp-records = if (hasAttr hostname config.fudo.hosts) then
(map (sshfp: "${hostname} IN SSHFP ${sshfp}") # (map (sshfp: "${hostname} IN SSHFP ${sshfp}")
config.fudo.hosts.${hostname}.ssh-fingerprints) # config.fudo.hosts.${hostname}.ssh-fingerprints)
else []; # else [];
a-record = optional (nethost-data.ipv4-address != null) "${hostname} IN A ${nethost-data.ipv4-address}"; # 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}"; # 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}\""; # description-record = optional (nethost-data.description != null) "${hostname} IN TXT \"${nethost-data.description}\"";
in # in
join-lines (a-record ++ aaaa-record ++ description-record ++ sshfp-records); # join-lines (a-record ++ aaaa-record ++ description-record ++ sshfp-records);
makeSrvRecords = protocol: type: records: # makeSrvRecords = protocol: type: records:
join-lines (map (record: # join-lines (map (record:
"_${type}._${protocol} IN SRV ${toString record.priority} ${ # "_${type}._${protocol} IN SRV ${toString record.priority} ${
toString record.weight # toString record.weight
} ${toString record.port} ${toString record.host}.") records); # } ${toString record.port} ${toString record.host}.") records);
makeSrvProtocolRecords = protocol: types: # makeSrvProtocolRecords = protocol: types:
join-lines (mapAttrsToList (makeSrvRecords protocol) types); # join-lines (mapAttrsToList (makeSrvRecords protocol) types);
cnameRecord = alias: host: "${alias} IN CNAME ${host}"; # cnameRecord = alias: host: "${alias} IN CNAME ${host}";
mxRecords = mxs: concatStringsSep "\n" (map (mx: "@ IN MX 10 ${mx}.") mxs); # mxRecords = mxs: concatStringsSep "\n" (map (mx: "@ IN MX 10 ${mx}.") mxs);
dmarcRecord = dmarc-email: # dmarcRecord = dmarc-email:
optionalString (dmarc-email != null) '' # optionalString (dmarc-email != null) ''
_dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"''; # _dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"'';
nsRecords = domain: ns-hosts: # nsRecords = domain: ns-hosts:
join-lines # join-lines
(mapAttrsToList (host: _: "@ IN NS ${host}.${domain}.") ns-hosts); # (mapAttrsToList (host: _: "@ IN NS ${host}.${domain}.") ns-hosts);
in { in {
@ -92,16 +68,16 @@ in {
enable = mkEnableOption "Enable master DNS services."; enable = mkEnableOption "Enable master DNS services.";
# FIXME: This should allow for AAAA addresses too... # FIXME: This should allow for AAAA addresses too...
nameservers = mkOption { # nameservers = mkOption {
type = attrsOf (submodule networkHostOpts); # type = attrsOf (submodule networkHostOpts);
description = "Map of domain nameserver FQDNs to IP."; # description = "Map of domain nameserver FQDNs to IP.";
example = { # example = {
"ns1.domain.com" = { # "ns1.domain.com" = {
ipv4-address = "1.1.1.1"; # ipv4-address = "1.1.1.1";
description = "my fancy dns server"; # description = "my fancy dns server";
}; # };
}; # };
}; # };
identity = mkOption { identity = mkOption {
type = str; type = str;
@ -143,38 +119,40 @@ in {
in nameValuePair "${dom}." { in nameValuePair "${dom}." {
dnssec = dom-cfg.dnssec; dnssec = dom-cfg.dnssec;
data = '' data = pkgs.lib.dns.networkToZone dom dom-cfg;
$ORIGIN ${dom}.
$TTL 12h
@ IN SOA ns1.${dom}. hostmaster.${dom}. ( # data = ''
${toString config.instance.build-timestamp} # $ORIGIN ${dom}.
30m # $TTL 12h
2m
3w
5m)
${optionalString (dom-cfg.default-host != null) # @ IN SOA ns1.${dom}. hostmaster.${dom}. (
"@ IN A ${dom-cfg.default-host}"} # ${toString config.instance.build-timestamp}
# 30m
# 2m
# 3w
# 5m)
${mxRecords dom-cfg.mx} # ${optionalString (dom-cfg.default-host != null)
# "@ IN A ${dom-cfg.default-host}"}
$TTL 6h # ${mxRecords dom-cfg.mx}
${optionalString (dom-cfg.gssapi-realm != null) # $TTL 6h
''_kerberos IN TXT "${dom-cfg.gssapi-realm}"''}
${nsRecords dom cfg.nameservers} # ${optionalString (dom-cfg.gssapi-realm != null)
${join-lines (mapAttrsToList hostRecords cfg.nameservers)} # ''_kerberos IN TXT "${dom-cfg.gssapi-realm}"''}
${dmarcRecord dom-cfg.dmarc-report-address} # ${nsRecords dom cfg.nameservers}
# ${join-lines (mapAttrsToList hostRecords cfg.nameservers)}
${join-lines # ${dmarcRecord dom-cfg.dmarc-report-address}
(mapAttrsToList makeSrvProtocolRecords net-cfg.srv-records)}
${join-lines (mapAttrsToList hostRecords net-cfg.hosts)} # ${join-lines
${join-lines (mapAttrsToList cnameRecord net-cfg.aliases)} # (mapAttrsToList makeSrvProtocolRecords net-cfg.srv-records)}
${join-lines net-cfg.verbatim-dns-records} # ${join-lines (mapAttrsToList hostRecords net-cfg.hosts)}
''; # ${join-lines (mapAttrsToList cnameRecord net-cfg.aliases)}
# ${join-lines net-cfg.verbatim-dns-records}
# '';
}) cfg.domains; }) cfg.domains;
}; };
}; };

View File

@ -67,6 +67,8 @@ let
hosts = attrNames cfg.sites; hosts = attrNames cfg.sites;
acme.auto = false;
# By default, listen on all ips # By default, listen on all ips
listen = let listen = let
common = { common = {
@ -89,7 +91,7 @@ let
cfg.sites; cfg.sites;
host_config = host_config =
mapAttrs (site: siteOpts: siteOpts.hostname) mapAttrs (site: siteOpts: siteOpts.site-config)
cfg.sites; cfg.sites;
}; };
@ -208,10 +210,11 @@ in {
fudo = let fudo = let
host-fqdn = config.instance.host-fqdn; host-fqdn = config.instance.host-fqdn;
in { in {
acme.host-domains.${hostname} = mapAttrs' (_: siteOpts: acme.host-domains.${hostname} = mapAttrs' (site: siteOpts:
nameValuePair siteOpts.hostname { nameValuePair siteOpts.hostname {
extra-domains = extra-domains =
(optional (siteOpts.hostname != host-fqdn) host-fqdn); (optional (siteOpts.hostname != host-fqdn) host-fqdn) ++
[ "pubsub.${site}" ];
local-copies.ejabberd = { local-copies.ejabberd = {
user = cfg.user; user = cfg.user;
group = cfg.group; group = cfg.group;

View File

@ -1,4 +1,4 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... } @ toplevel:
with lib; with lib;
let let
@ -13,7 +13,9 @@ let
}; };
}; };
stageOpts = { ... }: { stageOpts = { name, ... }: let
stage-name = name;
in {
options = with types; { options = with types; {
currencies = mkOption { currencies = mkOption {
type = attrsOf (submodule currencyOpts); type = attrsOf (submodule currencyOpts);
@ -39,25 +41,26 @@ let
''; '';
}; };
jabber-jid = mkOption { jabber = {
type = nullOr str; jid = mkOption {
description = "Jabber JID as which to connect."; type = str;
example = "chute-user@my.domain.org"; description = "Jabber JID as which to connect.";
default = null; example = "chute-user@my.domain.org";
}; default = null;
};
jabber-target = mkOption { resource = mkOption {
type = nullOr str; type = str;
description = "User to which logs will be sent."; description = "Jabber resource string.";
example = "target@my.domain.org"; default = "${toplevel.config.instance.hostname}-${stage-name}";
default = null; };
};
jabber-server = mkOption { target-jid = mkOption {
type = nullOr str; type = str;
description = "Jabber server."; description = "User to which logs will be sent.";
example = "my-server.domain.org"; example = "target@my.domain.org";
default = null; default = null;
};
}; };
}; };
}; };
@ -65,7 +68,9 @@ let
concatMapAttrs = f: attrs: concatMapAttrs = f: attrs:
foldr (a: b: a // b) {} (mapAttrsToList f attrs); foldr (a: b: a // b) {} (mapAttrsToList f attrs);
chute-job-definition = { stage, currency, stageOpts, currencyOpts }: { chute-job-definition = { stage, currency, stageOpts, currencyOpts }: let
join-args = concatStringsSep " ";
in {
after = [ "network-online.target" ]; after = [ "network-online.target" ];
wantedBy = [ "chute.target" ]; wantedBy = [ "chute.target" ];
partOf = [ "chute.target" ]; partOf = [ "chute.target" ];
@ -73,11 +78,15 @@ let
environmentFile = stageOpts.environment-file; environmentFile = stageOpts.environment-file;
execStart = let execStart = let
jabber-string = jabber-string =
optionalString (stageOpts.jabber-jid != null && optionalString (stageOpts.jabber != null)
stageOpts.jabber-target != null && (join-args ["--jabber-jid=${stageOpts.jabber.jid}"
stageOpts.jabber-server != null) "--target-jid=${stageOpts.jabber.target-jid}"
"--jabber-jid=${stageOpts.jabber-jid} --target-jid=${stageOpts.jabber-target} --jabber-server=${stageOpts.jabber-server}"; "--jabber-resource=${stageOpts.jabber.resource}-${currency}"]);
in "${stageOpts.package}/bin/chute --currency=${currency} --stop-at-percent=${toString currencyOpts.stop-percentile} ${jabber-string}";
in join-args ["${stageOpts.package}/bin/chute"
"--currency=${currency}"
"--stop-at-percent=${toString currencyOpts.stop-percentile}"
jabber-string];
privateNetwork = false; privateNetwork = false;
addressFamilies = [ "AF_INET" ]; addressFamilies = [ "AF_INET" ];
memoryDenyWriteExecute = false; # Needed becuz Clojure memoryDenyWriteExecute = false; # Needed becuz Clojure

View File

@ -45,6 +45,48 @@ let
}; };
}; };
hostRecords = hostname: nethost-data: let
sshfp-records = optionals (hasAttr hosttname config.fudo.hosts)
(map (sshfp: "${hostname} IN SSHFP ${sshfp}")
config.fudo.hosts.${hostname}.ssh-fingerprints);
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);
cnameRecord = alias: host: "${alias} IN CNAME ${host}";
dmarcRecord = dmarc-email:
optionalString (dmarc-email != null)
''_dmarc IN TXT "v=DMARC1;p=quarantine;sp=quarantine;rua=mailto:${dmarc-email};"'';
mxRecords = mxs: map (mx: "@ IN MX 10 ${mx}.") mxs;
nsRecords = domain: ns-hosts:
mapAttrsToList (host: _: "@ IN NS ${host}.${domain}.") ns-hosts;
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 A ${hostOpts.ipv6-address}";
description-record = host: hostOpts: (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: { srvRecordPair = domain: protocol: service: record: {
"_${service}._${protocol}.${domain}" = "_${service}._${protocol}.${domain}" =
"${toString record.priority} ${toString record.weight} ${ "${toString record.priority} ${toString record.weight} ${
@ -52,6 +94,36 @@ let
} ${record.host}."; } ${record.host}.";
}; };
domain-record = dom: domCfg: ''
$ORIGIN ${dom}.
$TTL ${domCfg.default-ttl}
${optionalString (domCfg.default-host != null)
"@ IN A ${domCfg.default-host}"}
${mxRecords domCfg.mx}
${optionalString (domCfg.gssapi-realm != null)
''_kerberos IN TXT "${domCfg.gssapi-realm}"''}
$TTL ${domCfg.host-record-ttl}
${nsRecords dom domCfg.nameservers}
${nsARecords dom domCfg.nameservers}
${dmarcRecord domCfg.dmarc-report-address}
${join-lines (mapAttrsToList makeSrvProtocolRecords domCfg.srv-records)}
${join-lines (mapAttrsToList hostRecords domCfg.hosts)}
${join-lines (mapAttrsToList cnameRecord domCfg.aliases)}
${join-lines domCfg.verbatim-dns-records}
${join-lines (mapAttrsToList
(subdom: subdomCfg: subdomain-record "${subdom}.${dom}" subdomCfg)
domCfg.subdomains)}
'';
in rec { in rec {
srvRecords = with types; attrsOf (attrsOf (listOf (submodule srvRecordOpts))); srvRecords = with types; attrsOf (attrsOf (listOf (submodule srvRecordOpts)));
@ -67,4 +139,18 @@ in rec {
concatMapAttrs concatMapAttrs
(service: records: map (srvRecordPair domain protocol service) records) services) (service: records: map (srvRecordPair domain protocol service) records) services)
srvRecords); srvRecords);
networkToZone = dom: domCfg: pkgs.writeText "zone-${dom}" ''
$ORIGIN ${dom}
$TTL ${domCfg.default-ttl}
@ IN SOA ns1.${dom}. hostmaster.${dom}. (
${toString config.instance.build-timestamp}
30m
2m
3w
5m)
${domain-record dom domCfg}
'';
} }

View File

@ -33,76 +33,115 @@ let
networkHostOpts = import ./network-host.nix { inherit lib; }; networkHostOpts = import ./network-host.nix { inherit lib; };
in { zoneOpts = { ... }: with types; {
options = with types; { options = {
hosts = mkOption { hosts = mkOption {
type = attrsOf (submodule networkHostOpts); type = attrsOf (submodule networkHostOpts);
description = "Hosts on the local network, with relevant settings."; description = "Hosts on the local network, with relevant settings.";
example = { example = {
my-host = { my-host = {
ipv4-address = "192.168.0.1"; ipv4-address = "192.168.0.1";
mac-address = "aa:aa:aa:aa:aa"; mac-address = "aa:aa:aa:aa:aa";
};
};
default = { };
};
srv-records = mkOption {
type = attrsOf (attrsOf (listOf (submodule srvRecordOpts)));
description = "SRV records for the network.";
example = {
tcp = {
kerberos = {
port = 88;
host = "krb-host.my-domain.com";
}; };
}; };
default = { };
}; };
default = { };
};
aliases = mkOption { nameservers = mkOption {
type = attrsOf str; type = attrsOf (submodule networkHostOpts);
default = { }; description = "Map of domain nameservers to host data.";
description = example = {
"A mapping of host-alias -> hostnames to add to the domain record."; "ns1" = {
example = { ipv4-address = "1.1.1.1";
mail = "my-mail-host"; ipv6-address = "1::1";
music = "musicall-host.other-domain.com."; };
};
default = {};
}; };
};
verbatim-dns-records = mkOption { srv-records = mkOption {
type = listOf str; type = attrsOf (attrsOf (listOf (submodule srvRecordOpts)));
description = "Records to be inserted verbatim into the DNS zone."; description = "SRV records for the network.";
example = [ "some-host IN CNAME base-host" ]; example = {
default = [ ]; tcp = {
}; kerberos = {
port = 88;
host = "krb-host.my-domain.com";
};
};
};
default = { };
};
dmarc-report-address = mkOption { aliases = mkOption {
type = nullOr str; type = attrsOf str;
description = "The email to use to recieve DMARC reports, if any."; default = { };
example = "admin-user@domain.com"; description =
default = null; "A mapping of host-alias -> hostnames to add to the domain record.";
}; example = {
mail = "my-mail-host";
music = "musicall-host.other-domain.com.";
};
};
default-host = mkOption { verbatim-dns-records = mkOption {
type = nullOr str; type = listOf str;
description = description = "Records to be inserted verbatim into the DNS zone.";
"IP of the host which will act as the default server for this domain, if any."; example = [ "some-host IN CNAME base-host" ];
default = null; default = [ ];
}; };
mx = mkOption { dmarc-report-address = mkOption {
type = listOf str; type = nullOr str;
description = "A list of mail servers serving this domain."; description = "The email to use to recieve DMARC reports, if any.";
default = [ ]; example = "admin-user@domain.com";
}; default = null;
};
gssapi-realm = mkOption { default-host = mkOption {
type = nullOr str; type = nullOr str;
description = "Kerberos GSSAPI realm of the zone."; description =
default = null; "IP of the host which will act as the default server for this domain, if any.";
default = null;
};
mx = mkOption {
type = listOf str;
description = "A list of mail servers serving this domain.";
default = [ ];
};
gssapi-realm = mkOption {
type = nullOr str;
description = "Kerberos GSSAPI realm of the zone.";
default = null;
};
default-ttl = mkOption {
type = str;
description = "Default time-to-live for this zone.";
default = "3h";
};
host-record-ttl = mkOption {
type = str;
description = "Default time-to-live for records in this zone";
default = "1h";
};
description = mkOption {
type = str;
description = "Description of this zone.";
};
subdomains = mkOption {
type = attrsOf (submodule zoneOpts);
description = "Subdomains of the current zone.";
default = {};
};
}; };
}; };
in {
options = zoneOpts;
} }