- Asterisk Phonebook — до пары сотен номеров, хранится в AstDB, номер должен совпасть символ в символ.
- CallerID Lookup Sources с источником MySQL — основной вариант для офиса: тысячи записей, своя таблица, кеш в AstDB.
- CID Superfecta — цепочка из нескольких источников, фильтр спама, уведомления. Модуль нужен версии 17.0.7 и новее, в старых есть RCE.
- func_odbc плюс модуль Set CallerID — полный контроль, экранирование SQL, минимум зависимостей от модулей.
Для 90% офисов хватает отдельной таблицы MariaDB, где номер приведён к 10 цифрам, и источника MySQL в CallerID Lookup Sources. Ниже — как это поднять за 30-40 минут.
Диагноз: поднял FreePBX, а на трубке только номер
CallerID FreePBX по умолчанию показывает ровно то, что прислал провайдер. В России это почти всегда голый номер: 79161234567 и ни одной буквы. Знакомо?
Звонит клиент. Трубку берёт новый менеджер. Он не знает, что это крупнейший заказчик, который третий день ждёт счёт. В CDR потом тоже одни цифры — отчёт по клиентам приходится сводить руками.
Секретарь помнила наизусть номера сорока ключевых клиентов. Секретарь ушла в декрет. Справочник ушёл вместе с ней.
Смотри, в чём корень. В США операторы передают CNAM — имя абонента прямо в сигнализации. У нас такой услуги по факту нет. Значит, имя должна подставить сама АТС из своей базы. FreePBX это умеет, причём четырьмя разными способами. И у каждого свои грабли.
Что получишь на выходе
- Понимание, какой вариант базы CallerID подходит под твой размер и задачи.
- Рабочую таблицу в MariaDB, которая находит номер в любом формате: +7, 8, 7 или 10 цифр.
- Кириллицу на трубках и в CDR без кракозябр.
- Импорт контактов из CSV, который можно повесить на таймер.
- Проверку, troubleshooting, бэкап, мониторинг и безопасное обновление.
- Готовые альтернативы: Phonebook, Superfecta, HTTP API и func_odbc.
- FAQ по типовым вопросам, которые задают в Яндексе.
Время: 30-40 минут на основной рецепт. Ещё 20 минут, если будешь ставить автосинхронизацию и мониторинг.
Что нужно: SSH-доступ к серверу FreePBX под root или через sudo, доступ в веб-интерфейс администратора, CSV с номерами и именами. Если CSV пока нет — сделаем тестовый на две строки.
Системные требования
| Компонент | Версия | Комментарий |
|---|---|---|
| ОС | Debian 12 Bookworm | Единственная официально поддерживаемая ОС для FreePBX 17 |
| FreePBX | 17.x | На 16.x рецепт тоже работает, отличия отмечены в тексте |
| Asterisk | 22 LTS по умолчанию, поддерживаются 20 и 21 | Начиная с 19 в Asterisk нет app_mysql |
| СУБД | MariaDB 10.11 из репозитория Debian 12 | Та же, что использует сам FreePBX |
| Модуль cidlookup | 15.0.25+ / 16.0.12+ / любой 17.x | С этих версий MySQL-запрос идёт через AGI, а не через app_mysql |
| Модуль superfecta | 16.0.40+ / 17.0.7+ | Только если выберешь Superfecta. Ниже этих версий — CVE-2026-54710 |
| Python | 3.11 из Debian 12 | Для скрипта импорта и HTTP API |
На момент публикации актуальна версия FreePBX 17 с Asterisk 22 LTS по умолчанию. Перед установкой проверь свежие релизы на странице FreePBX 17 в базе знаний Sangoma и в сообществе FreePBX.
Что будет в статье
- Причины, почему имя не появляется даже после настройки.
- Сравнение вариантов базы CallerID и их ограничения.
- Рецепт: таблица MariaDB, импорт, источник MySQL, привязка к входящему маршруту.
- Проверка и осложнения с командами.
- Альтернативы: Phonebook, Superfecta, HTTP, func_odbc.
- Профилактика, безопасность, бэкап, обновление.
- FAQ.
Причины: почему имя звонящего в FreePBX не появляется
Прежде чем строить базу, пойми, что ломается. Половина вопросов на форумах звучит как «настроил CallerID Lookup, не работает». А не работает по одной из семи причин ниже. Все они лечатся, но по-разному.
| Причина | Почему ломает |
|---|---|
| Провайдер передаёт только номер | CNAM в российской телефонии практически не встречается. Имени просто неоткуда взяться, пока его не подставит АТС. |
| Номер приходит в другом формате | В базе записано 8 916 123-45-67, провайдер шлёт 79161234567 или +79161234567. Точное сравнение не находит совпадения. |
| Источник не привязан к входящему маршруту | Lookup Source создан, но в Inbound Route на вкладке Other выбран None. FreePBX не делает запрос. |
| Кодировка latin1 по умолчанию | Поле Character Set в источнике пустое — используется latin1. Кириллица превращается в вопросики. |
| Устаревший кеш в AstDB | С включённым кешем имя, найденное однажды, возвращается всегда. Даже если в базе его давно поменяли. |
| Старый модуль cidlookup на новом Asterisk | Старые версии модуля ходили в MySQL через app_mysql. Его удалили в Asterisk 19, и запрос молча не выполняется. |
| Телефон не умеет показать имя | Старые трубки обрезают имя до 12-16 символов или не понимают UTF-8. В CDR имя есть, на экране нет. |
Вот тут важно. Причины 2 и 4 дают 80% проблем на российских АТС. Формат номера и кодировка. Поэтому рецепт ниже построен вокруг них: номер нормализуем в самой базе, кодировку задаём явно.
Про причину 6 отдельно. Это подробно разобрано в задаче FREEPBX-23206: модуль полагался на app_mysql, после его удаления в Asterisk 19 источники MySQL перестали работать. Позже запрос переписали на AGI-скрипт. Если у тебя FreePBX 17 с обновлёнными модулями — проблемы нет. Если сервер не обновлялся с 2021 года — она есть.
Варианты базы CallerID в FreePBX: сравнение и ограничения
Теперь по существу запроса. В FreePBX нет одной «базы CallerID». Есть несколько механизмов, которые решают задачу с разной глубиной. Выбирать нужно по объёму, по источнику данных и по тому, кто будет эту базу поддерживать.
| Вариант | Где хранит | Разумный объём | Формат номера | Главное ограничение |
|---|---|---|---|---|
| Asterisk Phonebook | AstDB, семейство cidname | до 200-300 записей | Только точное совпадение | Нет нормализации, неудобное массовое редактирование |
| CallerID Lookup: Internal | AstDB через Phonebook | как у Phonebook | Точное совпадение | По сути тот же Phonebook |
| CallerID Lookup: MySQL | Любая база MySQL/MariaDB | десятки тысяч и больше | Любая логика в SQL | Один запрос, одна колонка, кодировка по умолчанию latin1 |
| CallerID Lookup: HTTP/HTTPS | Твой веб-сервис | не ограничен | Решает сервис | Задержка сервиса добавляется к каждому входящему |
| CallerID Lookup: ENUM | DNS-зоны из enum.conf | — | E.164 | В России для имён почти не применяется |
| CallerID Lookup: OpenCNAM | Внешний сервис | — | Номера США | Для российских номеров бесполезен, бесплатный тариф 60 запросов в час |
| CID Superfecta | Много источников по цепочке | не ограничен | Правила и регулярки | Сложнее в настройке, история уязвимостей |
| func_odbc + Set CallerID | Любая база через ODBC | не ограничен | Любая логика в SQL | Ручные конфиги вне GUI |
Источники типов cidlookup подробно описаны в официальном руководстве по CallerID Lookup Sources. Там же предупреждение, которое все пропускают: поиск имени может замедлить АТС. Запрос выполняется синхронно, пока звонок висит на входящем маршруте.
Как выбрать без долгих раздумий — схема ниже.
%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
A["Нужна база имён звонящих"] --> B{"Больше 300 номеров?"}
B -->|Нет| C["Asterisk Phonebook"]
B -->|Да| D{"Данные уже в CRM с API?"}
D -->|Да| E["Lookup Source HTTP"]
D -->|Нет| F{"Нужны спам-фильтр и уведомления?"}
F -->|Да| G["CID Superfecta 17.0.7+"]
F -->|Нет| H{"Нужна полная гибкость SQL?"}
H -->|Да| I["func_odbc и Set CallerID"]
H -->|Нет| J["Lookup Source MySQL"]
style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style B fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
style F fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
style H fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
style C fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style E fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style G fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style I fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style J fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
Почему основным рецептом выбран MySQL-источник. Он штатный, настраивается в GUI, переживает обновления FreePBX и попадает в стандартный бэкап конфигурации. Таблицу при этом контролируешь ты: можешь наполнять её из CRM, 1С, Active Directory или руками. Superfecta мощнее, но для задачи «показать имя клиента» это танк для поездки в булочную.
Архитектура решения
Вот как пойдёт звонок после настройки. Провайдер присылает номер, входящий маршрут вызывает Lookup Source, тот проверяет кеш, потом идёт в MariaDB. Найденное имя записывается в CALLERID(name) — его видят телефоны и CDR.
%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#ffffff',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'lineColor': '#64748b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'flowchart': {'curve': 'linear', 'nodeSpacing': 50, 'rankSpacing': 50}
}}%%
flowchart TD
A["Входящий от провайдера"] --> B["Inbound Route по DID"]
B --> C["CID Lookup Source"]
C --> D{"Номер есть в кеше AstDB?"}
D -->|Да| E["Имя из кеша"]
D -->|Нет| F["AGI cidlookup_mysql"]
F --> G["MariaDB cid_directory"]
G --> H["CALLERID name"]
E --> H
H --> I["Ring Group или IVR"]
I --> J["Экран телефона"]
H --> K["CDR поле cnam"]
style A fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style J fill:#f8fafc,stroke:#3b82f6,stroke-width:2px,color:#1e40af
style D fill:#f8fafc,stroke:#f97316,stroke-width:2px,color:#9a3412
style G fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
style H fill:#f8fafc,stroke:#22c55e,stroke-width:2px,color:#15803d
Таблица портов
| Порт | Протокол | Назначение | Доступен снаружи? |
|---|---|---|---|
| 5060 | UDP/TCP | SIP, PJSIP | Только с IP провайдера и офисных сетей |
| 5061 | TCP | SIP over TLS | Только если используешь TLS |
| 10000-20000 | UDP | RTP, голос | Да, для провайдера и удалённых абонентов |
| 80/443 | TCP | Веб-интерфейс FreePBX | Нет, только из админской сети |
| 3306 | TCP | MariaDB | Нет. Слушает только 127.0.0.1 |
| 8085 | TCP | HTTP API для CallerID, если выберешь этот вариант | Нет. Только с IP АТС |
Рецепт: база CallerID для FreePBX на MariaDB
Договоримся об именах, чтобы ничего не перепутать. Сервер FreePBX называю pbx01. База живёт там же, в той MariaDB, которую уже использует FreePBX, но в отдельной базе данных cid_directory. Если хочешь вынести базу на отдельный сервер — всё то же самое, разница в одном поле Host и в правилах firewall.
Подготовка: проверь среду
Сначала убедись, что версии те, о которых речь. Пять секунд сейчас экономят час потом.
Хост: pbx01, сервер FreePBX, под root.
cat /etc/debian_version
asterisk -rx "core show version"
mariadb --version
fwconsole ma list | grep -Ei 'framework|core|cidlookup|superfecta|phonebook|setcid'
Результат: Debian 12.x, Asterisk 20, 21 или 22, MariaDB 10.11. В списке модулей framework и core версии 17.x. Модуль cidlookup может отсутствовать — поставим. Если superfecta стоит с версией ниже 17.0.7 — сначала прочитай блок про безопасность в разделе альтернатив.
Перед любыми изменениями сделай снимок. Если FreePBX крутится в виртуалке на Proxmox, это одна команда.
Хост: узел Proxmox, где запущена ВМ pbx01. Вместо 105 подставь VMID своей АТС.
qm snapshot 105 pre-callerid-db --description "Before CallerID DB setup"
Результат: снапшот виден в веб-интерфейсе Proxmox на вкладке Snapshots. Откат — qm rollback 105 pre-callerid-db.
Шаг 1. Поставь или обнови модуль CallerID Lookup
Зачем: источник MySQL в свежих версиях модуля работает через AGI-скрипт. В старых — через app_mysql, которого в Asterisk 22 нет. Обновление закрывает причину номер 6 из таблицы выше.
Хост: pbx01, под root.
fwconsole ma downloadinstall cidlookup
fwconsole ma upgrade cidlookup
fwconsole reload
ls -l /var/lib/asterisk/agi-bin/ | grep -i cidlookup
Результат: модуль в статусе Enabled, в каталоге agi-bin есть файл cidlookup_mysql.agi или похожий по имени. Если файла нет, а модуль свежий — проверь, не лежит ли AGI в каталоге модуля: find /var/www/html/admin/modules/cidlookup -name «*.agi».
Шаг 2. Создай базу и двух пользователей
Зачем: отдельная база не смешивается с таблицами FreePBX и не ломается при его обновлениях. Пользователей два, и это не паранойя. FreePBX подставляет номер звонящего прямо в текст SQL-запроса. Значит, у пользователя, под которым работает поиск, должно быть право только на SELECT и только на одну таблицу. Писать будет второй пользователь — для импорта.
Хост: pbx01, под root. В Debian 12 root заходит в MariaDB через unix_socket без пароля.
mariadb
Внутри клиента MariaDB выполни. Пароли замени на свои, длинные и случайные. Сгенерировать можно командой openssl rand -base64 24 в соседнем окне.
CREATE DATABASE cid_directory CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'cid_ro'@'localhost' IDENTIFIED BY 'ЗАМЕНИ_НА_ДЛИННЫЙ_ПАРОЛЬ_RO';
CREATE USER 'cid_rw'@'localhost' IDENTIFIED BY 'ЗАМЕНИ_НА_ДЛИННЫЙ_ПАРОЛЬ_RW';
GRANT SELECT ON cid_directory.* TO 'cid_ro'@'localhost';
GRANT SELECT, INSERT, UPDATE, DELETE ON cid_directory.* TO 'cid_rw'@'localhost';
FLUSH PRIVILEGES;
Результат: каждая команда отвечает Query OK. Проверить права можно так: SHOW GRANTS FOR ‘cid_ro’@’localhost’; — в ответе должен быть только SELECT.
Шаг 3. Создай таблицу с нормализацией номера
Это ключевой шаг всей статьи. Не перепутай.
Проблема: в одной базе номера записаны как «+7 (916) 123-45-67», «89161234567» и «7 916 1234567». Провайдер присылает «79161234567». А завтра сменишь провайдера — и он будет слать «+79161234567». Сравнивать строки как есть бессмысленно.
Решение: хранить рядом с исходным номером вычисляемую колонку — последние 10 цифр. Для российских мобильных и городских номеров это и есть уникальная часть. Колонка STORED, на ней уникальный индекс. MariaDB пересчитывает её сама при каждой вставке и правке.
Хост: pbx01, в том же клиенте MariaDB.
USE cid_directory;
CREATE TABLE contacts (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
number VARCHAR(32) NOT NULL,
num10 VARCHAR(10) AS (RIGHT(REGEXP_REPLACE(number, '[^0-9]', ''), 10)) STORED,
name VARCHAR(64) NOT NULL,
source VARCHAR(32) NOT NULL DEFAULT 'manual',
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uq_num10 (num10)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
INSERT INTO contacts (number, name) VALUES
('+7 (916) 123-45-67', 'Иванов И. Ромашка'),
('8 (343) 222-33-44', 'Бухгалтерия Ромашки');
SELECT number, num10, name FROM contacts;
Результат: SELECT показывает две строки, в колонке num10 значения 9161234567 и 3432223344. Скобки, пробелы, плюсы и восьмёрки исчезли.
Почему 10 цифр, а не 11. Первая цифра у российских номеров бывает 7 или 8, а иногда её вообще нет. Последние 10 цифр стабильны во всех вариантах записи. Если работаешь с международными номерами, где важна страна, — храни полный E.164 и сравнивай по нему. Но для внутреннего российского трафика 10 цифр закрывают задачу.
Про длину имени. VARCHAR(64) с запасом, но телефоны покажут меньше. Большинство настольных трубок выводят 16-24 символа в строке входящего. Поэтому «Иванов И. Ромашка» лучше, чем «Общество с ограниченной ответственностью Ромашка, Иванов Иван Иванович, главный инженер».
Шаг 4. Залей контакты из CSV
Рождённый в Excel телефонный справочник нормализации не боится. Он о ней просто не слышал. Поэтому импорт делаем скриптом, а не руками.
Формат CSV: два поля через точку с запятой, номер и имя, кодировка UTF-8. Excel умеет сохранять «CSV UTF-8» — выбирай именно этот тип. Пример файла:
+7 (916) 123-45-67;Иванов И. Ромашка
8 343 222-33-44;Бухгалтерия Ромашки
79035556677;Петров П. Вектор
Сначала поставь драйвер MariaDB для Python и положи учётку пользователя cid_rw в файл с правами только для root. Пароль в коде скрипта держать не будем.
Хост: pbx01, под root.
apt update && apt install -y python3-pymysql
cat > /root/.cid_rw.cnf <<'EOF'
[client]
user=cid_rw
password=ЗАМЕНИ_НА_ДЛИННЫЙ_ПАРОЛЬ_RW
host=localhost
EOF
chmod 600 /root/.cid_rw.cnf
Теперь сам скрипт. Он читает CSV, пропускает пустые строки и номера короче 10 цифр, обрезает имя до 64 символов и делает upsert: новые номера добавляет, существующие обновляет.
Хост: pbx01, файл /usr/local/sbin/cid-import.py.
#!/usr/bin/env python3
"""Импорт контактов CallerID из CSV в cid_directory.contacts.
Формат строки: номер;имя (UTF-8, допускается BOM)."""
import csv
import sys
import pathlib
import pymysql
CNF = "/root/.cid_rw.cnf"
STAMP = pathlib.Path("/var/lib/cid-sync/last_ok")
SQL = (
"INSERT INTO contacts (number, name, source) VALUES (%s, %s, 'csv') "
"ON DUPLICATE KEY UPDATE number = VALUES(number), "
"name = VALUES(name), source = 'csv'"
)
def load(path):
rows, skipped = [], 0
with open(path, newline="", encoding="utf-8-sig") as f:
for rec in csv.reader(f, delimiter=";"):
if len(rec) < 2:
skipped += 1
continue
number, name = rec[0].strip(), rec[1].strip()[:64]
digits = "".join(c for c in number if c.isdigit())
if len(digits) < 10 or not name:
skipped += 1
continue
rows.append((number, name))
return rows, skipped
def main():
if len(sys.argv) != 2:
sys.exit("usage: cid-import.py contacts.csv")
rows, skipped = load(sys.argv[1])
conn = pymysql.connect(read_default_file=CNF, database="cid_directory",
charset="utf8mb4")
with conn:
with conn.cursor() as cur:
cur.executemany(SQL, rows)
conn.commit()
STAMP.parent.mkdir(parents=True, exist_ok=True)
STAMP.touch()
print(f"ok: {len(rows)} upserted, {skipped} skipped")
if __name__ == "__main__":
main()
Хост: pbx01, под root. Сделай скрипт исполняемым и прогони на файле. Файл заранее скопируй на сервер, например в /srv/cid/contacts.csv.
chmod 750 /usr/local/sbin/cid-import.py
mkdir -p /srv/cid
/usr/local/sbin/cid-import.py /srv/cid/contacts.csv
Результат: строка вида ok: 3 upserted, 0 skipped. Если skipped больше нуля — открой CSV и посмотри, какие строки без имени или с коротким номером. Обычно это внутренние трёхзначные номера, им в этой таблице не место.
Обрати внимание на одну тонкость. Если в CSV два разных написания одного номера, последнее перезапишет первое — уникальный индекс на num10 не даст создать дубль. Это фича, а не баг: один номер — одно имя.
Шаг 5. Проверь запрос руками до GUI
Зачем: если запрос не работает в консоли, он не заработает и в FreePBX. А в FreePBX ошибку искать дольше. Проверяем ровно под тем пользователем и ровно тем запросом, что пойдут в Lookup Source.
Хост: pbx01. Пароль cid_ro введёшь интерактивно.
mariadb -u cid_ro -p --default-character-set=utf8mb4 cid_directory \
-e "SELECT name FROM contacts WHERE num10 = RIGHT('+79161234567', 10) LIMIT 1;"
mariadb -u cid_ro -p --default-character-set=utf8mb4 cid_directory \
-e "SELECT name FROM contacts WHERE num10 = RIGHT('89161234567', 10) LIMIT 1;"
Результат: оба запроса возвращают «Иванов И. Ромашка» нормальной кириллицей. Два разных формата — одно имя. Именно этого мы и добивались.
Почему RIGHT в самом запросе, а не только в колонке. Номер от провайдера тоже приходит в непредсказуемом виде. Мы нормализуем обе стороны сравнения. Индекс на num10 при этом используется: функция применяется к константе, а не к колонке.
Шаг 6. Создай источник в CallerID Lookup Sources
Зачем: это штатная точка, через которую FreePBX подставляет имя во входящий вызов. Открой веб-интерфейс: Admin → CallerID Lookup Sources → Add CID Lookup Source. Заполни поля так:
| Поле | Значение | Комментарий |
|---|---|---|
| Source Description | CID MariaDB | Это имя увидишь в выпадающем списке входящего маршрута |
| Source type | MySQL | Работает и с MariaDB |
| Cache Results | Нет на старте | Включишь позже, когда база устоится. Подробно ниже |
| Host | localhost | Именно localhost, а не 127.0.0.1 — пользователь создан для localhost |
| Database | cid_directory | |
| Query | запрос из блока ниже | [NUMBER] FreePBX заменит на номер звонящего |
| Username | cid_ro | Только SELECT |
| Password | пароль cid_ro | |
| Character Set | utf8mb4 | Не оставляй пустым, иначе latin1 и кракозябры |
Запрос для поля Query, одной строкой:
SELECT name FROM contacts WHERE num10 = RIGHT('[NUMBER]', 10) LIMIT 1
Нажми Submit, потом Apply Config. Запрос должен возвращать ровно одну колонку. FreePBX берёт первое значение первой строки и пишет его в имя звонящего.
Шаг 7. Привяжи источник к входящему маршруту
Зачем: сам по себе Lookup Source ничего не делает. Его нужно включить на конкретном Inbound Route. Это самая частая причина вопроса «всё настроил, ничего не происходит».
Открой Connectivity → Inbound Routes → нужный маршрут → вкладка Other. В поле CID Lookup Source выбери «CID MariaDB». Сохрани и примени конфиг. Если входящих маршрутов несколько, повтори для каждого, где нужно имя. Маршрут «по умолчанию» с пустым DID тоже не забудь.
После этого перегенерируй диалплан с консоли — так надёжнее, чем кнопка в браузере, и сразу видны ошибки.
Хост: pbx01, под root.
fwconsole reload
asterisk -rx "dialplan show cidlookup"
Результат: в контексте cidlookup есть расширение вида cidlookup_1 с вызовом AGI. Номер после подчёркивания — ID источника. Если контекст пустой, источник не привязан ни к одному маршруту или конфиг не применился.
Ниже — как это выглядит по времени. Обрати внимание: пока идёт запрос, у звонящего ещё не начались гудки. Поэтому быстрый запрос — это не эстетика, а качество обслуживания.
%%{init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#f8fafc',
'primaryTextColor': '#1e293b',
'primaryBorderColor': '#94a3b8',
'noteBkgColor': '#fefce8',
'noteTextColor': '#713f12',
'noteBorderColor': '#fbbf24',
'actorBkg': '#f8fafc',
'actorBorder': '#94a3b8',
'actorTextColor': '#1e293b',
'fontSize': '15px',
'fontFamily': 'ui-sans-serif, system-ui, sans-serif'
},
'sequence': {
'mirrorActors': false,
'messageAlign': 'center',
'actorMargin': 120,
'width': 160,
'noteMargin': 12
}
}}%%
sequenceDiagram
participant P as Провайдер
participant A as Asterisk
participant G as AGI cidlookup
participant D as MariaDB
participant T as Телефон
P->>A: INVITE с номером
A->>A: Проверка кеша AstDB
A->>G: Номер звонящего
G->>D: SELECT name
D->>G: Иванов И. Ромашка
G->>A: CALLERID name
Note over A,D: Пауза до гудков
A->>T: Вызов с именем
Шаг 8. Реши, включать ли кеш
Галочка Cache Results в источнике заставляет FreePBX сохранять найденное имя в AstDB. Следующий звонок с того же номера вообще не пойдёт в базу. Быстро. Но есть подвох.
Закешированное имя возвращается всегда. Даже если ты поменял его в таблице. Даже если клиент ушёл к конкурентам и номер теперь принадлежит другому человеку. Кеш сам не протухает.
| Ситуация | Кеш | Почему |
|---|---|---|
| База локальная, на том же сервере | Выключен | Запрос по индексу занимает миллисекунды, выигрыша нет, а риск устаревших имён есть |
| База на удалённом сервере в той же сети | По ситуации | Включай, если задержка до базы заметна, и чисти кеш после каждой синхронизации |
| HTTP API медленного внешнего сервиса | Включён | Иначе каждый входящий ждёт ответа сервиса |
| Имена часто меняются | Выключен | Иначе люди увидят старые имена и перестанут доверять системе |
Для рецепта из этой статьи кеш не нужен. Локальная MariaDB с уникальным индексом отвечает быстрее, чем ты успеешь об этом подумать.
Проверка: как убедиться, что CallerID FreePBX подставляет имя
Проверяем в трёх местах: живой лог Asterisk, CDR и экран телефона. Если имя есть в CDR, но нет на трубке — проблема в телефоне. Если нет и в CDR — в базе или маршруте.
Живой лог во время тестового звонка
Хост: pbx01, под root. Открой консоль, позвони на городской номер с мобильного, который есть в базе.
asterisk -rvvvvv
В выводе ищи строки с cidlookup. Должен быть вызов AGI, а после него Set с CALLERID(name). Выйти из консоли — exit или Ctrl+C, Asterisk при этом не остановится.
Если звонок уже прошёл, смотри в файле лога:
grep -iE 'cidlookup|CALLERID\(name\)' /var/log/asterisk/full | tail -n 30
Результат: видно, какой номер ушёл в запрос и какое имя вернулось. Если номер в логе выглядит неожиданно, например с префиксом маршрута или с лишним плюсом, — вот она, причина несовпадения.
Проверка через CDR
CDR хранит имя в поле cnam. Это удобно: видно сразу последние звонки и что по ним нашлось.
Хост: pbx01, в клиенте mariadb под root.
SELECT calldate, src, cnam, did, disposition
FROM asteriskcdrdb.cdr
WHERE calldate > NOW() - INTERVAL 1 HOUR
ORDER BY calldate DESC
LIMIT 10;
Результат: у тестового звонка в cnam стоит имя из твоей таблицы. Если там номер или пусто — lookup не сработал. Колонка src заодно покажет, в каком формате провайдер присылает номер.
Проверка кеша AstDB
Даже если кеш выключен, полезно знать, что в AstDB лежит. Phonebook и кеш cidlookup используют одно и то же семейство cidname.
asterisk -rx "database show cidname" | head -n 20
Результат: список ключей вида /cidname/79161234567 со значением-именем. Если там старое имя для нужного номера — именно оно и показывается, а твоя таблица даже не опрашивается.
Отладка FreePBX
Если AGI молчит или возвращает пусто, включи отладочный лог FreePBX. Окно держи открытым и сделай тестовый звонок во втором окне.
fwconsole dbug
Лог пишется в /var/log/asterisk/freepbx_dbug. Там видны ошибки подключения к базе из PHP, которые в консоль Asterisk не попадают.
Осложнения: типовые ошибки и как их лечить
Обычно в этом месте пишут: «если делал строго по инструкции, проблем не будет». Проблемы будут. Инструкцию писал человек, а сервер настраивал тоже человек.
| Ошибка | Причина | Решение |
|---|---|---|
| Вместо имени вопросики или кракозябры | Пустой Character Set, используется latin1 | Указать utf8mb4 в источнике, проверить кодировку таблицы |
| Имя не меняется после правки в базе | Включён кеш, значение лежит в AstDB | Удалить ключ из cidname или выключить кеш |
| Имя не находится, в консоли всё работает | Провайдер шлёт номер в другом формате | Посмотреть src в CDR, проверить нормализацию |
| Access denied for user | Хост в источнике 127.0.0.1, а пользователь создан для localhost | Поставить localhost или создать пользователя для 127.0.0.1 |
| Вместо номера unknown, когда имени в базе нет | Старый баг модуля cidlookup 15.0.24 | Обновить модуль до свежей версии |
| Предупреждение про app_mysql, MySQL нельзя выбрать | Старый cidlookup на Asterisk 19+ | Обновить модуль cidlookup |
| Пауза перед гудками | Медленная удалённая база или HTTP API | Индекс, локальная база, кеш |
| Имя есть в CDR, на телефоне нет | Телефон не показывает UTF-8 или обрезает | Прошивка, язык трубки, короткие имена |
Кракозябры вместо кириллицы
Причина: поле Character Set в источнике пустое. Подсказка в самом интерфейсе модуля честно пишет, что по умолчанию используется latin1. Кириллица из utf8mb4-таблицы в latin1 не помещается.
Решение: открой источник, впиши utf8mb4, сохрани, примени конфиг. Потом проверь, что сама таблица действительно в utf8mb4 — если её создавали без явной кодировки на старом сервере, могла получиться latin1.
Хост: pbx01, клиент mariadb под root.
SELECT TABLE_NAME, TABLE_COLLATION
FROM information_schema.TABLES
WHERE TABLE_SCHEMA = 'cid_directory';
ALTER TABLE cid_directory.contacts CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
ALTER нужен только если в TABLE_COLLATION не utf8mb4. Если данные уже залиты в неправильной кодировке, конвертация их не спасёт — перезалей из CSV.
Имя устарело и не обновляется
Причина: в источнике включён кеш. Имя лежит в AstDB и возвращается раньше, чем дело дойдёт до базы.
Решение: удали ключ конкретного номера. Номер пиши в том формате, в каком он приходит от провайдера, — смотри вывод database show cidname.
Хост: pbx01, под root.
asterisk -rx "database del cidname 79161234567"
asterisk -rx "database show cidname" > /root/cidname-backup-$(date +%F).txt
asterisk -rx "database deltree cidname"
Номер не находится, хотя в базе он есть
Причина: формат. Чаще всего провайдер присылает номер с префиксом, а то и вовсе с «+» и кодом страны, которого нет в твоей записи. Или наоборот: в базе 10 цифр, провайдер шлёт 7 цифр городского номера без кода. Тогда RIGHT отдаст 7 цифр, а сравнение провалится.
Решение: возьми реальный src из CDR и подставь его в ручной запрос из шага 5. Если не находит — смотри, сколько цифр приходит. Для городских без кода есть вариант: в поле Query дописать код города через CONCAT. Например, для Екатеринбурга с кодом 343:
SELECT name FROM contacts WHERE num10 = RIGHT(CONCAT('343', '[NUMBER]'), 10) LIMIT 1
Это костыль под конкретного провайдера. Сначала попробуй попросить провайдера передавать номер в полном формате — многие делают это по заявке за день.
Access denied for user ‘cid_ro’
Причина: в MariaDB пользователь определяется связкой имя плюс хост. Подключение к localhost идёт через сокет, к 127.0.0.1 — через TCP, и это могут оказаться разные учётные записи.
Решение: в источнике укажи Host = localhost. Если по каким-то причинам нужен именно 127.0.0.1, создай второго пользователя.
Хост: pbx01, клиент mariadb под root.
CREATE USER 'cid_ro'@'127.0.0.1' IDENTIFIED BY 'ТОТ_ЖЕ_ПАРОЛЬ_RO';
GRANT SELECT ON cid_directory.* TO 'cid_ro'@'127.0.0.1';
FLUSH PRIVILEGES;
Вместо номера показывается unknown
Причина: в 2022 году вышла версия cidlookup 15.0.24, которая при отсутствии имени в базе затирала исходный CallerID. Это обсуждалось в теме на форуме FreePBX и исправлено в 15.0.25 и 16.0.12.
Решение: обнови модуль. На FreePBX 17 этой проблемы быть не должно, но если сервер переносили бэкапом со старого — проверь версию.
Хост: pbx01, под root.
fwconsole ma list | grep cidlookup
fwconsole ma upgrade cidlookup
fwconsole reload
Пауза перед гудками
Причина: lookup выполняется до того, как звонок пойдёт на телефоны. Если запрос занимает две секунды, звонящий две секунды слушает тишину. Хуже всего с HTTP-источниками и удалёнными базами через VPN.
Решение: замерь время запроса. Для локальной базы с индексом это единицы миллисекунд.
Хост: pbx01.
time mariadb -u cid_ro -p cid_directory \
-e "SELECT name FROM contacts WHERE num10 = RIGHT('79161234567', 10) LIMIT 1;"
mariadb -u cid_ro -p cid_directory \
-e "EXPLAIN SELECT name FROM contacts WHERE num10 = RIGHT('79161234567', 10);"
В EXPLAIN в колонке key должен стоять uq_num10, а type — const. Если там ALL — индекс не используется, перепроверь шаг 3. Для удалённой базы или HTTP включай кеш и смотри на сеть.
Имя в CDR есть, на трубке нет
Причина: телефон. Старые модели не умеют UTF-8 в заголовке From, некоторые показывают только номер, если не включена опция отображения имени. Плюс обрезка по длине экрана.
Решение: обнови прошивку, проверь язык интерфейса трубки и опцию вида «Display Name» или «Caller ID Display». Если в Inbound Route задан CID name prefix, например «Продажи:», помни, что он съедает место на экране. Иногда проще убрать префикс, чем потерять фамилию клиента.
Superfecta не шлёт уведомления
Причина: функции Send to у Superfecta, будь то email, Telegram или URL, срабатывают только когда какой-то источник нашёл имя. Нет имени — нет уведомления. Это поведение, описанное разработчиками, а не баг.
Решение: для теста добавь в схему источник Abandon Lookup последним в цепочке — тогда имя найдётся всегда, и ты увидишь, работает ли отправка.
Альтернативы: Phonebook, Superfecta, HTTP API и func_odbc
Основной рецепт закрывает типовую задачу. Но бывает, что база маленькая, или данные живут в CRM, или нужна логика, которую в одно поле Query не впихнуть. Разберём каждый вариант с настройкой.
Asterisk Phonebook: когда номеров меньше пары сотен
Самый простой путь. Модуль Asterisk Phonebook хранит пары номер-имя прямо в AstDB, в семействе cidname. В источниках CallerID Lookup он подключается как тип Internal. Никакой внешней базы, никаких паролей.
Ограничения жёсткие. Совпадение только точное — «79161234567» и «89161234567» для него разные номера. Массовое редактирование неудобное. Нет истории изменений. Для отдела из десяти человек с тридцатью постоянными клиентами — нормально. Для отдела продаж с базой на пять тысяч — нет.
Записи можно добавлять в GUI, импортом CSV из интерфейса модуля, или прямо из консоли. Формат CSV для импорта у разных версий модуля отличался, поэтому сначала сделай экспорт из интерфейса и посмотри, какие колонки он ожидает.
Хост: pbx01, под root. Пример добавления записи напрямую в AstDB — номер в том формате, в каком его шлёт провайдер.
asterisk -rx 'database put cidname 79161234567 "Иванов И. Ромашка"'
asterisk -rx 'database get cidname 79161234567'
Если провайдер шлёт номера в двух форматах, придётся класть две записи. Вот почему при росте базы Phonebook быстро становится неудобным.
CID Superfecta: когда нужно несколько источников
CID Superfecta — это оркестратор. Он опрашивает несколько источников по очереди или параллельно: свою MySQL, Contact Manager, Phonebook, внешние сервисы. Умеет помечать спам, подставлять текст для подозрительных номеров, отправлять уведомления на email, в Telegram, Pushover и на произвольный URL. Схемы можно вешать на разные входящие маршруты.
Где это оправдано. Колл-центр, где нужно проверить номер в CRM, потом в общей базе, потом пометить спам. Офис, где руководитель хочет уведомление в Telegram о звонке от VIP-клиента. Если тебе нужно просто показать имя из одной таблицы — Superfecta избыточна.
Подробности уязвимости — в карточке CVE-2026-54710 на OpenCVE. Проверка и обновление:
Хост: pbx01, под root.
fwconsole ma list | grep -i superfecta
fwconsole ma upgrade superfecta
fwconsole reload
fwconsole ma list | grep -i superfecta
Результат: версия 17.0.7 или новее. Если модуль не нужен:
fwconsole ma uninstall superfecta
fwconsole ma remove superfecta
fwconsole reload
Свои источники для Superfecta пишутся на PHP и кладутся в каталог /var/www/html/admin/modules/superfecta/sources/. Это мощно, но каждый такой файл — код, который выполняется веб-сервером. Учитывай это, когда тащишь источники с GitHub без чтения.
HTTP API: когда данные живут в CRM
Если контакты лежат в CRM, 1С или самописной системе, тащить их в отдельную таблицу не всегда разумно. Источник HTTP или HTTPS в CallerID Lookup делает GET-запрос, подставляя номер вместо токена [NUMBER] в строке запроса, и берёт тело ответа как имя.
Минимальный сервис на Flask, который отвечает именем по номеру. Для примера он читает ту же таблицу, но вместо SQL ты подставишь запрос в API своей CRM.
Хост: pbx01 или отдельный сервер приложений, под root. Пакеты из репозитория Debian 12.
apt install -y python3-flask python3-pymysql gunicorn
useradd --system --no-create-home --shell /usr/sbin/nologin cidapi
install -d -o cidapi -g cidapi -m 750 /opt/cidapi
Учётка для API — отдельный файл, читаемый только пользователем сервиса. Используй cid_ro, писать сервису не нужно.
cat > /opt/cidapi/db.cnf <<'EOF'
[client]
user=cid_ro
password=ЗАМЕНИ_НА_ДЛИННЫЙ_ПАРОЛЬ_RO
host=localhost
EOF
chown cidapi:cidapi /opt/cidapi/db.cnf
chmod 600 /opt/cidapi/db.cnf
Хост: там же, файл /opt/cidapi/app.py.
import re
import pymysql
from flask import Flask, request, Response
app = Flask(__name__)
SQL = "SELECT name FROM contacts WHERE num10 = %s LIMIT 1"
def lookup(number):
digits = re.sub(r"\D", "", number or "")
if len(digits) < 10:
return ""
conn = pymysql.connect(read_default_file="/opt/cidapi/db.cnf",
database="cid_directory", charset="utf8mb4",
connect_timeout=1, read_timeout=1)
with conn:
with conn.cursor() as cur:
cur.execute(SQL, (digits[-10:],))
row = cur.fetchone()
return row[0] if row else ""
@app.get("/cid")
def cid():
name = lookup(request.args.get("number", ""))
return Response(name, mimetype="text/plain; charset=utf-8")
Тут номер передаётся в запрос параметром, а не склейкой строки. Это закрывает SQL-инъекцию полностью, в отличие от поля Query в GUI. Таймауты в одну секунду — чтобы зависшая база не держала звонок.
Юнит systemd, чтобы сервис поднимался сам. Слушаем только localhost, если API на том же сервере.
Хост: там же, файл /etc/systemd/system/cidapi.service.
[Unit]
Description=CallerID lookup HTTP API
After=network-online.target mariadb.service
[Service]
User=cidapi
Group=cidapi
WorkingDirectory=/opt/cidapi
ExecStart=/usr/bin/gunicorn --workers 2 --bind 127.0.0.1:8085 app:app
Restart=on-failure
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
[Install]
WantedBy=multi-user.target
chown cidapi:cidapi /opt/cidapi/app.py
systemctl daemon-reload
systemctl enable --now cidapi
curl -s "http://127.0.0.1:8085/cid?number=%2B79161234567"; echo
Результат: curl печатает «Иванов И. Ромашка». В CallerID Lookup Sources создай источник типа HTTP: Host 127.0.0.1, Port 8085, Path /cid, Query number=[NUMBER]. Привяжи к маршруту так же, как в шаге 7.
Если API живёт на отдельном сервере — слушай на внутреннем IP и закрой порт firewall-ом для всех, кроме IP АТС. Для HTTP через недоверенную сеть бери тип HTTPS и нормальный сертификат.
func_odbc и Set CallerID: максимум контроля
Вариант для тех, кому мало одного поля Query. ODBC — штатный путь Asterisk к внешним базам, именно он заменил удалённый app_mysql. FreePBX 17 уже использует ODBC для записи CDR, так что драйвер MariaDB на сервере стоит.
Схема такая. Описываем DSN, в Asterisk заводим соединение res_odbc и функцию func_odbc. Функцию вызываем из модуля Set CallerID, который ставим назначением входящего маршрута перед группой вызова. Получается без правок генерируемого диалплана, всё в GUI плюс три конфига.
Главный плюс — экранирование. В func_odbc есть функция SQL_ESC, и номер звонящего попадает в запрос безопасно.
Хост: pbx01, под root. Сначала узнай имя драйвера и посмотри, какие файлы custom подключаются в конфиги Asterisk.
odbcinst -q -d
grep -n include /etc/asterisk/res_odbc.conf /etc/asterisk/func_odbc.conf 2>/dev/null
fwconsole ma downloadinstall setcid
Добавь DSN в /etc/odbc.ini. Значение Driver возьми из вывода odbcinst — на FreePBX 17 это обычно MySQL или MariaDB.
[cid-directory]
Description = CallerID directory
Driver = MySQL
Server = localhost
Database = cid_directory
Port = 3306
Socket = /run/mysqld/mysqld.sock
Option = 3
Charset = utf8mb4
Проверь DSN до Asterisk — утилитой isql из unixODBC.
echo "SELECT name FROM contacts LIMIT 1;" | isql -v cid-directory cid_ro 'ПАРОЛЬ_RO'
Соединение в Asterisk. Если в res_odbc.conf подключается res_odbc_custom.conf — пиши туда. Если нет — допиши в res_odbc.conf, но помни, что этот файл может перезаписаться при обновлении.
[cid]
enabled => yes
dsn => cid-directory
username => cid_ro
password => ЗАМЕНИ_НА_ДЛИННЫЙ_ПАРОЛЬ_RO
pre-connect => yes
max_connections => 5
Функция в func_odbc — аналогично, в custom-файл, если он подключён.
[CIDNAME]
dsn=cid
readsql=SELECT name FROM contacts WHERE num10 = RIGHT('${SQL_ESC(${ARG1})}', 10) LIMIT 1
synopsis=CallerID name lookup in cid_directory
asterisk -rx "module reload res_odbc.so"
asterisk -rx "module reload func_odbc.so"
asterisk -rx "odbc show cid"
asterisk -rx "core show function ODBC_CIDNAME"
Результат: odbc show cid показывает соединение в статусе Connected, функция ODBC_CIDNAME описана. Теперь в Admin → Set CallerID создай запись: CallerID Name — ${ODBC_CIDNAME(${CALLERID(num)})}, CallerID Number — ${CALLERID(num)}, назначение — твоя группа вызова. Во входящем маршруте поставь назначением эту запись Set CallerID, а CID Lookup Source оставь None.
Одна тонкость: если имя не найдено, функция вернёт пустую строку, и на трубке будет пусто вместо номера. Лечится выражением IF в поле имени: ${IF($[«${ODBC_CIDNAME(${CALLERID(num)})}» != «»]?${ODBC_CIDNAME(${CALLERID(num)})}:${CALLERID(name)})}. Выглядит страшно, работает надёжно. Запрос при этом выполнится дважды, но по индексу это незаметно.
Синтаксис func_odbc и res_odbc подробно описан в документации Asterisk.
Итоговое сравнение альтернатив
| Критерий | Phonebook | Lookup MySQL | Lookup HTTP | Superfecta | func_odbc |
|---|---|---|---|---|---|
| Настройка | 5 минут | 30 минут | 1 час | 1-2 часа | 1 час |
| Нормализация номера | Нет | Да, в SQL | Да, в коде | Да, правила | Да, в SQL |
| Защита от SQL-инъекций | Не нужна | Только правами пользователя | Параметры в коде | Зависит от источника | SQL_ESC |
| Несколько источников | Нет | Нет | Сколько напишешь | Да | Через диалплан |
| Уведомления | Нет | Нет | Сколько напишешь | Да | Через диалплан |
| Только GUI | Да | Да | Плюс сервис | Да | Нет |
Профилактика: чтобы база CallerID не протухла
База имён полезна, пока она актуальна. Через полгода без синхронизации она начинает врать, а через год ей перестают верить. Поэтому синхронизация, мониторинг и бэкап — не опции.
Автосинхронизация через systemd timer
Одна строка в crontab без комментария живёт дольше, чем сотрудник, который её написал. И знает о системе больше него. Поэтому вместо crontab — таймер systemd: он виден в списке, пишет лог в journal и не теряется при переезде.
Схема: CRM или скрипт выгрузки кладёт свежий CSV в /srv/cid/contacts.csv, таймер раз в час запускает импорт.
Хост: pbx01, под root. Файл /etc/systemd/system/cid-sync.service.
[Unit]
Description=Import CallerID contacts from CSV
ConditionPathExists=/srv/cid/contacts.csv
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/cid-import.py /srv/cid/contacts.csv
Файл /etc/systemd/system/cid-sync.timer.
[Unit]
Description=Hourly CallerID contacts import
[Timer]
OnCalendar=hourly
RandomizedDelaySec=300
Persistent=true
[Install]
WantedBy=timers.target
systemctl daemon-reload
systemctl enable --now cid-sync.timer
systemctl list-timers cid-sync.timer
systemctl start cid-sync.service && journalctl -u cid-sync.service -n 5 --no-pager
Результат: таймер в списке с временем следующего запуска, в журнале строка ok с количеством записей. Если включал кеш в источнике — добавь в сервис вторую строку ExecStart с удалением устаревших ключей, иначе синхронизация бессмысленна.
Скрипт импорта только добавляет и обновляет. Удалённые из CRM контакты останутся в таблице. Если это важно, раз в неделю чисти записи источника csv, которые давно не обновлялись, или перезаливай таблицу целиком в транзакции.
Мониторинг
Что мониторить: что синхронизация прошла недавно, что таблица не опустела, что MariaDB жива. Скрипт импорта трогает файл-метку /var/lib/cid-sync/last_ok при каждом успешном прогоне. Возраст этой метки — лучший индикатор.
Пример для агента Zabbix. Возраст метки берёт штатный ключ vfs.file.time[/var/lib/cid-sync/last_ok,modify], триггер — если прошло больше трёх часов. Число записей — через UserParameter.
Хост: pbx01, файл /etc/zabbix/zabbix_agent2.d/cid.conf. Учётку cid_ro положи в /etc/zabbix/.cid_ro.cnf с владельцем zabbix и правами 600.
UserParameter=cid.rows,mariadb --defaults-extra-file=/etc/zabbix/.cid_ro.cnf -Nse "SELECT COUNT(*) FROM cid_directory.contacts"
systemctl restart zabbix-agent2
zabbix_agent2 -t cid.rows
Триггер на cid.rows: резкое падение больше чем на 20% за прогон. Это значит, что кто-то выгрузил из CRM пустой или обрезанный файл.
Резервное копирование
Вот тут важно. Штатный модуль Backup в FreePBX сохраняет свою конфигурацию и свои базы. Твою cid_directory он не знает и не бэкапит. После восстановления АТС из бэкапа источник будет на месте, а таблицы не будет.
| Что бэкапить | Как часто | Где хранить | Как восстановить |
|---|---|---|---|
| База cid_directory | Ежедневно | Локально 30 дней плюс копия вне сервера | mariadb cid_directory из дампа |
| Конфигурация FreePBX | Ежедневно, модуль Backup | Удалённое хранилище модуля Backup | Модуль Restore |
| Скрипты и юниты | При изменении | Git или вместе с дампом | Копирование файлов |
| Семейство cidname AstDB | Еженедельно, если используешь Phonebook | Вместе с дампом | database put по списку |
Хост: pbx01, под root. Скрипт /usr/local/sbin/cid-backup.sh.
#!/bin/bash
set -euo pipefail
DIR=/var/backups/cid
mkdir -p "$DIR"
mysqldump --single-transaction --default-character-set=utf8mb4 cid_directory \
| gzip > "$DIR/cid_directory_$(date +%F).sql.gz"
asterisk -rx "database show cidname" > "$DIR/cidname_$(date +%F).txt"
find "$DIR" -type f -mtime +30 -delete
chmod 750 /usr/local/sbin/cid-backup.sh
/usr/local/sbin/cid-backup.sh && ls -lh /var/backups/cid/
Запуск по расписанию — так же, через systemd timer с OnCalendar=daily. Каталог /var/backups/cid забирай на внешнее хранилище тем же инструментом, которым бэкапишь остальное: rsync, borg, rclone.
Восстановление. Проверь его хотя бы раз до того, как оно понадобится.
gunzip -c /var/backups/cid/cid_directory_2026-10-25.sql.gz | mariadb cid_directory
mariadb -e "SELECT COUNT(*) FROM cid_directory.contacts;"
Автозапуск
Всё, от чего зависит поиск, должно подниматься само после перезагрузки. Проверь одной командой.
systemctl is-enabled mariadb asterisk cid-sync.timer
systemctl is-enabled cidapi 2>/dev/null || true
Результат: enabled для всех. Asterisk на FreePBX 17 обычно запускается через юнит freepbx, поэтому статус asterisk может быть другим — смотри systemctl is-enabled freepbx.
Безопасность
База CallerID — это список клиентов с телефонами. Персональные данные в чистом виде. Относись к ней соответственно.
MariaDB должна слушать только localhost. Проверь:
Хост: pbx01.
ss -ltnp | grep -E ':3306|:8085'
Результат: адрес 127.0.0.1, а не 0.0.0.0. Если база слушает все интерфейсы и ты не выносил её на отдельный сервер — поправь bind-address в /etc/mysql/mariadb.conf.d/50-server.cnf и перезапусти MariaDB.
Firewall. Если используешь модуль Firewall самого FreePBX — работай в нём и не ставь параллельно UFW, два менеджера правил iptables друг друга не любят. Если модуля нет, минимальный UFW выглядит так. Вместо 203.0.113.10 — IP провайдера, вместо 10.0.0.0/24 — офисная сеть.
Хост: pbx01, под root.
ufw default deny incoming
ufw default allow outgoing
ufw allow from 10.0.0.0/24 to any port 22 proto tcp
ufw allow from 10.0.0.0/24 to any port 443 proto tcp
ufw allow from 10.0.0.0/24 to any port 5060 proto udp
ufw allow from 203.0.113.10 to any port 5060 proto udp
ufw allow 10000:20000/udp
ufw enable
ufw status numbered
fail2ban. Проверь, установлен ли он и есть ли тюрьма для Asterisk.
systemctl is-active fail2ban
fail2ban-client status
Если fail2ban нет и FreePBX не закрывает перебор своими средствами — поставь его и включи тюрьму asterisk. Фильтр в Debian уже есть, путь к логу укажи на /var/log/asterisk/full.
apt install -y fail2ban
cat > /etc/fail2ban/jail.d/asterisk.local <<'EOF'
[asterisk]
enabled = true
logpath = /var/log/asterisk/full
maxretry = 5
findtime = 600
bantime = 3600
EOF
systemctl restart fail2ban
fail2ban-client status asterisk
SSH hardening — отдельный файл, чтобы не трогать основной конфиг. Вместо admin — твой пользователь.
Хост: pbx01, файл /etc/ssh/sshd_config.d/10-hardening.conf.
PermitRootLogin prohibit-password
PasswordAuthentication no
MaxAuthTries 3
AllowUsers admin
sshd -t && systemctl reload ssh
sshd -t проверит синтаксис до перезагрузки. Ошибка — перезагрузка не произойдёт, и доступ не потеряешь. Заходить по ключу проверь заранее.
Обновление без сюрпризов
Обновления FreePBX трогают модуль cidlookup, Asterisk и PHP. Твоя таблица от них не зависит, но AGI-скрипт и поведение источника — зависят.
Что проверить до обновления:
- Свежий снапшот ВМ или полный бэкап FreePBX плюс дамп cid_directory.
- Список доступных обновлений: что именно поменяется.
- Release notes модулей cidlookup и superfecta, если они в списке.
- Время: обновляй вне рабочих часов, звонки во время fwconsole reload могут оборваться.
Хост: pbx01, под root.
/usr/local/sbin/cid-backup.sh
fwconsole ma showupgrades
fwconsole ma upgradeall
fwconsole reload
fwconsole ma list | grep -Ei 'cidlookup|superfecta'
После обновления сделай тестовый звонок и проверь CDR, как в разделе «Проверка». Откат — снапшот ВМ на Proxmox: qm rollback с ID машины и именем снапшота, на узле Proxmox.
Про будущее. Сообщество FreePBX называет 1 января 2027 года датой выхода FreePBX 18. Перед мажорным переходом проверь, как в новой версии работает источник MySQL, — один раз app_mysql уже убрали без особых предупреждений.
FAQ: частые вопросы про CallerID в FreePBX
Почему CallerID Lookup в FreePBX не работает после настройки?
В девяти случаях из десяти источник не выбран во входящем маршруте на вкладке Other, или номер от провайдера в другом формате, чем в базе. Проверь src в CDR и выполни запрос с этим номером вручную под пользователем источника. Если на старой системе модуль cidlookup не обновлялся, он может пытаться использовать удалённый из Asterisk app_mysql.
Как проверить, что база CallerID работает правильно?
Сделай тестовый звонок с номера из базы и посмотри поле cnam в таблице asteriskcdrdb.cdr. Параллельно в консоли asterisk -rvvvvv видно вызов AGI cidlookup и подставленное имя. Если имя в CDR есть, а на трубке нет — проблема в телефоне, а не в базе.
Что делать, если вместо имени кракозябры?
Впиши utf8mb4 в поле Character Set источника MySQL и примени конфиг. По умолчанию модуль использует latin1, кириллица в нём не живёт. Заодно проверь, что таблица создана в utf8mb4, и что CSV был сохранён в UTF-8.
Чем CID Superfecta отличается от CallerID Lookup Sources?
CallerID Lookup — один источник на маршрут: одна база, один HTTP-сервис или Phonebook. Superfecta опрашивает цепочку источников, умеет помечать спам и отправлять уведомления. Для простого показа имени из одной базы хватает Lookup Sources, Superfecta нужна для сложной логики. И держи её версии 17.0.7 или новее.
Как сбросить кеш CallerID в FreePBX?
Кеш лежит в AstDB в семействе cidname. Удаляй конкретный номер командой database del через asterisk -rx. Удаление всего семейства сотрёт и записи модуля Phonebook, так что сначала выгрузи его в файл.
Можно ли брать имена из Active Directory?
Напрямую из LDAP модуль cidlookup не умеет. Рабочая схема — скрипт, который выгружает пользователей с атрибутом telephoneNumber или mobile в CSV, и импорт из этой статьи на таймере. Для внутренних звонков между добавочными это не нужно — там имена берутся из настроек самих добавочных.
Сколько номеров выдержит база CallerID?
Phonebook в AstDB комфортен до пары сотен записей. Таблица MariaDB с уникальным индексом спокойно держит сотни тысяч номеров, время поиска остаётся в пределах миллисекунд. Узким местом становится не объём, а сеть до базы, если она удалённая.
Работает ли OpenCNAM с российскими номерами?
Для практических задач нет. OpenCNAM опирается на американскую систему CNAM, а у бесплатного тарифа ограничение 60 кешированных запросов в час. Для российских номеров базу имён придётся вести самому.
Прогноз
Что сделали. Разобрали все варианты базы CallerID в FreePBX и их ограничения. Подняли отдельную таблицу в MariaDB, которая находит номер в любом формате записи. Залили контакты скриптом, подключили источник MySQL с правильной кодировкой, привязали к входящему маршруту. Закрыли права, настроили синхронизацию, мониторинг и бэкап, который штатный модуль FreePBX сам бы не сделал.
Что теперь работает. Менеджер видит на трубке «Иванов И. Ромашка» до того, как снимет трубку. В CDR рядом с номером стоит имя — отчёт по клиентам строится одним запросом. А когда секретарь уйдёт в отпуск, справочник останется на сервере. Где ему и место.
Оставайтесь на связи
Рецепты от IT-боли. Без воды, без рекламы, без маркетинговой шелухи.
Подписаться на IT-Аптеку →



