From a113fb3ec9aad75c18582bba89cbdba79fa370c1 Mon Sep 17 00:00:00 2001 From: Divarion-D Date: Wed, 18 Mar 2026 20:32:51 +0300 Subject: [PATCH] refactor: remove legacy proxy files, migrate all callers to console.php - Delete src/status, src/tools, src/crons/epg.php (logic already in CLI commands) - Update BalancerCommand: fix remote file_exists to SSH test -f, fix stripos check - Update StartupCommand, UpdateCommand: use PHP_BIN + console.php - Update dashboard.php: replace hardcoded sudo php with PHP_BIN - Update install/test_installer: sentinel console.php, exec paths - Update Makefile: remove status/tools from LB_ROOT_FILES and set_permissions - Update docs: migration_guide, update-system (EN/RU) - Update test-install: README.md, auto_install.sh --- Makefile | 4 +- docs/en/administration/update-system.md | 2 +- docs/en/info/migration_guide.md | 4 +- docs/ru/administration/update-system.md | 2 +- docs/ru/info/migration_guide.md | 4 +- install | 4 +- src/cli/Commands/BalancerCommand.php | 14 +- src/cli/Commands/StartupCommand.php | 4 +- src/cli/Commands/UpdateCommand.php | 2 +- src/crons/epg.php | 566 ---------- src/public/Views/admin/dashboard.php | 4 +- src/status | 7 - src/tools | 7 - test_installer | 1293 +++++++++++++++-------- tools/test-install/README.md | 2 +- tools/test-install/auto_install.sh | 2 +- 16 files changed, 888 insertions(+), 1033 deletions(-) delete mode 100644 src/crons/epg.php delete mode 100644 src/status delete mode 100644 src/tools diff --git a/Makefile b/Makefile index 1fd47e2a..98a1c363 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ LB_DIRS := bin cli config content core domain includes \ infrastructure resources signals streaming tmp www # Root-level files to copy from MAIN to LB (not inside directories) -LB_ROOT_FILES := autoload.php bootstrap.php console.php service status update +LB_ROOT_FILES := autoload.php bootstrap.php console.php service update # Directories to remove from LB (admin-only content) LB_DIRS_TO_REMOVE := \ @@ -404,9 +404,7 @@ set_permissions: # Root-level executables chmod 0750 $(TEMP_DIR)/service 2>/dev/null || true - chmod 0750 $(TEMP_DIR)/status 2>/dev/null || true chmod 0755 $(TEMP_DIR)/tmp 2>/dev/null || true - chmod 0750 $(TEMP_DIR)/tools 2>/dev/null || true chmod 0750 $(TEMP_DIR)/update 2>/dev/null || true chmod 0750 $(TEMP_DIR)/signals 2>/dev/null || true diff --git a/docs/en/administration/update-system.md b/docs/en/administration/update-system.md index 428497e0..7c3977e9 100644 --- a/docs/en/administration/update-system.md +++ b/docs/en/administration/update-system.md @@ -121,7 +121,7 @@ Final steps are executed in the `post-update` phase of `UpdateCommand`: 6. Verify panel status: ```bash - sudo /home/xc_vm/status + sudo /home/xc_vm/bin/php/bin/php /home/xc_vm/console.php status ``` 7. Mark the update process as complete. diff --git a/docs/en/info/migration_guide.md b/docs/en/info/migration_guide.md index ae34f43e..077f2b2c 100644 --- a/docs/en/info/migration_guide.md +++ b/docs/en/info/migration_guide.md @@ -111,13 +111,13 @@ If login fails due to missing credentials or access code, use the rescue tools. ### Create a Rescue Access Code ```bash -/home/xc_vm/tools access +php /home/xc_vm/console.php tools access ``` ### Create an Administrator Account ```bash -/home/xc_vm/tools user +php /home/xc_vm/console.php tools user ``` > ⚠️ After regaining access, **immediately change** the access code and administrator credentials. diff --git a/docs/ru/administration/update-system.md b/docs/ru/administration/update-system.md index da1b9ffe..4c4a8fc4 100644 --- a/docs/ru/administration/update-system.md +++ b/docs/ru/administration/update-system.md @@ -124,7 +124,7 @@ sudo /usr/bin/python3 /home/xc_vm/update "/home/xc_vm/tmp/.update.tar.gz" "HASH" 6. Проверка статуса панели: ```bash - sudo /home/xc_vm/status + sudo /home/xc_vm/bin/php/bin/php /home/xc_vm/console.php status ``` 7. Завершение процесса обновления. diff --git a/docs/ru/info/migration_guide.md b/docs/ru/info/migration_guide.md index ca2ed758..a55908f6 100644 --- a/docs/ru/info/migration_guide.md +++ b/docs/ru/info/migration_guide.md @@ -111,13 +111,13 @@ sudo /home/xc_vm/bin/php/bin/php /home/xc_vm/console.php tools migration "/tmp/b ### Создание резервного кода доступа ```bash -/home/xc_vm/tools access +php /home/xc_vm/console.php tools access ``` ### Создание администратора ```bash -/home/xc_vm/tools user +php /home/xc_vm/console.php tools user ``` > ⚠️ После восстановления доступа **немедленно смените** код доступа и учётные данные администратора. diff --git a/install b/install index 9bbfa7a9..f9a76e52 100644 --- a/install +++ b/install @@ -291,7 +291,7 @@ if __name__ == "__main__": printc("Installing XC_VM") if os.path.exists("./xc_vm.tar.gz"): os.system('sudo tar -zxvf "./xc_vm.tar.gz" -C "/home/xc_vm/"') - if not os.path.exists("/home/xc_vm/status"): + if not os.path.exists("/home/xc_vm/console.php"): printc("Failed to extract! Exiting") sys.exit(1) @@ -520,7 +520,7 @@ if __name__ == "__main__": time.sleep(10) - os.system("sudo /home/xc_vm/status 1") + os.system("sudo /home/xc_vm/bin/php/bin/php /home/xc_vm/console.php status 1") os.system("sudo chown xc_vm:xc_vm -R /home/xc_vm/config/") os.system( "sudo /home/xc_vm/bin/php/bin/php /home/xc_vm/console.php startup >/dev/null 2>&1" diff --git a/src/cli/Commands/BalancerCommand.php b/src/cli/Commands/BalancerCommand.php index ad3adccd..a0aa1a97 100644 --- a/src/cli/Commands/BalancerCommand.php +++ b/src/cli/Commands/BalancerCommand.php @@ -139,9 +139,10 @@ class BalancerCommand implements CommandInterface { if ($rType == 1) { if ($this->sendFileSSH($rConn, $rInstallDir . $rInstallFiles, '/tmp/' . $rInstallFiles, true)) { echo "Extracting to directory\n"; - $this->runSSH($rConn, 'sudo rm -rf ' . MAIN_HOME . 'status'); + $this->runSSH($rConn, 'sudo rm -rf ' . MAIN_HOME . 'console.php'); $this->runSSH($rConn, 'sudo tar -zxvf "/tmp/' . $rInstallFiles . '" -C "' . MAIN_HOME . '"'); - if (!file_exists(MAIN_HOME . 'status')) { + $rRemoteCheck = trim($this->runSSH($rConn, 'test -f ' . MAIN_HOME . 'console.php && echo OK')['output']); + if ($rRemoteCheck !== 'OK') { $db->query('UPDATE `servers` SET `status` = 4 WHERE `id` = ?;', $rServerID); echo "Failed to extract files! Exiting\n"; return 1; @@ -157,9 +158,10 @@ class BalancerCommand implements CommandInterface { $fileHash = $this->runSSH($rConn, 'md5=($(md5sum /tmp/XC_VM.tar.gz)); echo $md5;'); if (!empty($fileHash['output']) && $hash == trim($fileHash['output'])) { echo "Extracting to directory\n"; - $this->runSSH($rConn, 'sudo rm -rf ' . MAIN_HOME . 'status'); + $this->runSSH($rConn, 'sudo rm -rf ' . MAIN_HOME . 'console.php'); $this->runSSH($rConn, 'sudo tar -zxvf "/tmp/XC_VM.tar.gz" -C "' . MAIN_HOME . '"'); - if (file_exists(MAIN_HOME . 'status')) { + $rRemoteCheck = trim($this->runSSH($rConn, 'test -f ' . MAIN_HOME . 'console.php && echo OK')['output']); + if ($rRemoteCheck === 'OK') { $this->runSSH($rConn, 'sudo rm -f "/tmp/XC_VM.tar.gz"'); } else { $db->query('UPDATE `servers` SET `status` = 4 WHERE `id` = ?;', $rServerID); @@ -174,7 +176,7 @@ class BalancerCommand implements CommandInterface { } if ($rType == 2) { - if (stripos($this->runSSH($rConn, 'sudo cat /etc/fstab')['output'], STREAMS_PATH) !== true) { + if (stripos($this->runSSH($rConn, 'sudo cat /etc/fstab')['output'], STREAMS_PATH) === false) { echo "Adding ramdisk mounts\n"; $this->runSSH($rConn, 'sudo echo "tmpfs ' . STREAMS_PATH . ' tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777,size=90% 0 0" >> /etc/fstab'); $this->runSSH($rConn, 'sudo echo "tmpfs ' . TMP_PATH . ' tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777,size=2G 0 0" >> /etc/fstab'); @@ -300,7 +302,7 @@ class BalancerCommand implements CommandInterface { $this->runSSH($rConn, 'sudo service xc_vm restart'); if ($rType == 2) { - $this->runSSH($rConn, 'sudo ' . MAIN_HOME . 'status 1'); + $this->runSSH($rConn, 'sudo ' . PHP_BIN . ' ' . MAIN_HOME . 'console.php status 1'); $this->runSSH($rConn, 'sudo -u xc_vm ' . PHP_BIN . ' ' . MAIN_HOME . 'console.php startup'); $this->runSSH($rConn, 'sudo -u xc_vm ' . PHP_BIN . ' ' . MAIN_HOME . 'console.php cron:servers'); } else { diff --git a/src/cli/Commands/StartupCommand.php b/src/cli/Commands/StartupCommand.php index 282fc695..4041486f 100644 --- a/src/cli/Commands/StartupCommand.php +++ b/src/cli/Commands/StartupCommand.php @@ -29,9 +29,7 @@ class StartupCommand implements CommandInterface { ini_set('display_startup_errors', 1); error_reporting(32767); - if (file_exists(MAIN_HOME . 'status')) { - exec('sudo ' . MAIN_HOME . 'status 1'); - } + exec('sudo ' . PHP_BIN . ' ' . MAIN_HOME . 'console.php status 1'); // ── Восстановление daemons.sh при повреждении ──────── if (filesize(MAIN_HOME . 'bin/daemons.sh') == 0) { diff --git a/src/cli/Commands/UpdateCommand.php b/src/cli/Commands/UpdateCommand.php index 9d2a86ab..e1acf605 100644 --- a/src/cli/Commands/UpdateCommand.php +++ b/src/cli/Commands/UpdateCommand.php @@ -79,7 +79,7 @@ class UpdateCommand implements CommandInterface { exec('sudo chown -R xc_vm:xc_vm ' . MAIN_HOME); exec('sudo systemctl daemon-reload'); exec("sudo echo 'net.ipv4.ip_unprivileged_port_start=0' > /etc/sysctl.d/50-allports-nonroot.conf && sudo sysctl --system"); - exec('sudo ' . MAIN_HOME . 'status'); + exec('sudo ' . PHP_BIN . ' ' . MAIN_HOME . 'console.php status'); break; } diff --git a/src/crons/epg.php b/src/crons/epg.php deleted file mode 100644 index 081b6325..00000000 --- a/src/crons/epg.php +++ /dev/null @@ -1,566 +0,0 @@ -loadEPG($rSource, $rCache); - } - - public function getData() { - $rOutput = []; - $channelCount = 0; - - print_log("[EPG] Starting getData() - parsing channels and languages..."); - - while ($rNode = $this->rEPGSource->getNode()) { - $rData = simplexml_load_string($rNode); - if (!$rData) continue; - - $rNodeName = $rData->getName(); - - if ($rNodeName === 'channel') { - $rChannelID = trim((string) $rData->attributes()->id); - $displayName = !empty($rData->{'display-name'}) ? trim((string) $rData->{'display-name'}) : 'Unknown'; - - if (!array_key_exists($rChannelID, $rOutput)) { - $rOutput[$rChannelID] = [ - 'display_name' => $displayName, - 'langs' => [] - ]; - $channelCount++; - } - continue; - } - - // ---------- PROGRAMME ---------- - if ($rNodeName !== 'programme') { - continue; - } - - $rChannelID = trim((string) $rData->attributes()->channel); - - if (!array_key_exists($rChannelID, $rOutput)) { - continue; - } - - if (empty($rData->title)) { - continue; - } - - foreach ($rData->title as $rTitle) { - $lang = (string) $rTitle->attributes()->lang; - if (!empty($lang) && !in_array($lang, $rOutput[$rChannelID]['langs'], true)) { - $rOutput[$rChannelID]['langs'][] = $lang; - } - } - } - - print_log("[EPG] Finished getData() - found $channelCount channels"); - return $rOutput; - } - - public function parseEPG($rEPGID, $rChannelInfo, $rOffset = 0) { - global $db; - - $rInsertQuery = []; - $programCount = 0; - - print_log("[EPG] Starting parseEPG() for EPG ID: $rEPGID (offset: {$rOffset}min)"); - - while ($rNode = $this->rEPGSource->getNode()) { - $rData = simplexml_load_string($rNode); - if (!$rData) { - continue; - } - - if ($rData->getName() !== 'programme') { - continue; - } - - $rChannelID = (string) $rData->attributes()->channel; - - if (!array_key_exists($rChannelID, $rChannelInfo)) { - continue; - } - - // --- timestamps --- - $rStart = strtotime((string) $rData->attributes()->start) + ($rOffset * 60); - $rStop = strtotime((string) $rData->attributes()->stop) + ($rOffset * 60); - - if ($rStart === false || $rStop === false) { - print_log("[EPG] Warning: Invalid timestamp for channel $rChannelID"); - continue; - } - - $rLangTitle = ''; - $rLangDesc = ''; - - // Title - if (!empty($rData->title)) { - $rTitles = $rData->title; - $preferredLang = $rChannelInfo[$rChannelID]['epg_lang']; - - if (is_object($rTitles)) { - $rFound = false; - foreach ($rTitles as $rTitle) { - if ((string) $rTitle->attributes()->lang === $preferredLang) { - $rLangTitle = (string) $rTitle; - $rFound = true; - break; - } - } - if (!$rFound && count($rTitles) > 0) { - $rLangTitle = (string) $rTitles[0]; - } - } else { - $rLangTitle = (string) $rTitles; - } - } else { - continue; - } - - // Description - if (!empty($rData->desc)) { - $rDescriptions = $rData->desc; - $preferredLang = $rChannelInfo[$rChannelID]['epg_lang']; - - if (is_object($rDescriptions)) { - $rFound = false; - foreach ($rDescriptions as $rDescription) { - if ((string) $rDescription->attributes()->lang === $preferredLang) { - $rLangDesc = (string) $rDescription; - $rFound = true; - $rLangDesc = $rDescription; - break; - } - } - if (!$rFound && count($rDescriptions) > 0) { - $rLangDesc = (string) $rDescriptions[0]; - } - } else { - $rLangDesc = (string) $rDescriptions; - } - } - - $rInsertQuery[] = '(' . - $db->escape($rEPGID) . ', ' . - $db->escape($rChannelID) . ', ' . - intval($rStart) . ', ' . - intval($rStop) . ', ' . - $db->escape($rChannelInfo[$rChannelID]['epg_lang']) . ', ' . - $db->escape($rLangTitle) . ', ' . - $db->escape($rLangDesc ?? '') . - ')'; - - $programCount++; - if ($programCount % 1000 === 0) { - print_log("[EPG] Parsed $programCount programmes so far..."); - } - } - - print_log("[EPG] Finished parseEPG() - collected $programCount programmes"); - return !empty($rInsertQuery) ? $rInsertQuery : false; - } - - public function downloadFile($rSource, $rFilename) { - print_log("[EPG] Downloading EPG file: $rSource"); - - $rExtension = pathinfo($rSource, PATHINFO_EXTENSION); - $rDecompress = ''; - - if ($rExtension === 'gz') { - $rDecompress = ' | gunzip -c'; - } elseif ($rExtension === 'xz') { - $rDecompress = ' | unxz -c'; - } - - $rCommand = 'wget -U "Mozilla/5.0" --timeout=30 --tries=3 -O - "' . $rSource . '"' . $rDecompress . ' > ' . $rFilename; - $rResult = shell_exec($rCommand); - - if (file_exists($rFilename) && filesize($rFilename) > 0) { - print_log("[EPG] Download successful: " . filesize($rFilename) . " bytes"); - return true; - } else { - print_log("[EPG] Download failed or file is empty: $rSource"); - return false; - } - } - - public function loadEPG($rSource, $rCache) { - try { - $this->rFilename = TMP_PATH . md5($rSource) . '.xml'; - - // If caching is enabled, check for existing file - if (!file_exists($this->rFilename) || !$rCache) { - if (!$this->downloadFile($rSource, $this->rFilename)) { - print_log("[EPG] Failed to load EPG source: $rSource"); - return; - } - } else { - print_log("[EPG] Using cached EPG file: " . basename($this->rFilename)); - } - - if (!$this->rFilename) { - FileLogger::log('epg', 'No XML found at: ' . $rSource); - return; - } - - $rXML = XmlStringStreamer::createStringWalkerParser($this->rFilename); - - if (!$rXML) { - FileLogger::log('epg', 'Not a valid EPG source: ' . $rSource); - print_log("[EPG] Failed to create XML parser for: $rSource"); - return; - } - - $this->rEPGSource = $rXML; - $this->rValid = true; - print_log("[EPG] EPG source loaded successfully: $rSource"); - } catch (Exception $e) { - FileLogger::log('epg', 'EPG failed to process: ' . $rSource); - print_log("[EPG] Exception while loading EPG: " . $e->getMessage() . " | Source: $rSource"); - } - } -} - -function print_log($message) { - echo "[" . date('Y-m-d H:i:s') . "] " . $message . "\n"; -} - -function reconect_db() { - global $db; - if ($db->ping()) { - print_log("[EPG] Database connection is alive."); - } else { - print_log("[EPG] Database connection lost. Attempting to reconnect..."); - $db->db_connect(); - if ($db->ping()) { - print_log("[EPG] Reconnected to the database successfully."); - } else { - print_log("[EPG] Failed to reconnect to the database. Exiting."); - exit(1); - } - } -} - -function getBouquetGroups() { - global $db; - print_log("[XMLTV] Building bouquet groups..."); - $db->query('SELECT DISTINCT(`bouquet`) AS `bouquet` FROM `lines`;'); - $ApiDependencyIdentifier = [ - 'all' => [ - 'streams' => [], - 'bouquets' => [] - ] - ]; - - foreach ($db->get_rows() as $rRow) { - $rBouquets = json_decode($rRow['bouquet'] ?? null, true); - - if (!is_array($rBouquets) || empty($rBouquets)) { - print_log("[XMLTV] Skipping invalid/empty bouquet value: " . var_export($rBouquets, true)); - continue; - } - - sort($rBouquets); - $ApiDependencyIdentifier[implode('_', $rBouquets)] = [ - 'streams' => [], - 'bouquets' => $rBouquets - ]; - } - $count = count($ApiDependencyIdentifier ?? []); - print_log("[XMLTV] Found $count bouquet groups (including 'all')"); - - foreach ($ApiDependencyIdentifier as $rGroup => $CacheFlushInterval) { - $FileReference = []; - - foreach ($CacheFlushInterval['bouquets'] as $rBouquetID) { - $db->query('SELECT `bouquet_channels` FROM `bouquets` WHERE `id` = ?;', $rBouquetID); - - foreach ($db->get_rows() as $rRow) { - $FileReference[] = $rBouquetID; - $ApiDependencyIdentifier[$rGroup]['streams'] = array_merge($ApiDependencyIdentifier[$rGroup]['streams'], json_decode($rRow['bouquet_channels'], true)); - } - - $ApiDependencyIdentifier[$rGroup]['streams'] = array_unique($ApiDependencyIdentifier[$rGroup]['streams']); - } - - $ApiDependencyIdentifier[$rGroup]['bouquets'] = $FileReference; - } - - return $ApiDependencyIdentifier; -} - -function getEPG($rStreamID) { - return file_exists(EPG_PATH . 'stream_' . $rStreamID) ? igbinary_unserialize(file_get_contents(EPG_PATH . 'stream_' . $rStreamID)) : []; -} - -function shutdown() { - global $db; - if (is_object($db)) { - $db->close_mysql(); - } - print_log("[EPG] Script finished and database connection closed."); -} - - -if (posix_getpwuid(posix_geteuid())['name'] != 'xc_vm') { - exit('Please run as XC_VM!' . "\n"); -} - -if (!@$argc) { - exit(0); -} - -$rEPGID = null; -if (count($argv) == 2) { - $rEPGID = intval($argv[1]); -} - -print_log("=== XC_VM[EPG] Process started ==="); -print_log("Mode: " . ($rEPGID ? "Single EPG ID: $rEPGID" : "Full update")); - -set_time_limit(0); -ini_set('memory_limit', -1); -register_shutdown_function('shutdown'); -require __DIR__ . '/../www/init.php'; -require INCLUDES_PATH . 'libs/XmlStringStreamer.php'; - -shell_exec('kill -9 `ps -ef | grep \'XC_VM\\[EPG\\]\' | grep -v grep | awk \'{print $2}\'`;'); -cli_set_process_title('XC_VM[EPG]'); - -if (SettingsManager::getAll()['force_epg_timezone']) { - date_default_timezone_set('UTC'); - print_log("[SYSTEM] Forced timezone to UTC"); -} - -print_log("[EPG] Clearing old channel mappings..."); -if ($rEPGID) { - $db->query('DELETE FROM `epg_channels` WHERE `epg_id` = ?;', $rEPGID); - $db->query('SELECT * FROM `epg` WHERE `id` = ?;', $rEPGID); -} else { - $db->query('TRUNCATE `epg_channels`;'); - $db->query('SELECT * FROM `epg`;'); -} - -$epgSources = $db->get_rows(); -print_log("[EPG] Found " . count($epgSources) . " EPG sources to process"); - -foreach ($epgSources as $rRow) { - print_log("[EPG] Processing source ID: {$rRow['id']} | File: {$rRow['epg_file']}"); - $rEPG = new EPG($rRow['epg_file']); - - if ($rEPG->rValid) { - $rData = $rEPG->getData(); - - reconect_db(); - - $db->query('UPDATE `epg` SET `data` = ?, `last_updated` = ? WHERE `id` = ?', json_encode($rData, JSON_UNESCAPED_UNICODE), time(), $rRow['id']); - - print_log("[EPG] Updated metadata for EPG ID {$rRow['id']}, found " . count($rData) . " channels"); - - foreach ($rData as $rID => $rArray) { - $db->query('INSERT INTO `epg_channels`(`epg_id`, `channel_id`, `name`, `langs`) VALUES(?, ?, ?, ?);', $rRow['id'], $rID, $rArray['display_name'], json_encode($rArray['langs'])); - } - } else { - print_log("[EPG] Failed to load EPG source ID {$rRow['id']}"); - } -} - -print_log("[EPG] Starting full programme data import..."); - -if ($rEPGID) { - $db->query('SELECT DISTINCT(t1.`epg_id`), t2.* FROM `streams` t1 INNER JOIN `epg` t2 ON t2.id = t1.epg_id WHERE t1.`epg_id` IS NOT NULL AND t2.id = ?;', $rEPGID); -} else { - $db->query('SELECT DISTINCT(t1.`epg_id`), t2.* FROM `streams` t1 INNER JOIN `epg` t2 ON t2.id = t1.epg_id WHERE t1.`epg_id` IS NOT NULL;'); -} - -foreach ($db->get_rows() as $rData) { - print_log("[EPG] === Processing EPG ID: {$rData['epg_id']} ==="); - - if ($rData['days_keep'] == 0) { - print_log("[EPG] Clearing all existing data for EPG ID {$rData['epg_id']}"); - $db->query('DELETE FROM `epg_data` WHERE `epg_id` = ?', $rData['epg_id']); - } - - $rEPG = new EPG($rData['epg_file'], true); - if ($rEPG->rValid) { - $db->query('SELECT t1.`channel_id`, t1.`epg_lang`, t1.`epg_offset`, last_row.start - FROM `streams` t1 - LEFT JOIN (SELECT channel_id, MAX(`start`) as start FROM epg_data WHERE epg_id = ? GROUP BY channel_id) last_row - ON last_row.channel_id = t1.channel_id - WHERE `epg_id` = ?;', $rData['epg_id'], $rData['epg_id']); - $channelMap = $db->get_rows(true, 'channel_id'); - - $batches = $rEPG->parseEPG($rData['epg_id'], $channelMap, intval($rData['offset']) ?: 0); - - reconect_db(); - - if ($batches) { - $totalInserted = 0; - foreach ($batches as $insertBatch) { - if (!empty($insertBatch)) { - $db->simple_query('INSERT INTO `epg_data` (`epg_id`,`channel_id`,`start`,`end`,`lang`,`title`,`description`) VALUES ' . $insertBatch); - $totalInserted += substr_count($insertBatch, '),(') + 1; - } - } - print_log("[EPG] Inserted $totalInserted programmes for EPG ID {$rData['epg_id']}"); - } else { - print_log("[EPG] No new programmes found for EPG ID {$rData['epg_id']}"); - } - - $db->query('UPDATE `epg` SET `last_updated` = ? WHERE `id` = ?', time(), $rData['epg_id']); - } else { - print_log("[EPG] Failed to parse EPG file for ID {$rData['epg_id']}"); - } - - if ($rData['days_keep'] > 0) { - $cleanupTime = strtotime('-' . (int)$rData['days_keep'] . ' days'); - if ($cleanupTime !== false) { - $db->query('DELETE FROM `epg_data` WHERE `epg_id` = ? AND `start` < ?', $rData['epg_id'], $cleanupTime); - echo "[EPG] Cleaned up old data (older than {$rData['days_keep']} days)\n"; - } else { - echo "[EPG] Invalid days_keep value, skipping cleanup\n"; - } - } -} - -print_log("[EPG] Removing duplicate EPG entries..."); -$db->query('DELETE n1 FROM `epg_data` n1, `epg_data` n2 WHERE n1.id < n2.id AND n1.epg_id = n2.epg_id AND n1.channel_id = n2.channel_id AND n1.start = n2.start;'); - -print_log("[EPG] Cleaning temporary XML files..."); -shell_exec('rm -f ' . TMP_PATH . '*.xml'); - -print_log("[XMLTV] Starting XMLTV generation..."); -$ApiDependencyIdentifier = getBouquetGroups(); - -$totalBouquets = count($ApiDependencyIdentifier); -print_log("[XMLTV] Generating XMLTV for $totalBouquets bouquet(s)"); - -foreach ($ApiDependencyIdentifier as $rBouquet => $BatchProcessId) { - if (!(strlen($rBouquet) > 0 && (count($BatchProcessId['streams']) > 0 || $rBouquet == 'all'))) { - continue; - } - - print_log("[XMLTV] Generating EPG for bouquet: " . ($rBouquet === 'all' ? 'ALL' : $rBouquet)); - - $rOutput = ''; - $rServerName = htmlspecialchars(SettingsManager::getAll()['server_name'], ENT_XML1 | ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); - $rOutput .= '' . "\n"; - $rOutput .= '' . "\n"; - - if ($rBouquet == 'all') { - $db->query('SELECT `stream_display_name`,`stream_icon`,`channel_id`,`epg_id`,`tv_archive_duration` FROM `streams` WHERE `epg_id` IS NOT NULL AND `channel_id` IS NOT NULL;'); - } else { - $db->query('SELECT `stream_display_name`,`stream_icon`,`channel_id`,`epg_id`,`tv_archive_duration` FROM `streams` WHERE `epg_id` IS NOT NULL AND `channel_id` IS NOT NULL AND `id` IN (' . implode(',', array_map('intval', $BatchProcessId['streams'])) . ');'); - } - - $channels = $db->get_rows(); - $channelCount = count($channels); - print_log("[XMLTV] Found $channelCount channels in this bouquet"); - - $fa4629d757fa3640 = []; - $hasArchive = 0; - - foreach ($channels as $rRow) { - if ($rRow['tv_archive_duration'] > 0) $hasArchive++; - - $displayName = htmlspecialchars($rRow['stream_display_name'], ENT_XML1 | ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); - $icon = htmlspecialchars(ImageUtils::validateURL($rRow['stream_icon']), ENT_XML1 | ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); - $channelID = htmlspecialchars($rRow['channel_id'], ENT_XML1 | ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); - - $rOutput .= "\t"; - $rOutput .= "\t\t$displayName"; - if (!empty($rRow['stream_icon'])) { - $rOutput .= "\t\t"; - } - $rOutput .= "\t"; - - $fa4629d757fa3640[] = $rRow['epg_id']; - } - - $fa4629d757fa3640 = array_unique($fa4629d757fa3640); - - if (count($fa4629d757fa3640) > 0) { - if ($hasArchive > 0) { - print_log("[XMLTV] Archive channels detected ($hasArchive), including all historical programmes"); - $db->query('SELECT * FROM `epg_data` WHERE `epg_id` IN (' . implode(',', array_map('intval', $fa4629d757fa3640)) . ');'); - } else { - print_log("[XMLTV] No archive channels, filtering only current/future programmes"); - $db->query('SELECT * FROM `epg_data` WHERE `epg_id` IN (' . implode(',', array_map('intval', $fa4629d757fa3640)) . ') AND `end` >= UNIX_TIMESTAMP();'); - } - - $programmes = $db->get_rows(); - $progCount = count($programmes); - print_log("[XMLTV] Adding $progCount programmes to XML"); - - $seen = []; - foreach ($programmes as $rRow) { - $key = $rRow['channel_id'] . '|' . $rRow['start']; - if (isset($seen[$key])) continue; - $seen[$key] = true; - - $rTitle = htmlspecialchars($rRow['title'] ?? '', ENT_XML1 | ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); - $rDescription = htmlspecialchars($rRow['description'] ?? '', ENT_XML1 | ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); - $rChannelID = htmlspecialchars($rRow['channel_id'], ENT_XML1 | ENT_QUOTES | ENT_DISALLOWED, 'UTF-8'); - $rStart = date('YmdHis', $rRow['start']) . ' ' . str_replace(':', '', date('P', $rRow['start'])); - $rEnd = date('YmdHis', $rRow['end']) . ' ' . str_replace(':', '', date('P', $rRow['end'])); - - $rOutput .= "\t"; - $rOutput .= "\t\t$rTitle"; - $rOutput .= "\t\t$rDescription"; - $rOutput .= "\t"; - } - } - - $rOutput .= ''; - $fileName = ($rBouquet == 'all' ? 'all' : md5($rBouquet)); - $xmlPath = EPG_PATH . 'epg_' . $fileName . '.xml'; - $gzPath = EPG_PATH . 'epg_' . $fileName . '.xml.gz'; - - file_put_contents($xmlPath, $rOutput); - $gz = gzopen($gzPath, 'w9'); - gzwrite($gz, $rOutput); - gzclose($gz); - - print_log("[XMLTV] Saved epg_$fileName.xml.gz (" . number_format(strlen($rOutput)) . " bytes)"); -} - -print_log("[CACHE] Building per-stream EPG cache..."); -$db->query('SELECT `id`, `epg_id`, `channel_id` FROM `streams` WHERE `type` = 1 AND `epg_id` IS NOT NULL AND `channel_id` IS NOT NULL;'); -$streams = $db->get_rows(); -print_log("[CACHE] Caching EPG for " . count($streams) . " live streams"); - -foreach ($streams as $rRow) { - $rEPG = []; - $seen = []; - - $db->query('SELECT * FROM `epg_data` WHERE `epg_id` = ? AND `channel_id` = ? ORDER BY `start` ASC;', $rRow['epg_id'], $rRow['channel_id']); - foreach ($db->get_rows() as $prog) { - if (!in_array($prog['start'], $seen)) { - $seen[] = $prog['start']; - $rEPG[] = $prog; - } - } - - if (count($rEPG) > 0) { - file_put_contents(EPG_PATH . 'stream_' . $rRow['id'], igbinary_serialize($rEPG)); - } -} - -print_log("[CLEANUP] Removing old cache files..."); -$deleted = 0; -foreach (scandir(EPG_PATH) as $rFile) { - if ($rFile === '.' || $rFile === '..') continue; - $fullPath = EPG_PATH . $rFile; - if (filemtime($fullPath) < (time() - 10)) { - unlink($fullPath); - $deleted++; - } -} -print_log("[CLEANUP] Deleted $deleted old cache files"); - -print_log("=== EPG processing completed successfully! ==="); diff --git a/src/public/Views/admin/dashboard.php b/src/public/Views/admin/dashboard.php index 65c5c6ed..7ec54752 100644 --- a/src/public/Views/admin/dashboard.php +++ b/src/public/Views/admin/dashboard.php @@ -281,7 +281,7 @@
Database Incomplete - Your database is outdated, please run /home/xc_vm/status as root user to update your tables.
+ Your database is outdated, please run sudo /home/xc_vm/console.php status as root user to update your tables.


@@ -295,7 +295,7 @@
Root Crons Missing - Root cronjob hasn't run recently, please check root crontab or run /home/xc_vm/status
+ Root cronjob hasn't run recently, please check root crontab or run sudo /home/xc_vm/console.php status


diff --git a/src/status b/src/status deleted file mode 100644 index c302b906..00000000 --- a/src/status +++ /dev/null @@ -1,7 +0,0 @@ -#!/home/xc_vm/bin/php/bin/php - 0: - printc(f"Usando archivo local de parche {display_name}: {local_patch_file}", col.OKGREEN) + printc( + f"Usando archivo local de parche {display_name}: {local_patch_file}", + col.OKGREEN, + ) temp_tar_file = local_patch_file else: - printc(f"Archivo local no encontrado. Descargando binarios {display_name} desde {remote_url}...", col.OKBLUE) + printc( + f"Archivo local no encontrado. Descargando binarios {display_name} desde {remote_url}...", + col.OKBLUE, + ) urllib.request.urlretrieve(remote_url, temp_tar_file) if not (os.path.exists(temp_tar_file) and os.path.getsize(temp_tar_file) > 0): @@ -262,8 +471,10 @@ def install_distribution_binaries(dist_id, version): shutil.rmtree(temp_extract_dir) # 3. Extraer a directorio temporal - printc(f"Extrayendo binarios de {display_name} a ubicación temporal...", col.OKBLUE) - with tarfile.open(temp_tar_file, 'r:gz') as tar: + printc( + f"Extrayendo binarios de {display_name} a ubicación temporal...", col.OKBLUE + ) + with tarfile.open(temp_tar_file, "r:gz") as tar: tar.extractall(path=temp_extract_dir) # Detectar automáticamente la estructura del directorio @@ -272,9 +483,11 @@ def install_distribution_binaries(dist_id, version): # Posibles rutas donde pueden estar los binarios possible_paths = [ os.path.join(temp_extract_dir, distro_dirname, "bin"), # debian11/bin/ - os.path.join(temp_extract_dir, "bin"), # bin/ directamente - os.path.join(temp_extract_dir, distro_dirname), # debian11/ (si bin está adentro) - temp_extract_dir # raíz del extracto + os.path.join(temp_extract_dir, "bin"), # bin/ directamente + os.path.join( + temp_extract_dir, distro_dirname + ), # debian11/ (si bin está adentro) + temp_extract_dir, # raíz del extracto ] source_bin_dir = None @@ -282,9 +495,11 @@ def install_distribution_binaries(dist_id, version): if os.path.exists(path): # Verificar si contiene archivos binarios o directorios típicos contents = os.listdir(path) - has_binaries = any(item in contents for item in ['php', 'nginx', 'nginx_rtmp', 'bin']) + has_binaries = any( + item in contents for item in ["php", "nginx", "nginx_rtmp", "bin"] + ) - if has_binaries or path.endswith('/bin'): + if has_binaries or path.endswith("/bin"): source_bin_dir = path printc(f"Estructura encontrada: {source_bin_dir}", col.OKGREEN) break @@ -294,31 +509,45 @@ def install_distribution_binaries(dist_id, version): printc("Buscando estructura de directorios recursivamente...", col.OKBLUE) for root, dirs, files in os.walk(temp_extract_dir): # Buscar directorios que contengan binarios típicos - if any(item in dirs for item in ['php', 'nginx', 'nginx_rtmp', 'bin']): + if any(item in dirs for item in ["php", "nginx", "nginx_rtmp", "bin"]): source_bin_dir = root - printc(f"Estructura encontrada recursivamente: {source_bin_dir}", col.OKGREEN) + printc( + f"Estructura encontrada recursivamente: {source_bin_dir}", + col.OKGREEN, + ) break target_bin_dir = "/home/xc_vm/bin" if not source_bin_dir: - printc(f"Error: No se pudo encontrar la estructura de binarios en el parche.", col.FAIL) - printc(f"Contenido de {temp_extract_dir}: {os.listdir(temp_extract_dir)}", col.WARNING) + printc( + f"Error: No se pudo encontrar la estructura de binarios en el parche.", + col.FAIL, + ) + printc( + f"Contenido de {temp_extract_dir}: {os.listdir(temp_extract_dir)}", + col.WARNING, + ) # Mostrar estructura completa para debug printc("Estructura completa del directorio extraído:", col.WARNING) for root, dirs, files in os.walk(temp_extract_dir): - level = root.replace(temp_extract_dir, '').count(os.sep) - indent = ' ' * 2 * level + level = root.replace(temp_extract_dir, "").count(os.sep) + indent = " " * 2 * level printc(f"{indent}{os.path.basename(root)}/", col.WARNING) - subindent = ' ' * 2 * (level + 1) + subindent = " " * 2 * (level + 1) for file in files[:10]: # Limitar a 10 archivos para no saturar printc(f"{subindent}{file}", col.WARNING) if len(files) > 10: - printc(f"{subindent}... y {len(files) - 10} archivos más", col.WARNING) + printc( + f"{subindent}... y {len(files) - 10} archivos más", col.WARNING + ) return False # 4. Reemplazar archivos específicos, no todo el directorio - printc(f"Reemplazando binarios específicos con versiones de {display_name}...", col.OKBLUE) + printc( + f"Reemplazando binarios específicos con versiones de {display_name}...", + col.OKBLUE, + ) # Recorrer recursivamente los archivos en el directorio fuente for root, dirs, files in os.walk(source_bin_dir): @@ -353,7 +582,7 @@ def install_distribution_binaries(dist_id, version): "/home/xc_vm/bin/php/bin/php", "/home/xc_vm/bin/php/sbin/php-fpm", "/home/xc_vm/bin/nginx/sbin/nginx", - "/home/xc_vm/bin/nginx_rtmp/sbin/nginx_rtmp" + "/home/xc_vm/bin/nginx_rtmp/sbin/nginx_rtmp", ] for exe_path in executables_to_chmod: if os.path.exists(exe_path): @@ -366,7 +595,10 @@ def install_distribution_binaries(dist_id, version): if os.path.exists(temp_extract_dir): shutil.rmtree(temp_extract_dir) - printc(f"Instalación de binarios específicos de {display_name} completada", col.OKGREEN) + printc( + f"Instalación de binarios específicos de {display_name} completada", + col.OKGREEN, + ) return True except Exception as e: @@ -378,21 +610,24 @@ def install_distribution_binaries(dist_id, version): shutil.rmtree(temp_extract_dir) return False + # Funciones específicas para cada versión (mantenidas para compatibilidad) def install_debian12_binaries(): """Wrapper function for Debian 12""" - return install_distribution_binaries('debian', '12') + return install_distribution_binaries("debian", "12") + def install_ubuntu20_binaries(): """Wrapper function for Ubuntu 20""" - return install_distribution_binaries('ubuntu', '20') + return install_distribution_binaries("ubuntu", "20") + def detect_distribution(): """Detect Linux distribution and version without external modules""" dist_id = "unknown" version = "unknown" family = "unknown" - + # Try /etc/os-release first (standard method) if os.path.exists("/etc/os-release"): try: @@ -406,7 +641,7 @@ def detect_distribution(): version = line.split("=")[1].strip().strip('"') except: pass - + # Try older methods if dist_id == "unknown": if os.path.exists("/etc/redhat-release"): @@ -442,42 +677,44 @@ def detect_distribution(): version = line.split("=")[1].strip().strip('"') except: pass - + # Determine family - if dist_id in ['centos', 'rhel', 'rocky', 'almalinux', 'fedora']: - family = 'redhat' - elif dist_id in ['ubuntu', 'debian']: - family = 'debian' + if dist_id in ["centos", "rhel", "rocky", "almalinux", "fedora"]: + family = "redhat" + elif dist_id in ["ubuntu", "debian"]: + family = "debian" else: family = dist_id - + return { - 'id': dist_id, - 'family': family, - 'version': version, - 'full_version': version + "id": dist_id, + "family": family, + "version": version, + "full_version": version, } + def check_supported_distro(dist_info): """Check if distribution is supported""" - dist_id = dist_info['id'] - version = dist_info['version'] - + dist_id = dist_info["id"] + version = dist_info["version"] + if dist_id in SUPPORTED_DISTROS: if version in SUPPORTED_DISTROS[dist_id]: return True else: # Check if any supported version starts with the same major version for supported_version in SUPPORTED_DISTROS[dist_id]: - if supported_version.startswith(version.split('.')[0]): + if supported_version.startswith(version.split(".")[0]): return True - + # If not in list but is a known family, we'll try anyway - if dist_info['family'] in ['debian', 'redhat']: + if dist_info["family"] in ["debian", "redhat"]: return True - + return False + def run_command(cmd, shell=True, capture_output=False): """Run shell command with error handling""" try: @@ -490,65 +727,68 @@ def run_command(cmd, shell=True, capture_output=False): except Exception as e: return 1, None, str(e) + def is_valid_zip(file_path): """Check if a file is a valid ZIP archive""" try: - with zipfile.ZipFile(file_path, 'r') as zip_ref: + with zipfile.ZipFile(file_path, "r") as zip_ref: # Try to list contents zip_ref.namelist() return True except: return False + def is_valid_tar(file_path): """Check if a file is a valid TAR archive""" try: - with tarfile.open(file_path, 'r:*') as tar_ref: + with tarfile.open(file_path, "r:*") as tar_ref: # Try to list contents tar_ref.getmembers() return True except: return False + def download_xc_vm(): """Download XC_VM from GitHub releases - SIGUIENDO LA MISMA LÓGICA DEL BASH""" printc("Checking for XC_VM installation files...", col.OKBLUE) - + # Check if valid files already exist (igual que antes) if os.path.exists("./xc_vm.tar.gz") and is_valid_tar("./xc_vm.tar.gz"): printc("Valid xc_vm.tar.gz found locally", col.OKGREEN) return True - + if os.path.exists("./XC_VM.zip") and is_valid_zip("./XC_VM.zip"): printc("Valid XC_VM.zip found locally", col.OKGREEN) return True - + printc("Not found. Trying to download from GitHub...", col.OKBLUE) - + try: # 1. Obtener la última versión de la API de GitHub (IGUAL QUE BASH) printc("Getting latest version from GitHub API...", col.OKBLUE) api_url = "https://api.github.com/repos/Vateron-Media/XC_VM/releases/latest" req = urllib.request.Request(api_url) - req.add_header('User-Agent', 'XC_VM-Installer/1.0') - + req.add_header("User-Agent", "XC_VM-Installer/1.0") + with urllib.request.urlopen(req, timeout=10) as response: data = json.loads(response.read().decode()) - latest_version = data['tag_name'] + latest_version = data["tag_name"] printc(f"Latest version: {latest_version}", col.OKGREEN) - + # 2. Descargar XC_VM.zip directamente (IGUAL QUE BASH: wget "https://github.com/Vateron-Media/XC_VM/releases/download/${latest_version}/XC_VM.zip") download_url = f"https://github.com/Vateron-Media/XC_VM/releases/download/{latest_version}/XC_VM.zip" printc(f"Downloading: {download_url}", col.OKBLUE) - + # Usar urllib para descargar urllib.request.urlretrieve(download_url, "XC_VM.zip") - + # Verificar la descarga if os.path.exists("XC_VM.zip") and os.path.getsize("XC_VM.zip") > 0: file_size = os.path.getsize("XC_VM.zip") printc(f"Download successful: XC_VM.zip ({file_size} bytes)", col.OKGREEN) - + # Validar que sea un ZIP válido if is_valid_zip("XC_VM.zip"): printc("ZIP archive validated successfully", col.OKGREEN) @@ -560,50 +800,51 @@ def download_xc_vm(): else: printc("Download failed or file is empty", col.FAIL) return False - + except Exception as e: printc(f"Download error: {e}", col.FAIL) - + # Intentar métodos alternativos si falla printc("Trying alternative download methods...", col.WARNING) - + # Método alternativo 1: Descarga directa del latest try: alt_url = "https://github.com/Vateron-Media/XC_VM/releases/latest/download/XC_VM.zip" printc(f"Trying alternative: {alt_url}", col.OKBLUE) urllib.request.urlretrieve(alt_url, "XC_VM.zip") - + if os.path.exists("XC_VM.zip") and is_valid_zip("XC_VM.zip"): printc("Alternative download successful", col.OKGREEN) return True except: pass - + # Método alternativo 2: Versión específica 1.2.10 try: specific_url = "https://github.com/Vateron-Media/XC_VM/releases/download/1.2.10/XC_VM.zip" printc(f"Trying specific version: {specific_url}", col.OKBLUE) urllib.request.urlretrieve(specific_url, "XC_VM.zip") - + if os.path.exists("XC_VM.zip") and is_valid_zip("XC_VM.zip"): printc("Specific version download successful", col.OKGREEN) return True except: pass - + return False + def install_mariadb_repo(dist_info): """Install MariaDB repository based on distribution""" - dist_id = dist_info['id'] - version = dist_info['version'] - family = dist_info['family'] + dist_id = dist_info["id"] + version = dist_info["version"] + family = dist_info["family"] printc(f"Configuring MariaDB repository for {dist_id} {version}", col.OKBLUE) - if family == 'debian': + if family == "debian": # Debian/Ubuntu - if dist_id == 'ubuntu': + if dist_id == "ubuntu": # Try to get codename from /etc/os-release codename = "jammy" # Default for Ubuntu 22.04 try: @@ -627,13 +868,24 @@ def install_mariadb_repo(dist_info): printc(f"Using Ubuntu codename: {codename}", col.OKGREEN) # Install prerequisites - run_command("apt-get install -y apt-transport-https curl gnupg software-properties-common") + run_command( + "apt-get install -y apt-transport-https curl gnupg software-properties-common" + ) # Special handling for Ubuntu 20.04 LTS (EOL) - if dist_id == 'ubuntu' and version.startswith("20"): - printc("Ubuntu 20.04 LTS detected, using system MariaDB packages...", col.WARNING) - printc("MariaDB 11.4 is not compatible with Ubuntu 20.04 (libc6 incompatibility)", col.OKBLUE) - printc("Using Ubuntu 20.04 default MariaDB packages for compatibility", col.OKGREEN) + if dist_id == "ubuntu" and version.startswith("20"): + printc( + "Ubuntu 20.04 LTS detected, using system MariaDB packages...", + col.WARNING, + ) + printc( + "MariaDB 11.4 is not compatible with Ubuntu 20.04 (libc6 incompatibility)", + col.OKBLUE, + ) + printc( + "Using Ubuntu 20.04 default MariaDB packages for compatibility", + col.OKGREEN, + ) # For Ubuntu 20.04, we'll use the system MariaDB packages # Ubuntu 20.04 default repositories have MariaDB 10.3 which is compatible @@ -646,7 +898,10 @@ def install_mariadb_repo(dist_info): if os.path.exists("/usr/share/keyrings/mariadb.gpg"): os.remove("/usr/share/keyrings/mariadb.gpg") - printc("Will use Ubuntu 20.04 default MariaDB packages (10.3.x)", col.OKGREEN) + printc( + "Will use Ubuntu 20.04 default MariaDB packages (10.3.x)", + col.OKGREEN, + ) # Skip external repository setup for Ubuntu 20.04 except Exception as e: @@ -655,39 +910,49 @@ def install_mariadb_repo(dist_info): else: # For other versions, use the official script - printc("Adding MariaDB repository using official setup script...", col.OKBLUE) - run_command("curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | bash -s -- --mariadb-server-version='mariadb-11.4'") + printc( + "Adding MariaDB repository using official setup script...", col.OKBLUE + ) + run_command( + "curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | bash -s -- --mariadb-server-version='mariadb-11.4'" + ) # CORREGIDO: Añadir MaxMind repository solo para Ubuntu (con manejo de errores) - if dist_id == 'ubuntu': + if dist_id == "ubuntu": printc("Adding MaxMind repository for GeoIP...", col.OKBLUE) try: run_command("add-apt-repository -y ppa:maxmind/ppa") printc("MaxMind repository added successfully", col.OKGREEN) except Exception as e: printc(f"MaxMind PPA failed to add: {e}", col.WARNING) - printc("Continuing without MaxMind PPA (not critical for functionality)", col.OKBLUE) + printc( + "Continuing without MaxMind PPA (not critical for functionality)", + col.OKBLUE, + ) else: printc("Skipping MaxMind PPA (not available for Debian)", col.OKBLUE) # Update package list printc("Updating package list...", col.OKBLUE) run_command("apt-get update") - - elif family == 'redhat': + + elif family == "redhat": # RedHat based distributions run_command("yum install -y curl") - + # Install MariaDB repository using official script printc("Adding MariaDB repository...", col.OKBLUE) - run_command("curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | bash -s -- --mariadb-server-version='mariadb-11.4'") - + run_command( + "curl -LsS https://r.mariadb.com/downloads/mariadb_repo_setup | bash -s -- --mariadb-server-version='mariadb-11.4'" + ) + else: printc(f"Unsupported distribution: {dist_id}", col.WARNING) return False - + return True + def secure_mariadb_installation(root_password, dist_info=None): """Secure MariaDB installation with root password (from bash script)""" printc("Securing MariaDB installation", col.OKBLUE) @@ -706,7 +971,11 @@ def secure_mariadb_installation(root_password, dist_info=None): ret, out, err = run_command(version_cmd, capture_output=True) mariadb_version = out.strip() if out else "" - is_mariadb_103 = "10.3" in mariadb_version or "5.7" in mariadb_version or "5.6" in mariadb_version + is_mariadb_103 = ( + "10.3" in mariadb_version + or "5.7" in mariadb_version + or "5.6" in mariadb_version + ) if is_mariadb_103: printc("MariaDB 10.3 detected, using legacy password syntax", col.OKBLUE) @@ -721,7 +990,9 @@ def secure_mariadb_installation(root_password, dist_info=None): auth_plugin = out.strip() if out else "" if ret == 0 and auth_plugin == "unix_socket": - printc("Using unix_socket authentication, converting to password...", col.OKBLUE) + printc( + "Using unix_socket authentication, converting to password...", col.OKBLUE + ) # Convert from unix_socket to password authentication with version-specific syntax if is_mariadb_103: # MariaDB 10.3 and older syntax @@ -732,7 +1003,7 @@ def secure_mariadb_installation(root_password, dist_info=None): "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');", "DROP DATABASE IF EXISTS test;", "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\\\_%';", - "FLUSH PRIVILEGES;" + "FLUSH PRIVILEGES;", ] else: # MariaDB 10.4+ and MariaDB 11.x syntax @@ -743,15 +1014,15 @@ def secure_mariadb_installation(root_password, dist_info=None): "DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1');", "DROP DATABASE IF EXISTS test;", "DELETE FROM mysql.db WHERE Db='test' OR Db='test\\\\_%';", - "FLUSH PRIVILEGES;" + "FLUSH PRIVILEGES;", ] for sql in sql_commands: if is_mariadb_103 and "SET PASSWORD" in sql: # Special handling for SET PASSWORD command - run_command(f"mariadb -u root -e \"{sql}\"", shell=True) + run_command(f'mariadb -u root -e "{sql}"', shell=True) else: - run_command(f"mariadb -u root -e \"{sql}\"") + run_command(f'mariadb -u root -e "{sql}"') printc("MariaDB secured with password authentication", col.OKGREEN) else: @@ -764,7 +1035,7 @@ def secure_mariadb_installation(root_password, dist_info=None): # MariaDB 11.x syntax set_cmd = f"mariadb -u root -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '{root_password}';\" 2>/dev/null || true" run_command(set_cmd) - + # Create custom security configuration (like 99-custom.cnf from bash script) printc("Creating custom MariaDB security configuration...", col.OKBLUE) custom_conf = """[mysqld] @@ -776,46 +1047,48 @@ slow_query_log = 1 slow_query_log_file = /var/log/mysql/mariadb-slow.log long_query_time = 2 log_error = /var/log/mysql/error.log""" - + conf_dir = "/etc/mysql/mariadb.conf.d/" if not os.path.exists(conf_dir): conf_dir = "/etc/my.cnf.d/" if not os.path.exists(conf_dir): os.makedirs(conf_dir, exist_ok=True) - + custom_path = os.path.join(conf_dir, "99-custom.cnf") with open(custom_path, "w") as f: f.write(custom_conf) - + # Create log directory and set permissions run_command("mkdir -p /var/log/mysql && chown mysql:mysql /var/log/mysql") - + # Restart MariaDB run_command("systemctl restart mariadb") time.sleep(3) - + # NO crear /root/mariadb_root_password.txt, solo usar /root/credentials.txt printc("MariaDB security hardening completed", col.OKGREEN) return root_password + def get_system_ram_mb(): """Get total system RAM in MB""" try: - with open('/proc/meminfo', 'r') as f: + with open("/proc/meminfo", "r") as f: for line in f: - if line.startswith('MemTotal:'): + if line.startswith("MemTotal:"): mem_kb = int(line.split()[1]) return mem_kb // 1024 # Convert to MB except: pass return 1024 # Value by default if unable to determine + def generate_mysql_config(total_ram_mb): """Generate MySQL configuration based on total RAM (from bash script logic)""" - + # Calculate based on RAM (similar to bash script) buffer_pool_mb = int(total_ram_mb * 0.25) - + if total_ram_mb < 512: buffer_pool_mb = 64 max_connections = 30 @@ -839,20 +1112,20 @@ def generate_mysql_config(total_ram_mb): if buffer_pool_mb > 2048: buffer_pool_mb = 2048 max_connections = 200 - + # Format buffer pool size if buffer_pool_mb >= 1024: buffer_pool_size = f"{buffer_pool_mb // 1024}G" else: buffer_pool_size = f"{buffer_pool_mb}M" - + # Calculate other values key_buffer = min(buffer_pool_mb // 8, 32) tmp_table_size = min(buffer_pool_mb // 4, 64) back_log = min(max_connections // 2, 256) thread_cache = min(max_connections // 4, 64) buffer_pool_instances = "1" if buffer_pool_mb < 1024 else "2" - + # Generate config from template config = rMySQLCnfTemplate config = config.replace("{{KEY_BUFFER}}", str(key_buffer)) @@ -862,21 +1135,25 @@ def generate_mysql_config(total_ram_mb): config = config.replace("{{BUFFER_POOL_SIZE}}", buffer_pool_size) config = config.replace("{{BUFFER_POOL_INSTANCES}}", buffer_pool_instances) config = config.replace("{{THREAD_CACHE}}", str(thread_cache)) - + printc(f"RAM detected: {total_ram_mb}MB", col.OKGREEN) - printc(f"Buffer pool configured: {buffer_pool_size} ({buffer_pool_mb}MB)", col.OKGREEN) + printc( + f"Buffer pool configured: {buffer_pool_size} ({buffer_pool_mb}MB)", col.OKGREEN + ) printc(f"Max connections: {max_connections}", col.OKGREEN) - + return config + def generate_random_password(length=32): """Generate random password (similar to bash script)""" - chars = '23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ' - return ''.join(random.choice(chars) for _ in range(length)) + chars = "23456789abcdefghjkmnpqrstuvwxyzABCDEFGHJKMNPQRSTUVWXYZ" + return "".join(random.choice(chars) for _ in range(length)) + def generate_root_password(): """Generate secure root password of 20 characters""" - chars = 'A-Za-z0-9!@#%^*()_+-=,.<>?' + chars = "A-Za-z0-9!@#%^*()_+-=,.<>?" try: # Try using /dev/urandom cmd = f"cat /dev/urandom | tr -dc '{chars}' | head -c 20" @@ -885,11 +1162,13 @@ def generate_root_password(): return out.strip() except: pass - + # Fallback to Python random import string - chars = string.ascii_letters + string.digits + '!@#%^*()_+-=,.<>?' - return ''.join(random.choice(chars) for _ in range(20)) + + chars = string.ascii_letters + string.digits + "!@#%^*()_+-=,.<>?" + return "".join(random.choice(chars) for _ in range(20)) + def getIP(): try: @@ -909,6 +1188,7 @@ def getIP(): pass return "127.0.0.1" + def printc(rText, rColour=col.OKBLUE, rPadding=0): rLeft = int(30 - (len(rText) / 2)) rRight = 60 - rLeft - len(rText) @@ -933,17 +1213,20 @@ def printc(rText, rColour=col.OKBLUE, rPadding=0): ) print(" ") + def extract_archive(archive_path): """Extract archive with proper validation and error handling""" printc(f"Extracting {archive_path}...", col.OKBLUE) - - if archive_path.endswith('.tar.gz') or archive_path.endswith('.tgz'): + + if archive_path.endswith(".tar.gz") or archive_path.endswith(".tgz"): try: - with tarfile.open(archive_path, 'r:gz') as tar: + with tarfile.open(archive_path, "r:gz") as tar: # Get member list for debugging members = tar.getmembers() - printc(f"Archive contains {len(members)} files/directories", col.OKGREEN) - + printc( + f"Archive contains {len(members)} files/directories", col.OKGREEN + ) + # Extract all files tar.extractall(path="/home/xc_vm/") printc("Extraction successful", col.OKGREEN) @@ -951,38 +1234,42 @@ def extract_archive(archive_path): except Exception as e: printc(f"Failed to extract tar.gz: {e}", col.FAIL) return False - - elif archive_path.endswith('.zip'): + + elif archive_path.endswith(".zip"): try: - with zipfile.ZipFile(archive_path, 'r') as zip_ref: + with zipfile.ZipFile(archive_path, "r") as zip_ref: # Get file list for debugging file_list = zip_ref.namelist() printc(f"Archive contains {len(file_list)} files", col.OKGREEN) - + # Extract all files zip_ref.extractall(path="/home/xc_vm/") printc("Extraction successful", col.OKGREEN) - + # Check if zip contains nested tar.gz for file in file_list: - if file.endswith('xc_vm.tar.gz') or file.endswith('.tar.gz'): + if file.endswith("xc_vm.tar.gz") or file.endswith(".tar.gz"): nested_path = os.path.join("/home/xc_vm", file) if os.path.exists(nested_path): - printc(f"Found nested archive: {file}, extracting...", col.OKBLUE) + printc( + f"Found nested archive: {file}, extracting...", + col.OKBLUE, + ) return extract_archive(nested_path) return True except Exception as e: printc(f"Failed to extract zip: {e}", col.FAIL) return False - + else: printc(f"Unsupported archive format: {archive_path}", col.FAIL) return False + def fix_ssh2_library_issue(): """Fix SSH2 library issue by creating proper symlinks""" printc("Configuring SSH2 libraries for PHP", col.OKBLUE) - + # List of possible libssh2.so.1 locations libssh2_paths = [ "/usr/lib/x86_64-linux-gnu/libssh2.so.1", @@ -990,24 +1277,24 @@ def fix_ssh2_library_issue(): "/usr/lib/x86_64-linux-gnu/libssh2.so", "/usr/lib64/libssh2.so.1", "/usr/lib/libssh2.so.1", - "/usr/local/lib/libssh2.so.1" + "/usr/local/lib/libssh2.so.1", ] - + found_lib = None for lib_path in libssh2_paths: if os.path.exists(lib_path): found_lib = lib_path printc(f"Found SSH2 library: {lib_path}", col.OKGREEN) break - + if found_lib: # Create symlinks in common locations symlink_targets = [ "/usr/lib/libssh2.so.1", "/usr/local/lib/libssh2.so.1", - "/lib/libssh2.so.1" + "/lib/libssh2.so.1", ] - + for target in symlink_targets: if not os.path.exists(target): try: @@ -1015,7 +1302,7 @@ def fix_ssh2_library_issue(): printc(f"Created symlink: {found_lib} -> {target}", col.OKGREEN) except: printc(f"Failed to create symlink for {target}", col.WARNING) - + # Also check if we need to symlink in PHP extensions directory php_ext_dir = "/home/xc_vm/bin/php/lib/php/extensions/" if os.path.exists(php_ext_dir): @@ -1026,53 +1313,67 @@ def fix_ssh2_library_issue(): printc(f"Found PHP ssh2.so at: {ssh2_so_path}", col.OKGREEN) break else: - printc("Warning: libssh2.so.1 not found. SSH2 may not work properly.", col.WARNING) - + printc( + "Warning: libssh2.so.1 not found. SSH2 may not work properly.", col.WARNING + ) + # Try to find any libssh2 version try: - ret, out, err = run_command("find /usr -name 'libssh2.so*' 2>/dev/null", capture_output=True) + ret, out, err = run_command( + "find /usr -name 'libssh2.so*' 2>/dev/null", capture_output=True + ) if ret == 0 and out: - libs = out.strip().split('\n') + libs = out.strip().split("\n") if libs: found_lib = libs[0] printc(f"Found alternative SSH2 library: {found_lib}", col.OKGREEN) - + # Create symlink run_command(f"ln -sf {found_lib} /usr/lib/libssh2.so.1") - printc(f"Created symlink: {found_lib} -> /usr/lib/libssh2.so.1", col.OKGREEN) + printc( + f"Created symlink: {found_lib} -> /usr/lib/libssh2.so.1", + col.OKGREEN, + ) except: pass - + # NO deshabilitar la extensión ssh2 - solo crear enlaces + if __name__ == "__main__": ################################################## # START # ################################################## - + printc("XC_VM Multi-Distribution Installer", col.OKGREEN, 2) - + # NUEVO: Verificar si se ejecuta como root check_root() - + # Detect distribution dist_info = detect_distribution() - printc(f"Detected: {dist_info['id']} {dist_info['version']} ({dist_info['family']} family)", col.OKGREEN) - + printc( + f"Detected: {dist_info['id']} {dist_info['version']} ({dist_info['family']} family)", + col.OKGREEN, + ) + # NUEVO: Instalar prerequisitos antes de continuar install_prerequisites(dist_info) - + if not check_supported_distro(dist_info): - printc(f"Warning: {dist_info['id']} {dist_info['version']} is not officially supported", col.WARNING) + printc( + f"Warning: {dist_info['id']} {dist_info['version']} is not officially supported", + col.WARNING, + ) response = input("Continue anyway? (Y/N): ").strip().upper() - if response != 'Y': + if response != "Y": sys.exit(1) printc("Continuing with installation...", col.WARNING) - + # Try to download XC_VM if not present has_valid_archive = False archive_path = None - + # Check for existing valid archives if os.path.exists("./xc_vm.tar.gz") and is_valid_tar("./xc_vm.tar.gz"): has_valid_archive = True @@ -1082,7 +1383,7 @@ if __name__ == "__main__": has_valid_archive = True archive_path = "./XC_VM.zip" printc("Found valid XC_VM.zip", col.OKGREEN) - + # Download if needed if not has_valid_archive: if download_xc_vm(): @@ -1093,10 +1394,15 @@ if __name__ == "__main__": elif os.path.exists("./XC_VM.zip") and is_valid_zip("./XC_VM.zip"): has_valid_archive = True archive_path = "./XC_VM.zip" - + if not has_valid_archive or not archive_path: - printc("XC_VM package not found or invalid. Please download manually.", col.FAIL) - printc("You can download from: https://github.com/Vateron-Media/XC_VM/releases", col.OKBLUE) + printc( + "XC_VM package not found or invalid. Please download manually.", col.FAIL + ) + printc( + "You can download from: https://github.com/Vateron-Media/XC_VM/releases", + col.OKBLUE, + ) sys.exit(1) rHost = "127.0.0.1" @@ -1105,27 +1411,32 @@ if __name__ == "__main__": rPassword = generate_random_password(32) # Password de 32 caracteres rDatabase = "xc_vm" rPort = 3306 - + # Ask for MariaDB root password (or generate) printc("MariaDB Root Password Configuration", col.OKBLUE) print("For security, you should set a strong root password for MariaDB.") print("Leave empty to generate a random password.") - - root_password = input("MariaDB root password (or press Enter to generate): ").strip() + + root_password = input( + "MariaDB root password (or press Enter to generate): " + ).strip() if not root_password: root_password = generate_root_password() # Ahora genera de 20 caracteres printc(f"Generated root password: {root_password}", col.OKGREEN) else: # Si el usuario proporciona una contraseña, asegurar que tenga al menos 20 caracteres if len(root_password) < 20: - printc(f"Warning: Provided password is only {len(root_password)} characters. For security, consider using at least 20 characters.", col.WARNING) + printc( + f"Warning: Provided password is only {len(root_password)} characters. For security, consider using at least 20 characters.", + col.WARNING, + ) response = input("Continue with provided password? (Y/N): ").strip().upper() - if response != 'Y': + if response != "Y": root_password = generate_root_password() printc(f"Using generated root password: {root_password}", col.OKGREEN) else: printc("Using provided root password", col.OKGREEN) - + if os.path.exists("/home/xc_vm/"): printc("XC_VM Directory Exists!", col.WARNING) while True: @@ -1134,14 +1445,14 @@ if __name__ == "__main__": break if rAnswer == "N": sys.exit(1) - + ################################################## # SYSTEM PREPARATION # ################################################## - + printc("Preparing System", col.OKBLUE) - - if dist_info['family'] == 'debian': + + if dist_info["family"] == "debian": # Debian/Ubuntu printc("Cleaning package locks", col.OKBLUE) for rFile in [ @@ -1155,31 +1466,34 @@ if __name__ == "__main__": os.remove(rFile) except: pass - + printc("Updating system", col.OKBLUE) run_command("apt-get update -y") - + # Install MariaDB repository if not install_mariadb_repo(dist_info): printc("Using system MariaDB repository", col.WARNING) - + # Stop conflicting services (like bash script) printc("Stopping conflicting services (Apache/System Nginx)...", col.OKBLUE) run_command("systemctl stop apache2 nginx 2>/dev/null || true") run_command("systemctl disable apache2 nginx 2>/dev/null || true") - + # Remove conflicting packages for rPackage in rRemove: printc(f"Removing {rPackage}", col.OKBLUE) run_command(f"apt-get remove {rPackage} -y") - + # Install packages in groups (like bash script) printc("Installing system packages", col.OKBLUE) - - # Select appropriate package list based on distribution - printc(f"DEBUG: Distribution detected: {dist_info['id']} version: {dist_info['version']}", col.OKBLUE) - if dist_info['id'] == 'ubuntu' and dist_info['version'].startswith("20"): + # Select appropriate package list based on distribution + printc( + f"DEBUG: Distribution detected: {dist_info['id']} version: {dist_info['version']}", + col.OKBLUE, + ) + + if dist_info["id"] == "ubuntu" and dist_info["version"].startswith("20"): printc("Using Ubuntu 20.04 compatible package installation", col.OKBLUE) printc("Skipping OpenSSL 3 (incompatible with Ubuntu 20.04)", col.WARNING) @@ -1190,29 +1504,44 @@ if __name__ == "__main__": run_command("apt-get autoclean || true") # Install all packages from PACKAGES['ubuntu20'] - ubuntu20_packages = PACKAGES.get('ubuntu20', []) + ubuntu20_packages = PACKAGES.get("ubuntu20", []) if ubuntu20_packages: packages_str = " ".join(ubuntu20_packages) - printc(f"Installing Ubuntu 20.04 packages ({len(ubuntu20_packages)} packages)...", col.OKBLUE) - run_command(f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'") + printc( + f"Installing Ubuntu 20.04 packages ({len(ubuntu20_packages)} packages)...", + col.OKBLUE, + ) + run_command( + f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'" + ) # Install OpenSSL 3 compatible library for PHP binaries (Ubuntu 20.04 specific) - printc("Installing OpenSSL 3 compatibility library for PHP binaries...", col.OKBLUE) + printc( + "Installing OpenSSL 3 compatibility library for PHP binaries...", + col.OKBLUE, + ) try: # Download and install libssl3 from Ubuntu 22.04 repository (compatible) run_command( - "wget -qO /tmp/libssl3_ubuntu20.deb \"https://packages.debian.org/trixie/amd64/libssl3/download\" " - "|| wget -qO /tmp/libssl3_ubuntu20.deb \"http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl3_3.0.2-0ubuntu1_amd64.deb\"" + 'wget -qO /tmp/libssl3_ubuntu20.deb "https://packages.debian.org/trixie/amd64/libssl3/download" ' + '|| wget -qO /tmp/libssl3_ubuntu20.deb "http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl3_3.0.2-0ubuntu1_amd64.deb"' ) # Try to install with --force-depends if needed - run_command("dpkg --force-depends -i /tmp/libssl3_ubuntu20.deb 2>/dev/null || dpkg -i /tmp/libssl3_ubuntu20.deb 2>/dev/null || true") + run_command( + "dpkg --force-depends -i /tmp/libssl3_ubuntu20.deb 2>/dev/null || dpkg -i /tmp/libssl3_ubuntu20.deb 2>/dev/null || true" + ) # Alternative method if library not present if not os.path.exists("/usr/lib/x86_64-linux-gnu/libssl.so.3"): - printc("Trying alternative OpenSSL 3 installation method...", col.OKBLUE) + printc( + "Trying alternative OpenSSL 3 installation method...", + col.OKBLUE, + ) run_command("find /usr -name 'libssl.so*' 2>/dev/null | head -5") - run_command("ls -la /usr/lib/x86_64-linux-gnu/libssl* 2>/dev/null || ls -la /usr/lib/libssl* 2>/dev/null || true") + run_command( + "ls -la /usr/lib/x86_64-linux-gnu/libssl* 2>/dev/null || ls -la /usr/lib/libssl* 2>/dev/null || true" + ) run_command("rm -f /tmp/libssl3_ubuntu20.deb") printc("OpenSSL 3 library installation completed", col.OKGREEN) @@ -1220,10 +1549,19 @@ if __name__ == "__main__": except Exception as e: printc(f"OpenSSL 3 installation failed: {e}", col.WARNING) printc("PHP binaries may not work without libssl.so.3", col.WARNING) - printc("You may need to manually install compatible OpenSSL 3 library", col.WARNING) + printc( + "You may need to manually install compatible OpenSSL 3 library", + col.WARNING, + ) - elif dist_info['id'] == 'ubuntu' and (dist_info['version'].startswith("24") or dist_info['version'].startswith("24.04")): - printc("Using Ubuntu 24.04 compatible package installation (t64 transition)", col.OKBLUE) + elif dist_info["id"] == "ubuntu" and ( + dist_info["version"].startswith("24") + or dist_info["version"].startswith("24.04") + ): + printc( + "Using Ubuntu 24.04 compatible package installation (t64 transition)", + col.OKBLUE, + ) # Fix broken packages first (especially important for Ubuntu 24.04) printc("Fixing broken packages from t64 transition...", col.OKBLUE) @@ -1231,20 +1569,28 @@ if __name__ == "__main__": run_command("apt-get autoremove -y || true") run_command("apt-get autoclean || true") - printc("Ubuntu 24.04 already has OpenSSL 3.13+ - no additional installation needed", col.OKGREEN) + printc( + "Ubuntu 24.04 already has OpenSSL 3.13+ - no additional installation needed", + col.OKGREEN, + ) # Install all packages from PACKAGES['ubuntu24'] - ubuntu24_packages = PACKAGES.get('ubuntu24', []) + ubuntu24_packages = PACKAGES.get("ubuntu24", []) if ubuntu24_packages: packages_str = " ".join(ubuntu24_packages) - printc(f"Installing Ubuntu 24.04 packages ({len(ubuntu24_packages)} packages)...", col.OKBLUE) - run_command(f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'") + printc( + f"Installing Ubuntu 24.04 packages ({len(ubuntu24_packages)} packages)...", + col.OKBLUE, + ) + run_command( + f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'" + ) # Fix broken packages again after installation printc("Final fix for any remaining broken packages...", col.OKBLUE) run_command("apt --fix-broken install -y || true") - elif dist_info['id'] == 'debian' and dist_info['version'].startswith("13"): + elif dist_info["id"] == "debian" and dist_info["version"].startswith("13"): printc("Using Debian 13 compatible package installation", col.OKBLUE) # Fix broken packages first @@ -1253,24 +1599,32 @@ if __name__ == "__main__": run_command("apt-get autoremove -y || true") run_command("apt-get autoclean || true") - printc("Debian 13 already has OpenSSL 3.x - no additional installation needed", col.OKGREEN) + printc( + "Debian 13 already has OpenSSL 3.x - no additional installation needed", + col.OKGREEN, + ) # Install packages from PACKAGES['debian'] - debian_packages = PACKAGES.get('debian13', []) + debian_packages = PACKAGES.get("debian13", []) if debian_packages: # Optionally install in batches of 10 to avoid long command lines batch_size = 10 for i in range(0, len(debian_packages), batch_size): - batch = debian_packages[i:i + batch_size] + batch = debian_packages[i : i + batch_size] packages_str = " ".join(batch) - printc(f"Installing package group {i // batch_size + 1} ({len(batch)} packages)...", col.OKBLUE) - run_command(f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'") + printc( + f"Installing package group {i // batch_size + 1} ({len(batch)} packages)...", + col.OKBLUE, + ) + run_command( + f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'" + ) # Fix broken packages again after installation printc("Final fix for any remaining broken packages...", col.OKBLUE) run_command("apt --fix-broken install -y || true") - elif dist_info['id'] == 'ubuntu' and dist_info['version'].startswith("22"): + elif dist_info["id"] == "ubuntu" and dist_info["version"].startswith("22"): printc("Using Ubuntu 22.04 compatible package installation", col.OKBLUE) # Fix broken packages first @@ -1280,20 +1634,28 @@ if __name__ == "__main__": run_command("apt-get autoclean || true") # Ubuntu 22.04 already has OpenSSL 3.0, no special handling needed - printc("Ubuntu 22.04 has OpenSSL 3.0 - compatible with PHP binaries", col.OKGREEN) + printc( + "Ubuntu 22.04 has OpenSSL 3.0 - compatible with PHP binaries", + col.OKGREEN, + ) # Install all packages from PACKAGES['ubuntu22'] - ubuntu22_packages = PACKAGES.get('ubuntu22', []) + ubuntu22_packages = PACKAGES.get("ubuntu22", []) if ubuntu22_packages: packages_str = " ".join(ubuntu22_packages) - printc(f"Installing Ubuntu 22.04 packages ({len(ubuntu22_packages)} packages)...", col.OKBLUE) - run_command(f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'") + printc( + f"Installing Ubuntu 22.04 packages ({len(ubuntu22_packages)} packages)...", + col.OKBLUE, + ) + run_command( + f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'" + ) # Fix broken packages again after installation printc("Final fix for any remaining broken packages...", col.OKBLUE) run_command("apt --fix-broken install -y || true") - elif dist_info['id'] == 'debian' and dist_info['version'].startswith("11"): + elif dist_info["id"] == "debian" and dist_info["version"].startswith("11"): printc("Using Debian 11 compatible package installation", col.OKBLUE) # Fix broken packages first @@ -1303,30 +1665,40 @@ if __name__ == "__main__": run_command("apt-get autoclean || true") # Debian 11 may need OpenSSL 3 for PHP binaries - printc("Installing OpenSSL 3 compatibility library for PHP binaries...", col.OKBLUE) + printc( + "Installing OpenSSL 3 compatibility library for PHP binaries...", + col.OKBLUE, + ) try: run_command( "wget -qO /tmp/libssl3_debian11.deb " - "\"http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl3_3.0.2-0ubuntu1_amd64.deb\"" + '"http://security.ubuntu.com/ubuntu/pool/main/o/openssl/libssl3_3.0.2-0ubuntu1_amd64.deb"' + ) + run_command( + "dpkg --force-depends -i /tmp/libssl3_debian11.deb 2>/dev/null || dpkg -i /tmp/libssl3_debian11.deb 2>/dev/null || true" ) - run_command("dpkg --force-depends -i /tmp/libssl3_debian11.deb 2>/dev/null || dpkg -i /tmp/libssl3_debian11.deb 2>/dev/null || true") run_command("rm -f /tmp/libssl3_debian11.deb") printc("OpenSSL 3 library installation completed", col.OKGREEN) except Exception as e: printc(f"OpenSSL 3 installation warning: {e}", col.WARNING) # Install packages from PACKAGES['debian11'] - debian11_packages = PACKAGES.get('debian11', []) + debian11_packages = PACKAGES.get("debian11", []) if debian11_packages: packages_str = " ".join(debian11_packages) - printc(f"Installing Debian 11 packages ({len(debian11_packages)} packages)...", col.OKBLUE) - run_command(f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'") + printc( + f"Installing Debian 11 packages ({len(debian11_packages)} packages)...", + col.OKBLUE, + ) + run_command( + f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'" + ) # Fix broken packages again after installation printc("Final fix for any remaining broken packages...", col.OKBLUE) run_command("apt --fix-broken install -y || true") - elif dist_info['id'] == 'debian' and dist_info['version'].startswith("12"): + elif dist_info["id"] == "debian" and dist_info["version"].startswith("12"): printc("Using Debian 12 compatible package installation", col.OKBLUE) # Fix broken packages first @@ -1335,14 +1707,22 @@ if __name__ == "__main__": run_command("apt-get autoremove -y || true") run_command("apt-get autoclean || true") - printc("Debian 12 already has OpenSSL 3.x - no additional installation needed", col.OKGREEN) + printc( + "Debian 12 already has OpenSSL 3.x - no additional installation needed", + col.OKGREEN, + ) # Install packages from PACKAGES['debian'] (generic debian list works for 12) - debian_packages = PACKAGES.get('debian', []) + debian_packages = PACKAGES.get("debian", []) if debian_packages: packages_str = " ".join(debian_packages) - printc(f"Installing Debian 12 packages ({len(debian_packages)} packages)...", col.OKBLUE) - run_command(f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'") + printc( + f"Installing Debian 12 packages ({len(debian_packages)} packages)...", + col.OKBLUE, + ) + run_command( + f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'" + ) # Fix broken packages again after installation printc("Final fix for any remaining broken packages...", col.OKBLUE) @@ -1350,59 +1730,74 @@ if __name__ == "__main__": else: # Fallback для других версий Debian/Ubuntu - debian_packages = PACKAGES.get('debian', []) - printc(f"Using standard Debian/Ubuntu package list for {dist_info['id']} {dist_info['version']}", col.OKBLUE) + debian_packages = PACKAGES.get("debian", []) + printc( + f"Using standard Debian/Ubuntu package list for {dist_info['id']} {dist_info['version']}", + col.OKBLUE, + ) # Install packages if debian_packages: packages_str = " ".join(debian_packages) - printc(f"Installing Debian packages ({len(debian_packages)} packages)...", col.OKBLUE) - run_command(f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'") + printc( + f"Installing Debian packages ({len(debian_packages)} packages)...", + col.OKBLUE, + ) + run_command( + f"DEBIAN_FRONTEND=noninteractive apt-get -yq install {packages_str} || echo 'Some packages may not be available'" + ) # Install SSH2 libraries specifically for unknown distributions printc("Installing SSH2 libraries...", col.OKBLUE) - run_command("apt-get install -y libssh2-1 libssh2-1-dev || apt-get install -y libssh2-1 libssh2-1t64 || true") - + run_command( + "apt-get install -y libssh2-1 libssh2-1-dev || apt-get install -y libssh2-1 libssh2-1t64 || true" + ) + # Fix SSH2 library issue printc("Configuring SSH2 libraries", col.OKBLUE) fix_ssh2_library_issue() - - elif dist_info['family'] == 'redhat': + + elif dist_info["family"] == "redhat": # RedHat based distributions printc("Configuring repositories", col.OKBLUE) - + # Install EPEL run_command("yum install -y epel-release") - + # Install MariaDB repository if not install_mariadb_repo(dist_info): printc("Using system MariaDB repository", col.WARNING) - + printc("Updating system", col.OKBLUE) run_command("yum update -y") - + # Install system packages from PACKAGES['redhat'] - redhat_packages = PACKAGES.get('redhat', []) + redhat_packages = PACKAGES.get("redhat", []) if redhat_packages: packages_str = " ".join(redhat_packages) - printc(f"Installing RedHat packages ({len(redhat_packages)} packages)...", col.OKBLUE) - run_command(f"yum install -y {packages_str} || echo 'Some packages may not be available'") - + printc( + f"Installing RedHat packages ({len(redhat_packages)} packages)...", + col.OKBLUE, + ) + run_command( + f"yum install -y {packages_str} || echo 'Some packages may not be available'" + ) + # Ensure libssh2 libraries are installed printc("Verifying SSH2 libraries for RedHat...", col.OKBLUE) run_command("yum install -y libssh2 libssh2-devel || true") - + # Enable services run_command("systemctl enable mariadb") run_command("systemctl enable crond") - + # Fix SSH2 library issue printc("Configuring SSH2 libraries", col.OKBLUE) fix_ssh2_library_issue() else: printc(f"Unsupported distribution family: {dist_info['family']}", col.FAIL) sys.exit(1) - + # Create user if doesn't exist (like bash script) printc("Creating/verifying xc_vm user", col.OKBLUE) try: @@ -1413,12 +1808,14 @@ if __name__ == "__main__": raise Exception("User not found") except: printc("Creating user xc_vm", col.OKBLUE) - if dist_info['family'] == 'debian': - run_command("adduser --system --shell /bin/false --no-create-home --home /nonexistent --group --disabled-login xc_vm") + if dist_info["family"] == "debian": + run_command( + "adduser --system --shell /bin/false --no-create-home --home /nonexistent --group --disabled-login xc_vm" + ) else: # redhat run_command("groupadd -r xc_vm") run_command("useradd -r -g xc_vm -s /bin/false -M -d /nonexistent xc_vm") - + if not os.path.exists("/home/xc_vm"): os.makedirs("/home/xc_vm", exist_ok=True) run_command("chown xc_vm:xc_vm /home/xc_vm") @@ -1426,86 +1823,110 @@ if __name__ == "__main__": ################################################## # INSTALL XC_VM # ################################################## - + printc("Installing XC_VM", col.OKBLUE) - + # Extract the archive if not extract_archive(archive_path): printc("Failed to extract archive! Exiting", col.FAIL) sys.exit(1) - + # Verify extraction - if not os.path.exists("/home/xc_vm/status"): - printc("Extraction failed: /home/xc_vm/status not found", col.FAIL) + if not os.path.exists("/home/xc_vm/console.php"): + printc("Extraction failed: /home/xc_vm/console.php not found", col.FAIL) sys.exit(1) else: printc("XC_VM extracted successfully", col.OKGREEN) - + # Instalar binarios específicos para distribuciones soportadas - dist_id = dist_info['id'] - version = dist_info['version'] + dist_id = dist_info["id"] + version = dist_info["version"] # Verificar si la distribución tiene parches disponibles - if dist_id in ['ubuntu', 'debian']: + if dist_id in ["ubuntu", "debian"]: # Ubuntu: 20, 22, 24 - if dist_id == 'ubuntu' and any(version.startswith(v) for v in ['20', '22', '24']): + if dist_id == "ubuntu" and any( + version.startswith(v) for v in ["20", "22", "24"] + ): if not install_distribution_binaries(dist_id, version): - printc(f"Warning: Failed to install {dist_id} {version} specific binaries", col.WARNING) + printc( + f"Warning: Failed to install {dist_id} {version} specific binaries", + col.WARNING, + ) # Debian: 11, 12, 13 - elif dist_id == 'debian' and any(version.startswith(v) for v in ['11', '12', '13']): + elif dist_id == "debian" and any( + version.startswith(v) for v in ["11", "12", "13"] + ): if not install_distribution_binaries(dist_id, version): - printc(f"Warning: Failed to install {dist_id} {version} specific binaries", col.WARNING) + printc( + f"Warning: Failed to install {dist_id} {version} specific binaries", + col.WARNING, + ) else: - printc(f"No specific patches available for {dist_id} {version}, using default binaries", col.OKBLUE) + printc( + f"No specific patches available for {dist_id} {version}, using default binaries", + col.OKBLUE, + ) - elif dist_id in ['rocky', 'almalinux', 'rhel', 'centos']: - major = version.split('.')[0] - if major in ['8', '9']: + elif dist_id in ["rocky", "almalinux", "rhel", "centos"]: + major = version.split(".")[0] + if major in ["8", "9"]: if not install_distribution_binaries(dist_id, version): - printc(f"Warning: Failed to install {dist_id} {version} specific binaries", col.WARNING) + printc( + f"Warning: Failed to install {dist_id} {version} specific binaries", + col.WARNING, + ) else: - printc(f"No specific patches available for {dist_id} {version}, using default binaries", col.OKBLUE) + printc( + f"No specific patches available for {dist_id} {version}, using default binaries", + col.OKBLUE, + ) else: - printc(f"No patches available for {dist_id} {version}, using default binaries", col.OKBLUE) + printc( + f"No patches available for {dist_id} {version}, using default binaries", + col.OKBLUE, + ) ################################################## # MariaDB CONFIGURATION # ################################################## - + printc("Configuring MariaDB", col.OKBLUE) - + # Secure MariaDB installation (using bash script logic) secure_mariadb_installation(root_password, dist_info) - + # Get total system RAM and generate config total_ram_mb = get_system_ram_mb() rMySQLCnf = generate_mysql_config(total_ram_mb) - + # Write MySQL configuration (like bash script) printc("Writing MySQL performance configuration", col.OKBLUE) - if dist_info['family'] == 'debian': + if dist_info["family"] == "debian": mysql_conf_path = "/etc/mysql/mariadb.conf.d/50-server.cnf" else: mysql_conf_path = "/etc/my.cnf.d/server.cnf" - + # Ensure directory exists os.makedirs(os.path.dirname(mysql_conf_path), exist_ok=True) - + with io.open(mysql_conf_path, "w", encoding="utf-8") as rFile: rFile.write(rMySQLCnf) - + # Restart MariaDB run_command("systemctl restart mariadb") time.sleep(5) - + # Connect to MariaDB and configure databases printc("Setting up databases and users", col.OKBLUE) - + # Create databases - run_command(f'mariadb -u root -p"{root_password}" -e "CREATE DATABASE IF NOT EXISTS xc_vm; CREATE DATABASE IF NOT EXISTS xc_vm_migrate;"') - + run_command( + f'mariadb -u root -p"{root_password}" -e "CREATE DATABASE IF NOT EXISTS xc_vm; CREATE DATABASE IF NOT EXISTS xc_vm_migrate;"' + ) + # Import database schema printc("Importing database schema", col.OKBLUE) db_schema_path = "/home/xc_vm/bin/install/database.sql" @@ -1513,19 +1934,19 @@ if __name__ == "__main__": run_command(f'mariadb -u root -p"{root_password}" xc_vm < "{db_schema_path}"') else: printc(f"Database schema not found at {db_schema_path}", col.WARNING) - + # Create XC_VM user with all privileges (like bash script) printc("Creating database user", col.OKBLUE) - + # Localhost grants commands_localhost = [ f"CREATE USER IF NOT EXISTS '{rUsername}'@'localhost' IDENTIFIED BY '{rPassword}';", f"GRANT ALL PRIVILEGES ON xc_vm.* TO '{rUsername}'@'localhost';", f"GRANT ALL PRIVILEGES ON xc_vm_migrate.* TO '{rUsername}'@'localhost';", f"GRANT ALL PRIVILEGES ON mysql.* TO '{rUsername}'@'localhost';", - f"GRANT GRANT OPTION ON xc_vm.* TO '{rUsername}'@'localhost';" + f"GRANT GRANT OPTION ON xc_vm.* TO '{rUsername}'@'localhost';", ] - + # 127.0.0.1 grants (REQUIRED for startup.php - like bash script) commands_127 = [ f"CREATE USER IF NOT EXISTS '{rUsername}'@'127.0.0.1' IDENTIFIED BY '{rPassword}';", @@ -1533,29 +1954,29 @@ if __name__ == "__main__": f"GRANT ALL PRIVILEGES ON xc_vm_migrate.* TO '{rUsername}'@'127.0.0.1';", f"GRANT ALL PRIVILEGES ON mysql.* TO '{rUsername}'@'127.0.0.1';", f"GRANT GRANT OPTION ON xc_vm.* TO '{rUsername}'@'127.0.0.1';", - "FLUSH PRIVILEGES;" + "FLUSH PRIVILEGES;", ] - + all_commands = commands_localhost + commands_127 - + for cmd in all_commands: run_command(f'mariadb -u root -p"{root_password}" -e "{cmd}"') - + # Write XC_VM configuration printc("Writing XC_VM configuration", col.OKBLUE) os.makedirs(os.path.dirname(rConfigPath), exist_ok=True) rConfigData = rConfig % (rUsername, rPassword) with io.open(rConfigPath, "w", encoding="utf-8") as rFile: rFile.write(rConfigData) - + printc("MariaDB configuration completed", col.OKGREEN) ################################################## # SYSTEM CONFIGURATION # ################################################## - + printc("Configuring System", col.OKBLUE) - + # Configure tmpfs mounts (like bash script) if not os.path.exists("/etc/fstab"): printc("/etc/fstab not found", col.WARNING) @@ -1563,32 +1984,36 @@ if __name__ == "__main__": try: with open("/etc/fstab", "r") as f: fstab_content = f.read() - + if "/home/xc_vm/" not in fstab_content: printc("Adding tmpfs mounts to /etc/fstab", col.OKBLUE) # Create directories first run_command("mkdir -p /home/xc_vm/content/streams") run_command("mkdir -p /home/xc_vm/tmp") - + with io.open("/etc/fstab", "a", encoding="utf-8") as rFile: - rFile.write("\ntmpfs /home/xc_vm/content/streams tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777,size=90% 0 0\ntmpfs /home/xc_vm/tmp tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777,size=6G 0 0") - + rFile.write( + "\ntmpfs /home/xc_vm/content/streams tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777,size=90% 0 0\ntmpfs /home/xc_vm/tmp tmpfs defaults,noatime,nosuid,nodev,noexec,mode=1777,size=6G 0 0" + ) + # Mount immediately run_command("mount -a") printc("Reloading systemd to recognize fstab changes", col.OKBLUE) run_command("systemctl daemon-reload") except Exception as e: printc(f"Error updating /etc/fstab: {e}", col.WARNING) - + # Remove any restrictive sudoers rules (like bash script) sudoers_file = "/etc/sudoers.d/xc_vm" if os.path.exists(sudoers_file): run_command(f"rm -f {sudoers_file}") - + # Configure HTTP/HTTPS ports (interactive like bash script) printc("Port Configuration", col.OKBLUE) - print("If you want to change the ports, enter new values, or leave empty to use the default ports") - + print( + "If you want to change the ports, enter new values, or leave empty to use the default ports" + ) + while True: http_port = input("HTTP port (default 80): ").strip() if not http_port: @@ -1620,52 +2045,58 @@ if __name__ == "__main__": rFile.write(f"listen {https_port} ssl;") printc(f"Ports configured: HTTP - {http_port}, HTTPS - {https_port}", col.OKGREEN) - + # Configure sysctl (like bash script) printc("Configuring kernel parameters (sysctl)", col.OKBLUE) with io.open("/etc/sysctl.conf", "w", encoding="utf-8") as rFile: rFile.write(rSysCtl) run_command("sysctl -p > /dev/null 2>&1") - + # Configure systemd limits (like bash script) printc("Configuring systemd file limits", col.OKBLUE) systemd_conf = "/etc/systemd/system.conf" if os.path.exists(systemd_conf): with open(systemd_conf, "r") as f: systemd_content = f.read() - + if "DefaultLimitNOFILE=655350" not in systemd_content: with open(systemd_conf, "a") as f: f.write("\nDefaultLimitNOFILE=655350\n") - + user_conf = "/etc/systemd/user.conf" if os.path.exists(user_conf): with open(user_conf, "r") as f: user_content = f.read() - + if "DefaultLimitNOFILE=655350" not in user_content: with open(user_conf, "a") as f: f.write("\nDefaultLimitNOFILE=655350\n") - + # Ask for systemd service (like bash script) printc("Systemd Service Configuration", col.OKBLUE) while True: - enable_systemd = input("Do you want to configure and enable XC_VM as a systemd service? (Y/N): ").strip().upper() + enable_systemd = ( + input( + "Do you want to configure and enable XC_VM as a systemd service? (Y/N): " + ) + .strip() + .upper() + ) if enable_systemd in ["Y", "N"]: break - + if enable_systemd == "Y": printc("Configuring systemd service", col.OKBLUE) service_path = "/etc/systemd/system/xc_vm.service" with io.open(service_path, "w", encoding="utf-8") as rFile: rFile.write(rSystemd) - + run_command("chmod +x /etc/systemd/system/xc_vm.service") run_command("systemctl daemon-reload") run_command("systemctl enable xc_vm") run_command("systemctl start xc_vm") printc("Systemd service configured and started", col.OKGREEN) - + # Configure Redis printc("Configuring Redis", col.OKBLUE) redis_conf_path = "/home/xc_vm/bin/redis/redis.conf" @@ -1680,15 +2111,15 @@ if __name__ == "__main__": ################################################## # ACCESS CODE # ################################################## - + printc("Generating access code", col.OKBLUE) rCodeDir = "/home/xc_vm/bin/nginx/conf/codes/" - + # CORREGIDO: Asegurar que el directorio de códigos exista os.makedirs(rCodeDir, exist_ok=True) - + admin_code = None - + if os.path.exists(rCodeDir): for filename in os.listdir(rCodeDir): if filename.endswith(".conf"): @@ -1704,27 +2135,27 @@ if __name__ == "__main__": break except: pass - + if not admin_code: admin_code = generate_random_password(8) printc(f"Generated access code: {admin_code}", col.OKGREEN) - + # Insert into database - insert_cmd = f'mariadb -u root -p"{root_password}" -e "USE xc_vm; INSERT INTO access_codes(code, type, enabled, groups) VALUES(\'{admin_code}\', 0, 1, \'[1]\');"' + insert_cmd = f"mariadb -u root -p\"{root_password}\" -e \"USE xc_vm; INSERT INTO access_codes(code, type, enabled, groups) VALUES('{admin_code}', 0, 1, '[1]');\"" run_command(insert_cmd) - + # Create nginx configuration template_path = os.path.join(rCodeDir, "template") if os.path.exists(template_path): with open(template_path, "r") as f: template_content = f.read() - + # Replace placeholders (like bash script) template_content = template_content.replace("#WHITELIST#", "") template_content = template_content.replace("#TYPE#", "admin") template_content = template_content.replace("#CODE#", admin_code) template_content = template_content.replace("#BURST#", "500") - + code_conf_path = os.path.join(rCodeDir, f"{admin_code}.conf") with io.open(code_conf_path, "w", encoding="utf-8") as rFile: rFile.write(template_content) @@ -1742,33 +2173,37 @@ if __name__ == "__main__": ################################################## # FINAL CONFIGURATION # ################################################## - + printc("Finalizing installation", col.OKBLUE) - + # Set permissions (like bash script) run_command("chown -R xc_vm:xc_vm /home/xc_vm") - + # Set executable permissions on key files run_command("chmod +x /home/xc_vm/service") - run_command("chmod +x /home/xc_vm/status") + run_command("chmod +x /home/xc_vm/console.php") run_command("chmod +x /home/xc_vm/bin/nginx/sbin/nginx") - + # Check for RTMP nginx nginx_rtmp_path = "/home/xc_vm/bin/nginx_rtmp/sbin/nginx_rtmp" if os.path.exists(nginx_rtmp_path): run_command(f"chmod +x {nginx_rtmp_path}") - + # Set capabilities for binding to privileged ports (like bash script) nginx_bin = "/home/xc_vm/bin/nginx/sbin/nginx" if os.path.exists(nginx_bin): - run_command(f"setcap 'cap_net_bind_service=+ep' {nginx_bin} 2>/dev/null || true") - + run_command( + f"setcap 'cap_net_bind_service=+ep' {nginx_bin} 2>/dev/null || true" + ) + if os.path.exists(nginx_rtmp_path): - run_command(f"setcap 'cap_net_bind_service=+ep' {nginx_rtmp_path} 2>/dev/null || true") - + run_command( + f"setcap 'cap_net_bind_service=+ep' {nginx_rtmp_path} 2>/dev/null || true" + ) + # Save credentials in the format you requested printc("Saving credentials", col.OKBLUE) - + # Save to /root/credentials.txt with io.open("/root/credentials.txt", "w", encoding="utf-8") as rFile: rFile.write("MariaDB Root \n") @@ -1777,7 +2212,7 @@ if __name__ == "__main__": rFile.write(f"XC_VM Username: {rUsername}\n") rFile.write(f"XC_VM Password: {rPassword}\n") rFile.write(f"Database: {rDatabase}\n") - + # Also save to installer directory (like original install script) local_creds_path = os.path.join(rPath, "credentials.txt") with io.open(local_creds_path, "w", encoding="utf-8") as rFile: @@ -1786,60 +2221,62 @@ if __name__ == "__main__": rFile.write(f"MariaDB Password: {rPassword}\n") rFile.write(f"Database: {rDatabase}\n") rFile.write(f"Admin Access Code: {admin_code}\n") - + # Remove the old mariadb_root_password.txt file if it exists if os.path.exists("/root/mariadb_root_password.txt"): os.remove("/root/mariadb_root_password.txt") - + printc("Credentials saved to /root/credentials.txt", col.OKGREEN) printc(f"Credentials also saved to {local_creds_path}", col.OKGREEN) - + # Restart service if systemd was enabled if enable_systemd == "Y": run_command("systemctl restart xc_vm") - + # Mount tmpfs filesystems (like original install script) run_command("mount -a >/dev/null 2>&1 || true") - + # Reload systemd daemon run_command("systemctl daemon-reload") - + # Post-install startup (like bash script) printc("Starting XC_VM processes...", col.OKBLUE) time.sleep(10) - + # Run status command - if os.path.exists("/home/xc_vm/status"): - run_command("/home/xc_vm/status 1") - + if os.path.exists("/home/xc_vm/console.php"): + run_command("/home/xc_vm/bin/php/bin/php /home/xc_vm/console.php status 1") + # Set config permissions run_command("chown -R xc_vm:xc_vm /home/xc_vm/config/") - + # Run startup command via console.php startup_cmd = "/home/xc_vm/console.php" if os.path.exists(startup_cmd): - run_command(f"/home/xc_vm/bin/php/bin/php {startup_cmd} startup >/dev/null 2>&1") - + run_command( + f"/home/xc_vm/bin/php/bin/php {startup_cmd} startup >/dev/null 2>&1" + ) + time.sleep(3) - + # Final restart if enable_systemd == "Y": run_command("systemctl restart xc_vm") - + # Get server IP server_ip = getIP() - + ################################################## # FINISHED - SHOW SUMMARY # ################################################## - + printc("=" * 60, col.OKGREEN) printc("INSTALLATION COMPLETED SUCCESSFULLY!", col.OKGREEN, 1) printc(f"Distribution: {dist_info['id']} {dist_info['version']}", col.OKGREEN) printc(f"Continue Setup: http://{server_ip}:{http_port}/{admin_code}", col.OKBLUE) printc(f"Total RAM: {total_ram_mb}MB", col.OKGREEN) - + printc("Credentials have been saved to:", col.OKBLUE) printc("/root/credentials.txt", col.OKGREEN) printc(f"{local_creds_path}", col.OKGREEN) diff --git a/tools/test-install/README.md b/tools/test-install/README.md index 09d870cd..0ca11ca7 100644 --- a/tools/test-install/README.md +++ b/tools/test-install/README.md @@ -35,7 +35,7 @@ docker exec -it xcvm-test-install bash 1. Распаковывает `XC_VM.zip` 2. Запускает `python3 install` с автоматическими ответами на интерактивные вопросы 3. Проверяет наличие ключевых файлов после установки: - - `/home/xc_vm/status`, `console.php`, `autoload.php`, `bootstrap.php` + - `/home/xc_vm/console.php`, `autoload.php`, `bootstrap.php` - `/home/xc_vm/config/config.ini` - `/home/xc_vm/bin/nginx/sbin/nginx` - `/home/xc_vm/bin/php/bin/php` diff --git a/tools/test-install/auto_install.sh b/tools/test-install/auto_install.sh index 9d879755..55932c8e 100644 --- a/tools/test-install/auto_install.sh +++ b/tools/test-install/auto_install.sh @@ -48,7 +48,7 @@ echo "" # Проверяем что ключевые файлы на месте echo "==> Post-install verification:" CHECKS=( - "/home/xc_vm/status" + "/home/xc_vm/console.php" "/home/xc_vm/console.php" "/home/xc_vm/autoload.php" "/home/xc_vm/bootstrap.php"