# DMR-сервер Простой Homebrew/MMDVM мастер для внутренней сети: несколько хотспотов **Pi-Star**, веб-панель с группами и живым журналом, попугай (эхотест) на TG 9990. **Ноль зависимостей** — только `python3` (3.10+). На Debian 13 действует PEP 668, и `pip install` в систему запрещён; venv ради пары библиотек — лишний слой, который однажды сломается. Поэтому всё на stdlib. ## Установка ```bash sudo bash install.sh ``` Ставит код в `/opt/dmr-server`, конфиг в `/etc/dmr-server/config.json`, заводит юнит systemd под отдельным пользователем и печатает адрес панели с паролем. Повторный запуск безопасен — конфиг не перезаписывается. ## Настройка Pi-Star `Configuration → DMR Master`: адрес сервера, порт **62031**, пароль — значение `passphrase` из конфига (**обязательно смени умолчание `passw0rd`**). ## Что умеет - Рукопожатие Homebrew: `RPTL → RPTACK+salt → RPTK → RPTC → RPTPING/MSTPONG`. - Групповые вызовы: кадр от одного хотспота уходит всем остальным, если TG есть в списке групп. - Приватные вызовы: туда, где абонента слышали последним; если не слышали ни разу — всем хотспотам (как в HBlink4). - Попугай на 9990, групповой и приватный: записывает передачу и проигрывает обратно. - Панель: список хотспотов, добавление/удаление групп, последние вызовы, живой журнал, принудительное отключение хотспота. ## Проверка ```bash python3 tests/test_e2e.py ``` Поднимает настоящий мастер и говорит с ним по UDP кадрами, которые шлёт MMDVMHost: рукопожатие, отказ по неверному паролю, keepalive, доставка группового вызова между двумя хотспотами, отсутствие эха отправителю, фильтр по разрешённым группам, приват. Проверяет и то, что легко испортить: длину кадра 55 байт, переписывание `peer_id`, дословную копию `stream_id` и неизменность бёрста. ## Устройство | Файл | Что делает | |---|---| | `dmrsrv/analyzer.py` | разбор и сборка кадров Homebrew/DMR | | `dmrsrv/server.py` | ядро мастера: рукопожатие, реестр хотспотов, маршрут в эфир | | `dmrsrv/router.py` | кому пересылать кадр + подстановка `peer_id` получателя | | `dmrsrv/parrot.py` | эхотест 9990 | | `dmrsrv/dmr_voice.py`, `bptc.py` | пересборка LC — нужна только приватному попугаю | | `dmrsrv/panel.py` | веб-панель на `http.server` | | `dmrsrv/config.py` | конфиг с атомарным сохранением | | `dmrsrv/state.py` | состояние и журнал для панели | Ядро протокола форкнуто из `viktor/dmrhubv2` (~1300 строк из 31700) — там оно уже обкатано на живом эфире. Отрезаны портал, БД, мосты, алерты, динамические подписки. **Перед правкой протокола прочти [`docs/PROTOCOL.md`](docs/PROTOCOL.md).** Спецификация 2015 года врёт в нескольких местах, включая собственный тест-вектор auth-хеша; там записано, где именно и почему прав код, а не бумажка. ## Ограничения - Один сервер = одна сеть, без транков между серверами (OpenBridge не реализован). - Группы статические, из панели. Динамических подписок через `RPTO` нет. - Панель по HTTP с Basic-авторизацией — рассчитана на доверенную внутреннюю сеть. Наружу выставлять только за HTTPS-прокси.