2021-03-20 02:34:13 +01:00
|
|
|
{ config, options, lib, ... }:
|
2021-02-24 23:44:23 +01:00
|
|
|
|
|
|
|
with lib;
|
|
|
|
let
|
2021-11-06 03:38:01 +01:00
|
|
|
# A host needs to know a network if it
|
|
|
|
# - is a configured interface, or
|
|
|
|
# - is required behind at least two different links
|
|
|
|
getHostLinkNetworks = hostName: link:
|
2021-11-03 01:07:44 +01:00
|
|
|
let
|
2021-11-06 03:38:01 +01:00
|
|
|
hostConfig = config.site.hosts.${hostName};
|
|
|
|
# all the host's links
|
|
|
|
hostLinkNetworks = builtins.mapAttrs (link: _:
|
|
|
|
networksBehindLink hostName link
|
|
|
|
) hostConfig.links;
|
|
|
|
# how many links have a net in networksBehindLink
|
|
|
|
networkLinkCount = net:
|
|
|
|
builtins.length (
|
|
|
|
builtins.filter (builtins.elem net)
|
|
|
|
(builtins.attrValues hostLinkNetworks)
|
|
|
|
);
|
2021-11-03 01:07:44 +01:00
|
|
|
in
|
2021-11-06 03:38:01 +01:00
|
|
|
# access port
|
|
|
|
if config.site.net ? ${link}
|
2021-11-03 01:07:44 +01:00
|
|
|
then [ link ]
|
|
|
|
|
2021-11-06 03:38:01 +01:00
|
|
|
# multiple vlans on this link
|
|
|
|
else
|
|
|
|
builtins.filter (net:
|
|
|
|
# this port and local interface
|
|
|
|
hostConfig.interfaces ? ${net}
|
|
|
|
||
|
|
|
|
# this port and another
|
|
|
|
networkLinkCount net > 1
|
|
|
|
) hostLinkNetworks.${link};
|
2021-11-03 01:07:44 +01:00
|
|
|
|
2021-11-06 03:38:01 +01:00
|
|
|
networksBehindLink = hostName: name:
|
|
|
|
networksBehindLink' {
|
|
|
|
"${hostName}" = true;
|
|
|
|
} [ name ];
|
2021-11-03 01:07:44 +01:00
|
|
|
|
2021-11-06 03:38:01 +01:00
|
|
|
networksBehindLink' = seen: links:
|
2021-11-03 01:07:44 +01:00
|
|
|
if links == []
|
2021-11-06 03:38:01 +01:00
|
|
|
then
|
|
|
|
# Done, result is the seen link names that are networks:
|
|
|
|
builtins.filter (name:
|
|
|
|
config.site.net ? ${name}
|
|
|
|
) (builtins.attrNames seen)
|
2021-11-03 01:07:44 +01:00
|
|
|
else
|
|
|
|
let
|
|
|
|
link = builtins.head links;
|
|
|
|
seen' = seen // {
|
|
|
|
"${link}" = true;
|
|
|
|
};
|
2021-11-04 19:17:31 +01:00
|
|
|
onlyUnseen = builtins.filter (link: ! seen' ? ${link});
|
2021-11-03 01:07:44 +01:00
|
|
|
links' = builtins.tail links;
|
|
|
|
in
|
2021-11-04 19:17:31 +01:00
|
|
|
if config.site.hosts ? ${link}
|
2021-11-06 03:38:01 +01:00
|
|
|
then networksBehindLink' seen' (
|
2021-11-04 19:17:31 +01:00
|
|
|
links' ++ (
|
2021-11-06 03:38:01 +01:00
|
|
|
builtins.attrNames config.site.hosts.${link}.interfaces
|
|
|
|
) ++ (
|
2021-11-04 19:17:31 +01:00
|
|
|
onlyUnseen (builtins.attrNames config.site.hosts.${link}.links)
|
|
|
|
)
|
2021-11-03 01:07:44 +01:00
|
|
|
)
|
2021-11-06 03:38:01 +01:00
|
|
|
else if config.site.net ? ${link}
|
|
|
|
then networksBehindLink' seen' links'
|
|
|
|
|
|
|
|
else throw "Link to invalid target ${link}";
|
2021-11-03 01:07:44 +01:00
|
|
|
|
2021-03-27 02:07:14 +01:00
|
|
|
dhcpOpts = {
|
|
|
|
start = mkOption {
|
|
|
|
description = "First IP address in pool";
|
|
|
|
type = types.str;
|
|
|
|
};
|
|
|
|
end = mkOption {
|
|
|
|
description = "Last IP address in pool";
|
|
|
|
type = types.str;
|
|
|
|
};
|
|
|
|
time = mkOption {
|
|
|
|
description = "Renew time in seconds";
|
|
|
|
type = types.int;
|
|
|
|
};
|
|
|
|
max-time = mkOption {
|
|
|
|
description = "Max renew time in seconds";
|
|
|
|
type = types.int;
|
|
|
|
};
|
2021-03-31 02:11:19 +02:00
|
|
|
server = mkOption {
|
|
|
|
description = "Container that runs the DHCP server";
|
|
|
|
type = types.str;
|
|
|
|
};
|
2021-03-27 02:07:14 +01:00
|
|
|
router = mkOption {
|
|
|
|
description = "Gateway";
|
|
|
|
type = types.str;
|
|
|
|
};
|
2021-03-31 02:20:08 +02:00
|
|
|
fixed-hosts = mkOption {
|
|
|
|
type = with types; attrsOf str;
|
|
|
|
default = {};
|
|
|
|
};
|
2021-03-27 02:07:14 +01:00
|
|
|
};
|
2021-09-19 02:18:17 +02:00
|
|
|
|
2021-02-24 23:44:23 +01:00
|
|
|
netOpts = { name, ... }: {
|
|
|
|
options = {
|
|
|
|
vlan = mkOption {
|
|
|
|
description = "VLAN tag number";
|
|
|
|
type = types.int;
|
|
|
|
};
|
|
|
|
subnet4 = mkOption {
|
|
|
|
description = "v.w.x.y/z";
|
2021-02-25 01:06:32 +01:00
|
|
|
type = with types; nullOr str;
|
2021-02-24 23:44:23 +01:00
|
|
|
default = null;
|
|
|
|
};
|
2021-03-31 02:11:19 +02:00
|
|
|
subnet4Net = mkOption {
|
|
|
|
type = with types; nullOr types.str;
|
|
|
|
default =
|
|
|
|
let
|
|
|
|
inherit (config.site.net.${name}) subnet4;
|
|
|
|
s = lib.splitString "/" subnet4;
|
|
|
|
in
|
|
|
|
if subnet4 != null && builtins.length s == 2
|
|
|
|
then builtins.head s
|
|
|
|
else null;
|
|
|
|
};
|
2021-03-25 00:46:46 +01:00
|
|
|
subnet4Len = mkOption {
|
|
|
|
type = with types; nullOr types.int;
|
|
|
|
default =
|
|
|
|
let
|
|
|
|
inherit (config.site.net.${name}) subnet4;
|
|
|
|
s = lib.splitString "/" subnet4;
|
|
|
|
in
|
|
|
|
if subnet4 != null && builtins.length s == 2
|
|
|
|
then lib.toInt (elemAt s 1)
|
|
|
|
else null;
|
|
|
|
};
|
2021-02-24 23:44:23 +01:00
|
|
|
subnets6 = mkOption {
|
|
|
|
description = "IPv6 subnets w/o prefixlen (always 64)";
|
2021-02-25 01:06:32 +01:00
|
|
|
type = with types; attrsOf str;
|
2021-02-24 23:44:23 +01:00
|
|
|
default = {};
|
|
|
|
};
|
2021-03-25 00:08:24 +01:00
|
|
|
hosts4 = mkOption {
|
|
|
|
description = "Attribute set of hostnames to IPv4 addresses";
|
|
|
|
type = with types; attrsOf str;
|
|
|
|
default = {};
|
|
|
|
};
|
|
|
|
hosts6 = mkOption {
|
|
|
|
description = "Attribute set of contexts to attribute sets of hostnames to IPv4 addresses";
|
|
|
|
type = with types; attrsOf (attrsOf str);
|
|
|
|
default = {};
|
|
|
|
};
|
2021-03-25 04:06:53 +01:00
|
|
|
ospf = {
|
|
|
|
secret = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
};
|
2021-03-27 02:07:14 +01:00
|
|
|
dhcp = mkOption {
|
|
|
|
type = with types; nullOr (submodule { options = dhcpOpts; });
|
|
|
|
default = null;
|
|
|
|
};
|
2021-04-02 03:09:45 +02:00
|
|
|
domainName = mkOption {
|
|
|
|
description = "Domain name option";
|
|
|
|
type = types.str;
|
2021-05-03 01:26:57 +02:00
|
|
|
default = "${name}.zentralwerk.dn42";
|
|
|
|
};
|
|
|
|
dynamicDomain = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = "Domain updated by DHCP server?";
|
2021-04-02 03:09:45 +02:00
|
|
|
};
|
2021-02-24 23:44:23 +01:00
|
|
|
};
|
|
|
|
};
|
2021-09-19 02:18:17 +02:00
|
|
|
|
2021-03-31 02:46:21 +02:00
|
|
|
upstreamOpts = {
|
2021-05-23 23:16:28 +02:00
|
|
|
provider = mkOption {
|
|
|
|
type = types.str;
|
|
|
|
};
|
2021-05-31 00:41:38 +02:00
|
|
|
link = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
description = "Underlying interface name for eg. PPPoE";
|
|
|
|
};
|
2021-09-06 22:58:52 +02:00
|
|
|
staticIpv4Address = mkOption {
|
|
|
|
type = with types; nullOr str;
|
2021-09-07 00:11:28 +02:00
|
|
|
default = null;
|
2021-09-06 22:58:52 +02:00
|
|
|
};
|
2021-03-31 02:46:21 +02:00
|
|
|
upBandwidth = mkOption {
|
|
|
|
type = with types; nullOr int;
|
2021-05-22 01:19:16 +02:00
|
|
|
default = null;
|
2021-03-31 02:46:21 +02:00
|
|
|
};
|
2021-05-01 01:14:54 +02:00
|
|
|
noNat.subnets6 = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
description = "Do not NAT66 traffic from these public static subnets";
|
|
|
|
};
|
2021-03-31 02:46:21 +02:00
|
|
|
};
|
2021-09-19 02:18:17 +02:00
|
|
|
|
2021-03-23 00:40:40 +01:00
|
|
|
interfaceOpts = { name, ... }: {
|
|
|
|
options = {
|
|
|
|
hwaddr = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
2021-04-10 14:52:13 +02:00
|
|
|
description = "Static MAC address";
|
2021-03-23 00:40:40 +01:00
|
|
|
};
|
|
|
|
type = mkOption {
|
2021-11-03 01:07:44 +01:00
|
|
|
type = types.enum [ "phys" "veth" "pppoe" "bridge" ];
|
2021-04-10 14:52:13 +02:00
|
|
|
description = ''
|
|
|
|
veth: Virtual ethernet to be attached to a bridge.
|
|
|
|
|
|
|
|
phys: (Physical) interface from a server moved into the
|
|
|
|
container. Do not use with VLAN interfaces because they
|
|
|
|
won't be moved back after lxc-stop.
|
|
|
|
'';
|
2021-03-23 00:40:40 +01:00
|
|
|
};
|
2021-03-25 00:08:24 +01:00
|
|
|
gw4 = mkOption {
|
2021-03-23 00:40:40 +01:00
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
2021-04-10 14:52:13 +02:00
|
|
|
description = "IPv4 gateway";
|
2021-03-23 00:40:40 +01:00
|
|
|
};
|
|
|
|
gw6 = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
2021-04-10 14:52:13 +02:00
|
|
|
description = "IPv6 gateway";
|
2021-03-23 00:40:40 +01:00
|
|
|
};
|
2021-03-31 02:46:21 +02:00
|
|
|
upstream = mkOption {
|
|
|
|
type = with types; nullOr (submodule { options = upstreamOpts; });
|
|
|
|
default = null;
|
2021-04-10 14:52:13 +02:00
|
|
|
description = "Upstream interface configuration";
|
2021-03-31 02:46:21 +02:00
|
|
|
};
|
2021-03-23 00:40:40 +01:00
|
|
|
};
|
|
|
|
};
|
2021-09-19 02:18:17 +02:00
|
|
|
|
2021-02-25 01:06:32 +01:00
|
|
|
hostOpts = { name, ... }: {
|
|
|
|
options = {
|
2021-04-30 22:38:57 +02:00
|
|
|
prebuilt = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = ''
|
|
|
|
Include the container system in the server's `build-container` script.
|
|
|
|
'';
|
|
|
|
};
|
2021-11-04 19:17:31 +01:00
|
|
|
firstboot = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = ''
|
|
|
|
true if host is a newly flashed OpenWRT device with a default address
|
|
|
|
'';
|
|
|
|
};
|
2021-02-25 01:06:32 +01:00
|
|
|
role = mkOption {
|
2021-03-20 00:06:31 +01:00
|
|
|
type = types.enum [ "ap" "switch" "server" "container" "client" ];
|
2021-02-25 01:06:32 +01:00
|
|
|
default = "client";
|
|
|
|
};
|
|
|
|
model = mkOption {
|
|
|
|
type = types.str;
|
2021-03-20 00:06:31 +01:00
|
|
|
default = {
|
|
|
|
ap = "unknown";
|
|
|
|
switch = "unknown";
|
|
|
|
server = "pc";
|
|
|
|
container = "lxc";
|
|
|
|
client = "any";
|
|
|
|
}."${config.site.hosts.${name}.role}";
|
2021-02-25 01:06:32 +01:00
|
|
|
};
|
|
|
|
password = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
location = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
};
|
2021-03-23 00:40:40 +01:00
|
|
|
interfaces = mkOption {
|
|
|
|
default = {};
|
|
|
|
type = with types; attrsOf (submodule interfaceOpts);
|
2021-04-10 14:52:13 +02:00
|
|
|
description = "Network interfaces";
|
2021-03-23 00:40:40 +01:00
|
|
|
};
|
2021-05-31 00:06:56 +02:00
|
|
|
physicalInterfaces = mkOption {
|
|
|
|
default = lib.filterAttrs (_: { type, ... }:
|
|
|
|
builtins.elem type [ "phys" "veth" ]
|
|
|
|
) config.site.hosts.${name}.interfaces;
|
|
|
|
type = with types; attrsOf (submodule interfaceOpts);
|
|
|
|
description = "Network interfaces that are not virtual (don't set!)";
|
|
|
|
};
|
2021-03-25 00:46:46 +01:00
|
|
|
isRouter = mkOption {
|
|
|
|
type = types.bool;
|
2021-04-10 14:44:44 +02:00
|
|
|
# isRouter = Part of the core network?
|
|
|
|
default =
|
|
|
|
config.site.hosts.${name}.interfaces ? core &&
|
2021-04-12 22:35:02 +02:00
|
|
|
config.site.net.core.hosts4 ? ${name};
|
2021-04-10 14:44:44 +02:00
|
|
|
description = "Should this host route?";
|
2021-03-25 00:46:46 +01:00
|
|
|
};
|
2021-04-14 20:04:28 +02:00
|
|
|
firewall.enable = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
description = "Enable firewall to disallow incoming connections from core";
|
|
|
|
};
|
2021-04-08 02:30:50 +02:00
|
|
|
forwardPorts = mkOption {
|
2021-03-31 02:46:21 +02:00
|
|
|
type = with types; listOf (submodule { options = {
|
|
|
|
proto = mkOption {
|
|
|
|
type = types.enum [ "tcp" "udp" ];
|
|
|
|
};
|
2021-04-01 01:16:13 +02:00
|
|
|
sourcePort = mkOption {
|
2021-03-31 02:46:21 +02:00
|
|
|
type = types.int;
|
|
|
|
};
|
2021-04-01 01:16:13 +02:00
|
|
|
destination = mkOption {
|
2021-03-31 02:46:21 +02:00
|
|
|
type = types.str;
|
|
|
|
};
|
2021-10-16 23:56:32 +02:00
|
|
|
reflect = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = true;
|
|
|
|
description = ''
|
|
|
|
Enable NAT reflection
|
|
|
|
|
|
|
|
Any forwarded connection will have our static IPv4
|
|
|
|
address as source so that forwarded services become
|
|
|
|
available internally.
|
|
|
|
|
|
|
|
Unfortunately, this breaks identification by IPv4
|
|
|
|
adress.
|
|
|
|
'';
|
|
|
|
};
|
2021-03-31 02:46:21 +02:00
|
|
|
}; });
|
|
|
|
default = [];
|
|
|
|
};
|
2021-03-25 04:06:53 +01:00
|
|
|
ospf.stubNets4 = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
2021-04-10 14:52:13 +02:00
|
|
|
description = "Additional IPv4 networks to announce";
|
2021-03-25 04:06:53 +01:00
|
|
|
};
|
|
|
|
ospf.stubNets6 = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
2021-04-10 14:52:13 +02:00
|
|
|
description = "Additional IPv6 networks to announce";
|
2021-03-25 04:06:53 +01:00
|
|
|
};
|
2021-04-29 01:44:48 +02:00
|
|
|
ospf.allowedUpstreams = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
default = [];
|
|
|
|
description = "Accept default routes from these OSPF routers, in order of preference";
|
|
|
|
};
|
2021-04-30 23:54:36 +02:00
|
|
|
ospf.upstreamInstance = mkOption {
|
|
|
|
type = with types; nullOr int;
|
|
|
|
default = null;
|
|
|
|
description = "OSPF instance for advertising the default route";
|
|
|
|
};
|
2021-04-05 15:54:15 +02:00
|
|
|
wireguard = mkOption {
|
|
|
|
default = {};
|
|
|
|
type = with types; attrsOf (submodule (
|
|
|
|
{ name, ... }: {
|
|
|
|
options = {
|
|
|
|
endpoint = mkOption {
|
|
|
|
type = str;
|
|
|
|
};
|
|
|
|
publicKey = mkOption {
|
|
|
|
type = str;
|
|
|
|
};
|
|
|
|
privateKey = mkOption {
|
|
|
|
type = str;
|
|
|
|
};
|
|
|
|
addresses = mkOption {
|
|
|
|
type = listOf str;
|
|
|
|
};
|
2021-04-06 19:32:25 +02:00
|
|
|
upBandwidth = mkOption {
|
|
|
|
type = with types; nullOr int;
|
|
|
|
};
|
2021-04-05 15:54:15 +02:00
|
|
|
};
|
|
|
|
}
|
|
|
|
));
|
|
|
|
};
|
2021-04-13 00:11:42 +02:00
|
|
|
bgp = mkOption {
|
|
|
|
default = null;
|
|
|
|
type = with types; nullOr (submodule {
|
|
|
|
options = bgpOpts;
|
|
|
|
});
|
|
|
|
};
|
2021-05-03 01:26:57 +02:00
|
|
|
services.dns = {
|
|
|
|
enable = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
};
|
|
|
|
};
|
2021-04-14 23:07:27 +02:00
|
|
|
services.dnscache.enable = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
default = false;
|
|
|
|
};
|
2021-11-03 01:07:44 +01:00
|
|
|
links = mkOption {
|
|
|
|
description = "Which port is connected to what other device? Keys are either network names or known hostnames.";
|
|
|
|
default = {};
|
2021-11-06 03:38:01 +01:00
|
|
|
type = with types; attrsOf (submodule (linkOpts name));
|
2021-11-03 01:07:44 +01:00
|
|
|
};
|
2021-11-04 19:17:31 +01:00
|
|
|
wifi = mkOption {
|
|
|
|
default = {};
|
|
|
|
type = with types; attrsOf (submodule (
|
|
|
|
{ name, ... }: {
|
|
|
|
options = {
|
|
|
|
htmode = mkOption {
|
|
|
|
type = enum [ "HT20" "HT40-" "HT40+" "VHT80" ];
|
|
|
|
};
|
|
|
|
channel = mkOption {
|
|
|
|
type = int;
|
|
|
|
};
|
|
|
|
ssids = mkOption {
|
|
|
|
type = attrsOf (submodule (
|
|
|
|
{ name, ... }: {
|
|
|
|
options = {
|
|
|
|
net = mkOption {
|
|
|
|
type = str;
|
|
|
|
};
|
|
|
|
psk = mkOption {
|
|
|
|
type = nullOr str;
|
|
|
|
default = null;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
));
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}
|
|
|
|
));
|
|
|
|
};
|
2021-04-13 00:11:42 +02:00
|
|
|
};
|
|
|
|
};
|
2021-09-19 02:18:17 +02:00
|
|
|
|
2021-04-13 00:11:42 +02:00
|
|
|
bgpOpts = {
|
|
|
|
asn = mkOption {
|
|
|
|
type = types.int;
|
|
|
|
};
|
2021-04-13 00:46:12 +02:00
|
|
|
peers = mkOption {
|
|
|
|
type = with types; attrsOf (submodule ({ name, ... }: {
|
|
|
|
options = {
|
|
|
|
asn = mkOption {
|
|
|
|
type = types.int;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
}));
|
2021-04-13 00:11:42 +02:00
|
|
|
default = {};
|
2021-02-25 01:06:32 +01:00
|
|
|
};
|
|
|
|
};
|
2021-11-03 01:07:44 +01:00
|
|
|
|
2021-11-06 03:38:01 +01:00
|
|
|
linkOpts = hostName: { name, ... }: {
|
2021-11-03 01:07:44 +01:00
|
|
|
options = {
|
|
|
|
ports = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
description = "Port names";
|
|
|
|
};
|
|
|
|
group = mkOption {
|
|
|
|
type = with types; nullOr str;
|
|
|
|
default = null;
|
|
|
|
description = "Link aggregation group with a fixed number";
|
|
|
|
};
|
|
|
|
nets = mkOption {
|
|
|
|
type = with types; listOf str;
|
2021-11-04 19:17:31 +01:00
|
|
|
description = "Automatically generated";
|
2021-11-06 03:38:01 +01:00
|
|
|
default = getHostLinkNetworks hostName name;
|
2021-11-03 01:07:44 +01:00
|
|
|
};
|
|
|
|
vlans = mkOption {
|
|
|
|
type = with types; listOf int;
|
|
|
|
description = "Automatically generated, do not set";
|
|
|
|
default = map (net:
|
|
|
|
config.site.net.${net}.vlan
|
2021-11-06 03:38:01 +01:00
|
|
|
) config.site.hosts.${hostName}.links.${name}.nets;
|
2021-11-03 01:07:44 +01:00
|
|
|
};
|
2021-11-06 22:56:54 +01:00
|
|
|
trunk = mkOption {
|
|
|
|
type = types.bool;
|
|
|
|
description = "Trunk with tagged VLANs?";
|
|
|
|
default =
|
|
|
|
if config.site.net ? ${name}
|
|
|
|
then false
|
|
|
|
else if config.site.hosts ? ${name}
|
|
|
|
then true
|
|
|
|
else throw "Invalid link target: \"${name}\"";
|
|
|
|
};
|
2021-11-03 01:07:44 +01:00
|
|
|
};
|
|
|
|
};
|
2021-02-24 23:44:23 +01:00
|
|
|
in
|
|
|
|
{
|
|
|
|
options.site = {
|
|
|
|
net = mkOption {
|
2021-09-19 02:18:17 +02:00
|
|
|
description = "All subnets";
|
2021-02-24 23:44:23 +01:00
|
|
|
default = {};
|
|
|
|
type = with types; attrsOf (submodule netOpts);
|
|
|
|
};
|
2021-11-04 19:17:57 +01:00
|
|
|
|
2021-02-25 01:06:32 +01:00
|
|
|
hosts = mkOption {
|
2021-09-19 02:18:17 +02:00
|
|
|
description = "All the static hosts";
|
2021-02-25 01:06:32 +01:00
|
|
|
default = {};
|
|
|
|
type = with types; attrsOf (submodule hostOpts);
|
|
|
|
};
|
2021-11-04 19:17:57 +01:00
|
|
|
|
|
|
|
sshPubKeys = mkOption {
|
|
|
|
type = with types; listOf str;
|
|
|
|
};
|
2021-02-24 23:44:23 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
config.warnings =
|
|
|
|
let
|
|
|
|
findCollisions = getter: xs:
|
|
|
|
(builtins.foldl' ({ known, dup }: k:
|
|
|
|
let
|
|
|
|
ks = builtins.toString k;
|
|
|
|
in
|
|
|
|
if known ? ${ks}
|
|
|
|
then { inherit known; dup = dup ++ [ks]; }
|
|
|
|
else { known = known // { ${ks} = true; }; inherit dup; }
|
|
|
|
) {
|
|
|
|
known = {}; dup = [];
|
|
|
|
} (
|
|
|
|
concatMap getter (builtins.attrValues xs)
|
|
|
|
)).dup;
|
|
|
|
reportCollisions = name: getter: xs:
|
|
|
|
map (k: "Duplicate ${name}: ${k}") (findCollisions getter xs);
|
2021-04-29 01:44:48 +02:00
|
|
|
|
|
|
|
ospfUpstreamXorGw =
|
|
|
|
builtins.concatMap (hostName:
|
|
|
|
let
|
|
|
|
hostConf = config.site.hosts.${hostName};
|
|
|
|
gwNets = builtins.filter (netName:
|
|
|
|
hostConf.interfaces.${netName}.gw4 != null
|
|
|
|
) (builtins.attrNames hostConf.interfaces);
|
|
|
|
in if gwNets != [] && hostConf.ospf.allowedUpstreams != []
|
|
|
|
then [ ''
|
|
|
|
Host ${hostName} has gateway on ${builtins.head gwNets} but accepts default routes from OSPF
|
|
|
|
'' ]
|
|
|
|
else []
|
|
|
|
) (builtins.attrNames config.site.hosts);
|
2021-02-24 23:44:23 +01:00
|
|
|
in
|
|
|
|
(reportCollisions "VLAN tag" (x: [x.vlan]) config.site.net) ++
|
|
|
|
(reportCollisions "IPv4 subnet" (x: if x.subnet4 == null then [] else [x.subnet4]) config.site.net) ++
|
2021-04-29 01:44:48 +02:00
|
|
|
(reportCollisions "IPv6 subnet" (x: builtins.attrValues x.subnets6) config.site.net) ++
|
|
|
|
ospfUpstreamXorGw;
|
2021-11-07 02:43:56 +01:00
|
|
|
|
|
|
|
config.assertions = map (name: {
|
|
|
|
assertion = ! config.site.net ? ${name};
|
|
|
|
message = "Host \"${name}\" must be named differently if net \"${name}\" exists.";
|
|
|
|
}) (builtins.attrNames config.site.hosts);
|
2021-02-24 23:44:23 +01:00
|
|
|
}
|