From dbf535bcc53b90eaa3b6093ca0b2baf5b93ebb5f Mon Sep 17 00:00:00 2001 From: Divarion_D Date: Fri, 21 Aug 2026 16:31:01 +0300 Subject: [PATCH] docs(translate): robust {N} sentinels + possessive handling; regenerate ru MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix Markdown-mangling artifacts the free web engine (yandex) produced in the committed docs/ru, and regenerate the whole tree cleanly (0 fallbacks): - Sentinel format @@N@@ -> {N}. MT engines are trained to preserve curly format-string placeholders, so {N} survives code-heavy lines where @@N@@ (and ZZZ…ZZZ, which also duplicated its Z) were split/moved — e.g. the stray "@0@@" in the FAQ and "load balancerZ" in the README are gone. - Possessive: a trailing English `'s` is consumed INTO the masked span and dropped on restore. Every sentinel format breaks when a bare `'s` sits right after it, and Russian has no possessive `'s`. - Validate + retry: after restore, any leftover brace fragment triggers a retry (the engine is non-deterministic); after a few failures the line stays English so a broken token is never emitted. - Glossary += KeyDB, yt-dlp, Ubuntu, iptables, MAGSCAN. Regenerated docs/ru (37 files, translators/yandex): no residual sentinels, mkdocs build --strict clean. --- docs/ru/README.md | 14 +- docs/ru/administration/backup-strategy.md | 20 +- docs/ru/administration/reseller-system.md | 16 +- docs/ru/administration/server-diagnostics.md | 42 ++--- docs/ru/administration/server-update.md | 2 +- docs/ru/administration/ssl-generation.md | 12 +- docs/ru/administration/update-system.md | 12 +- docs/ru/api/swagger.md | 10 +- docs/ru/builds/build_system.md | 30 +-- docs/ru/builds/updates_checklist.md | 58 +++--- docs/ru/development/architecture.md | 22 +-- docs/ru/development/autoloader.md | 30 +-- docs/ru/development/bootstrap-contexts.md | 12 +- docs/ru/development/caching-and-redis.md | 40 ++-- docs/ru/development/event-system.md | 12 +- docs/ru/development/exception-hierarchy.md | 4 +- docs/ru/development/http-request-handling.md | 54 +++--- docs/ru/development/modules.md | 176 +++++++++--------- docs/ru/development/navbar-rendering.md | 10 +- docs/ru/development/streaming-subsystem.md | 76 ++++---- docs/ru/guides/authentication-and-sessions.md | 94 +++++----- docs/ru/guides/cli-tools.md | 54 +++--- docs/ru/guides/dev-workflow.md | 20 +- docs/ru/guides/diagnostics-api.md | 14 +- docs/ru/guides/error-handling.md | 54 +++--- docs/ru/guides/feature-flags.md | 10 +- docs/ru/guides/geoip-and-device-detection.md | 20 +- docs/ru/guides/input-validation.md | 28 +-- docs/ru/guides/ministra-browser-emulation.md | 24 +-- docs/ru/guides/permissions-and-rbac.md | 64 +++---- docs/ru/guides/phpunit-phar.md | 4 +- docs/ru/guides/process-management.md | 6 +- docs/ru/guides/translations.md | 16 +- docs/ru/guides/ucs-integration.md | 18 +- docs/ru/info/faq.md | 66 +++---- docs/ru/info/migration_guide.md | 4 +- docs/ru/info/watch_folder.md | 14 +- tools/docs/glossary.txt | 5 + tools/docs/translate.py | 43 ++++- 39 files changed, 619 insertions(+), 591 deletions(-) diff --git a/docs/ru/README.md b/docs/ru/README.md index 4f123fb3..24803757 100644 --- a/docs/ru/README.md +++ b/docs/ru/README.md @@ -8,14 +8,14 @@ ## что это такое? -** XC_VM** - это современная IPTV-панель, построенная на PHP, Nginx, FFmpeg и MariaDB. +**XC_VM** - это современная IPTV-панель, построенная на PHP, Nginx, FFmpeg и MariaDB. -XC_VM поможет вам развернуть полноценную инфраструктуру IPTV: +XC_VM помогает вам развернуть полноценную инфраструктуру IPTV: - Управление телевизионными трансляциями, фильмами и сериалами в прямом эфире - Управление торговыми посредниками и пользователями - балансировка нагрузки -- EPG и VOD система +- система EPG и VOD - Инструменты мониторинга и API 💡 * Абсолютно бесплатно. Никаких лицензий. Никаких блокировок сервера.* @@ -39,13 +39,13 @@ XC_VM поможет вам развернуть полноценную инфр ## Технологии - **Nginx** — обратный прокси и веб-сервер -- **PHP 8.1** — основная серверная часть +- **PHP 8.1** — базовый сервер - **MariaDB** — база данных -- **База данных ключей** — механизм кэширования/сеанса +- **KeyDB** — механизм кэширования/сеанса - **FFmpeg 8.0** — перекодирование -- **yt-dlp** — медиа-эквайринг +- **yt-dlp** — получение мультимедийных данных -XC_VM официально поддерживает Ubuntu 22.04 и протестирован 24.04. +XC_VM официально поддерживается Ubuntu с 22.04 и протестирован 24.04. --- diff --git a/docs/ru/administration/backup-strategy.md b/docs/ru/administration/backup-strategy.md index cb81a90c..8d61f9cf 100644 --- a/docs/ru/administration/backup-strategy.md +++ b/docs/ru/administration/backup-strategy.md @@ -21,9 +21,9 @@ users_credits_logs, users_logs, watch_logs Резервные копии **не** включают: -- Данные файловой системы (записи, VOD files, EPG XML) +- Данные файловой системы (записи, VOD файлов, EPG XML) - Файлы конфигурации (`config/`) -- Бинарные зависимости (`bin/`) +- Двоичные зависимости (`bin/`) - Временные файлы (`tmp/`) --- @@ -67,8 +67,8 @@ users_credits_logs, users_logs, watch_logs ### Процесс резервного копирования -1. Закройте MySQL соединение перед сбросом. -2. Запустите `mysqldump --no-data` (структура) + `mysqldump --ignore-table` (данные, за исключением таблиц журнала). +1. Закройте соединение MySQL перед сбросом. +2. Запустите `mysqldump --no-data` (структура) + `mysqldump --ignore-table` (данные, исключая таблицы журнала). 3. Проверьте размер файла (пустые файлы удаляются). 4. Если включен Dropbox: загружайте с отслеживанием статуса. 5. Примените политику хранения (удалите самые старые файлы, превышающие установленный лимит). @@ -89,7 +89,7 @@ users_credits_logs, users_logs, watch_logs Процесс: -1. Если существует локальный файл, используйте его. В противном случае загрузите из Dropbox на `/home/xc_vm/tmp/restore.sql`. +1. Если существует локальный файл, используйте его. В противном случае загрузите из Dropbox в `/home/xc_vm/tmp/restore.sql`. 2. Удалите базу данных и создайте ее заново. 3. Импортируйте SQL-файл. 4. Повторно загрузите структуру после импорта. @@ -117,11 +117,11 @@ sudo /home/xc_vm/console.php tools migration /path/to/backup.sql ### Локальное удержание - Если `backups_to_keep > 0`: сохраняются только N самых последних файлов. Сначала удаляются самые старые. -- If `backups_to_keep = 0`: сохраняет все файлы (неограниченно). +- If `backups_to_keep = 0`: сохраняет все файлы (неограниченное количество). ### Удаленное хранение -- Если `dropbox_keep > 0`: в Dropbox сохраняются только N самых последних файлов. Самые старые удаляются первыми. +- If `dropbox_keep > 0`: сохраняет только N самых последних файлов в Dropbox. Самые старые удаляются первыми. - If `dropbox_keep = 0`: сохраняет все удаленные файлы (неограниченное количество). Очистка выполняется автоматически после каждого резервного копирования с помощью `BackupsCronJob`. @@ -132,12 +132,12 @@ sudo /home/xc_vm/console.php tools migration /path/to/backup.sql Файл: `src/Core/Storage/DropboxClient.php` -Когда `dropbox_remote` включен: +Когда параметр `dropbox_remote` включен: 1. После создания локальной резервной копии загрузите ее в Dropbox. -2. Во время загрузки создается файл `.uploading` marker. +2. Во время загрузки создается файл маркера `.uploading`. 3. В случае успеха: `.uploading` удаляется. -4. При сбое: `.error` создается файл с сообщением об ошибке. +4. On failure: `.error` file is created with the error message. Индикаторы состояния панели администратора: diff --git a/docs/ru/administration/reseller-system.md b/docs/ru/administration/reseller-system.md index 31deb144..adb34b38 100644 --- a/docs/ru/administration/reseller-system.md +++ b/docs/ru/administration/reseller-system.md @@ -53,7 +53,7 @@ if (isset($rOverride[$rPackage['id']]['official_credits'])) { ### Регистрация -Все кредитные операции записываются в `users_logs`: +Все кредитные операции отражаются в `users_logs`: |Поле|Описание| | --- | --- | @@ -84,7 +84,7 @@ if (isset($rOverride[$rPackage['id']]['official_credits'])) { 3. Сгенерируйте имя пользователя/пароль, если это разрешено разрешениями. 4. Применить пакет: `exp_date`, `max_connections`, `bouquets`, `allowed_outputs`. 5. Установите ограничения: `allowed_ips` (JSON), `allowed_ua`, `bypass_ua`, `is_isplock`. -6. Вставьте в таблицу `lines` через `REPLACE INTO`. +6. Вставить в таблицу `lines` через `REPLACE INTO`. 7. Синхронизируйте записи устройства (`mag_devices` или `enigma2_devices`). 8. Передача сигнала о событии на потоковые серверы. 9. Вычтите кредиты и зарегистрируйте транзакцию. @@ -100,12 +100,12 @@ if (isset($rOverride[$rPackage['id']]['official_credits'])) { ### МАГНИТНЫЕ устройства -Управляемый `MagService` (`src/Domain/Device/MagService.php`). +Управляется с помощью `MagService` (`src/Domain/Device/MagService.php`). Поля блокировки: `ver`, `device_id2`, `device_id`, `hw_version`, `image_version`, `stb_type`, `sn`. ### Устройства Enigma2 -Управляемый `EnigmaService` (`src/Domain/Device/EnigmaService.php`). +Управляется с помощью `EnigmaService` (`src/Domain/Device/EnigmaService.php`). Поля блокировки: `token`, `lversion`, `cpu`, `enigma_version`, `modem_mac`, `local_ip`. Оба типа устройств поддерживают `lock_device` (аппаратная привязка), `is_isplock` (привязка к провайдеру) и `forced_country`. @@ -114,7 +114,7 @@ if (isset($rOverride[$rPackage['id']]['official_credits'])) { ## Иерархия суб-реселлеров -Реселлеры могут создавать суб-реселлеров (при наличии разрешения `create_sub_resellers`).: +Реселлеры могут создавать суб-реселлеров (если получено разрешение `create_sub_resellers`).: - Суб-реселлеры связаны через поле `owner_id`. - Многоуровневый: суб-реселлер может создавать своих собственных суб-реселлеров. @@ -140,7 +140,7 @@ AuthRepository::getGroupPermissions() // builds all_reports recursively ## Разрешения -Разрешения поступают из таблицы `users_groups`, загружаемой через `AuthRepository::getPermissions()`. +Разрешения берутся из таблицы `users_groups`, загружаемой через `AuthRepository::getPermissions()`. ### Ключевые поля разрешений @@ -152,7 +152,7 @@ AuthRepository::getGroupPermissions() // builds all_reports recursively | `create_enigma` | `bool` |может создавать устройства Enigma2| | `create_sub_resellers` | `bool` |может создавать суб-реселлеров| | `create_sub_resellers_price` | `int` |стоимость кредита на одного суб-реселлера| -| `allow_change_bouquets` | `bool` |можно выбрать bouquet подмножество| +| `allow_change_bouquets` | `bool` |можно выбрать подмножество bouquet| | `allow_change_username` | `bool` |можно установить пользовательское имя пользователя| | `allow_change_password` | `bool` |можно установить пользовательский пароль| | `allow_restrictions` | `bool` |можно установить ограничения по IP/UA| @@ -209,7 +209,7 @@ AuthRepository::getGroupPermissions() // builds all_reports recursively |`activity_logs` / `live_connections`|данные о подключении| | `user_logs` |журналы действий суб-реселлеров| -Класс `ResellerAPIWrapper` проверяет API-ключ, инициализирует сеанс с помощью `ResellerAPI` и возвращает отфильтрованные ответы в формате JSON. +Класс `ResellerAPIWrapper` проверяет ключ API, инициализирует сеанс с помощью `ResellerAPI` и возвращает отфильтрованные ответы в формате JSON. --- diff --git a/docs/ru/administration/server-diagnostics.md b/docs/ru/administration/server-diagnostics.md index 46917801..a7d250a7 100644 --- a/docs/ru/administration/server-diagnostics.md +++ b/docs/ru/administration/server-diagnostics.md @@ -1,20 +1,20 @@ # Диагностика сервера (`server:diagnose`) -Панель помечает узел proxy/LB как "отключенный" исключительно из-за устаревшего сердцебиения (`enabled` + `status = 1` + недавний `last_check_ago`), который никогда не сообщает вам, почему узел перестал отчитываться. Команда `server:diagnose` отвечает на этот вопрос. +Панель помечает узел proxy/LB как "отключенный" исключительно из-за устаревшего сердцебиения (`enabled` + `status = 1` + недавнее `last_check_ago`), которое никогда не сообщает вам, почему узел перестал сообщать. Команда `server:diagnose` отвечает на этот вопрос. ```bash /home/xc_vm/console.php server:diagnose [server_id] ``` -Команда доступна только для чтения: она запускает только запросы ping/curl/`fsockopen`, `SELECT` и `sudo -n iptables -nL`. Она никогда ничего не перезапускает и не перенастраивает. +Команда доступна только для чтения: она выполняет только проверки ping/curl/`fsockopen`, `SELECT` и `sudo -n iptables -nL`. Она никогда ничего не перезапускает и не перенастраивает. -Реализовано `src/Cli/Commands/ServerDiagnoseCommand.php`. Команда поставляется как в основной, так и в LB сборках — весь смысл ее использования на узле заключается в локальном режиме. +Реализовано с помощью `src/Cli/Commands/ServerDiagnoseCommand.php`. Команда поставляется как в основной, так и в LB сборках — весь смысл ее использования на узле заключается в локальном режиме. --- ## Два режима -Режим автоматически выбирается из того места, где выполняется команда (`server_id` в `config.ini` → `is_main` в таблице `servers`): +Режим автоматически выбирается из того, в котором выполняется команда (`server_id` в `config.ini` → `is_main` в таблице `servers`).: ### Режим A — дистанционный датчик от ОСНОВНОГО @@ -26,19 +26,19 @@ sudo /home/xc_vm/console.php server:diagnose |Проверять|О чем это вам говорит| | --- | --- | -|Включено / Статус / Сердцебиение|Как панель в данный момент видит узел (`status = 4` означает неудачную установку/подготовку)| +|Включено / Статус / Сердцебиение|Как панель в данный момент видит узел (`status = 4` означает сбой установки/подготовки)| |Пинг по протоколу ICMP|Жив ли вообще носитель| |TCP к `http_broadcast_port`|Доступен ли nginx, или порт удален| -|HTTP `GET /api`|Действительно ли @@ 1@@ отвечает за @@ 0@@| +|HTTP `GET /api`|Действительно ли PHP отвечает за nginx| |Смещение часов|`time_offset` против панели (перекос > 30 с может привести к переключению узла в режим онлайн/оффлайн)| |Очередь сигналов|Неиспользованные строки в `signals` для этого узла (задержка > 120 секунд = цикл обратного вызова узла застрял)| Комбинации датчиков соответствуют причинам: - **Порт ICMP + не закрыт** — узел выключен, разделен сетью или полностью защищен брандмауэром. -- **ICMP отвечает, но порт не указан ** — классический случай: собственные iptables узла заблокировали IP-адрес главного сервера (RootSignals выдает ложные срабатывания), или nginx/служба не работает. Запустите локальный режим на узле для выяснения точной причины. -- **Порт открыт, но `/api` молчит** — nginx активен, PHP нет: проверьте php-fpm на узле. -- **`/api` отвечает, но частота сердечных сокращений устарела ** — демон узла watchdog (программа записи сердечных сокращений) не запущен или он не может выполнять запись в базу данных панели. Запустите локальный режим на узле. +- **ICMP replies but the port is dropped** — the classic: the node's own iptables blocked the main's IP (RootSignals flood/block false-positive), or nginx/the service is down. Run the local mode on the node for the exact cause. +- **Порт открыт, но `/api` молчит** — nginx включен, PHP нет: проверьте php-fpm на узле. +- **`/api` отвечает, но частота сердцебиения устарела ** — демон узла watchdog (программа записи сердцебиений) не запущен или не может выполнить запись в базу данных панели. Запустите локальный режим на узле. ### Режим B — локальная самодиагностика НА узле @@ -52,20 +52,20 @@ sudo /home/xc_vm/console.php server:diagnose 2. **Могу ли я подключиться к ГЛАВНОМУ** — подключение к базе данных (неявно подтвержденное), ICMP и TCP к широковещательному порту главного. 3. **Отключил ли я брандмауэр main?** — сканирует цепочку `iptables INPUT` этого узла на наличие `DROP` IP-адреса main, а также файла маркера блокировки флуда. Это классическая причина "узел отключился без причины": защита от наводнений автоматически отключает общедоступные IP-адреса, и обратные вызовы главного сервера перестают поступать. 4. **Сервис / nginx** — `systemctl is-active xc_vm` и локальная проверка TCP на собственном широковещательном порту узла. -5. ** Сторожевой демон ** — фактический регистратор сердцебиений: демон `watchdog` обновляет `last_check_ago` каждые несколько секунд. Когда его MySQL соединение с main прерывается, он ** ожидает возврата к базе данных ** (повторяя попытку каждые 5 секунд) и немедленно возобновляет сердцебиение. Вместо этого были запущены более старые сборки, из—за чего ** все узлы отключались в один и тот же момент ** при любом MySQL перезапуске / сбое на главном сервере, пока `cron:servers` не восстановил их. +5. ** Сторожевой демон** — фактически записывающий сердцебиение: демон `watchdog` обновляет `last_check_ago` каждые несколько секунд. Когда его MySQL подключение к главному серверу прерывается, он ** ожидает восстановления базы данных** (повторяя попытку каждые 5 секунд) и немедленно возобновляет сердцебиение. Вместо этого были запущены более старые сборки, из—за чего ** все узлы отключались в один и тот же момент ** при любом MySQL перезапуске / сбое на главном сервере, пока `cron:servers` не восстановил их. 6. **Хрон няни** — три дополнительные проверки, потому что мертвый watchdog остается мертвым только тогда, когда цепочка няни разорвана: - - присутствует ли `cron:servers` в **`xc_vm` пользовательской crontab** (если отсутствует, создайте заново с помощью `rm -f /home/xc_vm/tmp/crontab` и перезапустите службу); + - присутствует ли `cron:servers` в crontab пользователя **`xc_vm`** (если отсутствует, создайте заново с помощью `rm -f /home/xc_vm/tmp/crontab` и перезапустите службу); - активна ли системная служба cron (нет cron → crontab никогда не запускается); - - является ли предыдущий экземпляр `cron:servers` зависшим из—за блокировки cron ** - зависший экземпляр блокирует каждый последующий запуск на срок до 30 минут (`acquireCronLock` устаревший тайм-аут), что в точности соответствует тому, как один сбой в работе базы данных удерживает узел в автономном режиме в течение получаса. Команда выводит удерживающий PID и команду завершения. -7. **Перекос часов** — `time_offset` по сравнению с панелью. + - является ли предыдущий экземпляр `cron:servers` ** зависшим из—за блокировки cron ** - зависший экземпляр блокирует каждый последующий запуск на срок до 30 минут (`acquireCronLock` истекший тайм-аут), что в точности соответствует тому, как один сбой в работе базы данных удерживает узел в автономном режиме в течение получаса. Команда выводит удерживающий PID и команду завершения. +7. **Перекос часов** — `time_offset` относительно панели. -> **Примечание:** для проверки iptables требуется sudo без пароля (`sudo -n`). Без этого проверка выдает `cannot check (need sudo iptables)` вместо сбоя — запустите команду как `root` для получения полной картины. +> **Примечание:** для проверки iptables требуется sudo без пароля (`sudo -n`). Без него проверка выдает сообщение о `cannot check (need sudo iptables)` вместо сбоя — запустите команду как `root` для получения полной картины. --- ## Коды вывода и выхода -При каждой проверке выводится одна выровненная строка `[OK]`/`[WARN]`, за которой следует пронумерованная сводка о возможных причинах с указанием точной команды устранения, если таковая существует (например, строка `iptables -D INPUT ... -j DROP` разблокировать). +При каждой проверке выводится одна выровненная строка `[OK]`/`[WARN]`, за которой следует пронумерованная сводка о возможных причинах с указанием точной команды устранения, если таковая существует (например, строка разблокировки `iptables -D INPUT ... -j DROP`). |Код выхода|Значение| | --- | --- | @@ -102,13 +102,13 @@ Probable cause(s): |Симптом|Вероятная причина|Чинить| | --- | --- | --- | -|Звенит, порт сброшен|Iptables узла заблокировали основной IP-адрес|`sudo iptables -D INPUT -s -j DROP` + удалить маркер `block_` (команда выводит точную строку)| +|Звенит, порт сброшен|iptables узла заблокировал основной IP-адрес|`sudo iptables -D INPUT -s -j DROP` + удалить маркер `block_` (команда выводит точную строку)| |Ни пинга, ни порта|Отключен хост / сетевой раздел / внешний брандмауэр|Проверьте консоль хостинга, маршруты, брандмауэр провайдера| -|Порт открыт, `/api` мертв|сбой php-fpm|Перезапустите службу `xc_vm` на узле| -|`/api` прекрасно, сердцебиение замедлилось.|Сторожевой демон мертв (завершает работу при потере соединения с базой данных)|`sudo -u xc_vm console.php watchdog` на узле; проверьте разрешения базы данных (`tools mysql` на главном)| -|**Все узлы падают в один и тот же момент**|Перезапуск/сбой MySQL на главном сервере приводит к одновременному сбою всех узлов watchdog (фатально для сборок до исправления; текущие сборки переждут это)|Проверьте журнал ошибок main MySQL во время сброса; обновите узлы, чтобы watchdog пережил перебои в работе| +|Порт открыт, `/api` отключен|сбой php-fpm|Перезапустите службу `xc_vm` на узле| +|`/api` отлично, сердцебиение замедлилось|Сторожевой демон мертв (завершает работу при потере соединения с базой данных)|`sudo -u xc_vm console.php watchdog` на узле; проверьте разрешения базы данных (`tools mysql` на главном сервере)| +|**Все узлы падают в один и тот же момент**|Перезапуск MySQL /сбой на главном сервере приводит к одновременному сбою watchdog на каждом узле (фатально для сборок до исправления; текущие сборки переждут это).|Проверьте журнал ошибок MySQL на главном сервере во время сброса; обновите узлы, чтобы watchdog пережил перебои в работе| |Закрылки узлов онлайн/оффлайн|Перекос часов > 30 с (или повторяющиеся сигналы MySQL)|Синхронизируйте протокол NTP на узле; проверьте стабильность MySQL на главном| -|Статус = 4|Произошла ошибка установки/обеспечения|Повторно запустите `server:install` из главного| +|Статус = 4|Произошла ошибка установки/обеспечения|Повторный запуск `server:install` из основного| --- @@ -119,7 +119,7 @@ Probable cause(s): | `src/Cli/Commands/ServerDiagnoseCommand.php` |Диагностическая команда (в обоих режимах)| | `src/Cli/Commands/WatchdogCommand.php` |Демон watchdog — записывает сердцебиение (`last_check_ago`)| | `src/Cli/CronJobs/ServersCronJob.php` |Хрон няни — перезапускает мертвого watchdog| -| `src/Cli/CronJobs/RootSignalsCronJob.php` |Применяет блоки iptables (источник ложных срабатываний)| +| `src/Cli/CronJobs/RootSignalsCronJob.php` |Применяет блокировки iptables (источник ложных срабатываний)| | `src/Domain/Server/ServerRepository.php` |`servers` доступ к таблице| Смотрите также: [Инструменты интерфейса командной строки](../guides/cli-tools.md), [Обновление сервера](../administration/server-update.md). diff --git a/docs/ru/administration/server-update.md b/docs/ru/administration/server-update.md index 9c526f3c..5f49532e 100644 --- a/docs/ru/administration/server-update.md +++ b/docs/ru/administration/server-update.md @@ -26,7 +26,7 @@ ![Update Server button](../../_media/update5.png) -При нажатии на кнопку в базу данных вводится сигнал `update`. В течение минуты CRON распознает его, и обновление выполняется автоматически: панель останавливается, обновляется и перезапускается. Вы можете отслеживать прогресс с помощью версии сервера в таблице **Управление серверами**. +При нажатии на кнопку в базу данных заносится сигнал `update`. В течение минуты CRON распознает его, и обновление выполняется автоматически: панель останавливается, обновляется и перезапускается. Вы можете отслеживать прогресс с помощью версии сервера в таблице **Управление серверами**. ## Обновление вручную (CLI) diff --git a/docs/ru/administration/ssl-generation.md b/docs/ru/administration/ssl-generation.md index 3718affa..7eb6d8c6 100644 --- a/docs/ru/administration/ssl-generation.md +++ b/docs/ru/administration/ssl-generation.md @@ -3,7 +3,7 @@ В этом руководстве объясняется, как создать самозаверяющий SSL-сертификат, чтобы включить безопасные HTTPS-соединения для встроенного сервера Nginx в проекте XC_VM. > **Примечание:** При новой установке уже создается ** уникальный** самозаверяющий сертификат -> автоматически (установщик запускает `openssl` и записывает `server.key`/`server.crt` +> автоматически (программа установки запускает `openssl` и записывает `server.key`/`server.crt` > в `bin/nginx/conf/` перед запуском Nginx), а `CertbotCronJob` позже заменяет > с настоящим сертификатом Let's Encrypt. Следуйте этому руководству только для ** восстановления > или замените** сертификат вручную. @@ -13,7 +13,7 @@ ## Обзор **SSL (Secure Sockets Layer)** шифрует соединение между клиентом и сервером, обеспечивая конфиденциальность данных и доверие пользователей. -В этом руководстве показано, как создать самозаверяющий SSL-сертификат для встроенного сервера Nginx в проекте @XC_VM*. +В этом руководстве показано, как создать самозаверяющий SSL-сертификат для встроенного сервера Nginx в проекте XC_VM. --- @@ -104,13 +104,13 @@ openssl req -new -x509 -key server.key -out server.crt -days 3650 -config server - `-new -x509` — создает новый самозаверяющий сертификат. - `-days 3650` — срок действия сертификата (10 лет) - `-config server.cnf` — использует пользовательскую конфигурацию -- Результат: `server.crt` файл, содержащий общедоступный сертификат. +- Результат: `server.crt` файл, содержащий общедоступный сертификат --- ## Окончательные файлы -После выполнения всех шагов в @@0 должны присутствовать следующие файлы@@: +После выполнения всех шагов в `/home/xc_vm/bin/nginx/conf` должны присутствовать следующие файлы: |Файл|Цель| | --- | --- | @@ -122,7 +122,7 @@ openssl req -new -x509 -key server.key -out server.crt -days 3650 -config server ## Результат -Ваш сервер **XC_VM Nginx** теперь доступен по протоколу **HTTPS** с использованием недавно созданного самозаверяющего сертификата. +Ваш **XC_VM сервер Nginx** теперь доступен по **протоколу HTTPS** с использованием недавно созданного самозаверяющего сертификата. Браузеры будут отображать предупреждение “не доверенный” — это ожидаемое поведение для самозаверяющих сертификатов. --- @@ -131,7 +131,7 @@ openssl req -new -x509 -key server.key -out server.crt -days 3650 -config server - Самозаверяющие сертификаты подходят ** только для внутреннего использования или тестирования**. - Для общедоступных доменов используйте сертификаты от доверенных центров сертификации (например, [Let's Encrypt](https://letsencrypt.org/)). -- Если вы измените имя домена/хоста (`CN` или `DNS.1`), вы должны повторно создать сертификат. +- Если вы измените имя домена/хоста (`CN` или `DNS.1`), вы ** должны повторно создать** сертификат. - Чтобы проверить сгенерированный сертификат: ```bash diff --git a/docs/ru/administration/update-system.md b/docs/ru/administration/update-system.md index a27aec03..71cb62ea 100644 --- a/docs/ru/administration/update-system.md +++ b/docs/ru/administration/update-system.md @@ -2,7 +2,7 @@ Система обновления XC_VM реализована как многоуровневый процесс, от веб-интерфейса до сценариев системного уровня. Такой подход обеспечивает надежность, автоматизацию и целостность данных при обновлении панели. -> 📋 Пошаговое руководство со скриншотами смотрите в разделе [Обновление сервера](../administration/server-update.md). +> 📋 Пошаговое руководство со скриншотами см. в разделе [Обновление сервера](../administration/server-update.md). --- @@ -23,7 +23,7 @@ /home/xc_vm/console.php cron:root_signals ``` -Задание `root_signals` cron проверяет наличие новых сигналов. +Задание cron `root_signals` проверяет наличие новых сигналов. Когда он обнаруживает сигнал `update`, он запускает: ```bash @@ -32,7 +32,7 @@ --- -## 3. Управление обновлениями (PHP Уровень) +## 3. Управление обновлениями (уровеньPHP) Основная логика находится в классе `UpdateCommand`: @@ -54,7 +54,7 @@ src/Cli/Commands/UpdateCommand.php sudo /usr/bin/python3 /home/xc_vm/update "/home/xc_vm/tmp/.update.tar.gz" "HASH" > /dev/null 2>&1 & ``` -> 💡 После завершения обновления Python он вызывает `console.php update post-update`, который запускает [миграцию базы данных](../guides/cli-tools.md#database-updates-after-version-upgrade) и очистку после обновления. +> 💡 После завершения обновления Python программа вызывает `console.php update post-update`, что запускает [миграцию базы данных](../guides/cli-tools.md#database-updates-after-version-upgrade) и очистку после обновления. --- @@ -101,7 +101,7 @@ sudo /usr/bin/python3 /home/xc_vm/update "/home/xc_vm/tmp/.update.tar.gz" "HASH" 8. **Перезапустите** панель в нормальном рабочем режиме. 9. **Очистите временный каталог и удалите архив. -> ➡ ️ Для установки и обновления используется один и тот же архив. Фильтрация выполняется на сервере во время обновления — список исключений определяется непосредственно в `src/update`. +> ℹ️ Один и тот же архив используется как для установки, так и для обновления. Фильтрация выполняется на сервере во время обновления — список исключений определяется непосредственно в `src/update`. --- @@ -162,7 +162,7 @@ sudo /usr/bin/python3 /home/xc_vm/update "/home/xc_vm/tmp/.update.tar.gz" "HASH" ## ключевые функции -- **Двойная проверка целостности ** (оба уровня PHP и Python проверяют хэш). +- **Двойная проверка целостности ** (хэш проверяется как на уровне PHP, так и на уровне Python). - **Автоматическое распространение обновлений с MAIN на все подсистемы балансировки нагрузки. - **Очистка ** от устаревших файлов и нормализация разрешений. - **Перезапуск безопасной панели ** после установки. diff --git a/docs/ru/api/swagger.md b/docs/ru/api/swagger.md index 2f842dc3..623b8b30 100644 --- a/docs/ru/api/swagger.md +++ b/docs/ru/api/swagger.md @@ -1,12 +1,12 @@ # Интерактивная ссылка на API (Swagger) -Интерактивный, всегда синхронизированный пользовательский интерфейс Swagger для каждого XC_VM API, созданный на основе спецификаций OpenAPI 3.0. Используйте вкладки API в верхней части страницы для переключения между API или откройте один из них напрямую по ссылкам ниже. +Интерактивный, всегда синхронизируемый пользовательский интерфейс Swagger для каждого XC_VM API, созданный на основе спецификаций OpenAPI версии 3.0. Используйте вкладки API в верхней части страницы для переключения между API или откройте один из них напрямую по ссылкам ниже. |интерфейс прикладного программирования|Описание|Автор|Открыть| | --- | --- | --- | --- | |**API администратора**|XUI.ONE-совместимое администрирование — линии, пользователи, потоки, VOD, серии, серверы, настройки (104 конечные точки)|`api_key` + код доступа|[Открыть ↗](../../_media/swagger-ui.html?spec=admin)| -|**Системный API**|Внутренний `/api.php` — поток/VOD управление, статистика, процессы, файлы, подключения (31 действие)|`password` (`live_streaming_pass`)|[Открыть ↗](../../_media/swagger-ui.html?spec=system)| -|**API для игроков**|Проигрыватель XtreamCodes — Прямой эфир, VOD, Серия, EPG|`username` + `password`|[Открыть ↗](../../_media/swagger-ui.html?spec=player)| +|**Системный API**|Внутренний `/api.php` — поток/VOD управление, статистика, процессы, файлы, соединения (31 действие)|`password` (`live_streaming_pass`)|[Открыть ↗](../../_media/swagger-ui.html?spec=system)| +|**API для игроков**|Проигрыватель XtreamCodes — Прямые трансляции, VOD, Сериалы, EPG|`username` + `password`|[Открыть ↗](../../_media/swagger-ui.html?spec=player)| |**API списка воспроизведения**|`/playlist` аутентификация + создание списка воспроизведения|`username`/`password` или `token`|[Открыть ↗](../../_media/swagger-ui.html?spec=playlist)| --- @@ -14,7 +14,7 @@ ## Используя "Попробуй это" 1. Откройте спецификацию, затем используйте вкладки **API** для переключения API и вкладки **Документация / Интерактивные (Swagger)** для каждого API. -2. Разверните любую конечную точку → ** Попробуйте ** → заполните параметры → ** Выполните**, чтобы увидеть реальный URL запроса, команду cURL и оперативный ответ. +2. Разверните любую конечную точку → ** Попробуйте ** → заполните параметры → ** Выполните**, чтобы увидеть реальный URL-адрес запроса, команду cURL и оперативный ответ. 3. Чтобы получить доступ к API администратора, нажмите "Авторизоваться" и вставьте свой API—ключ - затем он автоматически прикрепляется к каждому запросу. > ** Примечание CORS:** CORS может заблокировать прямые вызовы "Попробуйте" из браузера на ваш сервер. Это ожидаемо — вместо этого используйте сгенерированную команду cURL, Postman или Insomnia. Ошибка не означает, что API не работает. @@ -23,7 +23,7 @@ ## Исходные технические характеристики -Каждая спецификация может быть импортирована в Postman, Insomnia или любой другой инструментарий OpenAPI 3.0: +Каждая спецификация может быть импортирована в Postman, Insomnia или любой другой инструментарий OpenAPI версии 3.0: - [`admin-api.openapi.yaml`](../../_media/admin-api.openapi.yaml) - [`system-api.openapi.yaml`](../../_media/system-api.openapi.yaml) diff --git a/docs/ru/builds/build_system.md b/docs/ru/builds/build_system.md index 732770cc..fce51435 100644 --- a/docs/ru/builds/build_system.md +++ b/docs/ru/builds/build_system.md @@ -40,15 +40,15 @@ XC_VM поддерживает две роли развертывания из `src/vendor/` (автозагрузчик Composer PSR-4 плюс производственные зависимости) - это **зафиксировано** и отправлено как есть - путь развертывания не содержит Composer и никогда не выполняется -`composer install`. Он поддерживается только в рабочем состоянии через `composer install --no-dev`, так что +`composer install`. Он поддерживается только для производства через `composer install --no-dev`, так что оба варианта сборки предназначены для бережливого производства без инструментов разработки. -- `src/composer.lock` фиксируется, поэтому `composer install` является воспроизводимым. -- Инструменты разработки (PHPStan, PHP-CS-Fixer) являются `require-dev` и ** отсутствуют** в +- `src/composer.lock` фиксируется таким образом, чтобы `composer install` можно было воспроизвести. +- Инструменты разработки (PHPStan, PHP-CS-Fixer) имеют значение `require-dev` и ** отсутствуют** в зарегистрированный поставщик или архивы. Разработчики и CI добавляют их с помощью `make dev-tools` (`composer install`); шлюз `check-vendor-prod-only` завершается сбоем, если пакет разработчика -когда-либо совершались под `src/vendor/`. -- Нет шага поставщика во время сборки — `make main` / `make lb` скопируйте зафиксированный +когда-либо совершенные под `src/vendor/`. +- Нет шага поставщика во время сборки - `make main` / `make lb` скопируйте зафиксированный `vendor/` непосредственно в архив. --- @@ -57,7 +57,7 @@ XC_VM поддерживает две роли развертывания из ### ОСНОВНАЯ сборка -ОСНОВНАЯ сборка содержит **весь ** каталог `src/`. +ОСНОВНАЯ сборка содержит **весь** каталог `src/`. ### Каталоги— включенные в сборку LB @@ -105,7 +105,7 @@ signals/ Streaming/ tmp/ www/ |`www/player_api.php`, `www/epg.php`, `www/enigma2.php`|Конечные точки клиентского API (обслуживаемые MAIN)| | `www/stream/auth.php` |Конечная точка аутентификации удалена из пакета LB| |`www/admin/api.php`, `www/admin/proxy_api.php`|API администратора| -| `bin/maxmind/GeoLite2-City.mmdb` |GeoIP DB поставляется отдельно| +| `bin/maxmind/GeoLite2-City.mmdb` |GeoIP БД поставляется отдельно| | `config/rclone.conf` |Конфигурация резервного копирования| | `Domain/Epg/EPG.php` |EPG класс обработки| | `bin/nginx/conf/gzip.conf` |Конфигурация Gzip (LB использует собственную)| @@ -132,7 +132,7 @@ signals/ Streaming/ tmp/ www/ | `Cli/CronJobs/ProvidersCronJob.php` |Синхронизация с поставщиком (только для ОСНОВНОГО)| | `Cli/CronJobs/SeriesCronJob.php` |Метаданные серии (только для основной версии)| -> ** Примечание:** Связанные с модулем crons (TMDB, Plex, Watch) теперь находятся внутри `modules//` и автоматически исключаются из сборок LB, поскольку `modules/` отсутствует в `LB_DIRS`. +> ** Примечание:** Связанные с модулем cron (TMDB, Plex, Watch) теперь находятся внутри `modules//` и автоматически исключаются из сборок LB, поскольку `modules/` отсутствует в `LB_DIRS`. ### Конфигурации, замененные при сборке LB @@ -141,7 +141,7 @@ signals/ Streaming/ tmp/ www/ |Источник|Цель|Цель| | --- | --- | --- | | `lb_configs/nginx.conf` | `bin/nginx/conf/nginx.conf` |Настроенная производительность nginx для потоковой передачи| -| `lb_configs/live.conf` | `bin/nginx_rtmp/conf/live.conf` |RTMP перехваты обратного вызова| +| `lb_configs/live.conf` | `bin/nginx_rtmp/conf/live.conf` |RTMP перехватчики обратного вызова| --- @@ -177,9 +177,9 @@ signals/ Streaming/ tmp/ www/ |Gzip-файл|прочь|Потоковые данные уже сжаты| |Журналы доступа|прочь|Сократите накладные расходы на ввод-вывод| |Ограничение скорости|20 запросов в секунду на IP-адрес|Смягчение последствий DDoS-атак| -|Тайм-аут отправки|20 мин|Поддержка длительных потоков| +|Время ожидания отправки|20 мин|Поддержка длительных потоков| -RTMP перехватывает (`lb_configs/live.conf`) маршрутизацию аутентификации через локальные HTTP-обратные вызовы вместо панели администратора: +RTMP перехватывает (`lb_configs/live.conf`) аутентификацию маршрута с помощью локальных обратных вызовов HTTP вместо панели администратора: ```nginx on_play http://127.0.0.1:8080/stream/rtmp; @@ -193,7 +193,7 @@ on_play_done http://127.0.0.1:8080/stream/rtmp; ### Загрузка условной команды -`console.php` использует `file_exists()` guards для команд, которые могут отсутствовать на серверах LB: +`console.php` использует защиту `file_exists()` для команд, которые могут отсутствовать на серверах LB: ```php if (file_exists(__DIR__ . '/Cli/Commands/CacheHandlerCommand.php')) { @@ -233,7 +233,7 @@ LB_DIRS = bin cli config content core domain ... your_dir ### Новый каталог, доступный только для администратора -Добавьте его в `LB_DIRS_TO_REMOVE`: +Добавьте его к `LB_DIRS_TO_REMOVE`: ```makefile LB_DIRS_TO_REMOVE = ... your_dir/admin_stuff @@ -241,7 +241,7 @@ LB_DIRS_TO_REMOVE = ... your_dir/admin_stuff ### Новый файл, доступный только для администратора -Добавьте его в `LB_FILES_TO_REMOVE`: +Добавьте его к `LB_FILES_TO_REMOVE`: ```makefile LB_FILES_TO_REMOVE = ... your_dir/admin_file.php @@ -249,7 +249,7 @@ LB_FILES_TO_REMOVE = ... your_dir/admin_file.php ### Новая команда CLI (только для администратора) -1. Добавить `file_exists()` guard в `console.php` +1. Добавить `file_exists()` защиту в `console.php` 2. Добавьте файл в `LB_FILES_TO_REMOVE` --- diff --git a/docs/ru/builds/updates_checklist.md b/docs/ru/builds/updates_checklist.md index 60f103b3..a346a3c3 100644 --- a/docs/ru/builds/updates_checklist.md +++ b/docs/ru/builds/updates_checklist.md @@ -1,4 +1,4 @@ -# XC_VM Контрольный список подготовки к выпуску +# XC_VM Контрольный список для подготовки релиза Пошаговое руководство по подготовке и публикации релиза XC_VM. @@ -14,9 +14,9 @@ mkdir -p dist git log --pretty=format:"- %s (%h)" "$PREV_TAG"..main > dist/changes.md ``` -> ❗️ требуется `mkdir -p dist`: `dist/` не существует в новом клоне, и `make new` стирает его — без этого перенаправление завершается ошибкой с сообщением "Нет такого файла или каталога". +> ⚠️ `mkdir -p dist` is required: `dist/` does not exist on a fresh clone and `make new` wipes it — without it the redirect fails with "No such file or directory". -**Обновление `changelog.json`** в корневом каталоге репозитория — этот файл содержит только изменения для предстоящего выпуска: +**Обновить `changelog.json`** в корневом каталоге репозитория — этот файл содержит только изменения для предстоящего выпуска: ```json { @@ -36,7 +36,7 @@ git log --pretty=format:"- %s (%h)" "$PREV_TAG"..main > dist/changes.md ## 2. Подготовить базовый уровень выпуска -Сначала завершите всю работу с функциями / исправлениями / документами и убедитесь, что она уже находится в `main`. +Сначала завершите всю работу с функциями / исправлениями / документами и убедитесь, что она уже включена в `main`. Установите переменную version один раз и повторно используйте ее во всех приведенных ниже командах: @@ -45,11 +45,11 @@ VERSION="X.Y.Z" ``` > ❗️ Не создавайте отдельную версию-измените фиксацию/push на этом шаге. -> В противном случае `dist/changes.md` будет включать дополнительные фиксации релиза и принудительно вносить дополнительные правки. +> В противном случае `dist/changes.md` будет включать дополнительные фиксации релиза и потребует внесения дополнительных правок. ### Восстановление переведенной документации -Документация написана только на **английском языке** (`docs/en`). Русское дерево +Документация написана ** только на английском языке** (`docs/en`). Дерево на русском языке (`docs/ru`) - это ** сгенерированный, зафиксированный** артефакт, обновляемый локально перед каждым release — перевод намеренно **не** выполняется в CI (он медленный); только CI создает зафиксированное дерево. Если `docs/en` изменено с момента последнего выпуска: @@ -59,10 +59,10 @@ make docs-translate # regenerate docs/ru from docs/en (free, no API key) make docs-build # strict build — fails on any broken link/anchor ``` -- `make docs-translate` повторно переводит только те файлы на английском языке, содержимое которых +- `make docs-translate` повторно переводятся только те файлы на английском языке, содержимое которых изменен (для каждого файлового кэша), так что при постепенном выпуске это происходит быстро. -- **Просмотрите и зафиксируйте восстановленный `docs/ru`** — он включен в единый -снимите фиксацию (шаг 5). Никогда не редактируйте `docs/ru` вручную. +- **Review and commit the regenerated `docs/ru`** — it is included in the single +снимите фиксацию (шаг 5). Никогда не редактируйте вручную `docs/ru`. - Нажатие кнопки изменения документов запускает `pages.yml`, которая создает и публикует страницы сайта на GitHub. @@ -76,9 +76,9 @@ make docs-build # strict build — fails on any broken link/anchor make generate_deleted_files ``` -Это запускает `git diff` между `LAST_TAG` и `HEAD`@, извлекает удаленные файлы под `src/`, удаляет префикс `src/` и записывает результат в `src/migrations/deleted_files.txt`. +Это запускает `git diff` между `LAST_TAG` и `HEAD`, извлекает удаленные файлы из `src/`, удаляет префикс `src/` и записывает результат в `src/migrations/deleted_files.txt`. -Если `LAST_TAG` не может быть автоматически обнаружен (нет сети / нет выпусков), передайте его явно: +Если `LAST_TAG` не может быть определено автоматически (нет сети / нет выпусков), передайте его явно: ```bash make generate_deleted_files LAST_TAG=1.2.16 @@ -90,11 +90,11 @@ make generate_deleted_files LAST_TAG=1.2.16 cat src/migrations/deleted_files.txt ``` -После проверки, `make main` / `make lb` файл будет помещен в архив с помощью `delete_files_list` / `lb_delete_files_list`. +После проверки `make main` / `make lb` упакует файл в архив с помощью `delete_files_list` / `lb_delete_files_list`. -Во время `php console.php update post-update`, `MigrationRunner::runFileCleanup()` считывает его и автоматически удаляет перечисленные файлы. +Во время `php console.php update post-update` `MigrationRunner::runFileCleanup()` считывает его и автоматически удаляет перечисленные файлы. -> ⚠️ Строки, начинающиеся с `#`, являются комментариями и будут проигнорированы. Вы можете закомментировать файлы, которые хотите сохранить. +> ❗️ Строки, начинающиеся с `#`, являются комментариями и будут проигнорированы. Вы можете закомментировать файлы, которые хотите сохранить. --- @@ -157,7 +157,7 @@ make lb make main ``` -После построения `dist/` должен содержать: +После построения `dist/` должно содержать: |Файл|Описание| | --- | --- | @@ -167,7 +167,7 @@ make main | `hashes.md5` |Контрольные суммы MD5| > Один и тот же архив используется как для чистой установки, так и для обновлений. -> Скрипт обновления (`src/update`) отфильтровывает двоичные каталоги/каталоги конфигурации во время выполнения, используя жестко заданный список `UPDATE_EXCLUDE_DIRS` внутри самого скрипта Python. +> Скрипт обновления (`src/update`) отфильтровывает двоичные/конфигурационные каталоги во время выполнения, используя жестко заданный список `UPDATE_EXCLUDE_DIRS` внутри самого скрипта Python. **Проверка целостности:** @@ -175,16 +175,16 @@ make main cd dist && md5sum -c hashes.md5 ``` -**Тестовая установка Docker** (смотрите `tools/test-install/`) — только после сборки, так как для этого требуется `dist/XC_VM.zip`: +**Тестовая установка Docker** (см. `tools/test-install/`) — только после сборки, поскольку для этого требуется `dist/XC_VM.zip`: ```bash bash tools/test-install/test_release.sh ``` -При этом создается образ, контейнер запускается с помощью systemd и программа установки запускается автоматически. +При этом создается образ, контейнер запускается с systemd и программа установки запускается автоматически. `dist/XC_VM.zip` монтируется в контейнер как том, доступный только для чтения. -> ✅ Убедитесь, что панель загружается с `http://localhost:8880` и логин администратора работает. +> ✅ Убедитесь, что панель загружается со значением `http://localhost:8880` и логин администратора работает. --- @@ -208,7 +208,7 @@ bash tools/test-install/test_release.sh ## 8. После выпуска - [ ] Убедитесь, что все 4 ресурса присоединены к релизу -- [ ] Запустите `md5sum -c hashes.md5` для загруженных файлов +- [ ] Выполнить `md5sum -c hashes.md5` для загруженных файлов - [ ] Проверьте, отправлено ли уведомление Telegram - [ ] Тесно связанные с GitHub проблемы / вехи @@ -218,7 +218,7 @@ bash tools/test-install/test_release.sh Все целевые объекты `make`, использованные во время подготовки релиза, в одном месте. -**Проверка качества ** — сначала запустите `make dev-tools`, после завершения - `make dev-clean`: +**Проверка качества** — сначала запустите `make dev-tools`, затем `make dev-clean`, когда закончите: |Команда|Цель| | --- | --- | @@ -227,8 +227,8 @@ bash tools/test-install/test_release.sh | `make phpstan-baseline` |Восстановите базовую линию PHPStan| | `make cs` |Проверка стиля кода - импорт/гигиена пространства имен (PHP—CS-Fixer, пробный запуск)| | `make cs-fix` |Примените исправления в стиле кода на месте| -| `make gates` |PSR-4 регрессионные шлюзы (для процедурного использования, LB-архив, только для продукта поставщика)| -| `make dev-clean` |Снова удалите инструменты разработки, восстановив рабочий режим только для `vendor/`| +| `make gates` |PSR-4 регрессионные параметры (для использования в процедурных целях, LB-архив, только для продукта поставщика)| +| `make dev-clean` |Снова удалите инструменты разработки, восстановив только производственную версию `vendor/`.| | `php tools/.bin/phpunit.phar -c tests/phpunit.xml.dist` |Модульные тесты| **Подготовка к выпуску и сборка:** @@ -236,16 +236,16 @@ bash tools/test-install/test_release.sh |Команда|Цель| | --- | --- | | `make generate_deleted_files` |Регенерировать `src/migrations/deleted_files.txt`| -| `make new` |Сбросьте выходной каталог `dist/` (запустите перед сборкой)| -| `make lb` |Создайте архив LoadBalancer в `dist/`| -| `make main` |Создайте ОСНОВНОЙ архив в `dist/`| +| `make new` |Сброс выходного каталога `dist/` (запуск перед сборкой)| +| `make lb` |Создайте архив LoadBalancer в виде `dist/`| +| `make main` |Соберите ОСНОВНОЙ архив в `dist/`| | `bash tools/test-install/test_release.sh` |Установочный тест Docker для встроенного выпуска| -**Документация** (англоязычный источник в `docs/en`; `docs/ru` сгенерирован + зафиксирован): +**Documentation** (English source in `docs/en`; `docs/ru` is generated + committed): |Команда|Цель| | --- | --- | | `make docs-venv` |Одноразовый: локальный venv (сборка + переводы)| | `make docs-translate` |Восстановить `docs/ru` из `docs/en` (перед выпуском)| -| `make docs-build` |Строгая MkDocs сборка в `./site` (что запускает CI)| -| `make docs-serve` |Предварительный просмотр документов в режиме реального времени на странице `http://127.0.0.1:8000`| +| `make docs-build` |Строгая сборка MkDocs в `./site` (что запускает CI)| +| `make docs-serve` |Предварительный просмотр документов в режиме реального времени на `http://127.0.0.1:8000`| diff --git a/docs/ru/development/architecture.md b/docs/ru/development/architecture.md index 37834559..d9680b5a 100644 --- a/docs/ru/development/architecture.md +++ b/docs/ru/development/architecture.md @@ -5,8 +5,8 @@ Структурированный монолит PHP с модульным дополнительным слоем. - Никакого DDD, никакой гексагональности, никакой чистой архитектуры — намеренно. -- Разбивка по контексту с минимальными абстракциями: `Controller → Service → Repository`. -- Два артефакта сборки из одной кодовой базы: **MAIN** (полная панель) и **LB** (load balancer subset). +- Split by context with minimal abstractions: `Controller → Service → Repository`. +- Два артефакта сборки из одной кодовой базы: **MAIN** (полная панель) и **LB** (подмножество load balancer). --- @@ -15,11 +15,11 @@ |Путь|Роль| | ---- | ---- | | `src/Core/` |Примитивы инфраструктуры: Контейнер DI, события, HTTP, настройка, аутентификация, ведение журнала| -| `src/Domain/` |Бизнес-контексты: Поток, VOD, линия, пользователь, Сервер, безопасность и т.д.| +| `src/Domain/` |Бизнес-контексты: Поток, VOD, линия, пользователь, сервер, безопасность и т.д.| | `src/Modules/` |Дополнительный слой расширения — загружается с помощью `ModuleLoader`| | `src/Public/` |Передний контроллер, маршрутизатор, контроллеры, представления, ресурсы| | `src/Cli/` |Консольные команды и точки входа в cron| -| `src/Ministra/` |Портал Сталкера — в ядре; обслуживается по адресу `/home/xc_vm/Ministra`| +| `src/Ministra/` | Stalker Portal — in core; served at `/home/xc_vm/Ministra` | --- @@ -36,15 +36,15 @@ Public/index.php └── Router → dispatch() ``` -Классы домена получают базу данных через `setDb()` injection (вызывается из -`bootstrap.php::wireDomainDatabase()`). В пути веб-запроса нет `global $db`. +Классы домена получают базу данных посредством внедрения `setDb()` (вызывается из +`bootstrap.php::wireDomainDatabase()`). Нет `global $db` в пути веб-запроса. --- ## Модульная система Модули представляют собой изолированные каталоги под `src/Modules/` с манифестом `module.json` -и класс, расширяющий `BaseModule`. Смотрите [Модульная система](modules.md) для получения полной информации. +и класс, расширяющий `BaseModule`. Полную информацию смотрите в разделе [Модульная система](modules.md). ``` src/Modules/my-module/ @@ -76,7 +76,7 @@ src/Modules/my-module/ |Потоковый|✅|✅| |Модульная система|✅|подмножество| -Управляется `ServerEnvironment` перечислением и `module.json` `environment` полем (`main` / `lb` / `any`). +Управляется перечислением `ServerEnvironment` и полем `module.json` `environment` (`main` / `lb` / `any`). --- @@ -84,7 +84,7 @@ src/Modules/my-module/ |Механизм|Как использовать| | --------- | ---------- | -|События PSR-14|@атрибут @0@@ или `#[ListensTo]`@| +|События PSR-14|атрибут `EventDispatcher::listen()` или `#[ListensTo]`| |Оформление сервиса| `$container->decorate('id', callable, priority)` | |Потоковое промежуточное программное обеспечение|Реализовать `StreamMiddlewareProviderInterface`| |Записи Cron|Переопределить `getCronEntries()` в классе модуля| @@ -95,7 +95,7 @@ src/Modules/my-module/ ## Правила для участников 1. Модули не должны изменять основные файлы. -2. Никаких `eval`, исправлений monkey или замены файлов во время выполнения. +2. Никаких `eval`, исправлений ошибок или замены файлов во время выполнения. 3. Любой модуль можно отключить с помощью `config/modules.php`, не прикасаясь к ядру. 4. Защищенные сервисы (`db`, `settings`, `config`, `auth`) не могут быть оформлены. 5. Синхронизируйте документы EN и RU в одном и том же коммите. @@ -105,7 +105,7 @@ src/Modules/my-module/ |Файл|Роль| | --- | --- | | `src/Core/` |Примитивы фреймворка (DI, события, HTTP, config, auth, ведение журнала)| -| `src/Domain/` |Бизнес-контексты (Поток, VOD, Линия, Пользователь, Сервер, безопасность)| +| `src/Domain/` |Бизнес-контексты (Поток, VOD, строка, Пользователь, Сервер, безопасность)| | `src/Infrastructure/` |Внешние адаптеры (DatabaseFactory, CacheReader, Redis)| | `src/Streaming/` |Подсистема потоковой передачи| | `src/Modules/` |Дополнительные модули (загружаются с помощью ModuleLoader)| diff --git a/docs/ru/development/autoloader.md b/docs/ru/development/autoloader.md index 6e266e66..8071f4b0 100644 --- a/docs/ru/development/autoloader.md +++ b/docs/ru/development/autoloader.md @@ -1,12 +1,12 @@ # Автоматическая загрузка (PSR-4) -XC_VM автоматически загружает классы с помощью стандартного **Composer PSR-4** автозагрузчика; пространство имен кодирует путь к файлу, поэтому разрешение выполняется напрямую `file_exists` без сканирования и кэширования. +XC_VM автоматически загружает классы с помощью стандартного **Composer PSR-4** автозагрузчика; в пространстве имен кодируется путь к файлу, поэтому разрешение выполняется напрямую `file_exists` без сканирования и кэширования. --- ## Обзор -Каждый сторонний класс находится в пространстве имен `XcVm\` root, сопоставленном с `src/`: +Каждый сторонний класс находится в корневом пространстве имен `XcVm\`, сопоставленном с `src/`: ```text XcVm\Core\Auth\Authenticator -> src/Core/Auth/Authenticator.php @@ -14,7 +14,7 @@ XcVm\Domain\Stream\StreamService -> src/Domain/Stream/StreamService.php XcVm\Public\Controllers\Admin\UserController -> src/Public/Controllers/Admin/UserController.php ``` -Сопоставление объявлено в `src/composer.json`: +Отображение объявлено в `src/composer.json`: ```json "autoload": { @@ -26,7 +26,7 @@ XcVm\Public\Controllers\Admin\UserController -> src/Public/Controllers/Admin/Use } ``` -`src/vendor/` (зависимости Composer autoloader + production) зафиксированы и +`src/vendor/` (автозагрузчик Composer + производственные зависимости) зафиксирован и отправлено — путь развертывания не содержит Composer и никогда не выполняется `composer install`. Там нет ли ** кэша карты классов ** (нет `optimize-autoloader`): пропущенный класс - это простой путь поиск, а не повторное сканирование каталога. @@ -44,14 +44,14 @@ class InvoiceService { } ``` -Ссылайтесь на него из другого кода в пространстве имен с помощью `use` import или по его полному номеру: +Ссылайтесь на него из другого кода в пространстве имен с помощью импорта `use` или по его полному номеру: ```php use XcVm\Domain\Billing\InvoiceService; ``` Не нужно очищать кэш, редактировать реестр. Совершенно новое подпространство имен (например, -`XcVm\Domain\Billing`) срабатывает немедленно, потому что он отображается прямо на +`XcVm\Domain\Billing`) срабатывает немедленно, поскольку он отображается прямо на каталог. ## Правила присвоения имен @@ -59,7 +59,7 @@ use XcVm\Domain\Billing\InvoiceService; |Правило|Пример| | --- | --- | |Имя файла **должно ** совпадать с именем класса|`InvoiceService.php` → `class InvoiceService`| -|Один класс на файл|PSR-4 разрешает один класс для каждого пути; разбивает файлы на несколько классов| +|Один класс на файл|PSR-4 разрешает один класс для каждого пути; разбивает файлы нескольких классов| |Пространство имен **должно** совпадать с путем к каталогу (с учетом регистра)|`src/Domain/Billing/` → `namespace XcVm\Domain\Billing;`| |Классы и каталоги PascalCase|`StreamService`, `DatabaseHandler`, `Core/Auth/`| |Соглашение о проекте: нет `declare(strict_types=1)`|—| @@ -76,23 +76,23 @@ use XcVm\Domain\Billing\InvoiceService; - процедурные точки входа, представления и загрузочный клей (например, `Public/index.php`, `Public/Views/**`, `Infrastructure/Bootstrap/*.php`); - глобальные константы и функции (`Core/Config/*`, обработчик ошибок); -- класс ioncube `XC_VM` и комплектный `Infrastructure/Tmdb/lib/*`. +- класс ioncube `XC_VM` и комплект поставки `Infrastructure/Tmdb/lib/*`. -У продаваемых пакетов `M3uParser` и `Chrisyue\PhpM3u8` есть свои собственные пакеты PSR-4 +Пакеты, поставляемые поставщиками `M3uParser` и `Chrisyue\PhpM3u8`, имеют свои собственные PSR-4 префиксы (указанные выше) и автозагрузка выполняются в обычном режиме. ## Модули Классы модулей используют пространство имен `XcVm\Module\\…`, но **не** зарегистрированы -в `composer.json` (каталогах модулей/торговых площадок — `plex`, `watch-d2bho` — -не соответствуют ни одному правилу PSR-4). Они разрешаются с помощью `ModuleLoader` собственного PSR-4 +в `composer.json` (каталоги модулей/торговых площадок — `plex`, `watch-d2bho` — +не соответствуют ни одному правилу PSR-4). Они разрешаются с помощью `ModuleLoader` собственных PSR-4 распознаватель: он удаляет базовое пространство имен модуля и отображает оставшееся в вложенный путь в каталоге модуля. Смотрите [Модульная система](modules.md). ## Инструменты для разработки -Исправленный `vendor/` предназначен только для рабочей среды. PHPStan и PHP-CS-Fixer являются -`require-dev` пакетов — установите их локально с помощью: +Зафиксированное значение `vendor/` доступно только для рабочей среды. PHPStan и PHP-CS-Fixer являются +`require-dev` пакеты — установите их локально с помощью: ```bash make dev-tools # = cd src && composer install @@ -107,6 +107,6 @@ make dev-tools # = cd src && composer install | --- | --- | | `src/composer.json` |PSR-4 префиксная карта + зависимости| | `src/composer.lock` |зафиксированная блокировка для воспроизводимого `composer install`| -| `src/vendor/` |зафиксировано Composer автозагрузчик + производственные потери| +| `src/vendor/` |зафиксированный Composer автозагрузчик + производственные удаления| | `src/bootstrap.php` |определяет `MAIN_HOME`, требует `vendor/autoload.php`| -| `src/Core/Module/ModuleLoader.php` |PSR-4 распознаватель для классов модулей| +| `src/Core/Module/ModuleLoader.php` |PSR-4 преобразователь для классов модулей| diff --git a/docs/ru/development/bootstrap-contexts.md b/docs/ru/development/bootstrap-contexts.md index 7d5fe4c5..19b9f54d 100644 --- a/docs/ru/development/bootstrap-contexts.md +++ b/docs/ru/development/bootstrap-contexts.md @@ -2,7 +2,7 @@ `XC_Bootstrap` - это единая точка входа для инициализации системы. Каждый контекст загружает только те подсистемы, которые необходимы для его пути выполнения. -Контекст выражается в виде значения `BootContext` enum. +Контекст выражается в виде значения перечисления `BootContext`. --- @@ -31,7 +31,7 @@ - регистратор (`Logger::init()`) - помощники по устранению ошибок (`generateError()`, `generate404()`) -Не включает: базу данных, Redis, сеансы, переводчик, API-интерфейсы администратора. +Не включает: базу данных, Redis, сеансы, транслятор, API-интерфейсы администратора. ```php require_once '/home/xc_vm/bootstrap.php'; @@ -43,13 +43,13 @@ XC_Bootstrap::boot(BootContext::MINIMAL); ### Загрузочный текст::CLI Используется для задач cron и CLI. -Добавляет инициализацию базы данных и устаревшего ядра через `MINIMAL`. +Добавляет инициализацию базы данных и устаревшего ядра поверх `MINIMAL`. Включает в себя: - Подключение к базе данных - `LegacyInitializer` -- необязательный Redis (`'redis' => true`) +- необязательно Redis (`'redis' => true`) - необязательный заголовок процесса (`'process' => '...'`) ```php @@ -71,7 +71,7 @@ XC_Bootstrap::boot(BootContext::CLI, [ - Подключение к базе данных (`cached=true`) - защита от наводнений и проверка хостинга -Не включает: Redis, переводчик, API-интерфейсы администратора, сеансы. +Исключает: Redis, переводчик, API-интерфейсы администратора, сеансы. ```php require_once '/home/xc_vm/bootstrap.php'; @@ -128,7 +128,7 @@ XC_Bootstrap::boot(BootContext $context, array $options = []); |Вариант|Тип|По умолчанию|Описание| | --- | --- | --- | --- | | `cached` | `bool` |`true` для ПОТОКА, `false` в противном случае|Использовать кэшированные настройки| -| `redis` | `bool` |`true` для АДМИНИСТРАТОРА, `false` в противном случае|Подключение Redis| +| `redis` | `bool` |`true` для АДМИНИСТРАТОРА, `false` в противном случае|Подключить Redis| | `process` | `string` | `''` |Название процесса для CLI| | `shutdown` | `callable` |встроенный|Переопределить обратный вызов завершения работы| diff --git a/docs/ru/development/caching-and-redis.md b/docs/ru/development/caching-and-redis.md index 1f19a35b..9de0820d 100644 --- a/docs/ru/development/caching-and-redis.md +++ b/docs/ru/development/caching-and-redis.md @@ -1,9 +1,9 @@ -# Кэширование и стратегия Redis +# Стратегия кэширования и Redis XC_VM использует двухуровневую стратегию кэширования: - **Файловый кэш (igbinary)** - основной уровень, используемый как потоковыми, так и административными потоками. -- **Redis/KeyDB** — дополнительный высокопроизводительный уровень для определения состояния соединения и расширенных операций +- **Redis/KeyDB** — дополнительный высокопроизводительный уровень для определения состояния подключения и расширенных операций Путь потоковой передачи считывается исключительно из файлового кэша (никаких запросов к базе данных). Путь администратора считывается из базы данных с дополнительным кратковременным кэшем. @@ -22,7 +22,7 @@ has($key, $maxAge = null) flush() ``` -- `$ttl = 0` означает кэширование навсегда (до ручного удаления или сброса). +- `$ttl = 0` означает кэширование навсегда (до ручного удаления или промывки). - `$maxAge` проверяет время изменения файла на свежесть (только для файлового кэша). --- @@ -48,7 +48,7 @@ FileCache::getCache($key, $maxAge = null) Характеристики: -- Сериализация: igbinary (если доступно) или резервный вариант PHP serialize. +- Сериализация: igbinary (если доступно) или PHP выполнить резервную сериализацию. - Блокировка: `LOCK_EX` при записи для предотвращения повреждения. - Расположение файла: `{basePath}/{key}` (нет подкаталогов для основных ключей). - Восстановление поврежденных файлов: обнаруживает поврежденные данные, автоматически удаляет поврежденные файлы. @@ -86,7 +86,7 @@ RedisManager::isConnected() // health check RedisManager::closeInstance() // disconnect ``` -Проверка работоспособности выдает сигнал Redis каждые 30 секунд (отключено). Автоматическое повторное подключение при сбое. Возвращает значение null при сбое соединения (постепенное ухудшение). +Проверка работоспособности выдает сигнал Redis каждые 30 секунд (отменено). При сбое автоматически восстанавливается соединение. При сбое соединения возвращается значение null (постепенное ухудшение). Конфигурация: @@ -106,18 +106,18 @@ RedisManager::closeInstance() // disconnect весь срок службы, при котором возможны два режима сбоя на загруженном сервере или на межсерверном сервере (LB → ГЛАВНАЯ) ссылка: -- **Сервер простаивает -закрывается.** Redis закрывает любой клиент, который простаивает дольше своего `timeout` (`300s` +- **Server idle-close.** Redis closes any client idle past its `timeout` (`300s` в комплекте `bin/redis/redis.conf`). затем phpredis прозрачно откроется снова. сокет в следующей команде ** без повторного воспроизведения AUTH**, поэтому более поздняя команда отвечает `NOAUTH` — или просто возвращает `false`. -- ** Устранен пробел в проверке работоспособности.** `instance()` пингуется только каждые 30 секунд, так что между +- **Debounced health-check gap.** `instance()` only pings every 30s, so between пингует, что сброшенное соединение еще не замечено. Охранники на месте: -- `instance()` обрабатывает любой ответ, не связанный с`PONG` ping (беззвучное повторное подключение / `NOAUTH` +- `instance()` обрабатывает любой ответ, не связанный с`PONG` пингом (беззвучное повторное подключение / `NOAUTH` состояние) как отключенное соединение и вызывает полное, **повторно прошедшее проверку подлинности** повторное подключение -через `\XC_VM::redis_connect()` — не просто повторная попытка на уровне сокета. +через `\XC_VM::redis_connect()` — это не просто повторная попытка на уровне сокета. - Вызывайте сайты, которые командами конвейера проверяют объект конвейера. Например `ConnectionTracker::getCapacity()` проверяет, что `$redis->multi()` вернул `\Redis` (сломанный сокет возвращает `false` и вызывает `zCard()` для этого bool @@ -145,7 +145,7 @@ RedisManager::closeInstance() // disconnect ### Большой объем кэша (CacheEngineCronJob) -Перестраивает потоковые, линейные и последовательные данные. Регулируется до одного раза в 5 минут с помощью `heavy_cache_built` marker: +Перестраивает потоковые, линейные и последовательные данные. Регулируется до одного раза в 5 минут с помощью маркера `heavy_cache_built`: - `STREAMS_TMP_PATH/stream_{id}` — метаданные отдельного потока - `LINES_TMP_PATH/line_i_{user_id}` — данные учетной записи пользователя @@ -153,24 +153,24 @@ RedisManager::closeInstance() // disconnect - `LINES_TMP_PATH/line_t_{access_token}` — токен → поиск идентификатора пользователя - `SERIES_TMP_PATH/series_{id}` — метаданные серии -Режим обнаружения изменений (если `cache_changes` включен): сравнивает временную метку базы данных `updated` с файлом `mtime`, восстанавливает только измененные элементы. +Режим обнаружения изменений (если включено `cache_changes`): сравнивает временную метку базы данных `updated` с файлом `mtime`, восстанавливает только измененные элементы. Режим полной перестройки: восстанавливает все записи. Регулируется параметром `cache_thread_count`. ### Готовность кэша -Файл `cache_complete` записывается после каждой полной сборки кэша. Путь к потоковой передаче проверяет наличие этого файла и завершает работу с ошибкой, если он отсутствует. +После каждой полной сборки кэша записывается файл `cache_complete`. Путь потоковой передачи проверяет наличие этого файла и завершает работу с ошибкой, если он отсутствует. ### Безопасность холодного кэширования -В потоковом загрузчике (`LegacyInitializer::initStreaming()`) отображается `servers`, +Потоковый загрузчик (`LegacyInitializer::initStreaming()`) считывает `servers`, блок-листы и `proxy_servers` из файлового кэша. Перед первой сборкой -(новая загрузка, сброшенный tmp) эти файлы не существуют и `CacheReader::get()` +(fresh boot, cleared tmp) those files do not exist and `CacheReader::get()` возвращает `null`, поэтому для каждого такого глобального массива по умолчанию используется пустой массив. Холодный кэш поэтому ** ошибка закрыта** — запрос не находит серверов и показывает "не в эфире". — -вместо предупреждения `foreach(null)` или `in_array($x, null)` фатального (PHP 8) +вместо предупреждения `foreach(null)` или `in_array($x, null)` со смертельным исходом (PHP 8) вниз по течению. Действительно поврежденный кэш *сборка* все еще отображается отдельно с помощью -@предупреждение @0@@ о сбое записи, поэтому это значение по умолчанию маскирует только временный +`FileCache` предупреждение о сбое записи, поэтому это значение по умолчанию маскирует только временный окно холодного пуска, а не настоящий сбой. --- @@ -234,14 +234,14 @@ RedisManager::closeInstance() // disconnect - `cached: true` по умолчанию. - Считывает данные исключительно из файлового кэша (никаких запросов к базе данных). -- Необработанная двоичная десериализация: `igbinary_unserialize(file_get_contents(...))`. +- Raw igbinary deserialization: `igbinary_unserialize(file_get_contents(...))`. - Если `cache_complete` отсутствует: завершите работу с ошибкой. ### Путь администратора (`Public/Controllers/Admin/*`) - `cached: false` по умолчанию. - Считывает данные из базы данных непосредственно через доменные службы. -- Дополнительный кратковременный кэш (пример из `BouquetService::getAll()`): +- Необязательный кратковременный кэш (пример из `BouquetService::getAll()`): ```php $rCache = FileCache::getCache('bouquets', 60); // only if < 60s old @@ -287,8 +287,8 @@ FileCache::setCache('bouquets', $rOutput); | --- | --- | | `src/Core/Cache/CacheInterface.php` |контракт на кэширование| | `src/Core/Cache/FileCache.php` |реализация кэша на основе файлов| -| `src/Core/Cache/RedisCache.php` |Redis реализация кэша| -| `src/Infrastructure/Redis/RedisManager.php` |Redis единичный элемент подключения| +| `src/Core/Cache/RedisCache.php` |Redis реализация кэширования| +| `src/Infrastructure/Redis/RedisManager.php` |Redis одноэлементное соединение| | `src/Infrastructure/Cache/CacheReader.php` |устаревший мост для чтения кэша| | `src/Cli/CronJobs/CacheCronJob.php` |облегченная генерация кэша| | `src/Cli/CronJobs/CacheEngineCronJob.php` |генерация большого объема кэша (потоки, строки, серии)| diff --git a/docs/ru/development/event-system.md b/docs/ru/development/event-system.md index 177ec661..e03f45f9 100644 --- a/docs/ru/development/event-system.md +++ b/docs/ru/development/event-system.md @@ -1,6 +1,6 @@ # Система событий -XC_VM использует типизированный диспетчер событий в стиле PSR-14. Все события являются простыми PHP классами +XC_VM использует типизированный диспетчер событий в стиле PSR-14. Все события являются простыми классами PHP отправлено и получено по имени. Диспетчер основан на экземпляре и хранится в Откройте контейнер под ключом `events`. @@ -100,7 +100,7 @@ class MyModuleModule extends BaseModule { ## Останавливаемые события -Наберите `AbstractEvent` и позвоните `$e->stopPropagation()`: +Продлить `AbstractEvent` и вызвать `$e->stopPropagation()`: ```php class MyGatingEvent extends AbstractEvent { @@ -115,7 +115,7 @@ EventDispatcher::listen(MyGatingEvent::class, function (MyGatingEvent $e): void }, priority: 100); ``` -Прослушиватели пропускаются, как только `isPropagationStopped()` возвращает `true`. +Прослушиватели пропускаются, как только `isPropagationStopped()` возвращает значение `true`. --- @@ -136,7 +136,7 @@ EventDispatcher::listen(MyGatingEvent::class, function (MyGatingEvent $e): void ## Создание пользовательского события -Простой класс — используйте свойства `readonly` для неизменяемых полезных нагрузок.: +Простой класс — используйте свойства `readonly` для неизменяемых полезных нагрузок: ```php Public/index.php ### Ключевая деталь: санитарная обработка входных данных -Рабочий процесс администрирования не использует `Request::capture()`. Вместо этого `LegacyInitializer::initCore()` управляет обработкой входных данных: +В процессе производственного администрирования не используется `Request::capture()`. Вместо этого `LegacyInitializer::initCore()` управляет обработкой входных данных: -1. `InputValidator::cleanGlobals()` вызывается на месте `$_GET`, `$_POST`, `$_SESSION`, и `$_COOKIE`, удаляя нулевые байты, последовательности обхода пути (`../`) и символы переопределения RTL. +1. `InputValidator::cleanGlobals()` вызывается при `$_GET`, `$_POST`, `$_SESSION`, и `$_COOKIE` на месте, удаляя нулевые байты, последовательности обхода пути (`../`) и символы переопределения RTL. 2. `InputValidator::parseIncomingRecursively()` очищает ключи и значения (HTML-объекты, теги скриптов, разделители комментариев, окончания строк) и возвращает чистый массив. 3. Результат (объединяется с сообщением, сообщение имеет приоритет) сохраняется через `RequestManager::set()`. @@ -81,7 +81,7 @@ nginx -> Public/index.php ## Поток запросов: Потоковая передача -Точка входа: `www/stream/*.php` конечные точки или `Public/index.php` для `player_api` +Точка входа: `www/stream/*.php` конечных точек или `Public/index.php` для `player_api` ```text nginx -> StreamingRequestBootstrap::init($filename) @@ -105,14 +105,14 @@ nginx -> StreamingRequestBootstrap::init($filename) Файл: `src/Core/Http/RequestGuard.php` -Процедурный защитный скрипт, включенный ранее в устаревший bootstrap. Выполняется только для HTTP-запросов (пропускается, если задано значение `$_SERVER['argc']`, указывающее CLI). +Процедурный защитный скрипт, включенный ранее в устаревший bootstrap. Выполняется только для HTTP-запросов (пропускается, если установлено значение `$_SERVER['argc']`, указывающее на CLI). ### Обязанности 1. **Защита от наводнений** -- Если файл `FLOOD_TMP_PATH/block_{IP}` существует, запрос отклоняется по протоколу HTTP 403. 2. **Загрузка кэша настроек** -- Считывает `$rSettings` из кэша файлов, сериализованных в igbinary, по адресу `CACHE_TMP_PATH/settings`. -3. **Проверка хоста** - Когда `$rSettings['verify_host']` имеет значение true, проверяется, что `HOST` отображается в кэшированном списке `allowed_domains`. Исключения: имя хоста `xc_vm` и любой действительный IP-адрес всегда разрешены. -4. **Флаг отображения ошибки** -- Устанавливает константу `PHP_ERRORS` вместо `$rSettings['debug_show_errors']`. +3. **Проверка хоста** - При значении `$rSettings['verify_host']` true проверяется, отображается ли `HOST` в кэшированном списке `allowed_domains`. Исключения: имя хоста `xc_vm` и любой действительный IP-адрес всегда разрешены. +4. **Флаг отображения ошибки** - Устанавливает значение константы `PHP_ERRORS` вместо `$rSettings['debug_show_errors']`. 5. **Инициализация регистратора** -- Вызывает `Logger::init(PHP_ERRORS, LOGS_TMP_PATH . 'error_log.log')`. Примечание: В современном bootstrap (`XC_Bootstrap::boot()`) эти обязанности выполняются методами `floodProtection()` и `hostVerification()` напрямую, а не путем включения `RequestGuard.php`. @@ -129,16 +129,16 @@ nginx -> StreamingRequestBootstrap::init($filename) |Метод|Описание| | --- | --- | -| `cleanGlobals(&$data, $iteration)` |Удаление нулевых байтов на месте, обход пути (`../`) и переопределение символов RTL. Максимум 10 уровней рекурсии.| +| `cleanGlobals(&$data, $iteration)` |Удаление нулевых байтов на месте, обход пути (`../`) и символы переопределения RTL. Максимум 10 уровней рекурсии.| | `parseIncomingRecursively(&$data, $input, $iteration)` |Возвращает новый обработанный массив. Очищаются как ключи, так и значения. Максимальное количество уровней рекурсии - 20.| -| `parseCleanKey($key)` |Наносим двойные точки, узоры `__wrapped__`, применяем `htmlspecialchars(urldecode())`.| -| `parseCleanValue($value)` |Удаляет теги ` STATUS_INVALID_INPUT, 'data' => $data]`, если недопустимо.| | `confirmIDs($ids)` |Фильтрует массив только по целочисленным положительным идентификаторам.| @@ -152,7 +152,7 @@ nginx -> StreamingRequestBootstrap::init($filename) ### Как данные попадают в -`LegacyInitializer::initCore()` звонков: +`LegacyInitializer::initCore()` вызовы: ```php $rInput = InputValidator::parseIncomingRecursively($_GET, array()); @@ -189,7 +189,7 @@ RequestManager::update('status', 'active'); Файл: `src/Core/Http/Request.php` -Объектно-ориентированная оболочка запроса. Содержит статическую фабрику `capture()` и методы экземпляра для доступа к обработанным входным данным. Хотя класс существует и полностью функционален, в основном производственном потоке вместо него используется `InputValidator` + `RequestManager`. Методы статической очистки класса `Request` (`cleanGlobals`, `parseIncomingRecursively`) используются `LegacyInitializer::initStreaming()` для обеспечения обратной совместимости. +Объектно-ориентированная оболочка запроса. Содержит статическую фабрику `capture()` и методы экземпляра для доступа к обработанным входным данным. Хотя класс существует и полностью функционален, в основном производственном потоке вместо него используются `InputValidator` + `RequestManager`. Методы статической очистки класса `Request` (`cleanGlobals`, `parseIncomingRecursively`) используются `LegacyInitializer::initStreaming()` для обеспечения обратной совместимости. ### Строительство @@ -207,12 +207,12 @@ $request = new Request($_GET, $_POST, $_SERVER, $_COOKIE); | --- | --- | --- | | `input` | `input($key, $default = null)` |Получить из объединенных входных данных (приоритет POST над GET)| | `get` | `get($key = null, $default = null)` |Получаем из строки запроса (`$_GET`). `null` ключ возвращает все.| -| `post` | `post($key = null, $default = null)` |Получаем данные из POST. `null` key возвращает все.| +| `post` | `post($key = null, $default = null)` |Получаем из данных POST. `null` ключ возвращает все.| | `all` | `all()` |Все объединенные входные данные| | `has` | `has($key)` |Проверьте, существует ли ключ в объединенном вводе| | `getInt` | `getInt($key, $default = 0)` |Получить значение в виде целого числа| | `getBool` | `getBool($key, $default = false)` |Получить значение в виде логического значения (через `filter_var`)| -| `server` | `server($key, $default = null)` |Получаем `$_SERVER` значение| +| `server` | `server($key, $default = null)` |Получить значение `$_SERVER`| | `cookie` | `cookie($key, $default = null)` |Получить значение файла cookie| | `method` | `method()` |Строка HTTP-метода (GET, POST и т.д.)| | `isPost` | `isPost()` |Проверьте, является ли метод POST| @@ -221,7 +221,7 @@ $request = new Request($_GET, $_POST, $_SERVER, $_COOKIE); | `uri` | `uri()` |URI запроса| | `userAgent` | `userAgent()` |Заголовок пользовательского агента| | `host` | `host()` |Заголовок узла (возвращается к `SERVER_NAME`)| -| `rawBody` | `rawBody()` |Необработанный текст сообщения от `php://input`| +| `rawBody` | `rawBody()` |Необработанный текст сообщения из `php://input`| | `json` | `json($assoc = true)` |Текст СООБЩЕНИЯ, декодированный в формате JSON| ### Методы статической дезинфекции (обратная совместимость) @@ -297,7 +297,7 @@ $router->group('watch', function (Router $r) { | `settings_watch` | `settings/watch` | | `plex_add.php` | `plex/add` | -Эта нормализация применяется как во время регистрации (`buildRoute`), так и во время отправки (`normalizePage`), поэтому маршруты, зарегистрированные как `watch/add`, соответствуют названиям страниц, таким как `watch_add`. +Эта нормализация применяется как во время регистрации (`buildRoute`), так и во время отправки (`normalizePage`), поэтому маршруты, зарегистрированные как `watch/add`, соответствуют названиям страниц, подобным `watch_add`. ### Отправка @@ -311,23 +311,23 @@ $router->dispatchApi($action); // returns true if matched #### `dispatch($page, $method)` порядок исполнения -1. Нормализовать `$page` (символы подчеркивания заменить косыми чертами, убрать `.php`). +1. Нормализовать `$page` (символы подчеркивания заменить косыми чертами, зачеркнуть `.php`). 2. Посмотрите в разделе POST routes (если используется метод POST) или GET routes. Если POST route не найден, вернитесь к GET routes. -3. **Проверка прав доступа ** через `checkPermission()`. Если отказано, наберите `denyAccess()` (переадресация или 403). -4. **Выполнение промежуточного программного обеспечения**. Вызывается каждый вызываемый объект в массиве `middleware`. Если какой-либо из них возвращает `false`, выполнение останавливается. +3. **Проверка прав доступа ** через `checkPermission()`. Если отказано, вызывает `denyAccess()` (перенаправление или 403). +4. **Выполнение промежуточного программного обеспечения**. Вызывается каждый вызываемый объект в массиве `middleware`. Если какой-либо из них возвращает значение `false`, выполнение прекращается. 5. **Вызов обработчика** через `callHandler()`. #### `dispatchApi($action)` порядок исполнения 1. Найдите в API маршруты по названию действия. -2. **Проверка прав доступа**. Если отказано, выводит `{"result": false}` и завершает работу. +2. **Проверка разрешений**. Если отказано, выводит `{"result": false}` и завершает работу. 3. **Вызов обработчика**. Промежуточное программное обеспечение не выполняется. -Важно: `dispatchApi()` не запускает промежуточное программное обеспечение. Это намеренное отличие от отправки страницы. +Важно: `dispatchApi()` не запускает промежуточное программное обеспечение. Это намеренное отличие от отправки страниц. ### Регистрация маршрута модуля -Модули регистрируют маршруты через `ModuleInterface::registerRoutes()`. Маршрутизатор поддерживает безопасный режим регистрации, предотвращающий перезапись модулями основных маршрутов: +Модули регистрируют маршруты с помощью `ModuleInterface::registerRoutes()`. Маршрутизатор поддерживает безопасный режим регистрации, предотвращающий перезапись модулями основных маршрутов: ```php $router->beginModuleRegistration(); @@ -347,7 +347,7 @@ $collisions = $router->drainRouteCollisions(); | --- | --- | | `hasRoute($page)` |Проверьте, существует ли маршрут страницы (GET или POST).| | `hasApiRoute($action)` |Проверьте, существует ли маршрут API| -| `getRoutes()` |Возвращает все зарегистрированные ключи маршрута как `['get' => [...], 'post' => [...], 'api' => [...]]`| +| `getRoutes()` |Возвращает все зарегистрированные ключи маршрута в виде `['get' => [...], 'post' => [...], 'api' => [...]]`| --- @@ -365,7 +365,7 @@ $collisions = $router->drainRouteCollisions(); | `notFound` | `notFound($message = 'Not Found')` |Отправьте запрос 404 и выйдите| | `header` | `header($name, $value)` |Установите один заголовок ответа| | `cors` | `cors()` |Установить заголовки CORS (`Access-Control-Allow-Origin: *`)| -| `noCache` | `noCache()` |Установить заголовки без кэширования (используется для плейлистов HLS)| +| `noCache` | `noCache()` |Установка заголовков без кэширования (используется для плейлистов HLS)| | `raw` | `raw($content, $contentType, $statusCode)` |Отправьте необработанный контент с указанием типа контента и завершите работу| | `empty` | `empty($statusCode = 204)` |Отправьте пустой ответ и завершите работу| @@ -379,8 +379,8 @@ $collisions = $router->drainRouteCollisions(); | --- | --- | | `CONTEXT_MINIMAL` |Автозагрузка + константы + конфигурация + регистратор. Нет подключения к базе данных.| | `CONTEXT_CLI` |+ База данных + `LegacyInitializer::initCore()` (очистка входных данных, настройки, пути FFmpeg). Необязательно Redis.| -| `CONTEXT_STREAM` |+ Только база данных (упрощенная, без `LegacyInitializer`). Конечные точки потоковой передачи используют `StreamingRequestBootstrap` вместо этого.| -| `CONTEXT_ADMIN` |+ Сессия + База данных + `LegacyInitializer::initCore()` + Redis + Admin API + Переводчик + глобальные настройки администратора. Полная инициализация.| +| `CONTEXT_STREAM` |+ Только база данных (упрощенная, без `LegacyInitializer`). Конечные точки потоковой передачи используют вместо этого `StreamingRequestBootstrap`.| +| `CONTEXT_ADMIN` |+ Сессия + База данных + `LegacyInitializer::initCore()` + Redis + API администратора + Переводчик + глобальные настройки администратора. Полная инициализация.| Все HTTP-контексты (не CLI) также запускают защиту от наводнений и проверку хоста перед инициализацией, зависящей от контекста. @@ -399,7 +399,7 @@ $collisions = $router->drainRouteCollisions(); | `src/Core/Init/LegacyInitializer.php` |Инициализация устаревшего ядра (очистка проводов в RequestManager)| | `src/Infrastructure/Bootstrap/StreamingRequestBootstrap.php` |Облегченный загрузчик конечной точки потоковой передачи| | `src/Streaming/StreamingBootstrap.php` |Потоковое подключение к базе данных и устаревшая инициализация| -| `src/bootstrap.php` |Унифицированный bootstrap (`XC_Bootstrap` class)| +| `src/bootstrap.php` |Унифицированный bootstrap (класс`XC_Bootstrap`)| | `src/Public/index.php` |Внешний контроллер для администратора/реселлера/игрока/API| | `src/Public/routes/admin.php` |Определения маршрутов на странице администратора| | `src/Public/routes/reseller.php` |Определения маршрута на странице реселлера| diff --git a/docs/ru/development/modules.md b/docs/ru/development/modules.md index b99d34ee..2ab14041 100644 --- a/docs/ru/development/modules.md +++ b/docs/ru/development/modules.md @@ -7,24 +7,24 @@ - Ядро (`Core/`) ничего не знает о модулях - Модули могут зависеть от `Core/` и `Domain/`, но никогда друг от друга (кроме как через объявленные зависимости). -- Любой модуль можно отключить с помощью `config/modules.php`, не касаясь ядра +- Любой модуль можно отключить из `config/modules.php`, не прикасаясь к ядру - Удаление каталога модуля не приводит к фатальным ошибкам --- ## Структура каталогов модулей -Имя каталога соответствует условию **`{name}_{hash5}`**, где `hash5` - это +The directory name follows the **`{name}_{hash5}`** convention, where `hash5` is the первые 5 символов модуля `hash_id`. Логическое имя модуля (`module.json` `name`, который никогда не содержит `_`) всегда разрешается из манифеста — никогда из базовое имя каталога. Это позволяет двум модулям с **одинаковым именем** работать в разных -каталоги (`watch_2541a`, `watch_9f1c0`) и установить без столкновения с файловой системой. То -конфигурация, график зависимостей и пространство имен - все это зависит от канонического `name`, поэтому каталог -переименование не требует переноса данных. Каждый модуль ** должен ** иметь `hash_id`: загружаемые файлы. -без него получите новый идентификатор, сгенерированный и записанный в их `module.json` перед размещением, +каталоги (`watch_2541a`, `watch_9f1c0`) и установите их без столкновения с файловой системой. То +конфигурация, график зависимостей и пространство имен - все это не соответствует каноническому `name`, поэтому каталог +переименование не требует переноса данных. Каждый модуль **должен ** иметь `hash_id`: загружаемые файлы, которые отправляются +без такового получите новый идентификатор, сгенерированный и записанный в их `module.json` перед размещением, таким образом, каталог без хэша никогда не создается. Устаревший пустой каталог `Modules/{name}/` из -более старое развертывание по-прежнему считывается, но оно ** автоматически переносится ** в `{name}_{hash5}` (генерируя -`hash_id`, если отсутствует) на следующем `console.php status` — макет без хэша удаляется, а не +более старое развертывание по-прежнему считывается, но оно ** автоматически переносится** в `{name}_{hash5}` (генерируя +`hash_id`, если отсутствует) при следующем `console.php status` — макет без хэша удаляется, а не держал. ```text @@ -44,21 +44,21 @@ src/Modules/my-module_9f1c0/ # {name}_{hash5}; canonical name is "my-module" └── my_page_scripts.php ``` -Модуль владеет своей схемой через ** три роли, которые отражают ядро** (`bin/install/database.sql` +Модуль владеет своей схемой через **три роли, которые отражают ядро** (`bin/install/database.sql` + `migrations/`): |Файл|Роль|Работает на| | ---- | ---- | ------- | -| `database.sql` |**Одна** главная схема — полная текущая `CREATE`/начальная|новая **установка**| +| `database.sql` | **One** master schema — the full current `CREATE`/seed |новая **установка**| | `database_drop.sql` |**Одно** удаление — `DROP TABLE` для каждой таблицы, которой владеет модуль|**удалить**| -| `migrations/.sql` |**Папка** прямых переходов между версиями|**обновление ** для версий в `(installed, current]`| +| `migrations/.sql` |**Папка** прямых переходов между версиями|**обновить** для версий в `(installed, current]`| Правила: - **Новая установка выполняется только `database.sql`**, поэтому она всегда должна соответствовать последней версии. -схема (каждая дельта загнута внутрь). Записанный символ `installed_version` является водяным знаком — +схема (каждая дельта загнута внутрь). Записанный `installed_version` является водяным знаком — ошибки никогда не воспроизводятся при новой установке. -- **Дельты доступны только в прямом направлении** (`ALTER`/`INSERT`), с именем `.sql` — разрыв - это +- **Дельты направлены только вперед** (`ALTER`/`INSERT`), названный `.sql` — демонтаж - это одинарный `database_drop.sql`, поэтому нет файлов для каждой версии `.down`. - Сохраняйте дельты ** идемпотентными** (`ADD COLUMN IF NOT EXISTS`, `INSERT IGNORE`), чтобы повторные запуски были безопасными. - Модуль без схемы не отправляет ни один из этих файлов. Модуль, работающий только с разницей (нет `database.sql`) @@ -88,12 +88,12 @@ src/Modules/my-module_9f1c0/ # {name}_{hash5}; canonical name is "my-module" |Поле|Тип|По умолчанию|Описание| | ------ | ----- | :---: | ------------ | -| `name` | `string` |—|Каноническое имя модуля (в случае с kebab, без `_`). Каталог - это `{name}_{hash5}`, но код всегда использует это значение манифеста, а не базовое имя каталога.| -| `hash_id` | `string` |сгенерированный|**Постоянный** идентификатор модуля — случайный шестнадцатеричный код из 32 символов, генерируемый ОДИН раз и никогда не изменяющийся при изменении версии или переименовании. Его первые 5 символов образуют суффикс каталога `{name}_{hash5}`. Не редактируйте вручную.| +| `name` | `string` |—|Каноническое имя модуля (в случае с kebab, без `_`). Каталог равен `{name}_{hash5}`, но код всегда использует это значение манифеста, а не базовое имя каталога.| +| `hash_id` | `string` |сгенерированный|**Постоянный** идентификатор модуля — случайный 32-разрядный шестнадцатеричный код, генерируемый ОДИН раз и никогда не изменяющийся при изменении версии или переименовании. Его первые 5 символов образуют суффикс каталога `{name}_{hash5}`. Не редактируйте вручную.| | `description` | `string` | `""` |Удобочитаемое описание| -| `version` | `string` |—|Предварительная версия (`1.0.0`)| +| `version` | `string` |—|Средняя версия (`1.0.0`)| | `requires_core` | `string` |—|Минимальная версия ядра (`>=2.0`)| -| `environment` | `string` | `"main"` |`main`, `lb`, или `any`| +| `environment` | `string` | `"main"` |`main`, `lb` или `any`| | `priority` | `int` | `0` |Приоритет загрузки — более высокие нагрузки раньше| | `dependencies` | `array` | `[]` |Жесткие зависимости; если они недоступны, зависимый объект пропускается (см. ниже)| | `optional_dependencies` | `array` | `[]` |Мягкие зависимости (загруженные ранее, если они есть)| @@ -102,24 +102,24 @@ src/Modules/my-module_9f1c0/ # {name}_{hash5}; canonical name is "my-module" > **`hash_id` — постоянный идентификатор модуля.** Это случайное значение из 32 шестнадцатеричных чисел, > сгенерированный ** один раз ** и ** никогда** впоследствии не изменявшийся — он должен пережить изменения версий -> и переименовывает (так что это случайное значение, а не производное от `name`/`version`). Сгенерируйте его с помощью +> и переименовывает (чтобы оно было случайным, а не производным от `name`/`version`). Сгенерируйте его с помощью > `php -r 'echo bin2hex(random_bytes(16));'` и вставьте его в `module.json`, когда -> создайте новый модуль. Не создавайте его вручную и не используйте повторно другой модуль. Это придает модулям стабильную идентификацию, независимую от `name`, +> создайте новый модуль. Не создавайте его вручную и не используйте повторно другой модуль. Это придает модулям стабильную идентичность, независимую от `name`, > который является основой для перемещения модулей в отдельные репозитории и для создания -> явный источник обновления для каждого модуля ** — блок `update` manifest (смотрите ниже). +> явный источник обновления для каждого модуля ** - блок манифеста `update` (смотрите ниже). **Жесткие и мягкие зависимости:** -- `dependencies` — если какой—либо модуль недоступен (отсутствует на диске, отключен или находится в состоянии `failed`), зависимый модуль ** пропускается ** с записанным каскадным предупреждением (все, что зависит от него, также пропускается). Остальные модули, панель администратора и интерфейс командной строки продолжают работать; единственная неудовлетворенная зависимость больше не прерывает всю загрузку. +- `dependencies` — если какой—либо модуль недоступен (отсутствует на диске, отключен или находится в состоянии `failed`), зависимый модуль ** пропускается** с каскадным предупреждением в журнале (все, что зависит от него, также пропускается). Остальные модули, панель администратора и интерфейс командной строки продолжают работать; единственная неудовлетворенная зависимость больше не прерывает всю загрузку. - `optional_dependencies` — загружается перед этим модулем, если присутствует, автоматически пропускается, если отсутствует -> ** Защита от смещения.** Модуль, от которого зависят все еще включенные модули, не может быть `disabled` через панель / `ModuleManager::setState()` - операция отклоняется вместе со списком зависимых объектов (зеркальное отображение защиты `uninstallModule()`). Это предотвращает переход в состояние "`plex` включен, но его `watch` зависимость отключена". +> **Защита от смещения.** Модуль, от которого зависят все еще включенные модули, не может быть `disabled` передан через панель / `ModuleManager::setState()` - операция отклоняется вместе со списком зависимых объектов (зеркальное отображение защиты `uninstallModule()`). Это предотвращает переход в состояние "`plex` включено, но его зависимость `watch` отключена". **Приоритет:** -- Сначала при топологической сортировке учитывается график зависимостей, затем в пределах той же группы выполняется сортировка по `priority` по убыванию (большее число = загружено ранее), затем по алфавиту +- Сначала при топологической сортировке учитывается график зависимостей, затем в пределах той же группы выполняется сортировка по убыванию `priority` (большее число = загружено ранее), затем по алфавиту -**Источник обновления (`update` block, необязательно):** +**Источник обновления (блок `update`, необязательный):** Откуда модуль получает свои обновления. Отсутствует → `bundled` (файлы отправляются вместе с панелью и обновляются вместе с ней). @@ -133,19 +133,19 @@ src/Modules/my-module_9f1c0/ # {name}_{hash5}; canonical name is "my-module" } ``` -- `source` — `bundled` ( с помощью панели), `platform` (магазин SaaS), `git` (репозитории), `url` (автономный хостинг). Неизвестные значения возвращаются к `bundled`. -- `repository` — git remote (для `git`); `slug` — store slug (для `platform`, по умолчанию используется значение `name`); `url` — URL версии/архива (для `url`); `channel` — `stable`/`beta` ( по умолчанию `stable`). +- `source` — `bundled` (с панелью управления), `platform` (хранилище SaaS), `git` (репозитории), `url` (автономный хостинг). Неизвестные значения возвращаются к значению `bundled`. +- `repository` — удаленный git (для `git`); `slug` — модуль хранилища (для `platform`, по умолчанию используется `name`); `url` — URL версии/архива (для `url`); `channel` — `stable`/`beta` (по умолчанию `stable`). -Блок нормализуется с помощью `ModuleLoader` и отображается с помощью `ModuleManager::listModules()`. Еженедельный cron (`cron:module_updates`) проверяет `git`/`url` источники и записи `available_version`, что приводит к нажатию кнопки "Обновить" на X** (отображается только при наличии более новой версии). При нажатии кнопки "Обновить" запускается `ModuleManager::updateModuleFromSource()`: +Блок нормализуется по `ModuleLoader` и выставляется через `ModuleManager::listModules()`. Еженедельный cron (`cron:module_updates`) проверяет источники `git`/`url` и записывает данные `available_version`, что приводит к нажатию кнопки обновления ** на X** (отображается только при наличии более новой версии). Нажатие кнопки обновления запускает `ModuleManager::updateModuleFromSource()`: - `bundled` — файлы поступают вместе с панелью; Обновление просто запускает отложенные миграции. -- `platform` — делегирован потоку установки/обновления в магазине (откат + разветвление LB внутри). -- `git` — загружает ресурс выпуска **`module.tar.gz`** по тегу == новая версия (md5-проверяется с помощью выпуска `hashes.md5`, если он присутствует). -- `url` — перечитывает `version.json` для его `download` (https) + необязательный `md5`. +- `platform` — делегировано потоку установки/обновления в магазине (откат + разветвление LB внутри). +- `git` — загружает ресурс выпуска **`module.tar.gz`** по тегу == новая версия (md5-проверяется с помощью выпуска `hashes.md5`, если присутствует). +- `url` — перечитывает `version.json` для его `download` (https) + необязательно `md5`. -Для `git`/`url` выбранный `module.json` **`hash_id` должен совпадать с установленным** (идентификация — репозиторий /URL-адрес не может выдавать себя за другой модуль), затем: резервное копирование → замена файлов → перенос → ** откат при любом сбое** → распространение в LB. +Для `git`/`url` выбранное значение `module.json` **`hash_id` должно совпадать с установленным значением ** (идентификация — репозиторий /URL-адрес не может выдавать себя за другой модуль), затем: резервное копирование → замена файлов → миграция → ** откат при любом сбое ** → распространение до фунта стерлингов. -**Стандартный набор и подготовка.** Модули, устанавливаемые панелью по умолчанию, перечислены в `config/bundled_modules.php`, а их ключ - в `hash_id` (неизменен для всех переименований). Сегодня все они имеют `bundled` (их файлы находятся в архиве панели). Когда модуль извлекается в свой собственный репозиторий, измените его запись на `git`/`url`/`platform` источник — `syncBundledModules()`, затем автоматически извлекает и устанавливает его с помощью `provisionStandardSet()` (не требуется, пока все находится в комплекте на диске). `ModuleManager::findModuleByHashId()` определяет модуль по его стабильному идентификатору независимо от каталога/имени. +**Стандартный набор и подготовка.** Модули, которые панель устанавливает по умолчанию, перечислены в `config/bundled_modules.php`, а их ключ - в `hash_id` (неизменен при переименовании). На сегодняшний день все модули имеют `bundled` (их файлы находятся в архиве панели). Когда модуль извлекается в свой собственный репозиторий, измените его запись на `git`/`url`/`platform` source — `syncBundledModules()`, затем автоматически извлекает и устанавливает его с помощью `provisionStandardSet()` (это не требуется, пока все находится в комплекте на диске). `ModuleManager::findModuleByHashId()` определяет модуль по его стабильному идентификатору независимо от каталога/имени. --- @@ -161,7 +161,7 @@ ModuleInterface └── NavbarProviderInterface → registerNavbar() ``` -`StreamMiddlewareProviderInterface` является ** необязательным ** — он НЕ является частью `ModuleInterface`. +`StreamMiddlewareProviderInterface` является **необязательным** — он не является частью `ModuleInterface`. Реализуйте это только в том случае, если модулю необходимо внедрить себя в потоковый конвейер. ```php @@ -177,7 +177,7 @@ class MyModule implements ModuleInterface, StreamMiddlewareProviderInterface { ## Класс модуля -Extend `BaseModule` — он не предоставляет значения по умолчанию для каждого необязательного метода, так что вы можете использовать только +Extend `BaseModule` — он предоставляет значения по умолчанию без операций для каждого необязательного метода, так что вы можете использовать только переопределите то, что на самом деле использует модуль. Требуются только `getName()` и `getVersion()`. ```php @@ -231,9 +231,9 @@ class MyModuleModule extends BaseModule { ``` > ** Совет:** модулю без маршрутов, элементов навигационной панели и команд CLI требуется только -> `getName()`, `getVersion()`, и `boot()`. -> Модуль изолированной подсистемы (его собственная точка входа и bootstrap, например, Ministra) обычно -> оставляет `boot()` и `registerRoutes()` унаследованными как недействительные. +> `getName()`, `getVersion()` и `boot()`. +> Модуль изолированной подсистемы (его собственная точка входа и bootstrap, например Ministra) обычно +> оставляет `boot()` и `registerRoutes()` унаследованными как не выполняемые операции. ### Метод контракта @@ -249,20 +249,20 @@ class MyModuleModule extends BaseModule { | `uninstall(): void` | `ModuleInterface` |Запуск при удалении модуля (очистка)| > **Важно — версия может храниться в двух местах.** Модуль объявляет свою версию -> **дважды**: поле `"version"` в поле `module.json` и возвращаемое значение +> **дважды**: поле `"version"` в `module.json` и возвращаемое значение > `getVersion()` в классе module. **Сохраняйте их идентичными и изменяйте оба перед > издательский.** Во время выполнения манифест `version` имеет приоритет — установка/обновление > и водяной знак `installed_version` сначала читается как `module.json`, и только потом возвращается -> to `getVersion()` — таким образом, устаревший @@ 1@@ тихо выходит из строя и является +> to `getVersion()` — so a stale `getVersion()` silently drifts out of sync and is a > распространенный источник ошибок типа "выполнена /не выполнена неправильная миграция". Если модуль отправляет файл -> миграции, `database.sql` (основная схема) и самый высокий `migrations/.sql` +> migrations, `database.sql` (master schema) and the highest `migrations/.sql` > дельта также должна соответствовать этой версии. --- ## PHP пространства имен -Каждый модуль находится в выделенном пространстве имен PHP: `XcVm\Module\{Pascal}`, где `{Pascal}` - это +Каждый модуль находится в выделенном пространстве имен PHP: _BOS_0}, где _BOS_1} - это преобразование имени каталога модуля в PascalCase. ``` @@ -270,7 +270,7 @@ src/Modules/my-module/ → namespace XcVm\Module\MyModule; src/Modules/watch/ → namespace XcVm\Module\Watch; ``` -В главном файле модуля должно быть объявлено это пространство имен с расширением `BaseModule`: +В главном файле модуля должно быть объявлено это пространство имен и расширено `BaseModule`: ```php Module.php` — обязательно (соглашение с загрузчиком модулей) -- Все остальные имена файлов класса: `.php` +- All other class filenames: `.php` - Добавьте `use ClassName;` для каждого базового класса, на который ссылается ссылка (базовый модуль, ServiceContainer, маршрутизатор и т.д.) - Никогда не импортируйте классы из других модулей — общайтесь через события или контейнер DI @@ -338,7 +338,7 @@ $container->decorate('stream.encoder', function (mixed $inner, ServiceContainer ``` Декораторы объединены в цепочки по приоритету (самый высокий и самый внешний). Защищенные сервисы -(`db`, `settings`, `config`, `auth`) не удается оформить — при любой попытке генерируется `RuntimeException`. +(`db`, `settings`, `config`, `auth`) не удается оформить — любая попытка приводит к результату `RuntimeException`. ### Соответствие требованиям стандарта PSR-11 @@ -355,7 +355,7 @@ public function has(string $id): bool; ## События PSR-14 -События - это простые PHP классы. Отправляйте их через `EventDispatcher`: +События - это простые классы PHP. Отправляйте их через `EventDispatcher`: ```php // In any module @@ -369,7 +369,7 @@ EventDispatcher::listen(MyEvent::class, function (MyEvent $e): void { **Приоритет** — более высокое целое число = вызывается первым. По умолчанию `0`. -**События, которые можно остановить ** — введите `AbstractEvent` и вызовите `$e->stopPropagation()`: +**Останавливаемые события** — продлить `AbstractEvent` и вызвать `$e->stopPropagation()`: ```php class MyGatingEvent extends AbstractEvent { @@ -423,7 +423,7 @@ class MyStreamMiddleware implements StreamMiddlewareInterface { ``` `StreamContext` - это набор атрибутов (`get`, `set`, `has`, `abort`, `isAborted`). `StreamPipeline` -выполняет промежуточное программное обеспечение, отсортированное по `getPriority()` убыванию. +выполняет промежуточное программное обеспечение, отсортированное по убыванию `getPriority()`. ### Приоритеты трубопровода @@ -453,16 +453,16 @@ return [ ]; ``` -Доступные значения `state` (подкрепленные `ModuleState` enum): +Доступные значения `state` (подкрепленные перечислением `ModuleState`): |Ценность|Значение| | ----- | ------- | | `enabled` |Загрузка модуля (по умолчанию)| | `disabled` |Модуль обнаружен, но пропущен| -| `installing` |Переходное состояние, заданное `ModuleManager` во время установки| +| `installing` |Переходное состояние, заданное значением `ModuleManager` во время установки| | `failed` |Ошибка установки; модуль пропущен (не загружен)| -> **Панельная диагностика.** На странице "Модули" отображается желтый значок "Проблема с зависимостями" рядом со статусом модуля, когда требуемая зависимость отсутствует или не включена (например, `plex` означает `Enabled`, а `watch` - это `failed`). Во всплывающей подсказке к значку перечислены конкретные проблемы. Это поле `dependency_warnings` вычисляется с помощью `ModuleManager::listModules()`. +> **Диагностика панели.** На странице "Модули" отображается желтый значок "Проблема с зависимостями" рядом со статусом модуля, когда требуемая зависимость отсутствует или не включена (например, `plex` означает `Enabled`, а `watch` - `failed`). Во всплывающей подсказке к значку перечислены конкретные проблемы. Это поле `dependency_warnings` вычисляется с помощью `ModuleManager::listModules()`. Чтобы переопределить класс, разрешенный для модуля: @@ -485,12 +485,12 @@ return [ 2. Применяет переопределения из `config/modules.php` 3. Фильтры по окружающей среде (`main` / `lb` / `any`) 4. Определяет порядок загрузки: - - `pruneUnsatisfiableModules()` удаляет модули, требуемые зависимости которых недоступны (каскадно, с зарегистрированным предупреждением), поэтому загрузка никогда не прерывается + - `pruneUnsatisfiableModules()` удаляет модули, требуемые зависимости которых недоступны (каскадно, с зарегистрированным предупреждением), чтобы загрузка никогда не прерывалась - Топологическая сортировка (DFS) по графу зависимостей - - В пределах одной и той же группы зависимостей выполните сортировку по `priority` по убыванию, затем по алфавиту - - Выдает `RuntimeException` в циклах (циклические зависимости остаются фатальными) + - В пределах одной и той же группы зависимостей выполните сортировку по убыванию `priority`, затем по алфавиту + - Выдает `RuntimeException` для циклов (циклические зависимости остаются фатальными) - Отсутствующие необязательные зависимости автоматически пропускаются -5. Преобразует имя класса: `my-module` → ПОЛНОЕ имя `XcVm\Module\MyModule\MyModuleModule` +5. Resolves class name: `my-module` → FQN `XcVm\Module\MyModule\MyModuleModule` (kebab-case → PascalCase; может быть переопределен с помощью клавиши `class` в конфигурации) 6. Регистрирует автозагрузчик модуля PSR-4 (сопоставляет `XcVm\Module\` с каталогом модуля) 7. Создает экземпляр класса module @@ -516,10 +516,10 @@ $manager->downloadFromPlatform(slug: 'my-module', version: '1.2.0', apiKey: $key Под капотом: -1. @Расширение @0@@ — C загружает, расшифровывает и распаковывает файлы +1. `XC_VM::module_install($slug, $version, $apiKey)` — Расширение C загружает, расшифровывает и распаковывает файлы 2. `installModule($slug)` — запускает `install()` в модуле 3. `EventDispatcher::dispatch(new PackageInstalledEvent(...))` — отправляет событие -4. `hotReload($slug, $path)` — загружает модуль в текущем запросе **без перезапуска PHP-FPM** +4. `hotReload($slug, $path)` — загружает модуль в текущем запросе **без перезапуска PHP-FPM.** --- @@ -527,7 +527,7 @@ $manager->downloadFromPlatform(slug: 'my-module', version: '1.2.0', apiKey: $key Модуль может быть полностью изолированной подсистемой со своей собственной точкой входа и начальной загрузкой (например, Ministra). Это ** соглашение**, а не маркерный интерфейс — он остается -обычный `ModuleInterface`/`BaseModule` модуль: +обычный модуль `ModuleInterface`/`BaseModule`: ```php class MyModule extends BaseModule { @@ -545,8 +545,8 @@ class MyModule extends BaseModule { Изоляция означает, что подсистема работает через свою собственную общедоступную точку входа (например, `my-module/portal.php`, путь относительно `src/`, который обрабатывает свой собственный bootstrap) с отдельным путем начальной загрузки. Он использует общую инфраструктуру (базу данных, кэш, конфигурацию), но -не участвует в основном `Router`, `ModuleLoader::bootAll()`, или -`NavbarRegistry`. Реализации `boot()` и `registerRoutes()`@ обычно являются +**не** участвует в основных `Router`, `ModuleLoader::bootAll()` или +`NavbarRegistry`. Реализации `boot()` и `registerRoutes()` обычно являются оставлено как унаследованное бездействие. --- @@ -576,14 +576,14 @@ class MyController { |Правило| | | --------- | -- | | `__DIR__ . '/views'` |viewsPath — контроллер находится внутри каталога модуля| -|ПОЛУЧАТЬ страницы|позвоните `renderUnifiedLayoutHeader` перед просмотром, `renderUnifiedLayoutFooter` после| +|ПОЛУЧАТЬ страницы|вызовите `renderUnifiedLayoutHeader` перед просмотром, `renderUnifiedLayoutFooter` после| |Действия API|нет макета — возвращаем JSON и выходим| --- ## Задача Cron -**Логика Cron** (`MyCron.php`) — только бизнес-логика, без подключения CLI. +**Cron logic** (`MyCron.php`) — только бизнес-логика, без подключения к CLI. **Оболочка CronJob** (`MyCronJob.php`) — реализует `CommandInterface`, использует `CronTrait`: @@ -629,7 +629,7 @@ public function getCronEntries(): array { ``` `ModuleLoader::collectCronEntries()` объединяет записи всех модулей и `StartupCommand` / -`StatusCommand` автоматически записывает их в системную crontab — никаких изменений в основных файлах не требуется. +`StatusCommand` автоматически записывайте их в системный crontab — никаких изменений в основных файлах не требуется. **Формат:** ключ = выражение cron, значение = имя консольной команды, зарегистрированное через `registerCommands()`. @@ -639,10 +639,10 @@ public function getCronEntries(): array { > **Два механизма, оба аддитивные.** Схема на основе файлов**, описанная в разделе > [Структура каталогов модулей](#module-directory-structure) (`database.sql` мастер + -> `database_drop.sql` teardown + `migrations/.sql` deltas) используется по умолчанию для +> `database_drop.sql` разборка + `migrations/.sql` дельты) используется по умолчанию для > простой DDL/seed. `MigratableInterface` ниже приведен программный путь для обновления. -> шаги, для которых требуется логика PHP (повторное заполнение данных, условные изменения). Модуль может использовать -> либо один из них, либо оба; `ModuleManager::updateModule()` сначала запускает файл delta, затем +> шаги, требующие логики PHP (повторное заполнение данных, условные изменения). Модуль может использовать +> один из них или оба; `ModuleManager::updateModule()` сначала запускает файл delta, затем > вызываемые миграции. Модули, для обновления которых требуется PHP логическая реализация `MigratableInterface`: @@ -672,22 +672,22 @@ class MyModuleModule extends BaseModule implements MigratableInterface { ``` `ModuleManager::updateModule()` считывает `installed_version` из хранилища переопределений, фильтрует -сопоставляет только записи `> fromVersion && <= toVersion`, сортирует по параметрам и запускает каждую из них. +сопоставляет только записи `> fromVersion && <= toVersion`, сортирует по полу и запускает каждую из них. вызываемый в своей собственной транзакции базы данных. `installModule()` записи `installed_version` после -успешная установка; `uninstallModule()` очищает ее. +успешная установка; `uninstallModule()` удаляет ее. **Основные правила:** - Ключи - это полустрочные строки (`'1.1.0'`, `'2.0.0'`) — `version_compare` используется упорядочение - Каждая миграция выполняется в рамках своей собственной транзакции — сбой откатывает только этот шаг - `BaseModule` предоставляет значение по умолчанию `getMigrations(): array { return []; }`, поэтому реализация -`MigratableInterface` необязательно +`MigratableInterface` является необязательным --- ## Composer обнаружение пакета -Модули могут распространяться в виде пакетов Composer с `"type": "xcvm-module"`: +Модули могут распространяться в виде Composer пакетов с `"type": "xcvm-module"`: ```json { @@ -702,8 +702,8 @@ class MyModuleModule extends BaseModule implements MigratableInterface { ``` `ModuleLoader` автоматически сканирует `vendor/composer/installed.json` (Composer 1 и 2 -форматы) и обнаруживает все установленные пакеты `xcvm-module` вместе со встроенным -`src/Modules/` directory. Пакеты дедуплицируются — модуль как в `modules/`, так и в @@ +форматирует) и обнаруживает все установленные пакеты `xcvm-module` вместе со встроенным +каталог `src/Modules/`. Пакеты дедуплицируются — модуль как в `modules/`, так и в `vendor/` загружается только один раз. --- @@ -711,22 +711,22 @@ class MyModuleModule extends BaseModule implements MigratableInterface { ## Контрольный список модулей - [ ] Создать `src/Modules//` -- [ ] Добавьте `namespace XcVm\Module\;` в каждый файл класса +- [ ] Добавить `namespace XcVm\Module\;` к каждому файлу класса - [ ] Создать `module.json` с помощью `name`, `version`, `requires_core`, `priority`, `dependencies`, `optional_dependencies` -- [ ] Поставьте перманентную печать `hash_id` (`php -r 'echo bin2hex(random_bytes(16));'`; никогда не пишите это от руки) -- [ ] Создать `Module.php` расширение `BaseModule` -- [ ] Укажите версию в **обоих вариантах** `module.json` `"version"` и `getVersion()` — они должны совпадать (измените оба перед публикацией) +- [ ] Поставьте постоянную отметку `hash_id` (`php -r 'echo bin2hex(random_bytes(16));'`; никогда не пишите ее от руки) +- [ ] Create `Module.php` extending `BaseModule` +- [ ] Укажите версию в ** как ** `module.json` `"version"`, так и `getVersion()` — они должны совпадать (измените обе версии перед публикацией) - [ ] Реализовать `boot()` для всех сервисов, предоставляемых модулем - [ ] Реализовать `registerRoutes()` для конечных точек HTTP/API -- [ ] Внедрить `registerNavbar()` для элементов панели администратора (или оставить пустыми) -- [ ] (Если кроны) Создать `MyCron.php` + `MyCronJob.php`, зарегистрироваться в `registerCommands()` -- [ ] (Если crons) Переопределяет `getCronEntries()` в классе module (основной файл не изменяется) -- [ ] (Схема If) Отправка `database.sql` (мастер), `database_drop.sql` (демонтаж) и `migrations/.sql` дельты +- [ ] Внедрить `registerNavbar()` для элементов панели администратора (или оставить пустым) +- [ ] (Если кроны) Создайте `MyCron.php` + `MyCronJob.php`, зарегистрируйтесь в `registerCommands()` +- [ ] (Если crons) Переопределяет `getCronEntries()` в классе модуля (основной файл не изменяется) +- [ ] (Схема If) Отправляет значения `database.sql` (мастер), `database_drop.sql` (демонтаж) и `migrations/.sql` дельт - [ ] (При переносе PHP-логики) Реализовать `MigratableInterface::getMigrations()` - [ ] (Если страницы) Создайте контроллер, используя `renderUnifiedLayoutHeader/Footer` -- [ ] (Если потоковое промежуточное программное обеспечение) Реализуйте `StreamMiddlewareProviderInterface` отдельно +- [ ] (Если потоковое промежуточное программное обеспечение) Реализовать `StreamMiddlewareProviderInterface` отдельно - [ ] Проверить: `php -l src/Modules//Module.php` -- [ ] Проверьте: `php console.php --list` отображает команды модуля +- [ ] Verify: `php console.php --list` shows the module's commands - [ ] Проверьте: удаление каталога модуля не приводит к фатальной ошибке --- @@ -734,11 +734,11 @@ class MyModuleModule extends BaseModule implements MigratableInterface { ## часто задаваемые вопросы **Вопрос: Как мне отключить модуль?** -В `src/config/modules.php` добавьте `'module-name' => ['state' => 'disabled']`. +В поле `src/config/modules.php` добавьте `'module-name' => ['state' => 'disabled']`. Устаревшая форма `'enabled' => false` также принята для обеспечения обратной совместимости. ** Вопрос: Как мне объявить, что мой модуль зависит от другого?** -Используйте `dependencies` в `module.json` для жесткого удаления (должно присутствовать) или `optional_dependencies` +Используйте `dependencies` в `module.json` для жестких удалений (должно присутствовать) или `optional_dependencies` для мягкого удаления (загружается раньше вашего, если присутствует, и автоматически пропускается, если отсутствует). ** Вопрос: Могу ли я украсить основную услугу?** @@ -746,21 +746,21 @@ class MyModuleModule extends BaseModule implements MigratableInterface { Защищенные сервисы (`db`, `settings`, `config`, `auth`) не могут быть оформлены. **Вопрос: Как мне прослушивать основные события?** -Вызывайте `EventDispatcher::listen(EventClass::class, callable, priority)` в любом месте после начальной загрузки, +Вызовите `EventDispatcher::listen(EventClass::class, callable, priority)` в любом месте после начальной загрузки, обычно внутри `boot()` или выделенного класса подписчиков. **Вопрос: Могу ли я отправлять пользовательские события из модуля?** Да. Создайте простой класс или расширьте `AbstractEvent` и вызовите `EventDispatcher::dispatch(new MyEvent(...))`. -** Вопрос: Для чего нужен `StreamMiddlewareProviderInterface`?** -Это позволяет модулю вводить `StreamMiddlewareInterface` в конвейер потоковой обработки -не изменяя `StreamProcess.php`. При необходимости применяйте его вместе с `ModuleInterface`. +**Вопрос: Для чего используется `StreamMiddlewareProviderInterface`?** +Это позволяет модулю вводить значение `StreamMiddlewareInterface` в конвейер потоковой обработки +без изменения `StreamProcess.php`. При необходимости реализуйте его вместе с `ModuleInterface`. ## Связанные файлы |Файл|Роль| | --- | --- | -| `src/Core/Module/ModuleLoader.php` |Обнаруживает, сортирует и загружает модули; PSR-4 распознаватель классов| +| `src/Core/Module/ModuleLoader.php` | Discovers, sorts and boots modules; PSR-4 class resolver | | `src/config/modules.php` |Конфигурация включения модуля / переопределения класса| | `src/Modules/` |Каталоги модулей| | `src/Core/Module/Contract/` |Подинтерфейсы модуля| diff --git a/docs/ru/development/navbar-rendering.md b/docs/ru/development/navbar-rendering.md index 73c1560c..2c9c3f5a 100644 --- a/docs/ru/development/navbar-rendering.md +++ b/docs/ru/development/navbar-rendering.md @@ -22,18 +22,18 @@ Рендеринг выполняется вспомогательными функциями: 1. `_xc_nav_visible()` - фильтрация видимости для узла. -2. `_xc_nav_label()` - разрешение текстовой надписи. +2. `_xc_nav_label()` - разрешение текстовой метки. 3. `_xc_nav_children()` - рекурсивный рендеринг дочерних элементов. Узлы верхнего уровня берутся из `NavbarRegistry::getTopLevel()`, дочерние узлы берутся из `NavbarRegistry::getChildren($key)`. ## Правила видимости -Проверки выполняются в режиме `_xc_nav_visible()`: +Проверки выполняются в `_xc_nav_visible()`: 1. `desktopOnly`: скрывает узел на мобильном устройстве. 2. `settingDisabled`: скрывает узел, когда включен флаг настройки. -3. `permissions`: ИЛИ-проверить через `Authorization::check('adv', $permission)`. +3. `permissions`: ИЛИ-проверить с помощью `Authorization::check('adv', $permission)`. 4. Группа с `url='#'` отображается только в том случае, если виден хотя бы один дочерний элемент. 5. `divider` всегда передается и отображается как разделитель. @@ -68,8 +68,8 @@ public function registerNavbar(): void { ## Практические правила для модулей 1. Используйте уникальные значения `key` в формате `section.group.item`. -2. Установите `parent` для существующего основного узла дерева или вашего собственного, уже добавленного узла. -3. Расположите элементы, используя `order` внутри одного родительского элемента. +2. Установите значение `parent` для существующего основного узла дерева или вашего собственного, уже добавленного узла. +3. Расположите элементы, используя `order`, внутри одного родительского элемента. 4. Используйте `label('translation_key')` для перевода текста. 5. Используйте `label('', 'Literal Text')` для фиксированного буквального текста. 6. Если в модуле нет пунктов меню, оставьте `registerNavbar()` пустым. diff --git a/docs/ru/development/streaming-subsystem.md b/docs/ru/development/streaming-subsystem.md index 486f12f3..741a0104 100644 --- a/docs/ru/development/streaming-subsystem.md +++ b/docs/ru/development/streaming-subsystem.md @@ -1,6 +1,6 @@ # Подсистема потоковой передачи -Подсистема потоковой передачи обрабатывает передачу в режиме реального времени, VOD и timeshift. +Подсистема потоковой передачи обрабатывает доставку в реальном времени, VOD и timeshift. Это быстрый путь (~10-100 тыс. запросов в минуту, <50 мс p99), и он использует отдельный облегченный bootstrap, чтобы избежать загрузки всего стека администратора. --- @@ -23,14 +23,14 @@ endpoint logic (live.php / vod.php / timeshift.php) ShutdownHandler::handle() ``` -nginx переписывает все URL-адреса потоковой передачи на PHP точки входа под `www/stream/`: +nginx переписывает все URL-адреса потоковой передачи на PHP точки входа в соответствии с `www/stream/`: |Шаблон URL-адреса|Точка входа|Цель| | --- | --- | --- | | `/auth/{token}` | `live.php` |Прямая трансляция| | `/vauth/{token}` | `vod.php` |Доставка видео по запросу| | `/tsauth/{token}` | `timeshift.php` |Архив/timeshift воспроизведение| -| `/hls/{token}` | `segment.php` |HLS сегментная поставка| +| `/hls/{token}` | `segment.php` |HLS сегментная доставка| | `/key/{token}` | `key.php` |Ключ шифрования AES-128| | `/subauth/{token}` | `subtitle.php` |Передача субтитров| @@ -87,12 +87,12 @@ src/www/stream/ Действия в порядке: 1. Загружайте коды ошибок, обработчик, пути, конфигурацию, двоичные файлы. -2. Защита от наводнений (только по протоколу HTTP): проверьте наличие `FLOOD_TMP_PATH . 'block_' . $rIP`. +2. Защита от наводнений (только HTTP): проверьте наличие `FLOOD_TMP_PATH . 'block_' . $rIP`. 3. Загрузите настройки из файлового кэша (`CACHE_TMP_PATH . 'settings'`). 4. Проверка хоста (только HTTP): проверка на соответствие `allowed_domains`. 5. Инициализируйте регистратор. 6. Аварийно закрытый шлюз: возвращает 404, если настройки отсутствуют (кроме `/status`). -7. Позвоните `StreamingBootstrap::bootstrap()`. +7. Вызовите `StreamingBootstrap::bootstrap()`. ### 2. Потоковый загрузчик::bootstrap() @@ -153,7 +153,7 @@ StreamAuthMiddleware::decryptToken($rToken, $rSettings, $rServers, $rIP): array Утверждение: 1. Расшифруйте токен, используя `live_streaming_pass`. -2. Проверьте срок действия: `$rTokenData['expires'] < time() - $rServers[SERVER_ID]['time_offset']`. +2. Проверьте истечение срока действия: `$rTokenData['expires'] < time() - $rServers[SERVER_ID]['time_offset']`. 3. Возвращает проанализированные данные токена или вызывает ошибку. Заголовки ответов задаются через `StreamAuthMiddleware::sendStreamHeaders()`: @@ -174,16 +174,16 @@ Alt-Svc: h3-29, h3-T051, h3-Q050 (HTTP/3 hints) Основная конечная точка доставки (~650 строк): 1. Расшифруйте токен с помощью `StreamAuthMiddleware::decryptToken()`. -2. Разрешить работу сервера/прокси-сервера: `StreamAuth::checkAccess()` + `ProxySelector::availableProxy()`. +2. Разрешить использование сервера/прокси-сервера: `StreamAuth::checkAccess()` + `ProxySelector::availableProxy()`. 3. Установите ограничения на подключение: `StreamAuth::validateConnections()`. 4. Создайте запись о подключении: `ConnectionTracker::createConnection()`. -5. Передача вручную демону **`xc_fanout` (смотрите ниже): PHP выдает сообщение +5. Hand delivery to the **`xc_fanout` daemon** (see below): PHP emits an `X-Accel-Redirect` и завершает байтовый путь — nginx передает байты в потоковом режиме. - **ТС:** `X-Accel-Redirect: /xc_fanout/?c=&prebuffer=N` (nginx перезаписывается в файл демона `/live/`). - **HLS:** список воспроизведения указывает на выделенные сегменты; `segment.php` транслируется в прямом эфире сегментирует только через демон (`/xc_fanout_hls/_`), иначе `404`. -6. При выходе: `ShutdownHandler::handle()` → закрыть запись подключения. +6. При выходе: `ShutdownHandler::handle()` → закрыть запись о подключении. ### VOD (vod.php) @@ -193,22 +193,22 @@ Alt-Svc: h3-29, h3-T051, h3-Q050 (HTTP/3 hints) Обслуживает архивные сегменты. Использует `TimeshiftClient` для разрешения архивного файла. -### Демон доставки — `xc_fanout` +### Доставка демона — `xc_fanout` -Оперативная доставка клиентов (TS **и** HLS) доступна ** только для демонов **: PHP авторизует +Live client delivery (TS **and** HLS) is **daemon-only**: PHP authorizes the средство просмотра, а затем полностью покидает байтовый путь, так что средство просмотра больше не закрепляет -PHP-FPM работник, отвечающий за жизнь потока. +PHP-FPM работник, отвечающий за жизнедеятельность потока. -- **Разветвление.** `xc_fanout` (встроенный демон Go) извлекает каждый источник ** один раз** и -он доступен каждому пользователю через сокет unix с помощью встроенного в оперативную память сегментатора HLS. -PHP отсутствует в байтовом пути для каждого зрителя; старый цикл поиска и чтения +- **Fan-out.** `xc_fanout` (a bundled Go daemon) pulls each source **once** and +предоставляет его каждому пользователю через сокет unix с помощью встроенного в оперативную память сегментатора HLS. +PHP не соответствует байтовому пути для каждого зрителя; старый цикл поиска и чтения (`AsyncFileOperations::awaitFileExists()`) и `HLSGenerator::generateHLS()` сервировочные дорожки были удалены при разделке. - **Два сокета.** Клиентский сокет (ориентированный на nginx) обслуживает `/live/` и -`/hls/...`; управляющий сокет, предназначенный только для PHP, регистрирует исходные коды +`/hls/...`; управляющий сокет, предназначенный только для PHP, регистрирует источники (`PUT /streams/` / `/ingest/`), отвечает на вопросы о статусе выхода в эфир (`GET /streams/`, `GET /probe/`) и предоставляет доступ к телеметрии. -- **Телеметрия / сверка.** `fanout_sync` опросов `GET /rates` (для каждого uuid +- **Telemetry / reconciliation.** `fanout_sync` polls `GET /rates` (per-uuid КБИТ/с → `lines_divergence`) и `GET /connections` (согласовывает `lines_live` строк, поскольку PHP не может видеть разъединение в `X-Accel`). - **Отключен.** Если демон сообщает об отсутствии данных (`has_data=false` / устаревшие), PHP @@ -221,10 +221,10 @@ PHP отсутствует в байтовом пути для каждого з Действие администратора "Отправить сообщение" отображает текстовый баннер на видео **одного** зрителя. PHP отправляет его в сокет управления демоном (`FanoutClient::sendSignal` → `POST /signal/`), и демон применяет -ffmpeg `drawtext` наложение на следующий сегмент этого просмотра HLS (или короткий фрагмент ~5 секунд). +ffmpeg `drawtext` наложение на следующий HLS сегмент этого просмотра (или короткий ~5-секундный фрагмент окно), однократный запуск, максимальное усилие - сигнал никогда не прерывает воспроизведение. Демон должен -запускается с помощью ffmpeg, который на самом деле имеет фильтр `drawtext`, так что -`service` launcher выбирает сборку с поддержкой drawtext. +быть запущенным с помощью ffmpeg, который на самом деле имеет фильтр `drawtext`, так что +`service` программа запуска выбирает сборку с поддержкой drawtext. --- @@ -234,11 +234,11 @@ ffmpeg `drawtext` наложение на следующий сегмент эт Управляет текущим состоянием соединения. Серверная часть выбрана с помощью `$rSettings['redis_handler']`: -**Redis (предпочтительно для масштаба):** +**Redis (предпочтительно для масштабирования):** - Соединения, хранящиеся в отсортированных наборах: - `LINE#{identity}` — подключения для пользователя - - `STREAM#{stream_id}` — соединения для потоковой передачи + - `STREAM#{stream_id}` — соединения для потока - `SERVER#{server_id}` — соединения на сервере **MySQL (резервный вариант):** @@ -277,10 +277,10 @@ ConnectionTracker::getCapacity() Файл: `src/Streaming/Lifecycle/ShutdownHandler.php` -Зарегистрирован через `register_shutdown_function()`. На выходе из PHP процесса: +Зарегистрирован с помощью `register_shutdown_function()`. При завершении процесса PHP: -1. Закройте запись о подключении с помощью `lines_live` или Redis. -2. Удалите tmp-файлы по адресу `CONS_TMP_PATH . $uuid`. +1. Закройте запись о соединении в `lines_live` или Redis. +2. Удалите tmp-файлы со значением `CONS_TMP_PATH . $uuid`. 3. Удалите поток по требованию из очереди, если это применимо. --- @@ -301,7 +301,7 @@ public static function checkAccess($rUserInfo, $rUserIP, $rCountryCode, $rUserIS 2. Сортировка по вместимости (по возрастанию) — сначала загружается наименее загруженный. 3. Применить маршрутизацию GeoIP (если `enable_geoip == 1`): - Точное соответствие стране → выберите немедленно. - - `geoip_type == 'strict'` → исключить несоответствие. + - `geoip_type == 'strict'` → исключить несоответствия. - В противном случае → присвоить приоритетный вес. 4. Примените маршрутизацию через интернет-провайдера (если `enable_isp == 1`): та же логика, что и GeoIP. 5. Верните сервер с наименьшей пропускной способностью из группы с наивысшим приоритетом. @@ -361,7 +361,7 @@ public static function generateHLS($rSettings, $rM3U8, $rUsername, $rPassword, 1. Сгенерируйте ключевой токен AES-128 из IP + StreamID + salt. 2. Замените IV содержимым из `STREAMS_PATH . $rStreamID . '_.iv'`. -3. Зашифруйте ссылку на каждый сегмент: `IP/StreamID/Segment/UUID/SERVER_ID/VideoCodec/OnDemand`. +3. Encrypt each segment reference: `IP/StreamID/Segment/UUID/SERVER_ID/VideoCodec/OnDemand`. 4. Замените названия сегментов на `/hls/{encrypted_token}`. Доставка ключей происходит через `key.php` с использованием того же механизма токенов. @@ -379,7 +379,7 @@ public static function generateHLS($rSettings, $rM3U8, $rUsername, $rPassword, |nginx буферизация|128 буферов по 32 КБАЙТ на запрос| |Объединение подключений в пул|Redis (предпочтительно) или постоянный MySQL| |Чтение только из кэша|Настройки и пользовательские данные считываются из файлового кэша без запросов к базе данных| -|Ранний выход|Отслеживает `connection_status()` каждые 5 секунд, чтобы обнаружить отключение клиента| +|Ранний выход|Отслеживает `connection_status()` каждые 5 секунд для обнаружения отключения клиента| |Обновление настроек|Каждые 5 минут (300 секунд) для отслеживания изменений конфигурации без перезапуска| --- @@ -404,10 +404,10 @@ VOD_PATH = /home/xc_vm/www/vod/ Два инструмента проверяют правильность доставки потока — что сегменты поступают по порядку и очередь на доставку не прерывается. -### `tools/stream_queue_check.py` (только для Python, stdlib) +### `tools/stream_queue_check.py` (Только Python, stdlib) Автономный монитор **целостности сегмента/очереди пакетов** с дополнительным ** функцией live -панель управления буфером**. Автоматически определяет HLS против MPEG-TS. +панель управления буфером**. Автоматически определяет HLS по сравнению с MPEG-TS. ```bash python3 tools/stream_queue_check.py "" --duration 30 # batch check @@ -419,15 +419,15 @@ python3 tools/stream_queue_check.py "" --live --duration 0 # live dashboar |Течение|Проверка очереди| | --- | --- | -|HLS (`.m3u8`)|`EXT-X-MEDIA-SEQUENCE` монотонный и непрерывный (без удаленных или перемотанных сегментов), без `EXT-X-DISCONTINUITY`, каждый вновь появляющийся сегмент доступен для загрузки. Основные плейлисты отображаются в их первом варианте.| -|MPEG-TS (`.ts`, `/play//ts`)|per-PID `continuity_counter` (потерянные / дублированные / переупорядоченные пакеты = разрыв очереди), потеря байта синхронизации, индикатор транспортной ошибки и остановка доставки.| +|HLS (`.m3u8`)|`EXT-X-MEDIA-SEQUENCE` монотонный и непрерывный (никаких удаленных или перемотанных сегментов), нет `EXT-X-DISCONTINUITY`, каждый вновь появляющийся сегмент доступен для загрузки. Основные плейлисты отображаются в их первом варианте.| +|MPEG-TS (`.ts`, `/play//ts`)|per-PID `continuity_counter` (потерянные / дублированные / переупорядоченные пакеты = разрыв очереди), потеря байта синхронизации, индикатор транспортной ошибки и задержка доставки.| Основные параметры: |Флаг|Цель| | --- | --- | | `--duration N` |секунды для наблюдения (`0` = до нажатия Ctrl-C в `--live`)| -| `--tolerance N` |разрешить N временных разрывов очереди, прежде чем сообщать о `BROKEN` (игнорирует редкие сбои в источнике, передаваемые `-c copy`)| +| `--tolerance N` |разрешить N временных разрывов очереди, прежде чем сообщать о `BROKEN` (игнорируются редкие сбои источника, переданные `-c copy`)| | `--stall-timeout S` |перерыв в доставке засчитывается как задержка; не превышайте продолжительность сегмента (по умолчанию 15).| | `--live` |цветная приборная панель TUI (внизу)| |`--prebuffer S` / `--buffer-target S`|live: предварительный буфер для виртуального игрока и масштаб буферного графика| @@ -459,8 +459,8 @@ python3 tools/stream_queue_check.py "" --live --duration 0 # live dashboar ### `console.php stream:check` (PHP, компаньон) Проверяет URL-адрес источника и с помощью `--decode` извлекает и декодирует медиафайл для перехвата -неработающие/поврежденные сегменты. HLS проверяется по сегментам; значение TS для одного сокета -конечная точка фиксируется с помощью cURL и декодируется в автономном режиме (ffmpeg в режиме реального времени `-i` зависает +поврежденные сегменты. HLS проверяется посегментно; сообщение об ошибке с одним сокетом +конечная точка фиксируется с помощью cURL и декодируется в автономном режиме (ffmpeg в режиме реального времени `-i` зависает на it). Источник: `src/Cli/Commands/StreamCheckCommand.php`. ```bash @@ -468,10 +468,10 @@ console.php stream:check "" # metadata probe (type, codecs console.php stream:check "" --decode=30 --json ``` -> **Примечание — темп доставки.** Цикл доставки TS в режиме реального времени в `live.php` истощается -> доступные данные без регулирования и приостанавливаются только при достижении ffmpeg's +> **Примечание — темп доставки.** Цикл доставки TS в реальном времени в `live.php` истощает +> доступные данные без регулирования и приостанавливаются только при достижении значения ffmpeg > заголовок записи. Более ранняя версия отключалась на одну секунду после каждого чтения, ограничивая -> пропускная способность на уровне `read_buffer_size` в секунду и голодающие клиенты; +> пропускная способность составляет `read_buffer_size` в секунду, а клиенты голодают; > `stream_queue_check.py --live` визуализирует результирующее поведение буфера. --- diff --git a/docs/ru/guides/authentication-and-sessions.md b/docs/ru/guides/authentication-and-sessions.md index c10bf8b6..469ed6c6 100644 --- a/docs/ru/guides/authentication-and-sessions.md +++ b/docs/ru/guides/authentication-and-sessions.md @@ -47,7 +47,7 @@ POST request with credentials 1. Проверка повторной проверки (если параметр `recaptcha_enable` включен и не пропущен). 2. Поиск учетных данных с помощью `UserRepository::getAuthUserByCredentials()`. 3. Проверка группы кодов доступа - имя пользователя `member_group_id` должно входить в разрешенные группы текущего кода доступа, в противном случае кодов доступа не должно существовать. -4. Проверка прав доступа - `is_admin` должно иметь значение true для группы пользователя. +4. Permission check -- `is_admin` must be true for the user's group. 5. Проверка состояния - `$rUserInfo['status'] == 1` (включено). 6. В случае успеха: повторно хэширует пароль, обновляет `last_login` и `ip` в базе данных, записывает сеансовые ключи, регистрирует логин. @@ -62,11 +62,11 @@ $_SESSION['verify'] = md5($rUserInfo['username'] . '||' . $rCrypt); // Verificat ### `Authenticator::resellerLogin(array $data): array` -Метод входа в систему реселлера. Структура идентична `login()` с некоторыми отличиями: +Метод входа в систему торгового посредника. Структура идентична `login()` с этими отличиями: - Повторная проверка всегда проверяется, когда она включена (параметр обхода отсутствует). - Для проверки разрешений требуется `is_reseller` вместо `is_admin`. -- Возвращает `STATUS_NOT_RESELLER`, если у пользователя нет разрешения реселлера. +- Возвращает `STATUS_NOT_RESELLER`, если у пользователя нет разрешения торгового посредника. - Журналы входа в систему записываются с типом `RESELLER` вместо `ADMIN`. Значения сеанса, записанные в зависимости от успеха: @@ -98,7 +98,7 @@ $_SESSION['rverify'] = md5($rUserInfo['username'] . '||' . $rCrypt); // Verific Authenticator::hashPassword(string $password, ?string $salt = null, int $rounds = 20000): string ``` -Использует `crypt()` с SHA-512 (`$6$`). Формат salt - это `$6$rounds=20000$$`, где `` - это 16 шестнадцатеричных символов, производных от `openssl_random_pseudo_bytes(16)`. Пароли повторно хэшируются при каждом успешном входе в систему, что меняет значение. +Использует `crypt()` с SHA-512 (`$6$`). Формат salt равен `$6$rounds=20000$$`, где `` - это 16 шестнадцатеричных символов, полученных из `openssl_random_pseudo_bytes(16)`. Пароли повторно хэшируются при каждом успешном входе в систему, что приводит к замене значения salt. ```php Authenticator::checkPassword(string $password, string $storedHash): string @@ -120,16 +120,16 @@ Authenticator::checkPassword(string $password, string $storedHash): string 1. **Поиск учетных данных** -- `UserRepository::getUserInfo()` (отличается от `getAuthUserByCredentials`, используемого администратором/реселлером). 2. **Отклонение типа линии** - Линии E2, MAG и Stalker отклоняются с определенными кодами ошибок. -3. **Проверка истечения срока действия** -- `exp_date` должно быть равно null или в будущем. -4. **Проверка с поддержкой администратора ** -- `admin_enabled == 0` возвращает `CLIENT_BANNED`. -5. **Проверка, включенная пользователем ** -- `enabled == 0` возвращает `CLIENT_DISABLED`. -6. **Список разрешенных IP-адресов** -- Если для пользователя задан `allowed_ips`, IP-адрес клиента должен совпадать (разрешен с помощью `gethostbyname`). +3. **Expiration check** -- `exp_date` must be null or in the future. +4. **Admin-enabled check** -- `admin_enabled == 0` returns `CLIENT_BANNED`. +5. **User-enabled check** -- `enabled == 0` returns `CLIENT_DISABLED`. +6. **Список разрешенных IP-адресов** -- Если для пользователя задано значение `allowed_ips`, IP-адрес клиента должен совпадать (определяется с помощью `gethostbyname`). 7. **Ограничение по стране ** - Два режима: - - Для каждого пользователя: если задано значение `forced_country`, а не `ALL`, то страна GeoIP должна совпадать. - - Глобальный: если нет переопределения для каждого пользователя, проверяется глобальный параметр `allow_countries` (если он не содержит `ALL`). -8. **Проверка агента пользователя** - Если для пользователя задано значение `allowed_ua`, пользовательский агент HTTP должен соответствовать. -9. **Проверка провайдера** -- флаг `isp_violate` отклоняет соединение. -10. **Проверка сервера интернет-провайдера** -- Если `isp_is_server` имеет значение true и пользователь не является рестримером, соединение будет отклонено. + - Для каждого пользователя: если задано значение `forced_country`, а не `ALL`, страна GeoIP должна совпадать. + - Глобальный: если нет переопределения для каждого пользователя, устанавливается глобальный параметр `allow_countries` (если только он не содержит `ALL`). +8. **Проверка агента пользователя** -- Если для пользователя задано значение `allowed_ua`, то пользовательский агент HTTP должен соответствовать. +9. **Проверка провайдера** -- флаг _BOS_0 отклоняет соединение. +10. **Проверка сервера интернет-провайдера** -- Если значение `isp_is_server` равно true и пользователь не является рестримером, соединение будет отклонено. Каждый сбой запускает `BruteforceGuard::checkFlood()` перед возвратом кода ошибки. @@ -153,7 +153,7 @@ $_SESSION['phash'] = $rUserInfo['id']; $_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password']); ``` -В контексте player хранятся только два сеансовых ключа. В отличие от ключей администратора и реселлера, здесь нет ключей `activity`, `ip`, или `code`. Это означает, что в сеансе player нет таймаута бездействия и не обнаруживается изменение IP-адреса на уровне сеанса. +В контексте player хранятся только два сеансовых ключа. В отличие от ключей администратора и реселлера, здесь нет ключей `activity`, `ip` или `code`. Это означает, что в сеансе player нет таймаута бездействия и не обнаруживается изменение IP-адреса на уровне сеанса. --- @@ -165,18 +165,18 @@ $_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password' Файл: `src/Public/Views/admin/functions.php` -Если задано значение `$_SESSION['hash']`, при каждой загрузке страницы выполняются следующие проверки: +Если установлено значение `$_SESSION['hash']`, при каждой загрузке страницы выполняются следующие проверки: 1. **Поиск пользователя** -- `UserRepository::getRegisteredUserById($_SESSION['hash'])`. Если пользователь больше не существует, сеанс завершается. -2. **Проверка разрешений** -- `AuthRepository::getPermissions()` должен возвращать допустимый набор с `is_admin == true`. +2. **Permission check** -- `AuthRepository::getPermissions()` must return a valid set with `is_admin == true`. 3. **Проверка IP-адреса** -- Сравнивает текущий IP-адрес с `$_SESSION['ip']`: - - Если включена настройка `ip_subnet_match`: сравниваются только первые три октета (например, `192.168.1.*` совпадает с `192.168.1.*`). - - Если `ip_subnet_match` отключен: требуется точное совпадение IP-адресов. + - Если параметр `ip_subnet_match` включен: сравниваются только первые три октета (например, `192.168.1.*` соответствует `192.168.1.*`). + - Если параметр `ip_subnet_match` отключен: требуется точное совпадение IP-адресов. - Если IP-адрес не совпадает и включена настройка `ip_logout`, сеанс завершается. - Если IP-адрес не совпадает и `ip_logout` отключен, `$_SESSION['ip']` автоматически обновляется до нового IP-адреса. -4. **Проверьте хэш-код ** -- `$_SESSION['verify']` должно быть равно `md5($rUserInfo['username'] . '||' . $rUserInfo['password'])`. Это гарантирует, что сеанс будет аннулирован в случае изменения пароля. +4. **Verify hash check** -- `$_SESSION['verify']` must equal `md5($rUserInfo['username'] . '||' . $rUserInfo['password'])`. This ensures the session is invalidated if the password changes. -Если какая-либо проверка завершается неудачей, сеанс очищается с помощью `SessionManager::clearContext('admin')` и пользователь перенаправляется на индексную страницу. +Если какая-либо проверка завершается неудачей, сеанс очищается с помощью `SessionManager::clearContext('admin')`, и пользователь перенаправляется на индексную страницу. ### Проверка сеанса работы с реселлером @@ -184,7 +184,7 @@ $_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password' Логика идентична проверке администратора, но используются сеансовые ключи реселлера: -- Проверяет `$_SESSION['reseller']` на наличие идентификатора пользователя. +- Проверяет `$_SESSION['reseller']` для идентификатора пользователя. - Использует `$_SESSION['rip']` для сравнения IP-адресов. - Использует `$_SESSION['rverify']` для проверки хэша. - Проверяет разрешение `is_reseller` вместо `is_admin`. @@ -195,11 +195,11 @@ $_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password' Файл: `src/Public/Views/admin/session.php` -Для сеансов администрирования выполняется отдельная проверка тайм-аута сеанса. Если заданы значения `$_SESSION['hash']` и `$_SESSION['last_activity']`, а с момента `last_activity` прошло более 60 минут, ключи сеанса (`hash`, `ip`, `code`, `verify`, `last_activity`) не заданы. При каждом действительном запросе `$_SESSION['last_activity']` обновляется, и сеанс закрывается для записи. +Для сеансов администрирования выполняется отдельная проверка тайм-аута сеанса. Если заданы значения `$_SESSION['hash']` и `$_SESSION['last_activity']`, а с момента `last_activity` прошло более 60 минут, ключи сеанса (`hash`, `ip`, `code`, `verify`, `last_activity`) не заданы. При каждом действительном запросе обновляется `$_SESSION['last_activity']`, и сеанс закрывается для записи. ### Проверка сеанса игрока -Контекст проигрывателя не выполняет проверку IP-адреса, соответствие подсети или тайм-аут активности на уровне сеанса. Сохраняются только `phash` и `pverify`@, и проверка выполняется на прикладном уровне для повторной проверки этих значений по базе данных. +Контекст проигрывателя не выполняет проверку IP-адреса, соответствие подсети или тайм-аут активности на уровне сеанса. Сохраняются только `phash` и `pverify`, и проверка выполняется на прикладном уровне для повторной проверки этих значений по базе данных. --- @@ -211,7 +211,7 @@ $_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password' ### Контекстная ключевая карта -|Логический ключ|Администратор `$_SESSION` ключ|Реселлер `$_SESSION` ключ|Игрок `$_SESSION` ключ| +|Логический ключ|Ключ администратора `$_SESSION`|Ключ реселлера `$_SESSION`|Ключ игрока `$_SESSION`| | --- | --- | --- | --- | | `auth` | `hash` | `reseller` | `phash` | | `activity` | `last_activity` | `rlast_activity` |--| @@ -219,17 +219,17 @@ $_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password' | `code` | `code` | `rcode` |--| | `verify` | `verify` | `rverify` | `pverify` | -Контекст игрока намеренно не содержит сопоставлений `activity`, `ip`, и `code`. +Контекст игрока намеренно опускает сопоставления `activity`, `ip` и `code`. ### Методы **`start(string $context, int $timeout = 60): void`** -Запускает сеанс PHP (если он еще не запущен), устанавливает активный контекст и запускает `checkTimeout()` для завершения устаревших сеансов. Контекст должен быть `'admin'`, `'reseller'`, или `'player'`. +Запускает сеанс PHP (если он еще не запущен), устанавливает активный контекст и запускает `checkTimeout()` для завершения устаревших сеансов. Контекст должен быть `'admin'`, `'reseller'` или `'player'`. **`requireAuth(?string $loginUrl = null): void`** -Проверяет наличие аутентифицированного сеанса. Если запрос направлен напрямую `session.php`, возвращает ответ в формате JSON `{"result": true/false}` (используется для опроса сеанса AJAX). В противном случае перенаправляет не прошедших проверку пользователей на страницу входа в систему. В случае успеха вызывает `touch()`, чтобы обновить временную метку действия. +Проверяет наличие аутентифицированного сеанса. Если запрос направлен напрямую на `session.php`, возвращает ответ в формате JSON `{"result": true/false}` (используется для опроса сеанса AJAX). В противном случае перенаправляет не прошедших проверку пользователей на страницу входа в систему. В случае успеха вызывает `touch()` для обновления временной метки действия. **`isAuthenticated(): bool`** @@ -237,11 +237,11 @@ $_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password' **`getUser(): ?string`** -Возвращает значение, хранящееся в ключе сеанса `auth` (идентификатор пользователя для администратора/реселлера или идентификатор строки для игрока), или `null`, если не пройдена проверка подлинности. +Возвращает значение, сохраненное в ключе сеанса `auth` (идентификатор пользователя для администратора/реселлера или идентификатор строки для игрока), или `null`, если аутентификация не пройдена. **`getValue(string $name): mixed`** -Возвращает значение сеанса по его логическому имени (`auth`, `activity`, `ip`, `code`, `verify`). Логическое имя сопоставляется с фактическим ключом `$_SESSION` в зависимости от текущего контекста. +Возвращает значение сеанса по его логическому имени (`auth`, `activity`, `ip`, `code`, `verify`). Логическое имя сопоставляется с фактическим ключом `$_SESSION` на основе текущего контекста. **`setValue(string $name, mixed $value): void`** @@ -265,13 +265,13 @@ $_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password' **`getContext(): ?string`** -Возвращает текущую строку контекста (`'admin'`, `'reseller'`, `'player'`) или `null`, если значение не задано. +Возвращает текущую контекстную строку (`'admin'`, `'reseller'`, `'player'`) или `null`, если она не задана. ### Поведение по истечении времени ожидания -`SessionManager::DEFAULT_TIMEOUT` равно 60 минутам. Метод `checkTimeout()` (автоматически вызываемый `start()`) сравнивает время, прошедшее с момента `last_activity`. Если тайм-аут превышен, все ключи сеанса, зависящие от контекста, сбрасываются, что фактически приводит к выходу пользователя из системы. +`SessionManager::DEFAULT_TIMEOUT` равно 60 минутам. Метод `checkTimeout()` (вызываемый автоматически `start()`) сравнивает время, прошедшее с момента `last_activity`. Если время ожидания превышено, все ключи сеанса, зависящие от контекста, сбрасываются, что приводит к выходу пользователя из системы. -Поскольку контекст игрока не содержит ключа `activity` в карте ключей, проверка тайм-аута не применяется к сессиям игрока. +Поскольку контекст игрока не имеет ключа `activity` в карте ключей, проверка тайм-аута не применяется к сеансам игрока. --- @@ -279,38 +279,38 @@ $_SESSION['pverify'] = md5($rUserInfo['username'] . '||' . $rUserInfo['password' Файл: `src/Core/Auth/BruteforceGuard.php` -Централизованное ограничение скорости и защита от перебора. Все методы используют файловое состояние, хранящееся по адресу `FLOOD_TMP_PATH` (`/home/xc_vm/tmp/flood/`). Разрешенные IP-адреса (IP-адреса сервера) и IP-адреса, указанные в параметре `flood_ips_exclude`, всегда исключаются. +Централизованное ограничение скорости и защита от перебора. Все методы используют файловое состояние, хранящееся в `FLOOD_TMP_PATH` (`/home/xc_vm/tmp/flood/`). Разрешенные IP-адреса (IP-адреса сервера) и IP-адреса, указанные в параметре `flood_ips_exclude`, всегда исключаются. ### `checkFlood(?string $ip = null, bool $useCachedMode = false): null` Скорость - ограничивает количество запросов по IP-адресу в пределах настраиваемого временного интервала. - **Настройки:** `flood_limit` (максимальное количество запросов), `flood_seconds` (размер окна). -- **Файл состояния:** `FLOOD_TMP_PATH . $ip` - хранит объект JSON с `requests` count и `last_request` timestamp. -- **Поведение:** Отслеживает количество запросов в пределах временного интервала. Если количество превышает `flood_limit`, IP-адрес блокируется (заносится в таблицу `blocked_ips` или передается через Redis в режиме кэширования/потоковой передачи). Файл состояния удаляется после блокировки. +- **Файл состояния:** `FLOOD_TMP_PATH . $ip` - хранит объект JSON с числом `requests` и временной меткой `last_request`. +- **Поведение:** Отслеживает количество запросов в пределах временного окна. Если количество превышает `flood_limit`, IP-адрес блокируется (заносится в таблицу `blocked_ips` или передается через Redis в кэшированном/потоковом режиме). Файл состояния удаляется после блокировки. - ** Используется: ** Логином игрока (вызывается при каждой неудачной попытке входа в систему), конечными точками потоковой передачи. ### `checkBruteforce(?string $ip = null, ?string $mac = null, ?string $username = null, bool $useCachedMode = false): null` Обнаруживает атаки методом перебора на основе количества уникальных MAC-адресов или имен пользователей, обнаруженных с одного IP-адреса. -- **Настройки:** `bruteforce_mac_attempts`, `bruteforce_username_attempts` (максимальное количество уникальных значений), `bruteforce_frequency` (временной интервал в секундах). -- **Файл состояния:** `FLOOD_TMP_PATH . $ip . '_mac'` или `FLOOD_TMP_PATH . $ip . '_user'` - сохраняет попытки в виде пар `{term: timestamp}`. -- ** Поведение:** Попытки с истекшим сроком действия (за пределами частотного диапазона) отсекаются с помощью `truncateAttempts()`. Если количество уникальных запросов превышает допустимое, IP-адрес блокируется. +- **Настройки:** `bruteforce_mac_attempts`, `bruteforce_username_attempts` ( максимальное количество уникальных значений), `bruteforce_frequency` (временной интервал в секундах). +- **State file:** `FLOOD_TMP_PATH . $ip . '_mac'` or `FLOOD_TMP_PATH . $ip . '_user'` -- stores attempts as `{term: timestamp}` pairs. +- **Поведение:** Попытки с истекшим сроком действия (за пределами частотного диапазона) отсекаются с помощью `truncateAttempts()`. Если количество уникальных запросов превышает допустимое, IP-адрес блокируется. - **Используется:** Конечными точками потоковой аутентификации. ### `checkAuthFlood(array $user, ?string $ip = null): null` Ограничивает скорость запросов на аутентификацию для конкретной комбинации пользователь +IP. Предназначен для ограничения повторных попыток авторизации без полной блокировки. -- ** Настройки:** `auth_flood_limit` (максимальное количество попыток), `auth_flood_seconds` (окно), `auth_flood_sleep` (задержка в секундах при блокировке). -- **Файл состояния:** `FLOOD_TMP_PATH . $userId . '_' . $ip` - сохраняет попытки в виде индексированных временных меток плюс необязательную временную метку `block_until`. -- ** Поведение:** Когда количество попыток превышает допустимое значение, устанавливается временная метка `block_until`. Последующие запросы в течение периода блокировки задерживаются на `auth_flood_sleep` секунд (через `sleep()`). IP-адрес не блокируется навсегда. Пользователи Restreamer (`is_restreamer`) освобождаются от ответственности. +- **Настройки:** `auth_flood_limit` (максимальное количество попыток), `auth_flood_seconds` (окно), `auth_flood_sleep` (задержка в секундах при блокировке). +- **State file:** `FLOOD_TMP_PATH . $userId . '_' . $ip` -- stores attempts as indexed timestamps plus an optional `block_until` timestamp. +- **Поведение:** Когда количество попыток превышает допустимое значение, устанавливается временная метка `block_until`. Последующие запросы в течение периода блокировки задерживаются на `auth_flood_sleep` секунды (через `sleep()`). IP-адрес не блокируется навсегда. Пользователи Restreamer (`is_restreamer`) освобождаются от этого требования. - **Используется:** Потоковой аутентификацией. ### `truncateAttempts(array $attempts, int $frequency, bool $list = false): array` -Отфильтровывает просроченные попытки из массива отслеживания. Когда `$list` равно `true`, массив обрабатывается как индексированный (для `checkAuthFlood`); в противном случае как ассоциативный, определяемый по термину (для `checkBruteforce`). +Отфильтровывает просроченные попытки из массива отслеживания. Если значение `$list` равно `true`, массив обрабатывается как индексированный (для `checkAuthFlood`); в противном случае как ассоциативный с ключом по термину (для `checkBruteforce`). ### Блокирующий механизм @@ -344,7 +344,7 @@ md5($username . '||' . $hashedPassword) Это значение сравнивается с текущими значениями базы данных при каждой загрузке страницы. Если администратор изменяет пароль пользователя (что изменяет сохраненный хэш), все существующие сеансы для этого пользователя автоматически становятся недействительными, поскольку проверяемый хэш больше не будет совпадать. -Для входа в систему администратора и торгового посредника пароль повторно хэшируется при входе в систему, поэтому используется `$rCrypt` (новый хэш). Для входа в систему игроков используется существующий сохраненный хэш `$rUserInfo['password']`. +Для логинов администратора и торгового посредника пароль повторно хэшируется во время входа в систему, поэтому используется `$rCrypt` (новый хэш). Для логинов игроков используется существующий сохраненный хэш `$rUserInfo['password']`. ### Обработка изменений IP-адреса @@ -352,10 +352,10 @@ md5($username . '||' . $hashedPassword) |Установка|Эффект| | --- | --- | -| `ip_logout` |Когда этот параметр включен, сеанс завершается при изменении IP-адреса клиента (точного или из подсети, в зависимости от `ip_subnet_match`).| +| `ip_logout` |Если включено, завершает сеанс при изменении IP-адреса клиента (точного или из подсети, в зависимости от `ip_subnet_match`).| | `ip_subnet_match` |Когда этот параметр включен, сравниваются только первые три октета IP-адреса, вместо того чтобы требовать точного совпадения. Позволяет пользователям с динамическими IP-адресами в пределах одной подсети сохранять сеанс связи.| -Когда `ip_logout` отключен и IP-адрес изменяется, сохраненный IP-адрес сеанса автоматически обновляется до нового IP-адреса. +Когда `ip_logout` отключено и IP-адрес изменяется, сохраненный IP-адрес сеанса автоматически обновляется до нового IP-адреса. --- @@ -377,7 +377,7 @@ VALUES($type, $codeId, $userId, $status, $ip, $timestamp); | `login_ip` |IP-адрес клиента| | `date` |Временная метка Unix| -Логины игроков не записываются на `login_logs`. +Логины игроков не записываются в `login_logs`. --- @@ -391,7 +391,7 @@ VALUES($type, $codeId, $userId, $status, $ip, $timestamp); Авторизация на уровне объекта. Проверяет, есть ли у текущего пользователя разрешение на доступ к определенному ресурсу (пользователь, поток и т.д.) на основе иерархии владельцев реселлеров и групповых разрешений. -- `Authorization::hasResellerPermissions($type)` -- устанавливает флажок единственного разрешения для `$rPermissions`. +- `Authorization::hasResellerPermissions($type)` -- устанавливает флажок единственного разрешения на `$rPermissions`. - `Authorization::check($type, $id)` -- проверяет доступ к определенному ресурсу по типу и идентификатору. ### `PageAuthorization` diff --git a/docs/ru/guides/cli-tools.md b/docs/ru/guides/cli-tools.md index babd1ec4..81730316 100644 --- a/docs/ru/guides/cli-tools.md +++ b/docs/ru/guides/cli-tools.md @@ -1,6 +1,6 @@ # Инструменты CLI и обновления баз данных -Справочник по интерфейсу командной строки XC_VM, системным инструментам и процессу обновления базы данных после обновления версии. Охватывает ежедневные операции, экстренный доступ и создание новых этапов обновления базы данных. +Справочник по интерфейсу командной строки XC_VM, системным инструментам и процессу обновления базы данных после обновления версии. Содержит описание ежедневных операций, экстренного доступа и создания новых этапов обновления базы данных. --- @@ -18,7 +18,7 @@ | --- | --- | --- | |**Команды**|28|Одноразовые операции (обновление, статус, инструменты и т.д.)| |**Закадычные друзья**|25|Запланированные задачи (автоматически вызываемые crontab)| -|**Демоны**|8|Длительно выполняющиеся фоновые процессы (команды с использованием `DaemonTrait`)| +|**Демоны**|8|Длительно выполняющиеся фоновые процессы (команды, использующие `DaemonTrait`)| > **Примечание:** Демоны - это обычные команды, которые используют `DaemonTrait`. Отдельного каталога `Daemons/` не существует. @@ -47,10 +47,10 @@ | `thumbnail` | `ThumbnailCommand` |Создание рамок миниатюр для потока|xc_vm| | `plex_item` | `PlexItemCommand` |Обработать один элемент Plex (фильм/сериал)|xc_vm| | `watch_item` | `WatchItemCommand` |Обработать один элемент наблюдения (поиск/обновление в базе данных TMDB)|xc_vm| -| `migrate` | `MigrateCommand` |Передача данных из базы данных `xc_vm_migrate`|xc_vm| +| `migrate` | `MigrateCommand` |Перенос данных из базы данных `xc_vm_migrate`|xc_vm| | `db:migrate` | `DbMigrateCommand` |Применить ожидающие переноса базы данных из каталога `migrations/`|xc_vm| | `server:install` | `ServerInstallCommand` |Установка/настройка сервера (Proxy/LB) через SSH|корень| -| `server:diagnose` | `ServerDiagnoseCommand` |Диагностируйте, почему прокси-узел/LB-узел не подключен к основному (сердцебиение, доступность, iptables, сервис).|корень| +| `server:diagnose` | `ServerDiagnoseCommand` |Диагностировать, почему прокси-узел/LB-узел не подключен к главному (частота сердечных сокращений, доступность, iptables, сервис)|корень| > Команды, помеченные **необязательно**, условно регистрируются с помощью `file_exists()` guard: `cache_handler`, `server:install`, `migrate`. @@ -74,14 +74,14 @@ | `archive` | `ArchiveCommand` |Телевизионный архив — запись потока на сегменты| | `created` | `CreatedCommand` |Созданный канал — создание канала из исходных текстов| | `delay` | `DelayCommand` |Задержка HLS воспроизведения потока| -| `loopback` | `LoopbackCommand` |Получать MPEG-TS с другого сервера| +| `loopback` | `LoopbackCommand` |Получить MPEG-TS с другого сервера| | `llod` | `LlodCommand` |Потоковый процессор с низкой задержкой по требованию| | `record` | `RecordCommand` |Запись потока в формате MP4| | `ondemand` | `OndemandCommand` |Прерывать трансляции без активных зрителей| ### Задания Cron (всего 26: 22 ядра + 4 модуля) -Все имена заданий cron имеют префикс `cron:`. Они используют `CronTrait` и вызываются системной crontab. +Все имена заданий cron имеют префикс `cron:`. Для них используется `CronTrait`, и они вызываются системой crontab. **Основные задания cron** (в `src/Cli/CronJobs/`): @@ -96,7 +96,7 @@ | `cron:epg` | `EpgCronJob` |EPG загрузка и обработка (необязательно)| | `cron:errors` | `ErrorsCronJob` |Журналы ошибок процесса| | `cron:lines_logs` | `LinesLogsCronJob` |Импорт журналов клиентских запросов в базу данных| -| `cron:maxmind` | `MaxMindCronJob` |Обновление баз данных MaxMind GeoIP (только по вторникам; `--force` запускать вручную)| +| `cron:maxmind` | `MaxMindCronJob` |Обновление баз данных MaxMind GeoIP (только по вторникам; `--force` для запуска вручную)| | `cron:providers` | `ProvidersCronJob` |Поставщики обновлений (необязательно)| | `cron:root_mysql` | `RootMysqlCronJob` |Обслуживание базы данных (root, необязательно)| | `cron:root_signals` | `RootSignalsCronJob` |Сигналы обработки, iptables, nginx, управление службами (root)| @@ -107,10 +107,10 @@ | `cron:streams_logs` | `StreamsLogsCronJob` |Импорт журналов потоков| | `cron:tmp` | `TmpCronJob` |Очистка временных файлов| | `cron:update` | `UpdateCronJob` |Проверять и применять обновления (необязательно)| -| `cron:users` | `UsersCronJob` |Управление подключениями пользователей, Redis синхронизация, расхождение| -| `cron:vod` | `VodCronJob` |Обрабатывать VOD содержимое| +| `cron:users` | `UsersCronJob` |Управление подключениями пользователей, синхронизацией Redis, расхождением| +| `cron:vod` | `VodCronJob` |Содержание процесса VOD| -**Модуль cron заданий ** (зарегистрирован через `ModuleInterface::registerCommands()`): +**Модуль cron заданий** (зарегистрирован через `ModuleInterface::registerCommands()`): |Команда|Класс|Модуль|Описание| | --- | --- | --- | --- | @@ -119,13 +119,13 @@ | `cron:tmdb_popular` | `TmdbPopularCronJob` |тмдб|Выборка популярного содержимого TMDB (необязательно)| | `cron:watch` | `WatchCronJob` |часы|Обрабатывать обновления библиотеки отслеживания| -> Необязательные задания cron (зарегистрированные условно): `cron:backups`, `cron:cache_engine`, `cron:epg`, `cron:providers`, `cron:root_mysql`, `cron:series`, `cron:tmdb`, `cron:tmdb_popular`, `cron:update`. +> Дополнительные задания cron (условно зарегистрированные): `cron:backups`, `cron:cache_engine`, `cron:epg`, `cron:providers`, `cron:root_mysql`, `cron:series`, `cron:tmdb`, `cron:tmdb_popular`, `cron:update`. --- ## Регистрация новой команды -Все команды CLI реализуют `CommandInterface`. Основные команды автоматически обнаруживаются из `src/Cli/` через отражение в `console.php`. Команды модуля регистрируются через `ModuleLoader::registerAllCommands()`. +Все команды CLI реализуют `CommandInterface`. Основные команды автоматически обнаруживаются из `src/Cli/` с помощью отражения в `console.php`. Команды модуля регистрируются с помощью `ModuleLoader::registerAllCommands()`. ### Командный интерфейс @@ -139,7 +139,7 @@ interface CommandInterface { ### Шаг 1. Создайте класс -Создайте новый файл в `src/Cli/Commands/` (или `src/Cli/CronJobs/` для заданий cron).: +Создайте новый файл в `src/Cli/Commands/` (или `src/Cli/CronJobs/` для заданий cron): ```php 💡 В хранилище исходных текстов это `src/migrations/`. +> 💡 В хранилище исходных текстов это значение равно `src/migrations/`. ### Шаг 4. Подтвердите обновление базы данных @@ -432,7 +432,7 @@ Migrations ``` -Если инструкция не выполняется, шаг все равно будет записан, но покажет `[WARN]` — просмотрите SQL и устраните любые проблемы. +Если инструкция не выполняется, шаг все равно будет записан, но покажет `[WARN]` — проверьте SQL и устраните любые проблемы. --- @@ -444,7 +444,7 @@ Migrations sudo /home/xc_vm/console.php status ``` -Проверяет, запущен ли XC_VM, подключается к базе данных, выполняет ожидающие обновления шаги, исправляет разрешения и проверяет конфигурацию nginx. Требуется после установки или восстановления. +Проверяет, запущен ли параметр XC_VM, подключается к базе данных, выполняет ожидающие обновления шаги, исправляет разрешения и проверяет конфигурацию nginx. Требуется после установки или восстановления. С аргументом `first-run` пропускает текущую проверку, используемую для начальной настройки: @@ -484,7 +484,7 @@ sudo /home/xc_vm/console.php server:diagnose sudo /home/xc_vm/console.php server:diagnose ``` -Выясняет, почему прокси—узел/LB—узел отображается в автономном режиме на панели: проверяет частоту сердечных сокращений, доступность (ICMP/TCP/HTTP `/api`), перекос часов, очередь сигналов и - локально на узле - выполняет ли узел брандмауэр на главном IP-адресе в своем собственные iptables, запущена ли служба `xc_vm`/nginx, запущен ли демон `watchdog` heartbeat и находится ли `cron:servers` в `xc_vm` crontab. Доступен только для чтения; код выхода `0` = проблем не обнаружено, `2` = указаны вероятные причины. Подробности смотрите в [Руководстве по диагностике сервера](../administration/server-diagnostics.md). +Выясняет, почему прокси—узел/LB—узел отображается в автономном режиме на панели: проверяет частоту сердечных сокращений, доступность (ICMP/TCP/HTTP `/api`), перекос часов, очередь сигналов и - локально на узле - выполняет ли узел брандмауэр на главном IP-адресе в своем собственном iptables, запущена ли служба `xc_vm`/nginx, запущен ли демон heartbeat `watchdog` и находится ли `cron:servers` в crontab `xc_vm`. Доступно только для чтения; код выхода `0` = проблем не обнаружено, `2` = указаны вероятные причины. Более подробную информацию смотрите в [Руководстве по диагностике сервера](../administration/server-diagnostics.md). ### SSL-сертификат @@ -498,7 +498,7 @@ sudo /home/xc_vm/console.php certbot su - xc_vm -c '/home/xc_vm/console.php db:migrate' ``` -Применяет все ожидающие обновления файлы `.sql` из `/home/xc_vm/migrations/`. Используйте это, когда вам нужно выполнить миграцию без полного обновления системы. +Применяет все ожидающие `.sql` файлы из `/home/xc_vm/migrations/`. Используйте это, когда вам нужно выполнить миграцию без полного обновления системы. ### Обновление базы данных данными из других систем diff --git a/docs/ru/guides/dev-workflow.md b/docs/ru/guides/dev-workflow.md index 7e8082d0..208177f7 100644 --- a/docs/ru/guides/dev-workflow.md +++ b/docs/ru/guides/dev-workflow.md @@ -6,7 +6,7 @@ ## Локальная настройка -Зафиксированный `src/vendor/` предназначен ** только для производства **, поэтому инструменты разработки (PHPStan, +Зафиксированное значение `src/vendor/` предназначено только для производства, поэтому инструменты разработки (PHPStan, PHP-CS-Fixer) отсутствуют в дереве. Установите их один раз из зафиксированной блокировки: ```bash @@ -15,7 +15,7 @@ make dev-tools # = cd src && composer install Это добавит пакеты `require-dev` в `src/vendor/`. **Никогда не фиксируйте их** — зарегистрированный поставщик должен оставаться доступным только для производства (`composer install --no-dev`). -`.gitignore` не допускает попадания пакетов разработчиков в `git add` и CI-шлюз +`.gitignore` не допускает попадания пакетов разработчика в `git add`, а CI-шлюз (`check-vendor-prod-only`) завершается сбоем сборки, если она когда-либо была зафиксирована. ## Проверка качества @@ -30,13 +30,13 @@ make dev-tools # = cd src && composer install | `make gates` |PSR-4 регрессионные параметры (ниже)| | `php tools/.bin/phpunit.phar -c tests/phpunit.xml.dist` |Модульные тесты| -@Для @0@@ и `make cs` нужны инструменты разработчика — сначала запустите `make dev-tools`. +для `make phpstan` и `make cs` нужны инструменты разработчика — сначала запустите `make dev-tools`. `make gates` связывает трех охранников: -- **проверка-процедурное использование ** - процедурные файлы / файлы просмотра импортируют каждый перенесенный класс, который они используют (PHP импортируется позиционно, поэтому `use` должен предшествовать использованию); +- **проверка-процедурное использование ** - процедурные файлы / файлы просмотра импортируют каждый перенесенный класс, который они используют (PHP импорт является позиционным, поэтому `use` должно предшествовать использованию); - **verify-lb-archive** — сборка балансировщика нагрузки исключает привилегированный код (контроллеры администратора/реселлера, домен пользователя/устройства, команды установки/root).; -- **только для проверки поставщика** - ни один пакет `require-dev` не зафиксирован в соответствии с `src/vendor/`. +- **проверка-только для поставщика-продукта** - ни один пакет `require-dev` не зафиксирован в соответствии с `src/vendor/`. ## Развертывание кода в VDS через SFTP @@ -109,24 +109,24 @@ make dev-tools # = cd src && composer install ### Основные настройки -- **`context: "./src/"`** — сопоставляет местное `src/` с удаленным `/home/xc_vm/` -- **`context: "./tests/"`** — сопоставляет местное `tests/` с удаленным `/home/xc_vm/tests/` +- **`context: "./src/"`** — сопоставляет локальное `src/` с удаленным `/home/xc_vm/` +- **`context: "./tests/"`** — сопоставляет локальное `tests/` с удаленным `/home/xc_vm/tests/` - **`uploadOnSave: true`** — каждое сочетание клавиш Ctrl+S мгновенно переносит файл в VDS - **`ignore`** — защищает файлы, зависящие от сервера. (`bin/`, `config/`, `tmp/`) -> ** Безопасность:** Используйте SSH-ключи вместо пароля. Каталог `.vscode/` находится в `.gitignore`, поэтому учетные данные не попадут в git. +> ** Безопасность:** Используйте SSH-ключи вместо пароля. Каталог `.vscode/` находится в каталоге `.gitignore`, поэтому учетные данные не попадут в git. ### Как синхронизировать папку с тестами 1. Добавьте вторую запись SFTP с `context: "./tests/"` и `remotePath: "/home/xc_vm/tests"`. 2. Сохраняйте файлы под `tests/` локально. 3. Расширение загрузит их отдельно из `src/` в `/home/xc_vm/tests`. -4. Это необходимо, поскольку тесты хранятся за пределами `src/` и не будут загружены основной записью. +4. Это необходимо, поскольку тесты хранятся за пределами `src/` и не будут загружены из основной записи. ### Рабочий процесс 1. Откройте проект в VS Code -2. Отредактируйте любой файл под `src/` +2. Отредактируйте любой файл в разделе `src/` 3. Если вы добавляете тест, отредактируйте файл в разделе `tests/` 4. Сохранить — соответствующая запись SFTP загружает файл в VDS 5. Запустите соответствующий тест на VDS diff --git a/docs/ru/guides/diagnostics-api.md b/docs/ru/guides/diagnostics-api.md index 6f597297..6b6c59b8 100644 --- a/docs/ru/guides/diagnostics-api.md +++ b/docs/ru/guides/diagnostics-api.md @@ -4,7 +4,7 @@ - Проверка SSL-сертификата - проверка совместимости кодеков -- экспорт/отправка журнала панели +- экспорт/отправка журнала панели управления - обнаружение IP-адреса внешнего API --- @@ -15,8 +15,8 @@ Возвращает сведения о сертификате (`serial`, `expiration`, `subject`, `path`). -Если `$certificate` равно null, путь к сертификату определяется автоматически из конфигурации nginx SSL. -Возвращает `null`, если файл отсутствует или при анализе OpenSSL произошел сбой. +Если значение `$certificate` равно null, путь к сертификату автоматически определяется из конфигурации SSL nginx. +Возвращает `null`, если файл отсутствует или не удалось выполнить синтаксический анализ OpenSSL. --- @@ -30,13 +30,13 @@ - видео: `h264`, `vp8`, `vp9`, `ogg`, `av1` - аудио: `aac`, `libfdk_aac`, `opus`, `vorbis`, `pcm_s16le`, `mp2`, `mp3`, `flac` -С `$allowHEVC=true`, позволяет `hevc`/`h265` и `ac3`. +При `$allowHEVC=true` допускаются значения `hevc`/`h265` и `ac3`. --- ### `downloadPanelLogs(object $db): array` -Извлекает до 1000 последних строк из `panel_logs` (исключая `epg`), очищает поля вывода и строки журнала после экспорта. +Извлекает до 1000 последних строк из `panel_logs` (исключая `epg`), обрабатывает поля вывода и очищает строки журнала после экспорта. Возвращаемая форма: @@ -47,7 +47,7 @@ ] ``` -Выдает `Exception` при сбое запроса к базе данных. +Выдает ошибку `Exception` при сбое запроса к базе данных. --- @@ -73,7 +73,7 @@ ### `getPIDs(int $rServerID): array` -Анализирует информацию о системном процессе из ответа API сервера. Возвращает структурированные данные процесса для мониторинга. +Анализирует информацию о системном процессе из ответа API сервера. Возвращает структурированные данные о процессе для мониторинга. --- diff --git a/docs/ru/guides/error-handling.md b/docs/ru/guides/error-handling.md index f10770f3..f88f7b27 100644 --- a/docs/ru/guides/error-handling.md +++ b/docs/ru/guides/error-handling.md @@ -115,7 +115,7 @@ Application code | `VOD_DOESNT_EXIST` |VOD файл не существует.| | `WAIT_TIME_EXPIRED` |Время начала трансляции истекло, запустить не удалось.| -Коды, относящиеся к потоковой передаче данных (`CACHE_INCOMPLETE`, `SUBTITLE_DOESNT_EXIST`, `NO_SERVERS_AVAILABLE`, `PROXY_ACCESS_DENIED`) были перенесены из `stream/init.php` в централизованный реестр. +Коды, относящиеся к потоковой передаче данных (`CACHE_INCOMPLETE`, `SUBTITLE_DOESNT_EXIST`, `NO_SERVERS_AVAILABLE`, `PROXY_ACCESS_DENIED`), были перенесены из `stream/init.php` в централизованный реестр. --- @@ -125,7 +125,7 @@ Application code ### `generateError(string $rError, bool $rKill = true, ?int $rCode = null)` -Выдает ответ об ошибке HTTP. Поведение зависит от параметра `debug_show_errors`.: +Выдает ответ об ошибке HTTP. Поведение зависит от параметра `debug_show_errors`: ```text if debug_show_errors === true @@ -156,7 +156,7 @@ generateError('STREAM_OFFLINE', false); // production: no output, no ### `generate404(bool $rKill = true)` -Возвращает страницу `404 Not Found` в стиле nginx и устанавливает HTTP 404. HTML-код содержит комментарии с дополнениями, чтобы скрыть страницы ошибок, отображаемые в браузере MSIE и Chrome. +Возвращает страницу в стиле nginx `404 Not Found` и устанавливает HTTP 404. HTML-код содержит комментарии с дополнениями для подавления страниц с ошибками, удобными для браузера, в MSIE и Chrome. ```php generate404(); // 404 + exit @@ -167,7 +167,7 @@ generate404(false); // 404, continue execution ## Подсистема регистратора -Определен в `src/Core/Logging/Logger.php`. Класс `final`, который регистрирует три глобальных обработчика PHP, чтобы фиксировать все ошибки во время выполнения и записывать их в файл. +Определен в `src/Core/Logging/Logger.php`. Класс `final`, который регистрирует три глобальных обработчика PHP для отслеживания всех ошибок во время выполнения и записи их в файл. ### Инициализация @@ -189,14 +189,14 @@ Logger::init(bool $showErrors, string $logFile): void |Путь входа|Файл|Как| | --- | --- | --- | -|Bootstrap (все контексты)| `src/bootstrap.php` |`XC_Bootstrap::loadConstants()` звонков `Logger::init(PHP_ERRORS, LOGS_TMP_PATH . 'error_log.log')`| +|Bootstrap (все контексты)| `src/bootstrap.php` |`XC_Bootstrap::loadConstants()` вызовы `Logger::init(PHP_ERRORS, LOGS_TMP_PATH . 'error_log.log')`| |Конечные точки потоковой передачи| `src/Core/Http/RequestGuard.php` |Загружает настройки из файлового кэша, определяет `PHP_ERRORS`, затем вызывает `Logger::init(PHP_ERRORS, LOGS_TMP_PATH . 'error_log.log')`| -В обоих случаях `PHP_ERRORS` отражает настройку `debug_show_errors` (по умолчанию используется `false`, когда настройки недоступны). +В обоих случаях параметр `PHP_ERRORS` соответствует параметру `debug_show_errors` (по умолчанию используется значение `false`, когда настройки недоступны). ### Отображение уровня ошибок -`Logger::handleError()` сопоставляет PHP константы ошибок со строками уровня журнала через `mapErrorLevel()`: +`Logger::handleError()` сопоставляет PHP константы ошибок со строками уровня журнала с помощью `mapErrorLevel()`: |PHP константа(ы)|Уровень регистрации| | --- | --- | @@ -217,7 +217,7 @@ Logger::init(bool $showErrors, string $logFile): void ### Формат журнала -Каждая запись в журнале записывается в виде одной строки: `base64_encode(json_encode($data))`, за которой следует новая строка. Это предотвращает искажение строк в многострочных сообщениях. +Each log entry is written as a single line: `base64_encode(json_encode($data))` followed by a newline. This prevents line corruption from multi-line messages. Декодированная структура JSON: @@ -241,13 +241,13 @@ Logger::init(bool $showErrors, string $logFile): void | `line` |Номер строки, в которой произошла ошибка| | `log_extra` |Трассировка стека (форматированная строка). Пусто для неустранимых ошибок.| | `time` |Временная метка Unix| -| `env` |PHP имя SAPI (`cli`, `fpm-fcgi`, и т.д.)| +| `env` |PHP Имя SAPI (`cli`, `fpm-fcgi` и т.д.)| ### Расположение файла журнала Путь по умолчанию: `LOGS_TMP_PATH . 'error_log.log'` -Если каталог журнала не существует, Logger создает его с правами доступа `0775`. При запуске от имени root (распространенного в контейнерах) файлу присваивается имя `xc_vm:xc_vm` с режимом `0664`. +Если каталог журнала не существует, Logger создает его с правами доступа `0775`. При запуске от имени root (распространенного в контейнерах) файлу присваивается значение `xc_vm:xc_vm` с режимом `0664`. ### Вывод на экран @@ -260,12 +260,12 @@ Logger::init(bool $showErrors, string $logFile): void ## Конвейер ведения журнала: Передача файла в базу данных -Регистратор записывает данные в `error_log.log` на диске. Отдельная подсистема считывает этот файл и сохраняет записи в таблице базы данных `panel_logs`: +Программа ведения журнала записывает данные в файл `error_log.log` на диске. Отдельная подсистема считывает этот файл и сохраняет записи в таблице базы данных `panel_logs`: 1. **Регистратор** записывает строки JSON в кодировке base64 в `error_log.log` -2. **FileLogger** (`src/Core/Logging/FileLogger.php`) предоставляет дополнительный интерфейс ведения журнала, используемый кодом приложения (ошибки PDO, EPG и т.д.), который записывает данные в тот же файл в том же формате +2. **FileLogger** (`src/Core/Logging/FileLogger.php`) предоставляет дополнительный интерфейс ведения журнала, используемый кодом приложения (ошибки PDO, ошибки EPG и т.д.), который записывает данные в тот же файл в том же формате 3. Записи заносятся в таблицу `panel_logs` -4. **Диагностический сервис** (`src/Core/Diagnostics/DiagnosticsService.php`) считывает данные из `panel_logs` для: +4. **DiagnosticsService** (`src/Core/Diagnostics/DiagnosticsService.php`) считывает данные из `panel_logs` для: - `downloadPanelLogs()` -- извлекает до 1000 последних ошибок, не связанных с EPG, затем обрезает таблицу - `submitPanelLogs()` -- отправляет логи на центральный сервер API для анализа 5. Панель администратора отображает эти журналы в разделе **Управление > Журналы > Ошибки панели** @@ -275,7 +275,7 @@ Logger::init(bool $showErrors, string $logFile): void `FileLogger::log()` пропускает записи, которые соответствуют: - Сообщения, содержащие `panel_logs` в дополнительном поле (предотвращает рекурсивное ведение журнала) -- Совпадающие сообщения `timeout exceeded`, `lock wait timeout`, или `duplicate entry` (ошибки с помехами MySQL) +- Сообщения, соответствующие `timeout exceeded`, `lock wait timeout` или `duplicate entry` (зашумленные ошибки MySQL) --- @@ -285,10 +285,10 @@ Logger::init(bool $showErrors, string $logFile): void |Класс|Файл|Цель| | --- | --- | --- | -| `Logger` | `Logger.php` |Глобальный PHP обработчик ошибок/исключений/фатальных исходов (описанный выше)| +| `Logger` | `Logger.php` |Обработчик глобальной PHP ошибки/исключения/фатального исхода (описанный выше)| | `FileLogger` | `FileLogger.php` |Ведение журнала на уровне приложения (ошибки PDO, EPG и т.д.) до `error_log.log`| -| `DatabaseLogger` | `DatabaseLogger.php` |Клиентская потоковая передача событий запроса в `client_request.log` (вводится в таблицу `client_logs`)| -| `UpdateLogger` | `UpdateLogger.php` |Операции обновления системы до `MAIN_HOME/update.log` (обычный текст, а не base64)| +| `DatabaseLogger` | `DatabaseLogger.php` |Клиент передает события потокового запроса в `client_request.log` (вводимые в таблицу `client_logs`)| +| `UpdateLogger` | `UpdateLogger.php` |Операции обновления системы до `MAIN_HOME/update.log` (обычный текст, не base64)| Все регистраторы, кроме `UpdateLogger`, реализуют `LoggerInterface` и записывают JSON в кодировке base64. @@ -296,7 +296,7 @@ Logger::init(bool $showErrors, string $logFile): void ## Типы исключений в кодовой базе -В кодовой базе определено небольшое количество пользовательских классов исключений. Все неперехваченные исключения перехватываются командой `Logger::handleException()`, которая регистрирует всю цепочку исключений (включая `getPrevious()`). +Кодовая база определяет небольшое количество пользовательских классов исключений. Все неперехваченные исключения перехватываются параметром `Logger::handleException()`, который регистрирует всю цепочку исключений (включая `getPrevious()`). |Класс исключений|Базовый класс|Местоположение| | --- | --- | --- | @@ -306,7 +306,7 @@ Logger::init(bool $showErrors, string $logFile): void | `DefinitionException` | `\RuntimeException` | `src/Core/Parsing/PhpM3u8/src/Definition/DefinitionException.php` | | `DumpingException` | `\RuntimeException` | `src/Core/Parsing/PhpM3u8/src/Dumper/DumpingException.php` | -В большинстве прикладных программ используются общие ошибки `Exception` или используется встроенная система ошибок PHP. Обработчик исключений регистратора принимает любые `Throwable`. +Большая часть кода приложения использует общие ошибки `Exception` или полагается на встроенную систему ошибок PHP. Обработчик исключений регистратора принимает любые `Throwable`. --- @@ -316,14 +316,14 @@ Logger::init(bool $showErrors, string $logFile): void - `generateError()` возвращает общую страницу 404 (или явный HTTP-код), скрывая внутреннюю причину сбоя - Регистратор по-прежнему записывает все ошибки в `error_log.log` на диск -- `display_errors` и `display_startup_errors` имеют значение `'0'` +- для `display_errors` и `display_startup_errors` заданы значения `'0'` - Ошибки видны только через панель администратора (страница ошибок панели) или файлы журналов ### Отладка (`debug_show_errors = true`) - `generateError()` показывает стилизованную страницу с ключом ошибки и сопоставленным описанием - Регистратор дополнительно отображает ошибки на экране (вывод CLI с цветовой кодировкой или встроенный HTML). -- `display_errors` и `display_startup_errors` имеют значение `'1'` +- для `display_errors` и `display_startup_errors` заданы значения `'1'` Не включайте отображение отладки на рабочих узлах. @@ -333,14 +333,14 @@ Logger::init(bool $showErrors, string $logFile): void Инфраструктура обработки ошибок загружается на ранней стадии загрузки: -1. `bootstrap.php` определяет `MAIN_HOME` и регистрирует Composer автозагрузчик +1. `bootstrap.php` определяет `MAIN_HOME` и регистрирует автозагрузчик Composer 2. `XC_Bootstrap::loadConstants()` загружает (по порядку): - `Core/Error/ErrorCodes.php` -- заполняет `$rErrorCodes` - `Core/Error/ErrorHandler.php` -- определяет `generateError()` и `generate404()` - Путь и конфигурационные файлы - `Core/Logging/Logger.php` -- определение класса -3. @вызывается @0@@, регистрирующий три глобальных обработчика -4. С этого момента фиксируются все PHP ошибки, неперехваченные исключения и фатальные сбои +3. вызывается `Logger::init(PHP_ERRORS, LOGS_TMP_PATH . 'error_log.log')`, регистрирующий три глобальных обработчика +4. Начиная с этого момента, фиксируются все ошибки PHP, неперехваченные исключения и фатальные сбои Для конечных точек потоковой передачи, которые обходят полную загрузку, `RequestGuard.php` выполняет шаги 2-3 независимо: загружает настройки из файлового кэша, определяет `PHP_ERRORS` и вызывает `Logger::init()`. @@ -369,12 +369,12 @@ generateError('MY_NEW_ERROR'); |Файл|Цель| | --- | --- | | `src/Core/Error/ErrorCodes.php` |Централизованная карта кодов ошибок (`$rErrorCodes`)| -| `src/Core/Error/ErrorHandler.php` |@функции @0@@ и `generate404()`| -| `src/Core/Logging/Logger.php` |Глобальные обработчики ошибок, исключений и фатальных исходов PHP| +| `src/Core/Error/ErrorHandler.php` |функции `generateError()` и `generate404()`| +| `src/Core/Logging/Logger.php` |Глобальные PHP обработчики ошибок, исключений и фатальных исходов| | `src/Core/Logging/LoggerInterface.php` |Интерфейс контракта ведения журнала| | `src/Core/Logging/FileLogger.php` |Ведение журнала файлов на уровне приложения (PDO, EPG и т.д.)| | `src/Core/Logging/DatabaseLogger.php` |Ведение журнала событий потокового запроса клиента| | `src/Core/Logging/UpdateLogger.php` |Ведение журнала операций обновления системы| | `src/Core/Http/RequestGuard.php` |Путь потоковой передачи: защита от наводнений, проверка хоста, запуск регистратора| -| `src/Core/Diagnostics/DiagnosticsService.php` |Считывает таблицу `panel_logs` для отображения администратором и отправки API| +| `src/Core/Diagnostics/DiagnosticsService.php` |Считывает таблицу `panel_logs` для отображения администратором и отправки по API| | `src/bootstrap.php` |Включает уровень ошибок и регистратор во всех контекстах начальной загрузки| diff --git a/docs/ru/guides/feature-flags.md b/docs/ru/guides/feature-flags.md index 08ce2699..618dece7 100644 --- a/docs/ru/guides/feature-flags.md +++ b/docs/ru/guides/feature-flags.md @@ -1,8 +1,8 @@ # Флаги функций разработки -XC_VM использует константы и флаги, зависящие от настроек, для управления поведением среды. +XC_VM использует константы и флаги, управляемые настройками, для управления поведением среды. -Константы приложения хранятся в `src/Core/Config/AppConfig.php`. +Константы приложения хранятся в виде `src/Core/Config/AppConfig.php`. --- @@ -14,7 +14,7 @@ XC_VM использует константы и флаги, зависящие define('PHP_ERRORS', $rShowErrors); // derived from $rSettings['debug_show_errors'] ``` -`PHP_ERRORS` управляет PHP/детализирует детализацию и выводит данные с экрана регистратора: +`PHP_ERRORS` управляет PHP детализацией/отладкой и выводом на экран регистратора: ```php Logger::init(PHP_ERRORS, LOGS_TMP_PATH . 'error_log.log'); @@ -48,7 +48,7 @@ define('DB_ACCESS_ENABLED', false); // enables phpMiniAdmin tab/page in admin pa ## Статические константы приложения -От `src/Core/Config/AppConfig.php`: +Из `src/Core/Config/AppConfig.php`: ```php define('DB_ACCESS_ENABLED', false); @@ -78,5 +78,5 @@ define('OPENSSL_EXTRA', '...'); | --- | --- | | `src/Core/Config/AppConfig.php` |статические константы приложения| | `src/Core/Http/RequestGuard.php` |загружает `$rSettings`, устанавливает `PHP_ERRORS`| -| `src/Core/Error/ErrorHandler.php` |использует `debug_show_errors` поведение| +| `src/Core/Error/ErrorHandler.php` |использует поведение `debug_show_errors`| | `src/Core/Logging/Logger.php` |поведение при отладке/детализации| diff --git a/docs/ru/guides/geoip-and-device-detection.md b/docs/ru/guides/geoip-and-device-detection.md index bd87b191..0fa9c317 100644 --- a/docs/ru/guides/geoip-and-device-detection.md +++ b/docs/ru/guides/geoip-and-device-detection.md @@ -20,7 +20,7 @@ GeoIP::isISPBlocked($ispName, $blockedISPs): int GeoIP::isASNBlocked($asn, $blockedServers): bool ``` -Результаты кэшируются в виде файлов по адресам `CONS_TMP_PATH/{md5(ip)}_geo2` и `CONS_TMP_PATH/{md5(ip)}_isp`. +Результаты кэшируются в виде файлов с параметрами `CONS_TMP_PATH/{md5(ip)}_geo2` и `CONS_TMP_PATH/{md5(ip)}_isp`. ### Геоипсервис (высокого уровня) @@ -38,7 +38,7 @@ GeoIPService::matchCIDR($rASN, $rIP): array|null // hosting/proxy detection ## Обнаружение интернет-провайдеров и ASN -Когда `show_isps` включен, каждый потоковый запрос разрешает клиентский провайдер: +Когда `show_isps` включено, каждый потоковый запрос разрешает клиентский провайдер: ```php $rISPLock = GeoIPService::getISP($rIP); @@ -81,7 +81,7 @@ $detect->isTablet(); // tablets $detect->isAndroid(); // brand-specific ``` -Используется в `src/bootstrap.php` для определения мобильных устройств по адаптивному интерфейсу администратора. +Используется в `src/bootstrap.php` для обнаружения мобильных устройств с адаптивным интерфейсом администратора. ### Телевизионные приставки @@ -141,7 +141,7 @@ GeoIPService::matchCIDR($asn, $ip) flag[4] = proxy → error: PROXY_DETECT ``` -Также проверяет заголовок `X-XC_VM-DETECT` на предмет обнаружения повторного потока. +Также проверяет заголовок `X-XC_VM-DETECT` для обнаружения повторного потока. ### 5. Блокировка пользовательского агента @@ -165,7 +165,7 @@ error: DEVICE_NOT_ALLOWED or TOKEN_EXPIRED ## Географический маршрут -GeoIP выбор сервера для передачи данных и прокси-сервера: +GeoIP выбор сервера для хранения данных и прокси-сервера: ### Выбор сервера (StreamAuth::checkAccess) @@ -204,7 +204,7 @@ $updater->update(); // downloads and extracts all configured editions - `GeoIP2-Country`, `GeoIP2-City`, `GeoIP2-ISP`, `GeoIP2-Anonymous-IP` ( оплаченный) Загружается через MaxMind API с использованием `maxmind_account_id` и `maxmind_license_key`. -Извлекает файлы `.mmdb` из tar.gz архивов в `BIN_PATH/maxmind/`. +Извлекает `.mmdb` файлов из tar.gz архивов в `BIN_PATH/maxmind/`. Пути к файлам базы данных (определены в `src/Core/Config/Binaries.php`): @@ -216,7 +216,7 @@ GEOISP_BIN = BIN_PATH/maxmind/GeoIP2-ISP.mmdb ### Автоматическое обновление -Базы данных обновляются с помощью задания `cron:maxmind` cron (`src/Cli/CronJobs/MaxMindCronJob.php`). +Базы данных обновляются с помощью задания cron `cron:maxmind` (`src/Cli/CronJobs/MaxMindCronJob.php`). Он запускается ** только по вторникам** — в день, когда MaxMind публикует новые версии. Логика разветвляется на настройки панели: - если `maxmind_account_id` + `maxmind_license_key` + `maxmind_editions` задано, базы данных извлекаются непосредственно из MaxMind API (`MaxMindUpdater`, загружаются только настроенные версии).; @@ -236,7 +236,7 @@ GEOISP_BIN = BIN_PATH/maxmind/GeoIP2-ISP.mmdb - `[SKIP]` — уже обновлено; - `[WARN]` / `[ERROR]` — с подробной информацией (неверные учетные данные, ошибка HTTP, сеть недоступна). -> ➡️ Путь к API MaxMind отправляет заголовок `If-Modified-Since`, поэтому уже обновленная база данных возвращает HTTP 304 и статус `[SKIP]`. Чтобы принудительно выполнить повторную загрузку, сначала удалите (или переименуйте) соответствующий `.mmdb` в `BIN_PATH/maxmind/`, чтобы заголовок не отправлялся. Резервный вариант GitHub не имеет такого поведения — он сравнивает md5 и повторно загружает при несоответствии. +> ➡️ Путь к API MaxMind отправляет заголовок `If-Modified-Since`, поэтому уже обновленная база данных возвращает HTTP 304 и статус `[SKIP]`. Чтобы принудительно выполнить повторную загрузку, сначала удалите (или переименуйте) соответствующий `.mmdb` в `BIN_PATH/maxmind/`, чтобы заголовок не отправлялся. Резервный вариант GitHub не имеет такого поведения — он сравнивает md5 и повторные загрузки при несоответствии. --- @@ -254,7 +254,7 @@ GEOISP_BIN = BIN_PATH/maxmind/GeoIP2-ISP.mmdb Вошел в систему `live.php`, `vod.php`, `timeshift.php`, и `rtmp.php`. -Периодически архивируется с `lines_live` на `lines_activity` с помощью `ActivityCronJob`. +Периодически архивируется с `lines_live` по `lines_activity` с помощью `ActivityCronJob`. --- @@ -290,7 +290,7 @@ GEOISP_BIN = BIN_PATH/maxmind/GeoIP2-ISP.mmdb | `src/Core/Device/MobileDetect.php` |Библиотека Mobile_Detect| | `src/Domain/Device/EnigmaService.php` |Управление STB Enigma2| | `src/Domain/Device/MagService.php` |Управление MAG STB| -| `src/Domain/User/UserRepository.php` |GeoIP обогащение записей пользователей| +| `src/Domain/User/UserRepository.php` |GeoIP обогащение пользовательских записей| | `src/www/stream/auth.php` |потоковая авторизация со всеми проверками местоположения / устройства| | `src/Streaming/Auth/StreamAuth.php` |Выбор сервера с поддержкой GeoIP| | `src/Streaming/Balancer/ProxySelector.php` |Выбор прокси-сервера с поддержкой GeoIP| diff --git a/docs/ru/guides/input-validation.md b/docs/ru/guides/input-validation.md index 8a54fb79..a68258b0 100644 --- a/docs/ru/guides/input-validation.md +++ b/docs/ru/guides/input-validation.md @@ -1,8 +1,8 @@ # Проверка и санитарная обработка входных данных -XC_VM использует двухуровневую защиту для входящих данных запроса. Во-первых, программа глобальной очистки удаляет опасный контент со всех PHP суперглобальных ресурсов во время начальной загрузки, перед запуском любого кода приложения. Во-вторых, уровень проверки на уровне действия проверяет наличие обязательных полей перед выполнением бизнес-логики. +XC_VM использует двухуровневую защиту для входящих данных запроса. Во-первых, программа глобальной очистки удаляет опасный контент со всех PHP суперглобальных объектов во время начальной загрузки, перед запуском любого кода приложения. Во-вторых, уровень проверки на уровне действия проверяет наличие обязательных полей перед выполнением бизнес-логики. -Оба уровня реализованы в `src/Core/Validation/InputValidator.php`. +Оба слоя реализованы в виде `src/Core/Validation/InputValidator.php`. --- @@ -24,7 +24,7 @@ LegacyInitializer::initCore() После выполнения этой последовательности все необработанные суперглобальные данные были обработаны на месте, и объединенные/очищенные данные GET+POST доступны через `RequestManager`. -Контекст потоковой передачи (`LegacyInitializer::initStreaming()`) выполняет ту же последовательность очистки, используя класс `Request`, который предоставляет эквивалентные методы для пути начальной загрузки потоковой передачи. +Контекст потоковой передачи (`LegacyInitializer::initStreaming()`) выполняет ту же последовательность очистки с использованием класса `Request`, который предоставляет эквивалентные методы для пути начальной загрузки потоковой передачи. ### Чистые глобальные значения(&$rData, $rIteration = 0) @@ -35,7 +35,7 @@ LegacyInitializer::initCore() |Угроза|Узор удален|Замена| | --- | --- | --- | |Ввод нулевого байта|`\0` (chr 0)|удаленный| -|Обход пути| `../` |`../` (в HTML-кодировке)| +|Обход пути| `../` |`../` (в кодировке HTML)| |Переопределение RTL (подмена пользовательского интерфейса)| `‮` |удаленный| Рекурсия ограничена 10 уровнями, чтобы предотвратить исчерпание стека из-за глубоко вложенных входных данных. @@ -52,7 +52,7 @@ LegacyInitializer::initCore() 1. URL-расшифровывает, а HTML-экранирует ключ (`htmlspecialchars(urldecode(...))`) 2. Удаляет последовательности с двумя точками (`..` -> `''`) -3. Удаляет маркеры стиля `__dunder__` с помощью регулярного выражения +3. Strips `__dunder__`-маркеры стиля с помощью регулярного выражения 4. Проверяет соответствие допустимому набору символов: символы word, точки, дефисы, подчеркивания ### Значение parseCleanValue($rValue) @@ -61,7 +61,7 @@ LegacyInitializer::initCore() |Шаг|Что он делает| | --- | --- | -|Неэкранированный пейзаж|`stripslashes()` и восстановите ` ` в исходное положение| +|Неэкранированный пейзаж|`stripslashes()` и вернуть ` ` в исходное положение| |Нормализовать новые строки|Преобразовать `\r\n`, `\n\r`, `\r` в `\n`| |Защита HTML-комментариев|`` становится `-->`| |Нейтрализация тегов скрипта|` member_group_id -> group -> advanced[] (array of permission keys) ``` -Состояние разрешений загружается в `$rPermissions` global во время инициализации сеанса и остается доступным на протяжении всего жизненного цикла запроса. +Состояние разрешения загружается в глобальное значение `$rPermissions` во время инициализации сеанса и остается доступным на протяжении всего жизненного цикла запроса. Ключевые поля в `$rPermissions`: @@ -37,7 +37,7 @@ user -> member_group_id -> group ## Ключи разрешений -Ключи разрешений объявляются в `src/config/permissions.php` как массив `$rPermissionKeys`. Каждый ключ представляет собой строковый идентификатор, используемый с `Authorization::check('adv', $key)`. +Ключи разрешений объявляются в `src/config/permissions.php` как массив `$rPermissionKeys`. Каждый ключ представляет собой строковый идентификатор, используемый в `Authorization::check('adv', $key)`. Категории: @@ -68,11 +68,11 @@ user -> member_group_id -> group Authorization::check(string $type, mixed $id): bool ``` -**Предварительные условия:** Возвращает `false` немедленно, если `$rUserInfo`, `$rPermissions`, или `$db` не инициализированы. +**Предварительные условия:** Возвращает `false` немедленно, если `$rUserInfo`, `$rPermissions` или `$db` не инициализированы. #### Тип: `user` -Проверяет, может ли текущий пользователь получить доступ к целевому пользователю-администратору. Создает список из идентификатора текущего пользователя плюс его дерева `all_reports`, затем запрашивает таблицу `users`, чтобы убедиться, что целевой пользователь `owner_id` находится в этом списке (или целью является текущий пользователь). +Проверяет, может ли текущий пользователь получить доступ к целевому пользователю-администратору. Создает список из идентификатора текущего пользователя и их дерева `all_reports`, затем запрашивает таблицу `users`, чтобы проверить, есть ли в этом списке имя целевого пользователя `owner_id` (или целевым пользователем является текущий пользователь). ```php Authorization::check('user', $userId); @@ -95,7 +95,7 @@ Authorization::check('adv', 'edit_bouquet'); Authorization::check('adv', 'block_isps'); ``` -**Важно: `is_admin` gate.** Перед проверкой массива расширенных разрешений метод требует, чтобы значение `$rPermissions['is_admin']` было равно true. Если пользователь не является администратором, `check('adv', ...)` всегда возвращает `false`: +**Важно: `is_admin` gate.** Перед проверкой массива расширенных разрешений метод требует, чтобы значение `$rPermissions['is_admin']` было равно true. Если пользователь не является администратором, `check('adv', ...)` всегда возвращает значение `false`: ```php if (!($rType == 'adv' && $rPermissions['is_admin'])) { @@ -124,7 +124,7 @@ return true; Authorization::hasResellerPermissions(string $type): bool ``` -Возвращает, не является ли значение `$rPermissions[$type]` непустым. Используется для логических флагов, специфичных для реселлера, таких как `create_line`, `create_mag`, и т.д. +Возвращает, не является ли значение `$rPermissions[$type]` непустым. Используется для логических флагов, специфичных для реселлера, таких как `create_line`, `create_mag` и т.д. --- @@ -143,7 +143,7 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool #### Поведение, разрешенное по умолчанию -Оба метода возвращают значение `true` для любой страницы, явно не указанной в их инструкциях switch. Это означает, что страницы без сопоставления доступны всем авторизованным пользователям соответствующего типа (администраторам или торговым посредникам). Доступ ограничен только к страницам с явно указанными записями. +Оба метода возвращают значение `true` для любой страницы, явно не указанной в их инструкциях switch. Это означает, что страницы без сопоставления доступны всем авторизованным пользователям соответствующего типа (администраторам или торговым посредникам). Доступ ограничен только к страницам с явно заданными параметрами. --- @@ -151,11 +151,11 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool Метод `checkPermissions()` сопоставляет страницы панели администратора с ключами доступа `adv`. Ниже приведено полное сопоставление, сгруппированное по категориям. -### Шаблон для создания и редактирования +### Шаблон создания или редактирования Многие страницы сущностей используют условную логику, основанную на параметрах запроса: -- Если указан параметр `id`, проверяется разрешение **редактировать** +- Если указан параметр `id`, то проверяется разрешение **редактировать** - Если параметр `id` отсутствует, проверяется разрешение **добавить** - Некоторые страницы (stream, movie) также проверяют наличие параметра `import` и требуют соответствующего разрешения на импорт @@ -166,7 +166,7 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |Страница|Разрешение|Записи| | --- | --- | --- | |`streams`, `stream_view`, `provider`, `providers`, `epg_view`, `created_channels`, `stream_rank`, `archive`| `streams` | | -| `stream` | `edit_stream` |Когда присутствует `id`| +| `stream` | `edit_stream` | When `id` is present | | `stream` | `add_stream` |Когда нет `id`| | `stream` | `import_streams` |Когда присутствует параметр `import` (в дополнение к параметру `add_stream`)| | `stream_categories` | `categories` | | @@ -176,7 +176,7 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool | `mass_edit_streams` | `edit_stream` | | | `review` | `import_streams` | | | `channel_order` | `channel_order` | | -| `created_channel` | `edit_cchannel` |Когда присутствует `id`| +| `created_channel` | `edit_cchannel` | When `id` is present | | `created_channel` | `create_channel` |Когда нет `id`| ### Фильмы и VOD @@ -184,7 +184,7 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |Страница|Разрешение|Записи| | --- | --- | --- | | `movies` | `movies` | | -| `movie` | `edit_movie` |Когда присутствует `id`| +| `movie` | `edit_movie` | When `id` is present | | `movie` | `add_movie` |Когда нет `id`| | `movie` | `import_movies` |Когда присутствует параметр `import` (в дополнение к параметру `add_movie`)| | `movie_mass` | `mass_sedits_vod` | | @@ -196,12 +196,12 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |Страница|Разрешение|Записи| | --- | --- | --- | | `series` | `series` | | -| `serie` | `edit_series` |Когда присутствует `id`| +| `serie` | `edit_series` | When `id` is present | | `serie` | `add_series` |Когда нет `id`| | `series_order` | `edit_series` | | | `episodes` | `episodes` | | -| `episode` | `edit_episode` |Когда присутствует `id`| -| `episode` | `add_episode` |Когда нет `id`; при отказе переключается на `episodes`| +| `episode` | `edit_episode` | When `id` is present | +| `episode` | `add_episode` |Когда нет `id`; при отказе переходит на `episodes`| |`series_mass`, `episodes_mass`| `mass_sedits` | | ### Радио @@ -209,7 +209,7 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |Страница|Разрешение|Записи| | --- | --- | --- | | `radios` | `radio` | | -| `radio` | `edit_radio` |Когда присутствует `id`| +| `radio` | `edit_radio` | When `id` is present | | `radio` | `add_radio` |Когда нет `id`| | `radio_mass` | `mass_edit_radio` | | @@ -218,7 +218,7 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |Страница|Разрешение|Записи| | --- | --- | --- | | `lines` | `users` | | -| `line` | `edit_user` |Когда присутствует `id`| +| `line` | `edit_user` | When `id` is present | | `line` | `add_user` |Когда нет `id`| | `line_mass` | `mass_edit_lines` | | |`line_activity`, `theft_detection`, `line_ips`| `connection_logs` | | @@ -229,7 +229,7 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |Страница|Разрешение|Записи| | --- | --- | --- | | `mags` | `manage_mag` | | -| `mag` | `edit_mag` |Когда присутствует `id`| +| `mag` | `edit_mag` | When `id` is present | | `mag` | `add_mag` |Когда нет `id`| | `mag_events` | `manage_events` | | | `mag_mass` | `mass_edit_mags` | | @@ -241,7 +241,7 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |Страница|Разрешение|Записи| | --- | --- | --- | | `users` | `mng_regusers` | | -| `user` | `edit_reguser` |Когда присутствует `id`| +| `user` | `edit_reguser` | When `id` is present | | `user` | `add_reguser` |Когда нет `id`| | `user_mass` | `mass_edit_users` | | | `user_logs` | `reg_userlog` | | @@ -251,11 +251,11 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |Страница|Разрешение|Записи| | --- | --- | --- | | `bouquets` | `bouquets` | | -| `bouquet` | `edit_bouquet` |Когда присутствует `id`| -| `bouquet` | `add_bouquet` |Когда нет `id`; при отказе переключается на `edit_bouquet`| +| `bouquet` | `edit_bouquet` | When `id` is present | +| `bouquet` | `add_bouquet` |Когда нет `id`; при отказе переходит на `edit_bouquet`| |`bouquet_order`, `bouquet_sort`| `edit_bouquet` | | |`packages`, `addons`| `mng_packages` | | -| `package` | `edit_package` |Когда присутствует `id`| +| `package` | `edit_package` | When `id` is present | | `package` | `add_packages` |Когда нет `id`| ### Группы @@ -263,23 +263,23 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |Страница|Разрешение|Записи| | --- | --- | --- | | `groups` | `mng_groups` | | -| `group` | `edit_group` |Когда присутствует `id`| -| `group` | `add_group` |Когда нет `id`; при отказе переключается на `mng_groups`| +| `group` | `edit_group` | When `id` is present | +| `group` | `add_group` |Когда нет `id`; при отказе переходит на `mng_groups`| ### EPG |Страница|Разрешение|Записи| | --- | --- | --- | | `epgs` | `epg` | | -| `epg` | `epg_edit` |Когда присутствует `id`| -| `epg` | `add_epg` |Когда нет `id`; при отказе переключается на `epg`| +| `epg` | `epg_edit` | When `id` is present | +| `epg` | `add_epg` |Когда нет `id`; при отказе переходит на `epg`| ### Серверы |Страница|Разрешение|Записи| | --- | --- | --- | |`servers`, `server_view`, `server_order`, `proxies`| `servers` | | -|`server`, `proxy`| `edit_server` |Когда присутствует `id`| +|`server`, `proxy`| `edit_server` | When `id` is present | |`server`, `proxy`| `add_server` |Когда нет `id`| | `server_install` | `add_server` | | @@ -357,7 +357,7 @@ PageAuthorization::checkResellerPermissions(?string $page = null): bool |`epg_view`, `streams`, `created_channels`, `movies`, `episodes`, `radios`| `can_view_vod` | |`live_connections`, `line_activity`| `reseller_client_connection_logs` | -Любая страница реселлера, не указанная выше, возвращает `true` (доступна по умолчанию). +Любая страница реселлера, не указанная выше, возвращает значение `true` (доступно по умолчанию). --- @@ -380,7 +380,7 @@ if (!Authorization::check('adv', 'my_new_permission')) { } ``` -3. Если разрешение должно открывать доступ к странице, добавьте обращение к `PageAuthorization::checkPermissions()`: +3. Если разрешение должно указывать на страницу, добавьте регистр в `PageAuthorization::checkPermissions()`: ```php case 'my_new_page': @@ -408,8 +408,8 @@ case 'my_entity': |Файл|Цель| | --- | --- | -| `src/config/permissions.php` |Реестр ключей разрешений (`$rPermissionKeys` array)| +| `src/config/permissions.php` |Реестр ключей разрешений (массив`$rPermissionKeys`)| | `src/Core/Auth/Authorization.php` |Проверка разрешений на уровне объекта и расширенные проверки разрешений| | `src/Core/Auth/PageAuthorization.php` |Настройка на уровне страницы для панелей администратора и реселлера| -| `src/Core/Auth/SessionManager.php` |Контекст сеанса; заполняет `$rPermissions` и `$rUserInfo`| +| `src/Core/Auth/SessionManager.php` |Контекст сеанса; заполняет значения `$rPermissions` и `$rUserInfo`| | `src/Core/Auth/Authenticator.php` |Аутентификация (вход в систему, проверка учетных данных)| diff --git a/docs/ru/guides/phpunit-phar.md b/docs/ru/guides/phpunit-phar.md index 28ecf385..2c2800da 100644 --- a/docs/ru/guides/phpunit-phar.md +++ b/docs/ru/guides/phpunit-phar.md @@ -20,7 +20,7 @@ ## 2. Скачать PHPUnit PHAR -Этот проект привязан к PHP 8.1, поэтому используйте PHPUnit 10. +Этот проект привязан к PHP версии 8.1, поэтому используйте PHPUnit 10. ```bash cd /home/xc_vm @@ -63,7 +63,7 @@ chmod +x tools/.bin/phpunit.phar ## 7. Дополнительно: Выход покрытия -Если установлен `xdebug` или `pcov`: +Если установлено значение `xdebug` или `pcov`: ```bash XDEBUG_MODE=coverage /home/xc_vm/bin/php/bin/php tools/.bin/phpunit.phar -c tests/phpunit.xml.dist --coverage-text diff --git a/docs/ru/guides/process-management.md b/docs/ru/guides/process-management.md index 1adb383f..81e8afab 100644 --- a/docs/ru/guides/process-management.md +++ b/docs/ru/guides/process-management.md @@ -1,7 +1,7 @@ # Модели управления процессами `ProcessManager` централизует проверку процессов Linux, завершение работы, проверку PID-файлов и блокировку cron. -Он заменяет разрозненные специальные проверки `posix_kill`, `ps`, и `/proc`. +Он заменяет разрозненные специальные проверки `posix_kill`, `ps` и `/proc`. --- @@ -29,7 +29,7 @@ ProcessManager::isNamedProcessRunning( Соответствует шаблону командной строки `NAME[ID]` (для работников, основанных на названии процесса). -### Проверьте потоковый процесс +### Проверка потокового процесса ```php ProcessManager::isStreamRunning(int $pid, int $streamId): bool @@ -75,7 +75,7 @@ ProcessManager::acquireCronLock(string $pidFile, int $maxAge = 1800): void ## `/proc` Проверить кэш -`isRunning()` использует короткое TTL-кэширование для проверки `/proc` (1 секунда), чтобы уменьшить количество повторных операций ввода-вывода в узких циклах. +`isRunning()` использует короткое TTL-кэширование для `/proc` проверок (1 секунда), чтобы уменьшить количество повторных операций ввода-вывода в узких циклах. --- diff --git a/docs/ru/guides/translations.md b/docs/ru/guides/translations.md index d6871d34..88d16288 100644 --- a/docs/ru/guides/translations.md +++ b/docs/ru/guides/translations.md @@ -10,7 +10,7 @@ XC_VM использует файловую систему перевода. К cp src/resources/langs/en.ini src/resources/langs/xx.ini ``` -Замените `xx` на [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) код языка (например, `it` для итальянского, `pl` для польского, `ja` для японского). +Замените `xx` кодом языка [ISO 639-1](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (например, `it` для итальянского, `pl` для польского, `ja` для японского). 2. Откройте `xx.ini` и переведите значения (справа от `=`).: @@ -27,7 +27,7 @@ actions = "Actions" ; ← translate this ## Формат файла -Каждый файл `.ini` соответствует этой структуре: +Каждый файл `.ini` имеет следующую структуру: ```ini [Language] @@ -37,8 +37,8 @@ another_key = "Another translated text" **Правила:** -- Заголовок раздела `[Language]` является **обязательным** в первой строке. -- Ключи являются идентификаторами `snake_case` — **не меняйте их **. +- Заголовок раздела `[Language]` должен быть **обязательным** в первой строке. +- Ключи являются идентификаторами `snake_case` — **не меняйте их**. - Значения должны быть заключены в **двойные кавычки**. - Строки отсортированы в алфавитном порядке по ключу для обеспечения согласованности. - Кодировка файла должна быть **UTF-8** (без спецификации). @@ -47,9 +47,9 @@ another_key = "Another translated text" |Шаг|Что происходит| |------|-------------| -|Панель загрузки|`Translator::init()` сканирует `src/resources/langs/` на наличие `*.ini` файлов| +|Панель загрузки|`Translator::init()` проверяет `src/resources/langs/` на наличие `*.ini` файлов| |Список языков|`Translator::available()` возвращает все найденные языковые коды| -|Выбор пользователя|Язык сохраняется в файле cookie `lang` (для каждого браузера) и в столбце `settings.language` DB (по умолчанию).| +|Выбор пользователя|Язык сохраняется в файле cookie `lang` (для каждого браузера) и в столбце базы данных `settings.language` (по умолчанию).| |Отсутствующий ключ|Если ключ перевода используется в коде, но отсутствует в вашем файле `.ini`, система ** автоматически добавит ** его с именем ключа в качестве значения по умолчанию| ## Доступные языки @@ -66,7 +66,7 @@ another_key = "Another translated text" ## Чаевые -- **Всегда используйте `en.ini` в качестве источника достоверности ** — он содержит все ключи. В других файлах могут отсутствовать ключи, которые автоматически заполняются во время выполнения. +- **Всегда используйте `en.ini` в качестве источника достоверности** — он содержит все ключи. В других файлах могут отсутствовать ключи, которые автоматически заполняются во время выполнения. - **Автоматическое создание отсутствующих ключей**: если в вашем файле отсутствует ключ, `Translator` автоматически добавит `key = "key"` к вашему файлу. Затем вы сможете найти и перевести эти непереведенные записи. - **Поиск непереведенных ключей** — поиск строк, в которых ключ равен значению: @@ -74,7 +74,7 @@ another_key = "Another translated text" grep -P '^(\w+)\s*=\s*"\1"$' src/resources/langs/xx.ini ``` -- ** Проверьте свой файл ** — убедитесь, что `parse_ini_file()` может его прочитать: +- **Проверьте свой файл ** — убедитесь, что `parse_ini_file()` может его прочитать: ```bash php -r "var_dump(parse_ini_file('src/resources/langs/xx.ini', false, INI_SCANNER_RAW));" | head -20 diff --git a/docs/ru/guides/ucs-integration.md b/docs/ru/guides/ucs-integration.md index 6bb2793f..fbab818b 100644 --- a/docs/ru/guides/ucs-integration.md +++ b/docs/ru/guides/ucs-integration.md @@ -11,14 +11,14 @@ |10| `wildcard.example.com` | `10.example.com` | |42| `wildcard.lb1.com` | `42.lb1.com` | -Каждая строка получает стабильный поддомен. UCS (или вышестоящий DNS/прокси-сервер) может перенаправлять `*.example.com` на нужный сервер или применять политики для каждой строки. +Каждая строка получает стабильный поддомен. UCS (или вышестоящий DNS/прокси-сервер) может перенаправлять `*.example.com` к нужному серверу или применять политики для каждой строки. ## Требования 1. **Сервер балансировки нагрузки** (не основной сервер), по крайней мере, с одним доменом, настроенным в соответствии с **Доменами и IP-адресами**. -2. **Для этого сервера включена поддержка произвольного IP / домена** (флажок`random_ip` на странице редактирования сервера). -3. Запись в домене, начинающаяся с **`wildcard.`** ( например, `wildcard.example.com`). -4. DNS: подстановочная запись для базовой зоны (например, `*.example.com`), указывающая на LB или ваш UCS edge. +2. **Обслуживать случайный IP / домен**, включенный на этом сервере (флажок`random_ip` на странице редактирования сервера). +3. Запись в домене, начинающаяся с **`wildcard.`** (например, `wildcard.example.com`). +4. DNS: подстановочная запись для базовой зоны (например, `*.example.com`), указывающая на LB или вашу границу UCS. ## Установка @@ -44,9 +44,9 @@ 1. Домены сервера LB: `wildcard.example.com`, `lb2.example.com` 2. **Обслуживать случайный IP / домен**: включен -3. Идентификатор клиентской линии: `10` +3. Client line ID: `10` 4. Auth выбирает `wildcard.example.com` из списка доменов -5. URL-адрес перенаправления хоста становится `10.example.com` +5. URL-адрес узла перенаправления становится `10.example.com` ## Реализация @@ -59,13 +59,13 @@ if ($rUserID && strpos($rDomain, 'wildcard.') !== false) { } ``` -Вызывается с `Public/stream/auth.php` с помощью `$rUserID = $rUserInfo['id']` для прямого эфира, VOD, timeshift, и связанных с ним перенаправлений. +Вызывается из `Public/stream/auth.php` с помощью `$rUserID = $rUserInfo['id']` для прямых, VOD, timeshift и связанных с ними перенаправлений. ## Записи - Используйте буквальный префикс **`wildcard.`** в строке домена; заменяется только этот сегмент. -- Идентификатор строки - это внутренний идентификатор таблицы **lines** (`users.id` в потоковой авторизации), а не имя пользователя. -- Подстановка подстановочных знаков применяется только к URL-адресам перенаправления LB; она не изменяет имена плейлистов или хостов API, если только эти пути также не вызывают `getStreamingURL()` с идентификатором строки. +- Идентификатор строки - это внутренний идентификатор таблицы **lines** (`users.id` в streaming auth), а не имя пользователя. +- Подстановка подстановочных знаков применяется только к URL-адресам перенаправления LB; она не изменяет имена плейлистов или хостов API, если только эти пути также не содержат `getStreamingURL()` с идентификатором строки. ## Связанные файлы diff --git a/docs/ru/info/faq.md b/docs/ru/info/faq.md index d1f7964b..5118cb56 100644 --- a/docs/ru/info/faq.md +++ b/docs/ru/info/faq.md @@ -7,7 +7,7 @@ ## Проблемы с потоками
-❌ Моя трансляция не начинается на MAIN или LB +❌ Мой стрим не запускается на MAIN или LB --- @@ -19,7 +19,7 @@ sudo -u xc_vm /home/xc_vm/console.php monitor 291 ```` -> 🧩 Где `291` - это ваш **идентификатор потока ** (замените его на свой собственный). +> 🧩 Где `291` - это ваш **идентификатор потока** (замените его на свой собственный). --- @@ -69,7 +69,7 @@ sudo apt install
-❌ Сбой потоковой передачи из-за "IP_MISMATCH" или "TOKEN_EXPIRED" +❌ Streaming fails with "IP_MISMATCH" or "TOKEN_EXPIRED" --- @@ -80,7 +80,7 @@ sudo apt install **Соответствующие настройки:** - `restrict_same_ip` — насколько строго соблюдается соответствие IP-адресов -- `disallow_2nd_ip_con` — блокирует одновременные подключения с разных IP-адресов +- `disallow_2nd_ip_con` — блокирует одновременные подключения с разных IP-адресов. Если это вызывает проблемы у законных пользователей (например, мобильные сети часто меняют IP-адреса), измените уровень ограничений в настройках панели администратора. @@ -93,20 +93,20 @@ sudo apt install ## Проблемы с логином и доступом
-❌ Я заблокирован — IP продолжает блокироваться +❌ Я заблокирован — IP продолжает блокироваться --- -@Защита от перебора @0@@ блокирует IP-адреса после слишком большого числа неудачных попыток входа в систему. Это контролируется: +XC_VM защита методом перебора блокирует IP-адреса после слишком большого числа неудачных попыток входа в систему. Это контролируется: - `bruteforce_mac_attempts` — количество попыток для каждого MAC за определенный промежуток времени -- `bruteforce_username_attempts` — количество попыток для каждого пользователя за определенный промежуток времени +- `bruteforce_username_attempts` — количество попыток для каждого пользователя в течение временного окна - `flood_limit` — общее количество запросов в окне ** Чтобы разблокировать себя:** 1. **Из панели администратора:** Сервис → Управление IP-адресами → удалить из списка заблокированных. -2. **Из интерфейса командной строки:** `sudo /home/xc_vm/console.php tools flush` — очищает все заблокированные IP-адреса. +2. **Из интерфейса командной строки:** `sudo /home/xc_vm/console.php tools flush` — удаляет все заблокированные IP-адреса. 3. **Если вы полностью заблокированы:** Используйте `console.php tools rescue` для создания аварийного кода доступа (см. [CLI Tools](../guides/cli-tools.md)). --- @@ -114,27 +114,27 @@ sudo apt install
-❌ Телеприставка блокируется после сброса настроек/смены прошивки (ее серийный номер или device_id изменились, но на панели установлен старый) +❌ Set-top box gets blocked after a reset/firmware change (its serial or device_id changed, but the panel has the old one) --- -Блок, который раньше работал, начинает блокировать свой ** IP-адрес ** после ** сброса настроек, изменения /обновления прошивки, аппаратной замены ** (или перемещения MAC в другой блок): теперь он сообщает ** другой серийный номер (`sn`) или `device_id`** больше, чем сохранено на панели. На `get_profile` сервер блокирует IP-адрес, и портал возвращает 404. +Блок, который раньше работал, начинает блокировать свой ** IP-адрес ** после ** сброса настроек, изменения/обновления прошивки, аппаратной замены ** (или перемещения MAC в другой блок): теперь он сообщает ** другой серийный номер (`sn`) или `device_id`** больше того, что сохранила панель. При `get_profile` сервер блокирует IP-адрес, и портал возвращает 404. -Это ** не ошибка ** — это защита портала от клонирования, **MAGSCAN**. Для этого требуется ввести серийный номер и сравнить опубликованный `sn` с сохраненным `mag_devices.sn`: +Это ** не ошибка** — это защита портала от клонирования **MAGSCAN**. Для этого требуется ввести серийный номер и сравнить опубликованное значение `sn` с сохраненным значением `mag_devices.sn`: - **Нет серийного номера** в запросе → запретить (`[MS] No Serial Number`). - **Опубликовано `sn` ≠ сохранено на устройстве `sn`** → бан (`[MS] Invalid Serial Number`). -В обоих случаях IP-адрес записывается в таблицу `blocked_ips` (а оттуда в iptables), и устройство получает код 404. Если на устройстве установлен флаг **`lock_device`**, `device_id`, `device_id2` и `hw_version` также отмечены — проверка несоответствия не выполняется, и устройство показывает, что "ваше устройство неактивно" (без запрета IP-адреса). +В обоих случаях IP-адрес записывается в таблицу `blocked_ips` (а оттуда в таблицу iptables), и устройство получает код 404. Если на устройстве установлен флаг **`lock_device`**, то `device_id`, `device_id2` и `hw_version` также отмечены — несоответствие не подтверждается, и устройство показывает, что "ваше устройство неактивно" (без запрета IP-адреса). **Как исправить (для легитимного ящика, данные которого действительно изменились):** 1. **Сбросьте привязку на панели:** откройте это устройство MAG в admin и **очистите его сохраненный серийный номер / `device_id`** (или удалите и повторно добавьте устройство). После этого условие "серийный номер уже записан" больше не срабатывает, и при следующем подключении новые значения будут привязаны. -2. **Разблокируйте IP.** Самый простой способ — **через веб-панель**: откройте **Сервис → Управление IP-адресами** (`//ips`), в котором перечислены заблокированные IP—адреса - удалите тот, который вам нужен (или очистите весь список). Настройки CLI / вручную, если вы не можете добраться до панели: - - CLI (очистить все блоки): `sudo /home/xc_vm/console.php tools flush`; - - Вручную, для каждого IP-адреса: `sudo iptables -D INPUT -s -j DROP && sudo rm -f /home/xc_vm/tmp/flood/block_`. +2. **Разблокируйте IP.** Самый простой способ — **через веб—панель**: откройте **Сервис → Управление IP** (`//ips`), где перечислены заблокированные IP-адреса - удалите тот, который вам нужен (или очистите весь список). Настройки CLI / вручную, если вы не можете добраться до панели: + - CLI (clear all blocks): `sudo /home/xc_vm/console.php tools flush`; + - Вручную, для каждого IP: `sudo iptables -D INPUT -s -j DROP && sudo rm -f /home/xc_vm/tmp/flood/block_`. -> ⚠️ Настройка `enable_debug_stalker` обходит проверку `lock_device` / изображений, но НЕ последовательный жесткий запрет MAGSCAN (который выполняется ранее) - вам все равно придется очистить сохраненные `sn` на панели. +> ⚠️ Установка `enable_debug_stalker` позволяет обойти проверку `lock_device` / изображений, но не последовательный жесткий запрет MAGSCAN (который выполняется ранее) - вам все равно придется очистить сохраненные `sn` на панели. --- @@ -168,18 +168,18 @@ sudo /home/xc_vm/console.php tools rescue ## База данных и конфигурация
-❌ " Не удалось подключиться к базе данных" при запуске +❌ "Не удалось подключиться к базе данных" при запуске --- Самая распространенная проблема. Причины: -1. **Неверные учетные данные в `config.ini`** — проверьте `host`, `port`, `db_user`, `db_pass`, `db_name` -2. **MySQL/MariaDB не работает** — `sudo systemctl status mariadb` +1. **Неверные учетные данные в `config.ini`** — проверка `host`, `port`, `db_user`, `db_pass`, `db_name` +2. **MySQL/MariaDB не запущен** — `sudo systemctl status mariadb` 3. **Сеть недоступна** — сервер базы данных на другом хосте и порту защищен брандмауэром. -4. **Пользователю не хватает привилегий ** — повторно предоставить их с помощью `console.php tools mysql` +4. **Пользователю не хватает привилегий** — повторно предоставить их с помощью `console.php tools mysql` -** Исправлено:** Отредактируйте `/home/xc_vm/config/config.ini`, затем запустите: +**Исправлено:** Отредактируйте `/home/xc_vm/config/config.ini`, затем запустите: ```bash sudo /home/xc_vm/console.php status @@ -190,19 +190,19 @@ sudo /home/xc_vm/console.php status
-❌ Сбой миграции базы данных во время обновления +❌ Во время обновления произошел сбой переноса базы данных --- Перенос `.sql` файлов из `migrations/` выполняется автоматически во время обновления. В случае сбоя одного из них: -- Миграция регистрируется со статусом `[WARN]` — она не будет повторена автоматически. +- Перенос зарегистрирован со статусом `[WARN]` — он не будет повторен автоматически. - Распространенные причины: синтаксическая ошибка, таблица уже существует, конфликт внешних ключей, отсутствует привилегия ALTER. **Отладка:** 1. Проверьте, какая миграция завершилась неудачно, в выходных данных консоли. -2. Откройте файл в `migrations/` и проверьте SQL. +2. Откройте файл в `migrations/` и проверьте SQL-код. 3. Устраните проблему вручную в MySQL, после чего следующее обновление продолжится с того места, где оно было остановлено. Дополнительные сведения см. в разделе [Миграция базы данных](../guides/cli-tools.md#database-updates-after-version-upgrade). @@ -220,12 +220,12 @@ sudo /home/xc_vm/console.php status --- -`console.php certbot` может завершиться ошибкой с разными кодами ошибок: +`console.php certbot` может произойти сбой с разными кодами ошибок: |Ошибка|Причина|Чинить| |---|---|---| |Ошибка 3|Домен - это простой IP-адрес|Для Certbot требуется доменное имя, а не IP-адрес| -|Ошибка 4|Не удалось выполнить пробный запуск — используется порт 80/443|Остановить конфликтующую службу: `sudo lsof -i :80`| +|Ошибка 4|Не удалось выполнить пробный запуск — используется порт 80/443| Stop conflicting service: `sudo lsof -i :80` | |Ошибка 0|Файлы, не найденные после генерации|Проверить `/home/xc_vm/bin/certbot/logs/xc_vm.log`| |Ошибка 2|Неожиданная ошибка certbot|Проверьте журналы, убедитесь, что DNS разрешает доступ к вашему серверу| @@ -240,7 +240,7 @@ sudo rm -f /home/xc_vm/bin/certbot/*/.certbot.lock
-❌ Nginx не будет перезагружаться — конфликты портов +❌ Nginx won't reload — port conflicts --- @@ -293,7 +293,7 @@ sudo -u xc_vm /home/xc_vm/console.php update update
-❌ Обслуживание неожиданно прекращается или не будет завершено полностью +❌ Обслуживание неожиданно прекращается или не завершается полностью --- @@ -310,7 +310,7 @@ sudo killall -9 -u xc_vm sudo /home/xc_vm/console.php service start ``` -Распространенные причины: PHP взаимоблокировка транзакций, бесконечный цикл потоковой обработки или тайм-аут сетевого сокета в ожидании ответа. +Common causes: PHP transaction deadlock, infinite loop in stream processing, or network socket timeout waiting for a response. --- @@ -321,7 +321,7 @@ sudo /home/xc_vm/console.php service start ## Разрешения и система
-❌ Ошибки с отказом в разрешении продолжают появляться +❌ Ошибки, связанные с отказом в разрешении, продолжают появляться --- @@ -332,7 +332,7 @@ sudo /home/xc_vm/console.php status ``` Что это исправляет: -- PHP-FPM разрешения на использование сокета (`bin/php/sockets/*`) +- PHP-FPM права доступа к сокету (`bin/php/sockets/*`) - Владелец каталога содержимого (`content/streams/`) - Владение конфигурационным файлом (`config/`) - Исполняемый бит на `daemons.sh` @@ -345,14 +345,14 @@ sudo /home/xc_vm/console.php status
-❌ Балансировщик нагрузки отображается как отключенный / не удается синхронизировать с основным +❌ Балансировщик нагрузки отображается как автономный / не удается синхронизировать с основным --- Серверы LB опрашивают MAIN по протоколу HTTP и обрабатывают сигналы. При сбое синхронизации: 1. **Сеть:** LB не может подключиться к HTTP—порту MAIN - проверьте правила брандмауэра -2. **База данных:** LB не может подключиться к MAIN'у MySQL — повторно предоставить привилегии: +2. **База данных:** LB не может подключиться к MAIN'у MySQL — повторно предоставьте привилегии: ```bash sudo /home/xc_vm/console.php tools mysql ``` diff --git a/docs/ru/info/migration_guide.md b/docs/ru/info/migration_guide.md index 1c2ad2a8..efedf691 100644 --- a/docs/ru/info/migration_guide.md +++ b/docs/ru/info/migration_guide.md @@ -29,7 +29,7 @@ XC_VM миграция переносит только **данные**. ## Прежде чем Вы начнете > 💡 **Рекомендация:** -> Выполните миграцию на **новой установке XC_VM**. +> Выполните миграцию при **новой установке XC_VM**. > > ⚠️ **Важно:** > Системные настройки и настройки панели ** НЕ перенесены**. @@ -37,7 +37,7 @@ XC_VM миграция переносит только **данные**. Если вы решите перейти на **существующую установку**, имейте в виду: -- XC_VM приведет к удалению всех таблиц** в основной базе данных, которые соответствуют данным из базы данных миграции. +- XC_VM приведет к удалению всех таблиц в основной базе данных, которые соответствуют данным из базы данных миграции. - **Резервное копирование является обязательным.** Автоматический откат не предусмотрен. --- diff --git a/docs/ru/info/watch_folder.md b/docs/ru/info/watch_folder.md index cf3f7a6b..cd2a5463 100644 --- a/docs/ru/info/watch_folder.md +++ b/docs/ru/info/watch_folder.md @@ -25,11 +25,11 @@ Watch Folder - это автоматизированная система имп ### Шаг за шагом 1. **Администратор создает папку просмотра** в панели администратора (Папка просмотра → Добавить) или через API (`create_watch_folder`). Конфигурация включает в себя: путь к каталогу, тип контента (фильм/сериал), целевую категорию, букеты, настройки парсера и назначенный сервер. -2. **Задание Cron `cron:watch`** выполняется периодически (контролируется `scan_offset` — секундами между сканированиями). Оно запрашивает таблицу `watch_folders` для активных папок, в которых `last_run` превышено смещение. +2. **Задание Cron `cron:watch`** выполняется периодически (контролируется с помощью `scan_offset` — секундного интервала между сканированиями). Оно запрашивает таблицу `watch_folders` для активных папок, где `last_run` превысило смещение. 3. **Обнаружение файлов** — cron использует `find` для локальных каталогов или `rclone lsjson` для облачных/удаленных подключений. Файлы фильтруются по разрешенным расширениям (по умолчанию: `mp4, mkv, avi, mpg, flv, 3gp, m4v, wmv, mov, ts`). Файлы, уже присутствующие в `streams.stream_source`, пропускаются. 4. **Проверка стабильности ** — файлы, измененные менее 30 секунд назад, пропускаются (чтобы избежать импорта частично загруженных файлов). -5. **Параллельная обработка ** — каждый новый файл отправляется команде `watch_item` (через `shell_exec`), параллельно обрабатывая до `thread_count` элементов с помощью `Multithread`. -6. **WatchItem** анализирует имя файла с помощью PTN или guessit (смотрите документацию по синтаксическому анализу ниже), преобразует метаданные с помощью TMDB API и вставляет запись в `streams` (для фильмов) или `streams_series` + `streams_episodes` ( для сериалов). +5. **Параллельная обработка** — каждый новый файл отправляется команде `watch_item` (через `shell_exec`), которая выполняет до `thread_count` элементов параллельно, используя `Multithread`. +6. **WatchItem** анализирует имя файла с помощью PTN или guessit (смотрите документацию по синтаксическому анализу ниже), преобразует метаданные с помощью TMDB API и вставляет запись в `streams` (для фильмов) или `streams_series` + `streams_episodes` (для сериалов). 7. **Назначение букета** — импортированные элементы автоматически добавляются в настроенные букеты. --- @@ -65,7 +65,7 @@ Watch Folder - это автоматизированная система имп | `auto_encode` |Автоматическое кодирование импортированного содержимого| | `auto_upgrade` |Замените существующую версию более низкого качества, если идентификатор TMDB совпадает| | `duplicate_tmdb` |Разрешить многократный импорт с одним и тем же идентификатором TMDB| -| `ffprobe_input` |Запустите ffprobe в исходном файле, чтобы извлечь метаданные кодека| +| `ffprobe_input` |Запустите ffprobe в исходном файле для извлечения метаданных кодека| | `extract_metadata` |Извлечение дополнительных метаданных из файла| ### Глобальные настройки @@ -124,10 +124,10 @@ sudo -u xc_vm /home/xc_vm/console.php cron:watch 5 |Синтаксический анализатор|Лучше всего для| |--------|----------| -|**PTN (Почтовый индекс)**|Простые имена файлов с пробелами: `San Andreas 2015 720p.mkv`| -|** угадать**|Имена файлов, разделенные точками: `The.Matrix.1999.1080p.BluRay.mkv`| +|**PTN (Почтовый индекс)**| Simple filenames with spaces: `San Andreas 2015 720p.mkv` | +|** угадать**| Dot-separated filenames: `The.Matrix.1999.1080p.BluRay.mkv` | -Установите основной синтаксический анализатор для каждой папки просмотра. Параметр `fallback_parser` global используется, когда основной синтаксический анализатор не возвращает совпадений. +Установите основной синтаксический анализатор для каждой папки просмотра. Глобальный параметр `fallback_parser` используется, когда основной синтаксический анализатор не возвращает совпадений. --- diff --git a/tools/docs/glossary.txt b/tools/docs/glossary.txt index 2255109d..73014568 100644 --- a/tools/docs/glossary.txt +++ b/tools/docs/glossary.txt @@ -25,6 +25,11 @@ nginx Redis MariaDB MySQL +KeyDB +yt-dlp +Ubuntu +iptables +MAGSCAN PHP PHP-FPM Composer diff --git a/tools/docs/translate.py b/tools/docs/translate.py index 924257b1..071cc056 100644 --- a/tools/docs/translate.py +++ b/tools/docs/translate.py @@ -38,7 +38,7 @@ import time from pathlib import Path # Bumped when the translation prompt/rules change, to invalidate the cache. -PROMPT_VERSION = "1" +PROMPT_VERSION = "4" LANG_NAMES = { "ru": "Russian", @@ -148,10 +148,14 @@ def provider_deepl(text: str, lang: str, glossary: list[str]) -> str: # The `translators` library (UlionTse/translators) drives free web engines # (yandex/google/bing/...). They translate PLAIN TEXT and will happily mangle # Markdown, so we translate line-by-line and mask everything that must survive -# verbatim (code, URLs, HTML tags, glossary terms) behind @@N@@ sentinels — which -# the engines pass through unchanged — then restore them afterwards. +# verbatim (code, URLs, HTML tags, glossary terms) behind {N} sentinels, then +# restore them afterwards. Curly {N} is what MT engines are trained to preserve +# (software format strings), so it survives code-heavy lines far better than +# @@N@@ / ZZZ…ZZZ / 〔N〕 did. A trailing English possessive (`XC_VM's`) is +# consumed INTO the masked span and dropped on restore (Russian has no `'s`). -_TS_SENTINEL = re.compile(r"@@(\d+)@@") +_TS_SENTINEL = re.compile(r"\{(\d+)\}") +_POSS = r"(?:['’]s|['’])?" # optional trailing possessive, dropped on restore _HR = re.compile(r"^\s*([-*_])( *\1){2,}\s*$") # thematic break --- _TABLE_SEP = re.compile(r"^\s*\|?[\s:|-]+\|?\s*$") # |---|:--:| row _HEADING = re.compile(r"^(\s{0,3}#{1,6}\s+)(.*)$") @@ -162,17 +166,21 @@ _QUOTE = re.compile(r"^(\s*>+\s*)(.*)$") def _protect_inline(text: str, glossary: list[str]) -> tuple[str, list[str]]: store: list[str] = [] - def keep(m: "re.Match[str]") -> str: + def keep(m: "re.Match[str]") -> str: # store the whole match store.append(m.group(0)) - return f"@@{len(store) - 1}@@" + return f"{{{len(store) - 1}}}" - text = re.sub(r"`[^`]*`", keep, text) # inline code + def keep1(m: "re.Match[str]") -> str: # store group 1 (drops possessive) + store.append(m.group(1)) + return f"{{{len(store) - 1}}}" + + text = re.sub(rf"(`[^`]*`){_POSS}", keep1, text) # inline code (+poss) text = re.sub(r"!\[[^\]]*\]\([^)]*\)", keep, text) # images (whole) text = re.sub(r"(?<=\])\([^)]*\)", keep, text) # link target (url) text = re.sub(r"https?://[^\s)]+", keep, text) # bare URLs text = re.sub(r"]*>", keep, text) # HTML tags for term in glossary: - text = re.sub(rf"(? str: def _ts_span(text, tr, glossary, memo): - """Translate one run of prose, masking inline non-translatables.""" + """Translate one run of prose, masking inline non-translatables. + + An engine can still occasionally mangle a `{N}` sentinel (split/duplicate a + brace) non-deterministically. Every brace we insert must be gone after + restore, so a clean result has exactly as many `{`/`}` as the source span. If + a fragment survives, we retry (a fresh translation usually comes back clean); + after a few failures we keep the English span rather than emit broken tokens. + """ if not text.strip(): return text if text in memo: @@ -190,7 +205,15 @@ def _ts_span(text, tr, glossary, memo): if not _TS_SENTINEL.sub("", protected).strip(): out = text # nothing left to translate (all masked) else: - out = _restore(tr(protected), store) + base_braces = text.count("{") + text.count("}") + out = None + for _ in range(4): + cand = _restore(tr(protected), store) + if cand.count("{") + cand.count("}") <= base_braces: # no leftovers + out = cand + break + if out is None: + out = text # give up: keep English, never emit a broken sentinel memo[text] = out return out