1 { config, lib, pkgs, ... }:
7 cfg4 = config.services.dhcpd4;
8 cfg6 = config.services.dhcpd6;
10 writeConfig = postfix: cfg: pkgs.writeText "dhcpd.conf"
12 default-lease-time 600;
14 ${optionalString (!cfg.authoritative) "not "}authoritative;
15 ddns-update-style interim;
16 log-facility local1; # see dhcpd.nix
20 ${lib.concatMapStrings
22 host ${machine.hostName} {
23 hardware ethernet ${machine.ethernetAddress};
25 optionalString (postfix == "6") postfix
26 } ${machine.ipAddress};
33 dhcpdService = postfix: cfg:
36 if cfg.configFile != null
38 else writeConfig postfix cfg;
39 leaseFile = "/var/lib/dhcpd${postfix}/dhcpd.leases";
41 "@${pkgs.dhcp}/sbin/dhcpd" "dhcpd${postfix}" "-${postfix}"
42 "-pf" "/run/dhcpd${postfix}/dhcpd.pid"
48 optionalAttrs cfg.enable {
50 description = "DHCPv${postfix} server";
51 wantedBy = [ "multi-user.target" ];
52 after = [ "network.target" ];
54 preStart = "touch ${leaseFile}";
56 ExecStart = concatMapStringsSep " " escapeShellArg args;
62 AmbientCapabilities = [
63 "CAP_NET_RAW" # to send ICMP messages
64 "CAP_NET_BIND_SERVICE" # to bind on DHCP port (67)
66 StateDirectory = "dhcpd${postfix}";
67 RuntimeDirectory = "dhcpd${postfix}";
68 PIDFile = "/run/dhcpd${postfix}/dhcpd.pid";
73 machineOpts = { ... }: {
80 description = lib.mdDoc ''
81 Hostname which is assigned statically to the machine.
85 ethernetAddress = mkOption {
87 example = "00:16:76:9a:32:1d";
88 description = lib.mdDoc ''
89 MAC address of the machine.
93 ipAddress = mkOption {
95 example = "192.168.1.10";
96 description = lib.mdDoc ''
97 IP address of the machine.
104 dhcpConfig = postfix: {
109 description = lib.mdDoc ''
110 Whether to enable the DHCPv${postfix} server.
114 extraConfig = mkOption {
118 option subnet-mask 255.255.255.0;
119 option broadcast-address 192.168.1.255;
120 option routers 192.168.1.5;
121 option domain-name-servers 130.161.158.4, 130.161.33.17, 130.161.180.1;
122 option domain-name "example.org";
123 subnet 192.168.1.0 netmask 255.255.255.0 {
124 range 192.168.1.100 192.168.1.200;
127 description = lib.mdDoc ''
128 Extra text to be appended to the DHCP server configuration
129 file. Currently, you almost certainly need to specify something
130 there, such as the options specifying the subnet mask, DNS servers,
135 extraFlags = mkOption {
136 type = types.listOf types.str;
138 description = lib.mdDoc ''
139 Additional command line flags to be passed to the dhcpd daemon.
143 configFile = mkOption {
144 type = types.nullOr types.path;
146 description = lib.mdDoc ''
147 The path of the DHCP server configuration file. If no file
148 is specified, a file is generated using the other options.
152 interfaces = mkOption {
153 type = types.listOf types.str;
155 description = lib.mdDoc ''
156 The interfaces on which the DHCP server should listen.
160 machines = mkOption {
161 type = with types; listOf (submodule machineOpts);
165 ethernetAddress = "00:16:76:9a:32:1d";
166 ipAddress = "192.168.1.10";
169 ethernetAddress = "00:19:d1:1d:c4:9a";
170 ipAddress = "192.168.1.11";
173 description = lib.mdDoc ''
174 A list mapping Ethernet addresses to IPv${postfix} addresses for the
179 authoritative = mkOption {
182 description = lib.mdDoc ''
183 Whether the DHCP server shall send DHCPNAK messages to misconfigured
184 clients. If this is not done, clients may be unable to get a correct
185 IP address after changing subnets until their old lease has expired.
196 (mkRenamedOptionModule [ "services" "dhcpd" ] [ "services" "dhcpd4" ])
197 ] ++ flip map [ "4" "6" ] (postfix:
198 mkRemovedOptionModule [ "services" "dhcpd${postfix}" "stateDir" ] ''
199 The DHCP server state directory is now managed with the systemd's DynamicUser mechanism.
200 This means the directory is named after the service (dhcpd${postfix}), created under
201 /var/lib/private/ and symlinked to /var/lib/.
209 services.dhcpd4 = dhcpConfig "4";
210 services.dhcpd6 = dhcpConfig "6";
215 ###### implementation
217 config = mkIf (cfg4.enable || cfg6.enable) {
219 systemd.services = dhcpdService "4" cfg4 // dhcpdService "6" cfg6;