Merge pull request #156 from GameServerPanel/copilot/slow-safe-upgrade-protocols

This commit is contained in:
Frank Harris 2026-05-18 17:22:20 -05:00 committed by GitHub
commit 924a82bc00
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 407 additions and 0 deletions

View file

@ -1,6 +1,7 @@
# Changelog
## 2026-05-18
- **Protocol/image upgrade Phase 1 scaffolding (non-breaking):** Added `protocol/gsp_query.php` normalized query wrapper (LGSL default provider with future-provider placeholders), documented current protocol integration and migration plan in `protocol/PROTOCOL_UPGRADE_REVIEW.md`, and documented image module comparison/unification direction in `modules/SERVER_IMAGE_MODULE_REVIEW.md` without removing LGSL, dsi, or `lgsl_with_img_mod`.
- **Cron ↔ Server Content action hook integration:** Added scheduler-callable Server Content hooks in `modules/addonsmanager/server_content_actions.php`, exposed API route `server_content/run_scheduled_action`, and wired cron/user-cron action builders/parsing to support server content scheduled actions (check/install/queue/restart/validate/backup flows) without embedding game-specific install logic in the scheduler.
- **Server Content Workshop Phase 1 in addonsmanager:** Added a new `Workshop Content` flow under Server Content with per-home Workshop ID storage, ID validation/deduplication, install/update/remove/update-all actions, manifest-based script handoff (`gsp_server_content/workshop_manifest.json`), safe placeholder workshop scripts for Linux/Cygwin, and schema support via `server_content_workshop` plus `addons.addon_type VARCHAR(32)`.
- **Updater layout hardening + pre-update patch framework:** Reworked `modules/administration/panel_update.php` to resolve explicit GSP root/Panel/Website paths, run mandatory preflight checks, self-update updater files before main sync when drift is detected, and apply ordered required patches from `modules/update/patches/` with DB/local state tracking. Backup/rollback now includes both Panel + Website archives and root `version.json`, logs moved to root `logs/update_trace.log`, and the admin Update UI now exposes preflight, patch apply, Apache path scan/fix, backup, update, and rollback actions.

View file

@ -19,3 +19,4 @@
- Add an admin preview/diff panel for Apache path repairs so staff can review exact vhost line changes before confirming `Fix Apache Paths`.
- Add Phase 2 Workshop Content UX in `addonsmanager`: browse/search/select Workshop items with metadata while reusing the Phase 1 per-home saved-ID action pipeline.
- Add localized language strings/tooltips for the new cron scheduler `server_content_*` action labels across all supported panel locales.
- Add a Game Manager "Live Server Status" panel that consumes `Panel/protocol/gsp_query.php` and shows banner preview plus copyable embed code.

View file

@ -0,0 +1,80 @@
# Server Image Module Review (Phase 1)
## Scope reviewed
- `Panel/modules/lgsl_with_img_mod/`
- `Panel/modules/dsi/`
- Integration touchpoints in `Panel/modules/gamemanager/`
## Entry points
### lgsl_with_img_mod
- Module metadata: `Panel/modules/lgsl_with_img_mod/module.php`
- User/admin pages:
- `lgsl.php`
- `lgsl_admin.php`
- Image endpoint:
- `image.php` (reads by `s` argument, supports `img_type`)
- Core query/cache/image logic:
- `lgsl_files/lgsl_class.php`
### dsi
- Module metadata: `Panel/modules/dsi/module.php`
- User/admin/list pages:
- `dsi_user.php`
- `dsi_admin.php`
- `dsi_list.php`
- Image endpoint:
- `image.php` (`modules/dsi/s-IP_PORT-type.png` style)
- Helpers:
- `includes/functions.php`
- `includes/functions_ui.php`
## Comparison
### Which module generates images?
- **Both** generate PNG status banners.
- `lgsl_with_img_mod` is LGSL-centric and built around the `OGP_DB_PREFIXlgsl` cache model.
- `dsi` supports LGSL/GameQ/TS3 monitor includes and can render banner/code snippets directly for panel users.
### Which has better cache support?
- **lgsl_with_img_mod** has deeper cache integration:
- DB-backed query cache (`OGP_DB_PREFIXlgsl.cache`, `cache_time`)
- image file cache handling
- pending/retry semantics in `lgsl_query_cached(...)`
- `dsi` has simple file cache (60s TTL) per generated image and relies on protocol monitor include side effects for query state.
### Which integrates with server monitor better?
- **dsi** is currently more user-facing for “banner + embed code” workflows:
- `dsi_render_table(...)` outputs HTML/BBCode snippets.
- Integrates query handlers by protocol in `dsi/image.php`.
- `lgsl_with_img_mod` is more standalone/legacy LGSL module flow.
### Which supports player info better?
- Both are focused on banner status fields (name/map/players/status).
- Neither is currently a full player-list UI provider for Game Manager.
- Query-level player data comes from monitor protocol paths, not these image modules as a first-class shared API.
### Which is easier to modernize?
- **dsi** is the better base for a future unified GSP banner module:
- simpler structure
- clear image endpoint + code generation UI
- already aware of LGSL/GameQ/TS3 protocol branching
- `lgsl_with_img_mod` contains useful mature cache ideas and map/image utilities worth reusing.
## Recommended future GSP banner direction
Future module target: `Panel/modules/server_status_banner/`
### Plan
1. Keep both current modules in place during migration.
2. Use `dsi` UX flow and embed-code patterns as baseline.
3. Reuse selective `lgsl_with_img_mod` cache + map/image helper ideas.
4. Drive data from normalized query cache (planned `server_query_cache`) and wrapper output.
5. Generate GSP-owned PNG banners (small / wide / large styles).
6. Avoid external GameTracker asset dependency.
7. Provide HTML / BBCode / direct image URL output.
8. Surface banner preview and code tool in Game Manager.
## No-removal statement (Phase 1 safety)
- `lgsl_with_img_mod` was **not removed**.
- `dsi` was **not removed**.
- This phase is documentation and direction-setting only.

View file

@ -0,0 +1,168 @@
# Protocol Upgrade Review (Phase 1, safe path)
## Scope reviewed
- `Panel/protocol/lgsl/`
- `Panel/protocol/GameQ/`
- `Panel/modules/gamemanager/server_monitor.php`
- `Panel/modules/gamemanager/ref_servermonitor.php`
- `Panel/modules/gamemanager/start_server.php`
- `Panel/modules/gamemanager/restart_server.php`
- `Panel/modules/gamemanager/mini_start.php`
- `Panel/modules/dashboard/query_ref.php`
- `Panel/modules/config_games/server_configs/*.xml`
## Current protocol folders
- `Panel/protocol/lgsl/`
- Legacy LGSL implementation and protocol map (`lgsl_protocol.php`).
- Monitor helper (`LGSLMonitor.php`).
- Player list rendering helper (`functions.php`).
- `Panel/protocol/GameQ/`
- Modern namespaced GameQ implementation with PSR-4 autoloader.
- Monitor helper (`GameQMonitor.php`) + player list rendering helper (`functions.php`).
- Very large protocol coverage in `Protocols/`.
- Also contains legacy-looking `gameq/` subtree alongside modern files (technical debt signal).
## How LGSL is currently called
- Monitor refresh path:
- `gamemanager/ref_servermonitor.php` loads `protocol/lgsl/LGSLMonitor.php` when XML protocol is `lgsl`.
- `LGSLMonitor.php` calls `lgsl_query_live(...)`, handles panel query cache (`getServerStatusCache`/`saveServerStatusCache`), and sets `$status/$map/$players/$player_list`.
- Start/restart detection path:
- `gamemanager/start_server.php` and `restart_server.php` call `lgsl_query_live(..., "sa")` after process launch to decide if server is considered running.
- Quick checks:
- `gamemanager/mini_start.php` and other helpers call `lgsl_port_conversion(...)` and `lgsl_query_live(...)`.
- Connection links:
- `server_monitor.php` and dsi/lgsl image flows use `lgsl_port_conversion(...)` and `lgsl_software_link(...)`.
## GameQ status (usable vs incomplete)
- Present and actively used in monitor/start/restart flows (`GameQMonitor.php`, `start_server.php`, `restart_server.php`, `dashboard/query_ref.php`).
- Usable for configured XML entries (`protocol=gameq` + `gameq_query_name`).
- Inconsistencies/risks:
- Mixed API usage exists in module code (`process()` and `requestData()` patterns).
- Legacy and modern GameQ structures coexist under `Panel/protocol/GameQ/`.
- GameQ is integrated, but implementation consistency is incomplete and should be normalized before broad protocol migration.
## Files that map game configs to protocol names
- Primary source of protocol selection:
- `Panel/modules/config_games/server_configs/*.xml` fields:
- `<protocol>`
- `<lgsl_query_name>`
- `<gameq_query_name>`
- Supporting editor/schema surfaces:
- `Panel/modules/config_games/config_servers.php` (schema order includes protocol tags).
- `Panel/modules/config_games/xml_tag_descriptions.php` (documents protocol fields).
- `Panel/modules/config_games/xml_config_creator.php` (protocol selector and query-name population).
## Where player list parsing happens
- LGSL:
- Query payload from `lgsl_query_live(...)` in `lgsl_protocol.php`.
- Player table rendering in `protocol/lgsl/functions.php::print_player_list(...)`.
- GameQ:
- Query payload from `GameQMonitor.php`.
- Player table rendering and field normalization in `protocol/GameQ/functions.php::print_player_list_gameq(...)`.
## Where map/player/server status data is returned
- LGSL live payload shape: `b/s/e/p/t` arrays from `lgsl_query_live(...)`:
- status: `b.status`
- map/name/player counts/password: `s.*`
- player list: `p`
- extras (including some bot fields): `e`
- GameQ normalized payload (after `normalise` filter):
- status: `server.gq_online`
- map: `server.gq_mapname`
- player counts: `server.gq_numplayers`, `server.gq_maxplayers`
- player list: `server.players`
## Known problems (Phase 1 findings)
- Query invocation is duplicated in multiple modules (monitor/start/restart/dashboard/image modules), increasing drift risk.
- Start detection currently combines process + query checks but does not explicitly represent a `starting` state in monitor output.
- LGSL uses hard exits for invalid parameters (`lgsl_query_live`), which is risky for direct callers without guard logic.
- GameQ integration style is not fully standardized across all call sites.
- Existing cache model is spread across current status cache and module-specific image caches, without one normalized query cache contract.
## New wrapper prepared in this phase
- Added `Panel/protocol/gsp_query.php`.
- Introduces `gsp_query_server($server_info, $options = [])` normalized result contract.
- Keeps default provider on LGSL (`lgsl_legacy`) for safety.
- Adds provider concept placeholders (no broad provider switch in this phase):
- `lgsl_legacy`
- `gameq`
- `xpaw_source_query`
- `minecraft_query`
- `custom_script`
## Proposed query cache table (planning only, no migration applied)
```sql
CREATE TABLE IF NOT EXISTS OGP_DB_PREFIXserver_query_cache (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
home_id INT NOT NULL,
ip VARCHAR(64) NOT NULL,
port INT NOT NULL,
query_port INT NULL,
protocol VARCHAR(64) NULL,
provider VARCHAR(64) NULL,
online TINYINT(1) NOT NULL DEFAULT 0,
server_name VARCHAR(255) NULL,
map_name VARCHAR(128) NULL,
players INT NULL,
max_players INT NULL,
bots INT NULL,
passworded TINYINT(1) NULL,
latency_ms INT NULL,
player_list_json MEDIUMTEXT NULL,
raw_json MEDIUMTEXT NULL,
last_query_at DATETIME NULL,
last_success_at DATETIME NULL,
last_error TEXT NULL,
UNIQUE KEY uniq_home_query (home_id),
KEY idx_last_query_at (last_query_at),
KEY idx_online (online)
);
```
Cache TTL target: **60 seconds** default.
## Server start detection improvement plan (no behavior change yet)
- After start command, mark status as **starting**.
- Poll on a short interval until timeout:
1. Check agent process state.
2. Check network port open.
3. Check query response (if protocol supported).
- Final states:
- Process + query OK: **Online**
- Process OK but query unavailable: **Running, query unavailable**
- Process missing at timeout: **Failed to start**
## Game Manager integration plan (`server_monitor.php`)
Future `Live Server Status` panel should include:
- State: Online / Offline / Starting / Running query unavailable
- Server name
- Current map
- Players / max players
- Player list
- Query latency
- Last query time
- Banner preview
- “Get banner code” action
## Admin query debug/test page plan
Future page: `Panel/protocol/query_test.php` (admin-only)
- Inputs: IP, port, query port, protocol, provider, timeout
- Outputs: normalized result, raw result, errors
- Security:
- admin-only access gate
- CSRF for submit actions
- request limits / timeout caps
- no anonymous/public proxy behavior
## Recommended next phase
1. Switch one low-risk monitor path to read `gsp_query_server()` output in parallel with existing behavior (feature-flag style).
2. Standardize GameQ call style (single API usage pattern) and document supported protocol mappings.
3. Add normalized cache write/read adapter (without removing existing caches yet).
4. Add explicit start-state model and timeout policy constants.
5. Begin unified banner module implementation against normalized cache payloads.
## Safety statement
- No protocol engines were removed.
- LGSL remains in place.
- GameQ remains in place.
- Existing server monitor behavior remains intact in this phase.

View file

@ -0,0 +1,157 @@
<?php
/*
* GSP query wrapper (Phase 1 scaffolding)
*
* Normalizes server query results while keeping LGSL as the default provider.
* This file intentionally avoids changing existing monitor paths in this phase.
*/
if (!function_exists('gsp_query_provider_names')) {
function gsp_query_provider_names()
{
return array(
// LGSL remains default for legacy/older game coverage.
'lgsl_legacy',
// TODO: Use for games where current GameQ support is proven reliable.
'gameq',
// TODO: Prefer for modern Source/Steam query games in a later phase.
'xpaw_source_query',
// TODO: Prefer dedicated Minecraft query handling in a later phase.
'minecraft_query',
// TODO: Allow custom scripts for unusual game protocols.
'custom_script',
);
}
}
if (!function_exists('gsp_query_default_result')) {
function gsp_query_default_result()
{
return array(
'success' => false,
'online' => false,
'provider' => 'lgsl_legacy',
'protocol' => '',
'game' => '',
'server_name' => '',
'map' => '',
'players' => 0,
'max_players' => 0,
'bots' => 0,
'passworded' => false,
'latency_ms' => null,
'address' => '',
'port' => 0,
'query_port' => 0,
'player_list' => array(),
'raw' => array(),
'error' => '',
);
}
}
if (!function_exists('gsp_query_normalize_player_list')) {
function gsp_query_normalize_player_list($players)
{
$normalized = array();
foreach ((array)$players as $player) {
$normalized[] = array(
'name' => isset($player['name']) ? (string)$player['name'] : '',
'score' => isset($player['score']) ? (int)$player['score'] : 0,
'time' => isset($player['time']) ? $player['time'] : '',
'ping' => isset($player['ping']) ? (int)$player['ping'] : 0,
'raw' => (array)$player,
);
}
return $normalized;
}
}
if (!function_exists('gsp_query_server')) {
function gsp_query_server($server_info, $options = array())
{
$result = gsp_query_default_result();
$server = (array)$server_info;
$options = (array)$options;
$provider = isset($options['provider']) ? (string)$options['provider'] : (isset($server['query_provider']) ? (string)$server['query_provider'] : 'lgsl_legacy');
$result['provider'] = $provider;
$ip = isset($server['ip']) ? trim((string)$server['ip']) : '';
$port = isset($server['port']) ? (int)$server['port'] : 0;
$query_ip = $ip;
if (!empty($server['use_nat']) && !empty($server['agent_ip'])) {
$query_ip = trim((string)$server['agent_ip']);
}
$result['address'] = ($ip !== '' && $port > 0) ? $ip . ':' . $port : '';
$result['port'] = $port;
if ($provider !== 'lgsl_legacy') {
$result['error'] = "Query provider not implemented yet: {$provider}";
return $result;
}
$query_name = '';
if (isset($server['lgsl_query_name'])) {
$query_name = (string)$server['lgsl_query_name'];
} elseif (isset($server['query_name'])) {
$query_name = (string)$server['query_name'];
}
$query_name = trim($query_name);
$result['protocol'] = $query_name;
if ($query_name === '') {
$result['error'] = 'Missing LGSL query name.';
return $result;
}
if ($query_ip === '' || preg_match("/[^0-9a-z\\.\\-\\[\\]\\:]/i", $query_ip)) {
$result['error'] = 'Invalid query IP/hostname.';
return $result;
}
if ($port <= 0) {
$result['error'] = 'Invalid server port.';
return $result;
}
require_once __DIR__ . '/lgsl/lgsl_protocol.php';
$protocols = lgsl_protocol_list();
if (!isset($protocols[$query_name])) {
$result['error'] = "Unsupported LGSL protocol type: {$query_name}";
return $result;
}
list($c_port, $default_q_port, $s_port) = lgsl_port_conversion($query_name, $port, "", "");
$q_port = isset($server['query_port']) && (int)$server['query_port'] > 0 ? (int)$server['query_port'] : (int)$default_q_port;
$result['query_port'] = $q_port;
if ($q_port <= 0) {
$result['error'] = 'Invalid query port for LGSL query.';
return $result;
}
$raw = lgsl_query_live($query_name, $query_ip, $c_port, $q_port, $s_port, "sep");
if (!is_array($raw) || !isset($raw['b']) || !isset($raw['b']['status'])) {
$result['error'] = 'LGSL query returned an invalid payload.';
return $result;
}
$result['raw'] = $raw;
$result['success'] = true;
$result['online'] = ((string)$raw['b']['status'] === '1' || (int)$raw['b']['status'] === 1);
$result['game'] = isset($raw['s']['game']) ? (string)$raw['s']['game'] : '';
$result['server_name'] = isset($raw['s']['name']) ? (string)$raw['s']['name'] : '';
$result['map'] = isset($raw['s']['map']) ? (string)$raw['s']['map'] : '';
$result['players'] = isset($raw['s']['players']) ? (int)$raw['s']['players'] : 0;
$result['max_players'] = isset($raw['s']['playersmax']) ? (int)$raw['s']['playersmax'] : 0;
$result['bots'] = isset($raw['e']['bots']) ? (int)$raw['e']['bots'] : 0;
$result['passworded'] = !empty($raw['s']['password']);
$result['latency_ms'] = isset($raw['t']['ping']) ? (int)$raw['t']['ping'] : null;
$result['player_list'] = isset($raw['p']) ? gsp_query_normalize_player_list($raw['p']) : array();
return $result;
}
}
?>