nixos-config/lib/fudo/jabber.nix

237 lines
5.8 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.";
};
};
};
concatMapAttrs = f: attrs:
foldr (a: b: a // b) {} (mapAttrs f attrs);
concatMapAttrsToList = f: attr:
concatMap (i: i) (attrValues (mapAttrs f attr));
host-domains = config.fudo.acme.host-domains.${hostname};
siteCerts = site: let
cert-copy = host-domains.${site}.local-copies.ejabberd;
in [
cert-copy.certificate
cert-copy.private-key
cert-copy.chain
];
siteCertService = site:
host-domains.${site}.local-copies.ejabberd.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 = map (ip: {
port = cfg.port;
module = "ejabberd_c2s";
ip = ip;
starttls = true;
starttls_required = true;
}) cfg.listen-ips;
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}
'';
cfg = config.fudo.jabber;
in {
options.fudo.jabber = with types; {
enable = mkEnableOption "Enable ejabberd server.";
listen-ips = mkOption {
type = listOf str;
description = "IPs on which to listen for Jabber connections.";
};
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 = listOf 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 = "/run/ejabberd/ejabberd.yaml";
};
log-level = mkOption {
type = int;
description = ''
Log level at which to run the server.
See: https://docs.ejabberd.im/admin/guide/troubleshooting/
'';
default = 3;
};
environment = mkOption {
type = attrsOf str;
description = "Environment variables to set for the ejabberd daemon.";
default = {};
};
};
config = mkIf cfg.enable {
users = {
users.${cfg.user} = {
isSystemUser = true;
};
groups.${cfg.group} = {
members = [ cfg.user ];
};
};
fudo = {
acme.host-domains.${hostname} = mapAttrs (site: siteCfg:
mkIf siteCfg.enableACME {
local-copies.ejabberd = {
user = cfg.user;
group = cfg.group;
};
}) cfg.sites;
system = let
config-dir = dirOf cfg.config-file;
in {
ensure-directories.${config-dir} = {
user = cfg.user;
perms = "0700";
};
services.ejabberd-config-generator = let
config-generator =
enter-secrets config-file-template cfg.secret-files cfg.config-file;
in {
script = "${config-generator}";
readWritePaths = [ config-dir ];
workingDirectory = config-dir;
user = cfg.user;
description = "Generate ejabberd config file with necessary passwords.";
postStart = ''
chown ${cfg.user} ${cfg.config-file}
chmod 0400 ${cfg.config-file}
'';
};
};
};
systemd = {
tmpfiles.rules = [
"D '${dirOf cfg.config-file}' 0550 ${cfg.user} ${cfg.group} - -"
];
services = {
ejabberd = {
wants = map (site: siteCertService site) (attrNames cfg.sites);
requires = [ "ejabberd-config-generator.service" ];
environment = cfg.environment;
};
};
};
services.ejabberd = {
enable = true;
user = cfg.user;
group = cfg.group;
configFile = cfg.config-file;
};
};
}