1{
2 config,
3 lib,
4 pkgs,
5 ...
6}:
7let
8 cfg = config.services.dnsmasq;
9 dnsmasq = cfg.package;
10 stateDir = "/var/lib/dnsmasq";
11
12 # True values are just put as `name` instead of `name=true`, and false values
13 # are turned to comments (false values are expected to be overrides e.g.
14 # lib.mkForce)
15 formatKeyValue =
16 name: value:
17 if value == true then
18 name
19 else if value == false then
20 "# setting `${name}` explicitly set to false"
21 else
22 lib.generators.mkKeyValueDefault { } "=" name value;
23
24 settingsFormat = pkgs.formats.keyValue {
25 mkKeyValue = formatKeyValue;
26 listsAsDuplicateKeys = true;
27 };
28
29 dnsmasqConf = settingsFormat.generate "dnsmasq.conf" cfg.settings;
30
31in
32
33{
34
35 imports = [
36 (lib.mkRenamedOptionModule
37 [ "services" "dnsmasq" "servers" ]
38 [ "services" "dnsmasq" "settings" "server" ]
39 )
40 (lib.mkRemovedOptionModule [
41 "services"
42 "dnsmasq"
43 "extraConfig"
44 ] "This option has been replaced by `services.dnsmasq.settings`")
45 ];
46
47 ###### interface
48
49 options = {
50
51 services.dnsmasq = {
52
53 enable = lib.mkOption {
54 type = lib.types.bool;
55 default = false;
56 description = ''
57 Whether to run dnsmasq.
58 '';
59 };
60
61 package = lib.mkPackageOption pkgs "dnsmasq" { };
62
63 resolveLocalQueries = lib.mkOption {
64 type = lib.types.bool;
65 default = true;
66 description = ''
67 Whether dnsmasq should resolve local queries (i.e. add 127.0.0.1 to
68 /etc/resolv.conf).
69 '';
70 };
71
72 alwaysKeepRunning = lib.mkOption {
73 type = lib.types.bool;
74 default = false;
75 description = ''
76 If enabled, systemd will always respawn dnsmasq even if shut down manually. The default, disabled, will only restart it on error.
77 '';
78 };
79
80 settings = lib.mkOption {
81 type = lib.types.submodule {
82
83 freeformType = settingsFormat.type;
84
85 options.server = lib.mkOption {
86 type = lib.types.listOf lib.types.str;
87 default = [ ];
88 example = [
89 "8.8.8.8"
90 "8.8.4.4"
91 ];
92 description = ''
93 The DNS servers which dnsmasq should query.
94 '';
95 };
96
97 };
98 default = { };
99 description = ''
100 Configuration of dnsmasq. Lists get added one value per line (empty
101 lists and false values don't get added, though false values get
102 turned to comments). Gets merged with
103
104 {
105 dhcp-leasefile = "${stateDir}/dnsmasq.leases";
106 conf-file = optional cfg.resolveLocalQueries "/etc/dnsmasq-conf.conf";
107 resolv-file = optional cfg.resolveLocalQueries "/etc/dnsmasq-resolv.conf";
108 }
109 '';
110 example = lib.literalExpression ''
111 {
112 domain-needed = true;
113 dhcp-range = [ "192.168.0.2,192.168.0.254" ];
114 }
115 '';
116 };
117
118 configFile = lib.mkOption {
119 type = lib.types.package;
120 readOnly = true;
121 description = ''
122 Path to the configuration file of dnsmasq.
123 '';
124 };
125
126 };
127
128 };
129
130 ###### implementation
131
132 config = lib.mkIf cfg.enable {
133
134 services.dnsmasq = {
135 settings = {
136 dhcp-leasefile = lib.mkDefault "${stateDir}/dnsmasq.leases";
137 conf-file = lib.mkDefault (lib.optional cfg.resolveLocalQueries "/etc/dnsmasq-conf.conf");
138 resolv-file = lib.mkDefault (lib.optional cfg.resolveLocalQueries "/etc/dnsmasq-resolv.conf");
139 };
140
141 configFile = dnsmasqConf;
142 };
143
144 networking.nameservers = lib.optional cfg.resolveLocalQueries "127.0.0.1";
145
146 services.dbus.packages = [ dnsmasq ];
147
148 users.users.dnsmasq = {
149 isSystemUser = true;
150 group = "dnsmasq";
151 description = "Dnsmasq daemon user";
152 };
153 users.groups.dnsmasq = { };
154
155 networking.resolvconf = lib.mkIf cfg.resolveLocalQueries {
156 useLocalResolver = lib.mkDefault true;
157
158 extraConfig = ''
159 dnsmasq_conf=/etc/dnsmasq-conf.conf
160 dnsmasq_resolv=/etc/dnsmasq-resolv.conf
161 '';
162
163 subscriberFiles = [
164 "/etc/dnsmasq-conf.conf"
165 "/etc/dnsmasq-resolv.conf"
166 ];
167 };
168
169 systemd.services.dnsmasq = {
170 description = "Dnsmasq Daemon";
171 after = [
172 "network.target"
173 "systemd-resolved.service"
174 ];
175 wantedBy = [ "multi-user.target" ];
176 path = [ dnsmasq ];
177 preStart = ''
178 mkdir -m 755 -p ${stateDir}
179 touch ${stateDir}/dnsmasq.leases
180 chown -R dnsmasq ${stateDir}
181 ${lib.optionalString cfg.resolveLocalQueries "touch /etc/dnsmasq-{conf,resolv}.conf"}
182 dnsmasq --test -C ${cfg.configFile}
183 '';
184 serviceConfig = {
185 Type = "dbus";
186 BusName = "uk.org.thekelleys.dnsmasq";
187 ExecStart = "${dnsmasq}/bin/dnsmasq -k --enable-dbus --user=dnsmasq -C ${cfg.configFile}";
188 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
189 PrivateTmp = true;
190 ProtectSystem = true;
191 ProtectHome = true;
192 Restart = if cfg.alwaysKeepRunning then "always" else "on-failure";
193 };
194 restartTriggers = [ config.environment.etc.hosts.source ];
195 };
196 };
197
198 meta.doc = ./dnsmasq.md;
199}