nixos-config/lib/fudo/jabber.nix

195 lines
4.7 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
hostname = config.instance.hostname;
siteOpts = { ... }: with types; {
options = {
enableACME = mkOption {
type = bool;
description = "Use ACME to get SSL certificates for this site.";
default = true;
};
site-config = mkOption {
type = attrs;
description = "Site-specific configuration.";
};
};
};
site-copy = site: "ejabberd-${site}";
concatMapAttrs = f: attrs:
foldr (a: b: a // b) {} (mapAttrs f attrs);
concatMapAttrsToList = f: attr:
attrValues (concatMapAttrs f attr);
host-domains = config.fudo.acme.host-domains.${hostname};
siteCerts = site: let
certPath = config.fudo.acme.local-copies.${site-copy site}.path;
in [
"${certPath}/fullchain.pem"
"${certPath}/privkey.pem"
"${certPath}/chain.pem"
];
siteCertService = site:
config.fudo.acme.local-copies.${site-copy site}.service;
config-file-template = let
jabber-config = {
loglevel = cfg.log-level;
access_rules = {
c2s = { allow = "all"; };
announce = { allow = "admin"; };
configure = { allow = "admin"; };
pubsub_createnode = { allow = "local"; };
};
acl = {
admin = {
user = concatMap
(admin: map (site: "${admin}@${site}")
(attrNames cfg.sites))
cfg.admins;
};
};
hosts = attrNames cfg.sites;
listen = [{
port = cfg.port;
module = "ejabberd_c2s";
ip = cfg.listen-ip;
starttls = true;
starttls_required = true;
}];
certfiles = concatMapAttrsToList
(site: siteOpts:
if (siteOpts.enableACME) then
(siteCerts site)
else [])
cfg.sites;
host_config =
mapAttrs (site: siteOpts: siteOpts.site-config)
cfg.sites;
};
config-file = builtins.toJSON jabber-config;
in pkgs.writeText "ejabberd.config.yml.template" config-file;
enter-secrets = template: secrets: target: let
secret-readers = concatStringsSep "\n"
(mapAttrsToList
(secret: file: "${secret}=$(cat ${file})")
secrets);
secret-swappers = map
(secret: "sed s/${secret}/\$${secret}/g")
(attrNames secrets);
swapper = concatStringsSep " | " secret-swappers;
in pkgs.writeShellScript "ejabberd-generate-config.sh" ''
cat ${template} | ${swapper} > ${target}
chown ${cfg.user}:${cfg.group} ${target}
'';
cfg = config.fudo.jabber;
in {
options.fudo.jabber = with types; {
enable = mkEnableOption "Enable ejabberd server.";
port = mkOption {
type = port;
description = "Port on which to listen for Jabber connections.";
default = 5222;
};
user = mkOption {
type = str;
description = "User as which to run the ejabberd server.";
default = "ejabberd";
};
group = mkOption {
type = str;
description = "Group as which to run the ejabberd server.";
default = "ejabberd";
};
admins = mkOption {
type = str;
description = "List of admin users for the server.";
default = [];
};
sites = mkOption {
type = attrsOf (submodule siteOpts);
description = "List of sites on which to listen for Jabber connections.";
};
secret-files = mkOption {
type = attrsOf str;
description = "Map of secret-name to file. File contents will be subbed for the name in the config.";
default = {};
};
config-file = mkOption {
type = str;
description = "Location at which to generate the configuration file.";
default = "/var/run/ejabberd/ejabberd.yaml";
};
};
config = mkIf cfg.enable {
users = {
users.${cfg.user} = {
isSystemUser = true;
};
groups.${cfg.group} = {
members = [ cfg.user ];
};
};
fudo.acme.local-copies = mapAttrs' (site: siteCfg:
nameValuePair (site-copy site)
mkif siteCfg.enableACME {
domain = site;
user = cfg.user;
group = cfg.group;
}) cfg.sites;
systemd = {
tmpfiles.rules = [
"D '${dirOf cfg.config-file}' 0550 ${cfg.user} ${cfg.group} - -"
];
services.ejabberd = let
config-generator = enter-secrets config-file-template cfg.secret-files cfg.config-file;
in {
wants = map (site: siteCertService site) (attrNames cfg.sites);
environment = cfg.secret-files;
serviceConfig = {
ExecStartPre = mkAfter "${config-generator}";
};
};
};
services.ejabberd = {
enable = true;
user = cfg.user;
group = cfg.group;
configFile = cfg.config-file;
};
};
}