1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.dhcpd;
8
9 stateDir = "/var/lib/dhcp"; # Don't use /var/state/dhcp; not FHS-compliant.
10
11 configFile = if cfg.configFile != null then cfg.configFile else pkgs.writeText "dhcpd.conf"
12 ''
13 default-lease-time 600;
14 max-lease-time 7200;
15 authoritative;
16 ddns-update-style interim;
17 log-facility local1; # see dhcpd.nix
18
19 ${cfg.extraConfig}
20
21 ${lib.concatMapStrings
22 (machine: ''
23 host ${machine.hostName} {
24 hardware ethernet ${machine.ethernetAddress};
25 fixed-address ${machine.ipAddress};
26 }
27 '')
28 cfg.machines
29 }
30 '';
31
32in
33
34{
35
36 ###### interface
37
38 options = {
39
40 services.dhcpd = {
41
42 enable = mkOption {
43 default = false;
44 description = "
45 Whether to enable the DHCP server.
46 ";
47 };
48
49 extraConfig = mkOption {
50 default = "";
51 example = ''
52 option subnet-mask 255.255.255.0;
53 option broadcast-address 192.168.1.255;
54 option routers 192.168.1.5;
55 option domain-name-servers 130.161.158.4, 130.161.33.17, 130.161.180.1;
56 option domain-name "example.org";
57 subnet 192.168.1.0 netmask 255.255.255.0 {
58 range 192.168.1.100 192.168.1.200;
59 }
60 '';
61 description = "
62 Extra text to be appended to the DHCP server configuration
63 file. Currently, you almost certainly need to specify
64 something here, such as the options specifying the subnet
65 mask, DNS servers, etc.
66 ";
67 };
68
69 configFile = mkOption {
70 default = null;
71 description = "
72 The path of the DHCP server configuration file. If no file
73 is specified, a file is generated using the other options.
74 ";
75 };
76
77 interfaces = mkOption {
78 default = ["eth0"];
79 description = "
80 The interfaces on which the DHCP server should listen.
81 ";
82 };
83
84 machines = mkOption {
85 default = [];
86 example = [
87 { hostName = "foo";
88 ethernetAddress = "00:16:76:9a:32:1d";
89 ipAddress = "192.168.1.10";
90 }
91 { hostName = "bar";
92 ethernetAddress = "00:19:d1:1d:c4:9a";
93 ipAddress = "192.168.1.11";
94 }
95 ];
96 description = "
97 A list mapping ethernet addresses to IP addresses for the
98 DHCP server.
99 ";
100 };
101
102 };
103
104 };
105
106
107 ###### implementation
108
109 config = mkIf config.services.dhcpd.enable {
110
111 users = {
112 extraUsers.dhcpd = {
113 uid = config.ids.uids.dhcpd;
114 description = "DHCP daemon user";
115 };
116 };
117
118 systemd.services.dhcpd =
119 { description = "DHCP server";
120
121 wantedBy = [ "multi-user.target" ];
122
123 after = [ "network.target" ];
124
125 path = [ pkgs.dhcp ];
126
127 preStart =
128 ''
129 mkdir -m 755 -p ${stateDir}
130
131 touch ${stateDir}/dhcpd.leases
132
133 mkdir -m 755 -p /run/dhcpd
134 chown dhcpd /run/dhcpd
135 '';
136
137 serviceConfig =
138 { ExecStart = "@${pkgs.dhcp}/sbin/dhcpd dhcpd"
139 + " -pf /run/dhcpd/dhcpd.pid -cf ${configFile}"
140 + " -lf ${stateDir}/dhcpd.leases -user dhcpd -group nogroup"
141 + " ${toString cfg.interfaces}";
142 Restart = "always";
143 Type = "forking";
144 PIDFile = "/run/dhcpd/dhcpd.pid";
145 };
146 };
147
148 };
149
150}