diff --git a/Agent-Windows/Install/agent_start.bat b/Agent-Windows/Install/agent_start.bat index d04a11d1..7c2031a9 100644 --- a/Agent-Windows/Install/agent_start.bat +++ b/Agent-Windows/Install/agent_start.bat @@ -1,41 +1,79 @@ @echo off -@title OGP Agent -FOR /f "tokens=2,3,4 delims=[.]" %%a IN ('ver') DO SET WVer=%%a -FOR /f "tokens=2,3 delims= " %%a IN ('echo %WVer%') DO SET Ver=%%a +setlocal EnableExtensions +title GSP Windows Agent + whoami /groups | find "S-1-16-12288" >nul 2>&1 -if NOT %errorLevel% == 0 if %VER% GEQ 6 ( - echo Failure: Current permissions inadequate. - echo[ - echo Run this script by using "Run as administrator" in the context menu. - pause >nul - exit +if not "%errorLevel%" == "0" ( + echo Failure: current permissions are inadequate. + echo. + echo Run this script with "Run as administrator". + pause + exit /b 1 ) -set WD=%~dp0 -pushd %WD% -set path=%WD%bin;%WD%usr\sbin;%path% -set CYGWIN=server ntsec -set SHELL=/bin/bash -set runAgentNormally=no -REM Stop any running agent -if exist %WD%var\run\pure-ftpd.pid set /p PID1=<%WD%var\run\pure-ftpd.pid -if exist %WD%OGP\ogp_agent.pid set /p PID2=<%WD%OGP\ogp_agent.pid -if exist %WD%OGP\ogp_agent_run.pid set /p PID3=<%WD%OGP\ogp_agent_run.pid -IF NOT [%PID1%] == [] kill -15 %PID1% -IF NOT [%PID2%] == [] kill -15 %PID2% -IF NOT [%PID3%] == [] kill -15 %PID3% +set "WD=%~dp0" +pushd "%WD%" >nul 2>&1 -REM Check for gameserver user and if it exists and the user running this script matches, run it the normal way, else prompt for elevation -if "%username%" == "" set runAgentNormally=yes -if "%username%" == "gameserver" set runAgentNormally=yes - -net user gameserver -if %ERRORLEVEL% EQU 0 ( - if %runAgentNormally% == yes ( - bash ogp_agent -pidfile /OGP/ogp_agent_run.pid - ) else ( - cygstart mintty /c "runas /profile /user:gameserver \"%WD%\bin\bash.exe %WD%\bin\ogp_agent -pidfile /OGP/ogp_agent_run.pid\"" +set "CYGWIN_ROOT=%WD%" +if not exist "%CYGWIN_ROOT%OGP\ogp_agent.pl" ( + if exist "%WD%..\OGP\ogp_agent.pl" ( + for %%I in ("%WD%..") do set "CYGWIN_ROOT=%%~fI\" ) -) else ( - bash ogp_agent -pidfile /OGP/ogp_agent_run.pid ) + +set "BASH_EXE=" +if exist "%CYGWIN_ROOT%bin\bash.exe" set "BASH_EXE=%CYGWIN_ROOT%bin\bash.exe" +if not defined BASH_EXE if exist "C:\cygwin64\bin\bash.exe" set "BASH_EXE=C:\cygwin64\bin\bash.exe" +if not defined BASH_EXE if exist "C:\cygwin\bin\bash.exe" set "BASH_EXE=C:\cygwin\bin\bash.exe" + +if not defined BASH_EXE ( + echo Failure: Cygwin bash.exe was not found. + echo. + echo Checked: + echo %CYGWIN_ROOT%bin\bash.exe + echo C:\cygwin64\bin\bash.exe + echo C:\cygwin\bin\bash.exe + pause + exit /b 1 +) + +set "PATH=%CYGWIN_ROOT%bin;%CYGWIN_ROOT%usr\sbin;%PATH%" +set "CYGWIN=server ntsec" +set "SHELL=/bin/bash" + +set "HELPER=/Install/agent_start_cygwin.sh" +if exist "%CYGWIN_ROOT%OGP\Install\agent_start_cygwin.sh" set "HELPER=/OGP/Install/agent_start_cygwin.sh" +if not exist "%CYGWIN_ROOT%Install\agent_start_cygwin.sh" if not exist "%CYGWIN_ROOT%OGP\Install\agent_start_cygwin.sh" ( + echo Failure: agent_start_cygwin.sh was not found. + echo. + echo Expected one of: + echo %CYGWIN_ROOT%Install\agent_start_cygwin.sh + echo %CYGWIN_ROOT%OGP\Install\agent_start_cygwin.sh + pause + exit /b 1 +) + +rem Stop any existing agent processes whose PID files still exist. +if exist "%CYGWIN_ROOT%var\run\pure-ftpd.pid" set /p PID1=<"%CYGWIN_ROOT%var\run\pure-ftpd.pid" +if exist "%CYGWIN_ROOT%OGP\ogp_agent.pid" set /p PID2=<"%CYGWIN_ROOT%OGP\ogp_agent.pid" +if exist "%CYGWIN_ROOT%OGP\ogp_agent_run.pid" set /p PID3=<"%CYGWIN_ROOT%OGP\ogp_agent_run.pid" +if defined PID1 kill -15 %PID1% >nul 2>&1 +if defined PID2 kill -15 %PID2% >nul 2>&1 +if defined PID3 kill -15 %PID3% >nul 2>&1 + +echo Starting GSP Windows Agent with: +echo %BASH_EXE% +echo. +"%BASH_EXE%" --login "%HELPER%" /OGP/ogp_agent_run.pid +set "AGENT_EXIT=%ERRORLEVEL%" + +if not "%AGENT_EXIT%" == "0" ( + echo. + echo GSP Windows Agent exited with error code %AGENT_EXIT%. + echo Review the messages above. The window is staying open so the failure is visible. + pause + exit /b %AGENT_EXIT% +) + +popd >nul 2>&1 +exit /b 0 diff --git a/Agent-Windows/Install/agent_start_cygwin.sh b/Agent-Windows/Install/agent_start_cygwin.sh new file mode 100644 index 00000000..2fc2060e --- /dev/null +++ b/Agent-Windows/Install/agent_start_cygwin.sh @@ -0,0 +1,116 @@ +#!/usr/bin/env bash + +set -u + +AGENT_DIR="/OGP" +PIDFILE="${1:-/OGP/ogp_agent_run.pid}" +PREFS_FILE="$AGENT_DIR/Cfg/bash_prefs.cfg" +REPO_URL_DEFAULT="http://forge.runlevelsystems.com/dev/GSP.git" +REPO_BRANCH_DEFAULT="Panel-unstable" + +warn() { + printf 'WARNING: %s\n' "$*" >&2 +} + +fail() { + printf 'ERROR: %s\n' "$*" >&2 + exit 1 +} + +normalize_text_files() { + local root="$1" + [ -d "$root" ] || return 0 + find "$root" -type f \( -name '*.pl' -o -name '*.pm' -o -name '*.sh' -o -name '*.cfg' \) -print0 2>/dev/null | + while IFS= read -r -d '' file; do + sed -i 's/\r$//' "$file" 2>/dev/null || warn "Could not normalize line endings for $file" + done +} + +load_agent_preferences() { + agent_auto_update=0 + agent_update_repo_url="$REPO_URL_DEFAULT" + agent_update_branch="$REPO_BRANCH_DEFAULT" + + if [ -f "$PREFS_FILE" ]; then + # shellcheck disable=SC1090 + . "$PREFS_FILE" + fi + + agent_auto_update="${agent_auto_update:-0}" + agent_update_repo_url="${agent_update_repo_url:-$REPO_URL_DEFAULT}" + agent_update_branch="${agent_update_branch:-$REPO_BRANCH_DEFAULT}" +} + +auto_update_windows_agent() { + [ "$agent_auto_update" = "1" ] || { + echo "Agent auto-update is disabled." + return 0 + } + + if ! command -v git >/dev/null 2>&1; then + warn "git is not installed; skipping agent auto-update and using the current agent." + return 0 + fi + + local tmp_dir repo_dir source_file target_file backup_file + tmp_dir="$(mktemp -d /tmp/gsp-agent-update.XXXXXX 2>/dev/null)" || { + warn "Could not create temporary update directory; skipping auto-update." + return 0 + } + repo_dir="$tmp_dir/repo" + target_file="$AGENT_DIR/ogp_agent.pl" + backup_file="$AGENT_DIR/ogp_agent.pl.bak.$(date +%Y%m%d%H%M%S)" + + echo "Checking for Windows agent update from $agent_update_repo_url ($agent_update_branch)..." + if ! git clone --depth 1 --branch "$agent_update_branch" "$agent_update_repo_url" "$repo_dir"; then + warn "Agent auto-update clone failed; using the current agent." + rm -rf "$tmp_dir" + return 0 + fi + + source_file="$repo_dir/Agent-Windows/ogp_agent.pl" + if [ ! -f "$source_file" ]; then + warn "Updated Windows agent source was not found at Agent-Windows/ogp_agent.pl; using the current agent." + rm -rf "$tmp_dir" + return 0 + fi + + cp "$target_file" "$backup_file" 2>/dev/null || { + warn "Could not backup $target_file; skipping auto-update." + rm -rf "$tmp_dir" + return 0 + } + + if ! cp "$source_file" "$target_file"; then + warn "Could not copy updated Windows agent; restoring backup." + cp "$backup_file" "$target_file" 2>/dev/null + rm -rf "$tmp_dir" + return 0 + fi + + sed -i 's/\r$//' "$target_file" 2>/dev/null || true + if ! perl -c "$target_file"; then + warn "Updated Windows agent failed perl syntax validation; restoring backup." + cp "$backup_file" "$target_file" 2>/dev/null + perl -c "$target_file" || true + rm -rf "$tmp_dir" + return 0 + fi + + echo "Windows agent auto-update completed." + rm -rf "$tmp_dir" + return 0 +} + +cd "$AGENT_DIR" || fail "Could not enter $AGENT_DIR. Is the Windows agent installed under Cygwin /OGP?" + +normalize_text_files "$AGENT_DIR" +normalize_text_files "/Install" +load_agent_preferences +auto_update_windows_agent + +echo "Validating $AGENT_DIR/ogp_agent.pl..." +perl -c "$AGENT_DIR/ogp_agent.pl" || fail "Perl syntax/dependency validation failed. Install missing Cygwin Perl packages or restore a valid Windows agent file." + +echo "Launching GSP Windows Agent..." +exec perl "$AGENT_DIR/ogp_agent.pl" -pidfile "$PIDFILE" diff --git a/Agent-Windows/ogp_agent.pl b/Agent-Windows/ogp_agent.pl index 72f2ac7f..c2ea2f00 100644 --- a/Agent-Windows/ogp_agent.pl +++ b/Agent-Windows/ogp_agent.pl @@ -749,7 +749,8 @@ sub is_port_listening_without_decrypt my ($server_ip, $port) = @_; return 0 unless(defined($port) && $port =~ /^[0-9]+$/ && $port > 0 && $port <= 65535); my $out = `netstat -an 2>/dev/null`; - return 1 if(defined($out) && $out =~ /[:.]$port\s+.*(LISTENING|UDP)/i); + return 1 if(defined($out) && $out =~ /^\s*TCP\s+\S+[:.]$port\s+\S+\s+LISTENING/im); + return 1 if(defined($out) && $out =~ /^\s*UDP\s+\S+[:.]$port\s+/im); return 0; } @@ -797,7 +798,8 @@ sub server_status_without_decrypt } elsif($game_port_listening) { - $status = "UNRESPONSIVE"; + $status = "ONLINE"; + $ready = 1; $last_error = "Game port is listening but the managed screen session is not running."; } else diff --git a/Agent_Linux/ogp_agent.pl b/Agent_Linux/ogp_agent.pl index e26a9011..1a90a487 100644 --- a/Agent_Linux/ogp_agent.pl +++ b/Agent_Linux/ogp_agent.pl @@ -44,9 +44,6 @@ use Archive::Extract; # Used to handle archived files. use File::Find; use Schedule::Cron; # Used for scheduling tasks -# Database connectivity for resource stats -use DBI; # Database interface for MySQL connection - # Compression tools use IO::Compress::Bzip2 qw(bzip2 $Bzip2Error); # Used to compress files to bz2. use Compress::Zlib; # Used to compress file download buffers to zlib. @@ -957,7 +954,8 @@ sub server_status_without_decrypt } elsif($game_port_listening) { - $status = "UNRESPONSIVE"; + $status = "ONLINE"; + $ready = 1; $last_error = "Game port is listening but the managed screen session is not running."; } else @@ -3188,6 +3186,20 @@ sub mon_stats return "1;$encoded_content"; } +sub ensure_dbi_available +{ + eval { + require DBI; + DBI->import(); + 1; + } and return 1; + + my $error = $@ || "unknown error"; + logger "DBI Perl module unavailable; resource stats database submission is disabled: $error"; + scheduler_log_events("DBI Perl module unavailable; resource stats database submission is disabled"); + return 0; +} + sub submit_resource_stats_to_db { my ($cpu_usage, $mem_used, $mem_total, $mem_percent, $disk_used, $disk_total, $disk_free, $disk_percent, $uptime, $load_1min, $load_5min, $load_15min) = @_; @@ -3202,6 +3214,10 @@ sub submit_resource_stats_to_db return -1; } + if (!ensure_dbi_available()) { + return -1; + } + my $dbh; eval { # Connect to MySQL database diff --git a/Panel/modules/administration/panel_update.php b/Panel/modules/administration/panel_update.php index 75dcd301..b3bae28e 100644 --- a/Panel/modules/administration/panel_update.php +++ b/Panel/modules/administration/panel_update.php @@ -134,6 +134,17 @@ $repo_root = gsp_detect_repo_root(); if (!$repo_root || !function_exists('exec')) { return null; } +$out = []; +$ret = 0; +exec('git -C ' . escapeshellarg($repo_root) . ' rev-parse HEAD 2>/dev/null', $out, $ret); +if ($ret === 0 && !empty($out[0])) { +$sha = trim($out[0]); +if (preg_match('/^[0-9a-f]{40,64}$/i', $sha)) { +return $sha; +} +} +return null; +} function gsp_update_settings() { @@ -169,17 +180,6 @@ $errors[] = 'Panel Path must point to the Panel folder inside Repository Root.'; } return $errors; } -$out = []; -$ret = 0; -exec('git -C ' . escapeshellarg($repo_root) . ' rev-parse HEAD 2>/dev/null', $out, $ret); -if ($ret === 0 && !empty($out[0])) { -$sha = trim($out[0]); -if (preg_match('/^[0-9a-f]{40,64}$/i', $sha)) { -return $sha; -} -} -return null; -} function gsp_read_version_json() { @@ -2254,19 +2254,7 @@ 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 ""; -echo ""; -echo ""; -echo ""; -echo "
"; -} +echo "

SSL certificate issues are diagnostic only and do not block Panel updates.

"; } echo "
\n"; echo "\n"; diff --git a/Panel/modules/gamemanager/home_handling_functions.php b/Panel/modules/gamemanager/home_handling_functions.php index 12575aaf..b509fe3e 100644 --- a/Panel/modules/gamemanager/home_handling_functions.php +++ b/Panel/modules/gamemanager/home_handling_functions.php @@ -55,11 +55,90 @@ function get_agent_server_status($remote, $server_xml, $home_id, $server_ip, $se $rcon_port = (string)$server_xml->rcon_port; } - if (!method_exists($remote, 'remote_server_status')) { - return array('status' => 'UNKNOWN', 'last_error' => 'Panel remote library does not support agent status.'); + $agent_status = array( + 'status' => 'UNKNOWN', + 'ready' => 0, + 'process_running' => 0, + 'session_running' => 0, + 'game_port_listening' => 0, + 'query_port_listening' => 0, + 'rcon_port_listening' => 0, + 'ip' => $server_ip, + 'port' => $server_port, + 'query_port' => $query_port, + 'rcon_port' => $rcon_port, + 'last_error' => 'Panel remote library does not support agent status.', + ); + if (method_exists($remote, 'remote_server_status')) { + $remote_status = $remote->remote_server_status($home_id, $server_ip, $server_port, $query_port, $rcon_port, $startup_timeout, $state_hint); + if (is_array($remote_status)) { + $agent_status = array_merge($agent_status, $remote_status); + } } - return $remote->remote_server_status($home_id, $server_ip, $server_port, $query_port, $rcon_port, $startup_timeout, $state_hint); + $fallback_session = gsp_agent_screen_running($remote, $home_id); + $fallback_port = gsp_agent_port_listening($remote, $server_port); + if ($fallback_session === true) { + $agent_status['session_running'] = 1; + $agent_status['process_running'] = 1; + } + if ($fallback_port === true) { + $agent_status['game_port_listening'] = 1; + } + if ($fallback_session === true || $fallback_port === true || !empty($agent_status['session_running']) || !empty($agent_status['process_running']) || !empty($agent_status['game_port_listening'])) { + $agent_status['status'] = 'ONLINE'; + $agent_status['ready'] = 1; + if (empty($agent_status['last_error']) || $agent_status['last_error'] === 'Agent status RPC unavailable.' || $agent_status['last_error'] === 'Panel remote library does not support agent status.') { + $agent_status['last_error'] = ''; + } + $agent_status['status_source'] = ($fallback_port === true) ? 'port' : 'session'; + return $agent_status; + } + if (isset($agent_status['status']) && strtoupper((string)$agent_status['status']) === 'UNKNOWN') { + if ($fallback_session === false && $fallback_port === false) { + $agent_status['status'] = 'OFFLINE'; + $agent_status['last_error'] = ''; + } + } + return $agent_status; +} + +function gsp_agent_screen_running($remote, $home_id) +{ + if (!method_exists($remote, 'is_screen_running')) { + return null; + } + $result = $remote->is_screen_running(OGP_SCREEN_TYPE_HOME, $home_id); + if ($result === 1) { + return true; + } + if ($result === 0) { + return false; + } + return null; +} + +function gsp_agent_port_listening($remote, $server_port) +{ + $server_port = (int)$server_port; + if ($server_port <= 0 || !method_exists($remote, 'exec')) { + return null; + } + $port_arg = escapeshellarg((string)$server_port); + $command = 'p='.$port_arg.'; ' + . 'if command -v ss >/dev/null 2>&1; then ss -lntu 2>/dev/null; else netstat -an 2>/dev/null; fi ' + . '| tr -d "\r" ' + . '| grep -Eai "(LISTEN|LISTENING|UDP|tcp|udp)" ' + . '| grep -E "[:.]$p([[:space:]]|$)" >/dev/null ' + . '&& echo GSP_PORT_LISTENING || echo GSP_PORT_CLOSED'; + $output = $remote->exec($command); + if (strpos((string)$output, 'GSP_PORT_LISTENING') !== false) { + return true; + } + if (strpos((string)$output, 'GSP_PORT_CLOSED') !== false) { + return false; + } + return null; } function is_agent_status_online($agent_status) diff --git a/Panel/modules/gamemanager/log.php b/Panel/modules/gamemanager/log.php index d1c995dd..38903958 100644 --- a/Panel/modules/gamemanager/log.php +++ b/Panel/modules/gamemanager/log.php @@ -89,31 +89,38 @@ require_once("modules/config_games/server_config_parser.php"); { $log_url = "home.php?m=gamemanager&p=log&type=cleared&refreshed&home_id-mod_id-ip-port=".rawurlencode($_GET['home_id-mod_id-ip-port']); echo ''; - echo ''; + echo '
'.htmlentities($home_log, ENT_QUOTES, "UTF-8").'
'; ?>