From d2e7fe827fa81e22abdb1153e810f4e81aa79eea Mon Sep 17 00:00:00 2001 From: Jan-Henrik Bruhn Date: Fri, 17 Apr 2026 23:14:49 +0200 Subject: [PATCH] feat: add cisco-9971 support --- hosts/telefonmann/default.nix | 1 + modules/voip/default.nix | 1 + modules/voip/phones.nix | 8 + .../provisioning/templates/cisco-8961.nix | 4 +- .../provisioning/templates/cisco-9971.nix | 285 ++++++++++++++++++ 5 files changed, 297 insertions(+), 2 deletions(-) create mode 100644 modules/voip/provisioning/templates/cisco-9971.nix diff --git a/hosts/telefonmann/default.nix b/hosts/telefonmann/default.nix index 338d6cc..ea7132a 100644 --- a/hosts/telefonmann/default.nix +++ b/hosts/telefonmann/default.nix @@ -61,6 +61,7 @@ in { mailboxGreeting = ./greetings/anrufbeantworter.wav; phones = { "jannel-mobile" = { model = "sip-client"; password = "changeme101"; }; + "f47f35a3fb72" = { model = "cisco-9971"; label = "Jannelfon"; password = "changeme101"; }; }; }; }; diff --git a/modules/voip/default.nix b/modules/voip/default.nix index d63e13d..5502160 100644 --- a/modules/voip/default.nix +++ b/modules/voip/default.nix @@ -89,6 +89,7 @@ in { "= /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 "*";''; }; + "= /auth" = { extraConfig = ''default_type text/plain; return 200 "AUTHORIZED";''; }; "/" = { root = "${diagram.webRoot}"; extraConfig = lib.optionalString (!hasRuntimeSecrets) "index index.html;"; diff --git a/modules/voip/phones.nix b/modules/voip/phones.nix index 33be46e..5ae0080 100644 --- a/modules/voip/phones.nix +++ b/modules/voip/phones.nix @@ -14,6 +14,14 @@ let hasMultiLine = true; # separate L2 line for family/shared DID codecs = [ "g722" "alaw" "ulaw" "ilbc" ]; }; + "cisco-9971" = { + endpointTemplate = "endpoint-cisco-8961"; + maxContacts = 1; + hasProvisioning = true; + hasIntercom = true; + hasMultiLine = true; + codecs = [ "g722" "alaw" "ulaw" "ilbc" ]; + }; "sip-client" = { endpointTemplate = "endpoint-generic"; maxContacts = 1; diff --git a/modules/voip/provisioning/templates/cisco-8961.nix b/modules/voip/provisioning/templates/cisco-8961.nix index ebb2a5a..29d2cf0 100644 --- a/modules/voip/provisioning/templates/cisco-8961.nix +++ b/modules/voip/provisioning/templates/cisco-8961.nix @@ -195,7 +195,7 @@ in { true 2 - sip8961.9-4-2ES-14 + 0 1 @@ -213,7 +213,7 @@ in { utf-8 1 - + http://${serverAddress}:${toString directoryPort}/auth http://${serverAddress}:${toString directoryPort}/directory.xml diff --git a/modules/voip/provisioning/templates/cisco-9971.nix b/modules/voip/provisioning/templates/cisco-9971.nix new file mode 100644 index 0000000..99f0915 --- /dev/null +++ b/modules/voip/provisioning/templates/cisco-9971.nix @@ -0,0 +1,285 @@ +{ lib }: + +let + cisco = import ./cisco-base.nix { inherit lib; }; +in { + desktopSize = "640x480x24"; + thumbnailSize = "123x111"; + + # Return a list of { name, content } provisioning files for this phone. + # provisioning/default.nix wraps each with pkgs.writeText for the linkFarm. + mkFiles = + { mac, label, displayName, password, serverAddress, ntpServer + , sipPort ? 5060 + , directoryPort ? 8080 + , familyLineEnabled ? false + , familyLineLabel ? "Familie" + , familyLinePassword ? "" + , intercomEnabled ? false + , intercomPassword ? "" + , intercomLineIndex ? 2 + , allExtensions ? [] + , allStarExtensions ? [] + , hasTrunk ? false + , hasIntercomButton ? false + , pageExtension ? null + , blfPersons ? [] + }: + let + # Line button assignments: + # button 1 / lineIndex 1 — personal/location L1 line (always present) + # button 2 / lineIndex 2 — family L2 line (when familyLineEnabled) + # button N / lineIndex N — intercom (when intercomEnabled; N = 2 or 3) + intercomButton = if familyLineEnabled then 3 else 2; + firstBlfButton = 1 + (if familyLineEnabled then 1 else 0) + (if intercomEnabled then 1 else 0) + 1; + + dialplanFile = cisco.dialplanFilename mac; + + configXml = '' + + + SIP + admin + password + + + D.M.YA + Central Europe Standard/Daylight Time + + + ${ntpServer} + + + + + + + + + ${toString sipPort} + + ${serverAddress} + + + + + + + + + 5060 + + + + + true + + + true + x-serviceuri-cfwdall + x-cisco-serviceuri-pickup + x-cisco-serviceuri-opickup + x-cisco-serviceuri-gpickup + x-cisco-serviceuri-meetme + x-cisco-serviceuri-abbrdial + 2 + 2 + 2 + 0 + true + + + 6 + 10 + 180 + 3600 + 5 + 120 + 120 + 5 + 500 + 4000 + 70 + false + None + + false + 3 + ${(builtins.substring 0 12 label)} + 1 + false + + + 9 + ${displayName} + USECALLMANAGER + ${toString sipPort} + ${mac} + ${displayName} + + 2 + + 3 + ${mac} + ${password} + 1 + *97 + ${mac} + + true + true + true + true + + + ${if familyLineEnabled then '' + + 9 + ${familyLineLabel} + USECALLMANAGER + ${toString sipPort} + ${mac}-l2 + ${familyLineLabel} + + 2 + + 3 + ${mac}-l2 + ${familyLinePassword} + 3 + *97 + ${mac}-l2 + + true + true + true + true + + + '' else ""}${if intercomEnabled then '' + + 23 + Intercom + USECALLMANAGER + ${toString sipPort} + ${mac}-intercom + Intercom + + 3 + Auto Answer with Speakerphone + + 3 + ${mac}-intercom + ${intercomPassword} + 1 + 1 + ${mac}-intercom + + '' else ""}${lib.concatImapStrings (i: p: '' + + 21 + ${p.displayName} + 1 + ${p.extension} + + '') blfPersons} + ${toString sipPort} + 16348 + 20134 + 184 + ${dialplanFile} + + 1 + + + true + 2 + + + + 0 + 1 + 0 + 1 + 1,2,3,4,5,6,7 + 0 + 00:00 + 00:00 + 00:05 + 1 + + + en_US + utf-8 + + 1 + http://${serverAddress}:${toString directoryPort}/auth + http://${serverAddress}:${toString directoryPort}/directory.xml + + + + + + 2 + + Missed Calls + Application:Cisco/MissedCalls + + + + + Received Calls + Application:Cisco/ReceivedCalls + + + + + Placed Calls + Application:Cisco/PlacedCalls + + + + + Voicemail + Application:Cisco/Voicemail + + + + + + 1 + 0 + + + 3804 + + + false + + ''; + + # Dial template: the phone tests patterns top-to-bottom and dials as soon + # as digits match a pattern with timeout="0", or after the timeout for + # timeout > 0. Explicit patterns must come before the catch-all. + h = builtins.hashString "sha256" (builtins.toJSON { + inherit mac allExtensions allStarExtensions hasTrunk hasIntercomButton intercomLineIndex blfPersons; + }); + versionStamp = "${builtins.substring 0 8 h}-${builtins.substring 8 4 h}-${builtins.substring 12 4 h}-${builtins.substring 16 4 h}-${builtins.substring 20 12 h}"; + extMatch = ext: "