119 lines
5.1 KiB
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; }];
|
|
};
|
|
};
|
|
}
|