nixos-config/config/service/mqtt.nix

165 lines
4.6 KiB
Nix
Raw Normal View History

2023-05-16 22:40:08 -07:00
{ config, lib, pkgs, ... }@toplevel:
with lib;
let
cfg = config.fudo.services.mqtt;
hostname = config.instance.hostname;
isMqttServer = cfg.host == hostname;
aclOption = with types;
mkOption {
type = listOf str;
description = "Topic filter to which this user has access.";
example = [ "some/topic/#" "other/specific/topic" ];
};
userOpts = { name, ... }: {
options = with types; {
username = mkOption {
type = str;
default = name;
};
password-file = mkOption {
type = str;
description =
"Path to file (on the BUILD HOST) containing the user's password.";
};
acl = aclOption;
};
};
mosquittoUser = config.systemd.services.mosquitto.serviceConfig.User;
pwTarget = type: username: "/run/mqtt/${type}-${username}.passwd";
mqttDomain = config.fudo.hosts."${cfg.host}".domain;
in {
options.fudo.services.mqtt = with types; {
enable = mkEnableOption "Enable MQTT server.";
host = mkOption {
type = str;
description =
"Hostname of the MQTT server for this site/domain/whatever.";
};
listen-address = mkOption {
type = str;
description = "IP address on which to listen.";
default = "0.0.0.0";
};
private = {
enable = mkOption {
type = bool;
description = "Enable a private (authenticated) MQTT server.";
default = true;
};
port = mkOption {
type = port;
description = "Port at which to listen for incoming MQTT requests.";
default = 1883;
};
users = mkOption {
type = attrsOf (submodule userOpts);
default = { };
};
};
public = {
enable = mkEnableOption "Enable a public (anonymous) MQTT server.";
port = mkOption {
type = port;
description = "Port at which to listen for incoming MQTT requests.";
default = 1884;
};
users = mkOption {
type = attrsOf (submodule userOpts);
default = { };
};
acl = aclOption;
};
state-directory = mkOption {
type = str;
description = "Directory where server can store persistent state.";
};
mqtt-hostname = let
mqtt-host = toplevel.config.fudo.services.mqtt.host;
mqtt-domain = toplevel.config.fudo.hosts."${mqtt-host}".domain;
in mkOption {
type = str;
description = "Hostname at which the MQTT server can be reached.";
default = "mqtt.${mqtt-domain}";
};
};
config = mkIf cfg.enable {
networking.firewall.allowedTCPPorts =
(optional cfg.private.enable cfg.private.port)
++ (optional cfg.public.enable cfg.public.port);
systemd = {
services.mosquitto = {
after = [ config.fudo.secrets.secret-target ];
restartIfChanged = true;
};
tmpfiles.rules = optional isMqttServer
"d ${cfg.state-directory} 0700 ${mosquittoUser} - - -";
};
fudo = {
zones."${mqttDomain}".aliases.mqtt = cfg.host;
secrets.host-secrets."${hostname}" = mkIf isMqttServer (let
publicUsers = mapAttrs' (_: userOpts:
nameValuePair "mqtt-public-user-${userOpts.username}" {
source-file = userOpts.password-file;
target-file = pwTarget "public" userOpts.username;
user = mosquittoUser;
}) cfg.public.users;
privateUsers = mapAttrs' (_: userOpts:
nameValuePair "mqtt-private-user-${userOpts.username}" {
source-file = userOpts.password-file;
target-file = pwTarget "private" userOpts.username;
user = mosquittoUser;
}) cfg.private.users;
in publicUsers // privateUsers);
};
services.mosquitto = mkIf isMqttServer {
enable = true;
dataDir = cfg.state-directory;
listeners = (optional cfg.private.enable {
settings.allow_anonymous = false;
port = cfg.private.port;
address = cfg.listen-address;
users = mapAttrs' (_: userOpts:
nameValuePair userOpts.username {
acl = userOpts.acl;
passwordFile = pwTarget "private" userOpts.username;
}) cfg.private.users;
}) ++ (optional cfg.public.enable {
settings.allow_anonymous = true;
acl = map (line: "topic ${line}") cfg.public.acl;
port = cfg.public.port;
address = cfg.listen-address;
users = mapAttrs' (_: userOpts:
nameValuePair userOpts.username {
acl = userOpts.acl;
passwordFile = pwTarget "public" userOpts.username;
}) cfg.public.users;
});
};
};
}