diff --git a/Panel/js/modules/addonsmanager.js b/Panel/js/modules/addonsmanager.js
index d81f63a2..b2135530 100644
--- a/Panel/js/modules/addonsmanager.js
+++ b/Panel/js/modules/addonsmanager.js
@@ -1,11 +1,9 @@
$(function() {
var methodToRows = {
- download_zip: ['#scm-row-url', '#scm-row-path', '#scm-row-post-script'],
- download_file: ['#scm-row-url', '#scm-row-path', '#scm-row-post-script'],
- steam_workshop: ['#scm-row-workshop-id', '#scm-row-workshop-app-id', '#scm-row-target-path-template', '#scm-row-optional-folder-name', '#scm-row-launch-param-additions', '#scm-row-config-edit-rule', '#scm-row-post-script'],
+ download_zip: ['#scm-row-url', '#scm-row-path'],
+ steam_workshop: ['#scm-row-workshop-id', '#scm-row-workshop-app-id', '#scm-row-target-path-template', '#scm-row-optional-folder-name'],
post_script: ['#scm-row-post-script'],
- config_edit: ['#scm-row-path', '#scm-row-config-edit-rule', '#scm-row-post-script'],
- create_folder: ['#scm-row-path']
+ config_edit: ['#scm-row-path', '#scm-row-config-edit-rule']
};
var allRows = [
'#scm-row-url',
@@ -34,7 +32,7 @@ $(function() {
var selectedOption = $method.find('option:selected');
var helpText = selectedOption.data('help') || '';
$help.text(helpText);
- $('#scm-path-label').text(value === 'config_edit' ? 'Config Target Path' : 'Target Path');
+ $('#scm-path-label').text(value === 'config_edit' ? 'Config Target Path' : 'Target Path / Extract Path (optional)');
}
$method.on('change', applyContentTypeUi);
diff --git a/Panel/lang/English/modules/addonsmanager.php b/Panel/lang/English/modules/addonsmanager.php
index 063c01d7..22e50cce 100644
--- a/Panel/lang/English/modules/addonsmanager.php
+++ b/Panel/lang/English/modules/addonsmanager.php
@@ -45,8 +45,9 @@ define('LANG_select_game_type', "Select Game Type");
define('LANG_plugin', "Plugins / Mods");
define('LANG_mappack', "Map Packs");
define('LANG_config', "Config Packs");
-// Additional category labels (for future content types already defined in server_content_categories.php)
-if (!defined('LANG_version')) define('LANG_version', "Version");
+if (!defined('LANG_version')) {
+ define('LANG_version', "Version");
+}
define('LANG_server_content_version', "Server Versions");
define('LANG_modpack', "Modpacks");
define('LANG_workshop', "Workshop Content");
@@ -63,12 +64,12 @@ define('LANG_create_addon', "Create Server Content Item");
define('LANG_addons_db', "Server Content Database");
define('LANG_addon_has_been_created', "The server content item \"%s\" has been created.");
define('LANG_remove_addon', "Remove");
-define('LANG_fill_the_url_address_to_a_compressed_file', "Please enter a URL for the compressed file to download.");
+define('LANG_fill_the_url_address_to_a_compressed_file', "Please enter a download URL.");
define('LANG_fill_the_download_url', "Please enter a download URL.");
define('LANG_fill_the_workshop_id', "Please enter a Workshop ID.");
-define('LANG_fill_the_target_install_path', "Please select a target install path.");
-define('LANG_fill_the_script_action_body', "Please enter a script/action body.");
-define('LANG_fill_the_config_edit_rule', "Please enter a config edit rule.");
+define('LANG_fill_the_target_install_path', "Please enter the config target and edit action.");
+define('LANG_fill_the_script_action_body', "Please enter the installer script/action.");
+define('LANG_fill_the_config_edit_rule', "Please enter the config target and edit action.");
define('LANG_fill_the_addon_name', "Please enter a name for the server content item.");
define('LANG_select_an_addon_type', "Please select a content type.");
define('LANG_select_a_game_type', "Please select a game type.");
@@ -92,10 +93,8 @@ define('LANG_target_path_template', "Target Path");
define('LANG_optional_folder_name', "Optional Folder Name");
define('LANG_config_edit_rule', "Config Edit Rule");
define('LANG_launch_param_additions', "Launch Parameter Additions");
-define('LANG_content_type_help_download_zip', "Downloads and extracts an archive into the target path.");
-define('LANG_content_type_help_download_file', "Downloads a single file without extraction.");
-define('LANG_content_type_help_steam_workshop', "Downloads/updates a Workshop item and applies it to the server.");
-define('LANG_content_type_help_post_script', "Runs a script/action only, with no URL required.");
-define('LANG_content_type_help_config_edit', "Applies config edit rules to a target path, with no URL required.");
-define('LANG_content_type_help_create_folder', "Creates target folders/paths only, with no URL required.");
+define('LANG_content_type_help_download_zip', "Downloads an archive/file from URL; extract path is optional.");
+define('LANG_content_type_help_steam_workshop', "Installs/updates a Workshop item with Workshop ID (no URL required).");
+define('LANG_content_type_help_post_script', "Runs a scripted installer action (no URL required).");
+define('LANG_content_type_help_config_edit', "Edits config at target path using provided action/rules (no URL required).");
?>
diff --git a/Panel/modules/addonsmanager/addons_installer.php b/Panel/modules/addonsmanager/addons_installer.php
index 6ea509ce..b602f5b7 100644
--- a/Panel/modules/addonsmanager/addons_installer.php
+++ b/Panel/modules/addonsmanager/addons_installer.php
@@ -156,7 +156,7 @@ function exec_ogp_module() {
// Use the full category map so newly added types are accepted without
// editing this file. The original three types are always present.
$addon_types = get_server_content_type_keys();
- $addon_type = isset($_REQUEST['addon_type']) ? $_REQUEST['addon_type'] : "";
+ $addon_type = isset($_REQUEST['addon_type']) ? scm_normalize_addon_type($_REQUEST['addon_type']) : "";
$state = isset($_REQUEST['state']) ? $_REQUEST['state'] : "";
$pid = isset($_REQUEST['pid']) ? $_REQUEST['pid'] : -1;
@@ -556,12 +556,6 @@ function exec_ogp_module() {
return;
}
- if ($addon_type === 'workshop') {
- scm_ensure_workshop_schema($db);
- $view->refresh('?m=addonsmanager&p=workshop_content&home_id='.(int)$home_id.'&mod_id='.(int)$mod_id.'&ip='.urlencode((string)$ip).'&port='.urlencode((string)$port), 0);
- return;
- }
-
?>
|
@@ -514,7 +496,6 @@ function exec_ogp_module() {
}
$home_cfg_id = !empty($_GET['home_cfg_id']) && (int)$_GET['home_cfg_id'] > 0 ? (int)$_GET['home_cfg_id'] : 0;
- // Validate the requested addon_type against the full category map so new types are accepted.
$addon_type = !empty($_GET['addon_type']) && in_array($_GET['addon_type'], $addon_types) ? $_GET['addon_type'] : "";
$group_id = isset($_GET['group_id']) && is_numeric($_GET['group_id']) ? (int)$_GET['group_id'] : 0;
diff --git a/Panel/modules/addonsmanager/server_content_categories.php b/Panel/modules/addonsmanager/server_content_categories.php
index 51719ce8..57b0af55 100644
--- a/Panel/modules/addonsmanager/server_content_categories.php
+++ b/Panel/modules/addonsmanager/server_content_categories.php
@@ -40,17 +40,10 @@
function get_server_content_categories()
{
return array(
- // ── Original types (must remain for backward compatibility) ──────────
- 'plugin' => 'Plugins / Mods',
- 'mappack' => 'Map Packs',
- 'config' => 'Config Packs',
-
- // ── Extended types (require addon_type VARCHAR(32) – db_version 2) ──
- 'version' => 'Server Versions', // e.g. Minecraft jar switcher
- 'modpack' => 'Modpacks', // e.g. CurseForge / ATLauncher packs
- 'workshop' => 'Workshop Content', // Steam Workshop item bundles
- 'script' => 'Scripted Installer', // Admin-defined install-only scripts
- 'profile' => 'Server Profiles', // Full profile: configs + mods + scripts
+ 'file_download' => 'File Download / Archive',
+ 'workshop_item' => 'Steam Workshop Item',
+ 'config_edit' => 'Config Edit',
+ 'scripted_installer' => 'Scripted Installer',
);
}
@@ -80,3 +73,34 @@ function get_server_content_type_keys()
{
return array_keys(get_server_content_categories());
}
+
+function scm_get_addon_type_from_install_method($install_method)
+{
+ $install_method = trim((string)$install_method);
+ $map = array(
+ 'download_zip' => 'file_download',
+ 'steam_workshop' => 'workshop_item',
+ 'config_edit' => 'config_edit',
+ 'post_script' => 'scripted_installer',
+ );
+ return isset($map[$install_method]) ? $map[$install_method] : 'file_download';
+}
+
+function scm_normalize_addon_type($addon_type, $install_method = '')
+{
+ $addon_type = trim((string)$addon_type);
+ $categories = get_server_content_categories();
+ if (isset($categories[$addon_type])) {
+ return $addon_type;
+ }
+ if ($addon_type === 'workshop') {
+ return 'workshop_item';
+ }
+ if ($addon_type === 'script') {
+ return 'scripted_installer';
+ }
+ if ($addon_type === 'config') {
+ return 'config_edit';
+ }
+ return scm_get_addon_type_from_install_method($install_method);
+}
diff --git a/Panel/modules/addonsmanager/server_content_helpers.php b/Panel/modules/addonsmanager/server_content_helpers.php
index 2f40f4aa..6cf12074 100644
--- a/Panel/modules/addonsmanager/server_content_helpers.php
+++ b/Panel/modules/addonsmanager/server_content_helpers.php
@@ -255,55 +255,53 @@ function scm_get_cache_mode($db)
function scm_get_install_methods()
{
return array(
- 'download_zip' => 'Compressed file download',
- 'download_file' => 'Direct file download',
- 'steam_workshop' => 'Steam Workshop item',
- 'post_script' => 'Script/action only',
- 'config_edit' => 'Config edit only',
- 'create_folder' => 'Folder/create path only',
+ 'download_zip' => 'File Download / Archive',
+ 'steam_workshop' => 'Steam Workshop Item',
+ 'config_edit' => 'Config Edit',
+ 'post_script' => 'Scripted Installer',
);
}
function scm_get_install_method_help_text()
{
return array(
- 'download_zip' => 'Downloads and extracts an archive into the target path.',
- 'download_file' => 'Downloads a single file to the target path without extraction.',
- 'steam_workshop' => 'Downloads/updates a Steam Workshop item and applies it to the server path.',
- 'post_script' => 'Runs only the post-install script/action body (no download).',
- 'config_edit' => 'Applies config edit rules to a target config file/path.',
- 'create_folder' => 'Creates the target directory path only.',
+ 'download_zip' => 'Downloads an archive or file from URL; extract path is optional.',
+ 'steam_workshop' => 'Installs a Steam Workshop item by Workshop ID without requiring URL.',
+ 'config_edit' => 'Applies config edits to the target file/path without requiring URL.',
+ 'post_script' => 'Runs an installer script/action body without requiring URL.',
);
}
function scm_get_install_method_required_fields()
{
return array(
- 'download_zip' => array('url', 'path'),
- 'download_file' => array('url', 'path'),
- 'steam_workshop' => array('workshop_item_id', 'target_path_template'),
+ 'download_zip' => array('url'),
+ 'steam_workshop' => array('workshop_item_id'),
'post_script' => array('post_script'),
'config_edit' => array('path', 'config_edit_rule'),
- 'create_folder' => array('path'),
);
}
function scm_get_install_method_validation_errors()
{
return array(
- 'url' => 'Please enter a download URL.',
- 'workshop_item_id' => 'Please enter a Workshop ID.',
- 'target_path_template' => 'Please select a target install path.',
- 'post_script' => 'Please enter a script/action body.',
- 'config_edit_rule' => 'Please enter a config edit rule.',
- 'path' => 'Please select a target install path.',
+ 'download_zip' => 'Please enter a download URL.',
+ 'steam_workshop' => 'Please enter a Workshop ID.',
+ 'config_edit' => 'Please enter the config target and edit action.',
+ 'post_script' => 'Please enter the installer script/action.',
);
}
function scm_get_install_method_default($value = '')
{
- $methods = scm_get_install_methods();
$value = trim((string)$value);
+ if ($value === 'download_file') {
+ $value = 'download_zip';
+ }
+ if ($value === 'create_folder') {
+ $value = 'config_edit';
+ }
+ $methods = scm_get_install_methods();
return isset($methods[$value]) ? $value : 'download_zip';
}
@@ -316,10 +314,29 @@ function scm_validate_install_method_payload($install_method, array $payload, &$
$message = 'Invalid install/content type selected.';
return false;
}
+ if ($install_method === 'config_edit') {
+ $path = isset($payload['path']) ? trim((string)$payload['path']) : '';
+ $rule = isset($payload['config_edit_rule']) ? trim((string)$payload['config_edit_rule']) : '';
+ if ($path === '' || $rule === '') {
+ $message = $errors['config_edit'];
+ return false;
+ }
+ $message = '';
+ return true;
+ }
+ if ($install_method === 'post_script') {
+ $script = isset($payload['post_script']) ? trim((string)$payload['post_script']) : '';
+ if ($script === '') {
+ $message = $errors['post_script'];
+ return false;
+ }
+ $message = '';
+ return true;
+ }
foreach ($required[$install_method] as $field) {
$value = isset($payload[$field]) ? trim((string)$payload[$field]) : '';
if ($value === '') {
- $message = isset($errors[$field]) ? $errors[$field] : 'Missing required field.';
+ $message = isset($errors[$install_method]) ? $errors[$install_method] : 'Missing required field.';
return false;
}
}
diff --git a/Panel/modules/addonsmanager/user_addons.php b/Panel/modules/addonsmanager/user_addons.php
index 77b1c2a6..6eb36a47 100644
--- a/Panel/modules/addonsmanager/user_addons.php
+++ b/Panel/modules/addonsmanager/user_addons.php
@@ -60,24 +60,6 @@ function exec_ogp_module() {
foreach ((array)$categories as $type_key => $type_label)
{
- if ($type_key === 'workshop')
- {
- $workshop_count = scm_get_workshop_saved_count($db, (int)$home_id);
- if ($printed_any_cell)
- echo " | \n";
- else
- echo " | \n";
- $printed_any_cell = true;
- echo "" .
- "Workshop Content (" . (int)$workshop_count . ")" .
- "\n";
- continue;
- }
-
$items = $db->resultQuery(
"SELECT DISTINCT addon_id, name, game_name " .
"FROM OGP_DB_PREFIXaddons " .
diff --git a/Panel/modules/administration/panel_update.php b/Panel/modules/administration/panel_update.php
index 808efe72..ddc46849 100644
--- a/Panel/modules/administration/panel_update.php
+++ b/Panel/modules/administration/panel_update.php
@@ -379,19 +379,41 @@ return ['success' => true, 'file' => $archive_file];
function gsp_backup_apache_configs($backup_dir)
{
-$apache_source = '/etc/apache2/sites-available';
-if (!is_dir($apache_source)) {
-return ['success' => false, 'error' => 'Apache sites-available path not found: ' . $apache_source];
+$available_source = '/etc/apache2/sites-available';
+$enabled_source = '/etc/apache2/sites-enabled';
+if (!is_dir($available_source)) {
+return ['success' => false, 'error' => 'Apache sites-available path not found: ' . $available_source];
}
-$dest = $backup_dir . '/apache-sites-available';
-if (!@mkdir($dest, 0755, true) && !is_dir($dest)) {
+$dest = $backup_dir . '/apache-configs';
+$dest_available = $dest . '/sites-available';
+$dest_enabled = $dest . '/sites-enabled';
+if (!@mkdir($dest_available, 0755, true) && !is_dir($dest_available)) {
return ['success' => false, 'error' => 'Cannot create apache backup directory.'];
}
-$files = glob($apache_source . '/*.conf') ?: [];
-foreach ($files as $file) {
-@copy($file, $dest . '/' . basename($file));
+if (!@mkdir($dest_enabled, 0755, true) && !is_dir($dest_enabled)) {
+return ['success' => false, 'error' => 'Cannot create apache enabled backup directory.'];
}
-return ['success' => true, 'path' => $dest, 'count' => count($files)];
+$count = 0;
+foreach ((glob($available_source . '/*.conf') ?: []) as $file) {
+if (@copy($file, $dest_available . '/' . basename($file))) {
+$count++;
+}
+}
+if (is_dir($enabled_source)) {
+foreach ((glob($enabled_source . '/*') ?: []) as $file) {
+$dst = $dest_enabled . '/' . basename($file);
+if (is_link($file)) {
+$target = @readlink($file);
+if ($target !== false) {
+@symlink($target, $dst);
+$count++;
+}
+} elseif (is_file($file) && @copy($file, $dst)) {
+$count++;
+}
+}
+}
+return ['success' => true, 'path' => $dest, 'count' => $count];
}
function gsp_prune_old_backups($max_backups = 5)
@@ -593,6 +615,7 @@ function gsp_copy_tree($src_root, $dst_root, $base_rel = '')
{
$copied = 0;
$skipped = [];
+$copied_files = [];
$source = rtrim($src_root, '/');
$iter = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS),
@@ -613,9 +636,12 @@ continue;
}
if (gsp_copy_file($item->getPathname(), $dst)) {
$copied++;
+if (count($copied_files) < 200) {
+$copied_files[] = $rel;
}
}
-return ['copied' => $copied, 'skipped' => $skipped];
+}
+return ['copied' => $copied, 'skipped' => $skipped, 'copied_files' => $copied_files];
}
function gsp_updater_watch_list()
@@ -694,7 +720,11 @@ function gsp_apply_layout_sync($source_root)
$top_level = scandir($source_root);
$skip = ['.', '..', '.git', '.github', '.gitignore', '.vscode'];
$copied = 0;
+$panel_copied = 0;
+$website_copied = 0;
$skipped = [];
+$copied_files = [];
+gsp_update_log('Layout sync source mapping: ' . $source_root . '/Panel => ' . GSP_PANEL_DIR . ' ; ' . $source_root . '/Website => ' . GSP_WEBSITE_DIR);
foreach ((array)$top_level as $entry) {
if (in_array($entry, $skip, true)) {
continue;
@@ -713,19 +743,32 @@ continue;
}
if (gsp_copy_file($src, $dst)) {
$copied++;
+if (count($copied_files) < 200) {
+$copied_files[] = $rel;
+}
}
continue;
}
if (is_dir($src)) {
$part = gsp_copy_tree($src, GSP_ROOT_DIR, $entry);
$copied += $part['copied'];
+$copied_files = array_merge($copied_files, array_slice((array)$part['copied_files'], 0, max(0, 200 - count($copied_files))));
+if ($entry === 'Panel') {
+$panel_copied += $part['copied'];
+}
+if ($entry === 'Website') {
+$website_copied += $part['copied'];
+}
$skipped = array_merge($skipped, $part['skipped']);
}
}
return [
'success' => true,
'files_copied' => $copied,
+'panel_files_copied' => $panel_copied,
+'website_files_copied' => $website_copied,
'skipped' => array_values(array_unique($skipped)),
+'copied_files' => array_slice(array_values(array_unique($copied_files)), 0, 200),
];
}
@@ -792,13 +835,27 @@ if (!$sync['success']) {
return $sync;
}
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'],
];
}
@@ -885,6 +942,9 @@ gsp_update_log("Update to {$ref} (type={$update_type}) complete");
return [
'success' => true,
'files_copied' => $apply['files_copied'],
+'panel_files_copied' => isset($apply['panel_files_copied']) ? $apply['panel_files_copied'] : 0,
+'website_files_copied' => isset($apply['website_files_copied']) ? $apply['website_files_copied'] : 0,
+'copied_files' => isset($apply['copied_files']) ? $apply['copied_files'] : [],
'backup_dir' => $backup['backup_dir'],
'preserved' => $apply['preserved'],
'patches' => $apply['patches'],
@@ -1016,8 +1076,9 @@ if (!$db_restore['success']) {
gsp_update_log('Revert warning: ' . $db_restore['error']);
}
-if ($restore_apache && is_dir($backup_dir . '/apache-sites-available')) {
-$apache_restore = gsp_restore_apache_backup($backup_dir . '/apache-sites-available', true);
+if ($restore_apache && (is_dir($backup_dir . '/apache-configs') || is_dir($backup_dir . '/apache-sites-available'))) {
+$apache_backup_dir = is_dir($backup_dir . '/apache-configs') ? ($backup_dir . '/apache-configs') : ($backup_dir . '/apache-sites-available');
+$apache_restore = gsp_restore_apache_backup($apache_backup_dir, true);
if (!$apache_restore['success']) {
gsp_update_log('Revert apache restore warning: ' . $apache_restore['error']);
}
@@ -1033,6 +1094,46 @@ gsp_update_log('Revert complete: ' . $backup_ts);
return ['success' => true, 'files_restored' => 0];
}
+function gsp_get_apache_vhost_target_path($filename)
+{
+$name = strtolower((string)$filename);
+if (strpos($name, 'panel.') === 0) {
+return GSP_PANEL_DIR;
+}
+if (strpos($name, 'gameservers.world') !== false) {
+return GSP_WEBSITE_DIR;
+}
+return null;
+}
+
+function gsp_is_apache_stale_path($path)
+{
+$path = trim((string)$path);
+$stale = [
+'/var/www/html/panel',
+'/var/www/html/GSP/Panel/GSP/Panel',
+'/var/www/html/GSP/Panel/modules/billing',
+];
+if (in_array($path, $stale, true)) {
+return true;
+}
+return (strpos($path, '/var/www/html/panel/') === 0 || strpos($path, '/var/www/html/GSP/Panel/modules/billing/') === 0);
+}
+
+function gsp_parse_apache_cert_error_line($line)
+{
+$line = trim((string)$line);
+if (preg_match("/(SSLCertificate(?:File|KeyFile)):\\s*file '([^']+)' (does not exist(?: or is empty)?|is empty)/i", $line, $m)) {
+return [
+'directive' => $m[1],
+'path' => $m[2],
+'reason' => $m[3],
+'line' => $line,
+];
+}
+return null;
+}
+
function gsp_scan_apache_configs()
{
$base = '/etc/apache2/sites-available';
@@ -1042,6 +1143,9 @@ $result = [
'base' => $base,
'files' => [],
'issues' => [],
+'stale_issues' => [],
+'ssl_issues' => [],
+'planned_replacements' => [],
'recommendations' => [],
];
if (!$result['available']) {
@@ -1049,64 +1153,156 @@ $result['success'] = false;
$result['issues'][] = 'Apache sites-available directory not found.';
return $result;
}
-$stale = [
-'/var/www/html/panel',
-'/var/www/html/GSP/Panel/GSP/Panel',
-'/var/www/html/GSP/Panel/modules/billing',
-];
$files = glob($base . '/*.conf') ?: [];
foreach ($files as $file) {
$lines = @file($file, FILE_IGNORE_NEW_LINES);
if (!is_array($lines)) {
continue;
}
-$file_info = ['file' => $file, 'document_roots' => [], 'directories' => [], 'stale_hits' => []];
-foreach ($lines as $line) {
+$vhost = basename($file);
+$target = gsp_get_apache_vhost_target_path($vhost);
+$file_info = [
+'file' => $file,
+'vhost' => $vhost,
+'target' => $target,
+'document_roots' => [],
+'directories' => [],
+'stale_hits' => [],
+'ssl_hits' => [],
+];
+foreach ($lines as $line_number => $line) {
if (preg_match('/^\s*DocumentRoot\s+(.+)$/i', $line, $m)) {
$path = trim($m[1], "\"' ");
$file_info['document_roots'][] = $path;
+if ($target !== null && $path !== $target && gsp_is_apache_stale_path($path)) {
+$msg = $vhost . ' stale DocumentRoot: ' . $path . ' -> ' . $target;
+$result['stale_issues'][] = $msg;
+$result['issues'][] = $msg;
+$result['planned_replacements'][] = ['vhost' => $vhost, 'directive' => 'DocumentRoot', 'from' => $path, 'to' => $target];
+$file_info['stale_hits'][] = $path;
+}
}
if (preg_match('/^\s*/i', $line, $m)) {
$path = trim($m[1], "\"' ");
$file_info['directories'][] = $path;
+if ($target !== null && $path !== $target && gsp_is_apache_stale_path($path)) {
+$msg = $vhost . ' stale : ' . $path . ' -> ' . $target;
+$result['stale_issues'][] = $msg;
+$result['issues'][] = $msg;
+$result['planned_replacements'][] = ['vhost' => $vhost, 'directive' => '', 'from' => $path, 'to' => $target];
+$file_info['stale_hits'][] = $path;
}
-foreach ($stale as $stalePath) {
-if (strpos($line, $stalePath) !== false) {
-$file_info['stale_hits'][] = $stalePath;
-$result['issues'][] = basename($file) . ' contains stale path: ' . $stalePath;
+}
+if (preg_match('/^\s*(SSLCertificate(?:File|KeyFile))\s+(.+)$/i', $line, $m)) {
+$directive = $m[1];
+$path = trim($m[2], "\"' ");
+if ($path !== '' && (!file_exists($path) || @filesize($path) === 0)) {
+$reason = !file_exists($path) ? 'missing' : 'empty';
+$msg = $vhost . ' ' . $directive . ' ' . $path . ' is ' . $reason;
+$issue = ['vhost' => $vhost, 'directive' => $directive, 'path' => $path, 'reason' => $reason, 'line' => ($line_number + 1), 'message' => $msg];
+$result['ssl_issues'][] = $issue;
+$result['issues'][] = $msg;
+$file_info['ssl_hits'][] = $issue;
}
}
}
-if (!empty($file_info['stale_hits'])) {
-if (stripos($file, 'gameservers.world') !== false) {
-$result['recommendations'][] = basename($file) . ' should target ' . GSP_WEBSITE_DIR;
-} else {
-$result['recommendations'][] = basename($file) . ' should target ' . GSP_PANEL_DIR;
-}
+if ($target !== null) {
+$result['recommendations'][] = $vhost . ' should target ' . $target;
}
$result['files'][] = $file_info;
}
+$result['stale_issues'] = array_values(array_unique($result['stale_issues']));
+$result['issues'] = array_values(array_unique($result['issues']));
return $result;
}
+function gsp_apache_configtest_only_cert_failures($configtest_output)
+{
+$lines = preg_split('/\r\n|\r|\n/', (string)$configtest_output);
+$seen = 0;
+foreach ((array)$lines as $line) {
+$line = trim((string)$line);
+if ($line === '') {
+continue;
+}
+if (stripos($line, 'Syntax OK') !== false) {
+continue;
+}
+if (preg_match('/^AH[0-9]+:\s+/i', $line) && stripos($line, 'Could not reliably determine') !== false) {
+continue;
+}
+if (gsp_parse_apache_cert_error_line($line) !== null) {
+$seen++;
+continue;
+}
+return false;
+}
+return $seen > 0;
+}
+
+function gsp_extract_apache_configtest_cert_issues($configtest_output)
+{
+$issues = [];
+$lines = preg_split('/\r\n|\r|\n/', (string)$configtest_output);
+foreach ((array)$lines as $line) {
+$parsed = gsp_parse_apache_cert_error_line($line);
+if ($parsed !== null) {
+$issues[] = $parsed;
+}
+}
+return $issues;
+}
+
function gsp_restore_apache_backup($backup_dir, $reload_apache)
{
-$target = '/etc/apache2/sites-available';
+$available_target = '/etc/apache2/sites-available';
+$enabled_target = '/etc/apache2/sites-enabled';
if (!is_dir($backup_dir)) {
return ['success' => false, 'error' => 'Apache backup folder not found.'];
}
-$files = glob($backup_dir . '/*.conf') ?: [];
-foreach ($files as $file) {
-@copy($file, $target . '/' . basename($file));
+$available_backup = is_dir($backup_dir . '/sites-available') ? $backup_dir . '/sites-available' : $backup_dir;
+$enabled_backup = $backup_dir . '/sites-enabled';
+$restored = 0;
+foreach ((glob($available_backup . '/*.conf') ?: []) as $file) {
+if (@copy($file, $available_target . '/' . basename($file))) {
+$restored++;
+}
+}
+if (is_dir($enabled_backup)) {
+foreach ((glob($enabled_backup . '/*') ?: []) as $file) {
+$dst = $enabled_target . '/' . basename($file);
+if (is_link($dst) || is_file($dst)) {
+@unlink($dst);
+}
+if (is_link($file)) {
+$link_target = @readlink($file);
+if ($link_target !== false) {
+@symlink($link_target, $dst);
+$restored++;
+}
+} elseif (is_file($file) && @copy($file, $dst)) {
+$restored++;
+}
+}
}
$test = gsp_apache_configtest();
if (!$test['success']) {
+if (gsp_apache_configtest_only_cert_failures($test['output'])) {
+return [
+'success' => true,
+'restored' => $restored,
+'configtest' => $test,
+'warnings' => ['Apache config restore completed, but SSL certificate file(s) are missing.'],
+'ssl_issues' => gsp_extract_apache_configtest_cert_issues($test['output']),
+];
+}
return ['success' => false, 'error' => 'apache2ctl configtest failed after restore: ' . $test['output']];
}
+$reload = ['success' => true, 'output' => 'Apache reload skipped'];
if ($reload_apache) {
-gsp_apache_reload();
+$reload = gsp_apache_reload();
}
-return ['success' => true, 'restored' => count($files)];
+return ['success' => true, 'restored' => $restored, 'configtest' => $test, 'reload' => $reload];
}
function gsp_apache_configtest()
@@ -1136,6 +1332,31 @@ 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.'];
@@ -1148,18 +1369,34 @@ return ['success' => false, 'error' => 'Could not create backup before apache fi
$base = '/etc/apache2/sites-available';
$files = glob($base . '/*.conf') ?: [];
-$replace = [
-'/var/www/html/panel' => GSP_PANEL_DIR,
-'/var/www/html/GSP/Panel/GSP/Panel' => GSP_PANEL_DIR,
-'/var/www/html/GSP/Panel/modules/billing' => GSP_WEBSITE_DIR,
-];
$changed = [];
+$planned = [];
foreach ($files as $file) {
$orig = @file_get_contents($file);
if ($orig === false) {
continue;
}
-$new = strtr($orig, $replace);
+$vhost = basename($file);
+$target = gsp_get_apache_vhost_target_path($vhost);
+if ($target === null) {
+continue;
+}
+$new = preg_replace_callback('/(^\s*DocumentRoot\s+)(["\']?)([^"\']+)(\2\s*$)/mi', function ($m) use ($target, $vhost, &$planned) {
+$current = trim((string)$m[3]);
+if ($current === $target || !gsp_is_apache_stale_path($current)) {
+return $m[0];
+}
+$planned[] = ['vhost' => $vhost, 'directive' => 'DocumentRoot', 'from' => $current, 'to' => $target];
+return $m[1] . $m[2] . $target . $m[2];
+}, $orig);
+$new = preg_replace_callback('/(^\s*]+)(\2\s*>)/mi', function ($m) use ($target, $vhost, &$planned) {
+$current = trim((string)$m[3]);
+if ($current === $target || !gsp_is_apache_stale_path($current)) {
+return $m[0];
+}
+$planned[] = ['vhost' => $vhost, 'directive' => '', 'from' => $current, 'to' => $target];
+return $m[1] . $m[2] . $target . $m[2] . '>';
+}, $new);
if ($new !== $orig) {
@file_put_contents($file, $new);
$changed[] = $file;
@@ -1168,13 +1405,27 @@ $changed[] = $file;
$test = gsp_apache_configtest();
if (!$test['success']) {
-$restore = gsp_restore_apache_backup($backup['backup_dir'] . '/apache-sites-available', false);
+$cert_only = gsp_apache_configtest_only_cert_failures($test['output']);
+if (!$cert_only) {
+$restore = gsp_restore_apache_backup($backup['backup_dir'] . '/apache-configs', false);
return [
'success' => false,
'error' => 'apache2ctl configtest failed; restored backup. Output: ' . $test['output']
. ($restore['success'] ? '' : (' | restore failed: ' . $restore['error'])),
];
}
+gsp_update_log('Apache path fix completed but SSL certificates are missing: ' . $test['output']);
+return [
+'success' => true,
+'warning' => 'Apache paths fixed, but SSL certificate is missing.',
+'changed_files' => $changed,
+'backup_dir' => $backup['backup_dir'],
+'configtest' => $test,
+'planned_replacements' => $planned,
+'ssl_issues' => gsp_extract_apache_configtest_cert_issues($test['output']),
+'reload' => ['success' => false, 'output' => 'Apache reload skipped because SSL certificate files are missing.'],
+];
+}
$reload = ['success' => true, 'output' => 'Apache reload skipped'];
if ($reload_apache) {
@@ -1182,12 +1433,19 @@ $reload = gsp_apache_reload();
}
gsp_update_log('Apache path fix changed ' . count($changed) . ' file(s).');
+if (!empty($planned)) {
+$samples = array_slice($planned, 0, 20);
+foreach ($samples as $entry) {
+gsp_update_log('Apache replacement planned/applied: ' . $entry['vhost'] . ' ' . $entry['directive'] . ' ' . $entry['from'] . ' => ' . $entry['to']);
+}
+}
return [
'success' => true,
'changed_files' => $changed,
'backup_dir' => $backup['backup_dir'],
'configtest' => $test,
'reload' => $reload,
+'planned_replacements' => $planned,
];
}
@@ -1293,10 +1551,26 @@ print_failure('Patch application failed: ' . htmlspecialchars($run['error']));
} elseif ($action === 'fix_apache') {
$apache_fix = gsp_fix_apache_paths(true, true);
if ($apache_fix['success']) {
+if (!empty($apache_fix['warning'])) {
+print_success(htmlspecialchars($apache_fix['warning']) . ' Updated files: ' . intval(count($apache_fix['changed_files'])) . '.');
+echo " Renew SSL certificate: certbot --apache -d gameservers.world -d www.gameservers.world \n";
+} else {
print_success('Apache paths fixed successfully. Updated files: ' . intval(count($apache_fix['changed_files'])) . '.');
+}
+if (!empty($apache_fix['configtest']['output'])) {
+echo "apache2ctl configtest output:
" . htmlspecialchars($apache_fix['configtest']['output']) . " \n";
+}
} else {
print_failure('Apache path fix failed: ' . htmlspecialchars($apache_fix['error']));
}
+} elseif ($action === 'disable_ssl_vhost') {
+$vhost = isset($_POST['gsp_ssl_vhost']) ? trim($_POST['gsp_ssl_vhost']) : '';
+$disable = gsp_disable_ssl_vhost($vhost, true);
+if ($disable['success']) {
+print_success(htmlspecialchars(isset($disable['message']) ? $disable['message'] : 'SSL vhost disabled.'));
+} else {
+print_failure('Disable SSL vhost failed: ' . htmlspecialchars($disable['error']));
+}
} elseif ($action === 'backup_only') {
$result = gsp_create_full_backup('backup-only', 'manual', false);
if ($result['success']) {
@@ -1315,7 +1589,7 @@ print_success('Updater files changed and were updated first. Restarting update w
$auto_restart_payload = ['action' => 'update_release', 'nonce' => $result['restart_nonce'], 'version' => $version];
} elseif ($result['success']) {
print_success('Panel updated to release ' . htmlspecialchars($version) . '. '
-. intval($result['files_copied']) . ' file(s) copied.');
+. 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']));
}
@@ -1327,7 +1601,7 @@ print_success('Updater files changed and were updated first. Restarting stable u
$auto_restart_payload = ['action' => 'update_stable', 'nonce' => $result['restart_nonce']];
} elseif ($result['success']) {
print_success('Panel updated to GitHub Stable (' . htmlspecialchars($stable_branch) . '). '
-. intval($result['files_copied']) . ' file(s) copied.');
+. 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']));
}
@@ -1338,7 +1612,7 @@ print_success('Updater files changed and were updated first. Restarting unstable
$auto_restart_payload = ['action' => 'update_unstable', 'nonce' => $result['restart_nonce']];
} elseif ($result['success']) {
print_success('Panel updated to GitHub Unstable (' . htmlspecialchars($unstable_branch) . '). '
-. intval($result['files_copied']) . ' file(s) copied.');
+. 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']));
}
@@ -1438,12 +1712,40 @@ echo "
Apache Configuration Status\n";
echo "\n";
echo "| Config Directory: | " . htmlspecialchars($apache_scan_result['base']) . " | \n";
echo "| Configs Found: | " . intval(count($apache_scan_result['files'])) . " | \n";
-echo "| Stale Path Hits: | " . intval(count($apache_scan_result['issues'])) . " | \n";
+echo "| Stale Path Hits: | " . intval(count($apache_scan_result['stale_issues'])) . " | \n";
+echo "| SSL Certificate Issues: | " . intval(count($apache_scan_result['ssl_issues'])) . " | \n";
echo "| Recommended Panel Path: | " . htmlspecialchars(GSP_PANEL_DIR) . " | \n";
echo "| Recommended Website Path: | " . htmlspecialchars(GSP_WEBSITE_DIR) . " | \n";
echo " \n";
-if (!empty($apache_scan_result['issues'])) {
-echo "Apache path issues: " . implode(' ', array_map('htmlspecialchars', array_unique($apache_scan_result['issues']))) . " \n";
+if (!empty($apache_scan_result['stale_issues'])) {
+echo "Apache stale path issues: " . implode(' ', array_map('htmlspecialchars', array_unique($apache_scan_result['stale_issues']))) . " \n";
+}
+if (!empty($apache_scan_result['planned_replacements'])) {
+echo "Planned replacements: | Vhost | Directive | Current | Replacement | ";
+foreach ((array)$apache_scan_result['planned_replacements'] as $plan_row) {
+echo "| " . htmlspecialchars($plan_row['vhost']) . " | " . htmlspecialchars($plan_row['directive']) . " | " . htmlspecialchars($plan_row['from']) . " | " . htmlspecialchars($plan_row['to']) . " | ";
+}
+echo " ";
+}
+if (!empty($apache_scan_result['ssl_issues'])) {
+echo "SSL certificate issues: ";
+foreach ((array)$apache_scan_result['ssl_issues'] as $ssl_issue) {
+echo htmlspecialchars($ssl_issue['vhost'] . ' ' . $ssl_issue['directive'] . ': ' . $ssl_issue['path'] . ' (' . $ssl_issue['reason'] . ')') . " ";
+}
+echo " \n";
+echo "Renew certificate command: certbot --apache -d gameservers.world -d www.gameservers.world ";
+foreach ((array)$apache_scan_result['ssl_issues'] as $ssl_issue) {
+$vhost = basename((string)$ssl_issue['vhost']);
+if (strpos($vhost, '-ssl.conf') === false) {
+continue;
+}
+echo "";
+}
}
echo " |