1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.dnsmasq;
7 dnsmasq = pkgs.dnsmasq;
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 = lib.mdDoc ''
52 Whether to run dnsmasq.
53 '';
54 };
55
56 resolveLocalQueries = mkOption {
57 type = types.bool;
58 default = true;
59 description = lib.mdDoc ''
60 Whether dnsmasq should resolve local queries (i.e. add 127.0.0.1 to
61 /etc/resolv.conf).
62 '';
63 };
64
65 alwaysKeepRunning = mkOption {
66 type = types.bool;
67 default = false;
68 description = lib.mdDoc ''
69 If enabled, systemd will always respawn dnsmasq even if shut down manually. The default, disabled, will only restart it on error.
70 '';
71 };
72
73 settings = mkOption {
74 type = types.submodule {
75
76 freeformType = settingsFormat.type;
77
78 options.server = mkOption {
79 type = types.listOf types.str;
80 default = [ ];
81 example = [ "8.8.8.8" "8.8.4.4" ];
82 description = lib.mdDoc ''
83 The DNS servers which dnsmasq should query.
84 '';
85 };
86
87 };
88 default = { };
89 description = lib.mdDoc ''
90 Configuration of dnsmasq. Lists get added one value per line (empty
91 lists and false values don't get added, though false values get
92 turned to comments). Gets merged with
93
94 {
95 dhcp-leasefile = "${stateDir}/dnsmasq.leases";
96 conf-file = optional cfg.resolveLocalQueries "/etc/dnsmasq-conf.conf";
97 resolv-file = optional cfg.resolveLocalQueries "/etc/dnsmasq-resolv.conf";
98 }
99 '';
100 example = literalExpression ''
101 {
102 domain-needed = true;
103 dhcp-range = [ "192.168.0.2,192.168.0.254" ];
104 }
105 '';
106 };
107
108 extraConfig = mkOption {
109 type = types.lines;
110 default = "";
111 description = lib.mdDoc ''
112 Extra configuration directives that should be added to
113 `dnsmasq.conf`.
114
115 This option is deprecated, please use {option}`settings` instead.
116 '';
117 };
118
119 };
120
121 };
122
123
124 ###### implementation
125
126 config = mkIf cfg.enable {
127
128 warnings = lib.optional (cfg.extraConfig != "") "Text based config is deprecated, dnsmasq now supports `services.dnsmasq.settings` for an attribute-set based config";
129
130 services.dnsmasq.settings = {
131 dhcp-leasefile = mkDefault "${stateDir}/dnsmasq.leases";
132 conf-file = mkDefault (optional cfg.resolveLocalQueries "/etc/dnsmasq-conf.conf");
133 resolv-file = mkDefault (optional cfg.resolveLocalQueries "/etc/dnsmasq-resolv.conf");
134 };
135
136 networking.nameservers =
137 optional cfg.resolveLocalQueries "127.0.0.1";
138
139 services.dbus.packages = [ dnsmasq ];
140
141 users.users.dnsmasq = {
142 isSystemUser = true;
143 group = "dnsmasq";
144 description = "Dnsmasq daemon user";
145 };
146 users.groups.dnsmasq = {};
147
148 networking.resolvconf = mkIf cfg.resolveLocalQueries {
149 useLocalResolver = mkDefault true;
150
151 extraConfig = ''
152 dnsmasq_conf=/etc/dnsmasq-conf.conf
153 dnsmasq_resolv=/etc/dnsmasq-resolv.conf
154 '';
155 };
156
157 systemd.services.dnsmasq = {
158 description = "Dnsmasq Daemon";
159 after = [ "network.target" "systemd-resolved.service" ];
160 wantedBy = [ "multi-user.target" ];
161 path = [ dnsmasq ];
162 preStart = ''
163 mkdir -m 755 -p ${stateDir}
164 touch ${stateDir}/dnsmasq.leases
165 chown -R dnsmasq ${stateDir}
166 touch /etc/dnsmasq-{conf,resolv}.conf
167 dnsmasq --test
168 '';
169 serviceConfig = {
170 Type = "dbus";
171 BusName = "uk.org.thekelleys.dnsmasq";
172 ExecStart = "${dnsmasq}/bin/dnsmasq -k --enable-dbus --user=dnsmasq -C ${dnsmasqConf}";
173 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
174 PrivateTmp = true;
175 ProtectSystem = true;
176 ProtectHome = true;
177 Restart = if cfg.alwaysKeepRunning then "always" else "on-failure";
178 };
179 restartTriggers = [ config.environment.etc.hosts.source ];
180 };
181 };
182}