···1152 <literal>coursier</literal>, you can create a shell alias.
1153 </para>
1154 </listitem>
000000001155 </itemizedlist>
1156 </section>
1157 <section xml:id="sec-release-21.11-notable-changes">
···1152 <literal>coursier</literal>, you can create a shell alias.
1153 </para>
1154 </listitem>
1155+ <listitem>
1156+ <para>
1157+ The <literal>services.mosquitto</literal> module has been
1158+ rewritten to support multiple listeners and per-listener
1159+ configuration. Module configurations from previous releases
1160+ will no longer work and must be updated.
1161+ </para>
1162+ </listitem>
1163 </itemizedlist>
1164 </section>
1165 <section xml:id="sec-release-21.11-notable-changes">
+3
nixos/doc/manual/release-notes/rl-2111.section.md
···355356- The `coursier` package's binary was renamed from `coursier` to `cs`. Completions which haven't worked for a while should now work with the renamed binary. To keep using `coursier`, you can create a shell alias.
357000358## Other Notable Changes {#sec-release-21.11-notable-changes}
359360
···355356- The `coursier` package's binary was renamed from `coursier` to `cs`. Completions which haven't worked for a while should now work with the renamed binary. To keep using `coursier`, you can create a shell alias.
357358+- The `services.mosquitto` module has been rewritten to support multiple listeners and per-listener configuration.
359+ Module configurations from previous releases will no longer work and must be updated.
360+361## Other Notable Changes {#sec-release-21.11-notable-changes}
362363
+35-8
nixos/modules/services/backup/borgbackup.nix
···42 ${cfg.postInit}
43 fi
44 '' + ''
45- borg create $extraArgs \
46- --compression ${cfg.compression} \
47- --exclude-from ${mkExcludeFile cfg} \
48- $extraCreateArgs \
49- "::$archiveName$archiveSuffix" \
50- ${escapeShellArgs cfg.paths}
000051 '' + optionalString cfg.appendFailedSuffix ''
52 borg rename $extraArgs \
53 "::$archiveName$archiveSuffix" "$archiveName"
···182 + " without at least one public key";
183 };
18400000000185 mkRemovableDeviceAssertions = name: cfg: {
186 assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice;
187 message = ''
···240 options = {
241242 paths = mkOption {
243- type = with types; coercedTo str lib.singleton (listOf str);
244- description = "Path(s) to back up.";
0000245 example = "/home/user";
0000000000246 };
247248 repo = mkOption {
···657 assertions =
658 mapAttrsToList mkPassAssertion jobs
659 ++ mapAttrsToList mkKeysAssertion repos
0660 ++ mapAttrsToList mkRemovableDeviceAssertions jobs;
661662 system.activationScripts = mapAttrs' mkActivationScript jobs;
···42 ${cfg.postInit}
43 fi
44 '' + ''
45+ (
46+ set -o pipefail
47+ ${optionalString (cfg.dumpCommand != null) ''${escapeShellArg cfg.dumpCommand} | \''}
48+ borg create $extraArgs \
49+ --compression ${cfg.compression} \
50+ --exclude-from ${mkExcludeFile cfg} \
51+ $extraCreateArgs \
52+ "::$archiveName$archiveSuffix" \
53+ ${if cfg.paths == null then "-" else escapeShellArgs cfg.paths}
54+ )
55 '' + optionalString cfg.appendFailedSuffix ''
56 borg rename $extraArgs \
57 "::$archiveName$archiveSuffix" "$archiveName"
···186 + " without at least one public key";
187 };
188189+ mkSourceAssertions = name: cfg: {
190+ assertion = count isNull [ cfg.dumpCommand cfg.paths ] == 1;
191+ message = ''
192+ Exactly one of borgbackup.jobs.${name}.paths or borgbackup.jobs.${name}.dumpCommand
193+ must be set.
194+ '';
195+ };
196+197 mkRemovableDeviceAssertions = name: cfg: {
198 assertion = !(isLocalPath cfg.repo) -> !cfg.removableDevice;
199 message = ''
···252 options = {
253254 paths = mkOption {
255+ type = with types; nullOr (coercedTo str lib.singleton (listOf str));
256+ default = null;
257+ description = ''
258+ Path(s) to back up.
259+ Mutually exclusive with <option>dumpCommand</option>.
260+ '';
261 example = "/home/user";
262+ };
263+264+ dumpCommand = mkOption {
265+ type = with types; nullOr path;
266+ default = null;
267+ description = ''
268+ Backup the stdout of this program instead of filesystem paths.
269+ Mutually exclusive with <option>paths</option>.
270+ '';
271+ example = "/path/to/createZFSsend.sh";
272 };
273274 repo = mkOption {
···683 assertions =
684 mapAttrsToList mkPassAssertion jobs
685 ++ mapAttrsToList mkKeysAssertion repos
686+ ++ mapAttrsToList mkSourceAssertions jobs
687 ++ mapAttrsToList mkRemovableDeviceAssertions jobs;
688689 system.activationScripts = mapAttrs' mkActivationScript jobs;
+511-167
nixos/modules/services/networking/mosquitto.nix
···5let
6 cfg = config.services.mosquitto;
78- 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- '';
0000000001415- passwordConf = optionalString cfg.checkPasswords ''
16- password_file ${cfg.dataDir}/passwd
17- '';
00001819- mosquittoConf = pkgs.writeText "mosquitto.conf" ''
20- acl_file ${aclFile}
21- persistence true
22- allow_anonymous ${boolToString cfg.allowAnonymous}
23- listener ${toString cfg.port} ${cfg.host}
24- ${passwordConf}
25- ${listenerConf}
26- ${cfg.extraConf}
27- '';
2829- userAcl = (concatStringsSep "\n\n" (mapAttrsToList (n: c:
30- "user ${n}\n" + (concatStringsSep "\n" c.acl)) cfg.users
31- ));
0000003233- aclFile = pkgs.writeText "mosquitto.acl" ''
34- ${cfg.aclExtraConf}
35- ${userAcl}
36- '';
000003738-in
000000003940-{
00000000004142- ###### Interface
000000000000000000000000000000000000000000004344- options = {
45- services.mosquitto = {
46- enable = mkEnableOption "the MQTT Mosquitto broker";
4748- host = mkOption {
49- default = "127.0.0.1";
50- example = "0.0.0.0";
51- type = types.str;
0000000000000000000000000000052 description = ''
53- Host to listen on without SSL.
54 '';
55 };
5657- port = mkOption {
58- default = 1883;
59- type = types.int;
60 description = ''
61- Port on which to listen without SSL.
062 '';
063 };
6465- ssl = {
66- enable = mkEnableOption "SSL listener";
000000006768- cafile = mkOption {
69- type = types.nullOr types.path;
70- default = null;
71- description = "Path to PEM encoded CA certificates.";
72- };
007374- certfile = mkOption {
75- type = types.nullOr types.path;
76- default = null;
77- description = "Path to PEM encoded server certificate.";
78- };
07980- keyfile = mkOption {
81- type = types.nullOr types.path;
82- default = null;
83- description = "Path to PEM encoded server key.";
84- };
00000000000000000000000008586- host = mkOption {
87- default = "0.0.0.0";
88- example = "localhost";
89- type = types.str;
90- description = ''
91- Host to listen on with SSL.
92- '';
93- };
09495- port = mkOption {
96- default = 8883;
97- type = types.int;
98- description = ''
99- Port on which to listen with SSL.
100- '';
101- };
102 };
103104- dataDir = mkOption {
105- default = "/var/lib/mosquitto";
106- type = types.path;
107 description = ''
108- The data directory.
00109 '';
0110 };
111112 users = mkOption {
113- type = types.attrsOf (types.submodule {
114- options = {
115- password = mkOption {
116- type = with types; uniq (nullOr str);
117- default = null;
118- description = ''
119- Specifies the (clear text) password for the MQTT User.
120- '';
121- };
122123- passwordFile = mkOption {
124- type = with types; uniq (nullOr str);
125- example = "/path/to/file";
126- default = null;
127- description = ''
128- Specifies the path to a file containing the
129- clear text password for the MQTT user.
130- '';
131- };
132133- hashedPassword = mkOption {
134- type = with types; uniq (nullOr str);
135- default = null;
136- description = ''
137- Specifies the hashed password for the MQTT User.
138- To generate hashed password install <literal>mosquitto</literal>
139- package and use <literal>mosquitto_passwd</literal>.
140- '';
141- };
00142143- hashedPasswordFile = mkOption {
144- type = with types; uniq (nullOr str);
145- example = "/path/to/file";
146- default = null;
0000000000000000000000000000000000000000000000000000000147 description = ''
148- Specifies the path to a file containing the
149- hashed password for the MQTT user.
150- To generate hashed password install <literal>mosquitto</literal>
151- package and use <literal>mosquitto_passwd</literal>.
152 '';
153 };
154155- acl = mkOption {
156- type = types.listOf types.str;
157- example = [ "topic read A/B" "topic A/#" ];
158 description = ''
159- Control client access to topics on the broker.
160 '';
0161 };
162 };
163 });
164- example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
165 description = ''
166- A set of users and their passwords and ACLs.
167 '';
168 };
169170- allowAnonymous = mkOption {
171- default = false;
172- type = types.bool;
173 description = ''
174- Allow clients to connect without authentication.
00175 '';
00176 };
177178- checkPasswords = mkOption {
179- default = false;
180- example = true;
181- type = types.bool;
182 description = ''
183- Refuse connection when clients provide incorrect passwords.
184 '';
0185 };
00186187- extraConf = mkOption {
188- default = "";
189- type = types.lines;
190- description = ''
191- Extra config to append to `mosquitto.conf` file.
192- '';
193- };
00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000194195- aclExtraConf = mkOption {
196- default = "";
197- type = types.lines;
198- description = ''
199- Extra config to prepend to the ACL file.
200- '';
201- };
20200000000203 };
204 };
2050000000000000000000000000000000206207 ###### Implementation
208209 config = mkIf cfg.enable {
210211- assertions = mapAttrsToList (name: cfg: {
212- assertion = length (filter (s: s != null) (with cfg; [
213- password passwordFile hashedPassword hashedPasswordFile
214- ])) <= 1;
215- message = "Cannot set more than one password option";
216- }) cfg.users;
217218 systemd.services.mosquitto = {
219 description = "Mosquitto MQTT Broker Daemon";
···227 RuntimeDirectory = "mosquitto";
228 WorkingDirectory = cfg.dataDir;
229 Restart = "on-failure";
230- ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${mosquittoConf}";
231 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
232233 # Hardening
···252 ReadWritePaths = [
253 cfg.dataDir
254 "/tmp" # mosquitto_passwd creates files in /tmp before moving them
255- ];
256- ReadOnlyPaths = with cfg.ssl; lib.optionals (enable) [
257- certfile
258- keyfile
259- cafile
260- ];
0000000000000000000000261 RemoveIPC = true;
262 RestrictAddressFamilies = [
263 "AF_UNIX" # for sd_notify() call
···275 ];
276 UMask = "0077";
277 };
278- preStart = ''
279- rm -f ${cfg.dataDir}/passwd
280- touch ${cfg.dataDir}/passwd
281- '' + concatStringsSep "\n" (
282- mapAttrsToList (n: c:
283- if c.hashedPasswordFile != null then
284- "echo '${n}:'$(cat '${c.hashedPasswordFile}') >> ${cfg.dataDir}/passwd"
285- else if c.passwordFile != null then
286- "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} $(cat '${c.passwordFile}')"
287- else if c.hashedPassword != null then
288- "echo '${n}:${c.hashedPassword}' >> ${cfg.dataDir}/passwd"
289- else optionalString (c.password != null)
290- "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} '${c.password}'"
291- ) cfg.users);
292 };
293294 users.users.mosquitto = {
···302 users.groups.mosquitto.gid = config.ids.gids.mosquitto;
303304 };
00305}
···5let
6 cfg = config.services.mosquitto;
78+ # note that mosquitto config parsing is very simplistic as of may 2021.
9+ # often times they'll e.g. strtok() a line, check the first two tokens, and ignore the rest.
10+ # there's no escaping available either, so we have to prevent any being necessary.
11+ str = types.strMatching "[^\r\n]*" // {
12+ description = "single-line string";
13+ };
14+ path = types.addCheck types.path (p: str.check "${p}");
15+ configKey = types.strMatching "[^\r\n\t ]+";
16+ optionType = with types; oneOf [ str path bool int ] // {
17+ description = "string, path, bool, or integer";
18+ };
19+ optionToString = v:
20+ if isBool v then boolToString v
21+ else if path.check v then "${v}"
22+ else toString v;
2324+ assertKeysValid = prefix: valid: config:
25+ mapAttrsToList
26+ (n: _: {
27+ assertion = valid ? ${n};
28+ message = "Invalid config key ${prefix}.${n}.";
29+ })
30+ config;
3132+ formatFreeform = { prefix ? "" }: mapAttrsToList (n: v: "${prefix}${n} ${optionToString v}");
000000003334+ userOptions = with types; submodule {
35+ options = {
36+ password = mkOption {
37+ type = uniq (nullOr str);
38+ default = null;
39+ description = ''
40+ Specifies the (clear text) password for the MQTT User.
41+ '';
42+ };
4344+ passwordFile = mkOption {
45+ type = uniq (nullOr types.path);
46+ example = "/path/to/file";
47+ default = null;
48+ description = ''
49+ Specifies the path to a file containing the
50+ clear text password for the MQTT user.
51+ '';
52+ };
5354+ hashedPassword = mkOption {
55+ type = uniq (nullOr str);
56+ default = null;
57+ description = ''
58+ Specifies the hashed password for the MQTT User.
59+ To generate hashed password install <literal>mosquitto</literal>
60+ package and use <literal>mosquitto_passwd</literal>.
61+ '';
62+ };
6364+ hashedPasswordFile = mkOption {
65+ type = uniq (nullOr types.path);
66+ example = "/path/to/file";
67+ default = null;
68+ description = ''
69+ Specifies the path to a file containing the
70+ hashed password for the MQTT user.
71+ To generate hashed password install <literal>mosquitto</literal>
72+ package and use <literal>mosquitto_passwd</literal>.
73+ '';
74+ };
7576+ acl = mkOption {
77+ type = listOf str;
78+ example = [ "read A/B" "readwrite A/#" ];
79+ default = [];
80+ description = ''
81+ Control client access to topics on the broker.
82+ '';
83+ };
84+ };
85+ };
86+87+ userAsserts = prefix: users:
88+ mapAttrsToList
89+ (n: _: {
90+ assertion = builtins.match "[^:\r\n]+" n != null;
91+ message = "Invalid user name ${n} in ${prefix}";
92+ })
93+ users
94+ ++ mapAttrsToList
95+ (n: u: {
96+ assertion = count (s: s != null) [
97+ u.password u.passwordFile u.hashedPassword u.hashedPasswordFile
98+ ] <= 1;
99+ message = "Cannot set more than one password option for user ${n} in ${prefix}";
100+ }) users;
101+102+ makePasswordFile = users: path:
103+ let
104+ makeLines = store: file:
105+ mapAttrsToList
106+ (n: u: "addLine ${escapeShellArg n} ${escapeShellArg u.${store}}")
107+ (filterAttrs (_: u: u.${store} != null) users)
108+ ++ mapAttrsToList
109+ (n: u: "addFile ${escapeShellArg n} ${escapeShellArg "${u.${file}}"}")
110+ (filterAttrs (_: u: u.${file} != null) users);
111+ plainLines = makeLines "password" "passwordFile";
112+ hashedLines = makeLines "hashedPassword" "hashedPasswordFile";
113+ in
114+ pkgs.writeScript "make-mosquitto-passwd"
115+ (''
116+ #! ${pkgs.runtimeShell}
117+118+ set -eu
119+120+ file=${escapeShellArg path}
121122+ rm -f "$file"
123+ touch "$file"
0124125+ addLine() {
126+ echo "$1:$2" >> "$file"
127+ }
128+ addFile() {
129+ if [ $(wc -l <"$2") -gt 1 ]; then
130+ echo "invalid mosquitto password file $2" >&2
131+ return 1
132+ fi
133+ echo "$1:$(cat "$2")" >> "$file"
134+ }
135+ ''
136+ + concatStringsSep "\n"
137+ (plainLines
138+ ++ optional (plainLines != []) ''
139+ ${pkgs.mosquitto}/bin/mosquitto_passwd -U "$file"
140+ ''
141+ ++ hashedLines));
142+143+ makeACLFile = idx: users: supplement:
144+ pkgs.writeText "mosquitto-acl-${toString idx}.conf"
145+ (concatStringsSep
146+ "\n"
147+ (flatten [
148+ supplement
149+ (mapAttrsToList
150+ (n: u: [ "user ${n}" ] ++ map (t: "topic ${t}") u.acl)
151+ users)
152+ ]));
153+154+ authPluginOptions = with types; submodule {
155+ options = {
156+ plugin = mkOption {
157+ type = path;
158 description = ''
159+ Plugin path to load, should be a <literal>.so</literal> file.
160 '';
161 };
162163+ denySpecialChars = mkOption {
164+ type = bool;
0165 description = ''
166+ Automatically disallow all clients using <literal>#</literal>
167+ or <literal>+</literal> in their name/id.
168 '';
169+ default = true;
170 };
171172+ options = mkOption {
173+ type = attrsOf optionType;
174+ description = ''
175+ Options for the auth plugin. Each key turns into a <literal>auth_opt_*</literal>
176+ line in the config.
177+ '';
178+ default = {};
179+ };
180+ };
181+ };
182183+ authAsserts = prefix: auth:
184+ mapAttrsToList
185+ (n: _: {
186+ assertion = configKey.check n;
187+ message = "Invalid auth plugin key ${prefix}.${n}";
188+ })
189+ auth;
190191+ formatAuthPlugin = plugin:
192+ [
193+ "auth_plugin ${plugin.plugin}"
194+ "auth_plugin_deny_special_chars ${optionToString plugin.denySpecialChars}"
195+ ]
196+ ++ formatFreeform { prefix = "auth_opt_"; } plugin.options;
197198+ freeformListenerKeys = {
199+ allow_anonymous = 1;
200+ allow_zero_length_clientid = 1;
201+ auto_id_prefix = 1;
202+ cafile = 1;
203+ capath = 1;
204+ certfile = 1;
205+ ciphers = 1;
206+ "ciphers_tls1.3" = 1;
207+ crlfile = 1;
208+ dhparamfile = 1;
209+ http_dir = 1;
210+ keyfile = 1;
211+ max_connections = 1;
212+ max_qos = 1;
213+ max_topic_alias = 1;
214+ mount_point = 1;
215+ protocol = 1;
216+ psk_file = 1;
217+ psk_hint = 1;
218+ require_certificate = 1;
219+ socket_domain = 1;
220+ tls_engine = 1;
221+ tls_engine_kpass_sha1 = 1;
222+ tls_keyform = 1;
223+ tls_version = 1;
224+ use_identity_as_username = 1;
225+ use_subject_as_username = 1;
226+ use_username_as_clientid = 1;
227+ };
228229+ listenerOptions = with types; submodule {
230+ options = {
231+ port = mkOption {
232+ type = port;
233+ description = ''
234+ Port to listen on. Must be set to 0 to listen on a unix domain socket.
235+ '';
236+ default = 1883;
237+ };
238239+ address = mkOption {
240+ type = nullOr str;
241+ description = ''
242+ Address to listen on. Listen on <literal>0.0.0.0</literal>/<literal>::</literal>
243+ when unset.
244+ '';
245+ default = null;
246 };
247248+ authPlugins = mkOption {
249+ type = listOf authPluginOptions;
0250 description = ''
251+ Authentication plugin to attach to this listener.
252+ Refer to the <link xlink:href="https://mosquitto.org/man/mosquitto-conf-5.html">
253+ mosquitto.conf documentation</link> for details on authentication plugins.
254 '';
255+ default = [];
256 };
257258 users = mkOption {
259+ type = attrsOf userOptions;
260+ example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
261+ description = ''
262+ A set of users and their passwords and ACLs.
263+ '';
264+ default = {};
265+ };
00266267+ acl = mkOption {
268+ type = listOf str;
269+ description = ''
270+ Additional ACL items to prepend to the generated ACL file.
271+ '';
272+ default = [];
273+ };
00274275+ settings = mkOption {
276+ type = submodule {
277+ freeformType = attrsOf optionType;
278+ };
279+ description = ''
280+ Additional settings for this listener.
281+ '';
282+ default = {};
283+ };
284+ };
285+ };
286287+ listenerAsserts = prefix: listener:
288+ assertKeysValid prefix freeformListenerKeys listener.settings
289+ ++ userAsserts prefix listener.users
290+ ++ imap0
291+ (i: v: authAsserts "${prefix}.authPlugins.${toString i}" v)
292+ listener.authPlugins;
293+294+ formatListener = idx: listener:
295+ [
296+ "listener ${toString listener.port} ${toString listener.address}"
297+ "password_file ${cfg.dataDir}/passwd-${toString idx}"
298+ "acl_file ${makeACLFile idx listener.users listener.acl}"
299+ ]
300+ ++ formatFreeform {} listener.settings
301+ ++ concatMap formatAuthPlugin listener.authPlugins;
302+303+ freeformBridgeKeys = {
304+ bridge_alpn = 1;
305+ bridge_attempt_unsubscribe = 1;
306+ bridge_bind_address = 1;
307+ bridge_cafile = 1;
308+ bridge_capath = 1;
309+ bridge_certfile = 1;
310+ bridge_identity = 1;
311+ bridge_insecure = 1;
312+ bridge_keyfile = 1;
313+ bridge_max_packet_size = 1;
314+ bridge_outgoing_retain = 1;
315+ bridge_protocol_version = 1;
316+ bridge_psk = 1;
317+ bridge_require_ocsp = 1;
318+ bridge_tls_version = 1;
319+ cleansession = 1;
320+ idle_timeout = 1;
321+ keepalive_interval = 1;
322+ local_cleansession = 1;
323+ local_clientid = 1;
324+ local_password = 1;
325+ local_username = 1;
326+ notification_topic = 1;
327+ notifications = 1;
328+ notifications_local_only = 1;
329+ remote_clientid = 1;
330+ remote_password = 1;
331+ remote_username = 1;
332+ restart_timeout = 1;
333+ round_robin = 1;
334+ start_type = 1;
335+ threshold = 1;
336+ try_private = 1;
337+ };
338+339+ bridgeOptions = with types; submodule {
340+ options = {
341+ addresses = mkOption {
342+ type = listOf (submodule {
343+ options = {
344+ address = mkOption {
345+ type = str;
346 description = ''
347+ Address of the remote MQTT broker.
000348 '';
349 };
350351+ port = mkOption {
352+ type = port;
0353 description = ''
354+ Port of the remote MQTT broker.
355 '';
356+ default = 1883;
357 };
358 };
359 });
360+ default = [];
361 description = ''
362+ Remote endpoints for the bridge.
363 '';
364 };
365366+ topics = mkOption {
367+ type = listOf str;
0368 description = ''
369+ Topic patterns to be shared between the two brokers.
370+ Refer to the <link xlink:href="https://mosquitto.org/man/mosquitto-conf-5.html">
371+ mosquitto.conf documentation</link> for details on the format.
372 '';
373+ default = [];
374+ example = [ "# both 2 local/topic/ remote/topic/" ];
375 };
376377+ settings = mkOption {
378+ type = submodule {
379+ freeformType = attrsOf optionType;
380+ };
381 description = ''
382+ Additional settings for this bridge.
383 '';
384+ default = {};
385 };
386+ };
387+ };
388389+ bridgeAsserts = prefix: bridge:
390+ assertKeysValid prefix freeformBridgeKeys bridge.settings
391+ ++ [ {
392+ assertion = length bridge.addresses > 0;
393+ message = "Bridge ${prefix} needs remote broker addresses";
394+ } ];
395+396+ formatBridge = name: bridge:
397+ [
398+ "connection ${name}"
399+ "addresses ${concatMapStringsSep " " (a: "${a.address}:${toString a.port}") bridge.addresses}"
400+ ]
401+ ++ map (t: "topic ${t}") bridge.topics
402+ ++ formatFreeform {} bridge.settings;
403+404+ freeformGlobalKeys = {
405+ allow_duplicate_messages = 1;
406+ autosave_interval = 1;
407+ autosave_on_changes = 1;
408+ check_retain_source = 1;
409+ connection_messages = 1;
410+ log_facility = 1;
411+ log_timestamp = 1;
412+ log_timestamp_format = 1;
413+ max_inflight_bytes = 1;
414+ max_inflight_messages = 1;
415+ max_keepalive = 1;
416+ max_packet_size = 1;
417+ max_queued_bytes = 1;
418+ max_queued_messages = 1;
419+ memory_limit = 1;
420+ message_size_limit = 1;
421+ persistence_file = 1;
422+ persistence_location = 1;
423+ persistent_client_expiration = 1;
424+ pid_file = 1;
425+ queue_qos0_messages = 1;
426+ retain_available = 1;
427+ set_tcp_nodelay = 1;
428+ sys_interval = 1;
429+ upgrade_outgoing_qos = 1;
430+ websockets_headers_size = 1;
431+ websockets_log_level = 1;
432+ };
433+434+ globalOptions = with types; {
435+ enable = mkEnableOption "the MQTT Mosquitto broker";
436+437+ bridges = mkOption {
438+ type = attrsOf bridgeOptions;
439+ default = {};
440+ description = ''
441+ Bridges to build to other MQTT brokers.
442+ '';
443+ };
444+445+ listeners = mkOption {
446+ type = listOf listenerOptions;
447+ default = {};
448+ description = ''
449+ Listeners to configure on this broker.
450+ '';
451+ };
452+453+ includeDirs = mkOption {
454+ type = listOf path;
455+ description = ''
456+ Directories to be scanned for further config files to include.
457+ Directories will processed in the order given,
458+ <literal>*.conf</literal> files in the directory will be
459+ read in case-sensistive alphabetical order.
460+ '';
461+ default = [];
462+ };
463+464+ logDest = mkOption {
465+ type = listOf (either path (enum [ "stdout" "stderr" "syslog" "topic" "dlt" ]));
466+ description = ''
467+ Destinations to send log messages to.
468+ '';
469+ default = [ "stderr" ];
470+ };
471+472+ logType = mkOption {
473+ type = listOf (enum [ "debug" "error" "warning" "notice" "information"
474+ "subscribe" "unsubscribe" "websockets" "none" "all" ]);
475+ description = ''
476+ Types of messages to log.
477+ '';
478+ default = [];
479+ };
480+481+ persistence = mkOption {
482+ type = bool;
483+ description = ''
484+ Enable persistent storage of subscriptions and messages.
485+ '';
486+ default = true;
487+ };
488489+ dataDir = mkOption {
490+ default = "/var/lib/mosquitto";
491+ type = types.path;
492+ description = ''
493+ The data directory.
494+ '';
495+ };
496497+ settings = mkOption {
498+ type = submodule {
499+ freeformType = attrsOf optionType;
500+ };
501+ description = ''
502+ Global configuration options for the mosquitto broker.
503+ '';
504+ default = {};
505 };
506 };
507508+ globalAsserts = prefix: cfg:
509+ flatten [
510+ (assertKeysValid prefix freeformGlobalKeys cfg.settings)
511+ (imap0 (n: l: listenerAsserts "${prefix}.listener.${toString n}" l) cfg.listeners)
512+ (mapAttrsToList (n: b: bridgeAsserts "${prefix}.bridge.${n}" b) cfg.bridges)
513+ ];
514+515+ formatGlobal = cfg:
516+ [
517+ "per_listener_settings true"
518+ "persistence ${optionToString cfg.persistence}"
519+ ]
520+ ++ map
521+ (d: if path.check d then "log_dest file ${d}" else "log_dest ${d}")
522+ cfg.logDest
523+ ++ map (t: "log_type ${t}") cfg.logType
524+ ++ formatFreeform {} cfg.settings
525+ ++ concatLists (imap0 formatListener cfg.listeners)
526+ ++ concatLists (mapAttrsToList formatBridge cfg.bridges)
527+ ++ map (d: "include_dir ${d}") cfg.includeDirs;
528+529+ configFile = pkgs.writeText "mosquitto.conf"
530+ (concatStringsSep "\n" (formatGlobal cfg));
531+532+in
533+534+{
535+536+ ###### Interface
537+538+ options.services.mosquitto = globalOptions;
539540 ###### Implementation
541542 config = mkIf cfg.enable {
543544+ assertions = globalAsserts "services.mosquitto" cfg;
00000545546 systemd.services.mosquitto = {
547 description = "Mosquitto MQTT Broker Daemon";
···555 RuntimeDirectory = "mosquitto";
556 WorkingDirectory = cfg.dataDir;
557 Restart = "on-failure";
558+ ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${configFile}";
559 ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
560561 # Hardening
···580 ReadWritePaths = [
581 cfg.dataDir
582 "/tmp" # mosquitto_passwd creates files in /tmp before moving them
583+ ] ++ filter path.check cfg.logDest;
584+ ReadOnlyPaths =
585+ map (p: "${p}")
586+ (cfg.includeDirs
587+ ++ filter
588+ (v: v != null)
589+ (flatten [
590+ (map
591+ (l: [
592+ (l.settings.psk_file or null)
593+ (l.settings.http_dir or null)
594+ (l.settings.cafile or null)
595+ (l.settings.capath or null)
596+ (l.settings.certfile or null)
597+ (l.settings.crlfile or null)
598+ (l.settings.dhparamfile or null)
599+ (l.settings.keyfile or null)
600+ ])
601+ cfg.listeners)
602+ (mapAttrsToList
603+ (_: b: [
604+ (b.settings.bridge_cafile or null)
605+ (b.settings.bridge_capath or null)
606+ (b.settings.bridge_certfile or null)
607+ (b.settings.bridge_keyfile or null)
608+ ])
609+ cfg.bridges)
610+ ]));
611 RemoveIPC = true;
612 RestrictAddressFamilies = [
613 "AF_UNIX" # for sd_notify() call
···625 ];
626 UMask = "0077";
627 };
628+ preStart =
629+ concatStringsSep
630+ "\n"
631+ (imap0
632+ (idx: listener: makePasswordFile listener.users "${cfg.dataDir}/passwd-${toString idx}")
633+ cfg.listeners);
00000000634 };
635636 users.users.mosquitto = {
···644 users.groups.mosquitto.gid = config.ids.gids.mosquitto;
645646 };
647+648+ meta.maintainers = with lib.maintainers; [ pennae ];
649}
+33
nixos/tests/borgbackup.nix
···81 environment.BORG_RSH = "ssh -oStrictHostKeyChecking=no -i /root/id_ed25519.appendOnly";
82 };
8300000000000000000084 };
85 };
86···171 client.fail("{} list borg\@server:wrong".format(borg))
172173 # TODO: Make sure that data is not actually deleted
000000000000000174 '';
175})
···89 for system in SYSTEMS:
90 for release_type in RELEASE_TYPES:
91 for release_channel in RELEASE_CHANNELS:
92- version = factorio_versions[release_channel.name][release_type.name]
0093 this_release = {
94 "name": f"factorio_{release_type.name}_{system.tar_name}-{version}.tar.xz",
95 "url": f"https://factorio.com/get-download/{version}/{release_type.name}/{system.url_name}",
···89 for system in SYSTEMS:
90 for release_type in RELEASE_TYPES:
91 for release_channel in RELEASE_CHANNELS:
92+ version = factorio_versions[release_channel.name].get(release_type.name)
93+ if version == None:
94+ continue
95 this_release = {
96 "name": f"factorio_{release_type.name}_{system.tar_name}-{version}.tar.xz",
97 "url": f"https://factorio.com/get-download/{version}/{release_type.name}/{system.url_name}",
···1self: super: {
23+ age = super.callPackage ./ext/age.nix { };
0045 periods = super.callPackage ./ext/periods.nix { };
6
+5
pkgs/servers/web-apps/discourse/default.nix
···266267 # Make sure the notification email setting applies
268 ./notification_email.patch
00000269 ];
270271 postPatch = ''
···266267 # Make sure the notification email setting applies
268 ./notification_email.patch
269+270+ # Change the path to the public directory reported by Discourse
271+ # to its real path instead of the symlink in the store, since
272+ # the store path won't be matched by any nginx rules
273+ ./public_dir_path.patch
274 ];
275276 postPatch = ''
···1+{ lib, rustPlatform, fetchFromGitHub }:
2+3+rustPlatform.buildRustPackage rec {
4+ pname = "anewer";
5+ version = "0.1.6";
6+7+ src = fetchFromGitHub {
8+ owner = "ysf";
9+ repo = pname;
10+ rev = version;
11+ sha256 = "181mi674354bddnq894yyq587w7skjh35vn61i41vfi6lqz5dy3d";
12+ };
13+14+ cargoSha256 = "sha256-LJ0l5CZM5NqdbCZe4ELkYf9EkKyBxL/LrNmFy+JS6gM=";
15+16+ meta = with lib; {
17+ description = "Append lines from stdin to a file if they don't already exist in the file";
18+ homepage = "https://github.com/ysf/anewer";
19+ license = licenses.gpl3Plus;
20+ maintainers = with maintainers; [ figsoda ];
21+ };
22+}
+2
pkgs/top-level/aliases.nix
···53 aminal = throw "aminal was renamed to darktile."; # added 2021-09-28
54 ammonite-repl = ammonite; # added 2017-05-02
55 amsn = throw "amsn has been removed due to being unmaintained."; # added 2020-12-09
056 antimicro = throw "antimicro has been removed as it was broken, see antimicroX instead."; # added 2020-08-06
57 arduino_core = arduino-core; # added 2015-02-04
58 ardour_5 = throw "ardour_5 has been removed. see https://github.com/NixOS/nixpkgs/issues/139549"; # added 2021-09-28
···390 keepassx2-http = keepassx-reboot; # added 2016-10-17
391 kexectools = kexec-tools; # added 2021-09-03
392 keybase-go = keybase; # added 2016-08-24
0393 kinetic-cpp-client = throw "kinetic-cpp-client has been removed from nixpkgs, as it's abandoned."; # 2020-04-28
394 kicad-with-packages3d = kicad; # added 2019-11-25
395 kindlegen = throw "kindlegen has been removed from nixpkgs, as it's abandoned and no longer available for download."; # 2021-03-09
···53 aminal = throw "aminal was renamed to darktile."; # added 2021-09-28
54 ammonite-repl = ammonite; # added 2017-05-02
55 amsn = throw "amsn has been removed due to being unmaintained."; # added 2020-12-09
56+ angelfish = libsForQt5.plasmaMobileGear.angelfish; # added 2021-10-06
57 antimicro = throw "antimicro has been removed as it was broken, see antimicroX instead."; # added 2020-08-06
58 arduino_core = arduino-core; # added 2015-02-04
59 ardour_5 = throw "ardour_5 has been removed. see https://github.com/NixOS/nixpkgs/issues/139549"; # added 2021-09-28
···391 keepassx2-http = keepassx-reboot; # added 2016-10-17
392 kexectools = kexec-tools; # added 2021-09-03
393 keybase-go = keybase; # added 2016-08-24
394+ keysmith = libsForQt5.plasmaMobileGear.keysmith; # added 2021-07-14
395 kinetic-cpp-client = throw "kinetic-cpp-client has been removed from nixpkgs, as it's abandoned."; # 2020-04-28
396 kicad-with-packages3d = kicad; # added 2019-11-25
397 kindlegen = throw "kindlegen has been removed from nixpkgs, as it's abandoned and no longer available for download."; # 2021-03-09