diff --git a/Panel/modules/administration/panel_update.php b/Panel/modules/administration/panel_update.php index 199aeadf..5773699a 100644 --- a/Panel/modules/administration/panel_update.php +++ b/Panel/modules/administration/panel_update.php @@ -22,9 +22,6 @@ defined('GSP_UPDATE_LOG') || define('GSP_UPDATE_LOG', GSP_ROOT_DIR . '/logs/upda defined('GSP_VERSION_FILE') || define('GSP_VERSION_FILE', GSP_PANEL_DIR . '/includes/panel_version.php'); defined('GSP_VERSION_JSON') || define('GSP_VERSION_JSON', GSP_ROOT_DIR . '/version.json'); defined('GSP_PATCH_DIR') || define('GSP_PATCH_DIR', GSP_PANEL_DIR . '/modules/update/patches'); -defined('GSP_EXPECTED_ROOT') || define('GSP_EXPECTED_ROOT', '/var/www/html/GSP'); -defined('GSP_EXPECTED_PANEL') || define('GSP_EXPECTED_PANEL', GSP_EXPECTED_ROOT . '/Panel'); -defined('GSP_EXPECTED_WEBSITE') || define('GSP_EXPECTED_WEBSITE', GSP_EXPECTED_ROOT . '/Website'); defined('GSP_CANONICAL_TIMESTAMP_FILE') || define('GSP_CANONICAL_TIMESTAMP_FILE', GSP_WEBSITE_DIR . '/timestamp.txt'); defined('GSP_BILLING_TIMESTAMP_FILE') || define('GSP_BILLING_TIMESTAMP_FILE', GSP_PANEL_DIR . '/modules/billing/timestamp.txt'); defined('GSP_LAST_UPDATE_FILE') || define('GSP_LAST_UPDATE_FILE', GSP_ROOT_DIR . '/LAST_UPDATE.txt'); @@ -32,11 +29,7 @@ defined('GSP_DEFAULT_REPO_URL') || define('GSP_DEFAULT_REPO_URL', 'http://forge. defined('GSP_DEFAULT_BRANCH') || define('GSP_DEFAULT_BRANCH', 'Panel-unstable'); defined('GSP_DEFAULT_REPO_ROOT') || define('GSP_DEFAULT_REPO_ROOT', '/var/www/html/GSP'); defined('GSP_DEFAULT_PANEL_PATH') || define('GSP_DEFAULT_PANEL_PATH', '/var/www/html/GSP/Panel'); -defined('GSP_DEFAULT_WEBSITE_PATH') || define('GSP_DEFAULT_WEBSITE_PATH', '/var/www/html/GSP/Website'); defined('GSP_DEFAULT_PANEL_SOURCE') || define('GSP_DEFAULT_PANEL_SOURCE', 'Panel'); -defined('GSP_DEFAULT_LINUX_AGENT_SOURCE') || define('GSP_DEFAULT_LINUX_AGENT_SOURCE', 'Agent_Linux'); -defined('GSP_DEFAULT_WINDOWS_AGENT_SOURCE') || define('GSP_DEFAULT_WINDOWS_AGENT_SOURCE', 'Agent-Windows'); -defined('GSP_DEFAULT_WEBSITE_SOURCE') || define('GSP_DEFAULT_WEBSITE_SOURCE', 'Website'); defined('GSP_DEFAULT_BACKUP_RETENTION') || define('GSP_DEFAULT_BACKUP_RETENTION', 5); $gspPatchManager = GSP_PANEL_DIR . '/modules/update/patch_manager.php'; @@ -230,24 +223,16 @@ function gsp_update_settings() global $settings; $repo_root = !empty($settings['gsp_update_repo_root']) ? (string)$settings['gsp_update_repo_root'] : GSP_DEFAULT_REPO_ROOT; $panel_path = !empty($settings['gsp_update_panel_path']) ? (string)$settings['gsp_update_panel_path'] : GSP_DEFAULT_PANEL_PATH; -$website_path = !empty($settings['gsp_update_website_path']) ? (string)$settings['gsp_update_website_path'] : GSP_DEFAULT_WEBSITE_PATH; return [ 'repo_url' => !empty($settings['gsp_update_repo_url']) ? (string)$settings['gsp_update_repo_url'] : GSP_DEFAULT_REPO_URL, 'branch' => !empty($settings['gsp_update_branch']) ? (string)$settings['gsp_update_branch'] : GSP_DEFAULT_BRANCH, 'repo_root' => rtrim($repo_root, '/'), 'panel_path' => rtrim($panel_path, '/'), -'website_path' => rtrim($website_path, '/'), 'panel_source_path' => !empty($settings['gsp_update_panel_source_path']) ? trim((string)$settings['gsp_update_panel_source_path'], '/') : GSP_DEFAULT_PANEL_SOURCE, -'linux_agent_source_path' => !empty($settings['gsp_update_linux_agent_source_path']) ? trim((string)$settings['gsp_update_linux_agent_source_path'], '/') : GSP_DEFAULT_LINUX_AGENT_SOURCE, -'windows_agent_source_path' => !empty($settings['gsp_update_windows_agent_source_path']) ? trim((string)$settings['gsp_update_windows_agent_source_path'], '/') : GSP_DEFAULT_WINDOWS_AGENT_SOURCE, -'website_source_path' => !empty($settings['gsp_update_website_source_path']) ? trim((string)$settings['gsp_update_website_source_path'], '/') : GSP_DEFAULT_WEBSITE_SOURCE, 'git_path' => !empty($settings['gsp_update_git_path']) ? trim((string)$settings['gsp_update_git_path']) : 'git', 'backup_path' => !empty($settings['gsp_update_backup_path']) ? rtrim((string)$settings['gsp_update_backup_path'], '/') : GSP_BACKUP_BASE, 'backup_retention' => !empty($settings['gsp_update_backup_retention']) ? (string)$settings['gsp_update_backup_retention'] : (string)GSP_DEFAULT_BACKUP_RETENTION, 'panel_post_update_command' => !empty($settings['gsp_update_panel_post_update_command']) ? (string)$settings['gsp_update_panel_post_update_command'] : '', -'website_post_update_command' => !empty($settings['gsp_update_website_post_update_command']) ? (string)$settings['gsp_update_website_post_update_command'] : '', -'linux_agent_post_update_command' => !empty($settings['gsp_update_linux_agent_post_update_command']) ? (string)$settings['gsp_update_linux_agent_post_update_command'] : '', -'windows_agent_post_update_command' => !empty($settings['gsp_update_windows_agent_post_update_command']) ? (string)$settings['gsp_update_windows_agent_post_update_command'] : '', 'backup_before_update' => !isset($settings['gsp_update_backup_before']) ? '1' : (string)$settings['gsp_update_backup_before'], ]; } @@ -275,15 +260,15 @@ if (trim((string)$cfg[$key]) === '' || strpos((string)$cfg[$key], "\0") !== fals $errors[] = ucfirst(str_replace('_', ' ', $key)) . ' is invalid.'; } } - foreach (['website_path', 'backup_path'] as $key) { - if (isset($cfg[$key]) && (trim((string)$cfg[$key]) === '' || strpos((string)$cfg[$key], "\0") !== false || strpos((string)$cfg[$key], '..') !== false || strpos((string)$cfg[$key], '/') !== 0)) { - $errors[] = ucfirst(str_replace('_', ' ', $key)) . ' must be a safe absolute path.'; - } - } +foreach (['backup_path'] as $key) { +if (isset($cfg[$key]) && (trim((string)$cfg[$key]) === '' || strpos((string)$cfg[$key], "\0") !== false || strpos((string)$cfg[$key], '..') !== false || strpos((string)$cfg[$key], '/') !== 0)) { +$errors[] = ucfirst(str_replace('_', ' ', $key)) . ' must be a safe absolute path.'; +} +} if (isset($cfg['backup_retention']) && (!preg_match('/^\d+$/', (string)$cfg['backup_retention']) || (int)$cfg['backup_retention'] < 1 || (int)$cfg['backup_retention'] > 200)) { $errors[] = 'Backup retention must be a whole number between 1 and 200.'; } -foreach (['panel_source_path', 'linux_agent_source_path', 'windows_agent_source_path', 'website_source_path'] as $key) { +foreach (['panel_source_path'] as $key) { $value = isset($cfg[$key]) ? trim((string)$cfg[$key], '/') : ''; if ($value === '' || strpos($value, "\0") !== false || strpos($value, '..') !== false || !preg_match('/^[A-Za-z0-9._\/-]+$/', $value)) { $errors[] = ucfirst(str_replace('_', ' ', $key)) . ' is invalid.'; @@ -292,7 +277,7 @@ $errors[] = ucfirst(str_replace('_', ' ', $key)) . ' is invalid.'; if (isset($cfg['git_path']) && trim((string)$cfg['git_path']) !== '' && (strpos((string)$cfg['git_path'], "\0") !== false || strpos((string)$cfg['git_path'], "\n") !== false || strpos((string)$cfg['git_path'], "\r") !== false)) { $errors[] = 'Git executable path is invalid.'; } -foreach (['panel_post_update_command', 'website_post_update_command', 'linux_agent_post_update_command', 'windows_agent_post_update_command'] as $key) { +foreach (['panel_post_update_command'] as $key) { if (isset($cfg[$key]) && (strpos((string)$cfg[$key], "\0") !== false || strpos((string)$cfg[$key], "\n") !== false || strpos((string)$cfg[$key], "\r") !== false)) { $errors[] = ucfirst(str_replace('_', ' ', $key)) . ' must be a single-line admin command.'; } @@ -334,41 +319,6 @@ $data = [ @file_put_contents(GSP_VERSION_JSON, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); } -function gsp_fetch_github_releases($repo_owner, $repo_name) -{ -$url = "https://api.github.com/repos/{$repo_owner}/{$repo_name}/releases?per_page=30"; -if (function_exists('curl_init')) { -$ch = curl_init($url); -curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); -curl_setopt($ch, CURLOPT_USERAGENT, 'GSP-Panel-Updater'); -curl_setopt($ch, CURLOPT_TIMEOUT, 15); -curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); -curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); -$data = curl_exec($ch); -$code = curl_getinfo($ch, CURLINFO_HTTP_CODE); -curl_close($ch); -if ($code === 200 && $data) { -return json_decode($data, true); -} -} -$ctx = stream_context_create([ -'http' => [ -'method' => 'GET', -'header' => "User-Agent: GSP-Panel-Updater\r\n", -'timeout' => 15, -], -'ssl' => [ -'verify_peer' => true, -'verify_peer_name' => true, -], -]); -$data = @file_get_contents($url, false, $ctx); -if ($data) { -return json_decode($data, true); -} -return false; -} - function gsp_preflight_check(array $update_cfg = null) { $errors = []; @@ -378,27 +328,21 @@ $cwd = getcwd(); $cwd_real = $cwd ? (realpath($cwd) ?: $cwd) : ''; $root_path = rtrim((string)$update_cfg['repo_root'], '/'); $panel_path = rtrim((string)$update_cfg['panel_path'], '/'); - $website_path = !empty($update_cfg['website_path']) ? rtrim((string)$update_cfg['website_path'], '/') : ($root_path . '/Website'); $backup_base = gsp_get_backup_base($update_cfg); $root_real = realpath($root_path) ?: $root_path; $panel_real = realpath($panel_path) ?: $panel_path; -$website_real = realpath($website_path) ?: $website_path; $layout = [ 'cwd' => $cwd, 'cwd_real' => $cwd_real, 'expected_root' => $root_path, 'expected_panel' => $panel_path, -'expected_website' => $website_path, 'gsp_root' => $root_path, 'gsp_root_real' => $root_real, 'panel_dir' => $panel_path, 'panel_dir_real' => $panel_real, -'website_dir' => $website_path, -'website_dir_real' => $website_real, 'backup_dir' => $backup_base, 'config_file' => $panel_path . '/includes/config.inc.php', 'destination_panel' => $panel_path, -'destination_website' => $website_path, ]; if (!$layout['cwd']) { @@ -412,9 +356,6 @@ $errors[] = 'Detected GSP root path is missing.'; if (!is_dir($panel_path)) { $errors[] = 'Panel directory is missing.'; } -if (!is_dir($website_path)) { -$warnings[] = 'Website directory is missing. Panel updates can still continue, but Website files will not sync cleanly.'; -} if (!file_exists($layout['config_file'])) { $errors[] = 'Panel includes/config.inc.php was not found and cannot be preserved.'; } @@ -685,7 +626,6 @@ function gsp_create_full_backup($update_target_type, $update_target_version, $in 'backup_base' => $backup_base, 'gsp_root' => GSP_ROOT_DIR, 'panel_root' => GSP_PANEL_DIR, -'website_root' => GSP_WEBSITE_DIR, 'update_target_type' => $update_target_type, 'update_target_version' => $update_target_version, 'created_at' => date('Y-m-d H:i:s'), @@ -710,16 +650,6 @@ return ['success' => false, 'error' => $panel_backup['error']]; } $meta['panel_archive'] = basename($panel_backup['file']); -$website_backup = gsp_create_archive( -GSP_WEBSITE_DIR, -$backup_dir . '/website-files.tar.gz', -['./logs', './cache', './tmp'] -); -if (!$website_backup['success']) { -return ['success' => false, 'error' => $website_backup['error']]; -} -$meta['website_archive'] = basename($website_backup['file']); - if (file_exists(GSP_VERSION_JSON)) { @copy(GSP_VERSION_JSON, $backup_dir . '/version.json.bak'); $meta['version_json_backup'] = 'version.json.bak'; @@ -749,133 +679,6 @@ $meta['apache_backup_error'] = $apache_backup['error']; ]; } -function gsp_download_zip($repo_owner, $repo_name, $ref, $temp_dir) -{ -$url = "https://api.github.com/repos/{$repo_owner}/{$repo_name}/zipball/{$ref}"; -$zip_file = $temp_dir . '/gsp_update.zip'; -if (function_exists('curl_init')) { -$ch = curl_init($url); -curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); -curl_setopt($ch, CURLOPT_USERAGENT, 'GSP-Panel-Updater'); -curl_setopt($ch, CURLOPT_TIMEOUT, 180); -curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true); -curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2); -curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); -$data = curl_exec($ch); -$code = curl_getinfo($ch, CURLINFO_HTTP_CODE); -curl_close($ch); -if ($code !== 200 || !$data) { -return false; -} -@file_put_contents($zip_file, $data); -} else { -$ctx = stream_context_create([ -'http' => [ -'method' => 'GET', -'header' => "User-Agent: GSP-Panel-Updater\r\n", -'timeout' => 180, -], -'ssl' => [ -'verify_peer' => true, -'verify_peer_name' => true, -], -]); -$data = @file_get_contents($url, false, $ctx); -if (!$data) { -return false; -} -@file_put_contents($zip_file, $data); -} -if (!file_exists($zip_file) || filesize($zip_file) < 1000) { -return false; -} -return $zip_file; -} - -function gsp_extract_update_source($zip_file) -{ -$temp_dir = sys_get_temp_dir() . '/gsp_upd_' . time() . '_' . mt_rand(1000, 9999); -if (!@mkdir($temp_dir, 0750, true)) { -return ['success' => false, 'error' => 'Cannot create temp extraction directory.']; -} -require_once(GSP_PANEL_DIR . '/modules/update/unzip.php'); -$result = extractZip($zip_file, $temp_dir); -if (!is_array($result)) { -gsp_rmdir_recursive($temp_dir); -return ['success' => false, 'error' => 'ZIP extraction failed: ' . $result]; -} -$source_root = $temp_dir; -$subdirs = glob($temp_dir . '/*', GLOB_ONLYDIR); -if ($subdirs && count($subdirs) === 1) { -$source_root = $subdirs[0]; -} -return ['success' => true, 'temp_dir' => $temp_dir, 'source_root' => $source_root]; -} - -function gsp_resolve_source_layout($temp_checkout_path, $source_root, array $update_cfg = null) -{ -$update_cfg = $update_cfg ?: gsp_update_settings(); -$source_root_real = realpath($source_root) ?: $source_root; -$candidates = [$source_root_real]; -if (basename($source_root_real) === 'Panel' || basename($source_root_real) === 'Website') { -$candidates[] = dirname($source_root_real); -} -$candidates = array_values(array_unique(array_filter($candidates))); -$repo_root = null; -foreach ($candidates as $candidate) { -if (is_dir($candidate . '/Panel') && is_dir($candidate . '/Website')) { -$repo_root = realpath($candidate) ?: $candidate; -break; -} -} - -$layout = [ -'cwd' => getcwd() ?: '', -'live_gsp_root' => rtrim((string)$update_cfg['repo_root'], '/'), -'live_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'), -'live_website_path' => !empty($update_cfg['website_path']) ? rtrim((string)$update_cfg['website_path'], '/') : (rtrim((string)$update_cfg['repo_root'], '/') . '/Website'), -'temporary_git_checkout_path' => $temp_checkout_path, -'source_root' => $source_root_real, -'source_repo_root' => $repo_root, -'source_panel_path' => $repo_root ? ($repo_root . '/' . trim((string)$update_cfg['panel_source_path'], '/')) : '', -'source_website_path' => $repo_root ? ($repo_root . '/' . trim((string)$update_cfg['website_source_path'], '/')) : '', -'destination_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'), -'destination_website_path' => !empty($update_cfg['website_path']) ? rtrim((string)$update_cfg['website_path'], '/') : (rtrim((string)$update_cfg['repo_root'], '/') . '/Website'), -]; - -$errors = []; -if (!$repo_root) { -$errors[] = 'Unable to resolve source repository root containing both Panel/ and Website/.'; -} else { -if (!is_dir($layout['source_panel_path'])) { -$errors[] = 'Source Panel path is missing: ' . $layout['source_panel_path']; -} -if (!is_dir($layout['source_website_path'])) { -$errors[] = 'Source Website path is missing: ' . $layout['source_website_path']; -} -} -if (strpos((string)$layout['destination_panel_path'], '/Panel/Panel') !== false) { -$errors[] = 'Destination Panel path is nested incorrectly: ' . $layout['destination_panel_path']; -} -if (strpos((string)$layout['destination_website_path'], '/Website/Website') !== false) { -$errors[] = 'Destination Website path is nested incorrectly: ' . $layout['destination_website_path']; -} -if (!is_dir($layout['destination_panel_path'])) { -$errors[] = 'Destination Panel path does not exist: ' . $layout['destination_panel_path']; -} - -gsp_update_log('Deployment layout detection: ' . json_encode($layout)); -foreach ($errors as $error) { -gsp_update_log('Deployment layout error: ' . $error); -} - -return [ -'success' => empty($errors), -'errors' => $errors, -'layout' => $layout, -]; -} - function gsp_normalize_rel($path) { $path = str_replace('\\', '/', $path); @@ -883,34 +686,6 @@ $path = ltrim($path, '/'); return $path; } -function gsp_is_preserved_path($relative_path) -{ -$relative_path = gsp_normalize_rel($relative_path); -$exact = [ -'Panel/includes/config.inc.php', -]; -$prefixes = [ -'logs/', -'backups/', -'Panel/logs/', -'Panel/backups/', -'Website/logs/', -'Website/uploads/', -'Website/upload/', -'Panel/uploads/', -'Panel/upload/', -]; -if (in_array($relative_path, $exact, true)) { -return true; -} -foreach ($prefixes as $prefix) { -if (strpos($relative_path, $prefix) === 0) { -return true; -} -} -return false; -} - function gsp_copy_file($src, $dst) { $parent = dirname($dst); @@ -920,216 +695,6 @@ if (!is_dir($parent)) { return @copy($src, $dst); } -function gsp_copy_tree($src_root, $dst_root, $base_rel = '') -{ -$copied = 0; -$skipped = []; -$copied_files = []; -$source = rtrim($src_root, '/'); -$iter = new RecursiveIteratorIterator( -new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS), -RecursiveIteratorIterator::SELF_FIRST -); -foreach ($iter as $item) { -$rel = gsp_normalize_rel(($base_rel !== '' ? $base_rel . '/' : '') . substr($item->getPathname(), strlen($source) + 1)); -if (gsp_is_preserved_path($rel)) { -$skipped[] = $rel; -continue; -} -$dst = rtrim($dst_root, '/') . '/' . $rel; -if ($item->isDir()) { -if (!is_dir($dst)) { -@mkdir($dst, 0755, true); -} -continue; -} -if (gsp_copy_file($item->getPathname(), $dst)) { -$copied++; -if (count($copied_files) < 200) { -$copied_files[] = $rel; -} -} -} -return ['copied' => $copied, 'skipped' => $skipped, 'copied_files' => $copied_files]; -} - -function gsp_updater_watch_list() -{ -return [ -'Panel/modules/administration/panel_update.php', -'Panel/modules/update/update.php', -'Panel/modules/update/updating.php', -'Panel/modules/update/post_update.php', -'Panel/modules/update/module.php', -'Panel/modules/update/unzip.php', -'Panel/modules/update/blacklist.php', -'Panel/modules/update/patch_manager.php', -'Panel/modules/update/patches', -]; -} - -function gsp_collect_files_under($path, $base_rel) -{ -$list = []; -if (is_file($path)) { -$list[] = gsp_normalize_rel($base_rel); -return $list; -} -if (!is_dir($path)) { -return $list; -} -$iter = new RecursiveIteratorIterator( -new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), -RecursiveIteratorIterator::LEAVES_ONLY -); -foreach ($iter as $item) { -if ($item->isFile()) { -$list[] = gsp_normalize_rel($base_rel . '/' . substr($item->getPathname(), strlen($path) + 1)); -} -} -return $list; -} - -function gsp_detect_updater_drift_files($source_root, $target_root) -{ -$drift = []; -foreach (gsp_updater_watch_list() as $rel) { -$src = rtrim($source_root, '/') . '/' . $rel; -$files = gsp_collect_files_under($src, $rel); -foreach ($files as $fileRel) { -$s = rtrim($source_root, '/') . '/' . $fileRel; -$d = rtrim($target_root, '/') . '/' . $fileRel; -if (!file_exists($d)) { -$drift[] = $fileRel; -continue; -} -if (@hash_file('sha256', $s) !== @hash_file('sha256', $d)) { -$drift[] = $fileRel; -} -} -} -return array_values(array_unique($drift)); -} - -function gsp_apply_updater_files_only($source_root, $target_root, array $drift_files) -{ -$copied = 0; -foreach ($drift_files as $rel) { -$src = rtrim($source_root, '/') . '/' . $rel; -$dst = rtrim($target_root, '/') . '/' . $rel; -if (is_file($src) && gsp_copy_file($src, $dst)) { -$copied++; -} -} -return $copied; -} - -function gsp_apply_layout_sync(array $layout) -{ -$source_root = $layout['source_repo_root']; -$source_panel = $layout['source_panel_path']; -$source_website = $layout['source_website_path']; -$destination_panel = $layout['destination_panel_path']; -$destination_website = $layout['destination_website_path']; -$top_level = scandir($source_root); -$skip = ['.', '..', '.git', '.github', '.gitignore', '.vscode']; -$copied = 0; -$panel_copied = 0; -$website_copied = 0; -$skipped = []; -$copied_files = []; -gsp_update_log('Layout sync source mapping: ' . $source_panel . ' => ' . $destination_panel . ' ; ' . $source_website . ' => ' . $destination_website); -foreach ((array)$top_level as $entry) { -if (in_array($entry, $skip, true)) { -continue; -} -if ($entry === 'Panel' || $entry === 'Website' || $entry === 'backups' || $entry === 'logs') { -continue; -} -$src = rtrim($source_root, '/') . '/' . $entry; -$dst = rtrim($layout['live_gsp_root'], '/') . '/' . $entry; -if (is_file($src)) { -$rel = gsp_normalize_rel($entry); -if (gsp_is_preserved_path($rel)) { -$skipped[] = $rel; -continue; -} -if (gsp_copy_file($src, $dst)) { -$copied++; -if (count($copied_files) < 200) { -$copied_files[] = $rel; -} -} -continue; -} -if (is_dir($src)) { -$part = gsp_copy_tree($src, $layout['live_gsp_root'], $entry); -$copied += $part['copied']; -$copied_files = array_merge($copied_files, array_slice((array)$part['copied_files'], 0, max(0, 200 - count($copied_files)))); -$skipped = array_merge($skipped, $part['skipped']); -} -} -$panel_part = gsp_copy_tree($source_panel, dirname($destination_panel), basename($destination_panel)); -$copied += $panel_part['copied']; -$panel_copied += $panel_part['copied']; -$copied_files = array_merge($copied_files, array_slice((array)$panel_part['copied_files'], 0, max(0, 200 - count($copied_files)))); -$skipped = array_merge($skipped, $panel_part['skipped']); - -$website_part = gsp_copy_tree($source_website, dirname($destination_website), basename($destination_website)); -$copied += $website_part['copied']; -$website_copied += $website_part['copied']; -$copied_files = array_merge($copied_files, array_slice((array)$website_part['copied_files'], 0, max(0, 200 - count($copied_files)))); -$skipped = array_merge($skipped, $website_part['skipped']); - -return [ -'success' => true, -'files_copied' => $copied, -'panel_files_copied' => $panel_copied, -'website_files_copied' => $website_copied, -'skipped' => array_values(array_unique($skipped)), -'copied_files' => array_slice(array_values(array_unique($copied_files)), 0, 200), -]; -} - -function gsp_validate_layout_sync_result(array $layout, array $sync) -{ -$errors = []; -$checks = [ -'Panel/modules/administration/panel_update.php', -'Panel/modules/addonsmanager/addons_manager.php', -'Website/index.php', -]; -foreach ($checks as $rel) { -$src = rtrim($layout['source_repo_root'], '/') . '/' . $rel; -$dst = rtrim($layout['live_gsp_root'], '/') . '/' . $rel; -if (!is_file($src)) { -continue; -} -if (!is_file($dst)) { -$errors[] = 'Missing deployed file: ' . $rel; -continue; -} -$src_hash = @hash_file('sha256', $src); -$dst_hash = @hash_file('sha256', $dst); -if ($src_hash === false || $dst_hash === false || $src_hash !== $dst_hash) { -$errors[] = 'Copied file verification failed: ' . $rel; -} -} -if (!empty($sync['copied_files']) && intval($sync['panel_files_copied']) === 0) { -$errors[] = 'No Panel files were copied during layout sync.'; -} -if (!empty($sync['copied_files']) && intval($sync['website_files_copied']) === 0) { -$errors[] = 'No Website files were copied during layout sync.'; -} -foreach ($errors as $error) { -gsp_update_log('Layout sync validation error: ' . $error); -} -return [ -'success' => empty($errors), -'errors' => $errors, -]; -} - function gsp_write_last_update_markers($repo_root = null) { $line = 'Last Updated at ' . date('g:ia') . ' on ' . date('Y-m-d'); @@ -1165,90 +730,6 @@ return [ return ['success' => true, 'run' => $run]; } -function gsp_apply_update_from_zip($zip_file, $restart_nonce = '', array $update_cfg = null) -{ -$update_cfg = $update_cfg ?: gsp_update_settings(); -$extract = gsp_extract_update_source($zip_file); -if (!$extract['success']) { -return $extract; -} -$temp_dir = $extract['temp_dir']; -$source_root = $extract['source_root']; -$resolved_layout = gsp_resolve_source_layout($temp_dir, $source_root, $update_cfg); -if (!$resolved_layout['success']) { -gsp_rmdir_recursive($temp_dir); -return ['success' => false, 'error' => 'Deployment layout validation failed: ' . implode(' | ', $resolved_layout['errors'])]; -} -$layout = $resolved_layout['layout']; -$_SESSION['gsp_last_update_layout'] = $layout; -$updater_version = substr((string)@hash_file('sha256', $layout['source_panel_path'] . '/modules/administration/panel_update.php'), 0, 12); - -$drift_files = gsp_detect_updater_drift_files($layout['source_repo_root'], GSP_ROOT_DIR); -if (!empty($drift_files) && empty($restart_nonce)) { -$copied = gsp_apply_updater_files_only($layout['source_repo_root'], GSP_ROOT_DIR, $drift_files); -$nonce = gsp_random_token(12); -$_SESSION['gsp_update_restart_nonce'] = $nonce; -gsp_update_log('Updater self-update applied (' . $copied . ' files); restart nonce=' . $nonce); -gsp_rmdir_recursive($temp_dir); - return [ - 'success' => false, - 'restart_required' => true, - 'restart_nonce' => $nonce, - 'updater_files_updated' => $copied, - 'drift_files' => $drift_files, - ]; - } - - if (!empty($restart_nonce)) { - $expected = isset($_SESSION['gsp_update_restart_nonce']) ? $_SESSION['gsp_update_restart_nonce'] : null; - if ($expected === null || !hash_equals($expected, $restart_nonce)) { - gsp_rmdir_recursive($temp_dir); - return ['success' => false, 'error' => 'Invalid updater restart marker.']; - } - unset($_SESSION['gsp_update_restart_nonce']); - } - - $patches = gsp_run_required_patches($updater_version); - if (!$patches['success']) { - gsp_rmdir_recursive($temp_dir); - return ['success' => false, 'error' => $patches['error']]; - } - - $sync = gsp_apply_layout_sync($layout); - gsp_rmdir_recursive($temp_dir); - if (!$sync['success']) { - return $sync; - } - $sync_validation = gsp_validate_layout_sync_result($layout, $sync); - if (!$sync_validation['success']) { - return ['success' => false, 'error' => 'Deployed file validation failed: ' . implode(' | ', $sync_validation['errors'])]; - } - gsp_update_log('Layout sync complete: copied=' . $sync['files_copied'] . ', skipped=' . count($sync['skipped'])); - gsp_update_log('Layout sync totals: Panel=' . intval($sync['panel_files_copied']) . ', Website=' . intval($sync['website_files_copied'])); - if (!empty($sync['skipped'])) { - gsp_update_log('Preserved paths: ' . implode(', ', array_slice($sync['skipped'], 0, 50))); - } - if (!empty($sync['copied_files'])) { - $copied_sample = array_slice((array)$sync['copied_files'], 0, 50); - gsp_update_log('Copied file sample: ' . implode(', ', $copied_sample)); - $addons_updates = array_values(array_filter((array)$sync['copied_files'], function ($rel) { - return strpos($rel, 'Panel/modules/addonsmanager/') === 0; - })); - if (!empty($addons_updates)) { - gsp_update_log('Addonsmanager files copied: ' . implode(', ', array_slice($addons_updates, 0, 50))); - } - } - return [ - 'success' => true, - 'files_copied' => $sync['files_copied'], - 'panel_files_copied' => $sync['panel_files_copied'], - 'website_files_copied' => $sync['website_files_copied'], - 'preserved' => $sync['skipped'], - 'copied_files' => $sync['copied_files'], - 'patches' => $patches['run'], - ]; -} - function gsp_checkout_update_source(array $update_cfg) { $repo_url = (string)$update_cfg['repo_url']; @@ -1277,68 +758,6 @@ gsp_update_log('Configured git checkout completed from ' . $repo_url . ' branch return ['success' => true, 'temp_dir' => $temp_dir, 'source_root' => $temp_dir, 'output' => implode("\n", $out)]; } -function gsp_apply_update_from_source($source_root, $restart_nonce = '', array $update_cfg = null) -{ -$update_cfg = $update_cfg ?: gsp_update_settings(); -$resolved_layout = gsp_resolve_source_layout($source_root, $source_root, $update_cfg); -if (!$resolved_layout['success']) { -return ['success' => false, 'error' => 'Deployment layout validation failed: ' . implode(' | ', $resolved_layout['errors'])]; -} -$layout = $resolved_layout['layout']; -$_SESSION['gsp_last_update_layout'] = $layout; -$updater_version = substr((string)@hash_file('sha256', $layout['source_panel_path'] . '/modules/administration/panel_update.php'), 0, 12); - -$drift_files = gsp_detect_updater_drift_files($layout['source_repo_root'], $layout['live_gsp_root']); -if (!empty($drift_files) && empty($restart_nonce)) { -$copied = gsp_apply_updater_files_only($layout['source_repo_root'], $layout['live_gsp_root'], $drift_files); -$nonce = gsp_random_token(12); -$_SESSION['gsp_update_restart_nonce'] = $nonce; -gsp_update_log('Updater self-update applied (' . $copied . ' files); restart nonce=' . $nonce); -return [ -'success' => false, -'restart_required' => true, -'restart_nonce' => $nonce, -'updater_files_updated' => $copied, -'drift_files' => $drift_files, -]; -} -if (!empty($restart_nonce)) { -$expected = isset($_SESSION['gsp_update_restart_nonce']) ? $_SESSION['gsp_update_restart_nonce'] : null; -if ($expected === null || !hash_equals($expected, $restart_nonce)) { -return ['success' => false, 'error' => 'Invalid updater restart marker.']; -} -unset($_SESSION['gsp_update_restart_nonce']); -} - -$config_file = rtrim($layout['destination_panel_path'], '/') . '/includes/config.inc.php'; -$config_backup = is_file($config_file) ? @file_get_contents($config_file) : false; -$patches = gsp_run_required_patches($updater_version); -if (!$patches['success']) { -return ['success' => false, 'error' => $patches['error']]; -} -$sync = gsp_apply_layout_sync($layout); -if ($config_backup !== false) { -@file_put_contents($config_file, $config_backup, LOCK_EX); -} -if (!$sync['success']) { -return $sync; -} -$sync_validation = gsp_validate_layout_sync_result($layout, $sync); -if (!$sync_validation['success']) { -return ['success' => false, 'error' => 'Deployed file validation failed: ' . implode(' | ', $sync_validation['errors'])]; -} -gsp_update_log('Layout sync complete: copied=' . $sync['files_copied'] . ', skipped=' . count($sync['skipped'])); -return [ -'success' => true, -'files_copied' => $sync['files_copied'], -'panel_files_copied' => $sync['panel_files_copied'], -'website_files_copied' => $sync['website_files_copied'], -'preserved' => $sync['skipped'], -'copied_files' => $sync['copied_files'], -'patches' => $patches['run'], -]; -} - function gsp_fix_permissions($root_dir) { @system( @@ -1364,73 +783,6 @@ foreach (glob($cache . '/*.cache') ?: [] as $f) { } } -function gsp_do_update($repo_owner, $repo_name, $ref, $update_type, $restart_nonce = '') -{ -global $db; -$preflight = gsp_preflight_check(); -if (!$preflight['success']) { -return ['success' => false, 'error' => 'Preflight failed: ' . implode(' | ', $preflight['errors'])]; -} - -$backup = gsp_create_full_backup($update_type, $ref, false); -if (!$backup['success']) { -return $backup; -} -gsp_update_log("Backup created at {$backup['backup_ts']} before {$update_type} update to {$ref}"); - -$temp_dir = sys_get_temp_dir() . '/gsp_dl_' . time() . '_' . mt_rand(1000, 9999); -@mkdir($temp_dir, 0750, true); -$zip_file = gsp_download_zip($repo_owner, $repo_name, $ref, $temp_dir); -if (!$zip_file) { -gsp_rmdir_recursive($temp_dir); -return ['success' => false, 'error' => 'Failed to download update ZIP from GitHub.']; -} - -$apply = gsp_apply_update_from_zip($zip_file, $restart_nonce); -@unlink($zip_file); -gsp_rmdir_recursive($temp_dir); -if (!empty($apply['restart_required'])) { -$apply['backup_dir'] = $backup['backup_dir']; -$apply['success'] = false; -return $apply; -} -if (!$apply['success']) { -return $apply; -} - -$commit_after = gsp_get_git_commit(); -gsp_fix_permissions(GSP_ROOT_DIR); -gsp_clear_panel_cache(GSP_PANEL_DIR); -gsp_write_version_file($ref, $update_type); -$vsource = ($update_type === 'release') ? 'GitHub Releases' : $ref; -$vversion = ($update_type === 'release') ? $ref : ($commit_after ?: $ref); -gsp_write_version_json($update_type, $vsource, $vversion, $commit_after); -gsp_write_last_update_markers(GSP_ROOT_DIR); -$db->setSettings(['ogp_version' => $ref, 'version_type' => $update_type]); - -if (file_exists(GSP_PANEL_DIR . '/modules/modulemanager/module_handling.php')) { -require_once(GSP_PANEL_DIR . '/modules/modulemanager/module_handling.php'); -} -if (function_exists('updateAllPanelModules')) { -updateAllPanelModules(); -} -if (function_exists('runPostUpdateOperations')) { -runPostUpdateOperations(); -} - -gsp_update_log("Update to {$ref} (type={$update_type}) complete"); -return [ -'success' => true, -'files_copied' => $apply['files_copied'], -'panel_files_copied' => isset($apply['panel_files_copied']) ? $apply['panel_files_copied'] : 0, -'website_files_copied' => isset($apply['website_files_copied']) ? $apply['website_files_copied'] : 0, -'copied_files' => isset($apply['copied_files']) ? $apply['copied_files'] : [], -'backup_dir' => $backup['backup_dir'], -'preserved' => $apply['preserved'], -'patches' => $apply['patches'], -]; -} - function gsp_do_configured_git_update(array $update_cfg, $restart_nonce = '') { global $db; @@ -1456,27 +808,50 @@ $checkout = gsp_checkout_update_source($update_cfg); if (!$checkout['success']) { return $checkout; } -$apply = gsp_apply_update_from_source($checkout['source_root'], $restart_nonce, $update_cfg); +$panel_source = gsp_component_source_path($checkout['source_root'], $update_cfg['panel_source_path']); +if ($panel_source === false || !is_dir($panel_source)) { gsp_rmdir_recursive($checkout['temp_dir']); -if (!empty($apply['restart_required'])) { -$apply['backup_dir'] = $backup['backup_dir']; -$apply['success'] = false; -return $apply; +return ['success' => false, 'error' => 'Source Panel folder is invalid or missing: ' . $update_cfg['panel_source_path']]; +} +$panel_dest = rtrim((string)$update_cfg['panel_path'], '/'); +if (strpos($panel_dest, '/') !== 0 || strpos($panel_dest, '..') !== false || strpos($panel_dest, "\0") !== false) { +gsp_rmdir_recursive($checkout['temp_dir']); +return ['success' => false, 'error' => 'Panel destination path is invalid.']; +} +if (!is_dir($panel_dest) && !@mkdir($panel_dest, 0755, true) && !is_dir($panel_dest)) { +gsp_rmdir_recursive($checkout['temp_dir']); +return ['success' => false, 'error' => 'Cannot create Panel destination directory: ' . $panel_dest]; +} +$panel_backup = ['success' => true, 'backup_dir' => null]; +if (!empty($update_cfg['backup_before_update'])) { +$panel_backup = gsp_create_full_backup('panel-update', $update_cfg['branch'], false, $update_cfg); +if (!$panel_backup['success']) { +gsp_rmdir_recursive($checkout['temp_dir']); +return $panel_backup; +} +gsp_update_log("Backup created before panel update to {$update_cfg['branch']}: {$panel_backup['backup_dir']}"); +} +$sync = gsp_component_copy_tree($panel_source, $panel_dest, 'Panel'); +gsp_rmdir_recursive($checkout['temp_dir']); +if (!$sync['success']) { +return $sync; +} +if (!empty($update_cfg['panel_post_update_command'])) { +$post = gsp_run_admin_post_update_command($update_cfg['panel_post_update_command'], $panel_dest); +if (!$post['success']) { +return ['success' => false, 'error' => 'Panel post-update command failed: ' . $post['error'], 'output' => $post['output']]; } -if (!$apply['success']) { -return $apply; } - $commit_after = gsp_get_git_commit(); -gsp_fix_permissions($update_cfg['repo_root']); -gsp_clear_panel_cache($update_cfg['panel_path']); +gsp_fix_permissions($panel_dest); +gsp_clear_panel_cache($panel_dest); gsp_write_version_file($update_cfg['branch'], 'git'); gsp_write_version_json('git', $update_cfg['repo_url'], $commit_after ?: $update_cfg['branch'], $commit_after); -gsp_write_last_update_markers(); +gsp_write_last_update_markers($update_cfg['repo_root']); $db->setSettings(['ogp_version' => $update_cfg['branch'], 'version_type' => 'git']); -if (file_exists($update_cfg['panel_path'] . '/modules/modulemanager/module_handling.php')) { -require_once($update_cfg['panel_path'] . '/modules/modulemanager/module_handling.php'); +if (file_exists($panel_dest . '/modules/modulemanager/module_handling.php')) { +require_once($panel_dest . '/modules/modulemanager/module_handling.php'); } if (function_exists('updateAllPanelModules')) { updateAllPanelModules(); @@ -1485,57 +860,17 @@ if (function_exists('runPostUpdateOperations')) { runPostUpdateOperations(); } -gsp_update_log("Configured git update complete: {$update_cfg['repo_url']} {$update_cfg['branch']}"); +gsp_update_log("Configured git panel update complete: {$update_cfg['repo_url']} {$update_cfg['branch']}"); return [ 'success' => true, -'files_copied' => $apply['files_copied'], -'panel_files_copied' => isset($apply['panel_files_copied']) ? $apply['panel_files_copied'] : 0, -'website_files_copied' => isset($apply['website_files_copied']) ? $apply['website_files_copied'] : 0, -'copied_files' => isset($apply['copied_files']) ? $apply['copied_files'] : [], -'backup_dir' => $backup['backup_dir'], -'preserved' => $apply['preserved'], -'patches' => $apply['patches'], +'files_copied' => $sync['files_copied'], +'panel_files_copied' => $sync['files_copied'], +'backup_dir' => $panel_backup['backup_dir'], +'preserved' => $sync['skipped'], +'copied_files' => isset($sync['copied_files']) ? $sync['copied_files'] : [], ]; } -function gsp_safe_component_backup($component, $source_path, $backup_base, $retention = GSP_DEFAULT_BACKUP_RETENTION) -{ - $component = preg_replace('/[^A-Za-z0-9._-]/', '_', (string)$component); - $backup_base = rtrim((string)$backup_base, '/'); - if ($backup_base === '' || strpos($backup_base, '/') !== 0) { - $backup_base = gsp_get_backup_base(); - } - $backup_base_ready = gsp_ensure_directory($backup_base, 'component backup path'); - if (!$backup_base_ready['success']) { - return ['success' => false, 'error' => $backup_base_ready['error']]; - } - $ts = date('Y-m-d_H-i-s'); - $backup_dir = $backup_base . '/component_' . $component . '_' . $ts; - $backup_dir_ready = gsp_ensure_directory($backup_dir, 'component backup directory'); - if (!$backup_dir_ready['success']) { - return ['success' => false, 'error' => $backup_dir_ready['error']]; - } -if (!is_dir($source_path)) { -return ['success' => true, 'backup_dir' => $backup_dir, 'skipped' => true]; -} -$archive = $backup_dir . '/' . $component . '.tar.gz'; -$archive_result = gsp_create_archive($source_path, $archive, ['./logs', './cache', './tmp', './backups', './uploads', './upload', './Cfg', './ServerFiles', './Schedule', './steamcmd', './startups']); -if (!$archive_result['success']) { -return $archive_result; -} -@file_put_contents($backup_dir . '/backup.json', json_encode([ -'component' => $component, -'source_path' => $source_path, - 'created_at' => date('Y-m-d H:i:s'), - 'archive' => basename($archive), - ], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); - $prune = gsp_prune_old_backups((int)$retention, $backup_base); - if (!empty($prune['deleted'])) { - gsp_update_log('Component backup retention pruned ' . count($prune['deleted']) . ' entries from ' . $backup_base . ': ' . implode(', ', $prune['deleted'])); - } - return ['success' => true, 'backup_dir' => $backup_dir, 'archive' => $archive]; -} - function gsp_component_source_path($source_root, $relative) { $relative = trim((string)$relative, '/'); @@ -1632,204 +967,6 @@ exec('cd ' . escapeshellarg($cwd) . ' && ' . $command . ' 2>&1', $out, $ret); return ['success' => $ret === 0, 'exit_code' => $ret, 'output' => implode("\n", $out), 'error' => $ret === 0 ? '' : implode("\n", $out)]; } -function gsp_do_local_component_update(array $update_cfg, array $components) -{ -$allowed = array('panel', 'website'); -$components = array_values(array_intersect($allowed, array_unique($components))); -if (empty($components)) { -return ['success' => true, 'files_copied' => 0, 'results' => []]; -} -$validation = gsp_validate_update_settings($update_cfg); -if (!empty($validation)) { -return ['success' => false, 'error' => implode(' | ', $validation)]; -} -$checkout = gsp_checkout_update_source($update_cfg); -if (!$checkout['success']) { -return $checkout; -} -$results = []; -$total = 0; -$all_skipped = []; -$all_copied = []; -$map = [ -'panel' => ['source' => $update_cfg['panel_source_path'], 'dest' => $update_cfg['panel_path'], 'post' => $update_cfg['panel_post_update_command']], -'website' => ['source' => $update_cfg['website_source_path'], 'dest' => $update_cfg['website_path'], 'post' => $update_cfg['website_post_update_command']], -]; -foreach ($components as $component) { -$source = gsp_component_source_path($checkout['source_root'], $map[$component]['source']); -if ($source === false) { -gsp_rmdir_recursive($checkout['temp_dir']); -return ['success' => false, 'error' => 'Source folder for ' . $component . ' is invalid or missing.']; -} -$dest = rtrim((string)$map[$component]['dest'], '/'); -if (strpos($dest, '/') !== 0 || strpos($dest, '..') !== false || strpos($dest, "\0") !== false) { -gsp_rmdir_recursive($checkout['temp_dir']); -return ['success' => false, 'error' => 'Destination path for ' . $component . ' is invalid.']; -} -$backup = ['success' => true, 'backup_dir' => null]; -if (!empty($update_cfg['backup_before_update'])) { - $backup = gsp_safe_component_backup($component, $dest, gsp_get_backup_base($update_cfg), gsp_get_backup_retention($update_cfg)); -if (!$backup['success']) { -gsp_rmdir_recursive($checkout['temp_dir']); -return $backup; -} -} -$sync = gsp_component_copy_tree($source, $dest, ucfirst($component)); -if (!$sync['success']) { -gsp_rmdir_recursive($checkout['temp_dir']); -return $sync; -} -$post = gsp_run_admin_post_update_command($map[$component]['post'], $dest); -if (!$post['success']) { -gsp_rmdir_recursive($checkout['temp_dir']); -return ['success' => false, 'error' => ucfirst($component) . ' post-update command failed: ' . $post['error'], 'results' => $results]; -} -$results[$component] = [ -'source' => $source, -'destination' => $dest, -'backup_dir' => $backup['backup_dir'], -'files_copied' => $sync['files_copied'], -'skipped' => $sync['skipped'], -'post_update' => $post, -]; -$total += (int)$sync['files_copied']; -$all_skipped = array_merge($all_skipped, $sync['skipped']); -$all_copied = array_merge($all_copied, $sync['copied_files']); -gsp_update_log('Local component update complete: ' . $component . ' copied=' . intval($sync['files_copied']) . ' source=' . $source . ' dest=' . $dest); -} -gsp_rmdir_recursive($checkout['temp_dir']); -gsp_write_last_update_markers($update_cfg['repo_root']); -if (in_array('panel', $components, true)) { -gsp_clear_panel_cache($update_cfg['panel_path']); -gsp_fix_permissions($update_cfg['panel_path']); -} -return [ -'success' => true, -'files_copied' => $total, -'results' => $results, -'preserved' => array_values(array_unique($all_skipped)), -'copied_files' => array_slice(array_values(array_unique($all_copied)), 0, 200), -]; -} - -function gsp_update_payload_encode(array $payload) -{ -$lines = []; -foreach ($payload as $key => $value) { -if (is_array($value)) { -$value = json_encode($value, JSON_UNESCAPED_SLASHES); -} -$lines[] = $key . '=' . base64_encode((string)$value); -} -return implode("\n", $lines); -} - -function gsp_infer_agent_os(array $remote_server, $reported_os = '') -{ -$reported = strtolower((string)$reported_os); -$haystack = strtolower((string)$remote_server['remote_server_name'] . ' ' . (string)$remote_server['agent_ip'] . ' ' . $reported); -if (strpos($haystack, 'windows') !== false || strpos($haystack, 'cygwin') !== false || strpos($haystack, 'mingw') !== false) { -return 'windows'; -} -return 'linux'; -} - -function gsp_get_remote_agent_rows() -{ -global $db; -$rows = $db->getRemoteServers(); -return is_array($rows) ? $rows : []; -} - -function gsp_dispatch_remote_agent_update(array $remote_server, array $update_cfg, $component) -{ -if (!function_exists('xmlrpc_encode_request') || !function_exists('xmlrpc_decode')) { -return ['success' => false, 'status' => 'missing_xmlrpc', 'message' => 'PHP xmlrpc extension is required for remote agent updates.']; -} -require_once(GSP_PANEL_DIR . '/includes/lib_remote.php'); -$component = ($component === 'windows_agent') ? 'windows_agent' : 'linux_agent'; -$source_path = ($component === 'windows_agent') ? $update_cfg['windows_agent_source_path'] : $update_cfg['linux_agent_source_path']; -$post_cmd = ($component === 'windows_agent') ? $update_cfg['windows_agent_post_update_command'] : $update_cfg['linux_agent_post_update_command']; -$payload = [ -'component' => $component, -'repo_url' => $update_cfg['repo_url'], -'branch' => $update_cfg['branch'], -'source_path' => $source_path, -'install_path' => '', -'git_path' => $update_cfg['git_path'], -'backup_path' => '', -'post_update_command' => $post_cmd, -'requested_at' => date('Y-m-d H:i:s'), -]; -$remote = new OGPRemoteLibrary($remote_server['agent_ip'], $remote_server['agent_port'], $remote_server['encryption_key'], $remote_server['timeout']); -if ((int)$remote->status_chk() !== 1) { -return ['success' => false, 'status' => 'agent_unreachable', 'message' => 'Agent unreachable or encryption mismatch.']; -} -if (!method_exists($remote, 'component_update')) { -return ['success' => false, 'status' => 'panel_missing_rpc_wrapper', 'message' => 'Panel remote library does not expose component_update().']; -} -$response = $remote->component_update(gsp_update_payload_encode($payload)); -if (is_array($response)) { -return $response; -} -return ['success' => false, 'status' => 'unexpected_response', 'message' => 'Unexpected agent response: ' . substr((string)$response, 0, 300)]; -} - -function gsp_do_remote_agents_update(array $update_cfg, array $remote_ids, array $component_filters) -{ -global $db; -if (!function_exists('xmlrpc_encode_request') || !function_exists('xmlrpc_decode')) { -return ['success' => false, 'results' => [], 'success_count' => 0, 'message' => 'PHP xmlrpc extension is required for remote agent updates.']; -} -require_once(GSP_PANEL_DIR . '/includes/lib_remote.php'); -$remote_ids = array_map('intval', $remote_ids); -$remote_ids = array_values(array_filter(array_unique($remote_ids))); -if (empty($remote_ids)) { -return ['success' => true, 'results' => [], 'message' => 'No remote agents selected.']; -} -$all = gsp_get_remote_agent_rows(); -$by_id = []; -foreach ($all as $row) { -$by_id[(int)$row['remote_server_id']] = $row; -} -$results = []; -$success_count = 0; -foreach ($remote_ids as $remote_id) { -if (empty($by_id[$remote_id])) { -$results[$remote_id] = ['success' => false, 'status' => 'not_found', 'message' => 'Remote host not found.']; -continue; -} -$row = $by_id[$remote_id]; -$reported_os = ''; -try { -$probe = new OGPRemoteLibrary($row['agent_ip'], $row['agent_port'], $row['encryption_key'], $row['timeout']); -$reported_os = (string)$probe->what_os(); -} catch (Exception $e) { -$reported_os = ''; -} -$agent_os = gsp_infer_agent_os($row, $reported_os); -if ($agent_os === 'linux' && !in_array('linux_agent', $component_filters, true)) { -$results[$remote_id] = ['success' => true, 'status' => 'skipped', 'message' => 'Skipped Linux agent by filter.', 'agent_os' => $agent_os]; -continue; -} -if ($agent_os === 'windows' && !in_array('windows_agent', $component_filters, true)) { -$results[$remote_id] = ['success' => true, 'status' => 'skipped', 'message' => 'Skipped Windows agent by filter.', 'agent_os' => $agent_os]; -continue; -} -$component = ($agent_os === 'windows') ? 'windows_agent' : 'linux_agent'; -$result = gsp_dispatch_remote_agent_update($row, $update_cfg, $component); -$result['agent_os'] = $agent_os; -$result['remote_server_name'] = $row['remote_server_name']; -$result['agent_ip'] = $row['agent_ip']; -$results[$remote_id] = $result; -if (!empty($result['success'])) { -$success_count++; -} -gsp_update_log('Remote agent update requested: id=' . $remote_id . ' component=' . $component . ' result=' . json_encode($result)); -} -return ['success' => true, 'results' => $results, 'success_count' => $success_count]; -} - function gsp_get_available_backups($backup_base = null) { $backups = []; @@ -1931,26 +1068,16 @@ $db->setSettings([ ]); } -$panel_restore = gsp_restore_archive($backup_dir . '/panel-files.tar.gz', GSP_PANEL_DIR); -if (!$panel_restore['success']) { -if (!$had_maintenance) { -$db->setSettings(['maintenance_mode' => '0']); -} -return $panel_restore; -} -$website_archive = $backup_dir . '/website-files.tar.gz'; -if (file_exists($website_archive)) { -$website_restore = gsp_restore_archive($website_archive, GSP_WEBSITE_DIR); -if (!$website_restore['success']) { -if (!$had_maintenance) { -$db->setSettings(['maintenance_mode' => '0']); -} -return $website_restore; -} -} -if (file_exists($backup_dir . '/version.json.bak')) { -@copy($backup_dir . '/version.json.bak', GSP_VERSION_JSON); -} + $panel_restore = gsp_restore_archive($backup_dir . '/panel-files.tar.gz', GSP_PANEL_DIR); + if (!$panel_restore['success']) { + if (!$had_maintenance) { + $db->setSettings(['maintenance_mode' => '0']); + } + return $panel_restore; + } + if (file_exists($backup_dir . '/version.json.bak')) { + @copy($backup_dir . '/version.json.bak', GSP_VERSION_JSON); + } $db_restore = gsp_restore_database_from_backup($backup_dir); if (!$db_restore['success']) { gsp_update_log('Revert warning: ' . $db_restore['error']); @@ -2449,7 +1576,7 @@ print_success(htmlspecialchars(isset($disable['message']) ? $disable['message'] } else { print_failure('Disable SSL vhost failed: ' . htmlspecialchars($disable['error'])); } - } elseif ($action === 'backup_only') { +} elseif ($action === 'backup_only') { $result = gsp_create_full_backup('backup-only', 'manual', false, $update_cfg); if ($result['success']) { print_success('Backup created: ' . htmlspecialchars($result['backup_dir']) . ''); @@ -2462,18 +1589,11 @@ $new_cfg = [ 'branch' => isset($_POST['gsp_update_branch']) ? trim((string)$_POST['gsp_update_branch']) : '', 'repo_root' => isset($_POST['gsp_update_repo_root']) ? rtrim(trim((string)$_POST['gsp_update_repo_root']), '/') : '', 'panel_path' => isset($_POST['gsp_update_panel_path']) ? rtrim(trim((string)$_POST['gsp_update_panel_path']), '/') : '', -'website_path' => isset($_POST['gsp_update_website_path']) ? rtrim(trim((string)$_POST['gsp_update_website_path']), '/') : '', 'panel_source_path' => isset($_POST['gsp_update_panel_source_path']) ? trim((string)$_POST['gsp_update_panel_source_path'], '/') : '', -'linux_agent_source_path' => isset($_POST['gsp_update_linux_agent_source_path']) ? trim((string)$_POST['gsp_update_linux_agent_source_path'], '/') : '', -'windows_agent_source_path' => isset($_POST['gsp_update_windows_agent_source_path']) ? trim((string)$_POST['gsp_update_windows_agent_source_path'], '/') : '', -'website_source_path' => isset($_POST['gsp_update_website_source_path']) ? trim((string)$_POST['gsp_update_website_source_path'], '/') : '', 'git_path' => isset($_POST['gsp_update_git_path']) ? trim((string)$_POST['gsp_update_git_path']) : '', 'backup_path' => isset($_POST['gsp_update_backup_path']) ? rtrim(trim((string)$_POST['gsp_update_backup_path']), '/') : '', 'backup_retention' => isset($_POST['gsp_update_backup_retention']) ? trim((string)$_POST['gsp_update_backup_retention']) : '', 'panel_post_update_command' => isset($_POST['gsp_update_panel_post_update_command']) ? trim((string)$_POST['gsp_update_panel_post_update_command']) : '', -'website_post_update_command' => isset($_POST['gsp_update_website_post_update_command']) ? trim((string)$_POST['gsp_update_website_post_update_command']) : '', -'linux_agent_post_update_command' => isset($_POST['gsp_update_linux_agent_post_update_command']) ? trim((string)$_POST['gsp_update_linux_agent_post_update_command']) : '', -'windows_agent_post_update_command' => isset($_POST['gsp_update_windows_agent_post_update_command']) ? trim((string)$_POST['gsp_update_windows_agent_post_update_command']) : '', 'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0', ]; $errors = gsp_validate_update_settings($new_cfg); @@ -2485,126 +1605,37 @@ $db->setSettings([ 'gsp_update_branch' => $new_cfg['branch'], 'gsp_update_repo_root' => $new_cfg['repo_root'], 'gsp_update_panel_path' => $new_cfg['panel_path'], -'gsp_update_website_path' => $new_cfg['website_path'], 'gsp_update_panel_source_path' => $new_cfg['panel_source_path'], -'gsp_update_linux_agent_source_path' => $new_cfg['linux_agent_source_path'], -'gsp_update_windows_agent_source_path' => $new_cfg['windows_agent_source_path'], -'gsp_update_website_source_path' => $new_cfg['website_source_path'], 'gsp_update_git_path' => $new_cfg['git_path'], 'gsp_update_backup_path' => $new_cfg['backup_path'], 'gsp_update_backup_retention' => $new_cfg['backup_retention'], 'gsp_update_panel_post_update_command' => $new_cfg['panel_post_update_command'], -'gsp_update_website_post_update_command' => $new_cfg['website_post_update_command'], -'gsp_update_linux_agent_post_update_command' => $new_cfg['linux_agent_post_update_command'], -'gsp_update_windows_agent_post_update_command' => $new_cfg['windows_agent_post_update_command'], 'gsp_update_backup_before' => $new_cfg['backup_before_update'], ]); $settings['gsp_update_repo_url'] = $new_cfg['repo_url']; $settings['gsp_update_branch'] = $new_cfg['branch']; $settings['gsp_update_repo_root'] = $new_cfg['repo_root']; $settings['gsp_update_panel_path'] = $new_cfg['panel_path']; -$settings['gsp_update_website_path'] = $new_cfg['website_path']; $settings['gsp_update_panel_source_path'] = $new_cfg['panel_source_path']; -$settings['gsp_update_linux_agent_source_path'] = $new_cfg['linux_agent_source_path']; -$settings['gsp_update_windows_agent_source_path'] = $new_cfg['windows_agent_source_path']; -$settings['gsp_update_website_source_path'] = $new_cfg['website_source_path']; $settings['gsp_update_git_path'] = $new_cfg['git_path']; $settings['gsp_update_backup_path'] = $new_cfg['backup_path']; $settings['gsp_update_backup_retention'] = $new_cfg['backup_retention']; $settings['gsp_update_panel_post_update_command'] = $new_cfg['panel_post_update_command']; -$settings['gsp_update_website_post_update_command'] = $new_cfg['website_post_update_command']; -$settings['gsp_update_linux_agent_post_update_command'] = $new_cfg['linux_agent_post_update_command']; -$settings['gsp_update_windows_agent_post_update_command'] = $new_cfg['windows_agent_post_update_command']; $settings['gsp_update_backup_before'] = $new_cfg['backup_before_update']; $update_cfg = gsp_update_settings(); print_success('Update settings saved.'); } -} elseif ($action === 'update_components') { -$update_cfg = [ -'repo_url' => isset($_POST['gsp_update_repo_url']) ? trim((string)$_POST['gsp_update_repo_url']) : $update_cfg['repo_url'], -'branch' => isset($_POST['gsp_update_branch']) ? trim((string)$_POST['gsp_update_branch']) : $update_cfg['branch'], -'repo_root' => isset($_POST['gsp_update_repo_root']) ? rtrim(trim((string)$_POST['gsp_update_repo_root']), '/') : $update_cfg['repo_root'], -'panel_path' => isset($_POST['gsp_update_panel_path']) ? rtrim(trim((string)$_POST['gsp_update_panel_path']), '/') : $update_cfg['panel_path'], -'website_path' => isset($_POST['gsp_update_website_path']) ? rtrim(trim((string)$_POST['gsp_update_website_path']), '/') : $update_cfg['website_path'], -'panel_source_path' => isset($_POST['gsp_update_panel_source_path']) ? trim((string)$_POST['gsp_update_panel_source_path'], '/') : $update_cfg['panel_source_path'], -'linux_agent_source_path' => isset($_POST['gsp_update_linux_agent_source_path']) ? trim((string)$_POST['gsp_update_linux_agent_source_path'], '/') : $update_cfg['linux_agent_source_path'], -'windows_agent_source_path' => isset($_POST['gsp_update_windows_agent_source_path']) ? trim((string)$_POST['gsp_update_windows_agent_source_path'], '/') : $update_cfg['windows_agent_source_path'], -'website_source_path' => isset($_POST['gsp_update_website_source_path']) ? trim((string)$_POST['gsp_update_website_source_path'], '/') : $update_cfg['website_source_path'], - 'git_path' => isset($_POST['gsp_update_git_path']) ? trim((string)$_POST['gsp_update_git_path']) : $update_cfg['git_path'], - 'backup_path' => isset($_POST['gsp_update_backup_path']) ? rtrim(trim((string)$_POST['gsp_update_backup_path']), '/') : $update_cfg['backup_path'], - 'backup_retention' => isset($_POST['gsp_update_backup_retention']) ? trim((string)$_POST['gsp_update_backup_retention']) : $update_cfg['backup_retention'], - 'panel_post_update_command' => isset($_POST['gsp_update_panel_post_update_command']) ? trim((string)$_POST['gsp_update_panel_post_update_command']) : $update_cfg['panel_post_update_command'], -'website_post_update_command' => isset($_POST['gsp_update_website_post_update_command']) ? trim((string)$_POST['gsp_update_website_post_update_command']) : $update_cfg['website_post_update_command'], -'linux_agent_post_update_command' => isset($_POST['gsp_update_linux_agent_post_update_command']) ? trim((string)$_POST['gsp_update_linux_agent_post_update_command']) : $update_cfg['linux_agent_post_update_command'], -'windows_agent_post_update_command' => isset($_POST['gsp_update_windows_agent_post_update_command']) ? trim((string)$_POST['gsp_update_windows_agent_post_update_command']) : $update_cfg['windows_agent_post_update_command'], -'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0', -]; -$errors = gsp_validate_update_settings($update_cfg); -if (!empty($errors)) { -print_failure('Update settings are invalid: ' . htmlspecialchars(implode(' | ', $errors))); -} else { -$requested_components = isset($_POST['gsp_update_components']) && is_array($_POST['gsp_update_components']) ? $_POST['gsp_update_components'] : []; -$requested_components = array_values(array_intersect(array('panel', 'website', 'linux_agent', 'windows_agent'), array_map('strval', $requested_components))); -$local_components = array_values(array_intersect(array('panel', 'website'), $requested_components)); -$agent_components = array_values(array_intersect(array('linux_agent', 'windows_agent'), $requested_components)); -$remote_ids = isset($_POST['gsp_update_remote_ids']) && is_array($_POST['gsp_update_remote_ids']) ? array_map('intval', $_POST['gsp_update_remote_ids']) : []; -if (!empty($_POST['gsp_update_all_agents'])) { -$remote_ids = array(); -foreach (gsp_get_remote_agent_rows() as $row) { -$remote_ids[] = (int)$row['remote_server_id']; -} -} -if (empty($requested_components)) { -print_failure('Select at least one component to update.'); -} else { -$messages = []; -if (!empty($local_components)) { -$local_result = gsp_do_local_component_update($update_cfg, $local_components); -if (!$local_result['success']) { -print_failure('Local component update failed: ' . htmlspecialchars($local_result['error'])); -} else { -$messages[] = 'Local components updated, files copied: ' . intval($local_result['files_copied']); -} -} -if (!empty($agent_components)) { -$remote_result = gsp_do_remote_agents_update($update_cfg, $remote_ids, $agent_components); -if (empty($remote_result['success']) && empty($remote_result['results'])) { -print_failure('Remote agent update failed: ' . htmlspecialchars(isset($remote_result['message']) ? $remote_result['message'] : 'Unknown error.')); -} -if (!empty($remote_result['results'])) { -echo "

Remote Agent Update Results

"; -foreach ($remote_result['results'] as $rid => $result_row) { -$status = !empty($result_row['success']) ? (isset($result_row['status']) ? $result_row['status'] : 'queued') : (isset($result_row['status']) ? $result_row['status'] : 'failed'); -$msg = isset($result_row['message']) ? $result_row['message'] : (isset($result_row['log_path']) ? $result_row['log_path'] : ''); -echo ""; -} -echo "
Remote IDStatusMessage
" . intval($rid) . "" . htmlspecialchars($status) . "" . htmlspecialchars($msg) . "
"; -} -$messages[] = 'Remote agent updates requested: ' . intval(isset($remote_result['success_count']) ? $remote_result['success_count'] : 0); -} -if (!empty($messages)) { -print_success(implode(' | ', array_map('htmlspecialchars', $messages))); -} -} -} } elseif ($action === 'update_configured') { $update_cfg = [ 'repo_url' => isset($_POST['gsp_update_repo_url']) ? trim((string)$_POST['gsp_update_repo_url']) : $update_cfg['repo_url'], 'branch' => isset($_POST['gsp_update_branch']) ? trim((string)$_POST['gsp_update_branch']) : $update_cfg['branch'], 'repo_root' => isset($_POST['gsp_update_repo_root']) ? rtrim(trim((string)$_POST['gsp_update_repo_root']), '/') : $update_cfg['repo_root'], 'panel_path' => isset($_POST['gsp_update_panel_path']) ? rtrim(trim((string)$_POST['gsp_update_panel_path']), '/') : $update_cfg['panel_path'], -'website_path' => isset($_POST['gsp_update_website_path']) ? rtrim(trim((string)$_POST['gsp_update_website_path']), '/') : $update_cfg['website_path'], 'panel_source_path' => isset($_POST['gsp_update_panel_source_path']) ? trim((string)$_POST['gsp_update_panel_source_path'], '/') : $update_cfg['panel_source_path'], -'linux_agent_source_path' => isset($_POST['gsp_update_linux_agent_source_path']) ? trim((string)$_POST['gsp_update_linux_agent_source_path'], '/') : $update_cfg['linux_agent_source_path'], -'windows_agent_source_path' => isset($_POST['gsp_update_windows_agent_source_path']) ? trim((string)$_POST['gsp_update_windows_agent_source_path'], '/') : $update_cfg['windows_agent_source_path'], -'website_source_path' => isset($_POST['gsp_update_website_source_path']) ? trim((string)$_POST['gsp_update_website_source_path'], '/') : $update_cfg['website_source_path'], 'git_path' => isset($_POST['gsp_update_git_path']) ? trim((string)$_POST['gsp_update_git_path']) : $update_cfg['git_path'], 'backup_path' => isset($_POST['gsp_update_backup_path']) ? rtrim(trim((string)$_POST['gsp_update_backup_path']), '/') : $update_cfg['backup_path'], 'backup_retention' => isset($_POST['gsp_update_backup_retention']) ? trim((string)$_POST['gsp_update_backup_retention']) : $update_cfg['backup_retention'], 'panel_post_update_command' => isset($_POST['gsp_update_panel_post_update_command']) ? trim((string)$_POST['gsp_update_panel_post_update_command']) : $update_cfg['panel_post_update_command'], -'website_post_update_command' => isset($_POST['gsp_update_website_post_update_command']) ? trim((string)$_POST['gsp_update_website_post_update_command']) : $update_cfg['website_post_update_command'], -'linux_agent_post_update_command' => isset($_POST['gsp_update_linux_agent_post_update_command']) ? trim((string)$_POST['gsp_update_linux_agent_post_update_command']) : $update_cfg['linux_agent_post_update_command'], -'windows_agent_post_update_command' => isset($_POST['gsp_update_windows_agent_post_update_command']) ? trim((string)$_POST['gsp_update_windows_agent_post_update_command']) : $update_cfg['windows_agent_post_update_command'], 'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0', ]; $db->setSettings([ @@ -2612,18 +1643,11 @@ $db->setSettings([ 'gsp_update_branch' => $update_cfg['branch'], 'gsp_update_repo_root' => $update_cfg['repo_root'], 'gsp_update_panel_path' => $update_cfg['panel_path'], -'gsp_update_website_path' => $update_cfg['website_path'], 'gsp_update_panel_source_path' => $update_cfg['panel_source_path'], -'gsp_update_linux_agent_source_path' => $update_cfg['linux_agent_source_path'], -'gsp_update_windows_agent_source_path' => $update_cfg['windows_agent_source_path'], -'gsp_update_website_source_path' => $update_cfg['website_source_path'], 'gsp_update_git_path' => $update_cfg['git_path'], 'gsp_update_backup_path' => $update_cfg['backup_path'], 'gsp_update_backup_retention' => $update_cfg['backup_retention'], 'gsp_update_panel_post_update_command' => $update_cfg['panel_post_update_command'], -'gsp_update_website_post_update_command' => $update_cfg['website_post_update_command'], -'gsp_update_linux_agent_post_update_command' => $update_cfg['linux_agent_post_update_command'], -'gsp_update_windows_agent_post_update_command' => $update_cfg['windows_agent_post_update_command'], 'gsp_update_backup_before' => $update_cfg['backup_before_update'], ]); if (!function_exists('gsp_do_configured_git_update')) { @@ -2631,50 +1655,9 @@ $result = ['success' => false, 'error' => 'Configured Git updater helper is unav } else { $result = gsp_do_configured_git_update($update_cfg, $restart_nonce); } -if (!empty($result['restart_required'])) { -print_success('Updater files changed and were updated first. Restarting update with refreshed updater...'); -$auto_restart_payload = ['action' => 'update_configured', 'nonce' => $result['restart_nonce']]; -} elseif ($result['success']) { +if ($result['success']) { print_success('Panel updated from configured repository branch ' . htmlspecialchars($update_cfg['branch']) . '. ' -. intval($result['files_copied']) . ' file(s) copied (Panel: ' . intval($result['panel_files_copied']) . ', Website: ' . intval($result['website_files_copied']) . ').'); -} else { -print_failure('Update failed: ' . htmlspecialchars($result['error'])); -} -} elseif ($action === 'update_release') { -$version = isset($_POST['gsp_release_version']) ? trim($_POST['gsp_release_version']) : ''; -if (!preg_match('/^[a-zA-Z0-9._\-]+$/', $version) || strlen($version) > 80) { -print_failure('Invalid release tag selected.'); -} else { -$result = gsp_do_update($repo_owner, $repo_name, $version, 'release', $restart_nonce); -if (!empty($result['restart_required'])) { -print_success('Updater files changed and were updated first. Restarting update with refreshed updater...'); -$auto_restart_payload = ['action' => 'update_release', 'nonce' => $result['restart_nonce'], 'version' => $version]; -} elseif ($result['success']) { -print_success('Panel updated to release ' . htmlspecialchars($version) . '. ' -. intval($result['files_copied']) . ' file(s) copied (Panel: ' . intval($result['panel_files_copied']) . ', Website: ' . intval($result['website_files_copied']) . ').'); -} else { -print_failure('Update failed: ' . htmlspecialchars($result['error'])); -} -} -} elseif ($action === 'update_stable') { -$result = gsp_do_update($repo_owner, $repo_name, $stable_branch, 'development', $restart_nonce); -if (!empty($result['restart_required'])) { -print_success('Updater files changed and were updated first. Restarting stable update...'); -$auto_restart_payload = ['action' => 'update_stable', 'nonce' => $result['restart_nonce']]; -} elseif ($result['success']) { -print_success('Panel updated to GitHub Stable (' . htmlspecialchars($stable_branch) . '). ' -. intval($result['files_copied']) . ' file(s) copied (Panel: ' . intval($result['panel_files_copied']) . ', Website: ' . intval($result['website_files_copied']) . ').'); -} else { -print_failure('Update failed: ' . htmlspecialchars($result['error'])); -} -} elseif ($action === 'update_unstable') { -$result = gsp_do_update($repo_owner, $repo_name, $unstable_branch, 'cutting-edge', $restart_nonce); -if (!empty($result['restart_required'])) { -print_success('Updater files changed and were updated first. Restarting unstable update...'); -$auto_restart_payload = ['action' => 'update_unstable', 'nonce' => $result['restart_nonce']]; -} elseif ($result['success']) { -print_success('Panel updated to GitHub Unstable (' . htmlspecialchars($unstable_branch) . '). ' -. intval($result['files_copied']) . ' file(s) copied (Panel: ' . intval($result['panel_files_copied']) . ', Website: ' . intval($result['website_files_copied']) . ').'); +. intval($result['files_copied']) . ' file(s) copied.'); } else { print_failure('Update failed: ' . htmlspecialchars($result['error'])); } @@ -2759,66 +1742,17 @@ echo "Repository URL / PathBranch / Channel\n"; echo "Repository Root\n"; echo "Panel Path\n"; -echo "Website Path\n"; echo "Panel Source Folder\n"; -echo "Linux Agent Source Folder\n"; -echo "Windows Agent Source Folder\n"; -echo "Website Source Folder\n"; echo "Git Executable Usually git.\n"; echo "Backup Path\n"; echo "Backup Retention Keep newest backups only.\n"; echo "Backup Before Update\n"; echo "Panel Post-update Command Admin-only dangerous command.\n"; -echo "Website Post-update Command\n"; -echo "Linux Agent Post-update Command\n"; -echo "Windows Agent Post-update Command\n"; echo "\n"; echo "

\n"; echo " "; echo "\n"; echo "

\n"; -echo "

Update Components

\n"; -echo "

Select the application components to update from the configured repository. Server homes, hosted game data, uploads, logs, cache, and agent configuration folders are protected.

\n"; -echo "

"; -echo " "; -echo " "; -echo " "; -echo " "; -echo ""; -echo "

\n"; -$remote_rows = gsp_get_remote_agent_rows(); -if (!empty($remote_rows)) { -if (!class_exists('OGPRemoteLibrary') && function_exists('xmlrpc_encode_request') && function_exists('xmlrpc_decode') && file_exists(GSP_PANEL_DIR . '/includes/lib_remote.php')) { -require_once(GSP_PANEL_DIR . '/includes/lib_remote.php'); -} -echo "\n"; -foreach ($remote_rows as $remote_row) { -$status_text = 'not checked'; -$os_text = ''; -if (class_exists('OGPRemoteLibrary')) { -try { -$probe = new OGPRemoteLibrary($remote_row['agent_ip'], $remote_row['agent_port'], $remote_row['encryption_key'], 2); -$status_text = ((int)$probe->status_chk() === 1) ? 'online' : 'offline/unknown'; -if ($status_text === 'online') { -$os_text = (string)$probe->what_os(); -} -} catch (Exception $e) { -$status_text = 'unknown'; -} -} -echo ""; -echo ""; -echo ""; -echo ""; -echo ""; -echo ""; -echo "\n"; -} -echo "
SelectNameHostStatusReported OS
" . htmlspecialchars($remote_row['remote_server_name']) . "" . htmlspecialchars($remote_row['agent_ip'] . ':' . $remote_row['agent_port']) . "" . htmlspecialchars($status_text) . "" . htmlspecialchars($os_text !== '' ? $os_text : gsp_infer_agent_os($remote_row, '')) . "
\n"; -} else { -echo "

No remote agents are configured.

\n"; -} -echo "

\n"; echo "\n"; echo "

Backup

\n"; @@ -2843,7 +1777,7 @@ echo "\n"; } echo " "; echo " "; -echo "\n"; +echo "\n"; echo "\n"; } @@ -2894,160 +1828,5 @@ if ($auto_restart_payload) { gsp_render_restart_form($auto_restart_payload['action'], $csrf_token, $auto_restart_payload['nonce'], isset($auto_restart_payload['version']) ? $auto_restart_payload['version'] : ''); } return; - -echo "

Panel Updates

\n"; -echo "
\n"; -echo "

Detected Layout

\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -if ($last_layout) { -echo "\n"; -echo "\n"; -echo "\n"; -} -echo "
Expected Root:" . htmlspecialchars(GSP_EXPECTED_ROOT) . "
Detected GSP Root:" . htmlspecialchars(GSP_ROOT_DIR) . "
Panel Path:" . htmlspecialchars(GSP_PANEL_DIR) . "
Website Path:" . htmlspecialchars(GSP_WEBSITE_DIR) . "
Configured Stable Branch:" . htmlspecialchars($stable_branch) . "
Configured Unstable Branch:" . htmlspecialchars($unstable_branch) . "
Update Trace Log:" . htmlspecialchars(GSP_UPDATE_LOG) . "
Last Temp Checkout Path:" . htmlspecialchars(isset($last_layout['temporary_git_checkout_path']) ? $last_layout['temporary_git_checkout_path'] : '') . "
Last Source Repo Root:" . htmlspecialchars(isset($last_layout['source_repo_root']) ? $last_layout['source_repo_root'] : '') . "
Last Source Panel Path:" . htmlspecialchars(isset($last_layout['source_panel_path']) ? $last_layout['source_panel_path'] : '') . "

\n"; - -echo "

Current Installation

\n"; -echo "\n"; -if ($vinfo) { -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -} else { -echo "\n"; -echo "\n"; -} -if ($git_commit) { -echo "\n"; -} -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "
Installed Type:" . htmlspecialchars($vinfo['installed_type']) . "
Installed Source:" . htmlspecialchars($vinfo['installed_source']) . "
Installed Version:" . htmlspecialchars($vinfo['installed_version']) . "
Installed At:" . htmlspecialchars($vinfo['installed_at']) . "
Installed Version:" . htmlspecialchars($current_version) . "
Current Branch:" . htmlspecialchars($current_branch) . "
Git Commit:" . htmlspecialchars(substr($git_commit, 0, 12)) . "
Latest Release:" . $latest_release . "
Repository:" . htmlspecialchars($repo_owner . '/' . $repo_name) . "
Backup Directory:" . htmlspecialchars(GSP_BACKUP_BASE) . "
Backups Stored:" . intval(count($backups)) . " (retention: 5)

\n"; - -echo "

Updater Preflight & Patches

\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "
Preflight Status:" . ($preflight_result['success'] ? 'PASS' : 'FAIL') . "
Pending Patches:" . intval(count($patch_overview['pending'])) . "
Patch Directory:" . htmlspecialchars(GSP_PATCH_DIR) . "
\n"; -if (!empty($preflight_result['warnings'])) { -echo "

Preflight warnings:
" . implode('
', array_map('htmlspecialchars', $preflight_result['warnings'])) . "

\n"; -} -if (!empty($preflight_result['errors'])) { -echo "

Preflight errors:
" . implode('
', array_map('htmlspecialchars', $preflight_result['errors'])) . "

\n"; -} - -echo "
\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "
\n"; -echo "
\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "
\n"; - -echo "

Apache Configuration Status

\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "
Config Directory:" . htmlspecialchars($apache_scan_result['base']) . "
Configs Found:" . intval(count($apache_scan_result['files'])) . "
Stale Path Hits:" . intval(count($apache_scan_result['stale_issues'])) . "
SSL Certificate Issues:" . intval(count($apache_scan_result['ssl_issues'])) . "
Recommended Panel Path:" . htmlspecialchars(GSP_PANEL_DIR) . "
Recommended Website Path:" . htmlspecialchars(GSP_WEBSITE_DIR) . "
\n"; -if (!empty($apache_scan_result['stale_issues'])) { -echo "

Apache stale path issues:
" . implode('
', array_map('htmlspecialchars', array_unique($apache_scan_result['stale_issues']))) . "

\n"; -} -if (!empty($apache_scan_result['planned_replacements'])) { -echo "

Planned replacements:

"; -foreach ((array)$apache_scan_result['planned_replacements'] as $plan_row) { -echo ""; -} -echo "
VhostDirectiveCurrentReplacement
" . htmlspecialchars($plan_row['vhost']) . "" . htmlspecialchars($plan_row['directive']) . "" . htmlspecialchars($plan_row['from']) . "" . htmlspecialchars($plan_row['to']) . "
"; -} -if (!empty($apache_scan_result['ssl_issues'])) { -echo "

SSL certificate issues:
"; -foreach ((array)$apache_scan_result['ssl_issues'] as $ssl_issue) { -echo htmlspecialchars($ssl_issue['vhost'] . ' ' . $ssl_issue['directive'] . ': ' . $ssl_issue['path'] . ' (' . $ssl_issue['reason'] . ')') . "
"; -} -echo "

\n"; -echo "

SSL certificate issues are diagnostic only and do not block Panel updates.

"; -} -echo "
\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "
\n"; - -echo "

Backup

\n"; -echo "
\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "
\n"; - -echo "

Update Panel

\n"; -if (is_array($releases) && !empty($releases)) { -echo "
\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo " \n"; -echo "
\n"; -} -echo "
\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "
\n"; -echo "
\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo "
\n"; - -if (!empty($backups)) { -echo "

Rollback

\n"; -echo "
\n"; -echo "\n"; -echo "\n"; -echo "\n"; -echo " \n"; -echo " \n"; -echo "
\n"; -} - -echo "
\n"; - -if ($auto_restart_payload) { -$version = isset($auto_restart_payload['version']) ? $auto_restart_payload['version'] : ''; -gsp_render_restart_form($auto_restart_payload['action'], $csrf_token, $auto_restart_payload['nonce'], $version); -} } ?> diff --git a/docs/development/CODEX_GUIDE.md b/docs/development/CODEX_GUIDE.md index aed14aa2..51fc622e 100644 --- a/docs/development/CODEX_GUIDE.md +++ b/docs/development/CODEX_GUIDE.md @@ -136,9 +136,9 @@ This file is the first stop for future Codex sessions working in this repository 1. Read `docs/modules/UPDATE.md`. 2. Check `Panel/modules/update/update.php`. 3. Check `Panel/modules/administration/panel_update.php`. -4. Check `Panel/includes/lib_remote.php` for the `component_update` wrapper. -5. Check both `Agent_Linux/ogp_agent.pl` and `Agent-Windows/OGP64/OGP/ogp_agent.pl` for the `component_update` RPC. -6. Remember that local Panel/Website updates and remote agent updates both clone a configured Git branch into staging and copy only configured component folders. +4. Check `Panel/includes/lib_remote.php` only if a shared helper is actually needed. +5. Remember that the active Panel update page updates the Panel only. +6. Website and agent updates are separate workflows and should not be reintroduced into the Panel update page. 7. Never let updater logic delete server homes, game install folders, user data, agent `Cfg/`, logs, uploads, backups, or runtime PID files. ## Things Already Investigated diff --git a/docs/modules/PANEL_UPDATE.md b/docs/modules/PANEL_UPDATE.md index fa9a0377..1ea36bf0 100644 --- a/docs/modules/PANEL_UPDATE.md +++ b/docs/modules/PANEL_UPDATE.md @@ -2,201 +2,98 @@ Workspace reference: [`GSP-WORKSPACE.md`](../../../GSP-WORKSPACE.md) -## Scope +## Purpose -This document covers the admin Panel updater implemented through: +This module documents the Panel-only update flow exposed at `Panel/modules/update/update.php` and implemented in `Panel/modules/administration/panel_update.php`. -- `Panel/modules/update/update.php` -- `Panel/modules/administration/panel_update.php` -- `Panel/includes/lib_remote.php` for remote agent component updates +The update page now updates the Panel only. Website and agent updates are separate workflows and are not rendered by this page. -## Current Workflow +## Configured Settings -```text -Admin opens home.php?m=update - -> update.php loads panel_update.php - -> gsp_panel_update_section() renders UI +The update page uses these settings: -Configured Git update: - -> load saved settings - -> validate repo/branch/paths/retention - -> preflight path and write checks - -> create backup when enabled - -> git clone configured branch to temp checkout - -> resolve source layout - -> self-update updater files first if needed - -> apply patches - -> sync Panel and Website files - -> preserve config.inc.php and protected paths - -> write LAST_UPDATE.txt and version metadata - -> clear cache / fix permissions - -> prune backups by retention -``` +- `gsp_update_repo_url` +- `gsp_update_branch` +- `gsp_update_repo_root` +- `gsp_update_panel_path` +- `gsp_update_panel_source_path` +- `gsp_update_git_path` +- `gsp_update_backup_path` +- `gsp_update_backup_retention` +- `gsp_update_panel_post_update_command` +- `gsp_update_backup_before` + +## Defaults + +- Repository URL / Path: `http://forge.runlevelsystems.com/dev/GSP.git` +- Branch: `Panel-unstable` +- Repository Root: `/var/www/html/GSP` +- Panel Path: `/var/www/html/GSP/Panel` +- Panel Source Folder: `Panel` +- Backup Before Update: enabled + +## Update Workflow + +1. Admin opens the Update page. +2. Panel loads the current settings. +3. Admin can save the repository and path settings. +4. Admin can create a backup of the Panel. +5. Admin can run the Panel-only update. +6. The updater clones the configured repository/branch into a temporary checkout. +7. The updater copies only the configured Panel source folder into the live Panel path. +8. `Panel/includes/config.inc.php` is preserved. +9. Optional panel post-update command runs after the copy step. +10. Version metadata and `LAST_UPDATE.txt` are written. ## Backup Workflow -Backup helpers: +When backup-before-update is enabled: -- `gsp_get_backup_base()` -- `gsp_get_backup_retention()` -- `gsp_create_full_backup()` -- `gsp_safe_component_backup()` -- `gsp_get_available_backups()` -- `gsp_get_managed_backup_entries()` -- `gsp_prune_old_backups()` +- the Panel tree is archived +- the database backup is attempted when available +- version metadata is copied +- Apache config backup can be included for repair workflows -Behavior: +The backup directory is created recursively if missing. -- full updater backups use the configured `gsp_update_backup_path` -- component-only backups also use the configured backup path -- missing parent directories are created recursively -- failures are logged to `logs/update_trace.log` with path diagnostics -- retention is enforced after full backups and component backups +Retention is enforced after each successful backup. -Managed backup directory names: +## Rollback Workflow -- full backups: `YYYY-MM-DD_HH-MM-SS` -- component backups: `component__YYYY-MM-DD_HH-MM-SS` - -Rollback uses only full backups. Component backups are retained and pruned, but are not shown in the rollback selector. - -## Retention Workflow - -Retention setting: - -- `gsp_update_backup_retention` -- default: `5` - -Behavior: - -1. Create backup. -2. Scan all updater-managed backup directories under the configured backup path. -3. Sort newest to oldest. -4. Delete entries beyond retention. -5. Log deleted backup directories to `logs/update_trace.log`. - -This applies to both: - -- full updater backups -- component backups created by local component updates - -## Required Folders And Permissions - -Minimum writable locations: - -- configured repository root -- configured Panel path -- configured backup path -- `logs/` under repository root for `update_trace.log` - -Required tools when used: - -- `git` -- `tar` -- `mysqldump` for database backup -- `mysql` for restore +- The rollback selector lists managed backups. +- Restoring a backup restores the Panel tree and metadata. +- Missing website archives are ignored because the active backup path is Panel-focused. +- Apache config restore is optional. ## Error Handling -The update page should not white-screen for routine helper/runtime failures. +Failures are logged to `logs/update_trace.log` and shown as user-facing errors on the page. -Current protections: +The page should remain usable even if: -- `update.php` checks that `gsp_panel_update_section()` exists before calling it -- `update.php` catches `Throwable` and logs unexpected failures -- `gsp_panel_update_section()` catches action-time `Throwable` and keeps rendering the UI -- backup directory creation logs detailed path diagnostics -- missing helper regressions are surfaced as user-visible failures instead of fatal white screens where possible - -Log file: - -- `GSP_ROOT/logs/update_trace.log` - -## Helper Scope Warnings - -These helpers must remain top-level: - -- `gsp_update_settings()` -- `gsp_validate_update_settings()` -- `gsp_checkout_update_source()` -- `gsp_do_configured_git_update()` -- `gsp_disable_ssl_vhost()` - -If any of these are accidentally nested inside another function, the update page can pass `php -l` but still fail at runtime with `Call to undefined function ...`. - -## Scheduler Audit - -Current finding: - -- the existing `cron` scheduler is game-server oriented -- it schedules `gamemanager` and `server_content` actions through `ogp_api.php` -- it does **not** currently expose a first-class scheduled self-update path for: - - Panel updates - - Website updates - - remote Linux agent code updates - - remote Windows agent code updates - -Result: - -- backup/retention integration for scheduled self-updates is currently not applicable because those scheduled self-update jobs do not exist in the current codebase -- game-server Steam/content scheduler jobs are separate and do not use this updater module - -## Remote Agent Update Flow - -Remote component updates use: - -- `lib_remote.php::component_update()` -- agent-side `component_update` RPC - -The Panel: - -1. validates configured repo and source folder -2. selects Linux or Windows agent source folder -3. builds an encoded payload -4. sends the payload over XML-RPC -5. shows queued/failed results per remote host +- the repository source is invalid +- git cannot run +- backup directory creation fails +- backup retention pruning fails +- the post-update command fails ## Troubleshooting -### Undefined function crash +If the page shows `Call to undefined function gsp_checkout_update_source()` or a related updater fatal: -Check: +1. Verify the deployed `Panel/modules/administration/panel_update.php` matches the repository version. +2. Confirm the helper exists at top level in the deployed file. +3. Confirm the page is not loading a stale cached copy. +4. Check `logs/update_trace.log` for the detailed error. -- `php -l Panel/modules/administration/panel_update.php` -- `php -r 'require "Panel/modules/administration/panel_update.php"; var_dump(function_exists("gsp_checkout_update_source"));'` +If backups fail to create: -If `php -l` passes but `function_exists(...)` is false, a helper was nested inside another function. +- verify the backup path exists or can be created recursively +- verify the web server user can write to the path +- verify the path is absolute and does not contain unsafe relative segments -### Backup directory creation fails +## Notes -Check: - -- configured `gsp_update_backup_path` -- parent directory existence -- web server write access -- `logs/update_trace.log` for the exact `mkdir` failure - -### Retention appears wrong - -Remember: - -- rollback list shows only full backups -- managed backup count includes full backups and component backups -- pruning applies to all updater-managed backup directories - -### Update page loads but remote agent updates fail - -Check: - -- PHP XML-RPC extension on the Panel host -- agent reachability and encryption key -- agent-side `component_update` support - -## Validation Commands - -```bash -php -l Panel/modules/administration/panel_update.php -php -l Panel/modules/update/update.php -php Panel/modules/update/tests/update_config_smoke.php -php -r 'require "Panel/modules/administration/panel_update.php"; foreach (["gsp_checkout_update_source","gsp_do_configured_git_update","gsp_disable_ssl_vhost"] as $f) echo $f,":",function_exists($f)?"yes":"no",PHP_EOL;' -``` +- Agents now update using their own scripts and workflows. +- Website updates are handled separately. +- The Panel update page intentionally does not render agent or website update controls. diff --git a/docs/modules/UPDATE.md b/docs/modules/UPDATE.md index 1ebbe0ba..7bd9bfca 100644 --- a/docs/modules/UPDATE.md +++ b/docs/modules/UPDATE.md @@ -8,22 +8,26 @@ Primary operational reference: ## Role -`Panel/modules/update` exposes the admin Panel update page. The page delegates most update behavior to: +`Panel/modules/update` exposes the admin Panel update page. The page delegates its logic to: - `Panel/modules/administration/panel_update.php` +This page now updates the **Panel only**. + ## Current Behavior -The update page is intentionally simple: +The active update page is intentionally narrow: - shows the installed Panel version - shows the current git branch and commit when available -- exposes editable repository settings -- can create a backup -- can update from the configured repository and branch -- can roll back to an existing backup +- exposes editable repository settings for the Panel update only +- can create a Panel backup +- can update the Panel from the configured repository and branch +- can roll back to an existing Panel backup - keeps Apache diagnostics in a collapsed Advanced Diagnostics section +It does not expose agent updates, website updates, or “update all components” behavior. + ## Update Settings The admin page stores these settings in the Panel settings table: @@ -32,14 +36,11 @@ The admin page stores these settings in the Panel settings table: - `gsp_update_branch` - `gsp_update_repo_root` - `gsp_update_panel_path` -- `gsp_update_website_path` - `gsp_update_panel_source_path` -- `gsp_update_linux_agent_source_path` -- `gsp_update_windows_agent_source_path` -- `gsp_update_website_source_path` - `gsp_update_git_path` - `gsp_update_backup_path` -- optional admin-only post-update commands per component +- `gsp_update_backup_retention` +- `gsp_update_panel_post_update_command` - `gsp_update_backup_before` Defaults: @@ -48,63 +49,40 @@ Defaults: - Branch: `Panel-unstable` - Repository Root: `/var/www/html/GSP` - Panel Path: `/var/www/html/GSP/Panel` -- Website Path: `/var/www/html/GSP/Website` - Panel Source Folder: `Panel` -- Linux Agent Source Folder: `Agent_Linux` -- Windows Agent Source Folder: `Agent-Windows` -- Website Source Folder: `Website` - Backup Before Update: enabled -Important implementation note: +## Backup and Rollback -- `gsp_update_settings()` and `gsp_validate_update_settings()` are defined at top level in `Panel/modules/administration/panel_update.php`. -- These helpers must not be nested inside another function. A previous bad edit placed `gsp_update_settings()` inside `gsp_get_git_commit()`, which caused a fatal error when the update page called the helper before `gsp_get_git_commit()` had ever executed. -- If the update page throws `Call to undefined function gsp_update_settings()`, first verify the deployed `Panel/modules/administration/panel_update.php` matches the repository version and that this helper exists near the top of the file before `gsp_panel_update_section()` is called. -- `gsp_do_configured_git_update()` must also remain top-level. A bad edit placed it inside `gsp_do_update()`, so `/home.php?m=update` called an undefined function until a legacy GitHub update path happened to execute first. -- `gsp_checkout_update_source()` must remain top-level. If it is nested inside `gsp_apply_update_from_zip()` or another helper, configured Git updates can fatal with `Call to undefined function gsp_checkout_update_source()`. +Backups are Panel-focused: + +- the Panel tree is archived +- `includes/config.inc.php` is preserved by the restore logic +- version markers are preserved +- database backup is included when the environment supports it +- Apache configs can be included for diagnostic/repair workflows + +Retention is enforced after each successful backup. + +If retention is `5`, the newest five managed backups are kept and older managed backups are pruned automatically. + +## Implementation Notes + +- `gsp_update_settings()` and `gsp_validate_update_settings()` remain top-level in `Panel/modules/administration/panel_update.php`. +- `gsp_checkout_update_source()` remains a top-level compatibility helper for the configured repository checkout. +- `gsp_do_configured_git_update()` now applies only the Panel source folder into the configured Panel path. +- The page no longer depends on website or agent update settings. ## Update Flow 1. Save or submit repository settings. -2. Validate repository URL, branch, repo root, and Panel path. -3. Run preflight against the configured paths. -4. Create a backup when enabled. -5. Clone the configured repository source and branch into a temporary checkout. -6. Sync files into the configured root/Panel paths. -7. Preserve `Panel/includes/config.inc.php`. -8. Run module updates/post-update hooks. -9. Write version metadata and `LAST_UPDATE.txt`. - -## Component Updates - -The update page can update selected components from one repository: - -- Panel files -- Website files -- Linux agents -- Windows/Cygwin agents - -Local Panel/Website updates clone the configured repository into a temporary checkout and copy only the configured component source folder into the configured destination path. Protected folders and files are not overwritten: - -- `includes/config.inc.php` -- `Cfg/` -- `ServerFiles/` -- `Schedule/` -- `logs/` -- `screenlogs/` -- `cache/` -- `tmp/` -- `uploads/` -- `backups/` -- `steamcmd/` -- `startups/` -- PID files - -Remote agent updates use the encrypted Panel-Agent XML-RPC channel and the `component_update` RPC. The agent writes a detached updater script, clones the repo to staging, backs up the current agent code, copies only the configured agent source folder, validates `ogp_agent.pl`, then restarts through `systemd` when available or the existing `screen` fallback. - -Remote update status is queued/asynchronous. The first response confirms that the update was accepted and gives the agent-side log path. - -Remote updates require PHP XML-RPC on the Panel host. If the extension is missing, the update page still loads and reports a clean `missing_xmlrpc` error when a remote agent update is requested. +2. Validate repository URL, branch, repo root, Panel path, and backup settings. +3. Create a backup when enabled. +4. Clone the configured repository source and branch into a temporary checkout. +5. Copy only the configured Panel source folder into the configured Panel path. +6. Preserve `Panel/includes/config.inc.php`. +7. Run module updates/post-update hooks. +8. Write version metadata and `LAST_UPDATE.txt`. ## Smoke Tests @@ -113,27 +91,22 @@ Useful validation commands: ```bash php -l Panel/modules/administration/panel_update.php php -l Panel/modules/update/update.php -php -l Panel/includes/lib_remote.php php Panel/modules/update/tests/update_config_smoke.php -perl -c ogp_agent.pl # run from each installed agent directory with dependencies present ``` ## Diagnostics Apache and SSL checks are diagnostics only. Missing SSL certificates do not block Panel updates. -The old repeated SSL vhost disable buttons are not part of the primary update page. Apache path repair remains available under Advanced Diagnostics, and SSL issues are shown as concise diagnostic warnings. - -## Remaining Issues - -- The updater still contains legacy GitHub release helper code that is no longer rendered by the simplified primary UI. -- Real production testing should confirm file ownership and web-server permissions on `/var/www/html/GSP`. - ## Repair Notes - The update page fatal `Call to undefined function gsp_update_settings()` means the deployed `Panel/modules/administration/panel_update.php` is missing the top-level helper or is not the current repository copy. - The update page fatal `Call to undefined function gsp_do_configured_git_update()` means the configured Git update helper is missing or nested inside another helper in the deployed `panel_update.php`. - The update page fatal `Call to undefined function gsp_checkout_update_source()` means the configured Git checkout helper is missing or nested inside another helper in the deployed `panel_update.php`. -- `Panel/modules/update/update.php` only loads `Panel/modules/administration/panel_update.php` and calls `gsp_panel_update_section()`. -- The configured update action uses `git clone --depth 1 --branch `. +- The current fix removes the multi-component update UI from the active page and keeps the Panel update path self-contained. - Clone failures are logged to `logs/update_trace.log` with the configured repository source and branch. + +## Remaining Issues + +- Legacy update helpers remain in the file for compatibility and historical reference, but the active page does not render them. +- Real production testing should confirm file ownership and web-server permissions on `/var/www/html/GSP`.