# XLX Server — панель управления XLX/DMR-рефлектором с платной регистрацией Серверная часть частного XLX-рефлектора: устанавливает и обслуживает `xlxd` (мультипротокольный рефлектор XLX для DMR/D-STAR/YSF), а поверх него даёт PHP-панель, в которой радиолюбитель регистрируется по позывному, оплачивает доступ (YooKassa или ручной перевод с чеком) и получает внутренний DMR ID и отдельный пароль для подключения хотспота Pi-Star. Администратору панель даёт status/start/stop/restart `xlxd`, редактирование его конфигурационных файлов (`xlxd.blacklist`, `xlxd.whitelist`, `xlxd.interlink`, `xlxd.terminal`) и дашборд последних событий из лога рефлектора. Проект — серверный ответ на репозиторий `pistar` (скрипты, которые прописывают чужие рефлекторы в хотспоты): здесь рефлектор свой, с учётом пользователей и оплатой. ## Стек - PHP 8 без фреймворка: свой автозагрузчик (`bootstrap.php`, префикс `Xlx\`), роутинг на `if` в `public/index.php`, PDO. - MariaDB/MySQL, схема в `database/schema.sql`. - Apache 2 + `libapache2-mod-php`, `FallbackResource /index.php`. - systemd (`xlxd.service`), Bash-установщики, `sudoers`-правило для управления сервисом от `www-data`. - Frontend: ванильные HTML/CSS/JS (`public/assets`), без сборки и зависимостей. - Внешние зависимости: `xlxd` (сборка из `https://github.com/LX3JL/xlxd.git`), API YooKassa `https://api.yookassa.ru/v3`. - Железо/протоколы: DMR (UDP 62030 для Pi-Star/MMDVM), YSF (42000), дашборд `xlxd` (8080). ## Структура | Путь | Назначение | |---|---| | `bootstrap.php` | Автозагрузчик, выбор конфига (`config/config.php`, иначе `config/config.example.php`), таймзона. | | `public/index.php` | Единственная точка входа: HTML-страницы `/` и `/admin`, JSON API, webhook YooKassa. | | `public/views/home.php` | Публичная страница: описание рефлектора, проверка позывного, форма регистрации. | | `public/views/admin.php` | Админка: поле admin token, форма настроек `xlxd`, редактор конфиг-файлов, управление сервисом. | | `public/assets/app.js`, `admin.js`, `styles.css` | Логика форм и запросов к API, стили. | | `src/Support/Config.php` | Доступ к конфигу через точечные ключи (`billing.yookassa.shop_id`). | | `src/Support/Database.php` | Создание PDO. | | `src/Support/Input.php`, `Response.php` | Чтение JSON-тела запроса, JSON-ответы и ошибки. | | `src/Support/Security.php` | Нормализация и валидация позывного (`/^[A-Z0-9][A-Z0-9\/-]{2,18}$/`), генерация пароля, подсказка пароля, проверка admin-токена. | | `src/Domain/RegistrationService.php` | Проверка позывного на дубли по локальной базе, регистрация пользователя, создание платежа. | | `src/Domain/PaymentService.php` | Создание платежа по тарифу, подтверждение вручную, приём и одобрение чека, обработка платежа YooKassa, продление `access_expires_at`. | | `src/Domain/YooKassaClient.php` | HTTP-клиент YooKassa: создание платежа с ключом идемпотентности, чтение платежа. | | `src/Domain/IdAllocator.php` | Выдача внутреннего DMR ID из диапазона (по умолчанию 9000001–9099999) с обработкой гонок по unique-индексу. | | `src/Domain/AccessCredentialService.php` | Генерация и ротация отдельного DMR-пароля (хранится хешем, в базе остаётся только подсказка вида `abc...xyz`). | | `src/Domain/AccessExportService.php` | CSV активных пользователей: позывной, DMR ID, username, подсказка пароля, срок доступа. | | `src/Domain/XlxdSettingsService.php` | Настройки `xlxd` в таблице `server_settings`, рендер `/etc/xlx/xlxd.env`, запись файла при `apply=true`. | | `src/Domain/XlxdConfigFileService.php` | Чтение и запись четырёх файлов `xlxd` из `XLX_INSTALL_PATH` (blacklist/whitelist/interlink/terminal). | | `src/Domain/XlxdRuntimeService.php` | `systemctl is-active/is-enabled`, `sudo -n systemctl start|stop|restart`, парсинг последних строк `/var/log/xlxd.log`, счётчики для дашборда. | | `database/schema.sql` | Таблицы: `users`, `callsign_checks`, `dmr_ids`, `tariffs`, `payments`, `payment_receipts`, `access_credentials`, `server_settings`, `connection_logs`, `audit_logs`, `xlx_service_events` + тариф по умолчанию (300 RUB / 30 дней). | | `scripts/install-system.sh` | Полный установщик: пакеты, MariaDB, схема, генерация `config/config.php`, `/etc/xlx/xlxd.env`, Apache vhost, sudoers, сборка `xlxd`, вывод admin-токена и webhook URL. | | `scripts/install-xlxd.sh` | Только сборка и установка `xlxd` из исходников, установка `xlx-control` и юнита. | | `scripts/xlx-control` | start/stop/restart/status/render-modules для `xlxd`, генерация `xlxd.module` из списка модулей. | | `scripts/firewall-ufw.sh` | Открытие портов ufw: 22/80/443 TCP и 8880, 10001, 10002, 30001, 30051, 42000, 62030 UDP. | | `deploy/apache/xlx-panel.conf` | Шаблон vhost с плейсхолдерами `__PANEL_PORT__`, `__PUBLIC_DIR__`. | | `deploy/systemd/xlxd.service` | Юнит `xlxd` типа forking через `xlx-control`. | | `docs/` | `ARCHITECTURE.md`, `ROADMAP.md`, `INSTALL_SYSTEM.md`, `INSTALL_XLXD.md`, `PAYMENTS_AND_REGISTRATION.md`, `ADMIN_DASHBOARD.md`. | | `README.upstream.md` | Прежний README автора. | ## Как запустить Полная установка на чистый Ubuntu/Debian-сервер (от root): ```bash sudo apt update sudo apt install -y git git clone <адрес репозитория> xlx cd xlx sudo bash scripts/install-system.sh ``` Панель копируется в `/opt/xlx-server`, поэтому каталог клона после установки не нужен. Порт панели выбирается автоматически: 80, иначе 8088/8090/8091/8099. В конце установщик печатает адрес сайта, адрес админки, admin token, webhook URL для YooKassa и реквизиты БД — их нужно сохранить, повторно они не показываются. С доменом и платежами: ```bash sudo \ XLX_PUBLIC_HOST=xlx.example.ru \ XLX_PUBLIC_SCHEME=https \ YOOKASSA_SHOP_ID= \ YOOKASSA_SECRET_KEY=<см. .env / secret store> \ bash scripts/install-system.sh ``` Только рефлектор, без панели: ```bash sudo mkdir -p /etc/xlx sudo cp config/xlxd.env.example /etc/xlx/xlxd.env sudo nano /etc/xlx/xlxd.env sudo bash scripts/install-xlxd.sh sudo systemctl start xlxd ``` Эксплуатация: ```bash sudo systemctl {start|stop|restart|status} xlxd sudo tail -f /var/log/xlxd.log sudo bash scripts/firewall-ufw.sh ``` Основные маршруты: | Метод и путь | Назначение | |---|---| | `GET /`, `GET /dashboard` | Публичная страница и регистрация. | | `GET /admin` | Админка. | | `GET /api/health` | Проверка живости API. | | `GET /api/dashboard/stats` | Статус сервиса, счётчики, последние события лога. | | `GET /api/callsigns/check?callsign=` | Проверка формата и занятости позывного. | | `POST /api/register` | Регистрация: позывной, email, пароль. | | `POST /api/payments/receipt` | Отправка номера/ссылки чека при ручном переводе. | | `POST /api/webhooks/yookassa?token=` | Webhook YooKassa, обрабатывается только событие `payment.succeeded`. | | `POST /api/admin/payments/confirm` | Ручное подтверждение платежа. | | `POST /api/admin/receipts/approve` | Одобрение чека. | | `GET /api/admin/access/export` | CSV активных пользователей. | | `GET/POST /api/admin/xlxd/settings` | Чтение и сохранение настроек `xlxd` (с опциональной записью `xlxd.env`). | | `GET/POST /api/admin/xlxd/config-files` | Чтение и запись blacklist/whitelist/interlink/terminal. | | `GET/POST /api/admin/xlxd/runtime` | Статус и управление сервисом (`start`, `stop`, `restart`, `status`). | Все `/api/admin/*` требуют заголовок `X-Admin-Token` со значением admin-токена. ## Конфигурация Конфиг — `config/config.example.php`, все значения читаются из переменных окружения. Установщик генерирует `config/config.php`, который через `putenv()` задаёт значения и подключает example-файл. Реальные секреты после установки лежат в `config/config.php` и `/etc/xlx/xlxd.env` на сервере; в репозитории только плейсхолдеры `change_me`. | Переменная | Назначение | Пример | |---|---|---| | `XLX_PUBLIC_HOST` | Домен или IP панели | `xlx.example.ru` | | `XLX_PUBLIC_SCHEME` | Схема для base URL | `https` | | `XLX_PANEL_PORT` | Порт Apache для панели | `80` | | `XLX_APP_ENV`, `XLX_DEBUG`, `XLX_TIMEZONE` | Окружение, отладка, таймзона | `production`, `0`, `Europe/Moscow` | | `XLX_DB_HOST`, `XLX_DB_PORT`, `XLX_DB_NAME`, `XLX_DB_USER`, `XLX_DB_PASSWORD` | Доступ к БД | `127.0.0.1`, `3306`, `xlx_server`, `xlx_user`, см. вывод установщика | | `XLX_REFLECTOR_NAME` | Имя рефлектора | `XLX391` | | `XLX_SYSOP_CALLSIGN`, `XLX_SYSOP_EMAIL`, `XLX_COUNTRY` | Данные сисопа | `N0CALL`, `admin@example.ru`, `RU` | | `XLX_DMR_PORT`, `XLX_YSF_PORT`, `XLX_DASHBOARD_PORT` | Порты рефлектора | `62030`, `42000`, `8080` | | `XLX_DEFAULT_MODULE`, `XLX_MODULES` | Модуль по умолчанию и список модулей | `A`, `ABCDEFGHIJKLMNOPQRSTUVWXYZ` | | `XLX_CONFIG_PATH`, `XLX_INSTALL_PATH`, `XLX_SOURCE_PATH`, `XLX_LOG_PATH`, `XLX_SERVICE_NAME` | Пути и имя сервиса | `/etc/xlx`, `/xlxd`, `/usr/src/xlxd`, `/var/log/xlxd.log`, `xlxd` | | `XLX_REPO_URL` | Репозиторий исходников `xlxd` | `https://github.com/LX3JL/xlxd.git` | | `XLX_MIN_ID`, `XLX_MAX_ID` | Диапазон выдаваемых внутренних DMR ID | `9000001`, `9099999` | | `XLX_BILLING_MODE`, `XLX_CURRENCY`, `XLX_DEFAULT_DURATION_DAYS` | Режим оплаты, валюта, срок доступа | `yookassa`, `RUB`, `30` | | `XLX_MANUAL_TRANSFER_ENABLED`, `XLX_MANUAL_TRANSFER_INSTRUCTIONS` | Ручной перевод и текст инструкции | `1`, произвольный текст | | `YOOKASSA_SHOP_ID`, `YOOKASSA_SECRET_KEY` | Реквизиты магазина YooKassa | см. `config/config.php` на сервере | | `YOOKASSA_RETURN_URL`, `YOOKASSA_WEBHOOK_SECRET`, `YOOKASSA_CAPTURE` | Возврат после оплаты, токен webhook, автосписание | `/payment/return`, случайный токен, `1` | | `XLX_SESSION_NAME`, `XLX_PASSWORD_MIN_LENGTH`, `XLX_DMR_PASSWORD_LENGTH` | Имя сессии, минимальная длина пароля кабинета, длина DMR-пароля | `xlx_session`, `10`, `16` | | `XLX_ADMIN_TOKEN` | Токен для `X-Admin-Token` | генерируется установщиком (48 символов) | | `XLX_APP_DIR` | Каталог установки панели | `/opt/xlx-server` | Файл `/etc/xlx/xlxd.env` (шаблон `config/xlxd.env.example`) читают `xlx-control` и юнит systemd. ## Состояние Прототип, доведённый до устанавливаемого состояния, работа остановлена. Все 12 коммитов сделаны в один день — 2026-05-18, последний коммит 2026-05-18. `VERSION` — `0.2.0-dev`. Установщик, схема БД, API регистрации и оплаты, управление `xlxd` и редактор его конфигов написаны и внутренне согласованы. На боевом сервере проект не подтверждён из кода. ## Что не доделано - Ключевой пункт из плана автора — UDP access gateway, который должен реально пускать к `xlxd` только оплаченных пользователей, — не реализован. Сейчас оплата и выданный DMR-пароль ни на что не влияют: `xlxd` принимает подключения по своим правилам, а связь панели с ним ограничена CSV-экспортом (`/api/admin/access/export`) и ручным редактированием whitelist/blacklist. - Нет личного кабинета и вообще авторизации пользователей: пароль при регистрации хешируется и сохраняется, но маршрута логина и сессий нет, хотя настройки сессии в конфиге есть. Пользователь получает DMR ID и пароль один раз в ответе на `POST /api/register` и посмотреть их повторно не может. - Админка защищена одним статическим токеном в заголовке, который вводится в поле на странице; ролей `moderator`/`admin` из схемы БД код не использует, `admin_user_id` приходит с клиента и не проверяется. - Таблицы `callsign_checks`, `connection_logs`, `audit_logs`, `xlx_service_events` созданы, но код в них ничего не пишет. Дашборд «последние события» парсит текстовый лог `/var/log/xlxd.log`, а не БД. - Проверка позывного идёт только по локальной базе зарегистрированных; сверки с внешним реестром (RadioID/QRZ) нет, хотя поле `source` в `callsign_checks` это предполагает. - Тарифов в UI нет — используется единственная запись, вставленная схемой (300 RUB / 30 дней); управления тарифами и продлением из админки нет. - `docs/ROADMAP.md` почти целиком не отмечен: из ~70 пунктов выполнено 4. Не сделаны миграции БД, бэкапы и обновления, rate-limit, SSL helper, уведомления пользователю, автопродление. - Тестов нет вообще, CI нет, зависимостей Composer нет. - Маркеров TODO/FIXME в коде нет — незавершённость видна по roadmap и пустым таблицам. - `README.upstream.md` описывает часть возможностей как готовые (например, «экспорт активных пользователей для будущего access gateway»), что стоит сверять с этим списком.