mirror of
https://github.com/NixOS/nixpkgs.git
synced 2025-02-17 01:24:47 +00:00
nixos/services.mosquitto: remove with lib;
This commit is contained in:
parent
36eaab4720
commit
481df957a6
@ -1,39 +1,36 @@
|
||||
{ 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 = ''
|
||||
@ -41,7 +38,7 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
passwordFile = mkOption {
|
||||
passwordFile = lib.mkOption {
|
||||
type = uniq (nullOr path);
|
||||
example = "/path/to/file";
|
||||
default = null;
|
||||
@ -54,7 +51,7 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
hashedPassword = mkOption {
|
||||
hashedPassword = lib.mkOption {
|
||||
type = uniq (nullOr str);
|
||||
default = null;
|
||||
description = ''
|
||||
@ -66,7 +63,7 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
hashedPasswordFile = mkOption {
|
||||
hashedPasswordFile = lib.mkOption {
|
||||
type = uniq (nullOr path);
|
||||
example = "/path/to/file";
|
||||
default = null;
|
||||
@ -82,7 +79,7 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
acl = mkOption {
|
||||
acl = lib.mkOption {
|
||||
type = listOf str;
|
||||
example = [ "read A/B" "readwrite A/#" ];
|
||||
default = [];
|
||||
@ -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}";
|
||||
@ -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:
|
||||
@ -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
|
||||
@ -154,7 +151,7 @@ let
|
||||
|
||||
set -eu
|
||||
|
||||
file=${escapeShellArg path}
|
||||
file=${lib.escapeShellArg path}
|
||||
|
||||
rm -f "$file"
|
||||
touch "$file"
|
||||
@ -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 `#`
|
||||
@ -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_*`
|
||||
@ -207,7 +204,7 @@ let
|
||||
};
|
||||
|
||||
authAsserts = prefix: auth:
|
||||
mapAttrsToList
|
||||
lib.mapAttrsToList
|
||||
(n: _: {
|
||||
assertion = configKey.check n;
|
||||
message = "Invalid auth plugin key ${prefix}.${n}";
|
||||
@ -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.
|
||||
@ -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`/`::`
|
||||
@ -272,7 +269,7 @@ let
|
||||
default = null;
|
||||
};
|
||||
|
||||
authPlugins = mkOption {
|
||||
authPlugins = lib.mkOption {
|
||||
type = listOf authPluginOptions;
|
||||
description = ''
|
||||
Authentication plugin to attach to this listener.
|
||||
@ -282,7 +279,7 @@ let
|
||||
default = [];
|
||||
};
|
||||
|
||||
users = mkOption {
|
||||
users = lib.mkOption {
|
||||
type = attrsOf userOptions;
|
||||
example = { john = { password = "123456"; acl = [ "readwrite john/#" ]; }; };
|
||||
description = ''
|
||||
@ -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
|
||||
@ -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.
|
||||
@ -309,7 +306,7 @@ let
|
||||
default = [];
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
settings = lib.mkOption {
|
||||
type = submodule {
|
||||
freeformType = attrsOf optionType;
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
@ -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.
|
||||
@ -400,7 +397,7 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
topics = mkOption {
|
||||
topics = lib.mkOption {
|
||||
type = listOf str;
|
||||
description = ''
|
||||
Topic patterns to be shared between the two brokers.
|
||||
@ -411,7 +408,7 @@ let
|
||||
example = [ "# both 2 local/topic/ remote/topic/" ];
|
||||
};
|
||||
|
||||
settings = mkOption {
|
||||
settings = lib.mkOption {
|
||||
type = submodule {
|
||||
freeformType = attrsOf optionType;
|
||||
};
|
||||
@ -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;
|
||||
@ -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 = ''
|
||||
@ -481,7 +478,7 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
listeners = mkOption {
|
||||
listeners = lib.mkOption {
|
||||
type = listOf listenerOptions;
|
||||
default = [];
|
||||
description = ''
|
||||
@ -489,7 +486,7 @@ let
|
||||
'';
|
||||
};
|
||||
|
||||
includeDirs = mkOption {
|
||||
includeDirs = lib.mkOption {
|
||||
type = listOf path;
|
||||
description = ''
|
||||
Directories to be scanned for further config files to include.
|
||||
@ -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.
|
||||
@ -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 = ''
|
||||
@ -517,7 +514,7 @@ let
|
||||
default = [];
|
||||
};
|
||||
|
||||
persistence = mkOption {
|
||||
persistence = lib.mkOption {
|
||||
type = bool;
|
||||
description = ''
|
||||
Enable persistent storage of subscriptions and messages.
|
||||
@ -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;
|
||||
};
|
||||
@ -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:
|
||||
@ -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
|
||||
|
||||
@ -578,7 +575,7 @@ in
|
||||
|
||||
###### Implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
||||
assertions = globalAsserts "services.mosquitto" cfg;
|
||||
|
||||
@ -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)
|
||||
@ -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)
|
||||
@ -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…
Reference in New Issue
Block a user