1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.avahi;
7
8 yesNo = yes : if yes then "yes" else "no";
9
10 avahiDaemonConf = with cfg; pkgs.writeText "avahi-daemon.conf" ''
11 [server]
12 ${# Users can set `networking.hostName' to the empty string, when getting
13 # a host name from DHCP. In that case, let Avahi take whatever the
14 # current host name is; setting `host-name' to the empty string in
15 # `avahi-daemon.conf' would be invalid.
16 optionalString (hostName != "") "host-name=${hostName}"}
17 browse-domains=${concatStringsSep ", " browseDomains}
18 use-ipv4=${yesNo ipv4}
19 use-ipv6=${yesNo ipv6}
20 ${optionalString (interfaces!=null) "allow-interfaces=${concatStringsSep "," interfaces}"}
21 ${optionalString (domainName!=null) "domain-name=${domainName}"}
22 allow-point-to-point=${yesNo allowPointToPoint}
23 ${optionalString (cacheEntriesMax!=null) "cache-entries-max=${toString cacheEntriesMax}"}
24
25 [wide-area]
26 enable-wide-area=${yesNo wideArea}
27
28 [publish]
29 disable-publishing=${yesNo (!publish.enable)}
30 disable-user-service-publishing=${yesNo (!publish.userServices)}
31 publish-addresses=${yesNo (publish.userServices || publish.addresses)}
32 publish-hinfo=${yesNo publish.hinfo}
33 publish-workstation=${yesNo publish.workstation}
34 publish-domain=${yesNo publish.domain}
35
36 [reflector]
37 enable-reflector=${yesNo reflector}
38 ${extraConfig}
39 '';
40in
41{
42 options.services.avahi = {
43 enable = mkOption {
44 type = types.bool;
45 default = false;
46 description = ''
47 Whether to run the Avahi daemon, which allows Avahi clients
48 to use Avahi's service discovery facilities and also allows
49 the local machine to advertise its presence and services
50 (through the mDNS responder implemented by `avahi-daemon').
51 '';
52 };
53
54 hostName = mkOption {
55 type = types.str;
56 default = config.networking.hostName;
57 defaultText = literalExample "config.networking.hostName";
58 description = ''
59 Host name advertised on the LAN. If not set, avahi will use the value
60 of <option>config.networking.hostName</option>.
61 '';
62 };
63
64 domainName = mkOption {
65 type = types.str;
66 default = "local";
67 description = ''
68 Domain name for all advertisements.
69 '';
70 };
71
72 browseDomains = mkOption {
73 type = types.listOf types.str;
74 default = [ ];
75 example = [ "0pointer.de" "zeroconf.org" ];
76 description = ''
77 List of non-local DNS domains to be browsed.
78 '';
79 };
80
81 ipv4 = mkOption {
82 type = types.bool;
83 default = true;
84 description = "Whether to use IPv4.";
85 };
86
87 ipv6 = mkOption {
88 type = types.bool;
89 default = config.networking.enableIPv6;
90 defaultText = "config.networking.enableIPv6";
91 description = "Whether to use IPv6.";
92 };
93
94 interfaces = mkOption {
95 type = types.nullOr (types.listOf types.str);
96 default = null;
97 description = ''
98 List of network interfaces that should be used by the <command>avahi-daemon</command>.
99 Other interfaces will be ignored. If <literal>null</literal>, all local interfaces
100 except loopback and point-to-point will be used.
101 '';
102 };
103
104 openFirewall = mkOption {
105 type = types.bool;
106 default = true;
107 description = ''
108 Whether to open the firewall for UDP port 5353.
109 '';
110 };
111
112 allowPointToPoint = mkOption {
113 type = types.bool;
114 default = false;
115 description= ''
116 Whether to use POINTTOPOINT interfaces. Might make mDNS unreliable due to usually large
117 latencies with such links and opens a potential security hole by allowing mDNS access from Internet
118 connections.
119 '';
120 };
121
122 wideArea = mkOption {
123 type = types.bool;
124 default = true;
125 description = "Whether to enable wide-area service discovery.";
126 };
127
128 reflector = mkOption {
129 type = types.bool;
130 default = false;
131 description = "Reflect incoming mDNS requests to all allowed network interfaces.";
132 };
133
134 extraServiceFiles = mkOption {
135 type = with types; attrsOf (either str path);
136 default = {};
137 example = literalExample ''
138 {
139 ssh = "''${pkgs.avahi}/etc/avahi/services/ssh.service";
140 smb = '''
141 <?xml version="1.0" standalone='no'?><!--*-nxml-*-->
142 <!DOCTYPE service-group SYSTEM "avahi-service.dtd">
143 <service-group>
144 <name replace-wildcards="yes">%h</name>
145 <service>
146 <type>_smb._tcp</type>
147 <port>445</port>
148 </service>
149 </service-group>
150 ''';
151 }
152 '';
153 description = ''
154 Specify custom service definitions which are placed in the avahi service directory.
155 See the <citerefentry><refentrytitle>avahi.service</refentrytitle>
156 <manvolnum>5</manvolnum></citerefentry> manpage for detailed information.
157 '';
158 };
159
160 publish = {
161 enable = mkOption {
162 type = types.bool;
163 default = false;
164 description = "Whether to allow publishing in general.";
165 };
166
167 userServices = mkOption {
168 type = types.bool;
169 default = false;
170 description = "Whether to publish user services. Will set <literal>addresses=true</literal>.";
171 };
172
173 addresses = mkOption {
174 type = types.bool;
175 default = false;
176 description = "Whether to register mDNS address records for all local IP addresses.";
177 };
178
179 hinfo = mkOption {
180 type = types.bool;
181 default = false;
182 description = ''
183 Whether to register a mDNS HINFO record which contains information about the
184 local operating system and CPU.
185 '';
186 };
187
188 workstation = mkOption {
189 type = types.bool;
190 default = false;
191 description = ''
192 Whether to register a service of type "_workstation._tcp" on the local LAN.
193 '';
194 };
195
196 domain = mkOption {
197 type = types.bool;
198 default = false;
199 description = "Whether to announce the locally used domain name for browsing by other hosts.";
200 };
201 };
202
203 nssmdns = mkOption {
204 type = types.bool;
205 default = false;
206 description = ''
207 Whether to enable the mDNS NSS (Name Service Switch) plug-in.
208 Enabling it allows applications to resolve names in the `.local'
209 domain by transparently querying the Avahi daemon.
210 '';
211 };
212
213 cacheEntriesMax = mkOption {
214 type = types.nullOr types.int;
215 default = null;
216 description = ''
217 Number of resource records to be cached per interface. Use 0 to
218 disable caching. Avahi daemon defaults to 4096 if not set.
219 '';
220 };
221
222 extraConfig = mkOption {
223 type = types.lines;
224 default = "";
225 description = ''
226 Extra config to append to avahi-daemon.conf.
227 '';
228 };
229 };
230
231 config = mkIf cfg.enable {
232 users.users.avahi = {
233 description = "avahi-daemon privilege separation user";
234 home = "/var/empty";
235 group = "avahi";
236 isSystemUser = true;
237 };
238
239 users.groups.avahi = {};
240
241 system.nssModules = optional cfg.nssmdns pkgs.nssmdns;
242 system.nssDatabases.hosts = optionals cfg.nssmdns (mkMerge [
243 (mkOrder 900 [ "mdns_minimal [NOTFOUND=return]" ]) # must be before resolve
244 (mkOrder 1501 [ "mdns" ]) # 1501 to ensure it's after dns
245 ]);
246
247 environment.systemPackages = [ pkgs.avahi ];
248
249 environment.etc = (mapAttrs' (n: v: nameValuePair
250 "avahi/services/${n}.service"
251 { ${if types.path.check v then "source" else "text"} = v; }
252 ) cfg.extraServiceFiles);
253
254 systemd.sockets.avahi-daemon = {
255 description = "Avahi mDNS/DNS-SD Stack Activation Socket";
256 listenStreams = [ "/run/avahi-daemon/socket" ];
257 wantedBy = [ "sockets.target" ];
258 };
259
260 systemd.tmpfiles.rules = [ "d /run/avahi-daemon - avahi avahi -" ];
261
262 systemd.services.avahi-daemon = {
263 description = "Avahi mDNS/DNS-SD Stack";
264 wantedBy = [ "multi-user.target" ];
265 requires = [ "avahi-daemon.socket" ];
266
267 # Make NSS modules visible so that `avahi_nss_support ()' can
268 # return a sensible value.
269 environment.LD_LIBRARY_PATH = config.system.nssModules.path;
270
271 path = [ pkgs.coreutils pkgs.avahi ];
272
273 serviceConfig = {
274 NotifyAccess = "main";
275 BusName = "org.freedesktop.Avahi";
276 Type = "dbus";
277 ExecStart = "${pkgs.avahi}/sbin/avahi-daemon --syslog -f ${avahiDaemonConf}";
278 };
279 };
280
281 services.dbus.enable = true;
282 services.dbus.packages = [ pkgs.avahi ];
283
284 networking.firewall.allowedUDPPorts = mkIf cfg.openFirewall [ 5353 ];
285 };
286}