server content features improved
This commit is contained in:
parent
a28d3e1a4f
commit
8a56ddc83c
7 changed files with 464 additions and 3 deletions
|
|
@ -11,14 +11,16 @@ $(function() {
|
|||
download_zip: ['#scm-row-url', '#scm-row-path'],
|
||||
steam_workshop: ['#scm-row-workshop-xml-info'],
|
||||
post_script: ['#scm-row-post-script'],
|
||||
config_edit: ['#scm-row-path', '#scm-row-config-edit-rule']
|
||||
config_edit: ['#scm-row-path', '#scm-row-config-edit-rule'],
|
||||
server_app: ['.scm-row-server-app']
|
||||
};
|
||||
var allRows = [
|
||||
'#scm-row-url',
|
||||
'#scm-row-path',
|
||||
'#scm-row-workshop-xml-info',
|
||||
'#scm-row-post-script',
|
||||
'#scm-row-config-edit-rule'
|
||||
'#scm-row-config-edit-rule',
|
||||
'.scm-row-server-app'
|
||||
];
|
||||
var $method = $('#scm-install-method');
|
||||
var $help = $('#scm-install-method-help');
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ function exec_ogp_module() {
|
|||
{
|
||||
$addon_id = (int)$_REQUEST['addon_id'];
|
||||
|
||||
$addons_rows = $db->resultQuery("SELECT url, path, post_script, addon_type, install_method, content_version, requires_stop, restart_after_install, workshop_item_id, workshop_app_id, target_path_template, optional_folder_name, config_edit_rule, launch_param_additions, name FROM OGP_DB_PREFIXaddons WHERE addon_id=".$addon_id.$query_groups);
|
||||
$addons_rows = $db->resultQuery("SELECT url, path, post_script, addon_type, install_method, content_version, requires_stop, restart_after_install, workshop_item_id, workshop_app_id, target_path_template, optional_folder_name, config_edit_rule, launch_param_additions, name, hook_name, hook_enabled, hook_platform, hook_working_dir, hook_start_command, hook_stop_command, hook_start_timing, hook_stop_with_server, hook_watch, hook_critical, hook_kill_game_if_app_exits, hook_restart_app_if_exits, hook_pid_name, hook_app_name, hook_description FROM OGP_DB_PREFIXaddons WHERE addon_id=".$addon_id.$query_groups);
|
||||
if (!is_array($addons_rows)) {
|
||||
$addons_rows = [];
|
||||
}
|
||||
|
|
@ -183,6 +183,15 @@ function exec_ogp_module() {
|
|||
}
|
||||
|
||||
$remote = new OGPRemoteLibrary($home_info['agent_ip'],$home_info['agent_port'],$home_info['encryption_key'],$home_info['timeout']);
|
||||
if ($state == "start") {
|
||||
$gsp_content_base = rtrim((string)$home_info['home_path'], '/\\') . '/_gsp_content';
|
||||
$remote->exec(
|
||||
'mkdir -p ' .
|
||||
scm_remote_shell_quote($gsp_content_base . '/hooks') . ' ' .
|
||||
scm_remote_shell_quote($gsp_content_base . '/generated') . ' ' .
|
||||
scm_remote_shell_quote($gsp_content_base . '/runtime')
|
||||
);
|
||||
}
|
||||
|
||||
$addon_info = $addons_rows[0];
|
||||
$install_method = scm_get_install_method_default(isset($addon_info['install_method']) ? $addon_info['install_method'] : 'download_zip');
|
||||
|
|
@ -393,6 +402,44 @@ function exec_ogp_module() {
|
|||
echo "<p><a href=\"?m=addonsmanager&p=user_addons&home_id=$home_id&mod_id=$mod_id&ip=$ip&port=$port\">".get_lang('back')."</a></p>";
|
||||
return;
|
||||
}
|
||||
if ($install_method === 'server_app') {
|
||||
$hook_message = '';
|
||||
$hook_ok = scm_install_server_app_hook($remote, $home_info, $addon_info, $hook_message);
|
||||
if ($hook_ok) {
|
||||
scm_record_install_done($db, (int)$history_id, 'installed', 0, 'hook_manifest=' . $hook_message);
|
||||
scm_upsert_manifest($db, $home_id, $addon_id, array(
|
||||
'install_method' => $install_method,
|
||||
'content_version' => $content_version,
|
||||
'install_state' => 'installed',
|
||||
'source_url' => '',
|
||||
'installed_by' => $user_id,
|
||||
));
|
||||
scm_log_content_install_action(array(
|
||||
'addon_id' => (int)$addon_id,
|
||||
'addon_name' => isset($addon_info['name']) ? $addon_info['name'] : '',
|
||||
'content_type' => $install_method,
|
||||
'home_id' => (int)$home_id,
|
||||
'home_cfg_id' => (int)$home_info['home_cfg_id'],
|
||||
'target_path' => $hook_message,
|
||||
'action' => 'succeeded',
|
||||
));
|
||||
print_success('Server-side application hook installed.');
|
||||
} else {
|
||||
scm_record_install_done($db, (int)$history_id, 'failed', 1, $hook_message);
|
||||
scm_log_content_install_action(array(
|
||||
'addon_id' => (int)$addon_id,
|
||||
'addon_name' => isset($addon_info['name']) ? $addon_info['name'] : '',
|
||||
'content_type' => $install_method,
|
||||
'home_id' => (int)$home_id,
|
||||
'home_cfg_id' => (int)$home_info['home_cfg_id'],
|
||||
'action' => 'failed',
|
||||
'error' => $hook_message,
|
||||
));
|
||||
print_failure($hook_message);
|
||||
}
|
||||
echo "<p><a href=\"?m=addonsmanager&p=user_addons&home_id=$home_id&mod_id=$mod_id&ip=$ip&port=$port\">".get_lang('back')."</a></p>";
|
||||
return;
|
||||
}
|
||||
if ($install_method === 'config_edit' || $install_method === 'create_folder') {
|
||||
$placeholder_map = scm_build_placeholder_map($home_info, array('exe_location' => (string)$server_xml->exe_location));
|
||||
$target_template = trim((string)$addon_info['path']);
|
||||
|
|
|
|||
|
|
@ -72,6 +72,21 @@ function exec_ogp_module() {
|
|||
$fields['optional_folder_name']= '';
|
||||
$fields['config_edit_rule'] = isset($_POST['config_edit_rule']) ? trim((string)$_POST['config_edit_rule']) : '';
|
||||
$fields['launch_param_additions'] = '';
|
||||
$fields['hook_name'] = isset($_POST['hook_name']) ? trim((string)$_POST['hook_name']) : '';
|
||||
$fields['hook_enabled'] = !empty($_POST['hook_enabled']) ? 1 : 0;
|
||||
$fields['hook_platform'] = scm_normalize_hook_platform(isset($_POST['hook_platform']) ? $_POST['hook_platform'] : 'both');
|
||||
$fields['hook_working_dir'] = isset($_POST['hook_working_dir']) ? trim((string)$_POST['hook_working_dir']) : '';
|
||||
$fields['hook_start_command'] = isset($_POST['hook_start_command']) ? trim((string)$_POST['hook_start_command']) : '';
|
||||
$fields['hook_stop_command'] = isset($_POST['hook_stop_command']) ? trim((string)$_POST['hook_stop_command']) : '';
|
||||
$fields['hook_start_timing'] = scm_normalize_hook_start_timing(isset($_POST['hook_start_timing']) ? $_POST['hook_start_timing'] : 'before_server');
|
||||
$fields['hook_stop_with_server'] = !empty($_POST['hook_stop_with_server']) ? 1 : 0;
|
||||
$fields['hook_watch'] = !empty($_POST['hook_watch']) ? 1 : 0;
|
||||
$fields['hook_critical'] = !empty($_POST['hook_critical']) ? 1 : 0;
|
||||
$fields['hook_kill_game_if_app_exits'] = !empty($_POST['hook_kill_game_if_app_exits']) ? 1 : 0;
|
||||
$fields['hook_restart_app_if_exits'] = !empty($_POST['hook_restart_app_if_exits']) ? 1 : 0;
|
||||
$fields['hook_pid_name'] = isset($_POST['hook_pid_name']) ? trim((string)$_POST['hook_pid_name']) : '';
|
||||
$fields['hook_app_name'] = isset($_POST['hook_app_name']) ? trim((string)$_POST['hook_app_name']) : '';
|
||||
$fields['hook_description'] = isset($_POST['hook_description']) ? trim((string)$_POST['hook_description']) : '';
|
||||
$fields['addon_type'] = scm_get_addon_type_from_install_method($fields['install_method']);
|
||||
if ($fields['install_method'] === 'steam_workshop') {
|
||||
$fields['url'] = '';
|
||||
|
|
@ -102,6 +117,21 @@ function exec_ogp_module() {
|
|||
'target_path_template' => $fields['target_path_template'],
|
||||
'post_script' => $fields['post_script'],
|
||||
'config_edit_rule' => $fields['config_edit_rule'],
|
||||
'hook_name' => $fields['hook_name'],
|
||||
'hook_enabled' => $fields['hook_enabled'],
|
||||
'hook_platform' => $fields['hook_platform'],
|
||||
'hook_working_dir' => $fields['hook_working_dir'],
|
||||
'hook_start_command' => $fields['hook_start_command'],
|
||||
'hook_stop_command' => $fields['hook_stop_command'],
|
||||
'hook_start_timing' => $fields['hook_start_timing'],
|
||||
'hook_stop_with_server' => $fields['hook_stop_with_server'],
|
||||
'hook_watch' => $fields['hook_watch'],
|
||||
'hook_critical' => $fields['hook_critical'],
|
||||
'hook_kill_game_if_app_exits' => $fields['hook_kill_game_if_app_exits'],
|
||||
'hook_restart_app_if_exits' => $fields['hook_restart_app_if_exits'],
|
||||
'hook_pid_name' => $fields['hook_pid_name'],
|
||||
'hook_app_name' => $fields['hook_app_name'],
|
||||
'hook_description' => $fields['hook_description'],
|
||||
);
|
||||
$validation_message = '';
|
||||
if (!scm_validate_install_method_payload($fields['install_method'], $validation_payload, $validation_message))
|
||||
|
|
@ -133,6 +163,21 @@ function exec_ogp_module() {
|
|||
$is_cacheable = isset($_POST['is_cacheable']) ? (int)$_POST['is_cacheable'] : 0;
|
||||
$description = isset($_POST['description']) ? $_POST['description'] : "";
|
||||
$config_edit_rule = isset($_POST['config_edit_rule']) ? $_POST['config_edit_rule'] : "";
|
||||
$hook_name = isset($_POST['hook_name']) ? $_POST['hook_name'] : "";
|
||||
$hook_enabled = isset($_POST['hook_enabled']) ? (int)$_POST['hook_enabled'] : 1;
|
||||
$hook_platform = isset($_POST['hook_platform']) ? $_POST['hook_platform'] : "both";
|
||||
$hook_working_dir = isset($_POST['hook_working_dir']) ? $_POST['hook_working_dir'] : "";
|
||||
$hook_start_command = isset($_POST['hook_start_command']) ? $_POST['hook_start_command'] : "";
|
||||
$hook_stop_command = isset($_POST['hook_stop_command']) ? $_POST['hook_stop_command'] : "";
|
||||
$hook_start_timing = isset($_POST['hook_start_timing']) ? $_POST['hook_start_timing'] : "before_server";
|
||||
$hook_stop_with_server = isset($_POST['hook_stop_with_server']) ? (int)$_POST['hook_stop_with_server'] : 1;
|
||||
$hook_watch = isset($_POST['hook_watch']) ? (int)$_POST['hook_watch'] : 1;
|
||||
$hook_critical = isset($_POST['hook_critical']) ? (int)$_POST['hook_critical'] : 0;
|
||||
$hook_kill_game_if_app_exits = isset($_POST['hook_kill_game_if_app_exits']) ? (int)$_POST['hook_kill_game_if_app_exits'] : 0;
|
||||
$hook_restart_app_if_exits = isset($_POST['hook_restart_app_if_exits']) ? (int)$_POST['hook_restart_app_if_exits'] : 1;
|
||||
$hook_pid_name = isset($_POST['hook_pid_name']) ? $_POST['hook_pid_name'] : "";
|
||||
$hook_app_name = isset($_POST['hook_app_name']) ? $_POST['hook_app_name'] : "";
|
||||
$hook_description = isset($_POST['hook_description']) ? $_POST['hook_description'] : "";
|
||||
|
||||
if (isset($_POST['addon_id']) && (int)$_POST['addon_id'] > 0 && isset($_POST['edit']))
|
||||
{
|
||||
|
|
@ -156,6 +201,21 @@ function exec_ogp_module() {
|
|||
$is_cacheable = isset($addon_info['is_cacheable']) ? (int)$addon_info['is_cacheable'] : 0;
|
||||
$description = isset($addon_info['description']) ? $addon_info['description'] : "";
|
||||
$config_edit_rule = isset($addon_info['config_edit_rule']) ? $addon_info['config_edit_rule'] : "";
|
||||
$hook_name = isset($addon_info['hook_name']) ? $addon_info['hook_name'] : "";
|
||||
$hook_enabled = isset($addon_info['hook_enabled']) ? (int)$addon_info['hook_enabled'] : 1;
|
||||
$hook_platform = isset($addon_info['hook_platform']) ? $addon_info['hook_platform'] : "both";
|
||||
$hook_working_dir = isset($addon_info['hook_working_dir']) ? $addon_info['hook_working_dir'] : "";
|
||||
$hook_start_command = isset($addon_info['hook_start_command']) ? $addon_info['hook_start_command'] : "";
|
||||
$hook_stop_command = isset($addon_info['hook_stop_command']) ? $addon_info['hook_stop_command'] : "";
|
||||
$hook_start_timing = isset($addon_info['hook_start_timing']) ? $addon_info['hook_start_timing'] : "before_server";
|
||||
$hook_stop_with_server = isset($addon_info['hook_stop_with_server']) ? (int)$addon_info['hook_stop_with_server'] : 1;
|
||||
$hook_watch = isset($addon_info['hook_watch']) ? (int)$addon_info['hook_watch'] : 1;
|
||||
$hook_critical = isset($addon_info['hook_critical']) ? (int)$addon_info['hook_critical'] : 0;
|
||||
$hook_kill_game_if_app_exits = isset($addon_info['hook_kill_game_if_app_exits']) ? (int)$addon_info['hook_kill_game_if_app_exits'] : 0;
|
||||
$hook_restart_app_if_exits = isset($addon_info['hook_restart_app_if_exits']) ? (int)$addon_info['hook_restart_app_if_exits'] : 1;
|
||||
$hook_pid_name = isset($addon_info['hook_pid_name']) ? $addon_info['hook_pid_name'] : "";
|
||||
$hook_app_name = isset($addon_info['hook_app_name']) ? $addon_info['hook_app_name'] : "";
|
||||
$hook_description = isset($addon_info['hook_description']) ? $addon_info['hook_description'] : "";
|
||||
}
|
||||
?>
|
||||
<form action="" method="post">
|
||||
|
|
@ -237,6 +297,78 @@ function exec_ogp_module() {
|
|||
<textarea name="config_edit_rule" style="width:99%;height:90px;" placeholder="Text/rules to append or apply to the target config."><?php echo htmlspecialchars($config_edit_rule, ENT_QUOTES, 'UTF-8'); ?></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="scm-row-server-app">
|
||||
<td align="right"><b>Application name</b></td>
|
||||
<td align="left">
|
||||
<input type="text" name="hook_name" value="<?php echo htmlspecialchars($hook_name, ENT_QUOTES, 'UTF-8'); ?>" size="60" placeholder="BEC" />
|
||||
<label style="margin-left:10px;">
|
||||
<input type="checkbox" name="hook_enabled" value="1" <?php echo $hook_enabled ? 'checked' : ''; ?> />
|
||||
Enabled
|
||||
</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="scm-row-server-app">
|
||||
<td align="right"><b>Platform</b></td>
|
||||
<td align="left">
|
||||
<select name="hook_platform">
|
||||
<option value="both" <?php echo $hook_platform === 'both' ? 'selected' : ''; ?>>Both</option>
|
||||
<option value="windows" <?php echo $hook_platform === 'windows' ? 'selected' : ''; ?>>Windows</option>
|
||||
<option value="linux" <?php echo $hook_platform === 'linux' ? 'selected' : ''; ?>>Linux</option>
|
||||
</select>
|
||||
|
||||
<select name="hook_start_timing">
|
||||
<option value="before_server" <?php echo $hook_start_timing === 'before_server' ? 'selected' : ''; ?>>Start before server</option>
|
||||
<option value="after_server" <?php echo $hook_start_timing === 'after_server' ? 'selected' : ''; ?>>Start after server</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="scm-row-server-app">
|
||||
<td align="right"><b>Working directory</b></td>
|
||||
<td align="left">
|
||||
<input type="text" name="hook_working_dir" value="<?php echo htmlspecialchars($hook_working_dir, ENT_QUOTES, 'UTF-8'); ?>" size="85" placeholder="bec" />
|
||||
<small style="color:#666;">Relative to the server home unless an absolute path is required.</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="scm-row-server-app">
|
||||
<td align="right"><b>Start command</b></td>
|
||||
<td align="left">
|
||||
<input type="text" name="hook_start_command" value="<?php echo htmlspecialchars($hook_start_command, ENT_QUOTES, 'UTF-8'); ?>" size="85" placeholder="BEC.exe -f Config.cfg" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="scm-row-server-app">
|
||||
<td align="right"><b>Stop command</b></td>
|
||||
<td align="left">
|
||||
<input type="text" name="hook_stop_command" value="<?php echo htmlspecialchars($hook_stop_command, ENT_QUOTES, 'UTF-8'); ?>" size="85" placeholder="Optional graceful stop command" />
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="scm-row-server-app">
|
||||
<td align="right"><b>PID / app names</b></td>
|
||||
<td align="left">
|
||||
<input type="text" name="hook_pid_name" value="<?php echo htmlspecialchars($hook_pid_name, ENT_QUOTES, 'UTF-8'); ?>" size="35" placeholder="bec" />
|
||||
<input type="text" name="hook_app_name" value="<?php echo htmlspecialchars($hook_app_name, ENT_QUOTES, 'UTF-8'); ?>" size="35" placeholder="BEC.exe" />
|
||||
<small style="color:#666;">Optional names used in runtime PID records and fallback cleanup.</small>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="scm-row-server-app">
|
||||
<td align="right"><b>Lifecycle behavior</b></td>
|
||||
<td align="left">
|
||||
<label><input type="checkbox" name="hook_stop_with_server" value="1" <?php echo $hook_stop_with_server ? 'checked' : ''; ?> /> Stop with server</label>
|
||||
|
||||
<label><input type="checkbox" name="hook_watch" value="1" <?php echo $hook_watch ? 'checked' : ''; ?> /> Watch app</label>
|
||||
|
||||
<label><input type="checkbox" name="hook_restart_app_if_exits" value="1" <?php echo $hook_restart_app_if_exits ? 'checked' : ''; ?> /> Restart app if it exits</label>
|
||||
<br>
|
||||
<label><input type="checkbox" name="hook_critical" value="1" <?php echo $hook_critical ? 'checked' : ''; ?> /> Critical</label>
|
||||
|
||||
<label><input type="checkbox" name="hook_kill_game_if_app_exits" value="1" <?php echo $hook_kill_game_if_app_exits ? 'checked' : ''; ?> /> Kill game if app exits</label>
|
||||
</td>
|
||||
</tr>
|
||||
<tr class="scm-row-server-app">
|
||||
<td align="right"><b>Hook description</b></td>
|
||||
<td align="left">
|
||||
<textarea name="hook_description" style="width:99%;height:55px;" placeholder="Battleye Extended Controls watchdog for DayZ/Arma servers"><?php echo htmlspecialchars($hook_description, ENT_QUOTES, 'UTF-8'); ?></textarea>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="right">
|
||||
<b><?php print_lang('select_game_type'); ?></b>
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ function get_server_content_categories()
|
|||
'file_download' => 'Downloadable Mod',
|
||||
'config_edit' => 'Configuration Package',
|
||||
'scripted_installer' => 'Scripted Installer',
|
||||
'server_app' => 'Server-side Application',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +80,7 @@ function scm_get_addon_type_from_install_method($install_method)
|
|||
'download_zip' => 'file_download',
|
||||
'config_edit' => 'config_edit',
|
||||
'post_script' => 'scripted_installer',
|
||||
'server_app' => 'server_app',
|
||||
);
|
||||
return isset($map[$install_method]) ? $map[$install_method] : 'file_download';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -696,6 +696,7 @@ function scm_get_install_methods()
|
|||
'download_zip' => 'Downloadable Mod',
|
||||
'config_edit' => 'Configuration Package',
|
||||
'post_script' => 'Scripted Installer',
|
||||
'server_app' => 'Server-side Application',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -705,6 +706,7 @@ function scm_get_install_method_help_text()
|
|||
'download_zip' => 'Download and extract a ZIP, RAR, or archive file.',
|
||||
'config_edit' => 'Install configuration files, profiles, or templates.',
|
||||
'post_script' => 'Run a custom scripted installation process.',
|
||||
'server_app' => 'Install a server-side application hook managed by the agent lifecycle.',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -715,6 +717,7 @@ function scm_get_install_method_required_fields()
|
|||
'steam_workshop' => array(), // No required fields; users provide Workshop IDs on their server page
|
||||
'post_script' => array('post_script'),
|
||||
'config_edit' => array('path', 'config_edit_rule'),
|
||||
'server_app' => array('hook_name', 'hook_platform', 'hook_working_dir', 'hook_start_command'),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -724,6 +727,7 @@ function scm_get_install_method_validation_errors()
|
|||
'download_zip' => 'Please enter a download URL.',
|
||||
'config_edit' => 'Please enter the config target and edit action.',
|
||||
'post_script' => 'Please enter the installer script/action.',
|
||||
'server_app' => 'Please enter the application name, platform, working directory, and start command.',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -752,6 +756,21 @@ function scm_get_install_payload_keys()
|
|||
'post_script',
|
||||
'config_edit_rule',
|
||||
'launch_param_additions',
|
||||
'hook_name',
|
||||
'hook_enabled',
|
||||
'hook_platform',
|
||||
'hook_working_dir',
|
||||
'hook_start_command',
|
||||
'hook_stop_command',
|
||||
'hook_start_timing',
|
||||
'hook_stop_with_server',
|
||||
'hook_watch',
|
||||
'hook_critical',
|
||||
'hook_kill_game_if_app_exits',
|
||||
'hook_restart_app_if_exits',
|
||||
'hook_pid_name',
|
||||
'hook_app_name',
|
||||
'hook_description',
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -844,6 +863,83 @@ function scm_validate_configuration_package(array $payload, &$message = '')
|
|||
return true;
|
||||
}
|
||||
|
||||
function scm_normalize_hook_platform($platform)
|
||||
{
|
||||
$platform = strtolower(trim((string)$platform));
|
||||
return in_array($platform, array('windows', 'linux', 'both'), true) ? $platform : 'both';
|
||||
}
|
||||
|
||||
function scm_normalize_hook_start_timing($start_timing)
|
||||
{
|
||||
$start_timing = strtolower(trim((string)$start_timing));
|
||||
return in_array($start_timing, array('before_server', 'after_server'), true) ? $start_timing : 'before_server';
|
||||
}
|
||||
|
||||
function scm_bool_to_int($value)
|
||||
{
|
||||
return !empty($value) ? 1 : 0;
|
||||
}
|
||||
|
||||
function scm_hook_safe_filename($name)
|
||||
{
|
||||
$name = strtolower(trim((string)$name));
|
||||
$name = preg_replace('/[^a-z0-9._-]+/', '_', $name);
|
||||
$name = trim($name, '._-');
|
||||
return $name !== '' ? $name : 'server_app';
|
||||
}
|
||||
|
||||
function scm_validate_server_app_content(array $payload, &$message = '')
|
||||
{
|
||||
$name = isset($payload['hook_name']) ? trim((string)$payload['hook_name']) : '';
|
||||
$working_dir = isset($payload['hook_working_dir']) ? trim((string)$payload['hook_working_dir']) : '';
|
||||
$start_command = isset($payload['hook_start_command']) ? trim((string)$payload['hook_start_command']) : '';
|
||||
if ($name === '' || $working_dir === '' || $start_command === '') {
|
||||
$message = 'Please enter the application name, working directory, and start command.';
|
||||
return false;
|
||||
}
|
||||
if (strpos($working_dir, '..') !== false) {
|
||||
$message = 'Hook working directory must not contain parent-directory traversal.';
|
||||
return false;
|
||||
}
|
||||
$platform = scm_normalize_hook_platform(isset($payload['hook_platform']) ? $payload['hook_platform'] : '');
|
||||
if (!in_array($platform, array('windows', 'linux', 'both'), true)) {
|
||||
$message = 'Hook platform must be Windows, Linux, or Both.';
|
||||
return false;
|
||||
}
|
||||
$message = '';
|
||||
return true;
|
||||
}
|
||||
|
||||
function scm_build_server_app_hook_manifest(array $addon_info)
|
||||
{
|
||||
$name = trim((string)(isset($addon_info['hook_name']) && $addon_info['hook_name'] !== '' ? $addon_info['hook_name'] : (isset($addon_info['name']) ? $addon_info['name'] : 'Server App')));
|
||||
$pid_name = trim((string)(isset($addon_info['hook_pid_name']) ? $addon_info['hook_pid_name'] : ''));
|
||||
$app_name = trim((string)(isset($addon_info['hook_app_name']) ? $addon_info['hook_app_name'] : ''));
|
||||
if ($pid_name === '') {
|
||||
$pid_name = scm_hook_safe_filename($name);
|
||||
}
|
||||
if ($app_name === '') {
|
||||
$app_name = $pid_name;
|
||||
}
|
||||
return array(
|
||||
'name' => $name,
|
||||
'enabled' => !empty($addon_info['hook_enabled']),
|
||||
'platform' => scm_normalize_hook_platform(isset($addon_info['hook_platform']) ? $addon_info['hook_platform'] : 'both'),
|
||||
'working_dir' => trim((string)(isset($addon_info['hook_working_dir']) ? $addon_info['hook_working_dir'] : '')),
|
||||
'start_command' => trim((string)(isset($addon_info['hook_start_command']) ? $addon_info['hook_start_command'] : '')),
|
||||
'stop_command' => trim((string)(isset($addon_info['hook_stop_command']) ? $addon_info['hook_stop_command'] : '')),
|
||||
'start_timing' => scm_normalize_hook_start_timing(isset($addon_info['hook_start_timing']) ? $addon_info['hook_start_timing'] : 'before_server'),
|
||||
'stop_with_server' => !empty($addon_info['hook_stop_with_server']),
|
||||
'watch' => !empty($addon_info['hook_watch']),
|
||||
'critical' => !empty($addon_info['hook_critical']),
|
||||
'kill_game_if_app_exits' => !empty($addon_info['hook_kill_game_if_app_exits']),
|
||||
'restart_app_if_exits' => !empty($addon_info['hook_restart_app_if_exits']),
|
||||
'pid_name' => $pid_name,
|
||||
'app_name' => $app_name,
|
||||
'description' => trim((string)(isset($addon_info['hook_description']) ? $addon_info['hook_description'] : '')),
|
||||
);
|
||||
}
|
||||
|
||||
function scm_validate_install_method_payload($install_method, array $payload, &$message = '')
|
||||
{
|
||||
$install_method = scm_get_install_method_default($install_method);
|
||||
|
|
@ -864,6 +960,9 @@ function scm_validate_install_method_payload($install_method, array $payload, &$
|
|||
if ($install_method === 'config_edit') {
|
||||
return scm_validate_configuration_package($payload, $message);
|
||||
}
|
||||
if ($install_method === 'server_app') {
|
||||
return scm_validate_server_app_content($payload, $message);
|
||||
}
|
||||
$message = '';
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1097,6 +1196,21 @@ function scm_ensure_phase2_schema($db)
|
|||
'max_workshop_ids' => "INT NULL",
|
||||
'required_workshop_ids' => "TEXT NULL",
|
||||
'blocked_workshop_ids' => "TEXT NULL",
|
||||
'hook_name' => "VARCHAR(128) NULL",
|
||||
'hook_enabled' => "TINYINT(1) NOT NULL DEFAULT 1",
|
||||
'hook_platform' => "VARCHAR(16) NOT NULL DEFAULT 'both'",
|
||||
'hook_working_dir' => "VARCHAR(255) NULL",
|
||||
'hook_start_command' => "TEXT NULL",
|
||||
'hook_stop_command' => "TEXT NULL",
|
||||
'hook_start_timing' => "VARCHAR(32) NOT NULL DEFAULT 'before_server'",
|
||||
'hook_stop_with_server' => "TINYINT(1) NOT NULL DEFAULT 1",
|
||||
'hook_watch' => "TINYINT(1) NOT NULL DEFAULT 1",
|
||||
'hook_critical' => "TINYINT(1) NOT NULL DEFAULT 0",
|
||||
'hook_kill_game_if_app_exits' => "TINYINT(1) NOT NULL DEFAULT 0",
|
||||
'hook_restart_app_if_exits' => "TINYINT(1) NOT NULL DEFAULT 1",
|
||||
'hook_pid_name' => "VARCHAR(128) NULL",
|
||||
'hook_app_name' => "VARCHAR(128) NULL",
|
||||
'hook_description' => "TEXT NULL",
|
||||
);
|
||||
foreach ($new_columns as $col => $definition) {
|
||||
$escaped_col = $db->realEscapeSingle($col);
|
||||
|
|
@ -1296,3 +1410,55 @@ function scm_upsert_manifest($db, $home_id, $addon_id, array $fields = array())
|
|||
updated_at = NOW()"
|
||||
);
|
||||
}
|
||||
|
||||
function scm_remote_shell_quote($value)
|
||||
{
|
||||
return "'" . str_replace("'", "'\"'\"'", (string)$value) . "'";
|
||||
}
|
||||
|
||||
function scm_install_server_app_hook($remote, array $home_info, array $addon_info, &$message = '')
|
||||
{
|
||||
$home_path = rtrim((string)(isset($home_info['home_path']) ? $home_info['home_path'] : ''), '/\\');
|
||||
if ($home_path === '') {
|
||||
$message = 'Server home path is missing.';
|
||||
return false;
|
||||
}
|
||||
$manifest = scm_build_server_app_hook_manifest($addon_info);
|
||||
$validation_message = '';
|
||||
if (!scm_validate_server_app_content(array(
|
||||
'hook_name' => $manifest['name'],
|
||||
'hook_platform' => $manifest['platform'],
|
||||
'hook_working_dir' => $manifest['working_dir'],
|
||||
'hook_start_command' => $manifest['start_command'],
|
||||
), $validation_message)) {
|
||||
$message = $validation_message;
|
||||
return false;
|
||||
}
|
||||
|
||||
$json = json_encode($manifest, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
|
||||
if ($json === false) {
|
||||
$message = 'Failed to encode server application hook manifest.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$base = $home_path . '/_gsp_content';
|
||||
$mkdir = 'mkdir -p ' .
|
||||
scm_remote_shell_quote($base . '/hooks') . ' ' .
|
||||
scm_remote_shell_quote($base . '/generated') . ' ' .
|
||||
scm_remote_shell_quote($base . '/runtime');
|
||||
$mkdir_result = $remote->exec($mkdir . ' && echo __GSP_HOOK_DIRS_OK');
|
||||
if (!is_string($mkdir_result) || strpos($mkdir_result, '__GSP_HOOK_DIRS_OK') === false) {
|
||||
$message = 'Failed to create server content hook directories.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$filename = scm_hook_safe_filename($manifest['pid_name'] !== '' ? $manifest['pid_name'] : $manifest['name']) . '.json';
|
||||
$write_path = $base . '/hooks/' . $filename;
|
||||
$write_result = $remote->remote_writefile($write_path, $json . PHP_EOL);
|
||||
if ((int)$write_result !== 1) {
|
||||
$message = 'Failed to write hook manifest to ' . $write_path . '.';
|
||||
return false;
|
||||
}
|
||||
$message = $write_path;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
90
docs/features/SERVER_CONTENT_APPLICATION_HOOKS.md
Normal file
90
docs/features/SERVER_CONTENT_APPLICATION_HOOKS.md
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
# Server Content Application Hooks
|
||||
|
||||
Server Content Application Hooks let installed server content manage companion
|
||||
applications such as BEC, Big Brother Bot, Discord bridges, RCON tools, and log
|
||||
watchers as part of the game server lifecycle.
|
||||
|
||||
The application files may live wherever the game requires them. GSP lifecycle
|
||||
metadata lives under each server home:
|
||||
|
||||
```text
|
||||
_gsp_content/
|
||||
hooks/
|
||||
generated/
|
||||
runtime/
|
||||
```
|
||||
|
||||
## Content Type
|
||||
|
||||
The Server Content Manager includes the `Server-side Application` content type.
|
||||
When this type is installed, the Panel writes a JSON hook manifest to:
|
||||
|
||||
```text
|
||||
_gsp_content/hooks/<app>.json
|
||||
```
|
||||
|
||||
The agents read these manifests during server startup.
|
||||
|
||||
## Hook Manifest
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "BEC",
|
||||
"enabled": true,
|
||||
"platform": "windows",
|
||||
"working_dir": "bec",
|
||||
"start_command": "BEC.exe -f Config.cfg",
|
||||
"stop_command": "",
|
||||
"start_timing": "before_server",
|
||||
"stop_with_server": true,
|
||||
"watch": true,
|
||||
"critical": true,
|
||||
"kill_game_if_app_exits": false,
|
||||
"restart_app_if_exits": true,
|
||||
"pid_name": "bec",
|
||||
"app_name": "BEC.exe",
|
||||
"description": "Battleye Extended Controls watchdog for DayZ/Arma servers"
|
||||
}
|
||||
```
|
||||
|
||||
Supported `platform` values are `windows`, `linux`, and `both`.
|
||||
Supported `start_timing` values are `before_server` and `after_server`.
|
||||
|
||||
## Runtime PID File
|
||||
|
||||
Agents write hook runtime PIDs to:
|
||||
|
||||
```text
|
||||
_gsp_content/runtime/server_content.pids
|
||||
```
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
watchdog|BEC|12345
|
||||
app|BEC|12346
|
||||
```
|
||||
|
||||
The main game server watchdog PID is not stored in this file.
|
||||
|
||||
## Lifecycle
|
||||
|
||||
On server start, the agent:
|
||||
|
||||
1. Creates `_gsp_content/hooks`, `_gsp_content/generated`, and `_gsp_content/runtime`.
|
||||
2. Reads enabled hook manifests matching the agent platform.
|
||||
3. Generates platform-specific hook watchdog scripts.
|
||||
4. Starts `before_server` hooks.
|
||||
5. Starts the game server.
|
||||
6. Starts `after_server` hooks.
|
||||
7. Cleans up hook watchdogs and apps when the game server exits.
|
||||
|
||||
On Panel Stop or Restart, agents kill hook watchdog PIDs first and hook app PIDs
|
||||
second, then continue normal game process and screen/session cleanup.
|
||||
|
||||
## Legacy `_alsoRun.bat`
|
||||
|
||||
Windows `_alsoRun.bat` support remains for compatibility, but it is deprecated.
|
||||
New companion applications should be installed as `Server-side Application`
|
||||
content so both Linux and Windows agents can manage them through the same hook
|
||||
contract.
|
||||
|
|
@ -33,10 +33,31 @@ The module can already represent several content types, including:
|
|||
- downloads/extracted packages
|
||||
- post-script driven installs
|
||||
- config packs
|
||||
- server-side applications with lifecycle hooks
|
||||
- future profile-type content
|
||||
|
||||
Steam Workshop is no longer a user-facing Server Content category. Workshop access belongs to the dedicated `steam_workshop` module.
|
||||
|
||||
## Server-Side Applications
|
||||
|
||||
`Server-side Application` content writes an agent-readable hook manifest under
|
||||
the target game home:
|
||||
|
||||
```text
|
||||
_gsp_content/hooks/<app>.json
|
||||
```
|
||||
|
||||
The agents generate runtime watchdog scripts in `_gsp_content/generated/` and
|
||||
track side-application PIDs in `_gsp_content/runtime/server_content.pids`.
|
||||
|
||||
Use this type for companion applications such as BEC, Big Brother Bot, Discord
|
||||
bridges, RCON tools, and log watchers. The application files themselves may
|
||||
still be installed wherever the game requires them.
|
||||
|
||||
Detailed lifecycle documentation:
|
||||
|
||||
- `docs/features/SERVER_CONTENT_APPLICATION_HOOKS.md`
|
||||
|
||||
## Current Limitations
|
||||
|
||||
- Cache and cleanup policy need a clearer product design.
|
||||
|
|
@ -56,6 +77,7 @@ This module is the right place for:
|
|||
- add-ons
|
||||
- config packs
|
||||
- script-driven installs
|
||||
- server-side companion application hooks
|
||||
- server content manifests
|
||||
- install history
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue