diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
index 4bbd46428524..cbcc3cb7cfcd 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
@@ -1138,6 +1138,14 @@ Superuser created successfully.
coursier, you can create a shell alias.
+
+
+ The services.mosquitto module has been
+ rewritten to support multiple listeners and per-listener
+ configuration. Module configurations from previous releases
+ will no longer work and must be updated.
+
+
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 36d03fd0b59b..982f87daecdc 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -351,6 +351,9 @@ In addition to numerous new and upgraded packages, this release has the followin
- 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.
+- The `services.mosquitto` module has been rewritten to support multiple listeners and per-listener configuration.
+ Module configurations from previous releases will no longer work and must be updated.
+
## Other Notable Changes {#sec-release-21.11-notable-changes}
diff --git a/nixos/modules/services/networking/mosquitto.nix b/nixos/modules/services/networking/mosquitto.nix
index b0fbfc194083..5a573cbf4ac9 100644
--- a/nixos/modules/services/networking/mosquitto.nix
+++ b/nixos/modules/services/networking/mosquitto.nix
@@ -5,35 +5,529 @@ with lib;
let
cfg = config.services.mosquitto;
- listenerConf = optionalString cfg.ssl.enable ''
- listener ${toString cfg.ssl.port} ${cfg.ssl.host}
- cafile ${cfg.ssl.cafile}
- certfile ${cfg.ssl.certfile}
- keyfile ${cfg.ssl.keyfile}
- '';
+ # note that mosquitto config parsing is very simplistic as of may 2021.
+ # often times they'll e.g. strtok() a line, check the first two tokens, and ignore the rest.
+ # there's no escaping available either, so we have to prevent any being necessary.
+ str = types.strMatching "[^\r\n]*" // {
+ description = "single-line string";
+ };
+ path = types.addCheck types.path (p: str.check "${p}");
+ configKey = types.strMatching "[^\r\n\t ]+";
+ optionType = with types; oneOf [ str path bool int ] // {
+ description = "string, path, bool, or integer";
+ };
+ optionToString = v:
+ if isBool v then boolToString v
+ else if path.check v then "${v}"
+ else toString v;
- passwordConf = optionalString cfg.checkPasswords ''
- password_file ${cfg.dataDir}/passwd
- '';
+ assertKeysValid = prefix: valid: config:
+ mapAttrsToList
+ (n: _: {
+ assertion = valid ? ${n};
+ message = "Invalid config key ${prefix}.${n}.";
+ })
+ config;
- mosquittoConf = pkgs.writeText "mosquitto.conf" ''
- acl_file ${aclFile}
- persistence true
- allow_anonymous ${boolToString cfg.allowAnonymous}
- listener ${toString cfg.port} ${cfg.host}
- ${passwordConf}
- ${listenerConf}
- ${cfg.extraConf}
- '';
+ formatFreeform = { prefix ? "" }: mapAttrsToList (n: v: "${prefix}${n} ${optionToString v}");
- userAcl = (concatStringsSep "\n\n" (mapAttrsToList (n: c:
- "user ${n}\n" + (concatStringsSep "\n" c.acl)) cfg.users
- ));
+ userOptions = with types; submodule {
+ options = {
+ password = mkOption {
+ type = uniq (nullOr str);
+ default = null;
+ description = ''
+ Specifies the (clear text) password for the MQTT User.
+ '';
+ };
- aclFile = pkgs.writeText "mosquitto.acl" ''
- ${cfg.aclExtraConf}
- ${userAcl}
- '';
+ passwordFile = mkOption {
+ type = uniq (nullOr types.path);
+ example = "/path/to/file";
+ default = null;
+ description = ''
+ Specifies the path to a file containing the
+ clear text password for the MQTT user.
+ '';
+ };
+
+ hashedPassword = mkOption {
+ type = uniq (nullOr str);
+ default = null;
+ description = ''
+ Specifies the hashed password for the MQTT User.
+ To generate hashed password install mosquitto
+ package and use mosquitto_passwd.
+ '';
+ };
+
+ hashedPasswordFile = mkOption {
+ type = uniq (nullOr types.path);
+ example = "/path/to/file";
+ default = null;
+ description = ''
+ Specifies the path to a file containing the
+ hashed password for the MQTT user.
+ To generate hashed password install mosquitto
+ package and use mosquitto_passwd.
+ '';
+ };
+
+ acl = mkOption {
+ type = listOf str;
+ example = [ "read A/B" "readwrite A/#" ];
+ default = [];
+ description = ''
+ Control client access to topics on the broker.
+ '';
+ };
+ };
+ };
+
+ userAsserts = prefix: users:
+ mapAttrsToList
+ (n: _: {
+ assertion = builtins.match "[^:\r\n]+" n != null;
+ message = "Invalid user name ${n} in ${prefix}";
+ })
+ users
+ ++ mapAttrsToList
+ (n: u: {
+ assertion = count (s: s != null) [
+ u.password u.passwordFile u.hashedPassword u.hashedPasswordFile
+ ] <= 1;
+ message = "Cannot set more than one password option for user ${n} in ${prefix}";
+ }) users;
+
+ makePasswordFile = users: path:
+ let
+ makeLines = store: file:
+ mapAttrsToList
+ (n: u: "addLine ${escapeShellArg n} ${escapeShellArg u.${store}}")
+ (filterAttrs (_: u: u.${store} != null) users)
+ ++ mapAttrsToList
+ (n: u: "addFile ${escapeShellArg n} ${escapeShellArg "${u.${file}}"}")
+ (filterAttrs (_: u: u.${file} != null) users);
+ plainLines = makeLines "password" "passwordFile";
+ hashedLines = makeLines "hashedPassword" "hashedPasswordFile";
+ in
+ pkgs.writeScript "make-mosquitto-passwd"
+ (''
+ #! ${pkgs.runtimeShell}
+
+ set -eu
+
+ file=${escapeShellArg path}
+
+ rm -f "$file"
+ touch "$file"
+
+ addLine() {
+ echo "$1:$2" >> "$file"
+ }
+ addFile() {
+ if [ $(wc -l <"$2") -gt 1 ]; then
+ echo "invalid mosquitto password file $2" >&2
+ return 1
+ fi
+ echo "$1:$(cat "$2")" >> "$file"
+ }
+ ''
+ + concatStringsSep "\n"
+ (plainLines
+ ++ optional (plainLines != []) ''
+ ${pkgs.mosquitto}/bin/mosquitto_passwd -U "$file"
+ ''
+ ++ hashedLines));
+
+ makeACLFile = idx: users: supplement:
+ pkgs.writeText "mosquitto-acl-${toString idx}.conf"
+ (concatStringsSep
+ "\n"
+ (flatten [
+ supplement
+ (mapAttrsToList
+ (n: u: [ "user ${n}" ] ++ map (t: "topic ${t}") u.acl)
+ users)
+ ]));
+
+ authPluginOptions = with types; submodule {
+ options = {
+ plugin = mkOption {
+ type = path;
+ description = ''
+ Plugin path to load, should be a .so file.
+ '';
+ };
+
+ denySpecialChars = mkOption {
+ type = bool;
+ description = ''
+ Automatically disallow all clients using #
+ or + in their name/id.
+ '';
+ default = true;
+ };
+
+ options = mkOption {
+ type = attrsOf optionType;
+ description = ''
+ Options for the auth plugin. Each key turns into a auth_opt_*
+ line in the config.
+ '';
+ default = {};
+ };
+ };
+ };
+
+ authAsserts = prefix: auth:
+ mapAttrsToList
+ (n: _: {
+ assertion = configKey.check n;
+ message = "Invalid auth plugin key ${prefix}.${n}";
+ })
+ auth;
+
+ formatAuthPlugin = plugin:
+ [
+ "auth_plugin ${plugin.plugin}"
+ "auth_plugin_deny_special_chars ${optionToString plugin.denySpecialChars}"
+ ]
+ ++ formatFreeform { prefix = "auth_opt_"; } plugin.options;
+
+ freeformListenerKeys = {
+ allow_anonymous = 1;
+ allow_zero_length_clientid = 1;
+ auto_id_prefix = 1;
+ cafile = 1;
+ capath = 1;
+ certfile = 1;
+ ciphers = 1;
+ "ciphers_tls1.3" = 1;
+ crlfile = 1;
+ dhparamfile = 1;
+ http_dir = 1;
+ keyfile = 1;
+ max_connections = 1;
+ max_qos = 1;
+ max_topic_alias = 1;
+ mount_point = 1;
+ protocol = 1;
+ psk_file = 1;
+ psk_hint = 1;
+ require_certificate = 1;
+ socket_domain = 1;
+ tls_engine = 1;
+ tls_engine_kpass_sha1 = 1;
+ tls_keyform = 1;
+ tls_version = 1;
+ use_identity_as_username = 1;
+ use_subject_as_username = 1;
+ use_username_as_clientid = 1;
+ };
+
+ listenerOptions = with types; submodule {
+ options = {
+ port = mkOption {
+ type = port;
+ description = ''
+ Port to listen on. Must be set to 0 to listen on a unix domain socket.
+ '';
+ default = 1883;
+ };
+
+ address = mkOption {
+ type = nullOr str;
+ description = ''
+ Address to listen on. Listen on 0.0.0.0/::
+ when unset.
+ '';
+ default = null;
+ };
+
+ authPlugins = mkOption {
+ type = listOf authPluginOptions;
+ description = ''
+ Authentication plugin to attach to this listener.
+ Refer to the
+ mosquitto.conf documentation for details on authentication plugins.
+ '';
+ default = [];
+ };
+
+ users = mkOption {
+ type = attrsOf userOptions;
+ example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
+ description = ''
+ A set of users and their passwords and ACLs.
+ '';
+ default = {};
+ };
+
+ acl = mkOption {
+ type = listOf str;
+ description = ''
+ Additional ACL items to prepend to the generated ACL file.
+ '';
+ default = [];
+ };
+
+ settings = mkOption {
+ type = submodule {
+ freeformType = attrsOf optionType;
+ };
+ description = ''
+ Additional settings for this listener.
+ '';
+ default = {};
+ };
+ };
+ };
+
+ listenerAsserts = prefix: listener:
+ assertKeysValid prefix freeformListenerKeys listener.settings
+ ++ userAsserts prefix listener.users
+ ++ imap0
+ (i: v: authAsserts "${prefix}.authPlugins.${toString i}" v)
+ listener.authPlugins;
+
+ formatListener = idx: listener:
+ [
+ "listener ${toString listener.port} ${toString listener.address}"
+ "password_file ${cfg.dataDir}/passwd-${toString idx}"
+ "acl_file ${makeACLFile idx listener.users listener.acl}"
+ ]
+ ++ formatFreeform {} listener.settings
+ ++ concatMap formatAuthPlugin listener.authPlugins;
+
+ freeformBridgeKeys = {
+ bridge_alpn = 1;
+ bridge_attempt_unsubscribe = 1;
+ bridge_bind_address = 1;
+ bridge_cafile = 1;
+ bridge_capath = 1;
+ bridge_certfile = 1;
+ bridge_identity = 1;
+ bridge_insecure = 1;
+ bridge_keyfile = 1;
+ bridge_max_packet_size = 1;
+ bridge_outgoing_retain = 1;
+ bridge_protocol_version = 1;
+ bridge_psk = 1;
+ bridge_require_ocsp = 1;
+ bridge_tls_version = 1;
+ cleansession = 1;
+ idle_timeout = 1;
+ keepalive_interval = 1;
+ local_cleansession = 1;
+ local_clientid = 1;
+ local_password = 1;
+ local_username = 1;
+ notification_topic = 1;
+ notifications = 1;
+ notifications_local_only = 1;
+ remote_clientid = 1;
+ remote_password = 1;
+ remote_username = 1;
+ restart_timeout = 1;
+ round_robin = 1;
+ start_type = 1;
+ threshold = 1;
+ try_private = 1;
+ };
+
+ bridgeOptions = with types; submodule {
+ options = {
+ addresses = mkOption {
+ type = listOf (submodule {
+ options = {
+ address = mkOption {
+ type = str;
+ description = ''
+ Address of the remote MQTT broker.
+ '';
+ };
+
+ port = mkOption {
+ type = port;
+ description = ''
+ Port of the remote MQTT broker.
+ '';
+ default = 1883;
+ };
+ };
+ });
+ default = [];
+ description = ''
+ Remote endpoints for the bridge.
+ '';
+ };
+
+ topics = mkOption {
+ type = listOf str;
+ description = ''
+ Topic patterns to be shared between the two brokers.
+ Refer to the
+ mosquitto.conf documentation for details on the format.
+ '';
+ default = [];
+ example = [ "# both 2 local/topic/ remote/topic/" ];
+ };
+
+ settings = mkOption {
+ type = submodule {
+ freeformType = attrsOf optionType;
+ };
+ description = ''
+ Additional settings for this bridge.
+ '';
+ default = {};
+ };
+ };
+ };
+
+ bridgeAsserts = prefix: bridge:
+ assertKeysValid prefix freeformBridgeKeys bridge.settings
+ ++ [ {
+ assertion = length bridge.addresses > 0;
+ message = "Bridge ${prefix} needs remote broker addresses";
+ } ];
+
+ formatBridge = name: bridge:
+ [
+ "connection ${name}"
+ "addresses ${concatMapStringsSep " " (a: "${a.address}:${toString a.port}") bridge.addresses}"
+ ]
+ ++ map (t: "topic ${t}") bridge.topics
+ ++ formatFreeform {} bridge.settings;
+
+ freeformGlobalKeys = {
+ allow_duplicate_messages = 1;
+ autosave_interval = 1;
+ autosave_on_changes = 1;
+ check_retain_source = 1;
+ connection_messages = 1;
+ log_facility = 1;
+ log_timestamp = 1;
+ log_timestamp_format = 1;
+ max_inflight_bytes = 1;
+ max_inflight_messages = 1;
+ max_keepalive = 1;
+ max_packet_size = 1;
+ max_queued_bytes = 1;
+ max_queued_messages = 1;
+ memory_limit = 1;
+ message_size_limit = 1;
+ persistence_file = 1;
+ persistence_location = 1;
+ persistent_client_expiration = 1;
+ pid_file = 1;
+ queue_qos0_messages = 1;
+ retain_available = 1;
+ set_tcp_nodelay = 1;
+ sys_interval = 1;
+ upgrade_outgoing_qos = 1;
+ websockets_headers_size = 1;
+ websockets_log_level = 1;
+ };
+
+ globalOptions = with types; {
+ enable = mkEnableOption "the MQTT Mosquitto broker";
+
+ bridges = mkOption {
+ type = attrsOf bridgeOptions;
+ default = {};
+ description = ''
+ Bridges to build to other MQTT brokers.
+ '';
+ };
+
+ listeners = mkOption {
+ type = listOf listenerOptions;
+ default = {};
+ description = ''
+ Listeners to configure on this broker.
+ '';
+ };
+
+ includeDirs = mkOption {
+ type = listOf path;
+ description = ''
+ Directories to be scanned for further config files to include.
+ Directories will processed in the order given,
+ *.conf files in the directory will be
+ read in case-sensistive alphabetical order.
+ '';
+ default = [];
+ };
+
+ logDest = mkOption {
+ type = listOf (either path (enum [ "stdout" "stderr" "syslog" "topic" "dlt" ]));
+ description = ''
+ Destinations to send log messages to.
+ '';
+ default = [ "stderr" ];
+ };
+
+ logType = mkOption {
+ type = listOf (enum [ "debug" "error" "warning" "notice" "information"
+ "subscribe" "unsubscribe" "websockets" "none" "all" ]);
+ description = ''
+ Types of messages to log.
+ '';
+ default = [];
+ };
+
+ persistence = mkOption {
+ type = bool;
+ description = ''
+ Enable persistent storage of subscriptions and messages.
+ '';
+ default = true;
+ };
+
+ dataDir = mkOption {
+ default = "/var/lib/mosquitto";
+ type = types.path;
+ description = ''
+ The data directory.
+ '';
+ };
+
+ settings = mkOption {
+ type = submodule {
+ freeformType = attrsOf optionType;
+ };
+ description = ''
+ Global configuration options for the mosquitto broker.
+ '';
+ default = {};
+ };
+ };
+
+ globalAsserts = prefix: cfg:
+ flatten [
+ (assertKeysValid prefix freeformGlobalKeys cfg.settings)
+ (imap0 (n: l: listenerAsserts "${prefix}.listener.${toString n}" l) cfg.listeners)
+ (mapAttrsToList (n: b: bridgeAsserts "${prefix}.bridge.${n}" b) cfg.bridges)
+ ];
+
+ formatGlobal = cfg:
+ [
+ "per_listener_settings true"
+ "persistence ${optionToString cfg.persistence}"
+ ]
+ ++ map
+ (d: if path.check d then "log_dest file ${d}" else "log_dest ${d}")
+ cfg.logDest
+ ++ map (t: "log_type ${t}") cfg.logType
+ ++ formatFreeform {} cfg.settings
+ ++ concatLists (imap0 formatListener cfg.listeners)
+ ++ concatLists (mapAttrsToList formatBridge cfg.bridges)
+ ++ map (d: "include_dir ${d}") cfg.includeDirs;
+
+ configFile = pkgs.writeText "mosquitto.conf"
+ (concatStringsSep "\n" (formatGlobal cfg));
in
@@ -41,179 +535,13 @@ in
###### Interface
- options = {
- services.mosquitto = {
- enable = mkEnableOption "the MQTT Mosquitto broker";
-
- host = mkOption {
- default = "127.0.0.1";
- example = "0.0.0.0";
- type = types.str;
- description = ''
- Host to listen on without SSL.
- '';
- };
-
- port = mkOption {
- default = 1883;
- type = types.int;
- description = ''
- Port on which to listen without SSL.
- '';
- };
-
- ssl = {
- enable = mkEnableOption "SSL listener";
-
- cafile = mkOption {
- type = types.nullOr types.path;
- default = null;
- description = "Path to PEM encoded CA certificates.";
- };
-
- certfile = mkOption {
- type = types.nullOr types.path;
- default = null;
- description = "Path to PEM encoded server certificate.";
- };
-
- keyfile = mkOption {
- type = types.nullOr types.path;
- default = null;
- description = "Path to PEM encoded server key.";
- };
-
- host = mkOption {
- default = "0.0.0.0";
- example = "localhost";
- type = types.str;
- description = ''
- Host to listen on with SSL.
- '';
- };
-
- port = mkOption {
- default = 8883;
- type = types.int;
- description = ''
- Port on which to listen with SSL.
- '';
- };
- };
-
- dataDir = mkOption {
- default = "/var/lib/mosquitto";
- type = types.path;
- description = ''
- The data directory.
- '';
- };
-
- users = mkOption {
- type = types.attrsOf (types.submodule {
- options = {
- password = mkOption {
- type = with types; uniq (nullOr str);
- default = null;
- description = ''
- Specifies the (clear text) password for the MQTT User.
- '';
- };
-
- passwordFile = mkOption {
- type = with types; uniq (nullOr str);
- example = "/path/to/file";
- default = null;
- description = ''
- Specifies the path to a file containing the
- clear text password for the MQTT user.
- '';
- };
-
- hashedPassword = mkOption {
- type = with types; uniq (nullOr str);
- default = null;
- description = ''
- Specifies the hashed password for the MQTT User.
- To generate hashed password install mosquitto
- package and use mosquitto_passwd.
- '';
- };
-
- hashedPasswordFile = mkOption {
- type = with types; uniq (nullOr str);
- example = "/path/to/file";
- default = null;
- description = ''
- Specifies the path to a file containing the
- hashed password for the MQTT user.
- To generate hashed password install mosquitto
- package and use mosquitto_passwd.
- '';
- };
-
- acl = mkOption {
- type = types.listOf types.str;
- example = [ "topic read A/B" "topic A/#" ];
- description = ''
- Control client access to topics on the broker.
- '';
- };
- };
- });
- example = { john = { password = "123456"; acl = [ "topic readwrite john/#" ]; }; };
- description = ''
- A set of users and their passwords and ACLs.
- '';
- };
-
- allowAnonymous = mkOption {
- default = false;
- type = types.bool;
- description = ''
- Allow clients to connect without authentication.
- '';
- };
-
- checkPasswords = mkOption {
- default = false;
- example = true;
- type = types.bool;
- description = ''
- Refuse connection when clients provide incorrect passwords.
- '';
- };
-
- extraConf = mkOption {
- default = "";
- type = types.lines;
- description = ''
- Extra config to append to `mosquitto.conf` file.
- '';
- };
-
- aclExtraConf = mkOption {
- default = "";
- type = types.lines;
- description = ''
- Extra config to prepend to the ACL file.
- '';
- };
-
- };
- };
-
+ options.services.mosquitto = globalOptions;
###### Implementation
config = mkIf cfg.enable {
- assertions = mapAttrsToList (name: cfg: {
- assertion = length (filter (s: s != null) (with cfg; [
- password passwordFile hashedPassword hashedPasswordFile
- ])) <= 1;
- message = "Cannot set more than one password option";
- }) cfg.users;
+ assertions = globalAsserts "services.mosquitto" cfg;
systemd.services.mosquitto = {
description = "Mosquitto MQTT Broker Daemon";
@@ -227,7 +555,7 @@ in
RuntimeDirectory = "mosquitto";
WorkingDirectory = cfg.dataDir;
Restart = "on-failure";
- ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${mosquittoConf}";
+ ExecStart = "${pkgs.mosquitto}/bin/mosquitto -c ${configFile}";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
# Hardening
@@ -252,12 +580,34 @@ in
ReadWritePaths = [
cfg.dataDir
"/tmp" # mosquitto_passwd creates files in /tmp before moving them
- ];
- ReadOnlyPaths = with cfg.ssl; lib.optionals (enable) [
- certfile
- keyfile
- cafile
- ];
+ ] ++ filter path.check cfg.logDest;
+ ReadOnlyPaths =
+ map (p: "${p}")
+ (cfg.includeDirs
+ ++ filter
+ (v: v != null)
+ (flatten [
+ (map
+ (l: [
+ (l.settings.psk_file or null)
+ (l.settings.http_dir or null)
+ (l.settings.cafile or null)
+ (l.settings.capath or null)
+ (l.settings.certfile or null)
+ (l.settings.crlfile or null)
+ (l.settings.dhparamfile or null)
+ (l.settings.keyfile or null)
+ ])
+ cfg.listeners)
+ (mapAttrsToList
+ (_: b: [
+ (b.settings.bridge_cafile or null)
+ (b.settings.bridge_capath or null)
+ (b.settings.bridge_certfile or null)
+ (b.settings.bridge_keyfile or null)
+ ])
+ cfg.bridges)
+ ]));
RemoveIPC = true;
RestrictAddressFamilies = [
"AF_UNIX" # for sd_notify() call
@@ -275,20 +625,12 @@ in
];
UMask = "0077";
};
- preStart = ''
- rm -f ${cfg.dataDir}/passwd
- touch ${cfg.dataDir}/passwd
- '' + concatStringsSep "\n" (
- mapAttrsToList (n: c:
- if c.hashedPasswordFile != null then
- "echo '${n}:'$(cat '${c.hashedPasswordFile}') >> ${cfg.dataDir}/passwd"
- else if c.passwordFile != null then
- "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} $(cat '${c.passwordFile}')"
- else if c.hashedPassword != null then
- "echo '${n}:${c.hashedPassword}' >> ${cfg.dataDir}/passwd"
- else optionalString (c.password != null)
- "${pkgs.mosquitto}/bin/mosquitto_passwd -b ${cfg.dataDir}/passwd ${n} '${c.password}'"
- ) cfg.users);
+ preStart =
+ concatStringsSep
+ "\n"
+ (imap0
+ (idx: listener: makePasswordFile listener.users "${cfg.dataDir}/passwd-${toString idx}")
+ cfg.listeners);
};
users.users.mosquitto = {
@@ -302,4 +644,6 @@ in
users.groups.mosquitto.gid = config.ids.gids.mosquitto;
};
+
+ meta.maintainers = with lib.maintainers; [ pennae ];
}
diff --git a/nixos/tests/home-assistant.nix b/nixos/tests/home-assistant.nix
index 699be8fd7dc6..0894736bac9c 100644
--- a/nixos/tests/home-assistant.nix
+++ b/nixos/tests/home-assistant.nix
@@ -12,13 +12,14 @@ in {
environment.systemPackages = with pkgs; [ mosquitto ];
services.mosquitto = {
enable = true;
- checkPasswords = true;
- users = {
- "${mqttUsername}" = {
- acl = [ "topic readwrite #" ];
- password = mqttPassword;
+ listeners = [ {
+ users = {
+ "${mqttUsername}" = {
+ acl = [ "readwrite #" ];
+ password = mqttPassword;
+ };
};
- };
+ } ];
};
services.home-assistant = {
inherit configDir;
diff --git a/nixos/tests/mosquitto.nix b/nixos/tests/mosquitto.nix
index e29bd559ed9b..1a534184066c 100644
--- a/nixos/tests/mosquitto.nix
+++ b/nixos/tests/mosquitto.nix
@@ -19,16 +19,18 @@ in {
server = { pkgs, ... }: {
networking.firewall.allowedTCPPorts = [ port ];
services.mosquitto = {
- inherit port;
enable = true;
- host = "0.0.0.0";
- checkPasswords = true;
- users.${username} = {
- inherit password;
- acl = [
- "topic readwrite ${topic}"
- ];
- };
+ listeners = [
+ {
+ inherit port;
+ users.${username} = {
+ inherit password;
+ acl = [
+ "readwrite ${topic}"
+ ];
+ };
+ }
+ ];
};
# disable private /tmp for this test