Commit 8640088
Changed files (4)
modules
hosts
mobius
tribios
services
modules/hosts/mobius/networking.nix
@@ -7,6 +7,11 @@
};
den.aspects.mobius = {
+ dhcpReservation = {
+ ip = "192.168.52.3";
+ mac = "00:e0:1a:00:1d:7b";
+ };
+
nixos = {
networking.networkmanager.enable = true;
};
modules/hosts/tribios/networking.nix
@@ -12,10 +12,24 @@
lan = ["br-lan"];
};
};
+ settings.services.kea = {
+ interfaces = ["br-lan"];
+ router = "192.168.52.1";
+ dnsServers = ["223.5.5.5" "119.29.29.29"];
+ extraReservations = [
+ {
+ # AP.
+ hostname = "ap";
+ mac = "f4:f6:47:33:56:98";
+ ip = "192.168.52.2";
+ }
+ ];
+ };
};
den.aspects.tribios = {
includes = [
+ den.aspects.services.kea
den.aspects.services.mihomo
den.aspects.services.mihomo.tproxy
];
@@ -76,36 +90,6 @@
};
systemd.services.miniupnpd.after = ["systemd-networkd.service"];
-
- services.kea.dhcp4 = {
- enable = true;
- settings = {
- interfaces-config.interfaces = ["br-lan"];
- lease-database = {
- name = "/var/lib/kea/dhcp4.leases";
- persist = true;
- type = "memfile";
- };
- valid-lifetime = 4000;
- subnet4 = [
- {
- id = 1;
- subnet = "192.168.52.0/24";
- pools = [{pool = "192.168.52.2 - 192.168.52.200";}];
- option-data = [
- {
- name = "routers";
- data = "192.168.52.1";
- }
- {
- name = "domain-name-servers";
- data = "223.5.5.5, 119.29.29.29";
- }
- ];
- }
- ];
- };
- };
};
};
}
modules/services/kea/default.nix
@@ -0,0 +1,156 @@
+{lib, ...}: let
+ octets = ip: lib.splitString "." ip;
+ lastOctet = ip: lib.toInt (builtins.elemAt (octets ip) 3);
+ prefix = ip: lib.concatStringsSep "." (lib.take 3 (octets ip));
+ joinIP = prefix: last: "${prefix}.${toString last}";
+in {
+ den.aspects.services.kea = {
+ settings.host = {
+ interfaces = lib.mkOption {
+ description = "Interfaces the DHCP server listens on.";
+ type = lib.types.listOf lib.types.str;
+ };
+ staticRange = {
+ start = lib.mkOption {
+ description = "Start of the static IP range. Reservations must fall within it.";
+ type = lib.types.str;
+ default = "192.168.52.2";
+ };
+ end = lib.mkOption {
+ description = "End of the static IP range. Reservations must fall within it.";
+ type = lib.types.str;
+ default = "192.168.52.50";
+ };
+ };
+ poolEnd = lib.mkOption {
+ description = "End of the dynamic pool. The pool starts right after the static range.";
+ type = lib.types.str;
+ default = "192.168.52.200";
+ };
+ router = lib.mkOption {
+ description = "Default gateway advertised to clients.";
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ };
+ dnsServers = lib.mkOption {
+ description = "DNS servers advertised to clients.";
+ type = lib.types.listOf lib.types.str;
+ default = [];
+ };
+ extraReservations = lib.mkOption {
+ description = "Reservations for devices not managed by Nix, e.g. an AP.";
+ type = lib.types.listOf (lib.types.submodule {
+ options = {
+ hostname = lib.mkOption {
+ description = "Hostname for the device.";
+ type = lib.types.nullOr lib.types.str;
+ default = null;
+ };
+ mac = lib.mkOption {
+ description = "MAC address to reserve.";
+ type = lib.types.str;
+ };
+ ip = lib.mkOption {
+ description = "Fixed IP to assign.";
+ type = lib.types.str;
+ };
+ };
+ });
+ default = [];
+ };
+ };
+
+ nixos = {
+ host,
+ dhcpReservation,
+ ...
+ }: let
+ cfg = host.settings.services.kea;
+
+ fromHosts = lib.unique (
+ map (e: {
+ hostname = e.source.host.name;
+ mac = e.value.mac;
+ ip = e.value.ip;
+ })
+ dhcpReservation
+ );
+
+ reservations = cfg.extraReservations ++ fromHosts;
+
+ staticPrefix = prefix cfg.staticRange.start;
+ staticStart = lastOctet cfg.staticRange.start;
+ staticEnd = lastOctet cfg.staticRange.end;
+ poolStart = joinIP staticPrefix (staticEnd + 1);
+
+ inStaticRange = ip:
+ prefix ip
+ == staticPrefix
+ && lastOctet ip >= staticStart
+ && lastOctet ip <= staticEnd;
+
+ nameOf = r:
+ if r.hostname != null
+ then r.hostname
+ else r.ip;
+
+ macs = map (r: r.mac) reservations;
+ ips = map (r: r.ip) reservations;
+ in {
+ assertions =
+ (map (r: {
+ assertion = inStaticRange r.ip;
+ message =
+ "Kea reservation '${nameOf r}' has ip '${r.ip}' outside the static "
+ + "range '${cfg.staticRange.start} - ${cfg.staticRange.end}'";
+ })
+ reservations)
+ ++ [
+ {
+ assertion = lib.unique macs == macs;
+ message = "Kea reservations contain duplicate MAC addresses";
+ }
+ {
+ assertion = lib.unique ips == ips;
+ message = "Kea reservations contain duplicate IP addresses";
+ }
+ ];
+
+ services.kea.dhcp4 = {
+ enable = true;
+ settings = {
+ interfaces-config.interfaces = cfg.interfaces;
+ lease-database = {
+ name = "/var/lib/kea/dhcp4.leases";
+ persist = true;
+ type = "memfile";
+ };
+ valid-lifetime = 4000;
+ subnet4 = [
+ {
+ id = 1;
+ subnet = "${staticPrefix}.0/24";
+ pools = [{pool = "${poolStart} - ${cfg.poolEnd}";}];
+ reservations = map (r:
+ {
+ hw-address = r.mac;
+ ip-address = r.ip;
+ }
+ // lib.optionalAttrs (r.hostname != null) {hostname = r.hostname;})
+ reservations;
+ option-data =
+ lib.optional (cfg.router != null) {
+ name = "routers";
+ data = cfg.router;
+ }
+ ++ lib.optional (cfg.dnsServers != []) {
+ name = "domain-name-servers";
+ data = lib.concatStringsSep ", " cfg.dnsServers;
+ };
+ }
+ ];
+ };
+ };
+ };
+ };
+}
modules/services/kea/quirks.nix
@@ -0,0 +1,22 @@
+{den, ...}: {
+ den.quirks.dhcpReservation.description = ''
+ DHCP reservations self-declared by hosts, collected fleet-wide.
+
+ Example:
+ {
+ mac = "00:11:22:33:44:55"; # MAC address to reserve
+ ip = "192.168.52.10"; # Fixed IP to assign
+ }
+ '';
+
+ den.policies.collect-dhcp-reservations = _: let
+ inherit (den.lib.policy) pipe;
+ in [
+ (pipe.from "dhcpReservation" [
+ (pipe.collectAll ({host, ...}: true))
+ pipe.withProvenance
+ ])
+ ];
+
+ den.schema.host.includes = [den.policies.collect-dhcp-reservations];
+}