1{ config, lib, pkgs, ...}:
2
3with lib;
4
5let
6 cfg = config.services.mosquitto;
7
8 listenerConf = optionalString cfg.ssl.enable ''
9 listener ${toString cfg.ssl.port} ${cfg.ssl.host}
10 cafile ${cfg.ssl.cafile}
11 certfile ${cfg.ssl.certfile}
12 keyfile ${cfg.ssl.keyfile}
13 '';
14
15 passwordConf = optionalString cfg.checkPasswords ''
16 password_file ${cfg.dataDir}/passwd
17 '';
18
19 mosquittoConf = pkgs.writeText "mosquitto.conf" ''
20 pid_file /run/mosquitto/pid
21 acl_file ${aclFile}
22 persistence true
23 allow_anonymous ${boolToString cfg.allowAnonymous}
24 bind_address ${cfg.host}
25 port ${toString cfg.port}
26 ${passwordConf}
27 ${listenerConf}
28 ${cfg.extraConf}
29 '';
30
31 userAcl = (concatStringsSep "\n\n" (mapAttrsToList (n: c:
32 "user ${n}\n" + (concatStringsSep "\n" c.acl)) cfg.users
33 ));
34
35 aclFile = pkgs.writeText "mosquitto.acl" ''
36 ${cfg.aclExtraConf}
37 ${userAcl}
38 '';
39
40in
41
42{
43
44 ###### Interface
45
46 options = {
47 services.mosquitto = {
48 enable = mkEnableOption "Enable the MQTT Mosquitto broker.";
49
50 host = mkOption {
51 default = "127.0.0.1";
52 example = "0.0.0.0";
53 type = types.string;
54 description = ''
55 Host to listen on without SSL.
56 '';
57 };
58
59 port = mkOption {
60 default = 1883;
61 example = 1883;
62 type = types.int;
63 description = ''
64 Port on which to listen without SSL.
65 '';
66 };
67
68 ssl = {
69 enable = mkEnableOption "Enable SSL listener.";
70
71 cafile = mkOption {
72 type = types.nullOr types.path;
73 default = null;
74 description = "Path to PEM encoded CA certificates.";
75 };
76
77 certfile = mkOption {
78 type = types.nullOr types.path;
79 default = null;
80 description = "Path to PEM encoded server certificate.";
81 };
82
83 keyfile = mkOption {
84 type = types.nullOr types.path;
85 default = null;
86 description = "Path to PEM encoded server key.";
87 };
88
89 host = mkOption {
90 default = "0.0.0.0";
91 example = "localhost";
92 type = types.string;
93 description = ''
94 Host to listen on with SSL.
95 '';
96 };
97
98 port = mkOption {
99 default = 8883;
100 example = 8883;
101 type = types.int;
102 description = ''
103 Port on which to listen with SSL.
104 '';
105 };
106 };
107
108 dataDir = mkOption {
109 default = "/var/lib/mosquitto";
110 type = types.path;
111 description = ''
112 The data directory.
113 '';
114 };
115
116 users = mkOption {
117 type = types.attrsOf (types.submodule {
118 options = {
119 password = mkOption {
120 type = with types; uniq (nullOr str);
121 default = null;
122 description = ''
123 Specifies the (clear text) password for the MQTT User.
124 '';
125 };
126
127 hashedPassword = mkOption {
128 type = with types; uniq (nullOr str);
129 default = null;
130 description = ''
131 Specifies the hashed password for the MQTT User.
132 <option>hashedPassword</option> overrides <option>password</option>.
133 To generate hashed password install <literal>mosquitto</literal>
134 package and use <literal>mosquitto_passwd</literal>.
135 '';
136 };
137
138 acl = mkOption {
139 type = types.listOf types.string;
140 example = [ "topic read A/B" "topic A/#" ];
141 description = ''
142 Control client access to topics on the broker.
143 '';
144 };
145 };
146 });
147 example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
148 description = ''
149 A set of users and their passwords and ACLs.
150 '';
151 };
152
153 allowAnonymous = mkOption {
154 default = false;
155 type = types.bool;
156 description = ''
157 Allow clients to connect without authentication.
158 '';
159 };
160
161 checkPasswords = mkOption {
162 default = false;
163 example = true;
164 type = types.bool;
165 description = ''
166 Refuse connection when clients provide incorrect passwords.
167 '';
168 };
169
170 extraConf = mkOption {
171 default = "";
172 type = types.lines;
173 description = ''
174 Extra config to append to `mosquitto.conf` file.
175 '';
176 };
177
178 aclExtraConf = mkOption {
179 default = "";
180 type = types.lines;
181 description = ''
182 Extra config to prepend to the ACL file.
183 '';
184 };
185
186 };
187 };
188
189
190 ###### Implementation
191
192 config = mkIf cfg.enable {
193
194 systemd.services.mosquitto = {
195 description = "Mosquitto MQTT Broker Daemon";
196 wantedBy = [ "multi-user.target" ];
197 after = [ "network.target" ];
198 serviceConfig = {
199 Type = "forking";
200 User = "mosquitto";
201 Group = "mosquitto";
202 RuntimeDirectory = "mosquitto";
203 WorkingDirectory = cfg.dataDir;
204 Restart = "on-failure";
205 ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${mosquittoConf} -d";
206 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
207 PIDFile = "/run/mosquitto/pid";
208 };
209 preStart = ''
210 rm -f ${cfg.dataDir}/passwd
211 touch ${cfg.dataDir}/passwd
212 '' + concatStringsSep "\n" (
213 mapAttrsToList (n: c:
214 if c.hashedPassword != null then
215 "echo '${n}:${c.hashedPassword}' >> ${cfg.dataDir}/passwd"
216 else optionalString (c.password != null)
217 "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} ${c.password}"
218 ) cfg.users);
219 };
220
221 users.extraUsers.mosquitto = {
222 description = "Mosquitto MQTT Broker Daemon owner";
223 group = "mosquitto";
224 uid = config.ids.uids.mosquitto;
225 home = cfg.dataDir;
226 createHome = true;
227 };
228
229 users.extraGroups.mosquitto.gid = config.ids.gids.mosquitto;
230
231 };
232}