{ 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; }); }; }; }