330 lines
13 KiB
Nix
330 lines
13 KiB
Nix
{ lib, ... }:
|
|
|
|
let
|
|
phones = import ./phones.nix { inherit lib; };
|
|
in {
|
|
options.services.voip = {
|
|
enable = lib.mkEnableOption "VoIP provisioning (Asterisk + TFTP)";
|
|
|
|
serverAddress = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "IP address or hostname of this server (used in phone configs and SIP).";
|
|
};
|
|
|
|
ntpServer = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "NTP server for phones. Defaults to serverAddress.";
|
|
default = "";
|
|
};
|
|
|
|
sipPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 5060;
|
|
};
|
|
|
|
rtpStart = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 10000;
|
|
};
|
|
|
|
rtpEnd = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 20000;
|
|
};
|
|
|
|
directoryName = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "tel.baubs.net";
|
|
description = "Name shown in the phone directory title.";
|
|
};
|
|
|
|
directoryPort = lib.mkOption {
|
|
type = lib.types.port;
|
|
default = 8080;
|
|
description = "HTTP port for the phone directory and services.";
|
|
};
|
|
|
|
backgroundImages = lib.mkOption {
|
|
default = {};
|
|
description = ''
|
|
Attrset of background images keyed by display name.
|
|
Value is a path to any image file — it will be resized automatically
|
|
to the correct dimensions for each phone model during build.
|
|
For best results, use a 4:3 aspect ratio source image.
|
|
'';
|
|
type = lib.types.attrsOf lib.types.path;
|
|
};
|
|
|
|
sharedPhones = lib.mkOption {
|
|
default = {};
|
|
description = ''
|
|
Shared/location phones not assigned to a specific person (e.g. hallway, kitchen).
|
|
These have their own extension but no personal voicemail mailbox.
|
|
For cisco-8961, the key must be the lowercase MAC address (no colons).
|
|
For sip-client, the key is a free-form username.
|
|
'';
|
|
type = lib.types.attrsOf (lib.types.submodule {
|
|
options = phones.phoneDeviceOptions false;
|
|
});
|
|
};
|
|
|
|
persons = lib.mkOption {
|
|
default = {};
|
|
description = "People with personal extensions, optional voicemail mailboxes, and their own phones.";
|
|
type = lib.types.attrsOf (lib.types.submodule {
|
|
options = {
|
|
extension = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Personal extension number.";
|
|
};
|
|
displayName = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "Name shown in the directory and on caller ID.";
|
|
};
|
|
mailbox = lib.mkOption {
|
|
type = lib.types.bool;
|
|
default = true;
|
|
description = "Whether this person gets a personal voicemail mailbox.";
|
|
};
|
|
ringTimeout = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 30;
|
|
description = "Seconds to ring before going to voicemail (or hanging up if no mailbox).";
|
|
};
|
|
mailboxGreeting = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Audio file played to callers before the voicemail beep. Transcoded to multiple formats at build time. null = Asterisk's default announcement.";
|
|
};
|
|
phones = lib.mkOption {
|
|
default = {};
|
|
description = ''
|
|
Phones belonging to this person, keyed by SIP identity.
|
|
For cisco-8961, the key must be the lowercase MAC address (no colons).
|
|
For sip-client, the key is a free-form username.
|
|
'';
|
|
type = lib.types.attrsOf (lib.types.submodule {
|
|
options = phones.phoneDeviceOptions true;
|
|
});
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
intercomPrefix = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Dial prefix for auto-generated intercom extensions. e.g. \"*80\" generates *80100 for ext 100. Only intercom-capable (provisioned) phones get entries.";
|
|
};
|
|
|
|
mohClasses = lib.mkOption {
|
|
default = {};
|
|
description = "Music on hold classes. Files are transcoded to ulaw at build time.";
|
|
type = lib.types.attrsOf (lib.types.submodule {
|
|
options = {
|
|
files = lib.mkOption {
|
|
type = lib.types.listOf lib.types.path;
|
|
description = "Audio files to play (MP3, WAV, etc). Transcoded to 8kHz ulaw automatically.";
|
|
};
|
|
sort = lib.mkOption {
|
|
type = lib.types.enum [ "random" "alphabetical" ];
|
|
default = "random";
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
sipTrunks = lib.mkOption {
|
|
default = {};
|
|
description = "External SIP provider trunks, keyed by a short name (e.g. \"provider-a\").";
|
|
type = lib.types.attrsOf (lib.types.submodule {
|
|
options = {
|
|
host = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "SIP provider hostname or IP address. Use hostFile to read from a file.";
|
|
};
|
|
hostFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "File containing the SIP provider hostname. Takes precedence over host.";
|
|
};
|
|
username = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "SIP account username. Use usernameFile to read from a file.";
|
|
};
|
|
usernameFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "File containing the SIP account username. Takes precedence over username.";
|
|
};
|
|
password = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "SIP account password. Use passwordFile to read from a file.";
|
|
};
|
|
passwordFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "File containing the SIP account password. Takes precedence over password.";
|
|
};
|
|
transport = lib.mkOption {
|
|
type = lib.types.enum [ "udp" "tcp" ];
|
|
default = "udp";
|
|
};
|
|
callerId = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "Outbound caller ID number presented to the provider. Leave empty to use provider default. Use callerIdFile to read from a file.";
|
|
};
|
|
callerIdFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "File containing the outbound caller ID. Takes precedence over callerId.";
|
|
};
|
|
codecs = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [ "alaw" "ulaw" ];
|
|
description = "Codec preference list for this trunk, ordered highest priority first. Most providers only support G.711.";
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
sharedMailbox = lib.mkOption {
|
|
default = null;
|
|
description = "Shared voicemail mailbox accessible by all phones (family answering machine).";
|
|
type = lib.types.nullOr (lib.types.submodule {
|
|
options = {
|
|
mailboxId = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "200";
|
|
description = "Numeric mailbox ID used internally in voicemail.conf and VoiceMail(). Must be numeric.";
|
|
};
|
|
checkExtension = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "*98";
|
|
description = "Extension dialled to check this shared mailbox directly (via VoiceMailMain).";
|
|
};
|
|
displayName = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "Shared";
|
|
description = "Name shown in voicemail configuration.";
|
|
};
|
|
greeting = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "Audio file played to callers before the voicemail beep. Transcoded to multiple formats at build time. null = Asterisk's default announcement.";
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
dids = lib.mkOption {
|
|
default = {};
|
|
description = "Inbound DID routing. Each DID must reference a key from sipTrunks.";
|
|
type = lib.types.attrsOf (lib.types.submodule {
|
|
options = {
|
|
number = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "DID number in E.164 format (e.g. \"+4912345678\"). Use numberFile to read from a file.";
|
|
};
|
|
numberFile = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.path;
|
|
default = null;
|
|
description = "File containing the DID number. Takes precedence over number.";
|
|
};
|
|
trunk = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Key of the sipTrunks entry this DID arrives on.";
|
|
};
|
|
displayName = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "Human-readable label for this DID (informational only).";
|
|
};
|
|
routing = lib.mkOption {
|
|
description = "How inbound calls on this DID are distributed to phones.";
|
|
type = lib.types.submodule {
|
|
options = {
|
|
type = lib.mkOption {
|
|
type = lib.types.enum [ "all" "person" "persons" ];
|
|
description = ''
|
|
all — ring all phones (sharedPhones + all persons) on their L2 line
|
|
person — ring a single person on their L1 line
|
|
persons — ring a list of persons on their L2 line
|
|
'';
|
|
};
|
|
person = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
description = "Person key for routing.type = \"person\".";
|
|
};
|
|
persons = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
default = [];
|
|
description = "Person keys for routing.type = \"persons\".";
|
|
};
|
|
timeout = lib.mkOption {
|
|
type = lib.types.ints.positive;
|
|
default = 30;
|
|
description = "Seconds to ring before going to voicemail (or hanging up).";
|
|
};
|
|
};
|
|
};
|
|
};
|
|
mailbox = lib.mkOption {
|
|
type = lib.types.enum [ "shared" "person" "none" ];
|
|
default = "shared";
|
|
description = ''
|
|
shared — go to sharedMailbox on no answer (requires sharedMailbox to be set)
|
|
person — go to the routed person's mailbox on no answer (only valid with routing.type = "person")
|
|
none — hang up on no answer
|
|
'';
|
|
};
|
|
musicOnHold = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = ''
|
|
MOH class name to play to the caller while phones ring, instead of ringback.
|
|
Must match a key in mohClasses. null = standard ringback.
|
|
'';
|
|
};
|
|
};
|
|
});
|
|
};
|
|
|
|
extensions = lib.mkOption {
|
|
default = {};
|
|
description = ''
|
|
Extra extensions: page groups and custom app entries.
|
|
Line extensions are auto-generated from sharedPhones and persons — do not declare them here.
|
|
'';
|
|
type = lib.types.attrsOf (lib.types.submodule {
|
|
options = {
|
|
mode = lib.mkOption {
|
|
type = lib.types.enum [ "page" "app" ];
|
|
default = "page";
|
|
description = ''
|
|
Extension mode:
|
|
- "page": one-way announcement to all phones
|
|
- "app": custom Asterisk dialplan application
|
|
'';
|
|
};
|
|
displayName = lib.mkOption {
|
|
type = lib.types.str;
|
|
default = "";
|
|
};
|
|
app = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.str;
|
|
default = null;
|
|
description = "Verbatim Asterisk dialplan app. Required for mode = \"app\".";
|
|
};
|
|
};
|
|
});
|
|
};
|
|
};
|
|
}
|