logfin expand
This commit is contained in:
parent
e422444b4d
commit
05b7d2e464
5 changed files with 513 additions and 48 deletions
|
|
@ -227,6 +227,8 @@ then
|
|||
sudo_password => '${sudo_password}',
|
||||
web_admin_api_key => '{your_admin_ogp_web_api_key_here}',
|
||||
web_api_url => '{your_url_to_ogp_api.php}',
|
||||
agent_event_url => '{your_url_to_agent_event_receiver.php}',
|
||||
remote_server_id => '{panel_remote_server_id}',
|
||||
steam_dl_limit => '0',
|
||||
# Resource stats database configuration
|
||||
stats_db_host => '${stats_db_host}',
|
||||
|
|
|
|||
52
docs/AGENT_ACTIVITY_EVENTS.md
Normal file
52
docs/AGENT_ACTIVITY_EVENTS.md
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Linux Agent Activity Events
|
||||
|
||||
The Linux agent can report meaningful server lifecycle events to the GSP Panel activity log. It does not send routine XML-RPC chatter, file reads, status polls with no state change, or every local agent log line.
|
||||
|
||||
## Configuration
|
||||
|
||||
Set these values in `Cfg/Config.pm`:
|
||||
|
||||
```perl
|
||||
agent_event_url => 'https://panel.example.com/agent_event_receiver.php',
|
||||
remote_server_id => '1',
|
||||
```
|
||||
|
||||
If `agent_event_url` is empty, the agent attempts to derive it from `web_api_url`. The `key` value must match the Panel remote server encryption key because it is used to sign event payloads.
|
||||
|
||||
## Authentication
|
||||
|
||||
Events are JSON POST requests signed with HMAC-SHA256:
|
||||
|
||||
- `X-GSP-Agent-Id`: Panel `remote_server_id`
|
||||
- `X-GSP-Agent-Timestamp`: Unix timestamp
|
||||
- `X-GSP-Agent-Signature`: `sha256=` plus HMAC of `timestamp.body`
|
||||
|
||||
The Panel validates the signature, remote server identity, event type, severity, and `home_id` ownership before writing the activity log.
|
||||
|
||||
## Offline Queue
|
||||
|
||||
If the Panel is unavailable, the Linux agent appends events to:
|
||||
|
||||
```text
|
||||
events/pending-events.jsonl
|
||||
```
|
||||
|
||||
The queue is retried after later successful event deliveries and rotates when it exceeds 1 MB. Server start, stop, and restart operations do not wait on Panel event delivery.
|
||||
|
||||
## Lifecycle Detection
|
||||
|
||||
The agent uses existing runtime evidence:
|
||||
|
||||
- screen/session status
|
||||
- tracked PID metadata
|
||||
- process existence
|
||||
- required game/query/RCON port validation
|
||||
- short-lived status hints such as `STARTING`, `STOPPING`, and `STOPPED`
|
||||
|
||||
It does not create or read `SERVER_STOPPED` marker files.
|
||||
|
||||
## Reported Events
|
||||
|
||||
The Linux agent reports confirmed start and stop outcomes, unresponsive process states, missing/restored port transitions, unexpected `ONLINE -> OFFLINE` transitions, and scheduled restart sequences when the scheduler invokes restart actions.
|
||||
|
||||
External kills, including a companion bot terminating a server process, are recorded after status polling observes the validated state change.
|
||||
19
docs/PANEL_INTEGRATION.md
Normal file
19
docs/PANEL_INTEGRATION.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# GSP Linux Agent Panel Integration
|
||||
|
||||
Workspace reference: [`GSP-WORKSPACE.md`](../../GSP-WORKSPACE.md)
|
||||
|
||||
The Panel is authoritative. The Linux agent executes the work the Panel requests.
|
||||
|
||||
## Legacy Workshop RPC
|
||||
|
||||
The dedicated Panel `steam_workshop` module still uses the legacy `steam_workshop` XML-RPC call.
|
||||
|
||||
Current compatibility rule:
|
||||
|
||||
- if the Panel sends a blank `config_file_path`, the Linux agent runs post-install logic only
|
||||
- the generated script must skip all `cat` / regex / config-write logic in that case
|
||||
- `WorkshopModsInfo` is still written so the Panel can list/uninstall installed items even when no game config file is edited
|
||||
- new installs write per-home records under `WorkshopModsInfo/home_<home_id>/`
|
||||
- each record stores mod string, Workshop item ID, title, installed folder/path, Workshop App ID, install status, and timestamps
|
||||
- the generated job writes `<game_home>/WorkshopInstallStatus.json` as `running`, `completed`, or `failed`
|
||||
- the generated job prints `GSP_WORKSHOP_INSTALL_COMPLETE` or `GSP_WORKSHOP_INSTALL_FAILED` so the Panel can avoid stale `Update in progress` displays if the generic update screen lingers
|
||||
|
|
@ -32,6 +32,8 @@ sudo bash agent_conf.sh -s "root-password" -u ogp_agent
|
|||
| `listen_port` | TCP port exposed to the panel. Default is `12679`. |
|
||||
| `key` | Shared secret copied from the panel → Administration → Game Servers. |
|
||||
| `web_api_url` | HTTPS URL to `ogp_api.php` on the panel. |
|
||||
| `agent_event_url` | HTTPS URL to `agent_event_receiver.php` on the panel for lifecycle activity-log events. |
|
||||
| `remote_server_id` | Panel remote server ID for this agent. Required for signed lifecycle event delivery. |
|
||||
| `stats_db_*` | Optional MySQL credentials for the resource stats cron. |
|
||||
|
||||
## Service management
|
||||
|
|
@ -81,6 +83,8 @@ Stop escalation now verifies all of the following are cleared before success:
|
|||
3. listening game port
|
||||
|
||||
Lifecycle control no longer uses game-home `SERVER_STOPPED` marker files.
|
||||
|
||||
See `docs/AGENT_ACTIVITY_EVENTS.md` for Panel activity-log event delivery, offline queue behavior, and troubleshooting.
|
||||
Explicit stop intent and autorestart suppression are now controlled through agent-owned runtime status hints (`STOPPING`/`STOPPED`) and verified runtime checks.
|
||||
|
||||
Restart remains stop-first and waits 60 seconds, then re-verifies stop completion before starting again to avoid duplicate instances.
|
||||
|
|
|
|||
484
ogp_agent.pl
484
ogp_agent.pl
|
|
@ -44,6 +44,9 @@ use File::Path qw(mkpath);
|
|||
use Archive::Extract; # Used to handle archived files.
|
||||
use File::Find;
|
||||
use Schedule::Cron; # Used for scheduling tasks
|
||||
use JSON::PP;
|
||||
use Digest::SHA qw(hmac_sha256_hex);
|
||||
use Sys::Hostname qw(hostname);
|
||||
|
||||
# Compression tools
|
||||
use IO::Compress::Bzip2 qw(bzip2 $Bzip2Error); # Used to compress files to bz2.
|
||||
|
|
@ -90,6 +93,10 @@ use constant GAME_STARTUP_DIR =>
|
|||
Path::Class::Dir->new(AGENT_RUN_DIR, 'startups');
|
||||
use constant SERVER_RUNTIME_DIR =>
|
||||
Path::Class::Dir->new(AGENT_RUN_DIR, 'runtime_status');
|
||||
use constant AGENT_EVENTS_DIR =>
|
||||
Path::Class::Dir->new(AGENT_RUN_DIR, 'events');
|
||||
use constant AGENT_EVENT_QUEUE_FILE =>
|
||||
Path::Class::File->new(AGENT_EVENTS_DIR, 'pending-events.jsonl');
|
||||
use constant SCREENRC_FILE =>
|
||||
Path::Class::File->new(AGENT_RUN_DIR, 'ogp_screenrc');
|
||||
use constant SCREENRC_FILE_BK =>
|
||||
|
|
@ -160,6 +167,252 @@ sub logger
|
|||
close(LOGFILE) or die("Failed to close log file.");
|
||||
}
|
||||
|
||||
sub agent_event_panel_url
|
||||
{
|
||||
return $Cfg::Config{agent_event_url} if(defined($Cfg::Config{agent_event_url}) && $Cfg::Config{agent_event_url} ne "");
|
||||
if(defined($Cfg::Config{web_api_url}) && $Cfg::Config{web_api_url} ne "")
|
||||
{
|
||||
my $url = $Cfg::Config{web_api_url};
|
||||
$url =~ s/ogp_api\.php(?:\?.*)?$/agent_event_receiver.php/;
|
||||
return $url if($url =~ /agent_event_receiver\.php$/);
|
||||
$url =~ s/\/+$//;
|
||||
return "$url/agent_event_receiver.php";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
sub agent_event_remote_server_id
|
||||
{
|
||||
return $Cfg::Config{remote_server_id} if(defined($Cfg::Config{remote_server_id}) && $Cfg::Config{remote_server_id} =~ /^\d+$/);
|
||||
return $Cfg::Config{agent_remote_server_id} if(defined($Cfg::Config{agent_remote_server_id}) && $Cfg::Config{agent_remote_server_id} =~ /^\d+$/);
|
||||
return "";
|
||||
}
|
||||
|
||||
sub new_correlation_id
|
||||
{
|
||||
return time() . "-" . $$ . "-" . int(rand(1000000000));
|
||||
}
|
||||
|
||||
sub new_agent_event_uuid
|
||||
{
|
||||
return "agent-" . new_correlation_id();
|
||||
}
|
||||
|
||||
sub build_agent_event
|
||||
{
|
||||
my ($event_type, $severity, $fields) = @_;
|
||||
$fields = {} unless(ref($fields) eq 'HASH');
|
||||
my %event = %$fields;
|
||||
$event{event_uuid} ||= new_agent_event_uuid();
|
||||
$event{timestamp_utc} ||= gmtime() . " UTC";
|
||||
$event{event_type} = $event_type;
|
||||
$event{severity} = $severity;
|
||||
$event{source} = "agent";
|
||||
$event{agent_os} ||= "Linux";
|
||||
$event{agent_hostname} ||= hostname();
|
||||
$event{remote_server_id} ||= agent_event_remote_server_id();
|
||||
$event{correlation_id} ||= new_correlation_id();
|
||||
foreach my $secret_key (qw(password control_password steam_password encryption_key database_password envVars environment_variables))
|
||||
{
|
||||
delete $event{$secret_key};
|
||||
}
|
||||
return \%event;
|
||||
}
|
||||
|
||||
sub agent_event_json
|
||||
{
|
||||
my ($event) = @_;
|
||||
return JSON::PP->new->ascii->canonical->encode($event);
|
||||
}
|
||||
|
||||
sub send_agent_event
|
||||
{
|
||||
my ($event) = @_;
|
||||
my $url = agent_event_panel_url();
|
||||
my $remote_server_id = agent_event_remote_server_id();
|
||||
return 0 if($url eq "" || $remote_server_id eq "" || AGENT_KEY eq "");
|
||||
my $body = agent_event_json($event);
|
||||
my $timestamp = time();
|
||||
my $signature = "sha256=" . hmac_sha256_hex($timestamp . "." . $body, AGENT_KEY);
|
||||
my $ua = LWP::UserAgent->new(timeout => 5, ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0x00 });
|
||||
my $response = $ua->post($url,
|
||||
'Content-Type' => 'application/json',
|
||||
'X-GSP-Agent-Id' => $remote_server_id,
|
||||
'X-GSP-Agent-Timestamp' => $timestamp,
|
||||
'X-GSP-Agent-Signature' => $signature,
|
||||
Content => $body
|
||||
);
|
||||
return ($response && $response->is_success) ? 1 : 0;
|
||||
}
|
||||
|
||||
sub append_agent_event_queue
|
||||
{
|
||||
my ($event) = @_;
|
||||
mkpath(AGENT_EVENTS_DIR) unless(-d AGENT_EVENTS_DIR);
|
||||
if(open(EVENTQ, '>>', AGENT_EVENT_QUEUE_FILE))
|
||||
{
|
||||
flock(EVENTQ, LOCK_EX);
|
||||
print EVENTQ agent_event_json($event) . "\n";
|
||||
flock(EVENTQ, LOCK_UN);
|
||||
close(EVENTQ);
|
||||
}
|
||||
if(-e AGENT_EVENT_QUEUE_FILE && -s AGENT_EVENT_QUEUE_FILE > 1048576)
|
||||
{
|
||||
rename(AGENT_EVENT_QUEUE_FILE, AGENT_EVENT_QUEUE_FILE . "." . time() . ".old");
|
||||
}
|
||||
}
|
||||
|
||||
sub flush_agent_event_queue
|
||||
{
|
||||
return 0 unless(-e AGENT_EVENT_QUEUE_FILE);
|
||||
open(EVENTQ, '<', AGENT_EVENT_QUEUE_FILE) or return 0;
|
||||
my @lines = <EVENTQ>;
|
||||
close(EVENTQ);
|
||||
my @remaining;
|
||||
my $delivered = 0;
|
||||
foreach my $line (@lines)
|
||||
{
|
||||
chomp($line);
|
||||
next if($line eq "");
|
||||
my $event = eval { JSON::PP->new->decode($line) };
|
||||
if($@ || ref($event) ne 'HASH')
|
||||
{
|
||||
next;
|
||||
}
|
||||
if(send_agent_event($event))
|
||||
{
|
||||
$delivered++;
|
||||
}
|
||||
else
|
||||
{
|
||||
push(@remaining, $line);
|
||||
}
|
||||
}
|
||||
if(open(EVENTQ, '>', AGENT_EVENT_QUEUE_FILE))
|
||||
{
|
||||
flock(EVENTQ, LOCK_EX);
|
||||
print EVENTQ join("\n", @remaining) . (@remaining ? "\n" : "");
|
||||
flock(EVENTQ, LOCK_UN);
|
||||
close(EVENTQ);
|
||||
}
|
||||
return $delivered;
|
||||
}
|
||||
|
||||
sub queue_agent_event
|
||||
{
|
||||
my ($event_type, $severity, $fields) = @_;
|
||||
my $event = build_agent_event($event_type, $severity, $fields);
|
||||
if(send_agent_event($event))
|
||||
{
|
||||
flush_agent_event_queue();
|
||||
return 1;
|
||||
}
|
||||
append_agent_event_queue($event);
|
||||
return 0;
|
||||
}
|
||||
|
||||
sub agent_event_state_path
|
||||
{
|
||||
my ($home_id) = @_;
|
||||
$home_id =~ s/[^0-9]//g;
|
||||
return Path::Class::File->new(AGENT_EVENTS_DIR, "state-$home_id.kv");
|
||||
}
|
||||
|
||||
sub read_agent_event_state
|
||||
{
|
||||
my ($home_id) = @_;
|
||||
my %state;
|
||||
my $file = agent_event_state_path($home_id);
|
||||
return \%state unless(-e $file);
|
||||
open(STATE, '<', $file) or return \%state;
|
||||
while(my $line = <STATE>)
|
||||
{
|
||||
chomp($line);
|
||||
next unless($line =~ /^([^=]+)=(.*)$/);
|
||||
$state{$1} = $2;
|
||||
}
|
||||
close(STATE);
|
||||
return \%state;
|
||||
}
|
||||
|
||||
sub write_agent_event_state
|
||||
{
|
||||
my ($home_id, $state) = @_;
|
||||
mkpath(AGENT_EVENTS_DIR) unless(-d AGENT_EVENTS_DIR);
|
||||
my $file = agent_event_state_path($home_id);
|
||||
open(STATE, '>', $file) or return 0;
|
||||
foreach my $key (sort keys %$state)
|
||||
{
|
||||
my $value = defined($state->{$key}) ? $state->{$key} : "";
|
||||
$value =~ s/[\r\n]//g;
|
||||
print STATE "$key=$value\n";
|
||||
}
|
||||
close(STATE);
|
||||
return 1;
|
||||
}
|
||||
|
||||
sub record_server_state_transition
|
||||
{
|
||||
my ($status_info) = @_;
|
||||
return unless(ref($status_info) eq 'HASH');
|
||||
my $home_id = $status_info->{home_id};
|
||||
return unless(defined($home_id) && $home_id =~ /^\d+$/);
|
||||
my $previous = read_agent_event_state($home_id);
|
||||
my $current_status = $status_info->{status} || "UNKNOWN";
|
||||
my $previous_status = $previous->{status} || "";
|
||||
my $now = time();
|
||||
my ($hint_time, $hint) = read_status_hint($home_id);
|
||||
my ($event_type, $severity);
|
||||
if($hint eq "AUTORESTARTING" && !($previous->{autorestart_pending} || 0))
|
||||
{
|
||||
($event_type, $severity) = ("automatic_restart_started", "warning");
|
||||
$previous->{autorestart_pending} = 1;
|
||||
}
|
||||
elsif(($previous->{autorestart_pending} || 0) && $current_status eq "ONLINE")
|
||||
{
|
||||
($event_type, $severity) = ("automatic_restart_succeeded", "info");
|
||||
delete $previous->{autorestart_pending};
|
||||
}
|
||||
elsif(($previous->{autorestart_pending} || 0) && $current_status eq "UNRESPONSIVE")
|
||||
{
|
||||
($event_type, $severity) = ("automatic_restart_failed", "error");
|
||||
delete $previous->{autorestart_pending};
|
||||
}
|
||||
elsif($previous_status eq "ONLINE" && $current_status eq "OFFLINE")
|
||||
{
|
||||
($event_type, $severity) = ($hint eq "STOPPING" || $hint eq "STOPPED") ? ("server_stop_confirmed", "info") : ("server_crash_detected", "warning");
|
||||
}
|
||||
elsif($previous_status ne "ONLINE" && $current_status eq "ONLINE")
|
||||
{
|
||||
($event_type, $severity) = ("server_start_confirmed", "info");
|
||||
}
|
||||
elsif($current_status eq "UNRESPONSIVE" && $previous_status ne "UNRESPONSIVE")
|
||||
{
|
||||
($event_type, $severity) = ("server_unresponsive", "warning");
|
||||
}
|
||||
elsif($current_status eq "WARNING" && $previous_status ne "WARNING")
|
||||
{
|
||||
$event_type = $status_info->{process_running} ? "server_process_found_without_port" : "server_port_found_without_managed_process";
|
||||
$severity = "warning";
|
||||
}
|
||||
elsif(($previous_status eq "WARNING" || $previous_status eq "UNRESPONSIVE") && $current_status eq "ONLINE")
|
||||
{
|
||||
($event_type, $severity) = ("server_port_restored", "notice");
|
||||
}
|
||||
if($event_type)
|
||||
{
|
||||
my $last_sent = $previous->{"sent_$event_type"} || 0;
|
||||
if($previous_status ne $current_status || ($now - $last_sent) > 900)
|
||||
{
|
||||
queue_agent_event($event_type, $severity, $status_info);
|
||||
$previous->{"sent_$event_type"} = $now;
|
||||
}
|
||||
}
|
||||
$previous->{status} = $current_status;
|
||||
$previous->{updated_at} = $now;
|
||||
write_agent_event_state($home_id, $previous);
|
||||
}
|
||||
|
||||
# Rotate the log file
|
||||
if (-e AGENT_LOG_FILE)
|
||||
{
|
||||
|
|
@ -636,6 +889,7 @@ sub create_screen_cmd_loop
|
|||
. "if [ -f \"$status_hint_file\" ] && grep -E \"^[0-9]+,(STOPPING|STOPPED)$\" \"$status_hint_file\" >/dev/null 2>&1; then" . "\n"
|
||||
. "exit 0" . "\n"
|
||||
. "fi" . "\n"
|
||||
. "if [ -n \"$status_hint_file\" ]; then echo \"`date +%s`,AUTORESTARTING\" > \"$status_hint_file\"; fi" . "\n"
|
||||
. "if [ \"\$DIFF\" -gt 15 ]; then" . "\n"
|
||||
. "startServer" . "\n"
|
||||
. "fi" . "\n"
|
||||
|
|
@ -1169,7 +1423,8 @@ sub server_status_without_decrypt
|
|||
clear_pid_metadata($home_id);
|
||||
}
|
||||
|
||||
return {
|
||||
my $status_info = {
|
||||
home_id => $home_id,
|
||||
status => $status,
|
||||
StatusState => $status_state,
|
||||
status_state => $status_state,
|
||||
|
|
@ -1196,9 +1451,12 @@ sub server_status_without_decrypt
|
|||
port => $server_port,
|
||||
query_port => $query_port,
|
||||
rcon_port => $rcon_port,
|
||||
game_port => $server_port,
|
||||
last_error => $last_error,
|
||||
query_info => ""
|
||||
};
|
||||
record_server_state_transition($status_info);
|
||||
return $status_info;
|
||||
}
|
||||
|
||||
# Universal startup function
|
||||
|
|
@ -1430,6 +1688,19 @@ sub universal_start_without_decrypt
|
|||
ip => $server_ip,
|
||||
port => $server_port
|
||||
});
|
||||
queue_agent_event("server_process_started", "info", {
|
||||
home_id => $home_id,
|
||||
game_home_path => $home_path,
|
||||
ip => $server_ip,
|
||||
game_port => $server_port,
|
||||
screen_session_name => $screen_id,
|
||||
tracked_pid => $game_pid,
|
||||
detected_process_pid => $game_pid,
|
||||
actual_process_state => "starting",
|
||||
actual_session_state => $screen_pid ne "" ? "present" : "unknown",
|
||||
actual_port_state => "pending_validation",
|
||||
message => "Server process start command completed; status polling will confirm port readiness."
|
||||
});
|
||||
|
||||
renice_process_without_decrypt($home_id, $nice);
|
||||
|
||||
|
|
@ -2033,6 +2304,16 @@ sub verify_runtime_stop_complete_without_decrypt
|
|||
{
|
||||
clear_pid_metadata($home_id);
|
||||
write_status_hint($home_id, "STOPPED");
|
||||
queue_agent_event("server_stop_confirmed", "info", {
|
||||
home_id => $home_id,
|
||||
ip => $server_ip,
|
||||
game_port => $server_port,
|
||||
tracked_pid => $pid_meta->{game_pid} || $pid_meta->{screen_pid} || "",
|
||||
actual_process_state => "stopped",
|
||||
actual_session_state => "stopped",
|
||||
actual_port_state => "closed",
|
||||
message => "Server stop confirmed after process, session, and port validation."
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
sleep 2;
|
||||
|
|
@ -2051,10 +2332,30 @@ sub verify_runtime_stop_complete_without_decrypt
|
|||
if($session_running || $pid_running || $port_listening)
|
||||
{
|
||||
logger "Server $server_ip:$server_port is still running or listening after stop escalation.";
|
||||
queue_agent_event("server_unresponsive", "error", {
|
||||
home_id => $home_id,
|
||||
ip => $server_ip,
|
||||
game_port => $server_port,
|
||||
tracked_pid => $pid_meta->{game_pid} || $pid_meta->{screen_pid} || "",
|
||||
actual_process_state => $pid_running ? "running" : "stopped",
|
||||
actual_session_state => $session_running ? "running" : "stopped",
|
||||
actual_port_state => $port_listening ? "listening" : "closed",
|
||||
message => "Server remained active after stop escalation."
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
clear_pid_metadata($home_id);
|
||||
write_status_hint($home_id, "STOPPED");
|
||||
queue_agent_event("server_stop_confirmed", "info", {
|
||||
home_id => $home_id,
|
||||
ip => $server_ip,
|
||||
game_port => $server_port,
|
||||
tracked_pid => $pid_meta->{game_pid} || $pid_meta->{screen_pid} || "",
|
||||
actual_process_state => "stopped",
|
||||
actual_session_state => "stopped",
|
||||
actual_port_state => "closed",
|
||||
message => "Server stop confirmed after escalation."
|
||||
});
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -4192,7 +4493,21 @@ sub restart_server_without_decrypt
|
|||
{
|
||||
my ($home_id, $server_ip, $server_port, $control_protocol,
|
||||
$control_password, $control_type, $home_path, $server_exe, $run_dir,
|
||||
$cmd, $cpu, $nice, $preStart, $envVars, $game_key, $console_log) = @_;
|
||||
$cmd, $cpu, $nice, $preStart, $envVars, $game_key, $console_log, $restart_reason) = @_;
|
||||
$restart_reason = "panel_restart" unless(defined($restart_reason) && $restart_reason ne "");
|
||||
my $correlation_id = new_correlation_id();
|
||||
my $restart_started_event = $restart_reason eq "scheduled_restart" ? "scheduled_restart_started" : "automatic_restart_started";
|
||||
my $restart_success_event = $restart_reason eq "scheduled_restart" ? "scheduled_restart_succeeded" : "automatic_restart_succeeded";
|
||||
my $restart_failed_event = $restart_reason eq "scheduled_restart" ? "scheduled_restart_failed" : "automatic_restart_failed";
|
||||
my $report_restart_events = ($restart_reason eq "scheduled_restart" || $restart_reason eq "automatic_restart") ? 1 : 0;
|
||||
queue_agent_event($restart_started_event, "info", {
|
||||
home_id => $home_id,
|
||||
ip => $server_ip,
|
||||
game_port => $server_port,
|
||||
restart_reason => $restart_reason,
|
||||
correlation_id => $correlation_id,
|
||||
message => "Restart operation started."
|
||||
}) if($report_restart_events);
|
||||
|
||||
if (stop_server_without_decrypt($home_id, $server_ip,
|
||||
$server_port, $control_protocol,
|
||||
|
|
@ -4203,20 +4518,54 @@ sub restart_server_without_decrypt
|
|||
if (verify_runtime_stop_complete_without_decrypt($home_id, $server_ip, $server_port) != 0)
|
||||
{
|
||||
logger "Restart cancelled: previous instance is still active after stop wait window.";
|
||||
queue_agent_event($restart_failed_event, "error", {
|
||||
home_id => $home_id,
|
||||
ip => $server_ip,
|
||||
game_port => $server_port,
|
||||
restart_reason => $restart_reason,
|
||||
correlation_id => $correlation_id,
|
||||
message => "Restart cancelled because previous instance was still active."
|
||||
}) if($report_restart_events);
|
||||
return -2;
|
||||
}
|
||||
if (universal_start_without_decrypt($home_id, $home_path, $server_exe, $run_dir,
|
||||
$cmd, $server_port, $server_ip, $cpu, $nice, $preStart, $envVars, $game_key, $console_log) == 1)
|
||||
{
|
||||
queue_agent_event($restart_success_event, "info", {
|
||||
home_id => $home_id,
|
||||
game_home_path => $home_path,
|
||||
ip => $server_ip,
|
||||
game_port => $server_port,
|
||||
restart_reason => $restart_reason,
|
||||
correlation_id => $correlation_id,
|
||||
message => "Restart command completed; status polling will confirm process and port readiness."
|
||||
}) if($report_restart_events);
|
||||
return 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
queue_agent_event($restart_failed_event, "error", {
|
||||
home_id => $home_id,
|
||||
game_home_path => $home_path,
|
||||
ip => $server_ip,
|
||||
game_port => $server_port,
|
||||
restart_reason => $restart_reason,
|
||||
correlation_id => $correlation_id,
|
||||
message => "Restart failed because the server start path returned an error."
|
||||
}) if($report_restart_events);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
queue_agent_event($restart_failed_event, "error", {
|
||||
home_id => $home_id,
|
||||
ip => $server_ip,
|
||||
game_port => $server_port,
|
||||
restart_reason => $restart_reason,
|
||||
correlation_id => $correlation_id,
|
||||
message => "Restart failed because the server could not be stopped."
|
||||
}) if($report_restart_events);
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
|
@ -5381,7 +5730,7 @@ sub scheduler_server_action
|
|||
elsif($action eq "%ACTION=restart")
|
||||
{
|
||||
my ($home_id, $ip, $port) = ($server_args[0], $server_args[1], $server_args[2]);
|
||||
my $ret = restart_server_without_decrypt(@server_args);
|
||||
my $ret = restart_server_without_decrypt(@server_args, "scheduled_restart");
|
||||
if($ret == 1)
|
||||
{
|
||||
scheduler_log_events("Restarted server home ID $home_id on address $ip:$port");
|
||||
|
|
@ -6074,7 +6423,7 @@ sub steam_workshop_without_decrypt
|
|||
$regex, $mods_backreference_index,
|
||||
$variable, $place_after, $mod_string,
|
||||
$string_separator, $config_file_path,
|
||||
$post_install, $mod_names_list);
|
||||
$post_install, $mod_names_list, $home_id);
|
||||
|
||||
my $bash_scripts_path = MANUAL_TMP_DIR . "/home_id_" . $home_id;
|
||||
|
||||
|
|
@ -6099,19 +6448,25 @@ sub generate_post_install_scripts
|
|||
$regex, $mods_backreference_index,
|
||||
$variable, $place_after, $mod_string,
|
||||
$string_separator, $config_file_path,
|
||||
$post_install, $mod_names_list) = @_;
|
||||
$post_install, $mod_names_list, $home_id) = @_;
|
||||
|
||||
my $post_install_scripts = "";
|
||||
my $mods_info_path = Path::Class::Dir->new(AGENT_RUN_DIR, 'WorkshopModsInfo');
|
||||
$post_install_scripts .= "mods_full_path=\"$mods_full_path\"\n".
|
||||
"workshop_id=\"$workshop_id\"\n".
|
||||
"home_id=\"$home_id\"\n".
|
||||
"regex=\"$regex\"\n".
|
||||
"mods_backreference_index=\"$mods_backreference_index\"\n".
|
||||
"variable=\"$variable\"\n".
|
||||
"place_after=\"$place_after\"\n".
|
||||
"string_separator=\"$string_separator\"\n".
|
||||
"config_file_path=\"$config_file_path\"\n".
|
||||
"mods_info_path=\"$mods_info_path/\"\n";
|
||||
"mods_info_path=\"$mods_info_path/\"\n".
|
||||
"mods_info_home_path=\"$mods_info_path/home_$home_id/\"\n".
|
||||
"workshop_status_path=\"$mods_full_path/WorkshopInstallStatus.json\"\n".
|
||||
"write_workshop_status(){ status=\"\$1\"; message=\"\$2\"; now=\"\$(date '+%Y-%m-%d %H:%M:%S')\"; message_json=\$(printf '%s' \"\$message\"|sed 's/\\\\/\\\\\\\\/g;s/\"/\\\\\"/g'); printf '{\"operation\":\"steam_workshop\",\"home_id\":\"%s\",\"workshop_app_id\":\"%s\",\"status\":\"%s\",\"message\":\"%s\",\"updated_at\":\"%s\"}\\n' \"\$home_id\" \"\$workshop_id\" \"\$status\" \"\$message_json\" \"\$now\" > \"\$workshop_status_path\"; }\n".
|
||||
"write_workshop_status running \"Workshop install running\"\n".
|
||||
"trap 'rc=\$?; if [ \$rc -ne 0 ]; then write_workshop_status failed \"Workshop install failed\"; echo GSP_WORKSHOP_INSTALL_FAILED; fi' EXIT\n";
|
||||
my @workshop_mods = split /,/, $mods_list;
|
||||
my @mod_names = split /,/, $mod_names_list;
|
||||
|
||||
|
|
@ -6121,6 +6476,10 @@ sub generate_post_install_scripts
|
|||
my $steamcmd_download_path = '/steamapps/workshop/content/'.$workshop_id.'/'.$workshop_mod_id.'/';
|
||||
my $workshop_mod_path = $mods_full_path.$steamcmd_download_path;
|
||||
my $this_mod_string = $mod_string;
|
||||
if(!defined $this_mod_string || $this_mod_string eq '')
|
||||
{
|
||||
$this_mod_string = '%workshop_mod_id%';
|
||||
}
|
||||
$this_mod_string =~ s/\%workshop_mod_id\%/$workshop_mod_id/g;
|
||||
|
||||
$post_install_scripts .= "mod_string[$index]=\"$this_mod_string\"\n".
|
||||
|
|
@ -6130,11 +6489,7 @@ sub generate_post_install_scripts
|
|||
$index++;
|
||||
}
|
||||
|
||||
$post_install_scripts .= 'if [ ! -e $config_file_path ];then'."\n".
|
||||
' if [ ! -d "$(dirname $config_file_path)" ];then mkdir -p "$(dirname $config_file_path)";fi'."\n".
|
||||
' echo -e "${place_after}\n${variable}" > $config_file_path'."\n".
|
||||
'fi'."\n".
|
||||
'i=0'."\n".
|
||||
$post_install_scripts .= 'i=0'."\n".
|
||||
'for mod_id in "${workshop_mod_id[@]}"'."\n".
|
||||
'do'."\n".
|
||||
' first_file="$(ls "${workshop_mod_path[$i]}"| sort -n | head -1)"'."\n";
|
||||
|
|
@ -6149,46 +6504,66 @@ sub generate_post_install_scripts
|
|||
}
|
||||
}
|
||||
|
||||
$post_install_scripts .= ' file_content=$(cat $config_file_path)'."\n".
|
||||
' if [[ $file_content =~ $regex ]]; then'."\n".
|
||||
' full_match="${BASH_REMATCH[0]}"'."\n".
|
||||
' mods_match="${BASH_REMATCH[$mods_backreference_index]}"'."\n".
|
||||
' found=1'."\n".
|
||||
' else'."\n".
|
||||
' found=0'."\n".
|
||||
' fi'."\n".
|
||||
' first_file_string="\%first_file%"'."\n".
|
||||
|
||||
$post_install_scripts .= ' first_file_string="\%first_file%"'."\n".
|
||||
' if [ -z "${mod_string[$i]##*$first_file_string*}" ];then'."\n".
|
||||
' mod_string[$i]="${mod_string[$i]/$first_file_string/$first_file}"'."\n".
|
||||
' fi'."\n".
|
||||
' if [ $found == 1 ] && [ "X$full_match" != "X" ];then'."\n".
|
||||
' if [ "X$mods_match" == "X" ];then'."\n".
|
||||
' new_mods=$(echo -e "${full_match}${mod_string[$i]}")'."\n".
|
||||
' echo -e "${file_content/$full_match/$new_mods}">"$config_file_path"'."\n".
|
||||
' else'."\n".
|
||||
' if [ ! -z "${mods_match##*${mod_string[$i]}*}" ];then'."\n".
|
||||
' new_mods=$(echo -e "${full_match}${string_separator}${mod_string[$i]}")'."\n".
|
||||
' echo -e "${file_content/$full_match/$new_mods}">"$config_file_path"'."\n".
|
||||
' fi'."\n".
|
||||
' fi'."\n".
|
||||
' else'."\n".
|
||||
' if [ "X$place_after" == "X" ];then'."\n".
|
||||
' echo -e "${file_content}${variable}${mod_string[$i]}">"$config_file_path"'."\n".
|
||||
' else'."\n".
|
||||
' if [ -z "${file_content##*${place_after}*}" ];then'."\n".
|
||||
' new_var="${variable}${mod_string[$i]}"'."\n".
|
||||
' place_after_esc=$(echo -e "$place_after"|sed -e \'s/[]\\/$*.^[]/\\\\&/g\')'."\n".
|
||||
' echo -e "$file_content"|sed \'/\'$place_after_esc\'/a \'$new_var>"$config_file_path"'."\n".
|
||||
' else'."\n".
|
||||
' echo -e "${file_content}${place_after}\n${variable}${mod_string[$i]}">"$config_file_path"'."\n".
|
||||
' fi'."\n".
|
||||
' fi'."\n".
|
||||
' fi'."\n".
|
||||
' fi'."\n";
|
||||
|
||||
if(defined $config_file_path && $config_file_path ne '')
|
||||
{
|
||||
$post_install_scripts .= ' if [ ! -e "$config_file_path" ];then'."\n".
|
||||
' if [ ! -d "$(dirname "$config_file_path")" ];then mkdir -p "$(dirname "$config_file_path")";fi'."\n".
|
||||
' echo -e "${place_after}\n${variable}" > "$config_file_path"'."\n".
|
||||
' fi'."\n".
|
||||
' file_content=$(cat "$config_file_path")'."\n".
|
||||
' if [[ $file_content =~ $regex ]]; then'."\n".
|
||||
' full_match="${BASH_REMATCH[0]}"'."\n".
|
||||
' mods_match="${BASH_REMATCH[$mods_backreference_index]}"'."\n".
|
||||
' found=1'."\n".
|
||||
' else'."\n".
|
||||
' found=0'."\n".
|
||||
' fi'."\n".
|
||||
' if [ $found == 1 ] && [ "X$full_match" != "X" ];then'."\n".
|
||||
' if [ "X$mods_match" == "X" ];then'."\n".
|
||||
' new_mods=$(echo -e "${full_match}${mod_string[$i]}")'."\n".
|
||||
' echo -e "${file_content/$full_match/$new_mods}">"$config_file_path"'."\n".
|
||||
' else'."\n".
|
||||
' if [ ! -z "${mods_match##*${mod_string[$i]}*}" ];then'."\n".
|
||||
' new_mods=$(echo -e "${full_match}${string_separator}${mod_string[$i]}")'."\n".
|
||||
' echo -e "${file_content/$full_match/$new_mods}">"$config_file_path"'."\n".
|
||||
' fi'."\n".
|
||||
' fi'."\n".
|
||||
' else'."\n".
|
||||
' if [ "X$place_after" == "X" ];then'."\n".
|
||||
' echo -e "${file_content}${variable}${mod_string[$i]}">"$config_file_path"'."\n".
|
||||
' else'."\n".
|
||||
' if [ -z "${file_content##*${place_after}*}" ];then'."\n".
|
||||
' new_var="${variable}${mod_string[$i]}"'."\n".
|
||||
' place_after_esc=$(echo -e "$place_after"|sed -e \'s/[]\\/$*.^[]/\\\\&/g\')'."\n".
|
||||
' echo -e "$file_content"|sed \'/\'$place_after_esc\'/a \'$new_var>"$config_file_path"'."\n".
|
||||
' else'."\n".
|
||||
' echo -e "${file_content}${place_after}\n${variable}${mod_string[$i]}">"$config_file_path"'."\n".
|
||||
' fi'."\n".
|
||||
' fi'."\n".
|
||||
' fi'."\n";
|
||||
}
|
||||
|
||||
$post_install_scripts .=
|
||||
' if [ ! -d "${mods_info_path}" ];then mkdir -p "${mods_info_path}";fi'."\n".
|
||||
' echo "${mod_name[$i]}" > "${mods_info_path}${mod_string[$i]}.ogpmod"'."\n".
|
||||
' if [ ! -d "${mods_info_home_path}" ];then mkdir -p "${mods_info_home_path}";fi'."\n".
|
||||
' installed_path=""'."\n".
|
||||
' if [ -e "$mods_full_path/${mod_string[$i]}" ];then installed_path="$mods_full_path/${mod_string[$i]}";fi'."\n".
|
||||
' if [ -z "$installed_path" ] && [ -e "$mods_full_path/@$mod_id" ];then installed_path="$mods_full_path/@$mod_id";fi'."\n".
|
||||
' if [ -z "$installed_path" ] && [ -e "$mods_full_path/$mod_id" ];then installed_path="$mods_full_path/$mod_id";fi'."\n".
|
||||
' installed_folder="$(basename "$installed_path")"'."\n".
|
||||
" install_time=\"\$(date '+%Y-%m-%d %H:%M:%S')\"\n".
|
||||
' printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "${mod_string[$i]}" "$mod_id" "${mod_name[$i]}" "$installed_folder" "$installed_path" "$workshop_id" "installed" "$install_time" "$install_time" > "${mods_info_home_path}${mod_id}.ogpmod"'."\n".
|
||||
' printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n" "${mod_string[$i]}" "$mod_id" "${mod_name[$i]}" "$installed_folder" "$installed_path" "$workshop_id" "installed" "$install_time" "$install_time" > "${mods_info_path}${mod_string[$i]}.ogpmod"'."\n".
|
||||
' i=$(expr $i + 1)'."\n".
|
||||
'done'."\n";
|
||||
'done'."\n".
|
||||
'write_workshop_status completed "Workshop install completed"'."\n".
|
||||
'trap - EXIT'."\n".
|
||||
'echo GSP_WORKSHOP_INSTALL_COMPLETE'."\n";
|
||||
return "$post_install_scripts";
|
||||
}
|
||||
|
||||
|
|
@ -6196,7 +6571,13 @@ sub get_workshop_mods_info()
|
|||
{
|
||||
return "Bad Encryption Key" unless(decrypt_param(pop(@_)) eq "Encryption checking OK");
|
||||
|
||||
my ($home_id) = decrypt_params(@_);
|
||||
my $mods_info_dir_path = Path::Class::Dir->new(AGENT_RUN_DIR, 'WorkshopModsInfo');
|
||||
if(defined $home_id && $home_id =~ /^\d+$/)
|
||||
{
|
||||
my $home_mods_info_dir_path = Path::Class::Dir->new($mods_info_dir_path, "home_$home_id");
|
||||
$mods_info_dir_path = $home_mods_info_dir_path if(-d $home_mods_info_dir_path);
|
||||
}
|
||||
|
||||
if(-d $mods_info_dir_path)
|
||||
{
|
||||
|
|
@ -6214,7 +6595,14 @@ sub get_workshop_mods_info()
|
|||
if($row ne "")
|
||||
{
|
||||
my ($string_name, $ext) = split(/\.ogp/, $mod_info_file);
|
||||
push @mods_info, "$string_name:$row";
|
||||
if(index($row, "\t") >= 0)
|
||||
{
|
||||
push @mods_info, $row;
|
||||
}
|
||||
else
|
||||
{
|
||||
push @mods_info, "$string_name:$row";
|
||||
}
|
||||
}
|
||||
close($fh);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue