Loading nixos/modules/services/networking/mosquitto.nix +91 −94 Original line number Diff line number Diff line { config, lib, pkgs, ...}: with lib; let cfg = config.services.mosquitto; # 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]*" // { str = lib.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 ] // { path = lib.types.addCheck lib.types.path (p: str.check "${p}"); configKey = lib.types.strMatching "[^\r\n\t ]+"; optionType = with lib.types; oneOf [ str path bool int ] // { description = "string, path, bool, or integer"; }; optionToString = v: if isBool v then boolToString v if lib.isBool v then lib.boolToString v else if path.check v then "${v}" else toString v; assertKeysValid = prefix: valid: config: mapAttrsToList lib.mapAttrsToList (n: _: { assertion = valid ? ${n}; message = "Invalid config key ${prefix}.${n}."; }) config; formatFreeform = { prefix ? "" }: mapAttrsToList (n: v: "${prefix}${n} ${optionToString v}"); formatFreeform = { prefix ? "" }: lib.mapAttrsToList (n: v: "${prefix}${n} ${optionToString v}"); userOptions = with types; submodule { userOptions = with lib.types; submodule { options = { password = mkOption { password = lib.mkOption { type = uniq (nullOr str); default = null; description = '' Loading @@ -41,7 +38,7 @@ let ''; }; passwordFile = mkOption { passwordFile = lib.mkOption { type = uniq (nullOr path); example = "/path/to/file"; default = null; Loading @@ -54,7 +51,7 @@ let ''; }; hashedPassword = mkOption { hashedPassword = lib.mkOption { type = uniq (nullOr str); default = null; description = '' Loading @@ -66,7 +63,7 @@ let ''; }; hashedPasswordFile = mkOption { hashedPasswordFile = lib.mkOption { type = uniq (nullOr path); example = "/path/to/file"; default = null; Loading @@ -82,7 +79,7 @@ let ''; }; acl = mkOption { acl = lib.mkOption { type = listOf str; example = [ "read A/B" "readwrite A/#" ]; default = []; Loading @@ -94,15 +91,15 @@ let }; userAsserts = prefix: users: mapAttrsToList lib.mapAttrsToList (n: _: { assertion = builtins.match "[^:\r\n]+" n != null; message = "Invalid user name ${n} in ${prefix}"; }) users ++ mapAttrsToList ++ lib.mapAttrsToList (n: u: { assertion = count (s: s != null) [ assertion = lib.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}"; Loading @@ -112,26 +109,26 @@ let userScope = prefix: index: "${prefix}-user-${toString index}"; credentialID = prefix: credential: "${prefix}-${credential}"; toScopedUsers = listenerScope: users: pipe users [ attrNames (imap0 (index: user: nameValuePair user toScopedUsers = listenerScope: users: lib.pipe users [ lib.attrNames (lib.imap0 (index: user: lib.nameValuePair user (users.${user} // { scope = userScope listenerScope index; }) )) listToAttrs lib.listToAttrs ]; userCredentials = user: credentials: pipe credentials [ (filter (credential: user.${credential} != null)) userCredentials = user: credentials: lib.pipe credentials [ (lib.filter (credential: user.${credential} != null)) (map (credential: "${credentialID user.scope credential}:${user.${credential}}")) ]; usersCredentials = listenerScope: users: credentials: pipe users [ usersCredentials = listenerScope: users: credentials: lib.pipe users [ (toScopedUsers listenerScope) (mapAttrsToList (_: user: userCredentials user credentials)) concatLists (lib.mapAttrsToList (_: user: userCredentials user credentials)) lib.concatLists ]; systemdCredentials = listeners: listenerCredentials: pipe listeners [ (imap0 (index: listener: listenerCredentials (listenerScope index) listener)) concatLists systemdCredentials = listeners: listenerCredentials: lib.pipe listeners [ (lib.imap0 (index: listener: listenerCredentials (listenerScope index) listener)) lib.concatLists ]; makePasswordFile = listenerScope: users: path: Loading @@ -139,12 +136,12 @@ let makeLines = store: file: let scopedUsers = toScopedUsers listenerScope users; in mapAttrsToList (name: user: ''addLine ${escapeShellArg name} "''$(systemd-creds cat ${credentialID user.scope store})"'') (filterAttrs (_: user: user.${store} != null) scopedUsers) ++ mapAttrsToList (name: user: ''addFile ${escapeShellArg name} "''${CREDENTIALS_DIRECTORY}/${credentialID user.scope file}"'') (filterAttrs (_: user: user.${file} != null) scopedUsers); lib.mapAttrsToList (name: user: ''addLine ${lib.escapeShellArg name} "''$(systemd-creds cat ${credentialID user.scope store})"'') (lib.filterAttrs (_: user: user.${store} != null) scopedUsers) ++ lib.mapAttrsToList (name: user: ''addFile ${lib.escapeShellArg name} "''${CREDENTIALS_DIRECTORY}/${credentialID user.scope file}"'') (lib.filterAttrs (_: user: user.${file} != null) scopedUsers); plainLines = makeLines "password" "passwordFile"; hashedLines = makeLines "hashedPassword" "hashedPasswordFile"; in Loading @@ -154,7 +151,7 @@ let set -eu file=${escapeShellArg path} file=${lib.escapeShellArg path} rm -f "$file" touch "$file" Loading @@ -170,23 +167,23 @@ let echo "$1:$(cat "$2")" >> "$file" } '' + concatStringsSep "\n" + lib.concatStringsSep "\n" (plainLines ++ optional (plainLines != []) '' ++ lib.optional (plainLines != []) '' ${cfg.package}/bin/mosquitto_passwd -U "$file" '' ++ hashedLines)); authPluginOptions = with types; submodule { authPluginOptions = with lib.types; submodule { options = { plugin = mkOption { plugin = lib.mkOption { type = path; description = '' Plugin path to load, should be a `.so` file. ''; }; denySpecialChars = mkOption { denySpecialChars = lib.mkOption { type = bool; description = '' Automatically disallow all clients using `#` Loading @@ -195,7 +192,7 @@ let default = true; }; options = mkOption { options = lib.mkOption { type = attrsOf optionType; description = '' Options for the auth plugin. Each key turns into a `auth_opt_*` Loading @@ -207,7 +204,7 @@ let }; authAsserts = prefix: auth: mapAttrsToList lib.mapAttrsToList (n: _: { assertion = configKey.check n; message = "Invalid auth plugin key ${prefix}.${n}"; Loading Loading @@ -253,9 +250,9 @@ let use_username_as_clientid = 1; }; listenerOptions = with types; submodule { listenerOptions = with lib.types; submodule { options = { port = mkOption { port = lib.mkOption { type = port; description = '' Port to listen on. Must be set to 0 to listen on a unix domain socket. Loading @@ -263,7 +260,7 @@ let default = 1883; }; address = mkOption { address = lib.mkOption { type = nullOr str; description = '' Address to listen on. Listen on `0.0.0.0`/`::` Loading @@ -272,7 +269,7 @@ let default = null; }; authPlugins = mkOption { authPlugins = lib.mkOption { type = listOf authPluginOptions; description = '' Authentication plugin to attach to this listener. Loading @@ -282,7 +279,7 @@ let default = []; }; users = mkOption { users = lib.mkOption { type = attrsOf userOptions; example = { john = { password = "123456"; acl = [ "readwrite john/#" ]; }; }; description = '' Loading @@ -291,7 +288,7 @@ let default = {}; }; omitPasswordAuth = mkOption { omitPasswordAuth = lib.mkOption { type = bool; description = '' Omits password checking, allowing anyone to log in with any user name unless Loading @@ -300,7 +297,7 @@ let default = false; }; acl = mkOption { acl = lib.mkOption { type = listOf str; description = '' Additional ACL items to prepend to the generated ACL file. Loading @@ -309,7 +306,7 @@ let default = []; }; settings = mkOption { settings = lib.mkOption { type = submodule { freeformType = attrsOf optionType; }; Loading @@ -324,7 +321,7 @@ let listenerAsserts = prefix: listener: assertKeysValid "${prefix}.settings" freeformListenerKeys listener.settings ++ userAsserts prefix listener.users ++ imap0 ++ lib.imap0 (i: v: authAsserts "${prefix}.authPlugins.${toString i}" v) listener.authPlugins; Loading @@ -333,9 +330,9 @@ let "listener ${toString listener.port} ${toString listener.address}" "acl_file /etc/mosquitto/acl-${toString idx}.conf" ] ++ optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}" ++ lib.optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}" ++ formatFreeform {} listener.settings ++ concatMap formatAuthPlugin listener.authPlugins; ++ lib.concatMap formatAuthPlugin listener.authPlugins; freeformBridgeKeys = { bridge_alpn = 1; Loading Loading @@ -373,19 +370,19 @@ let try_private = 1; }; bridgeOptions = with types; submodule { bridgeOptions = with lib.types; submodule { options = { addresses = mkOption { addresses = lib.mkOption { type = listOf (submodule { options = { address = mkOption { address = lib.mkOption { type = str; description = '' Address of the remote MQTT broker. ''; }; port = mkOption { port = lib.mkOption { type = port; description = '' Port of the remote MQTT broker. Loading @@ -400,7 +397,7 @@ let ''; }; topics = mkOption { topics = lib.mkOption { type = listOf str; description = '' Topic patterns to be shared between the two brokers. Loading @@ -411,7 +408,7 @@ let example = [ "# both 2 local/topic/ remote/topic/" ]; }; settings = mkOption { settings = lib.mkOption { type = submodule { freeformType = attrsOf optionType; }; Loading @@ -426,14 +423,14 @@ let bridgeAsserts = prefix: bridge: assertKeysValid "${prefix}.settings" freeformBridgeKeys bridge.settings ++ [ { assertion = length bridge.addresses > 0; assertion = lib.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}" "addresses ${lib.concatMapStringsSep " " (a: "${a.address}:${toString a.port}") bridge.addresses}" ] ++ map (t: "topic ${t}") bridge.topics ++ formatFreeform {} bridge.settings; Loading Loading @@ -468,12 +465,12 @@ let websockets_log_level = 1; }; globalOptions = with types; { enable = mkEnableOption "the MQTT Mosquitto broker"; globalOptions = with lib.types; { enable = lib.mkEnableOption "the MQTT Mosquitto broker"; package = mkPackageOption pkgs "mosquitto" { }; package = lib.mkPackageOption pkgs "mosquitto" { }; bridges = mkOption { bridges = lib.mkOption { type = attrsOf bridgeOptions; default = {}; description = '' Loading @@ -481,7 +478,7 @@ let ''; }; listeners = mkOption { listeners = lib.mkOption { type = listOf listenerOptions; default = []; description = '' Loading @@ -489,7 +486,7 @@ let ''; }; includeDirs = mkOption { includeDirs = lib.mkOption { type = listOf path; description = '' Directories to be scanned for further config files to include. Loading @@ -500,7 +497,7 @@ let default = []; }; logDest = mkOption { logDest = lib.mkOption { type = listOf (either path (enum [ "stdout" "stderr" "syslog" "topic" "dlt" ])); description = '' Destinations to send log messages to. Loading @@ -508,7 +505,7 @@ let default = [ "stderr" ]; }; logType = mkOption { logType = lib.mkOption { type = listOf (enum [ "debug" "error" "warning" "notice" "information" "subscribe" "unsubscribe" "websockets" "none" "all" ]); description = '' Loading @@ -517,7 +514,7 @@ let default = []; }; persistence = mkOption { persistence = lib.mkOption { type = bool; description = '' Enable persistent storage of subscriptions and messages. Loading @@ -525,15 +522,15 @@ let default = true; }; dataDir = mkOption { dataDir = lib.mkOption { default = "/var/lib/mosquitto"; type = types.path; type = lib.types.path; description = '' The data directory. ''; }; settings = mkOption { settings = lib.mkOption { type = submodule { freeformType = attrsOf optionType; }; Loading @@ -545,10 +542,10 @@ let }; globalAsserts = prefix: cfg: flatten [ lib.flatten [ (assertKeysValid "${prefix}.settings" freeformGlobalKeys cfg.settings) (imap0 (n: l: listenerAsserts "${prefix}.listener.${toString n}" l) cfg.listeners) (mapAttrsToList (n: b: bridgeAsserts "${prefix}.bridge.${n}" b) cfg.bridges) (lib.imap0 (n: l: listenerAsserts "${prefix}.listener.${toString n}" l) cfg.listeners) (lib.mapAttrsToList (n: b: bridgeAsserts "${prefix}.bridge.${n}" b) cfg.bridges) ]; formatGlobal = cfg: Loading @@ -561,12 +558,12 @@ let cfg.logDest ++ map (t: "log_type ${t}") cfg.logType ++ formatFreeform {} cfg.settings ++ concatLists (imap0 formatListener cfg.listeners) ++ concatLists (mapAttrsToList formatBridge cfg.bridges) ++ lib.concatLists (lib.imap0 formatListener cfg.listeners) ++ lib.concatLists (lib.mapAttrsToList formatBridge cfg.bridges) ++ map (d: "include_dir ${d}") cfg.includeDirs; configFile = pkgs.writeText "mosquitto.conf" (concatStringsSep "\n" (formatGlobal cfg)); (lib.concatStringsSep "\n" (formatGlobal cfg)); in Loading @@ -578,7 +575,7 @@ in ###### Implementation config = mkIf cfg.enable { config = lib.mkIf cfg.enable { assertions = globalAsserts "services.mosquitto" cfg; Loading Loading @@ -633,13 +630,13 @@ in ReadWritePaths = [ cfg.dataDir "/tmp" # mosquitto_passwd creates files in /tmp before moving them ] ++ filter path.check cfg.logDest; ] ++ lib.filter path.check cfg.logDest; ReadOnlyPaths = map (p: "${p}") (cfg.includeDirs ++ filter ++ lib.filter (v: v != null) (flatten [ (lib.flatten [ (map (l: [ (l.settings.psk_file or null) Loading @@ -652,7 +649,7 @@ in (l.settings.keyfile or null) ]) cfg.listeners) (mapAttrsToList (lib.mapAttrsToList (_: b: [ (b.settings.bridge_cafile or null) (b.settings.bridge_capath or null) Loading Loading @@ -680,26 +677,26 @@ in UMask = "0077"; }; preStart = concatStringsSep lib.concatStringsSep "\n" (imap0 (lib.imap0 (idx: listener: makePasswordFile (listenerScope idx) listener.users "${cfg.dataDir}/passwd-${toString idx}") cfg.listeners); }; environment.etc = listToAttrs ( imap0 environment.etc = lib.listToAttrs ( lib.imap0 (idx: listener: { name = "mosquitto/acl-${toString idx}.conf"; value = { user = config.users.users.mosquitto.name; group = config.users.users.mosquitto.group; mode = "0400"; text = (concatStringsSep text = (lib.concatStringsSep "\n" (flatten [ (lib.flatten [ listener.acl (mapAttrsToList (lib.mapAttrsToList (n: u: [ "user ${n}" ] ++ map (t: "topic ${t}") u.acl) listener.users) ])); Loading Loading
nixos/modules/services/networking/mosquitto.nix +91 −94 Original line number Diff line number Diff line { config, lib, pkgs, ...}: with lib; let cfg = config.services.mosquitto; # 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]*" // { str = lib.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 ] // { path = lib.types.addCheck lib.types.path (p: str.check "${p}"); configKey = lib.types.strMatching "[^\r\n\t ]+"; optionType = with lib.types; oneOf [ str path bool int ] // { description = "string, path, bool, or integer"; }; optionToString = v: if isBool v then boolToString v if lib.isBool v then lib.boolToString v else if path.check v then "${v}" else toString v; assertKeysValid = prefix: valid: config: mapAttrsToList lib.mapAttrsToList (n: _: { assertion = valid ? ${n}; message = "Invalid config key ${prefix}.${n}."; }) config; formatFreeform = { prefix ? "" }: mapAttrsToList (n: v: "${prefix}${n} ${optionToString v}"); formatFreeform = { prefix ? "" }: lib.mapAttrsToList (n: v: "${prefix}${n} ${optionToString v}"); userOptions = with types; submodule { userOptions = with lib.types; submodule { options = { password = mkOption { password = lib.mkOption { type = uniq (nullOr str); default = null; description = '' Loading @@ -41,7 +38,7 @@ let ''; }; passwordFile = mkOption { passwordFile = lib.mkOption { type = uniq (nullOr path); example = "/path/to/file"; default = null; Loading @@ -54,7 +51,7 @@ let ''; }; hashedPassword = mkOption { hashedPassword = lib.mkOption { type = uniq (nullOr str); default = null; description = '' Loading @@ -66,7 +63,7 @@ let ''; }; hashedPasswordFile = mkOption { hashedPasswordFile = lib.mkOption { type = uniq (nullOr path); example = "/path/to/file"; default = null; Loading @@ -82,7 +79,7 @@ let ''; }; acl = mkOption { acl = lib.mkOption { type = listOf str; example = [ "read A/B" "readwrite A/#" ]; default = []; Loading @@ -94,15 +91,15 @@ let }; userAsserts = prefix: users: mapAttrsToList lib.mapAttrsToList (n: _: { assertion = builtins.match "[^:\r\n]+" n != null; message = "Invalid user name ${n} in ${prefix}"; }) users ++ mapAttrsToList ++ lib.mapAttrsToList (n: u: { assertion = count (s: s != null) [ assertion = lib.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}"; Loading @@ -112,26 +109,26 @@ let userScope = prefix: index: "${prefix}-user-${toString index}"; credentialID = prefix: credential: "${prefix}-${credential}"; toScopedUsers = listenerScope: users: pipe users [ attrNames (imap0 (index: user: nameValuePair user toScopedUsers = listenerScope: users: lib.pipe users [ lib.attrNames (lib.imap0 (index: user: lib.nameValuePair user (users.${user} // { scope = userScope listenerScope index; }) )) listToAttrs lib.listToAttrs ]; userCredentials = user: credentials: pipe credentials [ (filter (credential: user.${credential} != null)) userCredentials = user: credentials: lib.pipe credentials [ (lib.filter (credential: user.${credential} != null)) (map (credential: "${credentialID user.scope credential}:${user.${credential}}")) ]; usersCredentials = listenerScope: users: credentials: pipe users [ usersCredentials = listenerScope: users: credentials: lib.pipe users [ (toScopedUsers listenerScope) (mapAttrsToList (_: user: userCredentials user credentials)) concatLists (lib.mapAttrsToList (_: user: userCredentials user credentials)) lib.concatLists ]; systemdCredentials = listeners: listenerCredentials: pipe listeners [ (imap0 (index: listener: listenerCredentials (listenerScope index) listener)) concatLists systemdCredentials = listeners: listenerCredentials: lib.pipe listeners [ (lib.imap0 (index: listener: listenerCredentials (listenerScope index) listener)) lib.concatLists ]; makePasswordFile = listenerScope: users: path: Loading @@ -139,12 +136,12 @@ let makeLines = store: file: let scopedUsers = toScopedUsers listenerScope users; in mapAttrsToList (name: user: ''addLine ${escapeShellArg name} "''$(systemd-creds cat ${credentialID user.scope store})"'') (filterAttrs (_: user: user.${store} != null) scopedUsers) ++ mapAttrsToList (name: user: ''addFile ${escapeShellArg name} "''${CREDENTIALS_DIRECTORY}/${credentialID user.scope file}"'') (filterAttrs (_: user: user.${file} != null) scopedUsers); lib.mapAttrsToList (name: user: ''addLine ${lib.escapeShellArg name} "''$(systemd-creds cat ${credentialID user.scope store})"'') (lib.filterAttrs (_: user: user.${store} != null) scopedUsers) ++ lib.mapAttrsToList (name: user: ''addFile ${lib.escapeShellArg name} "''${CREDENTIALS_DIRECTORY}/${credentialID user.scope file}"'') (lib.filterAttrs (_: user: user.${file} != null) scopedUsers); plainLines = makeLines "password" "passwordFile"; hashedLines = makeLines "hashedPassword" "hashedPasswordFile"; in Loading @@ -154,7 +151,7 @@ let set -eu file=${escapeShellArg path} file=${lib.escapeShellArg path} rm -f "$file" touch "$file" Loading @@ -170,23 +167,23 @@ let echo "$1:$(cat "$2")" >> "$file" } '' + concatStringsSep "\n" + lib.concatStringsSep "\n" (plainLines ++ optional (plainLines != []) '' ++ lib.optional (plainLines != []) '' ${cfg.package}/bin/mosquitto_passwd -U "$file" '' ++ hashedLines)); authPluginOptions = with types; submodule { authPluginOptions = with lib.types; submodule { options = { plugin = mkOption { plugin = lib.mkOption { type = path; description = '' Plugin path to load, should be a `.so` file. ''; }; denySpecialChars = mkOption { denySpecialChars = lib.mkOption { type = bool; description = '' Automatically disallow all clients using `#` Loading @@ -195,7 +192,7 @@ let default = true; }; options = mkOption { options = lib.mkOption { type = attrsOf optionType; description = '' Options for the auth plugin. Each key turns into a `auth_opt_*` Loading @@ -207,7 +204,7 @@ let }; authAsserts = prefix: auth: mapAttrsToList lib.mapAttrsToList (n: _: { assertion = configKey.check n; message = "Invalid auth plugin key ${prefix}.${n}"; Loading Loading @@ -253,9 +250,9 @@ let use_username_as_clientid = 1; }; listenerOptions = with types; submodule { listenerOptions = with lib.types; submodule { options = { port = mkOption { port = lib.mkOption { type = port; description = '' Port to listen on. Must be set to 0 to listen on a unix domain socket. Loading @@ -263,7 +260,7 @@ let default = 1883; }; address = mkOption { address = lib.mkOption { type = nullOr str; description = '' Address to listen on. Listen on `0.0.0.0`/`::` Loading @@ -272,7 +269,7 @@ let default = null; }; authPlugins = mkOption { authPlugins = lib.mkOption { type = listOf authPluginOptions; description = '' Authentication plugin to attach to this listener. Loading @@ -282,7 +279,7 @@ let default = []; }; users = mkOption { users = lib.mkOption { type = attrsOf userOptions; example = { john = { password = "123456"; acl = [ "readwrite john/#" ]; }; }; description = '' Loading @@ -291,7 +288,7 @@ let default = {}; }; omitPasswordAuth = mkOption { omitPasswordAuth = lib.mkOption { type = bool; description = '' Omits password checking, allowing anyone to log in with any user name unless Loading @@ -300,7 +297,7 @@ let default = false; }; acl = mkOption { acl = lib.mkOption { type = listOf str; description = '' Additional ACL items to prepend to the generated ACL file. Loading @@ -309,7 +306,7 @@ let default = []; }; settings = mkOption { settings = lib.mkOption { type = submodule { freeformType = attrsOf optionType; }; Loading @@ -324,7 +321,7 @@ let listenerAsserts = prefix: listener: assertKeysValid "${prefix}.settings" freeformListenerKeys listener.settings ++ userAsserts prefix listener.users ++ imap0 ++ lib.imap0 (i: v: authAsserts "${prefix}.authPlugins.${toString i}" v) listener.authPlugins; Loading @@ -333,9 +330,9 @@ let "listener ${toString listener.port} ${toString listener.address}" "acl_file /etc/mosquitto/acl-${toString idx}.conf" ] ++ optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}" ++ lib.optional (! listener.omitPasswordAuth) "password_file ${cfg.dataDir}/passwd-${toString idx}" ++ formatFreeform {} listener.settings ++ concatMap formatAuthPlugin listener.authPlugins; ++ lib.concatMap formatAuthPlugin listener.authPlugins; freeformBridgeKeys = { bridge_alpn = 1; Loading Loading @@ -373,19 +370,19 @@ let try_private = 1; }; bridgeOptions = with types; submodule { bridgeOptions = with lib.types; submodule { options = { addresses = mkOption { addresses = lib.mkOption { type = listOf (submodule { options = { address = mkOption { address = lib.mkOption { type = str; description = '' Address of the remote MQTT broker. ''; }; port = mkOption { port = lib.mkOption { type = port; description = '' Port of the remote MQTT broker. Loading @@ -400,7 +397,7 @@ let ''; }; topics = mkOption { topics = lib.mkOption { type = listOf str; description = '' Topic patterns to be shared between the two brokers. Loading @@ -411,7 +408,7 @@ let example = [ "# both 2 local/topic/ remote/topic/" ]; }; settings = mkOption { settings = lib.mkOption { type = submodule { freeformType = attrsOf optionType; }; Loading @@ -426,14 +423,14 @@ let bridgeAsserts = prefix: bridge: assertKeysValid "${prefix}.settings" freeformBridgeKeys bridge.settings ++ [ { assertion = length bridge.addresses > 0; assertion = lib.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}" "addresses ${lib.concatMapStringsSep " " (a: "${a.address}:${toString a.port}") bridge.addresses}" ] ++ map (t: "topic ${t}") bridge.topics ++ formatFreeform {} bridge.settings; Loading Loading @@ -468,12 +465,12 @@ let websockets_log_level = 1; }; globalOptions = with types; { enable = mkEnableOption "the MQTT Mosquitto broker"; globalOptions = with lib.types; { enable = lib.mkEnableOption "the MQTT Mosquitto broker"; package = mkPackageOption pkgs "mosquitto" { }; package = lib.mkPackageOption pkgs "mosquitto" { }; bridges = mkOption { bridges = lib.mkOption { type = attrsOf bridgeOptions; default = {}; description = '' Loading @@ -481,7 +478,7 @@ let ''; }; listeners = mkOption { listeners = lib.mkOption { type = listOf listenerOptions; default = []; description = '' Loading @@ -489,7 +486,7 @@ let ''; }; includeDirs = mkOption { includeDirs = lib.mkOption { type = listOf path; description = '' Directories to be scanned for further config files to include. Loading @@ -500,7 +497,7 @@ let default = []; }; logDest = mkOption { logDest = lib.mkOption { type = listOf (either path (enum [ "stdout" "stderr" "syslog" "topic" "dlt" ])); description = '' Destinations to send log messages to. Loading @@ -508,7 +505,7 @@ let default = [ "stderr" ]; }; logType = mkOption { logType = lib.mkOption { type = listOf (enum [ "debug" "error" "warning" "notice" "information" "subscribe" "unsubscribe" "websockets" "none" "all" ]); description = '' Loading @@ -517,7 +514,7 @@ let default = []; }; persistence = mkOption { persistence = lib.mkOption { type = bool; description = '' Enable persistent storage of subscriptions and messages. Loading @@ -525,15 +522,15 @@ let default = true; }; dataDir = mkOption { dataDir = lib.mkOption { default = "/var/lib/mosquitto"; type = types.path; type = lib.types.path; description = '' The data directory. ''; }; settings = mkOption { settings = lib.mkOption { type = submodule { freeformType = attrsOf optionType; }; Loading @@ -545,10 +542,10 @@ let }; globalAsserts = prefix: cfg: flatten [ lib.flatten [ (assertKeysValid "${prefix}.settings" freeformGlobalKeys cfg.settings) (imap0 (n: l: listenerAsserts "${prefix}.listener.${toString n}" l) cfg.listeners) (mapAttrsToList (n: b: bridgeAsserts "${prefix}.bridge.${n}" b) cfg.bridges) (lib.imap0 (n: l: listenerAsserts "${prefix}.listener.${toString n}" l) cfg.listeners) (lib.mapAttrsToList (n: b: bridgeAsserts "${prefix}.bridge.${n}" b) cfg.bridges) ]; formatGlobal = cfg: Loading @@ -561,12 +558,12 @@ let cfg.logDest ++ map (t: "log_type ${t}") cfg.logType ++ formatFreeform {} cfg.settings ++ concatLists (imap0 formatListener cfg.listeners) ++ concatLists (mapAttrsToList formatBridge cfg.bridges) ++ lib.concatLists (lib.imap0 formatListener cfg.listeners) ++ lib.concatLists (lib.mapAttrsToList formatBridge cfg.bridges) ++ map (d: "include_dir ${d}") cfg.includeDirs; configFile = pkgs.writeText "mosquitto.conf" (concatStringsSep "\n" (formatGlobal cfg)); (lib.concatStringsSep "\n" (formatGlobal cfg)); in Loading @@ -578,7 +575,7 @@ in ###### Implementation config = mkIf cfg.enable { config = lib.mkIf cfg.enable { assertions = globalAsserts "services.mosquitto" cfg; Loading Loading @@ -633,13 +630,13 @@ in ReadWritePaths = [ cfg.dataDir "/tmp" # mosquitto_passwd creates files in /tmp before moving them ] ++ filter path.check cfg.logDest; ] ++ lib.filter path.check cfg.logDest; ReadOnlyPaths = map (p: "${p}") (cfg.includeDirs ++ filter ++ lib.filter (v: v != null) (flatten [ (lib.flatten [ (map (l: [ (l.settings.psk_file or null) Loading @@ -652,7 +649,7 @@ in (l.settings.keyfile or null) ]) cfg.listeners) (mapAttrsToList (lib.mapAttrsToList (_: b: [ (b.settings.bridge_cafile or null) (b.settings.bridge_capath or null) Loading Loading @@ -680,26 +677,26 @@ in UMask = "0077"; }; preStart = concatStringsSep lib.concatStringsSep "\n" (imap0 (lib.imap0 (idx: listener: makePasswordFile (listenerScope idx) listener.users "${cfg.dataDir}/passwd-${toString idx}") cfg.listeners); }; environment.etc = listToAttrs ( imap0 environment.etc = lib.listToAttrs ( lib.imap0 (idx: listener: { name = "mosquitto/acl-${toString idx}.conf"; value = { user = config.users.users.mosquitto.name; group = config.users.users.mosquitto.group; mode = "0400"; text = (concatStringsSep text = (lib.concatStringsSep "\n" (flatten [ (lib.flatten [ listener.acl (mapAttrsToList (lib.mapAttrsToList (n: u: [ "user ${n}" ] ++ map (t: "topic ${t}") u.acl) listener.users) ])); Loading