foxed update and stsrt issues
This commit is contained in:
parent
c687165132
commit
c195c0930b
7 changed files with 544 additions and 70 deletions
|
|
@ -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_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_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_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';
|
$gspPatchManager = GSP_PANEL_DIR . '/modules/update/patch_manager.php';
|
||||||
if (file_exists($gspPatchManager)) {
|
if (file_exists($gspPatchManager)) {
|
||||||
|
|
@ -129,6 +134,41 @@ $repo_root = gsp_detect_repo_root();
|
||||||
if (!$repo_root || !function_exists('exec')) {
|
if (!$repo_root || !function_exists('exec')) {
|
||||||
return null;
|
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 = [];
|
$out = [];
|
||||||
$ret = 0;
|
$ret = 0;
|
||||||
exec('git -C ' . escapeshellarg($repo_root) . ' rev-parse HEAD 2>/dev/null', $out, $ret);
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function gsp_preflight_check()
|
function gsp_preflight_check(array $update_cfg = null)
|
||||||
{
|
{
|
||||||
$errors = [];
|
$errors = [];
|
||||||
$warnings = [];
|
$warnings = [];
|
||||||
|
$update_cfg = $update_cfg ?: gsp_update_settings();
|
||||||
$cwd = getcwd();
|
$cwd = getcwd();
|
||||||
$cwd_real = $cwd ? (realpath($cwd) ?: $cwd) : '';
|
$cwd_real = $cwd ? (realpath($cwd) ?: $cwd) : '';
|
||||||
$root_real = realpath(GSP_ROOT_DIR) ?: GSP_ROOT_DIR;
|
$root_path = rtrim((string)$update_cfg['repo_root'], '/');
|
||||||
$panel_real = realpath(GSP_PANEL_DIR) ?: GSP_PANEL_DIR;
|
$panel_path = rtrim((string)$update_cfg['panel_path'], '/');
|
||||||
$website_real = realpath(GSP_WEBSITE_DIR) ?: GSP_WEBSITE_DIR;
|
$website_path = $root_path . '/Website';
|
||||||
$expected_root_real = realpath(GSP_EXPECTED_ROOT) ?: GSP_EXPECTED_ROOT;
|
$root_real = realpath($root_path) ?: $root_path;
|
||||||
$expected_panel_real = realpath(GSP_EXPECTED_PANEL) ?: GSP_EXPECTED_PANEL;
|
$panel_real = realpath($panel_path) ?: $panel_path;
|
||||||
$expected_website_real = realpath(GSP_EXPECTED_WEBSITE) ?: GSP_EXPECTED_WEBSITE;
|
$website_real = realpath($website_path) ?: $website_path;
|
||||||
$layout = [
|
$layout = [
|
||||||
'cwd' => $cwd,
|
'cwd' => $cwd,
|
||||||
'cwd_real' => $cwd_real,
|
'cwd_real' => $cwd_real,
|
||||||
'expected_root' => GSP_EXPECTED_ROOT,
|
'expected_root' => $root_path,
|
||||||
'expected_panel' => GSP_EXPECTED_PANEL,
|
'expected_panel' => $panel_path,
|
||||||
'expected_website' => GSP_EXPECTED_WEBSITE,
|
'expected_website' => $website_path,
|
||||||
'gsp_root' => GSP_ROOT_DIR,
|
'gsp_root' => $root_path,
|
||||||
'gsp_root_real' => $root_real,
|
'gsp_root_real' => $root_real,
|
||||||
'panel_dir' => GSP_PANEL_DIR,
|
'panel_dir' => $panel_path,
|
||||||
'panel_dir_real' => $panel_real,
|
'panel_dir_real' => $panel_real,
|
||||||
'website_dir' => GSP_WEBSITE_DIR,
|
'website_dir' => $website_path,
|
||||||
'website_dir_real' => $website_real,
|
'website_dir_real' => $website_real,
|
||||||
'backup_dir' => GSP_BACKUP_BASE,
|
'backup_dir' => GSP_BACKUP_BASE,
|
||||||
'config_file' => GSP_PANEL_DIR . '/includes/config.inc.php',
|
'config_file' => $panel_path . '/includes/config.inc.php',
|
||||||
'destination_panel' => GSP_PANEL_DIR,
|
'destination_panel' => $panel_path,
|
||||||
'destination_website' => GSP_WEBSITE_DIR,
|
'destination_website' => $website_path,
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!$layout['cwd']) {
|
if (!$layout['cwd']) {
|
||||||
$errors[] = 'Unable to read current working directory.';
|
$errors[] = 'Unable to read current working directory.';
|
||||||
} elseif (strpos($cwd_real, $panel_real) !== 0) {
|
} 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.';
|
$errors[] = 'Detected GSP root path is missing.';
|
||||||
}
|
}
|
||||||
if ($root_real !== $expected_root_real) {
|
if (!is_dir($panel_path)) {
|
||||||
$errors[] = 'Detected GSP root does not match expected live root: ' . GSP_EXPECTED_ROOT;
|
|
||||||
}
|
|
||||||
if (!is_dir(GSP_PANEL_DIR)) {
|
|
||||||
$errors[] = 'Panel directory is missing.';
|
$errors[] = 'Panel directory is missing.';
|
||||||
}
|
}
|
||||||
if ($panel_real !== $expected_panel_real) {
|
if (!is_dir($website_path)) {
|
||||||
$errors[] = 'Detected Panel path does not match expected live Panel path: ' . GSP_EXPECTED_PANEL;
|
$warnings[] = 'Website directory is missing. Panel updates can still continue, but Website files will not sync cleanly.';
|
||||||
}
|
|
||||||
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 (!file_exists($layout['config_file'])) {
|
if (!file_exists($layout['config_file'])) {
|
||||||
$errors[] = 'Panel includes/config.inc.php was not found and cannot be preserved.';
|
$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)) {
|
if (!is_writable($path)) {
|
||||||
$errors[] = 'Path is not 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];
|
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;
|
$source_root_real = realpath($source_root) ?: $source_root;
|
||||||
$candidates = [$source_root_real];
|
$candidates = [$source_root_real];
|
||||||
if (basename($source_root_real) === 'Panel' || basename($source_root_real) === 'Website') {
|
if (basename($source_root_real) === 'Panel' || basename($source_root_real) === 'Website') {
|
||||||
|
|
@ -611,16 +644,16 @@ break;
|
||||||
|
|
||||||
$layout = [
|
$layout = [
|
||||||
'cwd' => getcwd() ?: '',
|
'cwd' => getcwd() ?: '',
|
||||||
'live_gsp_root' => GSP_ROOT_DIR,
|
'live_gsp_root' => rtrim((string)$update_cfg['repo_root'], '/'),
|
||||||
'live_panel_path' => GSP_PANEL_DIR,
|
'live_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'),
|
||||||
'live_website_path' => GSP_WEBSITE_DIR,
|
'live_website_path' => rtrim((string)$update_cfg['repo_root'], '/') . '/Website',
|
||||||
'temporary_git_checkout_path' => $temp_checkout_path,
|
'temporary_git_checkout_path' => $temp_checkout_path,
|
||||||
'source_root' => $source_root_real,
|
'source_root' => $source_root_real,
|
||||||
'source_repo_root' => $repo_root,
|
'source_repo_root' => $repo_root,
|
||||||
'source_panel_path' => $repo_root ? ($repo_root . '/Panel') : '',
|
'source_panel_path' => $repo_root ? ($repo_root . '/Panel') : '',
|
||||||
'source_website_path' => $repo_root ? ($repo_root . '/Website') : '',
|
'source_website_path' => $repo_root ? ($repo_root . '/Website') : '',
|
||||||
'destination_panel_path' => GSP_PANEL_DIR,
|
'destination_panel_path' => rtrim((string)$update_cfg['panel_path'], '/'),
|
||||||
'destination_website_path' => GSP_WEBSITE_DIR,
|
'destination_website_path' => rtrim((string)$update_cfg['repo_root'], '/') . '/Website',
|
||||||
];
|
];
|
||||||
|
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
@ -640,17 +673,8 @@ $errors[] = 'Destination Panel path is nested incorrectly: ' . $layout['destinat
|
||||||
if (strpos((string)$layout['destination_website_path'], '/Website/Website') !== false) {
|
if (strpos((string)$layout['destination_website_path'], '/Website/Website') !== false) {
|
||||||
$errors[] = 'Destination Website path is nested incorrectly: ' . $layout['destination_website_path'];
|
$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)) {
|
if (!is_dir($layout['destination_panel_path'])) {
|
||||||
$errors[] = 'Live root mismatch. Expected ' . GSP_EXPECTED_ROOT . ' but detected ' . GSP_ROOT_DIR;
|
$errors[] = 'Destination Panel path does not exist: ' . $layout['destination_panel_path'];
|
||||||
}
|
|
||||||
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.';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gsp_update_log('Deployment layout detection: ' . json_encode($layout));
|
gsp_update_log('Deployment layout detection: ' . json_encode($layout));
|
||||||
|
|
@ -836,7 +860,7 @@ if ($entry === 'Panel' || $entry === 'Website' || $entry === 'backups' || $entry
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
$src = rtrim($source_root, '/') . '/' . $entry;
|
$src = rtrim($source_root, '/') . '/' . $entry;
|
||||||
$dst = GSP_ROOT_DIR . '/' . $entry;
|
$dst = rtrim($layout['live_gsp_root'], '/') . '/' . $entry;
|
||||||
if (is_file($src)) {
|
if (is_file($src)) {
|
||||||
$rel = gsp_normalize_rel($entry);
|
$rel = gsp_normalize_rel($entry);
|
||||||
if (gsp_is_preserved_path($rel)) {
|
if (gsp_is_preserved_path($rel)) {
|
||||||
|
|
@ -852,7 +876,7 @@ $copied_files[] = $rel;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (is_dir($src)) {
|
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 += $part['copied'];
|
||||||
$copied_files = array_merge($copied_files, array_slice((array)$part['copied_files'], 0, max(0, 200 - count($copied_files))));
|
$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']);
|
$skipped = array_merge($skipped, $part['skipped']);
|
||||||
|
|
@ -890,7 +914,7 @@ $checks = [
|
||||||
];
|
];
|
||||||
foreach ($checks as $rel) {
|
foreach ($checks as $rel) {
|
||||||
$src = rtrim($layout['source_repo_root'], '/') . '/' . $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)) {
|
if (!is_file($src)) {
|
||||||
continue;
|
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');
|
$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) {
|
foreach ($targets as $target) {
|
||||||
$dir = dirname($target);
|
$dir = dirname($target);
|
||||||
if (!is_dir($dir)) {
|
if (!is_dir($dir)) {
|
||||||
|
|
@ -953,15 +978,16 @@ return [
|
||||||
return ['success' => true, 'run' => $run];
|
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);
|
$extract = gsp_extract_update_source($zip_file);
|
||||||
if (!$extract['success']) {
|
if (!$extract['success']) {
|
||||||
return $extract;
|
return $extract;
|
||||||
}
|
}
|
||||||
$temp_dir = $extract['temp_dir'];
|
$temp_dir = $extract['temp_dir'];
|
||||||
$source_root = $extract['source_root'];
|
$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']) {
|
if (!$resolved_layout['success']) {
|
||||||
gsp_rmdir_recursive($temp_dir);
|
gsp_rmdir_recursive($temp_dir);
|
||||||
return ['success' => false, 'error' => 'Deployment layout validation failed: ' . implode(' | ', $resolved_layout['errors'])];
|
return ['success' => false, 'error' => 'Deployment layout validation failed: ' . implode(' | ', $resolved_layout['errors'])];
|
||||||
|
|
@ -985,6 +1011,87 @@ return [
|
||||||
'drift_files' => $drift_files,
|
'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)) {
|
if (!empty($restart_nonce)) {
|
||||||
$expected = isset($_SESSION['gsp_update_restart_nonce']) ? $_SESSION['gsp_update_restart_nonce'] : null;
|
$expected = isset($_SESSION['gsp_update_restart_nonce']) ? $_SESSION['gsp_update_restart_nonce'] : null;
|
||||||
if ($expected === null || !hash_equals($expected, $restart_nonce)) {
|
if ($expected === null || !hash_equals($expected, $restart_nonce)) {
|
||||||
|
|
@ -1068,6 +1175,73 @@ if (!$preflight['success']) {
|
||||||
return ['success' => false, 'error' => 'Preflight failed: ' . implode(' | ', $preflight['errors'])];
|
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);
|
$backup = gsp_create_full_backup($update_type, $ref, false);
|
||||||
if (!$backup['success']) {
|
if (!$backup['success']) {
|
||||||
return $backup;
|
return $backup;
|
||||||
|
|
@ -1101,7 +1275,7 @@ gsp_write_version_file($ref, $update_type);
|
||||||
$vsource = ($update_type === 'release') ? 'GitHub Releases' : $ref;
|
$vsource = ($update_type === 'release') ? 'GitHub Releases' : $ref;
|
||||||
$vversion = ($update_type === 'release') ? $ref : ($commit_after ?: $ref);
|
$vversion = ($update_type === 'release') ? $ref : ($commit_after ?: $ref);
|
||||||
gsp_write_version_json($update_type, $vsource, $vversion, $commit_after);
|
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]);
|
$db->setSettings(['ogp_version' => $ref, 'version_type' => $update_type]);
|
||||||
|
|
||||||
if (file_exists(GSP_PANEL_DIR . '/modules/modulemanager/module_handling.php')) {
|
if (file_exists(GSP_PANEL_DIR . '/modules/modulemanager/module_handling.php')) {
|
||||||
|
|
@ -1686,10 +1860,7 @@ if ($_SESSION['users_group'] !== 'admin') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$repo_owner = !empty($settings['gsp_repo_owner']) ? $settings['gsp_repo_owner'] : 'GameServerPanel';
|
$update_cfg = gsp_update_settings();
|
||||||
$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';
|
|
||||||
|
|
||||||
if (empty($_SESSION['gsp_update_csrf'])) {
|
if (empty($_SESSION['gsp_update_csrf'])) {
|
||||||
$_SESSION['gsp_update_csrf'] = gsp_random_token();
|
$_SESSION['gsp_update_csrf'] = gsp_random_token();
|
||||||
|
|
@ -1754,6 +1925,58 @@ print_success('Backup created: <code>' . htmlspecialchars($result['backup_dir'])
|
||||||
} else {
|
} else {
|
||||||
print_failure('Backup failed: ' . htmlspecialchars($result['error']));
|
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') {
|
} elseif ($action === 'update_release') {
|
||||||
$version = isset($_POST['gsp_release_version']) ? trim($_POST['gsp_release_version']) : '';
|
$version = isset($_POST['gsp_release_version']) ? trim($_POST['gsp_release_version']) : '';
|
||||||
if (!preg_match('/^[a-zA-Z0-9._\-]+$/', $version) || strlen($version) > 80) {
|
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']
|
? $_SESSION['gsp_last_update_layout']
|
||||||
: null;
|
: null;
|
||||||
$vinfo = gsp_read_version_json();
|
$vinfo = gsp_read_version_json();
|
||||||
$releases = gsp_fetch_github_releases($repo_owner, $repo_name);
|
$latest_release = 'N/A';
|
||||||
$latest_release = (is_array($releases) && !empty($releases)) ? htmlspecialchars($releases[0]['tag_name']) : 'N/A';
|
|
||||||
$backups = gsp_get_available_backups();
|
$backups = gsp_get_available_backups();
|
||||||
$patch_overview = gsp_get_patch_overview();
|
$patch_overview = gsp_get_patch_overview();
|
||||||
if ($apache_scan_result === null) {
|
if ($apache_scan_result === null) {
|
||||||
$apache_scan_result = gsp_scan_apache_configs();
|
$apache_scan_result = gsp_scan_apache_configs();
|
||||||
}
|
}
|
||||||
if ($preflight_result === null) {
|
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 "<h2>Panel Updates</h2>\n";
|
||||||
echo "<table class='administration-table'><tr><td>\n";
|
echo "<table class='administration-table'><tr><td>\n";
|
||||||
echo "<h3>Detected Layout</h3>\n";
|
echo "<h3>Detected Layout</h3>\n";
|
||||||
|
|
|
||||||
|
|
@ -88,7 +88,32 @@ require_once("modules/config_games/server_config_parser.php");
|
||||||
if($log_retval == 1)
|
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']);
|
$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">
|
<script type="text/javascript">
|
||||||
(function(){
|
(function(){
|
||||||
|
|
|
||||||
|
|
@ -366,6 +366,10 @@ echo "<table id='servermonitor' class='tablesorter' data-sortlist='[[0,0],[3,1]]
|
||||||
$pos,
|
$pos,
|
||||||
$ctrlChkBoxes,
|
$ctrlChkBoxes,
|
||||||
$expiration_dates);
|
$expiration_dates);
|
||||||
|
$address = "";
|
||||||
|
$offlineT = "";
|
||||||
|
$pos = "";
|
||||||
|
$ctrlChkBoxes = "";
|
||||||
|
|
||||||
if ( $isAdmin )
|
if ( $isAdmin )
|
||||||
$server_home['access_rights'] = $db->getFullAccessRightsString();
|
$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)
|
if($screen_running)
|
||||||
{
|
{
|
||||||
// Check if the screen running the server is running.
|
// Check if the screen running the server is running.
|
||||||
$status = "online";
|
$status = ($agent_state === "ONLINE") ? "online" : "starting";
|
||||||
$order = 1 + $j;
|
$order = ($agent_state === "ONLINE") ? (1 + $j) : (2 + $j);
|
||||||
if($agent_state !== "ONLINE")
|
if($agent_state !== "ONLINE")
|
||||||
{
|
{
|
||||||
$address = "<span class='note'>".htmlentities($agent_state)."</span>";
|
$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++;
|
$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
|
else
|
||||||
{
|
{
|
||||||
$status = "offline";
|
$status = "offline";
|
||||||
|
|
@ -570,16 +583,20 @@ echo "<table id='servermonitor' class='tablesorter' data-sortlist='[[0,0],[3,1]]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else{
|
else{
|
||||||
$status = "offline";
|
$status = "unknown";
|
||||||
$order = 3;
|
$order = 3;
|
||||||
$address = "<span style='color:darkred;font-weight:bold;'>Agent Offline</span>";
|
$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']);
|
$user = $db->getUserById($server_home['user_id_main']);
|
||||||
|
|
||||||
|
|
||||||
// Template
|
// Template
|
||||||
@$first = "<tr class='maintr$trclass'>";
|
@$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'>" . "<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 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>";
|
$first .= "<td class='collapsible' data-status='$status' data-pos='$pos'><b>" . htmlentities($server_home['home_name']) . "</b>$mod_name</td>";
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ Important references:
|
||||||
- live log retrieval exists
|
- live log retrieval exists
|
||||||
- logs can be fetched through the Panel
|
- logs can be fetched through the Panel
|
||||||
- the viewer can update via AJAX
|
- the viewer can update via AJAX
|
||||||
|
- the main game log viewer uses a large monospace output panel
|
||||||
|
|
||||||
## What Still Needs Cleanup
|
## What Still Needs Cleanup
|
||||||
|
|
||||||
|
|
@ -31,3 +32,13 @@ Important references:
|
||||||
- better error highlighting
|
- better error highlighting
|
||||||
- better downloadable log history
|
- better downloadable log history
|
||||||
|
|
||||||
|
## Log Viewer Layout
|
||||||
|
|
||||||
|
`Panel/modules/gamemanager/log.php` keeps the AJAX refresh behavior and renders the log in a large textarea:
|
||||||
|
|
||||||
|
- desktop height around `55vh`
|
||||||
|
- mobile height around `45vh`
|
||||||
|
- monospace font
|
||||||
|
- preserved line breaks
|
||||||
|
- vertical scrolling inside the log panel
|
||||||
|
- long-line overflow remains usable without shrinking the panel to one line
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,19 @@ Important files:
|
||||||
- `Agent_Linux/ogp_agent.pl`
|
- `Agent_Linux/ogp_agent.pl`
|
||||||
- `Agent-Windows/ogp_agent.pl`
|
- `Agent-Windows/ogp_agent.pl`
|
||||||
|
|
||||||
|
## Panel Display Behavior
|
||||||
|
|
||||||
|
Game Monitor should not convert ambiguous status into false offline.
|
||||||
|
|
||||||
|
Panel display mapping:
|
||||||
|
|
||||||
|
- `ONLINE` displays green.
|
||||||
|
- `STARTING`, `STOPPING`, and `UNRESPONSIVE` display yellow/active.
|
||||||
|
- `OFFLINE` displays red only when the agent confirms offline.
|
||||||
|
- `UNKNOWN` displays gray.
|
||||||
|
|
||||||
|
LGSL/GameQ query failure is not enough to mark a server offline. Query success may add player/map/hostname metadata, but query failure should only show a small unavailable note when the agent says the server is otherwise active.
|
||||||
|
|
||||||
## Recommended State Model
|
## Recommended State Model
|
||||||
|
|
||||||
| State | Meaning |
|
| State | Meaning |
|
||||||
|
|
@ -51,4 +64,3 @@ The agent should check, in this order:
|
||||||
- use state hints for start/stop transitions
|
- use state hints for start/stop transitions
|
||||||
- expose clear messages for `STARTING` and `UNRESPONSIVE`
|
- expose clear messages for `STARTING` and `UNRESPONSIVE`
|
||||||
- add precise log excerpts when startup fails
|
- add precise log excerpts when startup fails
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -62,6 +62,19 @@ Useful state labels:
|
||||||
|
|
||||||
Query checks should remain optional metadata only.
|
Query checks should remain optional metadata only.
|
||||||
|
|
||||||
|
## Game Monitor Display Rules
|
||||||
|
|
||||||
|
`Panel/modules/gamemanager/server_monitor.php` should display state from the agent status response, not from LGSL/GameQ query success alone.
|
||||||
|
|
||||||
|
Current display mapping:
|
||||||
|
|
||||||
|
- `ONLINE` -> green online indicator
|
||||||
|
- `STARTING`, `STOPPING`, `UNRESPONSIVE` -> yellow active/starting indicator
|
||||||
|
- `OFFLINE` -> red offline indicator
|
||||||
|
- `UNKNOWN` or agent unavailable -> gray unknown indicator
|
||||||
|
|
||||||
|
Query metadata remains optional. A running process/session or listening game port must not be shown as red/offline only because query details are unavailable.
|
||||||
|
|
||||||
## Log Viewer
|
## Log Viewer
|
||||||
|
|
||||||
Relevant files:
|
Relevant files:
|
||||||
|
|
@ -72,6 +85,8 @@ Relevant files:
|
||||||
|
|
||||||
The log view should be treated as live, AJAX-updated output rather than a full page reload workflow.
|
The log view should be treated as live, AJAX-updated output rather than a full page reload workflow.
|
||||||
|
|
||||||
|
`log.php` now renders the live log as a large monospace textarea with viewport-based height, preserved line breaks, vertical scrolling, and mobile sizing.
|
||||||
|
|
||||||
## What This Module Depends On
|
## What This Module Depends On
|
||||||
|
|
||||||
- `config_games` for startup parameters and protocol definitions
|
- `config_games` for startup parameters and protocol definitions
|
||||||
|
|
@ -79,4 +94,3 @@ The log view should be treated as live, AJAX-updated output rather than a full p
|
||||||
- `user_games` for server home records
|
- `user_games` for server home records
|
||||||
- `rcon` for command support where available
|
- `rcon` for command support where available
|
||||||
- `addonsmanager` for content/mod interactions
|
- `addonsmanager` for content/mod interactions
|
||||||
|
|
||||||
|
|
|
||||||
60
docs/modules/UPDATE.md
Normal file
60
docs/modules/UPDATE.md
Normal file
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Update Module
|
||||||
|
|
||||||
|
## Role
|
||||||
|
|
||||||
|
`Panel/modules/update` exposes the admin Panel update page. The page delegates most update behavior to:
|
||||||
|
|
||||||
|
- `Panel/modules/administration/panel_update.php`
|
||||||
|
|
||||||
|
## Current Behavior
|
||||||
|
|
||||||
|
The update page is intentionally simple:
|
||||||
|
|
||||||
|
- 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
|
||||||
|
- keeps Apache diagnostics in a collapsed Advanced Diagnostics section
|
||||||
|
|
||||||
|
## Update Settings
|
||||||
|
|
||||||
|
The admin page stores these settings in the Panel settings table:
|
||||||
|
|
||||||
|
- `gsp_update_repo_url`
|
||||||
|
- `gsp_update_branch`
|
||||||
|
- `gsp_update_repo_root`
|
||||||
|
- `gsp_update_panel_path`
|
||||||
|
- `gsp_update_backup_before`
|
||||||
|
|
||||||
|
Defaults:
|
||||||
|
|
||||||
|
- Repository URL: `http://forge.runlevelsystems.com/dev/GSP.git`
|
||||||
|
- Branch: `Panel-unstable`
|
||||||
|
- Repository Root: `/var/www/html/GSP`
|
||||||
|
- Panel Path: `/var/www/html/GSP/Panel`
|
||||||
|
- Backup Before Update: enabled
|
||||||
|
|
||||||
|
## 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 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`.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
## 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`.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue