{ lib }: let # Per-model config. Adding a new hardware model: # 1. Add an entry here (PBX-relevant fields only) # 2. Add a provisioning template in ./provisioning/templates/.nix # exporting: desktopSize, thumbnailSize, mkConfig, mkDialplan models = { "cisco-8961" = { endpointTemplate = "endpoint-cisco-8961"; maxContacts = 1; hasProvisioning = true; hasIntercom = true; # auto-answer speakerphone line hasMultiLine = true; # separate L2 line for family/shared DID }; "sip-client" = { endpointTemplate = "endpoint-generic"; maxContacts = 1; hasProvisioning = false; hasIntercom = false; hasMultiLine = false; }; }; # Shared option set for a physical phone device. # isPersonPhone = true → no extension/displayName fields (inherited from person) # isPersonPhone = false → includes extension and displayName phoneDeviceOptions = isPersonPhone: { model = lib.mkOption { type = lib.types.enum (lib.attrNames models); description = "Phone model. Use \"sip-client\" for software SIP clients (no provisioning file)."; }; label = lib.mkOption { type = lib.types.str; default = ""; description = "Label shown on the phone screen (max ~12 chars for Cisco). Required for provisioned hardware phones."; }; password = lib.mkOption { type = lib.types.str; description = "SIP registration password."; }; } // lib.optionalAttrs (!isPersonPhone) { extension = lib.mkOption { type = lib.types.str; description = "Internal extension number for this shared phone."; }; displayName = lib.mkOption { type = lib.types.str; default = ""; description = "Name shown in the phone directory."; }; }; # Unified view of all physical devices, keyed by SIP identity (MAC or username). # Each entry carries the fields needed by sub-modules without them having to # know about sharedPhones vs persons. mkAllPhones = cfg: lib.mapAttrs (key: p: { inherit (p) model label password; extension = p.extension; displayName = p.displayName; personKey = null; mailboxExt = null; # shared phones have no personal mailbox }) cfg.sharedPhones // lib.foldlAttrs (acc: personKey: person: acc // lib.mapAttrs (_key: ph: { inherit (ph) model label password; extension = person.extension; displayName = person.displayName; personKey = personKey; mailboxExt = if person.mailbox then person.extension else null; }) person.phones ) {} cfg.persons; # Auto-generate intercom extensions for provisioned phones. mkIntercomEntries = cfg: allPhones: if cfg.intercomPrefix == null then [] else lib.concatLists (lib.mapAttrsToList (key: phone: let m = models.${phone.model}; in lib.optional m.hasIntercom { extension = "${cfg.intercomPrefix}${phone.extension}"; endpoint = "${key}-intercom"; phoneKey = key; target = phone.extension; displayName = "Intercom ${phone.displayName}"; password = phone.password; endpointTemplate = m.endpointTemplate; maxContacts = m.maxContacts; } ) allPhones); in { inherit models phoneDeviceOptions mkAllPhones mkIntercomEntries; }