foxed update and stsrt issues

This commit is contained in:
Frank Harris 2026-06-05 14:39:10 -05:00
parent c687165132
commit c195c0930b
7 changed files with 544 additions and 70 deletions

View file

@ -27,6 +27,11 @@ defined('GSP_EXPECTED_PANEL') || define('GSP_EXPECTED_PANEL', GSP_EXPECTED_ROOT
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');
defined('GSP_DEFAULT_REPO_URL') || define('GSP_DEFAULT_REPO_URL', 'http://forge.runlevelsystems.com/dev/GSP.git');
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');
$gspPatchManager = GSP_PANEL_DIR . '/modules/update/patch_manager.php';
if (file_exists($gspPatchManager)) {
@ -129,6 +134,41 @@ $repo_root = gsp_detect_repo_root();
if (!$repo_root || !function_exists('exec')) {
return null;
}
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;
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, '/'),
'backup_before_update' => !isset($settings['gsp_update_backup_before']) ? '1' : (string)$settings['gsp_update_backup_before'],
];
}
function gsp_validate_update_settings(array $cfg)
{
$errors = [];
if (!preg_match('/^https?:\/\/[^ \t\r\n]+\.git$/i', (string)$cfg['repo_url'])
&& !preg_match('/^(?:ssh:\/\/|git@)[^ \t\r\n]+$/i', (string)$cfg['repo_url'])) {
$errors[] = 'Repository URL must be an http(s), ssh, or git@ URL.';
}
if (!preg_match('/^[A-Za-z0-9._\/-]{1,128}$/', (string)$cfg['branch'])) {
$errors[] = 'Branch/channel contains invalid characters.';
}
foreach (['repo_root', 'panel_path'] as $key) {
if (trim((string)$cfg[$key]) === '' || strpos((string)$cfg[$key], "\0") !== false || strpos((string)$cfg[$key], '..') !== false) {
$errors[] = ucfirst(str_replace('_', ' ', $key)) . ' is invalid.';
}
}
if (rtrim((string)$cfg['panel_path'], '/') !== rtrim((string)$cfg['repo_root'], '/') . '/Panel') {
$errors[] = 'Panel Path must point to the Panel folder inside Repository Root.';
}
return $errors;
}
$out = [];
$ret = 0;
exec('git -C ' . escapeshellarg($repo_root) . ' rev-parse HEAD 2>/dev/null', $out, $ret);
@ -207,58 +247,50 @@ return json_decode($data, true);
return false;
}
function gsp_preflight_check()
function gsp_preflight_check(array $update_cfg = null)
{
$errors = [];
$warnings = [];
$update_cfg = $update_cfg ?: gsp_update_settings();
$cwd = getcwd();
$cwd_real = $cwd ? (realpath($cwd) ?: $cwd) : '';
$root_real = realpath(GSP_ROOT_DIR) ?: GSP_ROOT_DIR;
$panel_real = realpath(GSP_PANEL_DIR) ?: GSP_PANEL_DIR;
$website_real = realpath(GSP_WEBSITE_DIR) ?: GSP_WEBSITE_DIR;
$expected_root_real = realpath(GSP_EXPECTED_ROOT) ?: GSP_EXPECTED_ROOT;
$expected_panel_real = realpath(GSP_EXPECTED_PANEL) ?: GSP_EXPECTED_PANEL;
$expected_website_real = realpath(GSP_EXPECTED_WEBSITE) ?: GSP_EXPECTED_WEBSITE;
$root_path = rtrim((string)$update_cfg['repo_root'], '/');
$panel_path = rtrim((string)$update_cfg['panel_path'], '/');
$website_path = $root_path . '/Website';
$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' => GSP_EXPECTED_ROOT,
'expected_panel' => GSP_EXPECTED_PANEL,
'expected_website' => GSP_EXPECTED_WEBSITE,
'gsp_root' => GSP_ROOT_DIR,
'expected_root' => $root_path,
'expected_panel' => $panel_path,
'expected_website' => $website_path,
'gsp_root' => $root_path,
'gsp_root_real' => $root_real,
'panel_dir' => GSP_PANEL_DIR,
'panel_dir' => $panel_path,
'panel_dir_real' => $panel_real,
'website_dir' => GSP_WEBSITE_DIR,
'website_dir' => $website_path,
'website_dir_real' => $website_real,
'backup_dir' => GSP_BACKUP_BASE,
'config_file' => GSP_PANEL_DIR . '/includes/config.inc.php',
'destination_panel' => GSP_PANEL_DIR,
'destination_website' => GSP_WEBSITE_DIR,
'config_file' => $panel_path . '/includes/config.inc.php',
'destination_panel' => $panel_path,
'destination_website' => $website_path,
];
if (!$layout['cwd']) {
$errors[] = 'Unable to read current working directory.';
} elseif (strpos($cwd_real, $panel_real) !== 0) {
$errors[] = 'Current working directory must be under live Panel path: ' . $panel_real;
$warnings[] = 'Current working directory is not under configured Panel path: ' . $panel_real;
}
if (!is_dir(GSP_ROOT_DIR)) {
if (!is_dir($root_path)) {
$errors[] = 'Detected GSP root path is missing.';
}
if ($root_real !== $expected_root_real) {
$errors[] = 'Detected GSP root does not match expected live root: ' . GSP_EXPECTED_ROOT;
}
if (!is_dir(GSP_PANEL_DIR)) {
if (!is_dir($panel_path)) {
$errors[] = 'Panel directory is missing.';
}
if ($panel_real !== $expected_panel_real) {
$errors[] = 'Detected Panel path does not match expected live Panel path: ' . GSP_EXPECTED_PANEL;
}
if (!is_dir(GSP_WEBSITE_DIR)) {
$errors[] = 'Website directory is missing.';
}
if ($website_real !== $expected_website_real) {
$errors[] = 'Detected Website path does not match expected live Website path: ' . GSP_EXPECTED_WEBSITE;
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.';
@ -269,7 +301,7 @@ $errors[] = 'Backups directory is missing and cannot be created.';
}
}
foreach ([GSP_ROOT_DIR, GSP_PANEL_DIR, GSP_WEBSITE_DIR, GSP_BACKUP_BASE] as $path) {
foreach ([$root_path, $panel_path, GSP_BACKUP_BASE] as $path) {
if (!is_writable($path)) {
$errors[] = 'Path is not writable: ' . $path;
}
@ -593,8 +625,9 @@ $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)
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') {
@ -611,16 +644,16 @@ break;
$layout = [
'cwd' => getcwd() ?: '',
'live_gsp_root' => GSP_ROOT_DIR,
'live_panel_path' => GSP_PANEL_DIR,
'live_website_path' => GSP_WEBSITE_DIR,
'live_gsp_root' => rtrim((string)$update_cfg['repo_root'], '/'),
'live_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'),
'live_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 . '/Panel') : '',
'source_website_path' => $repo_root ? ($repo_root . '/Website') : '',
'destination_panel_path' => GSP_PANEL_DIR,
'destination_website_path' => GSP_WEBSITE_DIR,
'destination_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'),
'destination_website_path' => rtrim((string)$update_cfg['repo_root'], '/') . '/Website',
];
$errors = [];
@ -640,17 +673,8 @@ $errors[] = 'Destination Panel path is nested incorrectly: ' . $layout['destinat
if (strpos((string)$layout['destination_website_path'], '/Website/Website') !== false) {
$errors[] = 'Destination Website path is nested incorrectly: ' . $layout['destination_website_path'];
}
if ((realpath(GSP_ROOT_DIR) ?: GSP_ROOT_DIR) !== (realpath(GSP_EXPECTED_ROOT) ?: GSP_EXPECTED_ROOT)) {
$errors[] = 'Live root mismatch. Expected ' . GSP_EXPECTED_ROOT . ' but detected ' . GSP_ROOT_DIR;
}
if ((realpath(GSP_PANEL_DIR) ?: GSP_PANEL_DIR) !== (realpath(GSP_EXPECTED_PANEL) ?: GSP_EXPECTED_PANEL)) {
$errors[] = 'Live Panel mismatch. Expected ' . GSP_EXPECTED_PANEL . ' but detected ' . GSP_PANEL_DIR;
}
if ((realpath(GSP_WEBSITE_DIR) ?: GSP_WEBSITE_DIR) !== (realpath(GSP_EXPECTED_WEBSITE) ?: GSP_EXPECTED_WEBSITE)) {
$errors[] = 'Live Website mismatch. Expected ' . GSP_EXPECTED_WEBSITE . ' but detected ' . GSP_WEBSITE_DIR;
}
if (strpos((realpath($layout['cwd']) ?: $layout['cwd']), (realpath(GSP_PANEL_DIR) ?: GSP_PANEL_DIR)) !== 0) {
$errors[] = 'Updater must run from a working directory under the live Panel 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));
@ -836,7 +860,7 @@ if ($entry === 'Panel' || $entry === 'Website' || $entry === 'backups' || $entry
continue;
}
$src = rtrim($source_root, '/') . '/' . $entry;
$dst = GSP_ROOT_DIR . '/' . $entry;
$dst = rtrim($layout['live_gsp_root'], '/') . '/' . $entry;
if (is_file($src)) {
$rel = gsp_normalize_rel($entry);
if (gsp_is_preserved_path($rel)) {
@ -852,7 +876,7 @@ $copied_files[] = $rel;
continue;
}
if (is_dir($src)) {
$part = gsp_copy_tree($src, GSP_ROOT_DIR, $entry);
$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']);
@ -890,7 +914,7 @@ $checks = [
];
foreach ($checks as $rel) {
$src = rtrim($layout['source_repo_root'], '/') . '/' . $rel;
$dst = rtrim(GSP_ROOT_DIR, '/') . '/' . $rel;
$dst = rtrim($layout['live_gsp_root'], '/') . '/' . $rel;
if (!is_file($src)) {
continue;
}
@ -919,10 +943,11 @@ return [
];
}
function gsp_write_last_update_markers()
function gsp_write_last_update_markers($repo_root = null)
{
$line = 'Last Updated at ' . date('g:ia') . ' on ' . date('Y-m-d');
$targets = [GSP_CANONICAL_TIMESTAMP_FILE, GSP_BILLING_TIMESTAMP_FILE];
$last_update_file = rtrim((string)($repo_root ?: GSP_ROOT_DIR), '/') . '/LAST_UPDATE.txt';
$targets = [GSP_CANONICAL_TIMESTAMP_FILE, GSP_BILLING_TIMESTAMP_FILE, $last_update_file];
foreach ($targets as $target) {
$dir = dirname($target);
if (!is_dir($dir)) {
@ -953,15 +978,16 @@ return [
return ['success' => true, 'run' => $run];
}
function gsp_apply_update_from_zip($zip_file, $restart_nonce = '')
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);
$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'])];
@ -985,6 +1011,87 @@ return [
'drift_files' => $drift_files,
];
}
function gsp_checkout_update_source(array $update_cfg)
{
$repo_url = (string)$update_cfg['repo_url'];
$branch = (string)$update_cfg['branch'];
$temp_dir = sys_get_temp_dir() . '/gsp_git_' . time() . '_' . mt_rand(1000, 9999);
if (!@mkdir($temp_dir, 0750, true)) {
return ['success' => false, 'error' => 'Cannot create temporary git checkout directory.'];
}
$out = [];
$ret = 0;
$cmd = 'git clone --depth 1 --branch ' . escapeshellarg($branch) . ' ' . escapeshellarg($repo_url) . ' ' . escapeshellarg($temp_dir) . ' 2>&1';
exec($cmd, $out, $ret);
if ($ret !== 0) {
gsp_rmdir_recursive($temp_dir);
return ['success' => false, 'error' => 'git clone failed: ' . implode(' | ', array_slice($out, -20))];
}
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'],
];
}
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)) {
@ -1068,6 +1175,73 @@ if (!$preflight['success']) {
return ['success' => false, 'error' => 'Preflight failed: ' . implode(' | ', $preflight['errors'])];
}
function gsp_do_configured_git_update(array $update_cfg, $restart_nonce = '')
{
global $db;
$validation = gsp_validate_update_settings($update_cfg);
if (!empty($validation)) {
return ['success' => false, 'error' => implode(' | ', $validation)];
}
$preflight = gsp_preflight_check($update_cfg);
if (!$preflight['success']) {
return ['success' => false, 'error' => 'Preflight failed: ' . implode(' | ', $preflight['errors'])];
}
$backup = ['success' => true, 'backup_dir' => null];
if (!empty($update_cfg['backup_before_update'])) {
$backup = gsp_create_full_backup('git-update', $update_cfg['branch'], false);
if (!$backup['success']) {
return $backup;
}
gsp_update_log("Backup created before git update to {$update_cfg['branch']}: {$backup['backup_dir']}");
}
$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);
gsp_rmdir_recursive($checkout['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($update_cfg['repo_root']);
gsp_clear_panel_cache($update_cfg['panel_path']);
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();
$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 (function_exists('updateAllPanelModules')) {
updateAllPanelModules();
}
if (function_exists('runPostUpdateOperations')) {
runPostUpdateOperations();
}
gsp_update_log("Configured git 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'],
];
}
$backup = gsp_create_full_backup($update_type, $ref, false);
if (!$backup['success']) {
return $backup;
@ -1101,7 +1275,7 @@ 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_write_last_update_markers($update_cfg['repo_root']);
$db->setSettings(['ogp_version' => $ref, 'version_type' => $update_type]);
if (file_exists(GSP_PANEL_DIR . '/modules/modulemanager/module_handling.php')) {
@ -1686,10 +1860,7 @@ if ($_SESSION['users_group'] !== 'admin') {
return;
}
$repo_owner = !empty($settings['gsp_repo_owner']) ? $settings['gsp_repo_owner'] : 'GameServerPanel';
$repo_name = !empty($settings['gsp_repo_name']) ? $settings['gsp_repo_name'] : 'GSP';
$stable_branch = !empty($settings['gsp_stable_branch']) ? $settings['gsp_stable_branch'] : 'Panel-stable';
$unstable_branch = !empty($settings['gsp_unstable_branch']) ? $settings['gsp_unstable_branch'] : 'Panel-unstable';
$update_cfg = gsp_update_settings();
if (empty($_SESSION['gsp_update_csrf'])) {
$_SESSION['gsp_update_csrf'] = gsp_random_token();
@ -1754,6 +1925,58 @@ print_success('Backup created: <code>' . htmlspecialchars($result['backup_dir'])
} else {
print_failure('Backup failed: ' . htmlspecialchars($result['error']));
}
} elseif ($action === 'save_settings') {
$new_cfg = [
'repo_url' => isset($_POST['gsp_update_repo_url']) ? trim((string)$_POST['gsp_update_repo_url']) : '',
'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']), '/') : '',
'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0',
];
$errors = gsp_validate_update_settings($new_cfg);
if (!empty($errors)) {
print_failure('Update settings were not saved: ' . htmlspecialchars(implode(' | ', $errors)));
} else {
$db->setSettings([
'gsp_update_repo_url' => $new_cfg['repo_url'],
'gsp_update_branch' => $new_cfg['branch'],
'gsp_update_repo_root' => $new_cfg['repo_root'],
'gsp_update_panel_path' => $new_cfg['panel_path'],
'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_backup_before'] = $new_cfg['backup_before_update'];
$update_cfg = gsp_update_settings();
print_success('Update settings saved.');
}
} 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'],
'backup_before_update' => !empty($_POST['gsp_update_backup_before']) ? '1' : '0',
];
$db->setSettings([
'gsp_update_repo_url' => $update_cfg['repo_url'],
'gsp_update_branch' => $update_cfg['branch'],
'gsp_update_repo_root' => $update_cfg['repo_root'],
'gsp_update_panel_path' => $update_cfg['panel_path'],
'gsp_update_backup_before' => $update_cfg['backup_before_update'],
]);
$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']) {
print_success('Panel updated from configured repository branch <strong>' . htmlspecialchars($update_cfg['branch']) . '</strong>. '
. 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) {
@ -1821,17 +2044,129 @@ $last_layout = isset($_SESSION['gsp_last_update_layout']) && is_array($_SESSION[
? $_SESSION['gsp_last_update_layout']
: null;
$vinfo = gsp_read_version_json();
$releases = gsp_fetch_github_releases($repo_owner, $repo_name);
$latest_release = (is_array($releases) && !empty($releases)) ? htmlspecialchars($releases[0]['tag_name']) : 'N/A';
$latest_release = 'N/A';
$backups = gsp_get_available_backups();
$patch_overview = gsp_get_patch_overview();
if ($apache_scan_result === null) {
$apache_scan_result = gsp_scan_apache_configs();
}
if ($preflight_result === null) {
$preflight_result = gsp_preflight_check();
$preflight_result = gsp_preflight_check($update_cfg);
}
$ssl_issue_count = !empty($apache_scan_result['ssl_issues']) ? count($apache_scan_result['ssl_issues']) : 0;
echo "<h2>Panel Updates</h2>\n";
echo "<table class='administration-table'><tr><td>\n";
echo "<h3>Current Installation</h3>\n";
echo "<table class='center'>\n";
echo "<tr><td><strong>Installed Version:</strong></td><td>" . htmlspecialchars($current_version) . "</td></tr>\n";
echo "<tr><td><strong>Current Branch:</strong></td><td>" . htmlspecialchars($current_branch) . "</td></tr>\n";
if ($git_commit) {
echo "<tr><td><strong>Git Commit:</strong></td><td>" . htmlspecialchars(substr($git_commit, 0, 12)) . "</td></tr>\n";
}
echo "<tr><td><strong>Update Trace Log:</strong></td><td><code>" . htmlspecialchars(GSP_UPDATE_LOG) . "</code></td></tr>\n";
echo "<tr><td><strong>Backups Stored:</strong></td><td>" . intval(count($backups)) . " (retention: 5)</td></tr>\n";
echo "</table>\n";
if ($ssl_issue_count > 0) {
echo "<p style='color:#8a6d3b;'><strong>SSL warning:</strong> " . intval($ssl_issue_count) . " Apache SSL certificate issue(s) detected. Updates are not blocked by this. See Advanced Diagnostics for details.</p>\n";
}
if (!empty($preflight_result['errors'])) {
echo "<p style='color:#a94442;'><strong>Update preflight errors:</strong><br>" . implode('<br>', array_map('htmlspecialchars', $preflight_result['errors'])) . "</p>\n";
}
if (!empty($preflight_result['warnings'])) {
echo "<p style='color:#8a6d3b;'><strong>Update preflight warnings:</strong><br>" . implode('<br>', array_map('htmlspecialchars', $preflight_result['warnings'])) . "</p>\n";
}
echo "<h3>Repository Settings</h3>\n";
echo "<form method='POST'>\n";
echo "<input type='hidden' name='gsp_update_csrf' value='" . htmlspecialchars($csrf_token) . "'>\n";
echo "<table class='center'>\n";
echo "<tr><td><strong>Repository URL</strong></td><td><input type='text' name='gsp_update_repo_url' value='" . htmlspecialchars($update_cfg['repo_url'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
echo "<tr><td><strong>Branch / Channel</strong></td><td><input type='text' name='gsp_update_branch' value='" . htmlspecialchars($update_cfg['branch'], ENT_QUOTES, 'UTF-8') . "' size='40'></td></tr>\n";
echo "<tr><td><strong>Repository Root</strong></td><td><input type='text' name='gsp_update_repo_root' value='" . htmlspecialchars($update_cfg['repo_root'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
echo "<tr><td><strong>Panel Path</strong></td><td><input type='text' name='gsp_update_panel_path' value='" . htmlspecialchars($update_cfg['panel_path'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
echo "<tr><td><strong>Backup Before Update</strong></td><td><label><input type='checkbox' name='gsp_update_backup_before' value='1' " . (!empty($update_cfg['backup_before_update']) ? "checked" : "") . "> create backup before updating</label></td></tr>\n";
echo "</table>\n";
echo "<p>\n";
echo "<button type='submit' name='gsp_update_action' value='save_settings'>Save Settings</button> ";
echo "<button type='submit' name='gsp_update_action' value='update_configured' onclick='return confirm(\"Update Panel from the configured repository and branch?\");'>Update Panel</button>\n";
echo "</p>\n";
echo "</form>\n";
echo "<h3>Backup</h3>\n";
echo "<form method='POST'>\n";
echo "<input type='hidden' name='gsp_update_action' value='backup_only'>\n";
echo "<input type='hidden' name='gsp_update_csrf' value='" . htmlspecialchars($csrf_token) . "'>\n";
echo "<button type='submit'>Create Backup</button>\n";
echo "</form>\n";
if (!empty($backups)) {
echo "<h3>Rollback</h3>\n";
echo "<form method='POST'>\n";
echo "<input type='hidden' name='gsp_update_action' value='revert'>\n";
echo "<input type='hidden' name='gsp_update_csrf' value='" . htmlspecialchars($csrf_token) . "'>\n";
echo "<select name='gsp_revert_backup'>\n";
foreach ($backups as $bk) {
$label = htmlspecialchars($bk['ts']);
if (!empty($bk['meta']['update_target_type']) && !empty($bk['meta']['update_target_version'])) {
$label .= ' (before ' . htmlspecialchars($bk['meta']['update_target_type']) . ': ' . htmlspecialchars($bk['meta']['update_target_version']) . ')';
}
echo "<option value='" . htmlspecialchars($bk['ts']) . "'>{$label}</option>\n";
}
echo "</select> ";
echo "<label><input type='checkbox' name='gsp_restore_apache' value='1'> restore Apache configs if backup contains them</label> ";
echo "<button type='submit' onclick='return confirm(\"Restore Panel, Website, version.json, and database from selected backup?\");'>Rollback</button>\n";
echo "</form>\n";
}
echo "<details style='margin-top:18px;'>\n";
echo "<summary><strong>Advanced Diagnostics</strong></summary>\n";
echo "<h4>Preflight</h4>\n";
echo "<table class='center'>\n";
echo "<tr><td><strong>Status:</strong></td><td>" . ($preflight_result['success'] ? 'PASS' : 'FAIL') . "</td></tr>\n";
echo "<tr><td><strong>Pending Patches:</strong></td><td>" . intval(count($patch_overview['pending'])) . "</td></tr>\n";
echo "<tr><td><strong>Patch Directory:</strong></td><td><code>" . htmlspecialchars(GSP_PATCH_DIR) . "</code></td></tr>\n";
echo "</table>\n";
echo "<h4>Apache</h4>\n";
echo "<table class='center'>\n";
echo "<tr><td><strong>Config Directory:</strong></td><td><code>" . htmlspecialchars($apache_scan_result['base']) . "</code></td></tr>\n";
echo "<tr><td><strong>Configs Found:</strong></td><td>" . intval(count($apache_scan_result['files'])) . "</td></tr>\n";
echo "<tr><td><strong>Stale Path Hits:</strong></td><td>" . intval(count($apache_scan_result['stale_issues'])) . "</td></tr>\n";
echo "<tr><td><strong>SSL Certificate Issues:</strong></td><td>" . intval($ssl_issue_count) . "</td></tr>\n";
echo "</table>\n";
if (!empty($apache_scan_result['stale_issues'])) {
echo "<p style='color:#a94442;'><strong>Apache stale path issues:</strong><br>" . implode('<br>', array_map('htmlspecialchars', array_unique($apache_scan_result['stale_issues']))) . "</p>\n";
}
if (!empty($apache_scan_result['ssl_issues'])) {
echo "<p style='color:#8a6d3b;'><strong>SSL certificate issues:</strong><br>";
foreach ((array)$apache_scan_result['ssl_issues'] as $ssl_issue) {
echo htmlspecialchars($ssl_issue['vhost'] . ' ' . $ssl_issue['directive'] . ': ' . $ssl_issue['path'] . ' (' . $ssl_issue['reason'] . ')') . "<br>";
}
echo "</p>\n";
}
echo "<form method='POST' style='display:inline-block;margin-right:8px;'>\n";
echo "<input type='hidden' name='gsp_update_action' value='preflight'>\n";
echo "<input type='hidden' name='gsp_update_csrf' value='" . htmlspecialchars($csrf_token) . "'>\n";
echo "<button type='submit'>Run Preflight Check</button>\n";
echo "</form>\n";
echo "<form method='POST' style='display:inline-block;margin-right:8px;'>\n";
echo "<input type='hidden' name='gsp_update_action' value='apply_patches'>\n";
echo "<input type='hidden' name='gsp_update_csrf' value='" . htmlspecialchars($csrf_token) . "'>\n";
echo "<button type='submit'>Apply Required Patches</button>\n";
echo "</form>\n";
echo "<form method='POST' style='display:inline-block;'>\n";
echo "<input type='hidden' name='gsp_update_action' value='fix_apache'>\n";
echo "<input type='hidden' name='gsp_update_csrf' value='" . htmlspecialchars($csrf_token) . "'>\n";
echo "<button type='submit' onclick='return confirm(\"Backup Apache configs, run configtest, and apply path fixes?\");'>Fix Apache Paths</button>\n";
echo "</form>\n";
echo "</details>\n";
echo "</td></tr></table>\n";
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 "<h2>Panel Updates</h2>\n";
echo "<table class='administration-table'><tr><td>\n";
echo "<h3>Detected Layout</h3>\n";

View file

@ -88,7 +88,32 @@ require_once("modules/config_games/server_config_parser.php");
if($log_retval == 1)
{
$log_url = "home.php?m=gamemanager&p=log&type=cleared&refreshed&home_id-mod_id-ip-port=".rawurlencode($_GET['home_id-mod_id-ip-port']);
echo '<textarea id="live-server-log" class="log" readonly="readonly" style="height:500px;overflow:auto;max-width:1600px;width:100%;box-sizing:border-box;">'.htmlentities($home_log, ENT_QUOTES, "UTF-8").'</textarea>';
echo '<style>
#live-server-log {
display:block;
width:100%;
max-width:1600px;
min-height:55vh;
height:55vh;
box-sizing:border-box;
font-family:Consolas, Monaco, "Courier New", monospace;
font-size:13px;
line-height:1.35;
white-space:pre-wrap;
overflow-y:auto;
overflow-x:auto;
resize:vertical;
tab-size:4;
}
@media (max-width: 700px) {
#live-server-log {
min-height:45vh;
height:45vh;
font-size:12px;
}
}
</style>';
echo '<textarea id="live-server-log" class="log" readonly="readonly" wrap="soft">'.htmlentities($home_log, ENT_QUOTES, "UTF-8").'</textarea>';
?>
<script type="text/javascript">
(function(){

View file

@ -366,6 +366,10 @@ echo "<table id='servermonitor' class='tablesorter' data-sortlist='[[0,0],[3,1]]
$pos,
$ctrlChkBoxes,
$expiration_dates);
$address = "";
$offlineT = "";
$pos = "";
$ctrlChkBoxes = "";
if ( $isAdmin )
$server_home['access_rights'] = $db->getFullAccessRightsString();
@ -487,8 +491,8 @@ echo "<table id='servermonitor' class='tablesorter' data-sortlist='[[0,0],[3,1]]
if($screen_running)
{
// Check if the screen running the server is running.
$status = "online";
$order = 1 + $j;
$status = ($agent_state === "ONLINE") ? "online" : "starting";
$order = ($agent_state === "ONLINE") ? (1 + $j) : (2 + $j);
if($agent_state !== "ONLINE")
{
$address = "<span class='note'>".htmlentities($agent_state)."</span>";
@ -538,6 +542,15 @@ echo "<table id='servermonitor' class='tablesorter' data-sortlist='[[0,0],[3,1]]
}
$stats_servers_online++;
}
elseif($agent_state === "UNKNOWN")
{
$status = "unknown";
$order = 3;
$address = "<span class='note'>Unknown - agent status unavailable.</span>";
if(isset($agent_status['last_error']) && $agent_status['last_error'] !== "")
$address .= " <span class='failure'>".htmlentities($agent_status['last_error'])."</span>";
$offlineT = "<span class='note'>Server state is unknown. The Panel did not confirm that this server is offline.</span>";
}
else
{
$status = "offline";
@ -570,16 +583,20 @@ echo "<table id='servermonitor' class='tablesorter' data-sortlist='[[0,0],[3,1]]
}
}
else{
$status = "offline";
$status = "unknown";
$order = 3;
$address = "<span style='color:darkred;font-weight:bold;'>Agent Offline</span>";
$offlineT = "<span class='note'>Agent is offline. Server state is unknown.</span>";
}
$user = $db->getUserById($server_home['user_id_main']);
// Template
@$first = "<tr class='maintr$trclass'>";
$first .= "<td class='collapsible' data-status='$status' data-pos='$pos'><span class='hidden'>$order</span>" . "<img src='" . check_theme_image("images/$status.png") . "' />" . "</td>";
$status_color = $status === 'online' ? '#2da44e' : ($status === 'starting' ? '#d29922' : ($status === 'unknown' ? '#8b949e' : '#d1242f'));
$status_title = $status === 'starting' ? 'Starting' : ($status === 'unknown' ? 'Unknown' : ucfirst($status));
$status_icon = "<span title='" . htmlentities($status_title) . "' style='display:inline-block;width:14px;height:14px;border-radius:50%;background:" . $status_color . ";border:1px solid rgba(255,255,255,.35);vertical-align:middle;'></span>";
$first .= "<td class='collapsible' data-status='$status' data-pos='$pos'><span class='hidden'>$order</span>" . $status_icon . "</td>";
$first .= "<td class='collapsible'>" . "<span class='hidden'>$mod</span><img src='$icon_path' />" . "</td>";
$first .= "<td class='collapsible serverId hide'>" . $server_home["home_id"] . "</td>";
$first .= "<td class='collapsible' data-status='$status' data-pos='$pos'><b>" . htmlentities($server_home['home_name']) . "</b>$mod_name</td>";