nix/modules/voip/default.nix

119 lines
5.1 KiB
Nix

{ lib, pkgs, config, ... }:
let
cfg = config.services.voip;
phones = import ./phones.nix { inherit lib; };
inherit (phones) models;
allPhones = phones.mkAllPhones cfg;
intercomEntries = phones.mkIntercomEntries cfg allPhones;
mohDirs = import ./asterisk/moh.nix { inherit lib pkgs cfg; };
greetingDirs = import ./asterisk/greetings.nix { inherit lib pkgs cfg; };
confFiles = import ./asterisk/default.nix { inherit lib cfg models allPhones intercomEntries mohDirs greetingDirs; };
directory = import ./provisioning/directory.nix { inherit lib pkgs cfg allPhones intercomEntries; };
directoryJson = import ./provisioning/directory-json.nix { inherit lib pkgs cfg models allPhones; };
provisioningRoot = import ./provisioning/default.nix { inherit lib pkgs cfg models allPhones; };
diagram = import ./dashboard.nix { inherit lib pkgs cfg models allPhones intercomEntries; };
# True when any *File option is set — Asterisk's execincludes=yes is required in that case.
hasRuntimeSecrets =
lib.any (t: t.hostFile != null || t.usernameFile != null || t.passwordFile != null || t.callerIdFile != null)
(lib.attrValues cfg.sipTrunks)
|| lib.any (d: d.numberFile != null) (lib.attrValues cfg.dids);
# Nginx Lua handler: reads the static HTML template and substitutes every
# @@/path/to/keyfile@@ marker with the file's first line at request time.
luaPageHandler = pkgs.writeText "voip-page.lua" ''
local f = assert(io.open("${diagram.webRoot}/index.html", "rb"))
local html = f:read("*a")
f:close()
-- Placeholders embed the full key file path: @@/var/lib/voip-keys/name@@
html = html:gsub("@@([^@]+)@@", function(path)
local kf = io.open(path, "r")
if not kf then return "<em>(not yet uploaded)</em>" end
local val = kf:read("*l")
kf:close()
return val or ""
end)
ngx.header.content_type = "text/html; charset=utf-8"
ngx.print(html)
'';
in {
imports = [ ./options.nix ];
config = lib.mkIf cfg.enable {
services.voip.ntpServer = lib.mkDefault cfg.serverAddress;
assertions = import ./assertions.nix { inherit lib cfg models allPhones; };
services.asterisk = {
enable = true;
confFiles = confFiles;
# execincludes=yes is required when any *File option is in use.
extraConfig = lib.optionalString hasRuntimeSecrets ''
[options]
execincludes=yes
'';
};
services.atftpd = {
enable = true;
root = "${provisioningRoot}";
extraOptions = [ "--verbose=7" ];
};
services.nginx = {
enable = true;
# OpenResty bundles nginx + LuaJIT + resty.core and all required libraries.
# Needed for request-time secret substitution in the status page.
package = lib.mkIf hasRuntimeSecrets (lib.mkDefault pkgs.openresty);
# Cisco phones fetch provisioning files (SEP*.cnf.xml, dialplan-*.xml,
# backgrounds) over TFTP (primary) and HTTP port 6970 (fallback).
# Both serve the same Nix-built provisioning root.
virtualHosts."voip-provisioning" = {
listen = [{ addr = "0.0.0.0"; port = 6970; }];
locations."/" = {
root = "${provisioningRoot}";
extraConfig = "autoindex off;";
};
};
virtualHosts."voip-directory" = {
listen = [{ addr = "0.0.0.0"; port = cfg.directoryPort; }];
locations = {
"= /directory.xml" = { alias = "${directory.menuFile}"; extraConfig = "default_type text/xml;"; };
"= /directory-list.xml" = { alias = "${directory.listFile}"; extraConfig = "default_type text/xml;"; };
"= /intercom.xml" = { alias = "${directory.intercomFile}"; extraConfig = "default_type text/xml;"; };
"= /contacts.json" = { alias = "${directoryJson}"; extraConfig = ''default_type application/json; add_header Access-Control-Allow-Origin "*";''; };
"/" = {
root = "${diagram.webRoot}";
extraConfig = lib.optionalString (!hasRuntimeSecrets) "index index.html;";
};
} // lib.optionalAttrs hasRuntimeSecrets {
# Exact-match the index so the Lua handler intercepts it before the
# prefix location /. Other assets (voip.dot, SVG) fall through to /.
"= /" = { extraConfig = "default_type text/html;\ncontent_by_lua_file ${luaPageHandler};"; };
"= /index.html" = { extraConfig = "default_type text/html;\ncontent_by_lua_file ${luaPageHandler};"; };
};
};
};
# voip-keys group: both asterisk (#exec reads) and nginx (Lua reads) need access.
# Key files must be deployed with group = "voip-keys" and permissions = "0640".
users.groups.voip-keys = {};
users.users.asterisk.extraGroups = [ "voip-keys" ];
users.users.nginx.extraGroups = [ "voip-keys" ];
systemd.tmpfiles.rules = [ "d /var/log/nginx 0750 nginx nginx -" ];
networking.firewall = {
allowedTCPPorts = [ cfg.sipPort cfg.directoryPort 6970 ];
allowedUDPPorts = [ cfg.sipPort 69 ];
allowedUDPPortRanges = [{ from = cfg.rtpStart; to = cfg.rtpEnd; }];
};
};
}