# XC_VM — План миграции
> Этот документ описывает **порядок миграции**, **детали каждой фазы** и **стратегию управления рисками**.
> Архитектурные принципы, структура проекта и описание компонентов — см. [ARCHITECTURE.md](ARCHITECTURE.md).
## Содержание
1. [Принцип миграции](#1-принцип-миграции)
2. [Фазы 0–9: Завершённые](#2-фазы-09-завершённые)
3. [Фаза 10: Удаление legacy admin entry-points ✅](#3-фаза-10-удаление-legacy-admin-entry-points)
4. [Фаза 11: Унификация API ✅ (11.1–11.4)](#4-фаза-11-унификация-api)
5. [Фаза 12: CLI — единый runner ✅](#5-фаза-12-cli--единый-runner)
6. [Фаза 13: Streaming entry points ✅ (13.1–13.3)](#6-фаза-13-streaming-entry-points)
7. [Фаза 14: CSS/JS partials](#7-фаза-14-cssjs-partials)
8. [Фаза 15: Удаление includes/admin.php](#8-фаза-15-удаление-includesadminphp)
9. [Порядок выполнения и риск-матрица](#9-порядок-выполнения-и-риск-матрица)
10. [Стратегия миграции по рискам](#10-стратегия-миграции-по-рискам)
---
## 1. Принцип миграции
### Извлечение → делегирование → замена
Каждый шаг миграции следует одному паттерну:
```
1. Создать новый класс в целевой директории
2. Перенести в него методы из god-объекта
3. В старом файле оставить proxy-метод:
public static function oldMethod(...$args) {
return NewClass::method(...$args);
}
4. Зарегистрировать класс в autoloader
5. Проверить: система работает как раньше
6. (позже) Обновить вызывающий код → удалить proxy
```
Так каждый шаг безопасен и обратимо совместим.
---
## 2. Фазы 0–9: Завершённые
### Фаза 0: Подготовка ✅
Autoload, скелет директорий, bootstrap.php, ServiceContainer, разбиение constants.php → 7 core-файлов.
---
### Фаза 1: Извлечение core/ ✅
Database, Cache, Http/Request, Auth, Process, Util — все базовые компоненты извлечены из god-объектов.
### Фаза 1.7: Оставшиеся извлечения core/ ✅
Логирование, SystemInfo, BruteforceGuard, CurlClient, EventDispatcher, Authorization, Authenticator, ImageUtils — 8 шагов завершены.
---
### Фаза 2: Дедупликация CoreUtilities ↔ StreamingUtilities ✅
53 дублированных метода дедуплицированы: Redis/сигналы, трекинг подключений, справочные данные, init().
---
### Фаза 3: Извлечение domain/ — бизнес-логика ✅
12 доменных контекстов: Stream, Vod, Line, User, Device, Server, Bouquet, Epg, Settings/Ticket, Security, Auth, Playlist. Все entity/repository/service извлечены.
---
### Фаза 4: Извлечение streaming/ (hot path) ✅
streaming/Auth, Delivery, Codec, Protection/Health, StreamingBootstrap — лёгкий bootstrap для hot path.
---
### Фаза 5: Вынесение модулей ✅
6 модулей извлечены атомарно: plex, watch, tmdb, ministra, fingerprint/theft-detection/magscan. ModuleInterface + ModuleLoader. Thread/Multithread дедуплицированы в `core/Process/`.
- 🔲 `ministra/*.js` → modules/ministra/assets/ (JS-файлы портала — отложено до Фазы 13.4)
---
### Фаза 6: Контроллеры и Views (admin/reseller) ✅
#### Шаг 6.1 — Единый layout ✅
Unified wrappers: `public/Views/layouts/admin.php` + `footer.php`.
- **Admin: 112/112 page-файлов — 100% мигрированы**
- **Reseller: 22/22 page-файлов — 100% мигрированы**
- ⏭️ CSS/JS partials — **отложено** до Фазы 14 (footer.php: ~800 стр. page-specific inline JS)
#### Шаг 6.2 — Router + Front Controller ✅
`core/Http/Router.php` (450 стр.), `public/index.php` (Front Controller), `Request.php`, `Response.php`, `RequestGuard.php`. Трёхрежимный URL-парсинг (Access Code + XC_SCOPE / Direct URL / fallback). Access Codes поддержаны.
#### Шаг 6.3 — Конвертация admin-страниц (Controller/View) ✅
**111/111 admin-страниц** мигрированы: Controller + View + Scripts + routes. Паттерн: Thin Controller → Service → View. `BaseAdminController` + `_scripts_init.php`.
#### Шаг 6.4 — Объединение admin/reseller ✅
22 reseller-страницы мигрированы. `BaseResellerController` + 22 контроллера/view/маршрута.
#### Шаг 6.5 — Стабилизация Controller/View контракта ✅
Два прохода стабилизации: viewGlobals расширен, nullable-guards для foreach/in_array/count.
#### Шаг 6.5b — Reseller view nullable audit ✅
Source-level fixes: `getPermissions()` → `[]` fallback, defensive defaults в `functions.php`/`table.php` для `direct_reports`, `all_reports`, `stream_ids`, `category_ids`, `series_ids`, `subresellers`. P0/P1 точечные исправления в 10 файлах.
---
### Фаза 7: Миграция admin.php bootstrap
#### Шаг 7.1 — Вынос inline-данных ✅
Данные bootstrap консолидированы в `resources/data/admin_constants.php`.
#### Шаг 7.2 — Замена процедурного bootstrap ✅
8 инкрементов: runtime → `bootstrapAdminRuntime()` → `admin_session.php` + `admin_runtime.php` → `XC_Bootstrap::boot(CONTEXT_ADMIN)` → фасад `admin_bootstrap.php`.
#### Шаг 7.3 — Удаление proxy-обёрток из admin.php ✅
40 proxy-определений удалены, 560+ call-sites заменены на прямые вызовы domain-сервисов. `admin.php` сокращён с ~4448 до ~3050 строк.
#### Шаг 7.3.1 — Миграция getCategories/getOutputs ✅
`getCategories()` → `CategoryService::getAllByType()` (~75+ call-sites). `getOutputs()` → `OutputFormatRepository::getAll()` (3 call-sites). Создан `domain/Line/OutputFormatRepository.php`.
#### Шаг 7.4 — Устранение параметра `$db` из Domain-классов ✅
28 классов (100 методов) → `global $db` внутри. ~357 call-sites обновлены.
---
### Фаза 8: Ликвидация god-объектов ✅
**Цель:** Удалить три файла-монолита (`CoreUtilities.php`, `StreamingUtilities.php`, `admin_api.php`), заменив ~7 400 внешних вызовов на прямые обращения к целевым классам.
| Файл | Было строк | Методов | Внешних вызовов | Статус |
|------|:----------:|:-------:|:---------------:|--------|
| `admin_api.php` | 3 686 | 79 | ~300 | ✅ 8.1 — удалён |
| `StreamingUtilities.php` | 659 | 78 | 1 344 | ✅ 8.2 — удалён |
| `CoreUtilities.php` | 1 971 | 152 | 5 755 | ✅ 8.3–8.11 — удалён |
#### Шаг 8.1 — admin_api.php ✅
60 PROXY + 18 OWN методов мигрированы в domain-сервисы. Файл удалён.
#### Шаг 8.2 — StreamingUtilities.php ✅
42 PROXY + 33 OWN методов, 18 свойств. ~314 ссылок заменены в 10 батчах. Файл удалён.
#### Шаг 8.3 — CoreUtilities методы ✅
81 PROXY + 69 OWN методов извлечены в целевые классы (17 батчей). CU сокращён с 1 971 до 40 строк.
#### Шаги 8.4–8.6 — Свойства (blocklist, FFmpeg, light) ✅
- **8.4** 7 blocklist/allowlist свойств → `BlocklistService` lazy getters (FileCache)
- **8.5** 3 FFmpeg свойства → value-object `FfmpegPaths::resolve()`
- **8.6** 3 свойства ($rCategories, $rBouquets, $rSegmentSettings) → сервисные геттеры
#### Шаги 8.7–8.10 — Инфраструктурные свойства → singletons ✅
- **8.7** `$rCached` → `FileCache`, `$rConfig` → `ConfigReader`, `$rServers` (184 refs) → `ServerRepository::getAll()`
- **8.8** `$db` (30 refs) → `DatabaseFactory::set()/get()`, `$redis` (62 refs) → `RedisManager::instance()`
- **8.9** `$rSettings` (819 refs, 221 файл) → `SettingsManager::set()/getAll()/get()/update()`
- **8.10** `$rRequest` (3863 refs, 166 файлов) → `RequestManager::set()/getAll()/get()/update()`
#### Шаг 8.11 — Удаление CoreUtilities.php ✅
6 call-sites `CoreUtilities::init()` → `LegacyInitializer::initCore()`. Файл физически удалён.
**Эволюция CU:**
| Фаза | Строк | Методов | Свойств |
|------|------:|--------:|--------:|
| До рефакторинга | 1 971 | 152 | 21 |
| После 8.3 | 40 | 3 | 21 |
| После 8.4–8.6 | 19 | 2 | 8 |
| После 8.7–8.8 | 9 | 1 | 2 |
| После 8.9–8.10 | 5 | 1 | 0 |
| **8.11 — УДАЛЁН** | **0** | **0** | **0** |
**Новые singleton/service классы (Phase 8):** `SettingsManager`, `RequestManager`, `ConfigReader`, `DatabaseFactory` (singleton), `RedisManager` (singleton), `FfmpegPaths`, `FileCache`, `DataEncryptor`, `InputSanitizer`, `IpUtils`, `UrlBuilder`, `ImageUtils`, `Helpers`, `ProcessManager`, `ConnectionManager`, `BackupService`, `ProviderService`, `ProfileService`, `RadioService`, `SystemCheck`, `InputValidator`.
---
### Фаза 9: Стабилизация сборки ✅
> **Цель фазы:** после массовых рефакторингов (Phases 7–8) довести проект до состояния «зелёной» сборки: корректный Makefile, стандартизированные PHP-заголовки, унифицированный layout, экспорт глобалов.
#### Шаг 9.1 — Makefile: LB-сборка для новой архитектуры ✅
**Проблема:** `LB_FILES` не включал `core/`, `domain/`, `streaming/`, `infrastructure/`, `resources/`, `autoload.php`, `bootstrap.php` — LB-сборка не могла работать с мигрированным кодом.
**Решение (5 правок в Makefile):**
1. `LB_FILES` → `LB_DIRS` (14 dirs) + `LB_ROOT_FILES` (5 root files)
2. `lb_copy_files`: второй цикл для root-файлов через `git ls-files --error-unmatch`
3. `lb_update_copy_files`: каскадная проверка (dirs → root files) для delta-обновлений
4. `LB_DIRS_TO_REMOVE`: +6 admin-only исключений (`includes/bootstrap`, `domain/User`, `domain/Device`, `domain/Auth`, `resources/langs`, `resources/libs`)
5. `set_permissions`: `core/ domain/ streaming/ infrastructure/ resources/` → dirs:755, files:644; root PHP → 644
#### Шаг 9.2 — Стандартизация PHP-заголовков (Clean Headers) ✅
**Проблема:** Admin- и reseller-view-файлы начинались разнородно — невозможно безопасно включать как view-фрагменты из layout-контроллера.
**Решение:** Guard-условие `$__viewMode` (скрипт `tools/clean_headers.py`, 4 итерации):
```php
```
**Масштаб:** 112 admin-файлов + 22 reseller-файла.
#### Шаг 9.3 — Миграция view-layout: renderUnifiedLayoutHeader ✅
**Решение:** `renderUnifiedLayoutHeader($scope, $vars)` / `renderUnifiedLayoutFooter($scope, $vars)` — единая обёртка для подключения header/footer с извлечением 16 глобалов.
**Новые файлы:**
- `public/Views/layouts/admin.php`
- `public/Views/layouts/footer.php`
**Масштаб:** 112 admin + 22 reseller файла переведены.
#### Шаг 9.4 — Глобальные переменные: экспорт singleton-данных ✅
`LegacyInitializer::exportGlobals()` — экспортирует singleton-данные в `$GLOBALS` один раз при bootstrap.
**Экспортируемые переменные:** `$rSettings`, `$rRequest`, `$rConfig`, `$rServers`, `$rFFPROBE`, `$rFFMPEG_CPU`, `$rFFMPEG_GPU`, `$db` + streaming-контекст: `$rCached`, `$rBlockedUA/ISP/IPs/Servers`, `$rAllowedIPs`, `$rProxies`, `$rBouquets`, `$rSegmentSettings`.
**Масштаб:** 54 замены в 20 файлах.
---
## 3. Фаза 10: Удаление legacy admin entry-points ✅
> **Цель:** Все admin-запросы обслуживаются ТОЛЬКО через `public/index.php` (Front Controller → Router → Controller → View). Директория `src/admin/` удалена.
#### Шаг 10.1 — Устранение fallback в Front Controller ✅
**Что сделано:**
1. Созданы контроллеры для 7 ещё не маршрутизированных страниц:
- `LogoutController` — destroySession + redirect
- `PlayerEmbedController` — проксирует admin/player.php
- `PostController` — устанавливает `$GLOBALS['__forcePostMode']`, проксирует admin/post.php
- `LoginController` — проксирует admin/login.php (собственный HTML-документ)
- `SetupController` — методы `index()` (setup.php) и `database()` (database.php)
2. Добавлены 7 маршрутов в `public/routes/admin.php` (logout, player, post, login, setup, database, index)
3. Унифицирована секция 4a FC: все scope (admin/reseller/player) используют Router для noBootstrapPages
4. Секция 7 (legacy fallback) обёрнута feature flag `$rSettings['use_legacy_fallback']` (default: true)
5. Модифицирован `admin/post.php`: `$rICount` теперь учитывает `$GLOBALS['__forcePostMode']`
#### Шаг 10.2 — Миграция admin/login.php → LoginController ✅
LoginController создан как thin proxy: chdir(admin/) + require login.php. Маршруты `login` и `index` зарегистрированы.
#### Шаг 10.3 — Миграция admin/setup.php, database.php → SetupController ✅
SetupController: методы index() и database() проксируют в legacy-файлы. Маршруты зарегистрированы.
#### Шаг 10.4 — Миграция admin/api.php → AjaxController ✅
AjaxController — thin proxy: chdir(admin/) + require api.php. Маршрут `$router->any('api', ...)`. FC API-fallback обёрнут feature flag.
#### Шаг 10.5 — Консолидация session.php + functions.php ✅
**Создано:**
- `infrastructure/bootstrap/admin_session_fc.php` — clean-версия admin/session.php для FC-пути
- `infrastructure/bootstrap/admin_functions_fc.php` — clean-версия admin/functions.php
FC переключён на `infrastructure/bootstrap/admin_*_fc.php`. Оригиналы оставлены для direct nginx access.
#### Шаг 10.6 — Удаление директории src/admin/ ✅
**Что сделано:**
1. 127 PHP-файлов перемещены из `admin/` → `public/Views/admin/`
2. 423 статических файла (CSS, fonts, images, JS, libs) перемещены из `admin/assets/` → `public/assets/admin/`
3. Директория `src/admin/` полностью удалена
4. Обновлены все ссылки:
- **FC** (`public/index.php`): `$adminDir = MAIN_HOME . 'public/Views/admin/'`
- **5 контроллеров**: LoginController, SetupController, PostController, PlayerEmbedController, AjaxController — пути к `public/Views/admin/`
- **2 layout-файла**: `dirname(__DIR__) . '/admin/header.php'` / `footer.php`
- **BaseAdminController**: `$__viewMode = true` перед view require
- **112 view-файлов**: `__DIR__ . '/../layouts/'` (исправлены относительные пути)
- **8 view-файлов**: `dirname(__DIR__, 3) . '/modules/'` (исправлена глубина)
- **AuthRepository.php**: `$rAlias` admin → `'public/Views/admin'`
- **Makefile**: permissions `$(TEMP_DIR)/public/assets/admin`
5. nginx template (`bin/nginx/conf/codes/template`) уже корректный: `alias /home/xc_vm/public/assets/#TYPE#/`
6. Syntax check: 126/127 pass (review.php — pre-existing bug, не регрессия)
---
## 4. Фаза 11: Унификация API — внешние и внутренние ✅ (11.1–11.4)
> **Цель:** Единый API-слой через `public/Controllers/Api/` с Router-маршрутизацией. Удаление самостоятельных PHP-файлов в `src/www/` (`player_api.php`, `enigma2.php`, `epg.php`, `playlist.php`, `xplugin.php`).
#### Шаг 11.1 — Player API → PlayerApiController ✅
**Результат:** `public/Controllers/Api/PlayerApiController.php` (843 строки, 10 actions). Bootstrap через `stream/init.php` (hot path). Auth через `UserRepository::getStreamingUserInfo()`. Standalone — НЕ наследует BaseApiController.
`www/player_api.php` → thin proxy (7 строк).
#### Шаг 11.2 — Enigma2 API → Enigma2ApiController + XPluginApiController ✅
**Результат:** Разделены (вместо объединения по плану) — разные auth, output format, domain:
- `Enigma2ApiController.php` (507 строк, 10 XML-actions)
- `XPluginApiController.php` (181 строка, device management)
- `SimpleXMLExtended` извлечён в отдельный класс (был inline в switch/default — баг)
`www/enigma2.php` и `www/xplugin.php` → thin proxies (7 строк каждый).
#### Шаг 11.3 — Playlist/EPG → PlaylistApiController, EpgApiController ✅
**Результат:**
- `PlaylistApiController.php` (62 строки) — M3U/M3U8 генерация
- `EpgApiController.php` (105 строк) — XMLTV XML генерация
- `BaseApiController.php` (109 строк) — общий базовый класс
`www/playlist.php` и `www/epg.php` → thin proxies.
#### Шаг 11.4 — Internal Server API → InternalApiController ✅
**Результат:** `InternalApiController.php` (506 строк, 30+ actions). Auth: password + IP whitelist (server-to-server). Извлечены: `serveFile()`, `probeStream()`, `killProcessGroup()`.
`www/api.php` → thin proxy.
**Статистика:** 7 controller-файлов = 2313 строк. 6 proxy-файлов = 42 строки.
#### Шаг 11.5 — Admin/Reseller REST API → единые маршруты ⏳
**Статус:** ОТЛОЖЕНО. `APIWrapper` классы встроены в `index.php` (2065+556 строк, одинаковое имя класса). Требуется: переименование классов, извлечение, разрешение конфликта namespace. Отдельный PR.
#### Шаг 11.6 — Удаление legacy API файлов ⏳
**Статус:** ОТЛОЖЕНО. Требуется FC API routing (scope=api). `www/*.php` thin proxies должны оставаться до маршрутизации API через Front Controller.
**Предусловия:** 11.5 завершён, nginx rewrites обновлены.
---
## 5. Фаза 12: CLI — единый runner и структурированные команды ✅
> **Цель:** Все CLI-скрипты (`includes/cli/`, `crons/`, `src/service`, `src/tools`, `src/status`) запускаются через единую точку входа `console.php`. Каждый скрипт — класс-команда с интерфейсом `CommandInterface`.
#### Шаг 12.1 — Console entry point + CommandInterface ✅
**Создано:**
```
cli/
├── CommandInterface.php # interface: getName(), getDescription(), execute(array $args): int
├── CommandRegistry.php # Реестр команд (name → class)
├── DaemonTrait.php # Общий boilerplate для демонов
├── Commands/ # 26 команд
└── CronJobs/ # 25 cron-задач
console.php # Единая точка входа (root-level)
```
**Запуск:**
```bash
# Было:
php /home/xc_vm/includes/cli/startup.php
php /home/xc_vm/crons/servers.php
# Стало:
php /home/xc_vm/console.php startup
php /home/xc_vm/console.php cron:servers
```
#### Шаг 12.2 — Миграция includes/cli/ → cli/Commands/ ✅
**Результат:** 24 файла из `includes/cli/` → 26 Command-классов в `cli/Commands/`. Вся логика перенесена, proxy-файлы были созданы, затем удалены (шаг 12.6).
| Legacy файл | → Command | Тип |
|---|---|---|
| `startup.php` | `Commands/StartupCommand` | daemon |
| `watchdog.php` | `Commands/WatchdogCommand` | daemon |
| `signals.php` | `Commands/SignalsCommand` | daemon |
| `queue.php` | `Commands/QueueCommand` | daemon |
| `cache_handler.php` | `Commands/CacheHandlerCommand` | daemon |
| `connection_sync.php` | `Commands/ConnectionSyncCommand` | daemon |
| `monitor.php` | `Commands/MonitorCommand` | CLI |
| `scanner.php` | `Commands/ScannerCommand` | CLI |
| `balancer.php` | `Commands/BalancerCommand` | CLI |
| `migrate.php` | `Commands/MigrateCommand` | CLI |
| `tools.php` | `Commands/ToolsCommand` | CLI |
| `update.php` | `Commands/UpdateCommand` | CLI |
| `binaries.php` | `Commands/BinariesCommand` | CLI |
| `archive.php` | `Commands/ArchiveCommand` | CLI |
| `created.php` | `Commands/CreatedCommand` | CLI |
| `delay.php` | `Commands/DelayCommand` | CLI |
| `loopback.php` | `Commands/LoopbackCommand` | CLI |
| `llod.php` | `Commands/LlodCommand` | CLI |
| `ondemand.php` | `Commands/OndemandCommand` | CLI |
| `plex_item.php` | `Commands/PlexItemCommand` | CLI |
| `proxy.php` | `Commands/ProxyCommand` | CLI |
| `record.php` | `Commands/RecordCommand` | CLI |
| `thumbnail.php` | `Commands/ThumbnailCommand` | CLI |
| `watch_item.php` | `Commands/WatchItemCommand` | CLI |
| — | `Commands/CertbotCommand` | CLI |
| — | `Commands/ServiceCommand` | CLI |
#### Шаг 12.3 — Миграция crons/ → cli/CronJobs/ ✅
**Результат:** 25 cron-файлов → 25 CronJob-классов в `cli/CronJobs/`. Proxy-файлы и директория `crons/` удалены (шаг 12.8).
| Legacy файл | → CronJob | Заметки |
|---|---|---|
| `activity.php` | `CronJobs/ActivityCronJob` | |
| `backups.php` | `CronJobs/BackupsCronJob` | |
| `cache.php` | `CronJobs/CacheCronJob` | |
| `cache_engine.php` | `CronJobs/CacheEngineCronJob` | |
| `certbot.php` | `CronJobs/CertbotCronJob` | |
| `cleanup.php` | `CronJobs/CleanupCronJob` | |
| `epg.php` | `CronJobs/EpgCronJob` | Крупный: EPG-класс + парсинг XML |
| `errors.php` | `CronJobs/ErrorsCronJob` | |
| `lines_logs.php` | `CronJobs/LinesLogsCronJob` | |
| `plex.php` | `CronJobs/PlexCronJob` | Модуль plex |
| `providers.php` | `CronJobs/ProvidersCronJob` | |
| `root_mysql.php` | `CronJobs/RootMysqlCronJob` | Требует root, LB-excluded |
| `root_signals.php` | `CronJobs/RootSignalsCronJob` | Требует root |
| `series.php` | `CronJobs/SeriesCronJob` | |
| `servers.php` | `CronJobs/ServersCronJob` | |
| `stats.php` | `CronJobs/StatsCronJob` | |
| `streams.php` | `CronJobs/StreamsCronJob` | |
| `streams_logs.php` | `CronJobs/StreamsLogsCronJob` | |
| `tmdb.php` | `CronJobs/TmdbCronJob` | Модуль tmdb |
| `tmdb_popular.php` | `CronJobs/TmdbPopularCronJob` | Модуль tmdb |
| `tmp.php` | `CronJobs/TmpCronJob` | |
| `update.php` | `CronJobs/UpdateCronJob` | |
| `users.php` | `CronJobs/UsersCronJob` | |
| `vod.php` | `CronJobs/VodCronJob` | |
| `watch.php` | `CronJobs/WatchCronJob` | Модуль watch |
#### Шаг 12.4 — Миграция root-level скриптов ✅
| Legacy | → | Статус |
|---|---|---|
| `src/service` (shell) | `console.php service:{start\|stop\|restart\|reload}` | ✅ ServiceCommand |
| `src/status` (PHP) | `console.php status` | ✅ StatusCommand |
| `src/tools` (PHP) | `console.php tools:{rescue\|access\|ports}` | ✅ ToolsCommand |
| `src/update` (Python) | Остаётся Python-скриптом | Без изменений |
#### Шаг 12.5 — Обновление daemons.sh и crontab ✅
1. ✅ `bin/daemons.sh` → запуск через `console.php {command}`
2. ✅ Crontab генерируется `StartupCommand::installCrontab()` / `installRootCrontab()` → `console.php cron:{name}`
3. ✅ `src/service` → `console.php service:{start|stop|restart|reload}`
#### Шаг 12.6 — Удаление legacy CLI файлов ✅
**Выполнено:**
1. ✅ `CLI_PATH` constant удалён из `core/Config/Paths.php`
2. ✅ 37 ссылок на `CLI_PATH` заменены на `MAIN_HOME . 'console.php {command}'` в 14 файлах
3. ✅ `includes/cli/` — **папка удалена** (24 файла)
4. ✅ Makefile: 3 старые записи `includes/cli/` удалены из EXCLUDES
5. ✅ `php -l` — все 14 файлов проверены, синтаксис ОК
6. ✅ `crons/` — **папка удалена** (25 proxy-файлов), см. шаг 12.8
**Паттерн замены:**
```
# Было:
PHP_BIN . ' ' . CLI_PATH . 'script.php args'
# Стало:
PHP_BIN . ' ' . MAIN_HOME . 'console.php command args'
```
**Особые случаи:**
- `OndemandCommand`: `md5_file(CLI_PATH . 'ondemand.php')` → `md5_file(__FILE__)` (self-change detection)
- `BalancerCommand`: обе ветки `$rType == 2` → `console.php startup` (was using different legacy paths)
#### Шаг 12.8 — Удаление CRON_PATH и crons/ директории ✅
**Проблема:** 25 proxy-файлов в `crons/` и константа `CRON_PATH` — legacy остатки после миграции cron → `console.php cron:{name}`. 34+ ссылки на `CRON_PATH` в кодовой базе.
**Выполнено:**
1. ✅ 35 ссылок на `CRON_PATH` заменены на `MAIN_HOME . 'console.php cron:{name}'` в 9 файлах
2. ✅ Константа `CRON_PATH` удалена из `core/Config/Paths.php`
3. ✅ Мёртвый autoload-маппинг `EPG → crons/epg.php` удалён
4. ✅ Класс `EPG` восстановлен в `domain/Epg/EPG.php` (был потерян при proxy-конвертации)
5. ✅ `src/crons/` — **папка удалена** (25 proxy-файлов)
6. ✅ Makefile: `crons` убран из `LB_DIRS`, 9 записей `crons/*.php` из `LB_FILES_TO_REMOVE`, 2 строки permissions
7. ✅ 8 CronJob-файлов (MAIN-only) добавлены в `LB_FILES_TO_REMOVE` + `file_exists()` guards в `console.php`
8. ✅ `MigrationRunner::runFileCleanup()` — теперь удаляет опустевшие директории
9. ✅ Legacy-путь в `post.php` (`MAIN_HOME . '/php/bin/php ' . MAIN_HOME . '/crons/epg.php'`) исправлен
**Паттерн замены:**
```
# Было:
PHP_BIN . ' ' . CRON_PATH . 'name.php args'
# Стало:
PHP_BIN . ' ' . MAIN_HOME . 'console.php cron:name args'
```
**Затронутые файлы:**
- `cli/CronJobs/CacheEngineCronJob.php` (9 замен)
- `cli/Commands/StartupCommand.php` (4 замены, вкл. `file_exists()` → проверка `cache_complete`)
- `cli/Commands/SignalsCommand.php` (2 замены)
- `cli/Commands/BalancerCommand.php` (1 замена)
- `cli/Commands/CertbotCommand.php` (1 замена)
- `public/Controllers/Api/InternalApiController.php` (4 замены)
- `public/Controllers/Api/AdminApiController.php` (5 замен)
- `public/Views/admin/api.php` (8 замен)
- `public/Views/admin/post.php` (1 замена)
#### Шаг 12.7 — LB guard для MAIN-only команд ✅
**Проблема:** 4 CLI-класса исключены из LB-сборки через `LB_FILES_TO_REMOVE`, но `console.php` регистрировал их безусловно → crash `require_once` на LB.
**Решение:** `file_exists()` guards в `console.php` для условной регистрации.
| Файл | Guard в console.php | Guard в crontab |
|---|---|---|
| `cli/Commands/MigrateCommand.php` | ✅ `file_exists()` | — (не в crontab) |
| `cli/Commands/CacheHandlerCommand.php` | ✅ `file_exists()` | — (не в crontab) |
| `cli/Commands/BalancerCommand.php` | ✅ `file_exists()` | — (не в crontab) |
| `cli/CronJobs/RootMysqlCronJob.php` | ✅ `file_exists()` | ✅ `file_exists()` в StartupCommand + StatusCommand |
| `cli/CronJobs/BackupsCronJob.php` | ✅ `file_exists()` | — |
| `cli/CronJobs/CacheEngineCronJob.php` | ✅ `file_exists()` | — |
| `cli/CronJobs/EpgCronJob.php` | ✅ `file_exists()` | — |
| `cli/CronJobs/UpdateCronJob.php` | ✅ `file_exists()` | — |
| `cli/CronJobs/ProvidersCronJob.php` | ✅ `file_exists()` | — |
| `cli/CronJobs/SeriesCronJob.php` | ✅ `file_exists()` | — |
| `modules/tmdb/TmdbCronJob.php` | ✅ auto-discovery | Moved to module dir |
| `modules/tmdb/TmdbPopularCronJob.php` | ✅ auto-discovery | Moved to module dir |
| `domain/Epg/EPG.php` | — | — (autoload) |
**Makefile `LB_FILES_TO_REMOVE`:**
```makefile
cli/Commands/MigrateCommand.php
cli/Commands/CacheHandlerCommand.php
cli/Commands/BalancerCommand.php
cli/CronJobs/RootMysqlCronJob.php
cli/CronJobs/BackupsCronJob.php
cli/CronJobs/CacheEngineCronJob.php
cli/CronJobs/EpgCronJob.php
cli/CronJobs/UpdateCronJob.php
cli/CronJobs/ProvidersCronJob.php
cli/CronJobs/SeriesCronJob.php
domain/Epg/EPG.php
```
---
## 6. Фаза 13: Streaming entry points ✅ (13.1–13.3)
> **Цель:** Минимизировать дублирование bootstrap в `www/stream/*.php`, НЕ увеличивая latency. Hot path остаётся < 50ms p99.
> **ВАЖНО:** Streaming — священная территория. Никакого Router, никакого ServiceContainer, никакого полного autoload в hot path. Только точечные улучшения.
#### Шаг 13.1 — Единый streaming entry point (micro-router) ✅
**Результат:** `www/stream/index.php` — micro-router с `?handler=` параметром. Dormant-режим: nginx по-прежнему направляет на конкретные файлы, router активируется опционально.
Overhead < 0.1ms (один array lookup).
#### Шаг 13.2 — Дедупликация shutdown handlers ✅
**Результат:** `streaming/Lifecycle/ShutdownHandler.php` — заменяет дублированные `function shutdown()` в live/vod/timeshift. Context-параметр для live-специфичной очистки (tmp files, on_demand queue).
`auth.php` и `rtmp.php` сохраняют свои shutdown handlers (BruteforceGuard pattern).
#### Шаг 13.3 — Извлечение streaming auth middleware ✅
**Результат:** `streaming/Auth/StreamAuthMiddleware.php` — `sendStreamHeaders()` + `decryptToken()`. Извлекает ~35 дублированных строк из каждого streaming-файла.
**Изменённые файлы:**
- `www/stream/live.php` — ShutdownHandler + StreamAuthMiddleware
- `www/stream/vod.php` — аналогично
- `www/stream/timeshift.php` — аналогично
#### Шаг 13.4 — Ministra JS → modules/ministra/assets/ ⏳
**Статус:** ОТЛОЖЕНО. `www/c` — симлинк на `ministra/` (создаётся `RootSignalsCronJob::enable_ministra`). Перемещение JS ломает симлинк, требует nginx alias + изменения signal handler. Низкий benefit, высокий cost.
---
## 7. Фаза 14: CSS/JS partials — разбиение footer.php
> **Цель:** Вынести ~800 строк page-specific inline JS из `admin/footer.php` в отдельные файлы. footer.php остаётся < 100 строк (layout-only).
#### Шаг 14.1 — Аудит inline JS в footer.php
1. Список всех `
// Стало:
// public/assets/js/pages/streams.js — отдельный файл
// footer.php:
```
**Действия:**
1. Для каждого page-блока → создать `public/assets/js/pages/{page}.js`
2. В footer.php: динамический `