fixed updated
This commit is contained in:
parent
a1e5331f4a
commit
5ead40a761
7 changed files with 606 additions and 213 deletions
|
|
@ -37,6 +37,7 @@ 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';
|
||||
if (file_exists($gspPatchManager)) {
|
||||
|
|
@ -53,6 +54,79 @@ $line = '[' . date('Y-m-d H:i:s') . '] ' . $message . PHP_EOL;
|
|||
@file_put_contents(GSP_UPDATE_LOG, $line, FILE_APPEND | LOCK_EX);
|
||||
}
|
||||
|
||||
function gsp_path_debug_summary($path)
|
||||
{
|
||||
$path = rtrim((string)$path, '/');
|
||||
if ($path === '') {
|
||||
return '[empty path]';
|
||||
}
|
||||
$parent = dirname($path);
|
||||
$parts = array(
|
||||
'path=' . $path,
|
||||
'exists=' . (file_exists($path) ? 'yes' : 'no'),
|
||||
'is_dir=' . (is_dir($path) ? 'yes' : 'no'),
|
||||
'writable=' . (is_writable($path) ? 'yes' : 'no'),
|
||||
'parent=' . $parent,
|
||||
'parent_exists=' . (file_exists($parent) ? 'yes' : 'no'),
|
||||
'parent_writable=' . (is_writable($parent) ? 'yes' : 'no'),
|
||||
);
|
||||
return implode(', ', $parts);
|
||||
}
|
||||
|
||||
function gsp_ensure_directory($path, $label = 'directory')
|
||||
{
|
||||
$path = rtrim((string)$path, '/');
|
||||
if ($path === '' || strpos($path, "\0") !== false) {
|
||||
$message = 'Invalid ' . $label . ' path.';
|
||||
gsp_update_log($message . ' ' . gsp_path_debug_summary($path));
|
||||
return array('success' => false, 'error' => $message);
|
||||
}
|
||||
if (is_dir($path)) {
|
||||
if (!is_writable($path)) {
|
||||
$message = ucfirst($label) . ' exists but is not writable: ' . $path;
|
||||
gsp_update_log($message . ' [' . gsp_path_debug_summary($path) . ']');
|
||||
return array('success' => false, 'error' => $message);
|
||||
}
|
||||
return array('success' => true, 'path' => $path, 'created' => false);
|
||||
}
|
||||
if (!@mkdir($path, 0755, true) && !is_dir($path)) {
|
||||
$last = error_get_last();
|
||||
$detail = isset($last['message']) ? $last['message'] : 'mkdir returned false';
|
||||
$message = 'Cannot create ' . $label . ': ' . $path;
|
||||
gsp_update_log($message . ' [' . $detail . '] [' . gsp_path_debug_summary($path) . ']');
|
||||
return array('success' => false, 'error' => $message . ' (' . $detail . ')');
|
||||
}
|
||||
if (!is_writable($path)) {
|
||||
$message = ucfirst($label) . ' was created but is not writable: ' . $path;
|
||||
gsp_update_log($message . ' [' . gsp_path_debug_summary($path) . ']');
|
||||
return array('success' => false, 'error' => $message);
|
||||
}
|
||||
return array('success' => true, 'path' => $path, 'created' => true);
|
||||
}
|
||||
|
||||
function gsp_get_backup_base(array $update_cfg = null)
|
||||
{
|
||||
$update_cfg = $update_cfg ?: gsp_update_settings();
|
||||
$base = !empty($update_cfg['backup_path']) ? rtrim((string)$update_cfg['backup_path'], '/') : GSP_BACKUP_BASE;
|
||||
if ($base === '' || strpos($base, "\0") !== false || strpos($base, '..') !== false || strpos($base, '/') !== 0) {
|
||||
$base = GSP_BACKUP_BASE;
|
||||
}
|
||||
return $base;
|
||||
}
|
||||
|
||||
function gsp_get_backup_retention(array $update_cfg = null)
|
||||
{
|
||||
$update_cfg = $update_cfg ?: gsp_update_settings();
|
||||
$retention = isset($update_cfg['backup_retention']) ? (int)$update_cfg['backup_retention'] : GSP_DEFAULT_BACKUP_RETENTION;
|
||||
if ($retention < 1) {
|
||||
$retention = GSP_DEFAULT_BACKUP_RETENTION;
|
||||
}
|
||||
if ($retention > 200) {
|
||||
$retention = 200;
|
||||
}
|
||||
return $retention;
|
||||
}
|
||||
|
||||
function gsp_log_update_to_db($channel, $branch, $status, $message, $backup_path = null, $db_backup_path = null, $file_backup_path = null, $started_at = null, $finished_at = null)
|
||||
{
|
||||
global $db;
|
||||
|
|
@ -167,9 +241,10 @@ return [
|
|||
'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,
|
||||
'panel_post_update_command' => !empty($settings['gsp_update_panel_post_update_command']) ? (string)$settings['gsp_update_panel_post_update_command'] : '',
|
||||
'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'] : '',
|
||||
|
|
@ -200,11 +275,14 @@ 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 (['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.';
|
||||
}
|
||||
}
|
||||
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) {
|
||||
$value = isset($cfg[$key]) ? trim((string)$cfg[$key], '/') : '';
|
||||
if ($value === '' || strpos($value, "\0") !== false || strpos($value, '..') !== false || !preg_match('/^[A-Za-z0-9._\/-]+$/', $value)) {
|
||||
|
|
@ -298,10 +376,11 @@ $warnings = [];
|
|||
$update_cfg = $update_cfg ?: gsp_update_settings();
|
||||
$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');
|
||||
$root_real = realpath($root_path) ?: $root_path;
|
||||
$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 = [
|
||||
|
|
@ -316,7 +395,7 @@ $layout = [
|
|||
'panel_dir_real' => $panel_real,
|
||||
'website_dir' => $website_path,
|
||||
'website_dir_real' => $website_real,
|
||||
'backup_dir' => GSP_BACKUP_BASE,
|
||||
'backup_dir' => $backup_base,
|
||||
'config_file' => $panel_path . '/includes/config.inc.php',
|
||||
'destination_panel' => $panel_path,
|
||||
'destination_website' => $website_path,
|
||||
|
|
@ -339,13 +418,12 @@ $warnings[] = 'Website directory is missing. Panel updates can still continue, b
|
|||
if (!file_exists($layout['config_file'])) {
|
||||
$errors[] = 'Panel includes/config.inc.php was not found and cannot be preserved.';
|
||||
}
|
||||
if (!is_dir(GSP_BACKUP_BASE)) {
|
||||
if (!@mkdir(GSP_BACKUP_BASE, 0755, true) && !is_dir(GSP_BACKUP_BASE)) {
|
||||
$errors[] = 'Backups directory is missing and cannot be created.';
|
||||
}
|
||||
}
|
||||
$backup_ready = gsp_ensure_directory($backup_base, 'backup directory');
|
||||
if (!$backup_ready['success']) {
|
||||
$errors[] = $backup_ready['error'];
|
||||
}
|
||||
|
||||
foreach ([$root_path, $panel_path, GSP_BACKUP_BASE] as $path) {
|
||||
foreach ([$root_path, $panel_path, $backup_base] as $path) {
|
||||
if (!is_writable($path)) {
|
||||
$errors[] = 'Path is not writable: ' . $path;
|
||||
}
|
||||
|
|
@ -518,33 +596,94 @@ $count++;
|
|||
return ['success' => true, 'path' => $dest, 'count' => $count];
|
||||
}
|
||||
|
||||
function gsp_prune_old_backups($max_backups = 5)
|
||||
function gsp_get_managed_backup_entries($backup_base = null)
|
||||
{
|
||||
$entries = gsp_get_available_backups();
|
||||
if (count($entries) <= $max_backups) {
|
||||
return;
|
||||
}
|
||||
$to_delete = array_slice($entries, $max_backups);
|
||||
foreach ($to_delete as $entry) {
|
||||
gsp_rmdir_recursive(GSP_BACKUP_BASE . '/' . $entry['ts']);
|
||||
gsp_update_log('Pruned old backup: ' . $entry['ts']);
|
||||
}
|
||||
$backup_base = $backup_base ? rtrim((string)$backup_base, '/') : gsp_get_backup_base();
|
||||
$entries = array();
|
||||
if (!is_dir($backup_base)) {
|
||||
return $entries;
|
||||
}
|
||||
foreach ((array)scandir($backup_base) as $entry) {
|
||||
if ($entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
$dir = $backup_base . '/' . $entry;
|
||||
if (!is_dir($dir)) {
|
||||
continue;
|
||||
}
|
||||
$type = null;
|
||||
$stamp = null;
|
||||
if (preg_match('/^(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})$/', $entry, $m)) {
|
||||
$type = 'full';
|
||||
$stamp = $m[1];
|
||||
} elseif (preg_match('/^component_[A-Za-z0-9._-]+_(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})$/', $entry, $m)) {
|
||||
$type = 'component';
|
||||
$stamp = $m[1];
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
$meta_file = $dir . '/backup.json';
|
||||
$meta = array();
|
||||
if (is_file($meta_file)) {
|
||||
$meta = json_decode((string)@file_get_contents($meta_file), true) ?: array();
|
||||
}
|
||||
$sort_time = strtotime(str_replace('_', ' ', $stamp));
|
||||
if ($sort_time === false) {
|
||||
$sort_time = @filemtime($dir) ?: 0;
|
||||
}
|
||||
$entries[] = array(
|
||||
'name' => $entry,
|
||||
'path' => $dir,
|
||||
'type' => $type,
|
||||
'ts' => $stamp,
|
||||
'sort_time' => $sort_time,
|
||||
'meta' => $meta,
|
||||
);
|
||||
}
|
||||
usort($entries, function ($a, $b) {
|
||||
if ($a['sort_time'] === $b['sort_time']) {
|
||||
return strcmp($b['name'], $a['name']);
|
||||
}
|
||||
return ($a['sort_time'] < $b['sort_time']) ? 1 : -1;
|
||||
});
|
||||
return $entries;
|
||||
}
|
||||
|
||||
function gsp_create_full_backup($update_target_type, $update_target_version, $include_apache = false)
|
||||
function gsp_prune_old_backups($max_backups = 5, $backup_base = null)
|
||||
{
|
||||
$ts = date('Y-m-d_H-i-s');
|
||||
$backup_dir = GSP_BACKUP_BASE . '/' . $ts;
|
||||
if (!is_dir(GSP_BACKUP_BASE) && !@mkdir(GSP_BACKUP_BASE, 0755, true)) {
|
||||
return ['success' => false, 'error' => 'Cannot create backup base directory.'];
|
||||
}
|
||||
if (!@mkdir($backup_dir, 0755, true)) {
|
||||
return ['success' => false, 'error' => 'Cannot create backup directory: ' . $backup_dir];
|
||||
$entries = gsp_get_managed_backup_entries($backup_base);
|
||||
if (count($entries) <= $max_backups) {
|
||||
return array('success' => true, 'deleted' => array());
|
||||
}
|
||||
$to_delete = array_slice($entries, $max_backups);
|
||||
$deleted = array();
|
||||
foreach ($to_delete as $entry) {
|
||||
gsp_rmdir_recursive($entry['path']);
|
||||
$deleted[] = $entry['name'];
|
||||
gsp_update_log('Pruned old backup: ' . $entry['path']);
|
||||
}
|
||||
return array('success' => true, 'deleted' => $deleted);
|
||||
}
|
||||
|
||||
$meta = [
|
||||
'backup_timestamp' => $ts,
|
||||
'gsp_root' => GSP_ROOT_DIR,
|
||||
function gsp_create_full_backup($update_target_type, $update_target_version, $include_apache = false, array $update_cfg = null)
|
||||
{
|
||||
$ts = date('Y-m-d_H-i-s');
|
||||
$backup_base = gsp_get_backup_base($update_cfg);
|
||||
$retention = gsp_get_backup_retention($update_cfg);
|
||||
$backup_base_ready = gsp_ensure_directory($backup_base, 'backup base directory');
|
||||
if (!$backup_base_ready['success']) {
|
||||
return array('success' => false, 'error' => $backup_base_ready['error']);
|
||||
}
|
||||
$backup_dir = $backup_base . '/' . $ts;
|
||||
$backup_dir_ready = gsp_ensure_directory($backup_dir, 'backup directory');
|
||||
if (!$backup_dir_ready['success']) {
|
||||
return array('success' => false, 'error' => $backup_dir_ready['error']);
|
||||
}
|
||||
|
||||
$meta = [
|
||||
'backup_timestamp' => $ts,
|
||||
'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,
|
||||
|
|
@ -593,17 +732,21 @@ $meta['apache_backup'] = $apache_backup['path'];
|
|||
} else {
|
||||
$meta['apache_backup_error'] = $apache_backup['error'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@file_put_contents($backup_dir . '/backup.json', json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
gsp_prune_old_backups(5);
|
||||
gsp_update_log('Backup created: ' . $backup_dir);
|
||||
@file_put_contents($backup_dir . '/backup.json', json_encode($meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
$prune = gsp_prune_old_backups($retention, $backup_base);
|
||||
if (!empty($prune['deleted'])) {
|
||||
gsp_update_log('Backup retention pruned ' . count($prune['deleted']) . ' entries from ' . $backup_base . ': ' . implode(', ', $prune['deleted']));
|
||||
}
|
||||
gsp_update_log('Backup created: ' . $backup_dir);
|
||||
|
||||
return [
|
||||
'success' => true,
|
||||
'backup_dir' => $backup_dir,
|
||||
'backup_ts' => $ts,
|
||||
];
|
||||
return [
|
||||
'success' => true,
|
||||
'backup_base' => $backup_base,
|
||||
'backup_dir' => $backup_dir,
|
||||
'backup_ts' => $ts,
|
||||
];
|
||||
}
|
||||
|
||||
function gsp_download_zip($repo_owner, $repo_name, $ref, $temp_dir)
|
||||
|
|
@ -1047,13 +1190,63 @@ $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,
|
||||
];
|
||||
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)
|
||||
|
|
@ -1145,55 +1338,6 @@ return [
|
|||
'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)) {
|
||||
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_fix_permissions($root_dir)
|
||||
{
|
||||
|
|
@ -1299,12 +1443,12 @@ 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;
|
||||
}
|
||||
$backup = ['success' => true, 'backup_dir' => null];
|
||||
if (!empty($update_cfg['backup_before_update'])) {
|
||||
$backup = gsp_create_full_backup('git-update', $update_cfg['branch'], false, $update_cfg);
|
||||
if (!$backup['success']) {
|
||||
return $backup;
|
||||
}
|
||||
gsp_update_log("Backup created before git update to {$update_cfg['branch']}: {$backup['backup_dir']}");
|
||||
}
|
||||
|
||||
|
|
@ -1354,21 +1498,23 @@ return [
|
|||
];
|
||||
}
|
||||
|
||||
function gsp_safe_component_backup($component, $source_path, $backup_base)
|
||||
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_BACKUP_BASE;
|
||||
}
|
||||
if (!is_dir($backup_base) && !@mkdir($backup_base, 0755, true)) {
|
||||
return ['success' => false, 'error' => 'Cannot create backup path: ' . $backup_base];
|
||||
}
|
||||
$ts = date('Y-m-d_H-i-s');
|
||||
$backup_dir = $backup_base . '/component_' . $component . '_' . $ts;
|
||||
if (!@mkdir($backup_dir, 0755, true)) {
|
||||
return ['success' => false, 'error' => 'Cannot create component backup directory: ' . $backup_dir];
|
||||
}
|
||||
$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];
|
||||
}
|
||||
|
|
@ -1380,10 +1526,14 @@ 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));
|
||||
return ['success' => true, 'backup_dir' => $backup_dir, 'archive' => $archive];
|
||||
'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)
|
||||
|
|
@ -1518,7 +1668,7 @@ return ['success' => false, 'error' => 'Destination path for ' . $component . '
|
|||
}
|
||||
$backup = ['success' => true, 'backup_dir' => null];
|
||||
if (!empty($update_cfg['backup_before_update'])) {
|
||||
$backup = gsp_safe_component_backup($component, $dest, $update_cfg['backup_path']);
|
||||
$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;
|
||||
|
|
@ -1680,17 +1830,18 @@ gsp_update_log('Remote agent update requested: id=' . $remote_id . ' component='
|
|||
return ['success' => true, 'results' => $results, 'success_count' => $success_count];
|
||||
}
|
||||
|
||||
function gsp_get_available_backups()
|
||||
function gsp_get_available_backups($backup_base = null)
|
||||
{
|
||||
$backups = [];
|
||||
if (!is_dir(GSP_BACKUP_BASE)) {
|
||||
return $backups;
|
||||
}
|
||||
foreach ((array)scandir(GSP_BACKUP_BASE) as $entry) {
|
||||
$backups = [];
|
||||
$backup_base = $backup_base ? rtrim((string)$backup_base, '/') : gsp_get_backup_base();
|
||||
if (!is_dir($backup_base)) {
|
||||
return $backups;
|
||||
}
|
||||
foreach ((array)scandir($backup_base) as $entry) {
|
||||
if ($entry === '.' || $entry === '..') {
|
||||
continue;
|
||||
}
|
||||
$dir = GSP_BACKUP_BASE . '/' . $entry;
|
||||
$dir = $backup_base . '/' . $entry;
|
||||
if (!is_dir($dir)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -1763,10 +1914,10 @@ return ['success' => false, 'error' => 'MySQL restore failed: ' . implode(' | ',
|
|||
return ['success' => true];
|
||||
}
|
||||
|
||||
function gsp_do_revert($backup_ts, $restore_apache = false)
|
||||
function gsp_do_revert($backup_ts, $restore_apache = false, array $update_cfg = null)
|
||||
{
|
||||
global $db;
|
||||
$backup_dir = GSP_BACKUP_BASE . '/' . $backup_ts;
|
||||
global $db;
|
||||
$backup_dir = gsp_get_backup_base($update_cfg) . '/' . $backup_ts;
|
||||
if (!is_dir($backup_dir)) {
|
||||
return ['success' => false, 'error' => 'Backup not found: ' . $backup_ts];
|
||||
}
|
||||
|
|
@ -2058,37 +2209,12 @@ return [
|
|||
|
||||
function gsp_fix_apache_paths($confirmed, $reload_apache)
|
||||
{
|
||||
if (!$confirmed) {
|
||||
return ['success' => false, 'error' => 'Apache path fix requires explicit confirmation.'];
|
||||
}
|
||||
|
||||
function gsp_disable_ssl_vhost($vhost_file, $confirmed)
|
||||
{
|
||||
if (!$confirmed) {
|
||||
return ['success' => false, 'error' => 'SSL vhost disable requires confirmation.'];
|
||||
}
|
||||
$vhost = basename((string)$vhost_file);
|
||||
if (!preg_match('/\.conf$/', $vhost) || strpos($vhost, '-ssl') === false) {
|
||||
return ['success' => false, 'error' => 'Only SSL vhost .conf files can be disabled from this action.'];
|
||||
}
|
||||
$enabled_path = '/etc/apache2/sites-enabled/' . $vhost;
|
||||
if (!file_exists($enabled_path) && !is_link($enabled_path)) {
|
||||
return ['success' => true, 'message' => $vhost . ' is already disabled.'];
|
||||
}
|
||||
if (!@unlink($enabled_path)) {
|
||||
return ['success' => false, 'error' => 'Failed to disable SSL site: ' . $vhost];
|
||||
}
|
||||
$test = gsp_apache_configtest();
|
||||
if (!$test['success']) {
|
||||
return ['success' => false, 'error' => 'Disabled site but apache2ctl configtest still failed: ' . $test['output']];
|
||||
}
|
||||
$reload = gsp_apache_reload();
|
||||
gsp_update_log('Disabled SSL vhost in sites-enabled: ' . $vhost);
|
||||
return ['success' => true, 'configtest' => $test, 'reload' => $reload, 'message' => 'Disabled SSL vhost: ' . $vhost];
|
||||
}
|
||||
$scan = gsp_scan_apache_configs();
|
||||
if (!$scan['available']) {
|
||||
return ['success' => false, 'error' => 'Apache config folder not available.'];
|
||||
if (!$confirmed) {
|
||||
return ['success' => false, 'error' => 'Apache path fix requires explicit confirmation.'];
|
||||
}
|
||||
$scan = gsp_scan_apache_configs();
|
||||
if (!$scan['available']) {
|
||||
return ['success' => false, 'error' => 'Apache config folder not available.'];
|
||||
}
|
||||
|
||||
$backup = gsp_create_full_backup('apache-fix', 'apache-path-repair', true);
|
||||
|
|
@ -2174,8 +2300,33 @@ return [
|
|||
'backup_dir' => $backup['backup_dir'],
|
||||
'configtest' => $test,
|
||||
'reload' => $reload,
|
||||
'planned_replacements' => $planned,
|
||||
];
|
||||
'planned_replacements' => $planned,
|
||||
];
|
||||
}
|
||||
|
||||
function gsp_disable_ssl_vhost($vhost_file, $confirmed)
|
||||
{
|
||||
if (!$confirmed) {
|
||||
return ['success' => false, 'error' => 'SSL vhost disable requires confirmation.'];
|
||||
}
|
||||
$vhost = basename((string)$vhost_file);
|
||||
if (!preg_match('/\.conf$/', $vhost) || strpos($vhost, '-ssl') === false) {
|
||||
return ['success' => false, 'error' => 'Only SSL vhost .conf files can be disabled from this action.'];
|
||||
}
|
||||
$enabled_path = '/etc/apache2/sites-enabled/' . $vhost;
|
||||
if (!file_exists($enabled_path) && !is_link($enabled_path)) {
|
||||
return ['success' => true, 'message' => $vhost . ' is already disabled.'];
|
||||
}
|
||||
if (!@unlink($enabled_path)) {
|
||||
return ['success' => false, 'error' => 'Failed to disable SSL site: ' . $vhost];
|
||||
}
|
||||
$test = gsp_apache_configtest();
|
||||
if (!$test['success']) {
|
||||
return ['success' => false, 'error' => 'Disabled site but apache2ctl configtest still failed: ' . $test['output']];
|
||||
}
|
||||
$reload = gsp_apache_reload();
|
||||
gsp_update_log('Disabled SSL vhost in sites-enabled: ' . $vhost);
|
||||
return ['success' => true, 'configtest' => $test, 'reload' => $reload, 'message' => 'Disabled SSL vhost: ' . $vhost];
|
||||
}
|
||||
|
||||
function gsp_get_patch_overview()
|
||||
|
|
@ -2255,10 +2406,11 @@ $submitted_csrf = isset($_POST['gsp_update_csrf']) ? $_POST['gsp_update_csrf'] :
|
|||
if (!hash_equals($csrf_token, $submitted_csrf)) {
|
||||
print_failure('Invalid security token. Please reload and try again.');
|
||||
} else {
|
||||
$action = $_POST['gsp_update_action'];
|
||||
$started_at = date('Y-m-d H:i:s');
|
||||
$restart_nonce = isset($_POST['gsp_restart_nonce']) ? trim($_POST['gsp_restart_nonce']) : '';
|
||||
set_time_limit(0);
|
||||
$action = $_POST['gsp_update_action'];
|
||||
$started_at = date('Y-m-d H:i:s');
|
||||
$restart_nonce = isset($_POST['gsp_restart_nonce']) ? trim($_POST['gsp_restart_nonce']) : '';
|
||||
set_time_limit(0);
|
||||
try {
|
||||
|
||||
if ($action === 'preflight') {
|
||||
$preflight_result = gsp_preflight_check();
|
||||
|
|
@ -2297,8 +2449,8 @@ print_success(htmlspecialchars(isset($disable['message']) ? $disable['message']
|
|||
} else {
|
||||
print_failure('Disable SSL vhost failed: ' . htmlspecialchars($disable['error']));
|
||||
}
|
||||
} elseif ($action === 'backup_only') {
|
||||
$result = gsp_create_full_backup('backup-only', 'manual', false);
|
||||
} elseif ($action === 'backup_only') {
|
||||
$result = gsp_create_full_backup('backup-only', 'manual', false, $update_cfg);
|
||||
if ($result['success']) {
|
||||
print_success('Backup created: <code>' . htmlspecialchars($result['backup_dir']) . '</code>');
|
||||
} else {
|
||||
|
|
@ -2315,9 +2467,10 @@ $new_cfg = [
|
|||
'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']), '/') : '',
|
||||
'panel_post_update_command' => isset($_POST['gsp_update_panel_post_update_command']) ? trim((string)$_POST['gsp_update_panel_post_update_command']) : '',
|
||||
'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']) : '',
|
||||
|
|
@ -2337,9 +2490,10 @@ $db->setSettings([
|
|||
'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_panel_post_update_command' => $new_cfg['panel_post_update_command'],
|
||||
'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'],
|
||||
|
|
@ -2354,9 +2508,10 @@ $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_panel_post_update_command'] = $new_cfg['panel_post_update_command'];
|
||||
$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'];
|
||||
|
|
@ -2375,9 +2530,10 @@ $update_cfg = [
|
|||
'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'],
|
||||
'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'],
|
||||
'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'],
|
||||
|
|
@ -2442,9 +2598,10 @@ $update_cfg = [
|
|||
'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'],
|
||||
'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'],
|
||||
'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'],
|
||||
|
|
@ -2460,9 +2617,10 @@ $db->setSettings([
|
|||
'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_panel_post_update_command' => $update_cfg['panel_post_update_command'],
|
||||
'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'],
|
||||
|
|
@ -2526,32 +2684,41 @@ $restore_apache = !empty($_POST['gsp_restore_apache']);
|
|||
if (!preg_match('/^\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}$/', $backup_ts)) {
|
||||
print_failure('Invalid backup timestamp selected.');
|
||||
} else {
|
||||
$result = gsp_do_revert($backup_ts, $restore_apache);
|
||||
$result = gsp_do_revert($backup_ts, $restore_apache, $update_cfg);
|
||||
if ($result['success']) {
|
||||
print_success('Reverted to backup <strong>' . htmlspecialchars($backup_ts) . '</strong>.');
|
||||
} else {
|
||||
print_failure('Revert failed: ' . htmlspecialchars($result['error']));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$finished_at = date('Y-m-d H:i:s');
|
||||
gsp_log_update_to_db('panel-update', null, 'info', 'Admin action: ' . $action, null, null, null, $started_at, $finished_at);
|
||||
}
|
||||
$finished_at = date('Y-m-d H:i:s');
|
||||
gsp_log_update_to_db('panel-update', null, 'info', 'Admin action: ' . $action, null, null, null, $started_at, $finished_at);
|
||||
} catch (Throwable $e) {
|
||||
$finished_at = date('Y-m-d H:i:s');
|
||||
gsp_update_log('Update action failure [' . $action . ']: ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
|
||||
gsp_log_update_to_db('panel-update', null, 'error', 'Admin action failed: ' . $action . ' | ' . $e->getMessage(), null, null, null, $started_at, $finished_at);
|
||||
print_failure('Update action failed: ' . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
}
|
||||
$_SESSION['gsp_update_csrf'] = gsp_random_token();
|
||||
$csrf_token = $_SESSION['gsp_update_csrf'];
|
||||
}
|
||||
|
||||
$current_version = gsp_get_current_version();
|
||||
$current_branch = gsp_get_current_branch();
|
||||
$git_commit = gsp_get_git_commit();
|
||||
$current_branch = gsp_get_current_branch();
|
||||
$git_commit = gsp_get_git_commit();
|
||||
$last_layout = isset($_SESSION['gsp_last_update_layout']) && is_array($_SESSION['gsp_last_update_layout'])
|
||||
? $_SESSION['gsp_last_update_layout']
|
||||
: null;
|
||||
$vinfo = gsp_read_version_json();
|
||||
$latest_release = 'N/A';
|
||||
$backups = gsp_get_available_backups();
|
||||
$patch_overview = gsp_get_patch_overview();
|
||||
$vinfo = gsp_read_version_json();
|
||||
$latest_release = 'N/A';
|
||||
$backup_base = gsp_get_backup_base($update_cfg);
|
||||
$backup_retention = gsp_get_backup_retention($update_cfg);
|
||||
$managed_backups = gsp_get_managed_backup_entries($backup_base);
|
||||
$backups = gsp_get_available_backups($backup_base);
|
||||
$patch_overview = gsp_get_patch_overview();
|
||||
if ($apache_scan_result === null) {
|
||||
$apache_scan_result = gsp_scan_apache_configs();
|
||||
}
|
||||
|
|
@ -2570,7 +2737,9 @@ 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 "<tr><td><strong>Backup Path:</strong></td><td><code>" . htmlspecialchars($backup_base) . "</code></td></tr>\n";
|
||||
echo "<tr><td><strong>Managed Backups Stored:</strong></td><td>" . intval(count($managed_backups)) . " (retention: " . intval($backup_retention) . ")</td></tr>\n";
|
||||
echo "<tr><td><strong>Rollback Backups Available:</strong></td><td>" . intval(count($backups)) . "</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";
|
||||
|
|
@ -2595,9 +2764,10 @@ echo "<tr><td><strong>Panel Source Folder</strong></td><td><input type='text' na
|
|||
echo "<tr><td><strong>Linux Agent Source Folder</strong></td><td><input type='text' name='gsp_update_linux_agent_source_path' value='" . htmlspecialchars($update_cfg['linux_agent_source_path'], ENT_QUOTES, 'UTF-8') . "' size='45'></td></tr>\n";
|
||||
echo "<tr><td><strong>Windows Agent Source Folder</strong></td><td><input type='text' name='gsp_update_windows_agent_source_path' value='" . htmlspecialchars($update_cfg['windows_agent_source_path'], ENT_QUOTES, 'UTF-8') . "' size='45'></td></tr>\n";
|
||||
echo "<tr><td><strong>Website Source Folder</strong></td><td><input type='text' name='gsp_update_website_source_path' value='" . htmlspecialchars($update_cfg['website_source_path'], ENT_QUOTES, 'UTF-8') . "' size='45'></td></tr>\n";
|
||||
echo "<tr><td><strong>Git Executable</strong></td><td><input type='text' name='gsp_update_git_path' value='" . htmlspecialchars($update_cfg['git_path'], ENT_QUOTES, 'UTF-8') . "' size='45'> <small>Usually <code>git</code>.</small></td></tr>\n";
|
||||
echo "<tr><td><strong>Backup Path</strong></td><td><input type='text' name='gsp_update_backup_path' value='" . htmlspecialchars($update_cfg['backup_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 "<tr><td><strong>Git Executable</strong></td><td><input type='text' name='gsp_update_git_path' value='" . htmlspecialchars($update_cfg['git_path'], ENT_QUOTES, 'UTF-8') . "' size='45'> <small>Usually <code>git</code>.</small></td></tr>\n";
|
||||
echo "<tr><td><strong>Backup Path</strong></td><td><input type='text' name='gsp_update_backup_path' value='" . htmlspecialchars($update_cfg['backup_path'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||
echo "<tr><td><strong>Backup Retention</strong></td><td><input type='number' min='1' max='200' name='gsp_update_backup_retention' value='" . htmlspecialchars($update_cfg['backup_retention'], ENT_QUOTES, 'UTF-8') . "' style='width:90px;'> <small>Keep newest backups only.</small></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 "<tr><td><strong>Panel Post-update Command</strong></td><td><input type='text' name='gsp_update_panel_post_update_command' value='" . htmlspecialchars($update_cfg['panel_post_update_command'], ENT_QUOTES, 'UTF-8') . "' size='85'> <small>Admin-only dangerous command.</small></td></tr>\n";
|
||||
echo "<tr><td><strong>Website Post-update Command</strong></td><td><input type='text' name='gsp_update_website_post_update_command' value='" . htmlspecialchars($update_cfg['website_post_update_command'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||
echo "<tr><td><strong>Linux Agent Post-update Command</strong></td><td><input type='text' name='gsp_update_linux_agent_post_update_command' value='" . htmlspecialchars($update_cfg['linux_agent_post_update_command'], ENT_QUOTES, 'UTF-8') . "' size='85'></td></tr>\n";
|
||||
|
|
|
|||
|
|
@ -13,8 +13,11 @@ function gsp_update_smoke_assert($condition, $message)
|
|||
|
||||
$cfg = gsp_update_settings();
|
||||
gsp_update_smoke_assert(function_exists('gsp_do_configured_git_update'), 'configured Git updater helper is top-level');
|
||||
gsp_update_smoke_assert(function_exists('gsp_checkout_update_source'), 'configured Git checkout helper is top-level');
|
||||
gsp_update_smoke_assert(function_exists('gsp_disable_ssl_vhost'), 'SSL vhost disable helper is top-level');
|
||||
gsp_update_smoke_assert($cfg['repo_url'] === 'http://forge.runlevelsystems.com/dev/GSP.git', 'default repo URL is Forgejo');
|
||||
gsp_update_smoke_assert($cfg['branch'] === 'Panel-unstable', 'default branch is Panel-unstable');
|
||||
gsp_update_smoke_assert((int)$cfg['backup_retention'] === 5, 'default backup retention is 5');
|
||||
gsp_update_smoke_assert($cfg['panel_source_path'] === 'Panel', 'default Panel source folder');
|
||||
gsp_update_smoke_assert($cfg['linux_agent_source_path'] === 'Agent_Linux', 'default Linux agent source folder');
|
||||
gsp_update_smoke_assert($cfg['windows_agent_source_path'] === 'Agent-Windows', 'default Windows agent source folder');
|
||||
|
|
|
|||
|
|
@ -30,6 +30,17 @@ function exec_ogp_module()
|
|||
}
|
||||
|
||||
require_once(dirname(__FILE__) . '/../administration/panel_update.php');
|
||||
gsp_panel_update_section();
|
||||
if (!function_exists('gsp_panel_update_section')) {
|
||||
print_failure('Update module is unavailable because the updater helper file did not load correctly.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
gsp_panel_update_section();
|
||||
} catch (Throwable $e) {
|
||||
if (function_exists('gsp_update_log')) {
|
||||
gsp_update_log('Unhandled update page error: ' . $e->getMessage() . ' @ ' . $e->getFile() . ':' . $e->getLine());
|
||||
}
|
||||
print_failure('Update module failed: ' . htmlspecialchars($e->getMessage()));
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
|
|
|||
200
docs/modules/PANEL_UPDATE.md
Normal file
200
docs/modules/PANEL_UPDATE.md
Normal file
|
|
@ -0,0 +1,200 @@
|
|||
# Panel Update Module
|
||||
|
||||
## Scope
|
||||
|
||||
This document covers the admin Panel updater implemented through:
|
||||
|
||||
- `Panel/modules/update/update.php`
|
||||
- `Panel/modules/administration/panel_update.php`
|
||||
- `Panel/includes/lib_remote.php` for remote agent component updates
|
||||
|
||||
## Current Workflow
|
||||
|
||||
```text
|
||||
Admin opens home.php?m=update
|
||||
-> update.php loads panel_update.php
|
||||
-> gsp_panel_update_section() renders UI
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Backup Workflow
|
||||
|
||||
Backup helpers:
|
||||
|
||||
- `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()`
|
||||
|
||||
Behavior:
|
||||
|
||||
- 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
|
||||
|
||||
Managed backup directory names:
|
||||
|
||||
- full backups: `YYYY-MM-DD_HH-MM-SS`
|
||||
- component backups: `component_<name>_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
|
||||
|
||||
## Error Handling
|
||||
|
||||
The update page should not white-screen for routine helper/runtime failures.
|
||||
|
||||
Current protections:
|
||||
|
||||
- `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
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Undefined function crash
|
||||
|
||||
Check:
|
||||
|
||||
- `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 `php -l` passes but `function_exists(...)` is false, a helper was nested inside another function.
|
||||
|
||||
### Backup directory creation fails
|
||||
|
||||
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;'
|
||||
```
|
||||
|
|
@ -94,3 +94,6 @@ If scheduler behavior needs deeper investigation, start with:
|
|||
- `Agent_Linux/ogp_agent.pl` scheduler subroutines
|
||||
- `Agent-Windows/ogp_agent.pl` scheduler subroutines
|
||||
|
||||
## Current Panel Update Finding
|
||||
|
||||
The current scheduler does not provide a first-class path for scheduling Panel, Website, or remote agent self-updates. Existing scheduled update actions are game-server oriented, for example `gamemanager/update&type=steam` and Server Content actions. Backup and retention behavior in `Panel/modules/administration/panel_update.php` therefore applies to manual/admin-triggered updater flows, not to any existing scheduled self-update job.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
# Update Module
|
||||
|
||||
Primary operational reference:
|
||||
|
||||
- `docs/modules/PANEL_UPDATE.md`
|
||||
|
||||
## Role
|
||||
|
||||
`Panel/modules/update` exposes the admin Panel update page. The page delegates most update behavior to:
|
||||
|
|
|
|||
2
logs/update_trace.log
Normal file
2
logs/update_trace.log
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
[2026-06-07 14:29:08] Pruned old backup: /tmp/gsp_backup_test_2775997/component_panel_2026-06-01_00-00-01
|
||||
[2026-06-07 14:32:04] Pruned old backup: /tmp/gsp_retention_6a2580e44c1eb/component_panel_2026-06-07_09-00-00
|
||||
Loading…
Add table
Add a link
Reference in a new issue