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 };
119
120 };
121
122 ###### implementation
123
124 config = lib.mkIf cfg.enable {
125
126 services.dnsmasq.settings = {
127 dhcp-leasefile = lib.mkDefault "${stateDir}/dnsmasq.leases";
128 conf-file = lib.mkDefault (lib.optional cfg.resolveLocalQueries "/etc/dnsmasq-conf.conf");
129 resolv-file = lib.mkDefault (lib.optional cfg.resolveLocalQueries "/etc/dnsmasq-resolv.conf");
130 };
131
132 networking.nameservers = lib.optional cfg.resolveLocalQueries "127.0.0.1";
133
134 services.dbus.packages = [ dnsmasq ];
135
136 users.users.dnsmasq = {
137 isSystemUser = true;
138 group = "dnsmasq";
139 description = "Dnsmasq daemon user";
140 };
141 users.groups.dnsmasq = { };
142
143 networking.resolvconf = lib.mkIf cfg.resolveLocalQueries {
144 useLocalResolver = lib.mkDefault true;
145
146 extraConfig = ''
147 dnsmasq_conf=/etc/dnsmasq-conf.conf
148 dnsmasq_resolv=/etc/dnsmasq-resolv.conf
149 '';
150
151 subscriberFiles = [
152 "/etc/dnsmasq-conf.conf"
153 "/etc/dnsmasq-resolv.conf"
154 ];
155 };
156
157 systemd.services.dnsmasq = {
158 description = "Dnsmasq Daemon";
159 after = [
160 "network.target"
161 "systemd-resolved.service"
162 ];
163 wantedBy = [ "multi-user.target" ];
164 path = [ dnsmasq ];
165 preStart = ''
166 mkdir -m 755 -p ${stateDir}
167 touch ${stateDir}/dnsmasq.leases
168 chown -R dnsmasq ${stateDir}
169 ${lib.optionalString cfg.resolveLocalQueries "touch /etc/dnsmasq-{conf,resolv}.conf"}
170 dnsmasq --test
171 '';
172 serviceConfig = {
173 Type = "dbus";
174 BusName = "uk.org.thekelleys.dnsmasq";
175 ExecStart = "${dnsmasq}/bin/dnsmasq -k --enable-dbus --user=dnsmasq -C ${dnsmasqConf}";
176 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
177 PrivateTmp = true;
178 ProtectSystem = true;
179 ProtectHome = true;
180 Restart = if cfg.alwaysKeepRunning then "always" else "on-failure";
181 };
182 restartTriggers = [ config.environment.etc.hosts.source ];
183 };
184 };
185
186 meta.doc = ./dnsmasq.md;
187}