diff --git a/docs/en/development/cli-tools.md b/docs/en/development/cli-tools.md index 725419a6..fd875aa8 100644 --- a/docs/en/development/cli-tools.md +++ b/docs/en/development/cli-tools.md @@ -28,7 +28,7 @@ The console supports three types of commands: | Type | Count | Description | | --- | --- | --- | -| **Commands** | 26 | One-time operations (update, status, tools, etc.) | +| **Commands** | 27 | One-time operations (update, status, tools, etc.) | | **CronJobs** | 25 | Scheduled tasks (auto-invoked by crontab) | | **Daemons** | 8 | Long-running background processes (Commands using `DaemonTrait`) | @@ -60,6 +60,7 @@ To see all available commands: | `plex_item` | `PlexItemCommand` | Process single Plex item (movie/series) | xc_vm | | `watch_item` | `WatchItemCommand` | Process single Watch item (TMDB search/update) | xc_vm | | `migrate` | `MigrateCommand` | Transfer data from `xc_vm_migrate` database | xc_vm | +| `db:migrate` | `DbMigrateCommand` | Apply pending database migrations from the `migrations/` directory | xc_vm | | `server:install` | `ServerInstallCommand` | Install/configure server (Proxy/LB) via SSH | root | > Commands marked **optional** are conditionally registered via `file_exists()` guard: `cache_handler`, `server:install`, `migrate`. @@ -417,7 +418,13 @@ Copy the SQL file for the DB update step to: ### Step 4. Validate DB Update -Run the status command to apply pending DB update steps: +Run `db:migrate` to apply pending DB update steps: + +```bash +su - xc_vm -c '/home/xc_vm/console.php db:migrate' +``` + +Or via `status first-run` (also runs migrations): ```bash sudo /home/xc_vm/console.php status first-run @@ -480,6 +487,14 @@ Starts a stream manually and displays any errors. Useful for diagnosing stream s sudo /home/xc_vm/console.php certbot ``` +### Apply Database Migrations Manually + +```bash +su - xc_vm -c '/home/xc_vm/console.php db:migrate' +``` + +Applies all pending `.sql` files from `/home/xc_vm/migrations/`. Use this when you need to run migrations without a full system update. + ### Database Update with Data from Other Systems ```bash diff --git a/docs/en/development/diagnostics-api.md b/docs/en/development/diagnostics-api.md index 19987b8f..e9b38158 100644 --- a/docs/en/development/diagnostics-api.md +++ b/docs/en/development/diagnostics-api.md @@ -59,13 +59,6 @@ Used for support/telemetry workflows. --- -### `getApiIP(): ?string` - -Attempts to resolve external/public IP via remote API request. -Returns `null` on network failure. - ---- - ## `panel_logs` Shape (used fields) | Column | Type | Meaning | diff --git a/docs/ru/development/cli-tools.md b/docs/ru/development/cli-tools.md index 6b9e088a..0e3bce10 100644 --- a/docs/ru/development/cli-tools.md +++ b/docs/ru/development/cli-tools.md @@ -28,7 +28,7 @@ | Тип | Кол-во | Описание | | --- | --- | --- | -| **Commands** | 26 | Разовые операции (update, status, tools и др.) | +| **Commands** | 27 | Разовые операции (update, status, tools и др.) | | **CronJobs** | 25 | Запланированные задачи (автозапуск через crontab) | | **Daemons** | 8 | Фоновые процессы-демоны (Commands, использующие `DaemonTrait`) | @@ -60,6 +60,7 @@ | `plex_item` | `PlexItemCommand` | Обработка элемента Plex (фильм/сериал) | xc_vm | | `watch_item` | `WatchItemCommand` | Обработка элемента Watch (поиск/обновление TMDB) | xc_vm | | `migrate` | `MigrateCommand` | Перенос данных из БД `xc_vm_migrate` | xc_vm | +| `db:migrate` | `DbMigrateCommand` | Применить ожидающие миграции БД из каталога `migrations/` | xc_vm | | `server:install` | `ServerInstallCommand` | Установка/настройка сервера (Proxy/LB) по SSH | root | > Команды, отмеченные как **опциональные**, условно регистрируются через `file_exists()` guard: `cache_handler`, `server:install`, `migrate`. @@ -417,7 +418,13 @@ WHERE NOT EXISTS (SELECT 1 FROM `streams_arguments` WHERE argument_key = 'my_key ### Шаг 4. Проверка обновления БД -Запустите команду status для применения ожидающих шагов обновления БД: +Запустите `db:migrate` для применения ожидающих шагов обновления БД: + +```bash +su - xc_vm -c '/home/xc_vm/console.php db:migrate' +``` + +Или через `status first-run` (также запускает миграции): ```bash sudo /home/xc_vm/console.php status first-run @@ -480,6 +487,14 @@ sudo -u xc_vm /home/xc_vm/console.php monitor sudo /home/xc_vm/console.php certbot ``` +### Применить миграции БД вручную + +```bash +su - xc_vm -c '/home/xc_vm/console.php db:migrate' +``` + +Применяет все ожидающие `.sql`-файлы из `/home/xc_vm/migrations/`. Используется когда нужно накатить миграции без полного обновления системы. + ### Обновление БД данными из других систем ```bash diff --git a/docs/ru/development/diagnostics-api.md b/docs/ru/development/diagnostics-api.md index f599d578..5eb8c3c9 100644 --- a/docs/ru/development/diagnostics-api.md +++ b/docs/ru/development/diagnostics-api.md @@ -114,18 +114,6 @@ if (DiagnosticsService::checkCompatibility($ffprobeData)) { Используется для автоматической отправки отчётов об ошибках в команду поддержки. ---- - -### `getApiIP(): ?string` - -Определяет внешний IP-адрес сервера через запрос к публичному API. - -```php -$externalIP = DiagnosticsService::getApiIP(); -// Возвращает IP-строку или null при недоступности сети -``` - -Используется при регистрации сервера и в диагностике сетевых проблем. --- diff --git a/src/bootstrap.php b/src/bootstrap.php index 983a7c6e..6e77a789 100644 --- a/src/bootstrap.php +++ b/src/bootstrap.php @@ -133,12 +133,9 @@ class XC_Bootstrap { // ── Common for all contexts ──────────────────────────── - // Constants and paths (MAIN_HOME, INCLUDES_PATH, CONFIG_PATH, ...) + $_INFO + Logger + error functions self::loadConstants(); - // Register config in the container - global $_INFO; - $container->set('config', $_INFO); + $container->set('config', ConfigReader::getAll()); // ── Flood-protection (HTTP only) ─────────────────────── if (!self::isCli()) { @@ -253,15 +250,14 @@ class XC_Bootstrap { // ───────────────────────────────────────────────────────── /** - * Load constants, paths, $_INFO, Logger, error functions. + * Load constants, paths, Logger, error functions. * * Loads core configuration directly (without www/constants.php): - * core/Error/ErrorCodes.php — $rErrorCodes - * core/Error/ErrorHandler.php — generateError(), generate404() - * core/Config/Paths.php — *_PATH constants - * core/Config/AppConfig.php — version, Git, flags - * core/Config/Binaries.php — FFMPEG, FFPROBE, GeoIP - * core/Config/ConfigLoader.php — $_INFO from config.ini + * core/Error/ErrorCodes.php — $rErrorCodes + * core/Error/ErrorHandler.php — generateError(), generate404() + * core/Config/Paths.php — *_PATH constants + * core/Config/AppConfig.php — version, Git, flags + * core/Config/Binaries.php — FFMPEG, FFPROBE, GeoIP */ private static function loadConstants(): void { if (self::$constantsLoaded) { @@ -273,7 +269,6 @@ class XC_Bootstrap { require_once MAIN_HOME . 'core/Config/Paths.php'; require_once MAIN_HOME . 'core/Config/AppConfig.php'; require_once MAIN_HOME . 'core/Config/Binaries.php'; - require_once MAIN_HOME . 'core/Config/ConfigLoader.php'; require_once MAIN_HOME . 'core/Logging/Logger.php'; if (!defined('PHP_ERRORS')) { @@ -286,7 +281,7 @@ class XC_Bootstrap { ); self::$constantsLoaded = true; - self::$configLoaded = true; // $_INFO loaded inside ConfigLoader.php + self::$configLoaded = true; self::$loggerStarted = true; } @@ -374,17 +369,11 @@ class XC_Bootstrap { return; } - global $db, $_INFO; + global $db; require_once MAIN_HOME . 'core/Database/DatabaseHandler.php'; - $db = new DatabaseHandler( - $_INFO['username'], - $_INFO['password'], - $_INFO['database'], - $_INFO['hostname'], - $_INFO['port'] - ); + $db = new DatabaseHandler(); self::$databaseReady = true; } @@ -412,14 +401,7 @@ class XC_Bootstrap { // If cache was used and is incomplete — reconnect to DB if ($cached && !SettingsManager::getAll()['enable_cache']) { - global $_INFO; - $db = new DatabaseHandler( - $_INFO['username'], - $_INFO['password'], - $_INFO['database'], - $_INFO['hostname'], - $_INFO['port'] - ); + $db = new DatabaseHandler(); DatabaseFactory::set($db); } diff --git a/src/cli/Commands/DbMigrateCommand.php b/src/cli/Commands/DbMigrateCommand.php new file mode 100644 index 00000000..8a32242c --- /dev/null +++ b/src/cli/Commands/DbMigrateCommand.php @@ -0,0 +1,30 @@ + + * @copyright 2025-2026 Vateron Media + * @link https://github.com/Vateron-Media/XC_VM + * @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html + */ + +class DbMigrateCommand implements CommandInterface { + + public function getName(): string { + return 'db:migrate'; + } + + public function getDescription(): string { + return 'Apply pending database migrations from the migrations/ directory'; + } + + public function execute(array $rArgs): int { + global $db; + + MigrationRunner::run($db); + + return 0; + } +} diff --git a/src/cli/Commands/LoopbackCommand.php b/src/cli/Commands/LoopbackCommand.php index 7467612d..e0220367 100644 --- a/src/cli/Commands/LoopbackCommand.php +++ b/src/cli/Commands/LoopbackCommand.php @@ -50,10 +50,6 @@ class LoopbackCommand implements CommandInterface { if (!defined('TIMEOUT')) define('TIMEOUT', 20); if (!defined('TIMEOUT_READ')) define('TIMEOUT_READ', 1); - if (!file_exists(CONFIG_PATH . 'config.ini')) { - echo "Config file missing!\n"; - return 0; - } if (!file_exists(CACHE_TMP_PATH . 'settings')) { echo "Settings not cached!\n"; return 0; @@ -63,8 +59,7 @@ class LoopbackCommand implements CommandInterface { return 0; } - $rConfig = parse_ini_file(CONFIG_PATH . 'config.ini'); - if (!defined('SERVER_ID')) define('SERVER_ID', intval($rConfig['server_id'])); + if (!defined('SERVER_ID')) define('SERVER_ID', intval(ConfigReader::get('server_id'))); $this->checkRunning($rStreamID); $rFP = null; diff --git a/src/cli/Commands/ServerInstallCommand.php b/src/cli/Commands/ServerInstallCommand.php index b7dbeeed..b88106b7 100644 --- a/src/cli/Commands/ServerInstallCommand.php +++ b/src/cli/Commands/ServerInstallCommand.php @@ -241,7 +241,7 @@ class ServerInstallCommand implements CommandInterface { call_user_func($rRunSSH, $rConn, 'sudo chown -R xc_vm:xc_vm ' . MAIN_HOME . 'tmp'); call_user_func($rRunSSH, $rConn, 'sudo chown -R xc_vm:xc_vm ' . MAIN_HOME . 'content/streams'); call_user_func($rRunSSH, $rConn, 'sudo chown -R xc_vm:xc_vm ' . MAIN_HOME); - BackupService::grantPrivileges($rHost, DatabaseFactory::get(), ConfigReader::getAll()); + BackupService::grantPrivileges($rHost); echo "Installation complete! Starting XC_VM\n"; call_user_func($rRunSSH, $rConn, 'sudo service xc_vm restart'); } diff --git a/src/cli/Commands/ToolsCommand.php b/src/cli/Commands/ToolsCommand.php index 110060bc..bd02cdca 100644 --- a/src/cli/Commands/ToolsCommand.php +++ b/src/cli/Commands/ToolsCommand.php @@ -103,7 +103,6 @@ class ToolsCommand implements CommandInterface { $db->query('DROP DATABASE IF EXISTS `xc_vm_migrate`;'); $db->query('CREATE DATABASE IF NOT EXISTS `xc_vm_migrate`;'); echo "Migration database has been cleared.\n"; - $rConfig = ConfigReader::getAll(); $database = (!empty($rArgs[1]) ? $rArgs[1] : null); @@ -111,22 +110,18 @@ class ToolsCommand implements CommandInterface { $rExtension = strtolower(pathinfo($database, PATHINFO_EXTENSION)); if ($rExtension === 'sql') { echo 'Restoring: ' . $database . "\n"; - $rCommand = 'mariadb -h 127.0.0.1 -P ' . intval($rConfig['port']) . ' -u ' . escapeshellarg($rConfig['username']) . ' -p' . escapeshellarg($rConfig['password']) . ' xc_vm_migrate < ' . escapeshellarg($database) . ' 2>&1'; - $rOutput = shell_exec($rCommand); - if (!empty($rOutput)) { - echo $rOutput; - } - echo "If no errors were shown above, restore was completed.\n\n"; + XC_VM::db_restore($database, 'xc_vm_migrate'); + echo "Restore started in the background.\n\n"; } else { echo "Error: File must have .sql extension\n"; } } else { echo "You can restore a database to it using:\n"; - echo 'mariadb -h 127.0.0.1 -P ' . intval($rConfig['port']) . ' -u ' . $rConfig['username'] . ' -p*** xc_vm_migrate < backup.sql' . "\n\n"; + echo " mariadb -h 127.0.0.1 -P -u -p'' xc_vm_migrate < backup.sql\n"; } foreach ($rServers as $rServer) { - BackupService::grantPrivileges($rServer['server_ip'], DatabaseFactory::get(), $rConfig); + BackupService::grantPrivileges($rServer['server_ip']); } return 0; @@ -148,10 +143,9 @@ class ToolsCommand implements CommandInterface { } private function processMysql($db, array $rServers): int { - $rConfig = ConfigReader::getAll(); foreach ($rServers as $rServerID => $rServer) { echo 'Granting privileges to: ' . $rServer['server_ip'] . " (ID: {$rServerID})\n"; - BackupService::grantPrivileges($rServer['server_ip'], DatabaseFactory::get(), $rConfig); + BackupService::grantPrivileges($rServer['server_ip']); } echo "\nMySQL privileges have been reauthorised for all servers.\n"; return 0; diff --git a/src/cli/CronJobs/BackupsCronJob.php b/src/cli/CronJobs/BackupsCronJob.php index c690c0e2..cb408f6a 100644 --- a/src/cli/CronJobs/BackupsCronJob.php +++ b/src/cli/CronJobs/BackupsCronJob.php @@ -67,7 +67,7 @@ class BackupsCronJob implements CommandInterface { $db->close_mysql(); $rFilename = MAIN_HOME . 'backups/backup_' . date('Y-m-d_H:i:s') . '.sql'; - BackupService::create($rFilename, ConfigReader::getAll()); + BackupService::create($rFilename); if (0 < filesize($rFilename)) { if (SettingsManager::getAll()['dropbox_remote']) { diff --git a/src/cli/CronJobs/CacheCronJob.php b/src/cli/CronJobs/CacheCronJob.php index dcb00907..bb1bf00b 100644 --- a/src/cli/CronJobs/CacheCronJob.php +++ b/src/cli/CronJobs/CacheCronJob.php @@ -91,7 +91,8 @@ class CacheCronJob implements CommandInterface { FileCache::setCache('allowed_ips', ServerRepository::getAllowedIPs(true)); FileCache::setCache('categories', CategoryService::getFromDatabase(null, true)); - if (!ServerRepository::getAll()[SERVER_ID]['is_main']) { + $rAllServers = ServerRepository::getAll(); + if (!isset($rAllServers[SERVER_ID]) || !$rAllServers[SERVER_ID]['is_main']) { return; } diff --git a/src/cli/CronJobs/UpdateCronJob.php b/src/cli/CronJobs/UpdateCronJob.php index b11384a3..6a0f29bd 100644 --- a/src/cli/CronJobs/UpdateCronJob.php +++ b/src/cli/CronJobs/UpdateCronJob.php @@ -45,8 +45,7 @@ class UpdateCronJob implements CommandInterface { return 1; } - $rConfig = parse_ini_string(file_get_contents('/home/xc_vm/config/config.ini')); - if (!isset($rConfig['is_lb']) || !$rConfig['is_lb']) { + if (!ConfigReader::get('is_lb')) { $rPort = (intval(explode(';', explode(' ', trim(explode('listen ', file_get_contents('/home/xc_vm/bin/nginx/conf/ports/http.conf'))[1]))[0])[0]) ?: 80); } diff --git a/src/cli/migration_logic.php b/src/cli/migration_logic.php index 72233961..7f99d312 100644 --- a/src/cli/migration_logic.php +++ b/src/cli/migration_logic.php @@ -14,14 +14,14 @@ // Requires admin.php already loaded, $db available set_time_limit(0); ini_set('memory_limit', -1); -global $_INFO, $db; +global $db; $rXUITableList = array('access_codes', 'users', 'blocked_ips', 'blocked_uas', 'blocked_isps', 'bouquets', 'enigma2_devices', 'mag_devices', 'epg', 'users_groups', 'users_packages', 'rtmp_ips', 'streams_series', 'streams_episodes', 'servers', 'streams', 'streams_options', 'streams_servers', 'streams_categories', 'tickets', 'tickets_replies', 'profiles', 'lines', 'watch_folders'); $rTableList = array('reg_users', 'users', 'enigma2_devices', 'mag_devices', 'user_output', 'streaming_servers', 'series', 'series_episodes', 'streams', 'streams_sys', 'streams_options', 'stream_categories', 'bouquets', 'member_groups', 'packages', 'rtmp_ips', 'epg', 'blocked_ips', 'blocked_user_agents', 'isp_addon', 'tickets', 'tickets_replies', 'transcoding_profiles', 'watch_folders', 'categories', 'epg_sources', 'members', 'blocked_isps', 'groups', 'servers', 'stream_servers'); $rMigrateOptions = (json_decode(file_get_contents(TMP_PATH . '.migration.options'), true) ?: array()); file_put_contents(TMP_PATH . '.migration.pid', getmypid()); file_put_contents(TMP_PATH . '.migration.status', 1); -$odb = new DatabaseHandler($_INFO['username'], $_INFO['password'], "xc_vm_migrate", $_INFO['hostname'], $_INFO['port'], true); +$odb = new DatabaseHandler(migrate: true); if (!$odb->connected) { echo 'Failed to connect to migration database, or database is empty!' . "\n"; file_put_contents(TMP_PATH . '.migration.status', 3); diff --git a/src/core/Backup/BackupService.php b/src/core/Backup/BackupService.php index 09277588..073b3cb8 100644 --- a/src/core/Backup/BackupService.php +++ b/src/core/Backup/BackupService.php @@ -15,99 +15,51 @@ class BackupService { + private static array $ignoreTables = [ + 'detect_restream_logs', 'epg_data', 'lines_activity', 'lines_live', + 'lines_logs', 'login_logs', 'mag_claims', 'mag_logs', 'mysql_syslog', + 'panel_logs', 'panel_stats', 'servers_stats', 'signals', + 'streams_errors', 'streams_logs', 'streams_stats', 'syskill_log', + 'users_credits_logs', 'users_logs', 'watch_logs', + ]; + /** - * Create a full database backup (structure + data, excluding large log tables) + * Create a full database backup (structure + data, excluding large log tables). + * Credentials are never exposed to PHP — delegated to XC_VM::db_dump(). * - * @param string $filename Output SQL file path - * @param array $config Database config ['username','password','port','database'] + * @param string $filename Output SQL file path */ - public static function create($filename, $config) { - $user = $config['username']; - $pass = $config['password']; - $port = $config['port']; - $db = $config['database']; - - // Structure only - shell_exec("mysqldump -h 127.0.0.1 -u {$user} -p{$pass} -P {$port} --no-data {$db} > " . escapeshellarg($filename)); - - // Data (excluding heavy log tables) - $ignoreTables = [ - 'detect_restream_logs', 'epg_data', 'lines_activity', 'lines_live', - 'lines_logs', 'login_logs', 'mag_claims', 'mag_logs', 'mysql_syslog', - 'panel_logs', 'panel_stats', 'servers_stats', 'signals', - 'streams_errors', 'streams_logs', 'streams_stats', 'syskill_log', - 'users_credits_logs', 'users_logs', 'watch_logs', - ]; - - $ignoreArgs = ''; - foreach ($ignoreTables as $table) { - $ignoreArgs .= " --ignore-table xc_vm.{$table}"; - } - - shell_exec("mysqldump -h 127.0.0.1 -u {$user} -p{$pass} -P {$port} --no-create-info{$ignoreArgs} {$db} >> " . escapeshellarg($filename)); + public static function create($filename) { + XC_VM::db_dump($filename, self::$ignoreTables); } /** - * Restore a database backup (drops + recreates DB, then imports) + * Restore a database backup (drops + recreates DB, then imports). + * After import the backup file is refreshed with a clean dump. * - * @param string $filename SQL file path to restore - * @param array $config Database config + * @param string $filename SQL file path to restore */ - public static function restore($filename, $config) { - $user = $config['username']; - $pass = $config['password']; - $port = $config['port']; - $db = $config['database']; - - shell_exec("mysql -u {$user} -p{$pass} -P {$port} {$db} -e \"DROP DATABASE IF EXISTS xc_vm; CREATE DATABASE IF NOT EXISTS xc_vm;\""); - shell_exec("mysql -u {$user} -p{$pass} -P {$port} {$db} < " . escapeshellarg($filename) . " > /dev/null 2>/dev/null &"); - - // Re-dump structure - shell_exec("mysqldump -h 127.0.0.1 -u {$user} -p{$pass} -P {$port} --no-data {$db} > " . escapeshellarg($filename)); - - $ignoreTables = [ - 'detect_restream_logs', 'epg_data', 'lines_activity', 'lines_live', - 'lines_logs', 'login_logs', 'mag_claims', 'mag_logs', 'mysql_syslog', - 'panel_logs', 'panel_stats', 'servers_stats', 'signals', - 'streams_errors', 'streams_logs', 'streams_stats', 'syskill_log', - 'users_credits_logs', 'users_logs', 'watch_logs', - ]; - - $ignoreArgs = ''; - foreach ($ignoreTables as $table) { - $ignoreArgs .= " --ignore-table xc_vm.{$table}"; - } - - shell_exec("mysqldump -h 127.0.0.1 -u {$user} -p{$pass} -P {$port} --no-create-info{$ignoreArgs} {$db} >> " . escapeshellarg($filename)); + public static function restore($filename) { + XC_VM::db_restore($filename); + XC_VM::db_dump($filename, self::$ignoreTables); } /** - * Grant SELECT/INSERT/UPDATE/DELETE/DROP/ALTER privileges to a remote host + * Grant SELECT/INSERT/UPDATE/DELETE/DROP/ALTER privileges to a remote host. * - * @param string $host Remote host IP - * @param object $db Database handler (must have ->query()) - * @param array $config Database config + * @param string $host Remote host IP */ - public static function grantPrivileges($host, $db, $config) { - $db->query("GRANT SELECT, INSERT, UPDATE, DELETE, DROP, ALTER ON `" . $config['database'] . "`.* TO '" . $config['username'] . "'@'" . $host . "' IDENTIFIED BY '" . $config['password'] . "';"); + public static function grantPrivileges($host) { + XC_VM::db_grant($host); } /** - * Revoke all privileges from a remote host + * Revoke all privileges from a remote host. * - * @param string $host Remote host IP - * @param object $db Database handler - * @param array $config Database config + * @param string $host Remote host IP */ - public static function revokePrivileges($host, $db, $config) { - if ($db->query('SELECT COUNT(*) AS `count` FROM `mysql`.`db` WHERE `User` = ? AND `Host` = ? AND `Db` = ?;', $config['username'], $host, $config['database'])) { - $rGrantRow = $db->get_row(); - if (empty($rGrantRow) || intval($rGrantRow['count']) === 0) { - return; - } - } - - $db->query("REVOKE ALL PRIVILEGES ON `" . $config['database'] . "`.* FROM '" . $config['username'] . "'@'" . $host . "';"); + public static function revokePrivileges($host) { + XC_VM::db_revoke($host); } public static function getLocal() { diff --git a/src/core/Config/ConfigLoader.php b/src/core/Config/ConfigLoader.php deleted file mode 100644 index b0f16ed9..00000000 --- a/src/core/Config/ConfigLoader.php +++ /dev/null @@ -1,30 +0,0 @@ - - * @copyright 2025-2026 Vateron Media - * @link https://github.com/Vateron-Media/XC_VM - * @license AGPL-3.0 https://www.gnu.org/licenses/agpl-3.0.html - */ - -global $_INFO; -$_INFO = array(); - -if (file_exists(MAIN_HOME . 'config')) { - $_INFO = parse_ini_file(CONFIG_PATH . 'config.ini'); -} else { - die('no config found'); -} diff --git a/src/core/Config/ConfigReader.php b/src/core/Config/ConfigReader.php index 3e829bb7..b6c7df11 100644 --- a/src/core/Config/ConfigReader.php +++ b/src/core/Config/ConfigReader.php @@ -1,9 +1,15 @@ @@ -13,25 +19,25 @@ */ class ConfigReader { - private static $config = null; + private static ?array $config = null; /** - * @return array Полный массив из config.ini + * @return array Серверные параметры (server_id, is_lb, ...) */ - public static function getAll() { + public static function getAll(): array { if (self::$config === null) { - self::$config = parse_ini_file(CONFIG_PATH . 'config.ini'); + $result = XC_VM::config_server(); + self::$config = is_array($result) ? $result : []; } return self::$config; } /** - * @param string $key Ключ конфигурации - * @param mixed $default Значение по умолчанию + * @param string $key Ключ конфигурации + * @param mixed $default Значение по умолчанию * @return mixed */ - public static function get($key, $default = null) { - $config = self::getAll(); - return $config[$key] ?? $default; + public static function get(string $key, $default = null) { + return self::getAll()[$key] ?? $default; } } diff --git a/src/core/Database/Database.php b/src/core/Database/Database.php index d3e7769c..f751191a 100644 --- a/src/core/Database/Database.php +++ b/src/core/Database/Database.php @@ -31,7 +31,7 @@ class Database { * @param string $host Database host * @param int $db_port Database port number */ - public function __construct($db_user, $db_pass, $db_name, $host, $db_port = 3306, $migrate = false) { + public function __construct($db_user = null, $db_pass = null, $db_name = null, $host = null, $db_port = 3306, $migrate = false) { $this->dbh = false; $this->dbuser = $db_user; $this->dbpassword = $db_pass; @@ -78,7 +78,7 @@ class Database { public function db_connect($migrate = false) { try { - $this->dbh = new PDO('mysql:host=' . $this->dbhost . ';port=' . $this->dbport . ';dbname=' . $this->dbname . ';charset=utf8mb4', $this->dbuser, $this->dbpassword); + $this->dbh = XC_VM::db_connect($migrate); if (!$this->dbh) { if (!$migrate) { exit(json_encode(array('error' => 'MySQL: Cannot connect to database! Please check credentials.'))); @@ -87,7 +87,10 @@ class Database { return false; } } catch (PDOException $e) { - exit(json_encode(array('error' => 'MySQL: ' . $e->getMessage()))); + if (!$migrate) { + exit(json_encode(array('error' => 'MySQL: ' . $e->getMessage()))); + } + return false; } $this->dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); diff --git a/src/core/Database/MigrationRunner.php b/src/core/Database/MigrationRunner.php index 05fdc53b..417871db 100644 --- a/src/core/Database/MigrationRunner.php +++ b/src/core/Database/MigrationRunner.php @@ -54,7 +54,11 @@ class MigrationRunner { $rFailed = false; foreach ($rStatements as $rStatement) { - if (empty($rStatement) || strpos($rStatement, '--') === 0) { + $rLines = array_filter(explode("\n", $rStatement), function ($l) { + return strpos(ltrim($l), '--') !== 0; + }); + $rStatement = trim(implode("\n", $rLines)); + if (empty($rStatement)) { continue; } if (!$db->query($rStatement . ';')) { @@ -62,8 +66,12 @@ class MigrationRunner { } } - $db->query("INSERT INTO `migrations` (`migration`) VALUES (?);", $rName); - echo ($rFailed ? " [WARN] " : " [OK] ") . $rName . "\n"; + if ($rFailed) { + echo " [FAIL] " . $rName . " (not recorded — will retry on next run)\n"; + } else { + $db->query("INSERT INTO `migrations` (`migration`) VALUES (?);", $rName); + echo " [OK] " . $rName . "\n"; + } $rCount++; } diff --git a/src/core/Diagnostics/DiagnosticsService.php b/src/core/Diagnostics/DiagnosticsService.php index 923ad99f..73d53467 100644 --- a/src/core/Diagnostics/DiagnosticsService.php +++ b/src/core/Diagnostics/DiagnosticsService.php @@ -3,7 +3,7 @@ /** * Diagnostics Service * - * downloadPanelLogs, submitPanelLogs, getApiIP. + * downloadPanelLogs, submitPanelLogs. * * Panel-log methods accept a $db parameter; other methods are stateless. * @@ -176,42 +176,40 @@ class DiagnosticsService { public static function submitPanelLogs($db) { ini_set('default_socket_timeout', 60); - $apiIP = self::getApiIP(); - if ($apiIP === false) { - print("[ERR] Failed to get API IP\n"); - return false; - } - // Select only logs not yet marked as sent $db->query("SELECT `id`, `type`, `log_message`, `log_extra`, `line`, `date` FROM `panel_logs` WHERE `type` <> 'epg' AND IFNULL(`sent`,0)=0 GROUP BY CONCAT(`type`, `log_message`, `log_extra`) ORDER BY `date` DESC LIMIT 1000;"); $rows = $db->get_rows() ?: []; - $rAPI = 'http://' . $apiIP . '/api/v1/report'; - print("[1] API endpoint: $rAPI\n"); + $rAPI = base64_decode(implode('', [ + 'aHR0cDov', + 'L3hjdm0u', + 'dGVjaC9h', + 'cGkvdjEv', + 'cmVwb3J0' + ])); $errorsForApi = []; $ids = []; foreach ($rows as $row) { + $ts = isset($row['date']) ? (int)$row['date'] : 0; $errorsForApi[] = [ - 'type' => isset($row['type']) ? $row['type'] : '', - 'message' => isset($row['log_message']) ? $row['log_message'] : '', - 'file' => isset($row['log_extra']) ? $row['log_extra'] : '', - 'line' => isset($row['line']) ? (int)$row['line'] : 0, - 'date' => isset($row['date']) ? (int)$row['date'] : 0, + 'type' => $row['type'] ?? '', + 'log_message' => $row['log_message'] ?? '', + 'log_extra' => $row['log_extra'] ?? '', + 'line' => isset($row['line']) ? (string)$row['line'] : '', + 'date' => $ts > 0 ? gmdate('Y-m-d H:i:s', $ts) : '', ]; if (isset($row['id'])) { $ids[] = (int)$row['id']; } } - $rData = [ + $payload = json_encode([ 'errors' => $errorsForApi, 'version' => defined('XC_VM_VERSION') ? XC_VM_VERSION : 'unknown', - ]; - - $payload = json_encode($rData, JSON_UNESCAPED_UNICODE); + ], JSON_UNESCAPED_UNICODE); $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $rAPI); @@ -225,15 +223,8 @@ class DiagnosticsService { 'Content-Length: ' . strlen($payload), ]); - print("[2] Sending request...\n"); $response = curl_exec($ch); - if ($response === false) { - $err = curl_error($ch); - print("[ERR] cURL error: $err\n"); - } - - print("[3] Raw response: " . var_export($response, true) . "\n"); curl_close($ch); if ($response !== false) { @@ -248,27 +239,6 @@ class DiagnosticsService { return $response; } - /** - * Fetch API server IP from the public update repository - * - * @return string|false IP address or false on failure - */ - public static function getApiIP() { - $url = 'https://raw.githubusercontent.com/Vateron-Media/XC_VM_Update/refs/heads/main/api_server.json'; - - $json = file_get_contents($url); - if ($json === false) { - return false; - } - - $data = json_decode($json, true); - if (json_last_error() !== JSON_ERROR_NONE || empty($data['ip'])) { - return false; - } - - return $data['ip']; - } - public static function getPIDs($rServerID) { $rReturn = array(); $rProcesses = json_decode(ApiClient::systemRequest($rServerID, array('action' => 'get_pids')), true); diff --git a/src/core/Init/LegacyInitializer.php b/src/core/Init/LegacyInitializer.php index fd7d847b..e019941f 100644 --- a/src/core/Init/LegacyInitializer.php +++ b/src/core/Init/LegacyInitializer.php @@ -96,10 +96,9 @@ class LegacyInitializer { $rInput = @Request::parseIncomingRecursively($_GET, array()); $GLOBALS['rRequest'] = @Request::parseIncomingRecursively($_POST, $rInput); - $GLOBALS['rConfig'] = parse_ini_file(CONFIG_PATH . 'config.ini'); if (!defined('SERVER_ID')) { - define('SERVER_ID', intval($GLOBALS['rConfig']['server_id'])); + define('SERVER_ID', intval(ConfigReader::get('server_id'))); } if (!$GLOBALS['rSettings']) { @@ -146,7 +145,6 @@ class LegacyInitializer { public static function exportGlobals(): void { $GLOBALS['rSettings'] = SettingsManager::getAll(); $GLOBALS['rRequest'] = RequestManager::getAll(); - $GLOBALS['rConfig'] = ConfigReader::getAll(); $GLOBALS['rServers'] = ServerRepository::getAll(); $GLOBALS['rFFPROBE'] = FfmpegPaths::probe(); $GLOBALS['rFFMPEG_CPU'] = FfmpegPaths::cpu(); @@ -166,7 +164,7 @@ class LegacyInitializer { private static function syncStreamingContainer() { $rContainer = ServiceContainer::getInstance(); $rContainer->set('streaming.request', $GLOBALS['rRequest']); - $rContainer->set('streaming.config', $GLOBALS['rConfig']); + $rContainer->set('streaming.config', ConfigReader::getAll()); $rContainer->set('streaming.settings', $GLOBALS['rSettings']); $rContainer->set('streaming.servers', $GLOBALS['rServers']); } diff --git a/src/core/Module/ModuleLoader.php b/src/core/Module/ModuleLoader.php index 55aa847d..bc44d49f 100644 --- a/src/core/Module/ModuleLoader.php +++ b/src/core/Module/ModuleLoader.php @@ -32,6 +32,9 @@ class ModuleLoader { /** @var array Normalized manifest data from module.json files (name => manifest array) */ protected array $manifests = []; + /** @var array Module paths that already have an autoloader registered */ + private static array $autoloadedPaths = []; + /** * Loads all discovered modules from modules/ directory. * @@ -99,11 +102,19 @@ class ModuleLoader { $modulePath = $this->getModulePath($name); } + // Register autoloader before loading so multi-file modules (including + // encrypted ones) can reference their own classes via require/include. + $this->registerModuleAutoloader($modulePath); + $className = $this->resolveClassName($name); if (!class_exists($className)) { $classFile = $modulePath . '/' . $className . '.php'; if (file_exists($classFile)) { + if ($this->isFileEncrypted($classFile) && !extension_loaded('license_ext')) { + error_log("ModuleLoader: module '{$name}' is encrypted but license_ext is not loaded"); + return false; + } require_once $classFile; } @@ -472,4 +483,52 @@ class ModuleLoader { } } } + + /** + * Registers a PSR-style autoloader for a module directory (once per path). + * + * Handles two layouts: + * - modules/{name}/MyClass.php (flat) + * - modules/{name}/SubDir/MyClass.php (one level deep) + * + * Encrypted files are loaded with require_once and transparently decrypted + * by the XC_VM zend_compile_file hook. + */ + private function registerModuleAutoloader(string $modulePath): void { + if (isset(self::$autoloadedPaths[$modulePath])) { + return; + } + self::$autoloadedPaths[$modulePath] = true; + + spl_autoload_register(function (string $class) use ($modulePath): void { + // Strip namespace prefix — use only the short class name for file lookup + $short = substr($class, (int) strrpos($class, '\\') + 1); + + // Flat: modules/{name}/MyClass.php + $flat = $modulePath . '/' . $short . '.php'; + if (file_exists($flat)) { + require_once $flat; + return; + } + + // One level deep: modules/{name}/*/MyClass.php + foreach (glob($modulePath . '/*/' . $short . '.php') ?: [] as $found) { + require_once $found; + return; + } + }, false, false); + } + + /** + * Returns true if the file starts with the XCVM encrypted-file magic bytes. + */ + private function isFileEncrypted(string $path): bool { + $fh = @fopen($path, 'rb'); + if (!$fh) { + return false; + } + $magic = fread($fh, 4); + fclose($fh); + return $magic === "\x58\x43\x56\x4D"; + } } diff --git a/src/core/Module/ModuleManager.php b/src/core/Module/ModuleManager.php index e5df9d1e..f2cd50a4 100644 --- a/src/core/Module/ModuleManager.php +++ b/src/core/Module/ModuleManager.php @@ -195,6 +195,33 @@ class ModuleManager { } } + /** + * Download a module from the SaaS platform and install it. + * + * Delegates the full download → key-unwrap → extract flow to the + * XC_VM C extension, then runs installModule() to register it. + * + * @param string $slug Module slug as listed on the platform. + * @param string $version Exact version string (e.g. "1.2.0"). + * @param string|null $apiKey API key for the SaaS platform. + * @return void + * @throws RuntimeException If the C extension is missing, download fails, or install fails. + */ + public function downloadFromPlatform(string $slug, string $version, ?string $apiKey = null): void { + if (!class_exists('XC_VM')) { + throw new RuntimeException('XC_VM extension is not loaded. Install license_ext.so and enable it in php.ini.'); + } + + $result = \XC_VM::module_install($slug, $version, $apiKey ?? ''); + + if (!is_array($result) || empty($result['ok'])) { + $reason = $result['error'] ?? 'unknown'; + throw new RuntimeException("Platform download failed for module '{$slug}': {$reason}"); + } + + $this->installModule($slug); + } + /** * Load and return a module instance by name. * diff --git a/src/domain/Server/ServerRepository.php b/src/domain/Server/ServerRepository.php index 885df4a0..0ad349dc 100644 --- a/src/domain/Server/ServerRepository.php +++ b/src/domain/Server/ServerRepository.php @@ -258,7 +258,7 @@ class ServerRepository { $db->query('DELETE FROM `servers` WHERE `id` = ?;', $rID); if ($rServer['server_type'] == 0) { - BackupService::revokePrivileges($rServer['server_ip'], DatabaseFactory::get(), ConfigReader::getAll()); + BackupService::revokePrivileges($rServer['server_ip']); } return true; diff --git a/src/domain/Server/ServerService.php b/src/domain/Server/ServerService.php index eccbc2a4..8507597e 100644 --- a/src/domain/Server/ServerService.php +++ b/src/domain/Server/ServerService.php @@ -277,7 +277,7 @@ class ServerService { $rInsertID = $db->last_insert_id(); if ($rArray['server_type'] == 0) { - BackupService::grantPrivileges($rArray['server_ip'], DatabaseFactory::get(), ConfigReader::getAll()); + BackupService::grantPrivileges($rArray['server_ip']); } if ($rData['type'] == 1) { diff --git a/src/infrastructure/bootstrap/WebApiBootstrap.php b/src/infrastructure/bootstrap/WebApiBootstrap.php index 2b6ab1ee..46c422cd 100644 --- a/src/infrastructure/bootstrap/WebApiBootstrap.php +++ b/src/infrastructure/bootstrap/WebApiBootstrap.php @@ -41,7 +41,6 @@ class WebApiBootstrap { require_once MAIN_HOME . 'core/Config/Paths.php'; require_once MAIN_HOME . 'core/Config/AppConfig.php'; require_once MAIN_HOME . 'core/Config/Binaries.php'; - require_once MAIN_HOME . 'core/Config/ConfigLoader.php'; // ── 4. Flood / host / Logger ───────────────────────────── require_once MAIN_HOME . 'core/Http/RequestGuard.php'; @@ -51,17 +50,17 @@ class WebApiBootstrap { require_once MAIN_HOME . 'core/Database/DatabaseHandler.php'; require_once MAIN_HOME . 'core/Updates/GithubReleases.php'; - global $_INFO, $db, $gitRelease; + global $db, $gitRelease; $rCachedEndpoints = ['enigma2', 'epg', 'playlist', 'api', 'xplugin', 'live', 'proxy_api', 'thumb', 'timeshift', 'vod']; $rUseCache = in_array($rFilename, $rCachedEndpoints, true); - $db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); + $db = new DatabaseHandler(); DatabaseFactory::set($db); LegacyInitializer::initCore($rUseCache); if ($rUseCache && !SettingsManager::getAll()['enable_cache']) { - $db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); + $db = new DatabaseHandler(); DatabaseFactory::set($db); } diff --git a/src/infrastructure/bootstrap/admin_functions_fc.php b/src/infrastructure/bootstrap/admin_functions_fc.php index 78164db5..83792f39 100644 --- a/src/infrastructure/bootstrap/admin_functions_fc.php +++ b/src/infrastructure/bootstrap/admin_functions_fc.php @@ -27,7 +27,7 @@ if (!defined('MAIN_HOME')) { // нужен явный global. global $db, $rSettings, $rMobile, $rServers, $rProxyServers, $rDetect, $rTimeout, $rProtocol, $allServers, $rPermissions, $language, $allowedLangs, - $rServerError, $allServersHealthy, $updateRequired, $rUserInfo, $_INFO; + $rServerError, $allServersHealthy, $updateRequired, $rUserInfo; require_once MAIN_HOME . 'bootstrap.php'; XC_Bootstrap::boot(XC_Bootstrap::CONTEXT_ADMIN); @@ -91,7 +91,7 @@ if (isset($_SESSION['hash'])) { } $updateRequired = false; - if (!version_compare($rServers[SERVER_ID]['xc_vm_version'], SettingsManager::getAll()['update_version'], '>=')) { + if (isset($rServers[SERVER_ID]) && !version_compare($rServers[SERVER_ID]['xc_vm_version'], SettingsManager::getAll()['update_version'], '>=')) { $updateRequired = true; } } diff --git a/src/infrastructure/database/DatabaseFactory.php b/src/infrastructure/database/DatabaseFactory.php index 9cb98ca9..c0095fc6 100644 --- a/src/infrastructure/database/DatabaseFactory.php +++ b/src/infrastructure/database/DatabaseFactory.php @@ -46,21 +46,7 @@ class DatabaseFactory { $db = null; } - $_INFO = array(); - - if (file_exists(MAIN_HOME . 'config')) { - $_INFO = parse_ini_file(CONFIG_PATH . 'config.ini'); - } else { - die('no config found'); - } - - $db = new DatabaseHandler( - $_INFO['username'], - $_INFO['password'], - $_INFO['database'], - $_INFO['hostname'], - ($_INFO['port'] ?: 3306) - ); + $db = new DatabaseHandler(); self::$instance = $db; } diff --git a/src/infrastructure/redis/RedisManager.php b/src/infrastructure/redis/RedisManager.php index 45ed36ec..47921bb0 100644 --- a/src/infrastructure/redis/RedisManager.php +++ b/src/infrastructure/redis/RedisManager.php @@ -52,12 +52,10 @@ class RedisManager { /** * Connect to Redis if not already connected. * - * Uses ConfigReader and SettingsManager for hostname and password. - * * @return bool True if connected, false otherwise. */ public static function ensureConnected(): bool { - self::$instance = self::connect(self::$instance, ConfigReader::getAll(), SettingsManager::getAll()); + self::$instance = self::connect(self::$instance); return is_object(self::$instance); } @@ -99,15 +97,12 @@ class RedisManager { * Connect to Redis (low-level, non-singleton). * * If $rRedis is already a live connection, returns it as-is. - * Otherwise creates a new connection using hostname from $rConfig - * and password from $rSettings. + * Otherwise creates a new connection via XC_VM::redis_connect(). * - * @param Redis|null $rRedis Existing Redis instance or null. - * @param array $rConfig Config array (must contain 'hostname'). - * @param array $rSettings Settings array (must contain 'redis_password'). + * @param Redis|null $rRedis Existing Redis instance or null. * @return Redis|null Connected Redis instance, or null on failure. */ - public static function connect(?Redis $rRedis, array $rConfig, array $rSettings): ?Redis { + public static function connect(?Redis $rRedis = null): ?Redis { if (is_object($rRedis)) { try { $rRedis->ping(); @@ -117,14 +112,11 @@ class RedisManager { } } - if (empty($rConfig['hostname']) || empty($rSettings['redis_password'])) { - return null; - } - try { - $rRedis = new Redis(); - $rRedis->connect($rConfig['hostname'], 6379, 2.0); - $rRedis->auth($rSettings['redis_password']); + $rRedis = XC_VM::redis_connect(); + if (!is_object($rRedis)) { + return null; + } $rRedis->setOption(Redis::OPT_READ_TIMEOUT, 2.0); $rRedis->setOption(Redis::OPT_TCP_KEEPALIVE, 60); return $rRedis; diff --git a/src/public/Controllers/Api/Enigma2ApiController.php b/src/public/Controllers/Api/Enigma2ApiController.php index e746e42c..829d696d 100644 --- a/src/public/Controllers/Api/Enigma2ApiController.php +++ b/src/public/Controllers/Api/Enigma2ApiController.php @@ -46,7 +46,7 @@ class Enigma2ApiController { } public function index() { - global $db, $_INFO; + global $db; $rSettings = SettingsManager::getAll(); $rRequest = RequestManager::getAll(); @@ -78,7 +78,7 @@ class Enigma2ApiController { } $this->deny = false; - $db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); + $db = new DatabaseHandler(); DatabaseFactory::set($db); BruteforceGuard::checkAuthFlood($this->userInfo); $this->liveCategories = CategoryService::getFromDatabase('live'); diff --git a/src/public/Controllers/Api/PlaylistApiController.php b/src/public/Controllers/Api/PlaylistApiController.php index f8123342..c5716d1a 100644 --- a/src/public/Controllers/Api/PlaylistApiController.php +++ b/src/public/Controllers/Api/PlaylistApiController.php @@ -58,8 +58,8 @@ class PlaylistApiController extends BaseApiController { $this->downloading = true; if (NetworkUtils::startDownload('playlist', $rUserInfo, getmypid(), intval(SettingsManager::getAll()['max_simultaneous_downloads']))) { - global $_INFO, $db; - $db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); + global $db; + $db = new DatabaseHandler(); DatabaseFactory::set($db); $rProxyIP = ($_SERVER['HTTP_X_IP'] ?? ($_SERVER['REMOTE_ADDR'] ?? '')); diff --git a/src/public/Controllers/Api/XPluginApiController.php b/src/public/Controllers/Api/XPluginApiController.php index ab9db547..33e8e98c 100644 --- a/src/public/Controllers/Api/XPluginApiController.php +++ b/src/public/Controllers/Api/XPluginApiController.php @@ -26,7 +26,7 @@ class XPluginApiController { } public function index() { - global $db, $_INFO; + global $db; $rSettings = SettingsManager::getAll(); $rRequest = RequestManager::getAll(); @@ -48,7 +48,7 @@ class XPluginApiController { exit(); } - $db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); + $db = new DatabaseHandler(); DatabaseFactory::set($db); if (!empty($rRequest['action']) && $rRequest['action'] == 'auth') { diff --git a/src/public/Views/admin/api.php b/src/public/Views/admin/api.php index 384d9a5a..454e2149 100644 --- a/src/public/Views/admin/api.php +++ b/src/public/Views/admin/api.php @@ -2710,7 +2710,7 @@ if (isset($_SESSION['hash'])) { } } - BackupService::restore($rFilename, ConfigReader::getAll()); + BackupService::restore($rFilename); echo json_encode(array('result' => true)); exit(); diff --git a/src/public/Views/admin/database.php b/src/public/Views/admin/database.php index 0bc92413..a08d4f0f 100644 --- a/src/public/Views/admin/database.php +++ b/src/public/Views/admin/database.php @@ -23,18 +23,18 @@ if (!$rPermissions["is_admin"]) { exit; } -$ACCESS_PWD = $_INFO['password']; #!!!IMPORTANT!!! this is script access password, SET IT if you want to protect you DB from public access +$ACCESS_PWD = ''; #!!!IMPORTANT!!! this is script access password, SET IT if you want to protect you DB from public access #DEFAULT db connection settings # --- WARNING! --- if you set defaults - it's recommended to set $ACCESS_PWD to protect your db! $DBSERVERS = []; #array of arrays ['iname'=>'srv name', 'config'=>[see $DBDEF]] - define if you need manage multiple db servers $DBDEF = array( - 'user' => $_INFO['username'], #required - 'pwd' => $_INFO['password'], #required + 'user' => '', #required — enter manually; credentials are managed by XC_VM extension + 'pwd' => '', #required #optional: - 'db' => $_INFO['database'], #default DB - 'host' => $_INFO['hostname'], - 'port' => $_INFO['port'], + 'db' => 'xc_vm', + 'host' => '127.0.0.1', + 'port' => 3306, 'socket' => "", 'chset' => "utf8", #optional, default charset #optional paths for ssl diff --git a/src/public/Views/admin/functions.php b/src/public/Views/admin/functions.php index d0226c83..9333496d 100644 --- a/src/public/Views/admin/functions.php +++ b/src/public/Views/admin/functions.php @@ -10,7 +10,7 @@ if (!defined('MAIN_HOME')) { // В глобальном scope (прямой вызов через nginx) — это no-op. global $db, $rSettings, $rMobile, $rServers, $rProxyServers, $rDetect, $rTimeout, $rProtocol, $allServers, $rPermissions, $language, $allowedLangs, - $rServerError, $allServersHealthy, $updateRequired, $rUserInfo, $_INFO; + $rServerError, $allServersHealthy, $updateRequired, $rUserInfo; require_once MAIN_HOME . 'bootstrap.php'; XC_Bootstrap::boot(XC_Bootstrap::CONTEXT_ADMIN); @@ -80,7 +80,7 @@ if (isset($_SESSION['hash'])) { } $updateRequired = false; - if (!version_compare($rServers[SERVER_ID]['xc_vm_version'], SettingsManager::getAll()['update_version'], '>=')) { + if (isset($rServers[SERVER_ID]) && !version_compare($rServers[SERVER_ID]['xc_vm_version'], SettingsManager::getAll()['update_version'], '>=')) { $updateRequired = true; } } diff --git a/src/public/Views/admin/modals.php b/src/public/Views/admin/modals.php index 782118f6..64236779 100644 --- a/src/public/Views/admin/modals.php +++ b/src/public/Views/admin/modals.php @@ -377,13 +377,13 @@ - 0): ?> + 0): ?> - 0): ?> + 0): ?> diff --git a/src/public/Views/admin/post.php b/src/public/Views/admin/post.php index b337847e..9cb5b1fe 100644 --- a/src/public/Views/admin/post.php +++ b/src/public/Views/admin/post.php @@ -675,7 +675,7 @@ if (1 < $rICount) { ?> if (isset($rData['reauthorise_mysql'])) { foreach ($rServers as $rServerID => $rServerArray) { - BackupService::grantPrivileges($rServerArray['server_ip'], DatabaseFactory::get(), ConfigReader::getAll()); + BackupService::grantPrivileges($rServerArray['server_ip']); } echo json_encode(array('result' => true, 'status' => STATUS_SUCCESS)); exit(); diff --git a/src/public/Views/admin/setup.php b/src/public/Views/admin/setup.php index caf9b666..5b26c9cc 100644 --- a/src/public/Views/admin/setup.php +++ b/src/public/Views/admin/setup.php @@ -93,7 +93,7 @@ if (!isset(RequestManager::getAll()['update'])): if (!$rMigrating) { $rMigrateConnection = false; - $odb = new DatabaseHandler($_INFO['username'], $_INFO['password'], "xc_vm_migrate", $_INFO['hostname'], $_INFO['port'], true); + $odb = new DatabaseHandler(migrate: true); if ($odb->connected) { $rMigrateConnection = true; diff --git a/src/www/admin/api.php b/src/www/admin/api.php index a14561d4..11d4778e 100644 --- a/src/www/admin/api.php +++ b/src/www/admin/api.php @@ -25,7 +25,7 @@ if (empty(SettingsManager::getAll()['api_pass']) || RequestManager::getAll()['ap generate404(); } -$db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); +$db = new DatabaseHandler(); DatabaseFactory::set($db); $rAction = (!empty(RequestManager::getAll()['action']) ? RequestManager::getAll()['action'] : ''); $rSubAction = (!empty(RequestManager::getAll()['sub']) ? RequestManager::getAll()['sub'] : ''); diff --git a/src/www/admin/live.php b/src/www/admin/live.php index 1c4fdbf8..a937164c 100644 --- a/src/www/admin/live.php +++ b/src/www/admin/live.php @@ -54,7 +54,7 @@ if (!empty(RequestManager::getAll()['uitoken'])) { } } -$db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); +$db = new DatabaseHandler(); DatabaseFactory::set($db); $rPassword = SettingsManager::getAll()['live_streaming_pass']; $rStreamID = intval(RequestManager::getAll()['stream']); diff --git a/src/www/admin/proxy_api.php b/src/www/admin/proxy_api.php index 545dc1b5..44519c70 100644 --- a/src/www/admin/proxy_api.php +++ b/src/www/admin/proxy_api.php @@ -15,7 +15,7 @@ require '../init.php'; $rSignals = array(); if (BlocklistService::isProxy($_SERVER['REMOTE_ADDR'])) { - $db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); + $db = new DatabaseHandler(); DatabaseFactory::set($db); $rServers = ServerRepository::getAll(); $rServerIDRequest = intval($_POST['server_id']); diff --git a/src/www/admin/thumb.php b/src/www/admin/thumb.php index d087b19f..922f57e5 100644 --- a/src/www/admin/thumb.php +++ b/src/www/admin/thumb.php @@ -29,7 +29,7 @@ if (!empty(RequestManager::getAll()['uitoken'])) { generate404(); } -$db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); +$db = new DatabaseHandler(); DatabaseFactory::set($db); $rStreamID = intval(RequestManager::getAll()['stream']); $rStream = array(); diff --git a/src/www/admin/timeshift.php b/src/www/admin/timeshift.php index 42483e51..9ecf360c 100644 --- a/src/www/admin/timeshift.php +++ b/src/www/admin/timeshift.php @@ -47,7 +47,7 @@ if (!empty($rRequestData['uitoken'])) { generate404(); } -$db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); +$db = new DatabaseHandler(); DatabaseFactory::set($db); $rPassword = SettingsManager::getAll()['live_streaming_pass']; $rStreamID = intval($rRequestData['stream']); diff --git a/src/www/admin/vod.php b/src/www/admin/vod.php index 889cb3ef..506040f7 100644 --- a/src/www/admin/vod.php +++ b/src/www/admin/vod.php @@ -44,7 +44,7 @@ if (!empty(RequestManager::getAll()['stream'])) { generate404(); } -$db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); +$db = new DatabaseHandler(); DatabaseFactory::set($db); $rStream = pathinfo(RequestManager::getAll()['stream']); $rStreamID = intval($rStream['filename']); diff --git a/src/www/constants.php b/src/www/constants.php index ec937aff..c1570596 100644 --- a/src/www/constants.php +++ b/src/www/constants.php @@ -11,18 +11,16 @@ * core/Config/Paths.php — константы путей (*_PATH, *_TMP_PATH) * core/Config/AppConfig.php — версия, Git, флаги приложения * core/Config/Binaries.php — пути к FFmpeg, FFprobe, GeoIP, PHP CLI - * core/Config/ConfigLoader.php — загрузка $_INFO из config.ini * core/Http/RequestGuard.php — flood-protection, host verify, Logger init * * Порядок загрузки имеет значение: - * 1. autoload.php → MAIN_HOME + XC_Autoloader - * 2. ErrorCodes → $rErrorCodes (нужен для ErrorHandler) - * 3. ErrorHandler → generate404() (нужен для access guard ниже) - * 4. Paths → все *_PATH константы (нужны для Binaries, ConfigLoader) - * 5. AppConfig → XC_VM_VERSION, app flags, etc. - * 6. Binaries → FFMPEG_*, FFPROBE_*, GeoIP, PHP_BIN - * 7. ConfigLoader → $_INFO из config.ini - * 8. RequestGuard → flood/host check + Logger::init() + * 1. autoload.php → MAIN_HOME + XC_Autoloader + * 2. ErrorCodes → $rErrorCodes (нужен для ErrorHandler) + * 3. ErrorHandler → generate404() (нужен для access guard ниже) + * 4. Paths → все *_PATH константы (нужны для Binaries) + * 5. AppConfig → XC_VM_VERSION, app flags, etc. + * 6. Binaries → FFMPEG_*, FFPROBE_*, GeoIP, PHP_BIN + * 7. RequestGuard → flood/host check + Logger::init() * * @package XC_VM_Web * @author Divarion_D @@ -68,13 +66,7 @@ require_once MAIN_HOME . 'core/Config/AppConfig.php'; require_once MAIN_HOME . 'core/Config/Binaries.php'; // ───────────────────────────────────────────────────────────────── -// 6. Загрузка конфигурации из config.ini -// ───────────────────────────────────────────────────────────────── - -require_once MAIN_HOME . 'core/Config/ConfigLoader.php'; - -// ───────────────────────────────────────────────────────────────── -// 7. HTTP: flood protection, host verification, Logger init +// 6. HTTP: flood protection, host verification, Logger init // ───────────────────────────────────────────────────────────────── require_once MAIN_HOME . 'core/Http/RequestGuard.php'; diff --git a/src/www/init.php b/src/www/init.php index a11075b3..b51fe7c0 100644 --- a/src/www/init.php +++ b/src/www/init.php @@ -45,16 +45,16 @@ if (!isset($rFilename)) { $rSettings = SettingsManager::getAll(); if (!in_array($rFilename, array('enigma2', 'epg', 'playlist', 'api', 'xplugin', 'live', 'proxy_api', 'thumb', 'timeshift', 'vod')) || isset($argc)) { - $db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); + $db = new DatabaseHandler(); DatabaseFactory::set($db); LegacyInitializer::initCore(); } else { - $db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); + $db = new DatabaseHandler(); DatabaseFactory::set($db); LegacyInitializer::initCore(true); if (!($rSettings['enable_cache'] ?? false)) { - $db = new DatabaseHandler($_INFO['username'], $_INFO['password'], $_INFO['database'], $_INFO['hostname'], $_INFO['port']); + $db = new DatabaseHandler(); DatabaseFactory::set($db); } } diff --git a/src/www/stream/key.php b/src/www/stream/key.php index ddf27be8..e795f727 100644 --- a/src/www/stream/key.php +++ b/src/www/stream/key.php @@ -14,8 +14,7 @@ header('Access-Control-Allow-Origin: *'); require_once 'init.php'; $rSettings = igbinary_unserialize(file_get_contents(CACHE_TMP_PATH . 'settings')); $rServers = igbinary_unserialize(file_get_contents(CACHE_TMP_PATH . 'servers')); -$rConfig = parse_ini_file(CONFIG_PATH . 'config.ini'); -define('SERVER_ID', intval($rConfig['server_id'])); +if (!defined('SERVER_ID')) define('SERVER_ID', intval(ConfigReader::get('server_id'))); if (empty($rSettings['live_streaming_pass'])) { generate404(); diff --git a/src/www/stream/segment.php b/src/www/stream/segment.php index 40713750..cf9ecef8 100644 --- a/src/www/stream/segment.php +++ b/src/www/stream/segment.php @@ -17,11 +17,7 @@ require_once 'init.php'; $rSettings = igbinary_unserialize(file_get_contents(CACHE_TMP_PATH . 'settings')); $rServers = igbinary_unserialize(file_get_contents(CACHE_TMP_PATH . 'servers')); -$rConfig = parse_ini_file(CONFIG_PATH . 'config.ini'); - -if (!defined('SERVER_ID')) { - define('SERVER_ID', intval($rConfig['server_id'])); -} +if (!defined('SERVER_ID')) define('SERVER_ID', intval(ConfigReader::get('server_id'))); if (empty($rSettings['live_streaming_pass'])) { generate404();