worshop work
This commit is contained in:
parent
0d44c65ea5
commit
3829a4a83d
92 changed files with 487 additions and 110 deletions
|
|
@ -16,14 +16,12 @@ Phase 1 adds manual Workshop ID support inside the existing `addonsmanager` modu
|
|||
- Update All
|
||||
- Panel generates a per-server manifest at:
|
||||
- `%home_path%/gsp_server_content/workshop_manifest.json`
|
||||
- Panel runs an approved handler only, never a user-supplied command/path.
|
||||
- If a game does not define a custom Workshop script, the panel stages the
|
||||
bundled generic handler for the agent OS:
|
||||
- Linux: `generic_steam_workshop_linux.sh`
|
||||
- Windows/Cygwin: `generic_steam_workshop_windows_cygwin.sh`
|
||||
- If a custom configured script is missing on the agent, the panel falls back
|
||||
to the bundled generic handler and logs a warning instead of failing with
|
||||
"script not found."
|
||||
- Panel generates an approved per-job handler only, never a user-supplied command/path.
|
||||
- The generated job is written under:
|
||||
- `%home_path%/gsp_server_content/jobs/workshop/workshop_job_<timestamp>_<random>.sh`
|
||||
- The generated job writes a temporary SteamCMD runscript and invokes SteamCMD with `+runscript`.
|
||||
- Static Workshop script paths in XML are deprecated for the primary workflow.
|
||||
- The agent does not need `generic_steam_workshop_linux.sh` or `generic_steam_workshop_windows_cygwin.sh` to exist on disk.
|
||||
|
||||
## Security model
|
||||
|
||||
|
|
@ -51,10 +49,7 @@ and keeps `OGP_DB_PREFIXaddons.addon_type` at `VARCHAR(32)` so `workshop` is val
|
|||
Each game should define and document:
|
||||
|
||||
- `workshop_app_id`
|
||||
- optional Linux workshop script path only when the bundled generic handler is
|
||||
not sufficient
|
||||
- optional Windows/Cygwin workshop script path only when the bundled generic
|
||||
handler is not sufficient
|
||||
- install strategy and target path
|
||||
- target install location
|
||||
- restart/update behavior
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import os
|
|||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
manifest_path = os.path.abspath(sys.argv[1])
|
||||
|
|
@ -192,14 +193,16 @@ try:
|
|||
if action in ('install', 'update', 'check_updates', 'download_only', 'validate_files'):
|
||||
if not workshop_app_id:
|
||||
fail(f"Workshop App ID is missing for Workshop item {workshop_id}.")
|
||||
command = [
|
||||
steamcmd_path,
|
||||
'+force_install_dir', server_root,
|
||||
'+login', 'anonymous',
|
||||
'+workshop_download_item', workshop_app_id, workshop_id, 'validate',
|
||||
'+quit',
|
||||
]
|
||||
log(f"workshop_id={workshop_id} steamcmd={' '.join(command)}", 'Downloading Workshop Item')
|
||||
fd, runscript_path = tempfile.mkstemp(prefix=f"steamcmd_workshop_{workshop_id}_", suffix=".txt", dir=manifest_dir, text=True)
|
||||
with os.fdopen(fd, 'w', encoding='utf-8') as script_handle:
|
||||
script_handle.write("@ShutdownOnFailedCommand 0\n")
|
||||
script_handle.write("@NoPromptForPassword 1\n")
|
||||
script_handle.write(f"force_install_dir {server_root}\n")
|
||||
script_handle.write("login anonymous\n")
|
||||
script_handle.write(f"workshop_download_item {workshop_app_id} {workshop_id} validate\n")
|
||||
script_handle.write("quit\n")
|
||||
command = [steamcmd_path, '+runscript', runscript_path]
|
||||
log(f"workshop_id={workshop_id} runscript={runscript_path}", 'Downloading Workshop Item')
|
||||
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, cwd=server_root)
|
||||
if result.stdout:
|
||||
for line in result.stdout.splitlines():
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import os
|
|||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
|
||||
manifest_path = os.path.abspath(sys.argv[1])
|
||||
|
|
@ -193,14 +194,16 @@ try:
|
|||
if action in ('install', 'update', 'check_updates', 'download_only', 'validate_files'):
|
||||
if not workshop_app_id:
|
||||
fail(f"Workshop App ID is missing for Workshop item {workshop_id}.")
|
||||
command = [
|
||||
steamcmd_path,
|
||||
'+force_install_dir', server_root,
|
||||
'+login', 'anonymous',
|
||||
'+workshop_download_item', workshop_app_id, workshop_id, 'validate',
|
||||
'+quit',
|
||||
]
|
||||
log(f"workshop_id={workshop_id} steamcmd={' '.join(command)}", 'Downloading Workshop Item')
|
||||
fd, runscript_path = tempfile.mkstemp(prefix=f"steamcmd_workshop_{workshop_id}_", suffix=".txt", dir=manifest_dir, text=True)
|
||||
with os.fdopen(fd, 'w', encoding='utf-8') as script_handle:
|
||||
script_handle.write("@ShutdownOnFailedCommand 0\n")
|
||||
script_handle.write("@NoPromptForPassword 1\n")
|
||||
script_handle.write(f"force_install_dir {server_root}\n")
|
||||
script_handle.write("login anonymous\n")
|
||||
script_handle.write(f"workshop_download_item {workshop_app_id} {workshop_id} validate\n")
|
||||
script_handle.write("quit\n")
|
||||
command = [steamcmd_path, '+runscript', runscript_path]
|
||||
log(f"workshop_id={workshop_id} runscript={runscript_path}", 'Downloading Workshop Item')
|
||||
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, cwd=server_root)
|
||||
if result.stdout:
|
||||
for line in result.stdout.splitlines():
|
||||
|
|
|
|||
|
|
@ -507,8 +507,8 @@ function scm_is_legacy_panel_workshop_script_path($script_path)
|
|||
function scm_get_agent_managed_workshop_script_path(array $home_info)
|
||||
{
|
||||
$home_path = rtrim(clean_path((string)$home_info['home_path']), '/');
|
||||
$filename = scm_is_windows_home($home_info) ? SCM_WORKSHOP_SCRIPT_WINDOWS_DEFAULT : SCM_WORKSHOP_SCRIPT_LINUX_DEFAULT;
|
||||
$remote_path = clean_path($home_path . '/gsp_server_content/scripts/workshop/' . $filename);
|
||||
$filename = 'workshop_job_' . date('Ymd_His') . '_' . mt_rand(1000, 9999) . '.sh';
|
||||
$remote_path = clean_path($home_path . '/gsp_server_content/jobs/workshop/' . $filename);
|
||||
if (!scm_path_is_under_home($home_path, $remote_path)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -520,20 +520,17 @@ function scm_prepare_workshop_script_for_agent($remote, array $home_info, $serve
|
|||
$error = '';
|
||||
$configured_path = scm_get_configured_workshop_script_path($home_info, $server_xml);
|
||||
if ($configured_path !== '' && !scm_is_default_workshop_script_name($configured_path) && !scm_is_legacy_panel_workshop_script_path($configured_path)) {
|
||||
if ((int)$remote->rfile_exists($configured_path) === 1) {
|
||||
return $configured_path;
|
||||
}
|
||||
scm_log_content_install_action(array(
|
||||
'type' => 'workshop_script_fallback',
|
||||
'type' => 'workshop_script_deprecated',
|
||||
'home_id' => isset($home_info['home_id']) ? (int)$home_info['home_id'] : 0,
|
||||
'configured_path' => $configured_path,
|
||||
'message' => 'Configured workshop script was not found on the agent; falling back to bundled generic handler.',
|
||||
'message' => 'Configured static Workshop script ignored; Server Content generates a per-job script and runs it through the generic agent exec path.',
|
||||
));
|
||||
}
|
||||
|
||||
$source_path = scm_get_bundled_workshop_script_source($home_info);
|
||||
if (!is_file($source_path)) {
|
||||
$error = 'Bundled workshop script is missing from the panel: ' . $source_path;
|
||||
$error = 'Panel Workshop job template is missing: ' . $source_path;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -545,7 +542,7 @@ function scm_prepare_workshop_script_for_agent($remote, array $home_info, $serve
|
|||
|
||||
$script_body = @file_get_contents($source_path);
|
||||
if ($script_body === false || $script_body === '') {
|
||||
$error = 'Failed to read bundled workshop script: ' . $source_path;
|
||||
$error = 'Failed to read Panel Workshop job template: ' . $source_path;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,26 +75,29 @@ $emptyXml = simplexml_load_string('<game_config></game_config>');
|
|||
$linuxRemote = new ScmWorkshopFakeRemote();
|
||||
$error = '';
|
||||
$script = scm_prepare_workshop_script_for_agent($linuxRemote, $linuxHome, $emptyXml, $error);
|
||||
scm_workshop_test_assert($script === '/srv/games/arma3/gsp_server_content/scripts/workshop/generic_steam_workshop_linux.sh', 'stages default Linux script under server home');
|
||||
scm_workshop_test_assert(isset($linuxRemote->files[$script]), 'writes Linux bundled script to fake agent');
|
||||
scm_workshop_test_assert($error === '', 'default Linux script staging does not report missing script');
|
||||
scm_workshop_test_assert(strpos($script, '/srv/games/arma3/gsp_server_content/jobs/workshop/workshop_job_') === 0, 'stages per-job Linux script under server home');
|
||||
scm_workshop_test_assert(isset($linuxRemote->files[$script]), 'writes Linux per-job script to fake agent');
|
||||
scm_workshop_test_assert(strpos($linuxRemote->files[$script], '+runscript') !== false, 'Linux per-job script runs SteamCMD through runscript');
|
||||
scm_workshop_test_assert(strpos($linuxRemote->files[$script], 'workshop_download_item {workshop_app_id} {workshop_id} validate') !== false, 'Linux per-job script generates workshop_download_item runscript command');
|
||||
scm_workshop_test_assert($error === '', 'default Linux job staging does not report missing agent script');
|
||||
|
||||
$windowsRemote = new ScmWorkshopFakeRemote();
|
||||
$script = scm_prepare_workshop_script_for_agent($windowsRemote, $windowsHome, $emptyXml, $error);
|
||||
scm_workshop_test_assert($script === '/cygdrive/c/OGP_User_Files/11/gsp_server_content/scripts/workshop/generic_steam_workshop_windows_cygwin.sh', 'stages default Windows/Cygwin script under server home');
|
||||
scm_workshop_test_assert(isset($windowsRemote->files[$script]), 'writes Windows/Cygwin bundled script to fake agent');
|
||||
scm_workshop_test_assert($error === '', 'default Windows/Cygwin script staging does not report missing script');
|
||||
scm_workshop_test_assert(strpos($script, '/cygdrive/c/OGP_User_Files/11/gsp_server_content/jobs/workshop/workshop_job_') === 0, 'stages per-job Windows/Cygwin script under server home');
|
||||
scm_workshop_test_assert(isset($windowsRemote->files[$script]), 'writes Windows/Cygwin per-job script to fake agent');
|
||||
scm_workshop_test_assert(strpos($windowsRemote->files[$script], '+runscript') !== false, 'Windows/Cygwin per-job script runs SteamCMD through runscript');
|
||||
scm_workshop_test_assert($error === '', 'default Windows/Cygwin job staging does not report missing agent script');
|
||||
|
||||
$configuredXml = simplexml_load_string('<game_config><workshop_support><script_linux>/agent/custom/workshop.sh</script_linux></workshop_support></game_config>');
|
||||
$customRemote = new ScmWorkshopFakeRemote();
|
||||
$customRemote->existing[] = '/agent/custom/workshop.sh';
|
||||
$script = scm_prepare_workshop_script_for_agent($customRemote, $linuxHome, $configuredXml, $error);
|
||||
scm_workshop_test_assert($script === '/agent/custom/workshop.sh', 'uses existing configured custom script');
|
||||
scm_workshop_test_assert(strpos($script, '/srv/games/arma3/gsp_server_content/jobs/workshop/workshop_job_') === 0, 'ignores configured static script and uses generated per-job script');
|
||||
|
||||
$missingCustomRemote = new ScmWorkshopFakeRemote();
|
||||
$script = scm_prepare_workshop_script_for_agent($missingCustomRemote, $linuxHome, $configuredXml, $error);
|
||||
scm_workshop_test_assert($script === '/srv/games/arma3/gsp_server_content/scripts/workshop/generic_steam_workshop_linux.sh', 'falls back to bundled script when configured custom script is missing');
|
||||
scm_workshop_test_assert($error === '', 'missing custom script fallback does not expose script-not-found error');
|
||||
scm_workshop_test_assert(strpos($script, '/srv/games/arma3/gsp_server_content/jobs/workshop/workshop_job_') === 0, 'missing custom static script still uses generated per-job script');
|
||||
scm_workshop_test_assert($error === '', 'missing custom script does not expose script-not-found error');
|
||||
@unlink(dirname(__DIR__) . '/logs/content_install.log');
|
||||
@rmdir(dirname(__DIR__) . '/logs');
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue