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