diff --git a/flake.nix b/flake.nix index 13b63d7..2ccc30c 100644 --- a/flake.nix +++ b/flake.nix @@ -100,7 +100,28 @@ pythonImportsCheck = [ "diffsync" ]; }; + yate = pkgs.yate.overrideAttrs (old: { + configureFlags = [ "--with-libpq=${pkgs.postgresql.withPackages (ps: [ ])}" ]; + }); + }; + nixosModules = { + fieldpoc = { ... }: { + imports = [ + ./nix/modules/dhcp.nix + ./nix/modules/fieldpoc.nix + ./nix/modules/yate.nix + ]; + + nixpkgs.overlays = [ + (_: _: { + inherit (self.packages."x86_64-linux") + fieldpoc + yate; + }) + ]; + }; + default = self.nixosModules.fieldpoc; }; hydraJobs = { diff --git a/nix/modules/dhcp.nix b/nix/modules/dhcp.nix new file mode 100644 index 0000000..22c7674 --- /dev/null +++ b/nix/modules/dhcp.nix @@ -0,0 +1,141 @@ +{ config, pkgs, lib, ... }: + +with lib; + +let + cfg = config.services.fieldpoc.dhcp; +in { + options.services.fieldpoc.dhcp = { + enable = mkEnableOption "fieldpoc-dhcp"; + interface = mkOption { + type = types.str; + }; + subnet = mkOption { + type = types.str; + }; + pool = mkOption { + type = types.str; + }; + router = mkOption { + type = types.str; + }; + dnsServers = mkOption { + type = types.str; + }; + omm = mkOption { + type = types.str; + }; + reservations = mkOption { + type = with types; listOf (submodule { + options = { + name = mkOption { + type = types.str; + }; + macAddress = mkOption { + type = types.str; + }; + ipAddress = mkOption { + type = types.str; + }; + }; + }); + default = []; + }; + }; + + config = mkIf cfg.enable { + services.kea.dhcp4 = { + enable = true; + settings = { + interfaces-config = { + interfaces = [ cfg.interface ]; + }; + option-def = [ + { + space = "dhcp4"; + name = "vendor-encapsulated-options"; + code = 43; + type = "empty"; + encapsulate = "sipdect"; + } + { + space = "sipdect"; + name = "ommip1"; + code = 10; + type = "ipv4-address"; + } + { + space = "sipdect"; + name = "ommip2"; + code = 19; + type = "ipv4-address"; + } + { + space = "sipdect"; + name = "syslogip"; + code = 14; + type = "ipv4-address"; + } + { + space = "sipdect"; + name = "syslogport"; + code = 15; + type = "int16"; + } + { + space = "dhcp4"; + name = "magic_str"; + code = 224; + type = "string"; + } + ]; + + subnet4 = [ + { + subnet = cfg.subnet; + pools = [ + { + pool = cfg.pool; + } + ]; + option-data = [ + { + name = "routers"; + data = cfg.router; + } + { + name = "domain-name-servers"; + data = cfg.dnsServers; + } + { + name = "vendor-encapsulated-options"; + } + { + space = "sipdect"; + name = "ommip1"; + data = cfg.omm; + } + { + name = "magic_str"; + data = "OpenMobilitySIP-DECT"; + } + ]; + + reservations = map (r: { + hostname = r.name; + hw-address = r.macAddress; + ip-address = r.ipAddress; + option-data = [ + { + name = "host-name"; + data = r.name; + } + ]; + }) cfg.reservations; + } + ]; + }; + }; + }; +} + diff --git a/nix/modules/fieldpoc.nix b/nix/modules/fieldpoc.nix new file mode 100644 index 0000000..58cfbd4 --- /dev/null +++ b/nix/modules/fieldpoc.nix @@ -0,0 +1,156 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.fieldpoc; +in { + options = { + services.fieldpoc = { + enable = mkEnableOption "fieldpoc"; + ommIp = mkOption { + type = types.str; + }; + ommUser = mkOption { + type = types.str; + }; + ommPasswordPath = mkOption { + type = types.path; + }; + sipsecretPath = mkOption { + type = types.path; + }; + }; + }; + + config = mkIf cfg.enable { + environment.systemPackages = with pkgs; [ + fieldpoc + ]; + + systemd.services.fieldpoc = { + description = "Fieldpoc daemon"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" "yate.service" ]; + + serviceConfig = { + Type = "simple"; + ExecStart = "${pkgs.fieldpoc}/bin/fieldpoc -c /run/fieldpoc/config.json --debug"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + User = "fieldpoc"; + Group = "fieldpoc"; + RuntimeDirectory = "fieldpoc"; + RuntimeDirectoryMode = "0755"; + ConfigurationDirectory = "fieldpoc"; + StateDirectory = "fieldpoc"; + StateDirectoryMode = "0700"; + }; + + preStart = let + cfgFile = pkgs.writeText "config.json" (lib.generators.toJSON { } { + extensions = { + file = "/var/lib/fieldpoc/extensions.json"; + }; + controller = { + host = "127.0.0.1"; + port = 9437; + }; + dect = { + host = cfg.ommIp; + username = cfg.ommUser; + password = "!!OMMPASSWORD!!"; + sipsecret = "!!SIPSECRET!!"; + }; + yate = { + host = "127.0.0.1"; + port = 5039; + }; + database = { + hostname = "127.0.0.1"; + username = "fieldpoc"; + password = "fieldpoc"; + database = "fieldpoc"; + }; + }); + in '' + ${pkgs.gnused}/bin/sed -e "s/!!OMMPASSWORD!!/$(cat ${cfg.ommPasswordPath})/g" -e "s/!!SIPSECRET!!/$(cat ${cfg.sipsecretPath})/g" ${cfgFile} > /run/fieldpoc/config.json + if [ ! -f "/var/lib/fieldpoc/extensions.json" ]; then + echo '{"extensions": {}}' > /var/lib/fieldpoc/extensions.json + fi + ''; + }; + + users.users.fieldpoc = { + isSystemUser = true; + group = "fieldpoc"; + }; + + users.groups.fieldpoc = { }; + + services.postgresql = { + enable = true; + initialScript = pkgs.writeText "backend-initScript" '' + CREATE ROLE fieldpoc WITH LOGIN PASSWORD 'fieldpoc' CREATEDB; + CREATE DATABASE fieldpoc; + GRANT ALL PRIVILEGES ON DATABASE fieldpoc TO fieldpoc; + ''; + }; + + services.yate = { + enable = true; + config = { + extmodule = { + "listener ywsd" = { + type = "tcp"; + addr = "127.0.0.1"; + port = "5039"; + }; + }; + cdrbuild = { + parameters = { + X-Eventphone-Id = "false"; + }; + }; + pgsqldb = { + default = { + host = "localhost"; + port = "5432"; + database = "fieldpoc"; + user = "fieldpoc"; + password = "fieldpoc"; + }; + }; + register = { + general = { + expires = 30; + "user.auth" = "yes"; + "user.register" = "yes"; + "user.unregister" = "yes"; + "engine.timer" = "yes"; + "call.cdr" = "yes"; + "linetracker" = "yes"; + }; + default = { + priority = 30; + account = "default"; + }; + "user.auth" = { + query = "SELECT password FROM users WHERE username='\${username}' AND password IS NOT NULL AND password<>'' AND type='user' LIMIT 1;"; + result = "password"; + }; + "user.register".query = "INSERT INTO registrations (username, location, oconnection_id, expires) VALUES ('\${username}', '\${data}', '\${oconnection_id}', NOW() + INTERVAL '\${expires} s') ON CONFLICT ON CONSTRAINT uniq_registrations DO UPDATE SET expires = NOW() + INTERVAL '\${expires} s'"; + "user.unregister".query = "DELETE FROM registrations WHERE (username = '\${username}' AND location = '\${data}' AND oconnection_id = '\${connection_id}') OR ('\${username}' = '' AND '\${data}' = '' AND oconnection_id = '\${connection_id}')"; + "engine.timer".query = "DELETE FROM registrations WHERE expires<=CURRENT_TIMESTAMP;"; + "call.cdr".critial = "no"; + "linetracker" = { + critical = "yes"; + initquery = "UPDATE users SET inuse=0 WHERE inuse is not NULL;DELETE from active_calls;"; + cdr_initialize = "UPDATE users SET inuse=inuse+1 WHERE username='\${external}';INSERT INTO active_calls SELECT username, x_eventphone_id FROM (SELECT '\${external}' as username, '\${X-Eventphone-Id}' as x_eventphone_id, '\${direction}' as direction) as active_call WHERE x_eventphone_id != '' AND x_eventphone_id IS NOT NULL and direction = 'outgoing';"; + cdr_finalize = "UPDATE users SET inuse=(CASE WHEN inuse>0 THEN inuse-1 ELSE 0 END) WHERE username='\${external}';DELETE FROM active_calls WHERE username = '\${external}' AND x_eventphone_id = '\${X-Eventphone-Id}' AND '\${direction}' = 'outgoing';"; + }; + }; + }; + }; + }; +} + diff --git a/nix/modules/yate.nix b/nix/modules/yate.nix new file mode 100644 index 0000000..3084a6b --- /dev/null +++ b/nix/modules/yate.nix @@ -0,0 +1,56 @@ + +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.yate; +in { + options = { + services.yate = { + enable = mkEnableOption "yate"; + config = mkOption { + type = with types; attrsOf anything; + default = { }; + }; + }; + }; + config = mkIf cfg.enable { + environment.etc = mapAttrs' (name: config: nameValuePair "yate/${name}.conf" { text = (if (isAttrs config) then generators.toINI {} config else config); }) cfg.config; + + systemd.services.yate = { + description = "YATE Telephony Server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network-online.target" "postgresql.service" ]; + + environment = { PWLIB_ASSERT_ACTION = "C"; }; + + serviceConfig = { + Type = "forking"; + ExecStart = + "${pkgs.yate}/bin/yate -d -p /run/yate/yate.pid -c /etc/yate -F -s -vvv -DF -r -l /var/lib/yate/yate.log"; + ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID"; + User = "yate"; + Group = "yate"; + AmbientCapabilities = "CAP_NET_BIND_SERVICE"; + RuntimeDirectory = "yate"; + RuntimeDirectoryMode = "0755"; + ConfigurationDirectory = "yate"; + StateDirectory = "yate"; + StateDirectoryMode = "0700"; + PIDFile = "/run/yate/yate.pid"; + TimeoutSec = 30; + }; + + reloadTriggers = map (name: config.environment.etc."yate/${name}.conf".source) (attrNames cfg.config); + }; + + users.users.yate = { + isSystemUser = true; + group = "yate"; + }; + + users.groups.yate = { }; + }; +} +