Panel/docs/decisions/STEAM_WORKSHOP_DESIGN.md
2026-06-08 16:09:54 -05:00

40 KiB

GSP Steam Workshop and Server Content Design

Workspace reference: GSP-WORKSPACE.md

Scope

This document is an investigation and design report only. It does not implement code.

The goal is a professional Steam Workshop and general server content system for GSP that works across:

  • Agent-Windows
  • Agent_Linux (the Linux agent directory currently uses an underscore in this repository)
  • Panel
  • Website

The system should support games that use Steam Workshop directly, games that require server-side mod installation, and games that use non-Workshop add-ons or content packs.

Target games include DayZ, Arma / Arma 2 / Arma 3, Rust, Garry's Mod, CS / Source games where applicable, and future games with installable server content.

Current Code Findings

Add-ons manager state

The old add-ons manager has already been partially converted into a broader Server Content Manager.

Relevant files:

  • Panel/modules/addonsmanager/addons_manager.php
  • Panel/modules/addonsmanager/addons_installer.php
  • Panel/modules/addonsmanager/server_content_helpers.php
  • Panel/modules/addonsmanager/server_content_actions.php
  • Panel/modules/addonsmanager/workshop_content.php
  • Panel/modules/addonsmanager/workshop_action.php
  • Panel/modules/addonsmanager/server_content_categories.php
  • Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_linux.sh
  • Panel/modules/addonsmanager/scripts/workshop/generic_steam_workshop_windows_cygwin.sh
  • Panel/modules/addonsmanager/SERVER_CONTENT_ROADMAP.md
  • Panel/modules/addonsmanager/SERVER_CONTENT_WORKSHOP_PHASE1.md

What exists:

  • Server Content language and categories have started replacing legacy "add-ons" language.
  • Install methods include download_zip, steam_workshop, config_edit, and post_script.
  • Workshop content has a user-facing page where customers can enter Workshop item IDs.
  • Workshop IDs are validated as numeric strings.
  • A server_content_workshop table is created/migrated by helper code.
  • A remote manifest is written under the game server home.
  • Panel-owned Linux and Cygwin/Windows templates are used to generate per-job scripts under the server home.
  • The generated job script creates a temporary SteamCMD runscript and calls SteamCMD with +runscript.
  • Basic install, update, update selected, update all, and remove selected actions exist.
  • Actions are logged through the panel logger and the script writes a text log under gsp_server_content.

What is incomplete:

  • No search or metadata lookup workflow exists in the UI.
  • Titles are stored but not reliably resolved.
  • There is no load order management.
  • There is no enabled/disabled state separate from installed/removed state.
  • There is no per-game install strategy model.
  • There is no first-class DayZ/Arma -mod= generation.
  • There is no robust copy-key behavior for DayZ .bikey files.
  • There is no clone/copy mod list workflow.
  • There is no asynchronous job/progress system for long Workshop installs.
  • Current generic scripts are synchronous from the Panel request.
  • Current scripts assume anonymous SteamCMD login.
  • Current scripts only constrain writes under the server home, so they do not support a true agent global cache outside the game directory.
  • Current scripts run optional post_install_script using bash -lc, which should remain admin-only and should not be customer-provided.
  • The XML schema does not yet define the Workshop support tags that the helper code expects.

Current Workshop Phase 1 flow

The current user flow is:

  1. User opens the Workshop Mods page for a server.
  2. User enters numeric Workshop item IDs.
  3. Panel inserts or updates rows in server_content_workshop.
  4. Panel writes a manifest to:
    • {GAME_HOME}/gsp_server_content/workshop_manifest.json
  5. Panel writes a generated job script to:
    • {GAME_HOME}/gsp_server_content/jobs/workshop/workshop_job_<timestamp>_<random>.sh
  6. Panel executes:
    • bash <script_path> <manifest_path>
  7. The script writes a temporary SteamCMD runscript:
    • @ShutdownOnFailedCommand 0
    • @NoPromptForPassword 1
    • force_install_dir <server_root>
    • login anonymous
    • workshop_download_item <workshop_app_id> <workshop_id> validate
  8. The script runs SteamCMD with +runscript <scriptfile>.
  9. The script copies downloaded files to a target folder, defaulting to:
    • {SERVER_ROOT}/@{WORKSHOP_ID}
  10. Panel updates DB row state to installed, failed, or removed.

This is useful as a proof of concept, but it is not yet a commercial Workshop/mod system.

Current DB/storage state

The current helper creates:

server_content_workshop

Known columns:

  • id
  • content_id
  • home_id
  • home_cfg_id
  • remote_server_id
  • workshop_app_id
  • workshop_item_id
  • title
  • install_state
  • last_installed_at
  • last_updated_at
  • last_error
  • created_by
  • created_at
  • updated_at

The current helper also writes agent-side files under the server home:

  • gsp_server_content/workshop_manifest.json
  • gsp_server_content/manifests/*.json
  • gsp_server_content/installed_content.json
  • gsp_server_content/workshop_install.log
  • gsp_server_content/workshop_install_windows.log
  • gsp_server_content/workshop/removed/*
  • gsp_server_content/jobs/workshop/workshop_job_*.sh

Current DB weaknesses:

  • No load order column.
  • No enabled column.
  • No install path column per row.
  • No install folder/name column per row.
  • No installed Workshop timestamp/version/hash metadata from Steam.
  • No job table for long-running installs.
  • No normalized global Workshop item metadata table.
  • No clean split between desired state, installed state, and job state.

Current Panel-Agent communication

Panel communicates with agents through XML-RPC using Panel/includes/lib_remote.php.

Relevant methods:

  • steam_cmd(...)
  • steam_workshop(...)
  • get_workshop_mods_info(...)
  • start_file_download(...)
  • remote_writefile(...)
  • remote_readfile(...)
  • exec(...)

There are two Workshop paths:

  1. Legacy agent RPC:

    • Panel method: OGPRemoteLibrary::steam_workshop(...)
    • Agent subroutine: steam_workshop_without_decrypt(...)
    • Runs SteamCMD in a screen update session.
    • Generates post-install shell snippets that edit a config file.
  2. Current Server Content manifest path:

    • Panel writes a JSON manifest using remote_writefile.
    • Panel copies an approved script to the agent using remote_writefile.
    • Panel runs the script through exec.
    • This path does not use the old steam_workshop RPC.

The second path is cleaner for future Server Content work because it is manifest-driven, but it should be moved away from direct synchronous exec into a first-class agent job/action.

Current agent SteamCMD behavior

Linux agent:

  • File: Agent_Linux/ogp_agent.pl
  • SteamCMD path constants:
    • AGENT_RUN_DIR/steamcmd
    • steamcmd.sh
  • check_steam_cmd_client downloads and installs SteamCMD if missing.
  • steam_cmd_without_decrypt creates a SteamCMD runscript and runs it in a screen update session.
  • steam_workshop_without_decrypt creates a SteamCMD runscript containing workshop_download_item lines and runs it in a screen update session.
  • get_workshop_mods_info reads .ogpmod files from AGENT_RUN_DIR/WorkshopModsInfo.

Windows/Cygwin agent:

  • File: Agent-Windows/ogp_agent.pl
  • SteamCMD path constants:
    • /OGP/steamcmd
    • steamcmd.exe
  • steam_cmd_without_decrypt uses Cygwin path conversion for force_install_dir.
  • steam_workshop_without_decrypt uses Cygwin path conversion for the mods path and the SteamCMD runscript.
  • get_workshop_mods_info mirrors the Linux behavior.

Agent limitations:

  • The legacy steam_workshop RPC is not tied to the newer server_content_workshop DB table.
  • It edits config files through generated shell script content instead of structured startup parameter state.
  • It does not understand enabled/disabled state or load order.
  • It does not expose a durable install job ID with pollable progress.
  • It does not track per-server install manifests as the source of truth.
  • It does not provide a DayZ-specific strategy.

Current game XML and startup parameter behavior

Relevant files:

  • Panel/modules/config_games/schema_server_config.xml
  • Panel/modules/config_games/config_servers.php
  • Panel/modules/config_games/cli-params.php
  • Panel/modules/gamemanager/mini_start.php
  • Panel/modules/gamemanager/cfg_text_replace.php
  • Panel/modules/gamemanager/update_actions.php

The XML schema currently supports:

  • cli_template
  • cli_params
  • replace_texts
  • custom_fields
  • pre_install
  • post_install
  • pre_start
  • post_start
  • environment_variables
  • lock_files
  • configuration_files

The XML schema does not currently support a first-class workshop_support block. Helper code already checks ad hoc fields such as:

  • workshop_app_id
  • workshop_appid
  • steam_workshop_app_id
  • steam_workshop_appid
  • workshop_script_linux
  • workshop_script_windows

Because read_server_config() validates XML against schema_server_config.xml, these ad hoc fields are not safe to use in game XML until the schema is extended.

Startup parameters are currently generated in mini_start.php from cli_template and cli_params, with stored server values/custom fields layered in. This should be the integration point for generated mod parameters, but the mod list must be structured data, not raw customer command text.

Current config/custom field behavior

cfg_text_replace.php reads custom fields from the database and applies configured replacements to files through remote_readfile and remote_writefile.

This is useful for game config editing, but it should not be used as the primary Workshop/mod list system because:

  • It is text replacement based.
  • It does not model enabled/disabled mods.
  • It does not model load order.
  • It does not model install state.
  • It can be fragile for repeated add/remove/reorder operations.

Existing Workshop support in game configs

Some Source/Garry's Mod configs expose Workshop-related startup params directly:

  • +host_workshop_collection
  • +host_workshop_map
  • +workshop_start_map

This is game-managed Workshop support. GSP should preserve that behavior and not force all games through server-side file copying.

Current Add-ons/Content Module State

The current module is best described as "Server Content Phase 1."

Partially working pieces:

  • Admins can define content records in the addons table.
  • Users can view server content from the add-ons manager.
  • steam_workshop is recognized as an install method.
  • Workshop IDs can be entered manually.
  • A manifest-driven script runner exists.
  • Basic install/update/remove states exist.

Broken or incomplete pieces for commercial use:

  • No schema-level game capability declaration.
  • No game-specific install strategy selection.
  • No load order UI.
  • No enable/disable UI.
  • No safe startup parameter generation for mod lists.
  • No DayZ/Arma key copy strategy.
  • No metadata discovery or title resolution.
  • No async job/progress model.
  • No central cache design.
  • No cache cleanup policy.
  • No rollback/repair strategy.
  • No robust uninstall ownership model for copied keys/files.
  • No support for private Workshop items requiring login beyond older SteamCMD update settings.

Main design rule

The Panel database should be the source of truth for desired server content state.

The agent should be the source of truth for execution state and filesystem results.

Agent files should be treated as runtime manifests, logs, caches, and recovery aids, not as the only source of truth.

Core model

Create a first-class Server Content / Workshop system with:

  • XML-declared game capabilities.
  • Database-stored per-server content state.
  • Agent-executed install/update/uninstall jobs.
  • Structured startup parameter generation.
  • Game-specific install strategies.
  • Safe, pollable job logs.
  • Optional global cache.
  • Cross-platform behavior for Linux and Cygwin/Windows agents.

Required states

For a server Workshop item:

  • selected: saved but not installed.
  • queued: job has been queued.
  • installing: agent is installing it.
  • installed: installed and available.
  • disabled: installed but excluded from generated startup parameters.
  • updating: update job is running.
  • failed: last install/update failed.
  • removing: uninstall job is running.
  • removed: removed from desired state.

For an install job:

  • queued
  • running
  • succeeded
  • failed
  • cancelled

Panel:

  • Reads game XML capability definitions.
  • Shows Workshop/Mods/Server Content UI only when supported.
  • Stores desired content state.
  • Queues install/update/remove/reorder actions.
  • Generates desired startup mod list from DB state.
  • Sends structured manifests/jobs to the agent.
  • Polls agent for progress/logs.
  • Shows clear error messages and restart-required prompts.

Agent:

  • Runs SteamCMD.
  • Stages downloads.
  • Copies/installs files using approved strategies.
  • Copies DayZ/Arma keys when configured.
  • Writes progress logs.
  • Writes per-job result JSON.
  • Tracks installed file manifests where useful.
  • Does not trust customer-supplied shell commands.
  • Does not decide desired load order.

Game XML:

  • Declares whether Workshop/content support exists.
  • Declares the provider, Steam app IDs, install strategy, path templates, startup parameter format, key copy behavior, cache policy, and authentication needs.
  • Provides admin-defined strategy data, not customer-editable commands.

XML Capability Proposal

Add a first-class optional block to schema_server_config.xml.

Example:

<workshop_support>
  <enabled>1</enabled>
  <provider>steam</provider>
  <steam_app_id>221100</steam_app_id>
  <workshop_app_id>221100</workshop_app_id>
  <download_method>steamcmd</download_method>
  <install_strategy>dayz_mod_folder</install_strategy>
  <install_path>{GAME_PATH}</install_path>
  <mod_folder_format>@{SAFE_TITLE}</mod_folder_format>
  <startup_param_format>-mod={MOD_LIST}</startup_param_format>
  <mod_separator>;</mod_separator>
  <mod_prefix>@</mod_prefix>
  <requires_restart>1</requires_restart>
  <supports_load_order>1</supports_load_order>
  <supports_disable>1</supports_disable>
  <allow_cache>1</allow_cache>
  <requires_steam_login>0</requires_steam_login>
  <copy_keys enabled="1">
    <source_pattern>{MOD_PATH}/keys/*.bikey</source_pattern>
    <target_path>{GAME_PATH}/keys</target_path>
  </copy_keys>
</workshop_support>

Required XML fields

  • enabled
  • provider
  • download_method
  • install_strategy

For Steam Workshop:

  • steam_app_id
  • workshop_app_id

For strategies that install files:

  • install_path

For strategies that update startup parameters:

  • startup_param_format
  • mod_separator

Optional XML fields

  • mod_prefix
  • mod_folder_format
  • server_mod_param_format
  • client_mod_param_format
  • copy_keys
  • requires_restart
  • supports_load_order
  • supports_disable
  • allow_cache
  • cache_scope
  • requires_steam_login
  • steam_login_profile
  • max_items
  • allowed_item_types
  • install_script_key
  • post_install_strategy
  • validate_files
  • backup_before_update
  • preserve_previous_version

Supported install strategies

  • game_managed_workshop
  • steamcmd_download_only
  • copy_to_game_root
  • copy_to_mod_folder
  • dayz_mod_folder
  • arma_mod_folder
  • config_only
  • custom_scripted_install

Strategy meanings

game_managed_workshop

  • The game downloads/uses Workshop content internally.
  • GSP should generate game-supported startup params such as Workshop collection IDs.
  • No server-side file copy is required.
  • Example: Garry's Mod or Source games using +host_workshop_collection.

steamcmd_download_only

  • Agent downloads Workshop item to a known cache/staging path.
  • No direct install into game root.
  • Useful for games/tools that consume downloaded content separately.

copy_to_game_root

  • Agent downloads then copies files directly under {GAME_PATH} or a safe subpath.

copy_to_mod_folder

  • Agent downloads then copies content to a generated mod folder.

dayz_mod_folder

  • Agent downloads content.
  • Agent installs under {GAME_PATH}/@ModName.
  • Agent copies .bikey files to {GAME_PATH}/keys.
  • Panel generates -mod=@Mod1;@Mod2 from enabled ordered items.

arma_mod_folder

  • Similar to DayZ, but should support Arma-specific client/server mod parameter variants as needed.

config_only

  • The content changes structured config values only.
  • No Workshop download.

custom_scripted_install

  • Admin-defined trusted script/action.
  • Should be used sparingly.
  • Customer must never provide arbitrary script text.

XML validation rules

  • enabled must be 0 or 1.
  • App IDs and Workshop IDs must be numeric.
  • Strategy must be one of the allowed strategy names.
  • Path templates must only use approved variables.
  • Path templates must resolve under allowed roots.
  • Startup param format must only contain approved placeholders.
  • copy_keys target path must resolve under the game home unless explicitly allowed by admin policy.
  • custom_scripted_install must reference an approved script key/path, not inline customer text.

Backward compatibility

Older XML files should continue to work with no Workshop support.

If workshop_support is absent:

  • Existing startup parameters such as +host_workshop_collection continue to work.
  • Existing add-ons manager content can still be shown if admin-created content records exist.
  • No Workshop Mods page should be shown unless enabled by admin record or capability detection.

Existing ad hoc fields such as workshop_app_id should either be migrated into workshop_support or tolerated by a compatibility parser after the schema is explicitly updated.

DB Table Proposal

gsp_workshop_items

Global metadata cache for Workshop items.

Suggested columns:

  • id
  • provider
  • app_id
  • workshop_id
  • title
  • description_short
  • preview_url
  • author_name
  • visibility
  • file_size
  • time_created
  • time_updated
  • metadata_json
  • last_metadata_fetch_at
  • last_metadata_error
  • created_at
  • updated_at

Unique key:

  • (provider, app_id, workshop_id)

gsp_server_workshop_items

Desired per-server Workshop state.

Suggested columns:

  • id
  • home_id
  • home_cfg_id
  • remote_server_id
  • content_id
  • provider
  • app_id
  • workshop_app_id
  • workshop_id
  • item_title
  • install_name
  • safe_folder_name
  • install_path
  • enabled
  • load_order
  • install_strategy
  • install_state
  • installed_version
  • installed_timestamp
  • last_update_check
  • last_installed_at
  • last_updated_at
  • last_error
  • restart_required
  • installed_by
  • created_at
  • updated_at

Recommended unique key:

  • (home_id, provider, workshop_id)

gsp_workshop_install_jobs

Pollable install/update/remove jobs.

Suggested columns:

  • id
  • job_uuid
  • home_id
  • remote_server_id
  • action
  • status
  • requested_by
  • started_at
  • finished_at
  • agent_job_id
  • manifest_path
  • log_path
  • result_path
  • progress_percent
  • last_message
  • last_error
  • created_at
  • updated_at

gsp_workshop_install_job_items

Per-item job details.

Suggested columns:

  • id
  • job_id
  • server_workshop_item_id
  • workshop_id
  • action
  • status
  • install_path
  • message
  • error
  • started_at
  • finished_at

gsp_server_content_profiles

Optional admin-defined reusable profiles per game/config.

Suggested columns:

  • id
  • home_cfg_id
  • profile_key
  • display_name
  • provider
  • steam_app_id
  • workshop_app_id
  • install_strategy
  • install_path_template
  • startup_param_format
  • copy_keys_json
  • cache_policy
  • created_at
  • updated_at

Source of truth

The Panel DB should be the source of truth for:

  • Which mods are desired.
  • Which mods are enabled.
  • Load order.
  • Generated startup params.
  • Last known install state.

The agent should write runtime state for:

  • Active job progress.
  • Install logs.
  • Download/cache manifests.
  • Files installed during a job.
  • Last job result.

Agent Execution Proposal

Preferred direction

Add a dedicated agent action for server content jobs instead of running generic exec synchronously from a web request.

Potential XML-RPC methods:

  • server_content_start_job(home_id, manifest_json)
  • server_content_job_status(home_id, job_id)
  • server_content_job_log(home_id, job_id, offset)
  • server_content_cancel_job(home_id, job_id)

The agent should return quickly with a job ID. The Panel polls status and logs.

Manifest shape

Example:

{
  "manifest_version": 2,
  "job_uuid": "uuid",
  "home_id": 123,
  "home_path": "/home/ogp_agent/OGP_User_Files/123",
  "provider": "steam",
  "action": "install_update",
  "strategy": "dayz_mod_folder",
  "steam_app_id": "221100",
  "workshop_app_id": "221100",
  "startup": {
    "param_format": "-mod={MOD_LIST}",
    "separator": ";",
    "mod_prefix": "@"
  },
  "copy_keys": {
    "enabled": true,
    "source_pattern": "{MOD_PATH}/keys/*.bikey",
    "target_path": "{GAME_PATH}/keys"
  },
  "items": [
    {
      "workshop_id": "1559212036",
      "title": "CF",
      "safe_folder_name": "@CF",
      "enabled": true,
      "load_order": 10,
      "install_path": "{GAME_PATH}/@CF"
    }
  ]
}

Agent job execution

For each job:

  1. Validate manifest fields.
  2. Resolve paths from approved templates only.
  3. Create job directory under agent control.
  4. Start a background worker, preferably screen-backed for consistency with existing agent behavior.
  5. Write progress logs.
  6. Run SteamCMD download.
  7. Verify expected download path exists.
  8. Stage content.
  9. Install/copy/sync to final path.
  10. Copy keys if strategy requires it.
  11. Write installed file manifest.
  12. Write job result JSON.
  13. Return status to Panel polling.

SteamCMD command

Baseline command:

steamcmd +login anonymous +workshop_download_item <workshop_app_id> <workshop_id> validate +quit

Authenticated variant:

steamcmd +login <configured_user> <configured_password> +workshop_download_item <workshop_app_id> <workshop_id> validate +quit

The agent must redact credentials from logs.

Agent paths

Recommended paths:

  • Job root:
    • {CONTROL_PATH}/server_content/jobs/{job_uuid}
  • Job manifest:
    • {CONTROL_PATH}/server_content/jobs/{job_uuid}/manifest.json
  • Job log:
    • {CONTROL_PATH}/server_content/jobs/{job_uuid}/job.log
  • Job result:
    • {CONTROL_PATH}/server_content/jobs/{job_uuid}/result.json
  • Per-server content manifest:
    • {CONTROL_PATH}/server_content/installed_manifest.json
  • Per-server staging:
    • {CONTROL_PATH}/server_content/staging/{job_uuid}
  • Per-server Workshop staging:
    • {CONTROL_PATH}/server_content/workshop/staging/{workshop_id}

CONTROL_PATH should be agent-managed and should not be customer-editable through FTP/file manager.

Cache paths

Optional global cache:

  • {AGENT_CACHE}/steam_workshop/{workshop_app_id}/{workshop_id}

Optional per-server cache:

  • {CONTROL_PATH}/server_content/cache/steam_workshop/{workshop_app_id}/{workshop_id}

The first version can use per-server staging only. Global cache can be added once ownership, quota, cleanup, and sharing rules are defined.

User Workflow

Customer workflow:

  1. Open a game server.
  2. Click Workshop, Mods, or Server Content.
  3. Paste a Workshop URL or numeric Workshop ID.
  4. Panel extracts and validates the numeric ID.
  5. Panel optionally fetches title/metadata.
  6. User adds the item to the install queue.
  7. User chooses enabled or disabled.
  8. User adjusts load order where the game supports it.
  9. User clicks Install / Update.
  10. Panel shows live progress and logs.
  11. When complete, Panel marks restart required if the game is running.
  12. User restarts server.
  13. Startup params are generated from the enabled ordered mod list.

Installed mod list should show:

  • Enabled checkbox/toggle.
  • Load order controls.
  • Workshop ID.
  • Title.
  • Folder/install name.
  • Installed status.
  • Last updated.
  • Last error.
  • Actions: update, repair, disable, uninstall.

Admin Workflow

Admin workflow:

  1. Edit game XML or admin content profile.
  2. Enable Workshop support.
  3. Set provider and Steam app/workshop IDs.
  4. Select install strategy.
  5. Define install path template.
  6. Define startup parameter format.
  7. Define key copy behavior.
  8. Choose cache policy.
  9. Choose whether Steam login is required.
  10. Save and validate schema.
  11. Test install/update/uninstall on Linux and Cygwin/Windows agents.

Admins should not need to write shell scripts for common games like DayZ, Arma, Rust, Garry's Mod, or Source games.

Workshop Item Discovery

First version

Support:

  • Numeric Workshop ID entry.
  • Steam Workshop URL paste.
  • Extraction of the id= query parameter.
  • Basic local validation.
  • Optional metadata fetch when a Steam Web API key is configured.

Accepted examples:

  • 1559212036
  • https://steamcommunity.com/sharedfiles/filedetails/?id=1559212036
  • Multiple IDs separated by newline or comma.

Validation:

  • Extract only numeric IDs.
  • Reject non-numeric values.
  • Deduplicate IDs.
  • Enforce optional max item count per game/server.

Professional version

Support:

  • Steam Web API metadata fetch.
  • Cached metadata table.
  • Search by title when API key is configured.
  • Preview image/title/author display.
  • Visibility and removed/private item warnings.
  • Update timestamp checks.

Limitations:

  • Some Workshop content requires authenticated Steam credentials.
  • Some items are private, age-gated, removed, or region-restricted.
  • Search can be rate-limited.
  • SteamCMD may download content even when metadata fetch fails, or vice versa.

Recommendation:

  • Build the first version around URL/ID entry.
  • Add metadata enrichment as a progressive enhancement.
  • Do not block install solely because metadata is unavailable.

Download and Install Strategy

SteamCMD execution

SteamCMD should run on the agent host because the agent has filesystem access to the game server install.

Linux:

  • Use steamcmd.sh.
  • Existing agent already knows AGENT_RUN_DIR/steamcmd/steamcmd.sh.

Cygwin/Windows:

  • Use steamcmd.exe.
  • Convert Cygwin paths to Windows paths when passing paths to SteamCMD.
  • Keep scripts and screen usage as similar to Linux as possible.

Download flow

Recommended flow:

  1. Resolve SteamCMD path.
  2. Resolve download/cache path.
  3. Run workshop_download_item.
  4. Capture stdout/stderr to job log.
  5. Verify expected content folder exists.
  6. Stage content.
  7. Install via strategy.
  8. Record installed file manifest.

DayZ install flow

For each enabled mod:

  1. Download Workshop item with app ID 221100.
  2. Resolve safe display title if available.
  3. Generate safe folder name:
    • Prefer @Title normalized for filesystem and startup use.
    • Fall back to @<workshop_id>.
  4. Copy content to:
    • {GAME_PATH}/@ModName
  5. Copy .bikey files from common locations:
    • {MOD_PATH}/keys/*.bikey
    • {MOD_PATH}/Keys/*.bikey
    • optionally recursive search if configured.
  6. Copy keys to:
    • {GAME_PATH}/keys
  7. Track copied keys in an installed file manifest.
  8. Generate -mod=@Mod1;@Mod2 from enabled ordered items.
  9. Mark restart required if server is running.

Generic game support

Game-managed Workshop games:

  • Store Workshop collection/map IDs.
  • Generate the game's native Workshop startup params.
  • Do not copy files unless configured.

Copy-to-folder games:

  • Download Workshop item.
  • Copy folder contents to a configured target path.
  • Optionally add startup parameters.

Config-only content:

  • Apply structured config changes from approved templates.

Custom scripted install:

  • Admin-defined trusted scripts only.
  • Scripts should receive a manifest path.
  • Scripts should write structured result JSON.

Startup Parameter Integration

Startup parameter generation must be structured and repeatable.

For DayZ/Arma style mods:

  1. Load enabled Workshop items for the server ordered by load_order.
  2. Build MOD_LIST using the XML mod_separator.
  3. Render startup_param_format.
  4. Inject the rendered mod parameter into startup generation.
  5. Avoid duplicate -mod= entries.
  6. Preserve unrelated startup parameters.
  7. Remove generated mod params when all mods are disabled/uninstalled.

Example:

-mod=@CF;@Dabs Framework;@VPPAdminTools

Rules:

  • Customers should not edit the raw generated -mod= string.
  • Customers may reorder/enable/disable structured mod rows.
  • Generated startup fragments should be labeled internally as GSP-managed.
  • Existing custom startup params should remain separate where possible.

Recommended implementation:

  • Add a startup fragment resolver before mini_start.php finalizes $start_cmd.
  • The resolver reads the server content DB state and XML workshop_support.
  • It returns a generated fragment such as -mod=....
  • The fragment is inserted through a reserved placeholder or appended according to XML rules.

Possible XML placeholder:

<cli_template>%GAME_TYPE% -config=serverDZ.cfg {GSP_WORKSHOP_PARAMS}</cli_template>

If no placeholder exists, XML can specify:

<startup_injection mode="append" />

Uninstall, Disable, Update, Repair

Disable

Disable should:

  • Set enabled=0.
  • Keep installed files.
  • Remove the item from generated startup parameters.
  • Mark restart required if server is running.

Uninstall

Uninstall should:

  • Set job status to removing.
  • Remove item from startup mod list.
  • Stop using the mod immediately after next restart.
  • Optionally delete installed files.
  • Optionally leave global cache intact.
  • Remove copied keys only if safe.
  • Update DB row status.

Key cleanup rule:

  • Only remove keys that GSP knows it copied and that are not still required by another installed mod.
  • If ownership is unclear, leave the key and warn the user/admin.

Update

Update should:

  • Download new Workshop content.
  • Stage it separately.
  • Preserve old installed folder until staging succeeds.
  • Swap/copy into place only after successful download.
  • Roll back if install fails where feasible.
  • Mark restart required if server is running.

Repair/reinstall

Repair should:

  • Re-run download with validate.
  • Re-copy content.
  • Re-copy keys.
  • Rebuild startup parameters.

Clone mod set

Clone should:

  • Copy desired mod list, enabled state, and load order to another compatible server.
  • Validate same game/config capability.
  • Queue install/update job for the target server.
  • Reuse cache when safe and available.

Caching and Cleanup Plan

Cache options

No cache:

  • Lowest complexity.
  • Highest repeated bandwidth usage.

Per-server cache:

  • Safer ownership model.
  • Easier cleanup.
  • Less sharing benefit.

Agent global cache:

  • Best reuse across servers.
  • Saves bandwidth and time.
  • Requires quota, permissions, locking, and cleanup.

First implementation:

  • Use per-server staging/cache under agent control.
  • Do not expose cache through customer file manager.

Later:

  • Add optional global cache by app ID and Workshop ID.
  • Track size and last-used timestamp.
  • Admin-configurable max cache size.
  • Cleanup least recently used items.
  • Support hardlink/symlink/junction only after cross-platform testing.

Cache metadata

Cache manifest should track:

  • Provider.
  • App ID.
  • Workshop ID.
  • Download timestamp.
  • Steam update timestamp if known.
  • Size.
  • Source path.
  • Last used by home ID.
  • Last validation result.

Security Considerations

Customer input

Customers may provide:

  • Workshop URLs.
  • Workshop IDs.
  • Enable/disable choices.
  • Load order.

Customers must not provide:

  • Shell commands.
  • SteamCMD command fragments.
  • Arbitrary install paths.
  • Arbitrary startup command fragments.
  • Arbitrary copy patterns.

Command safety

  • Commands should be generated from trusted XML/admin config and validated DB state.
  • Workshop IDs must be numeric.
  • URL parsing must only extract numeric id values.
  • Path templates must resolve under allowed roots.
  • No path traversal.
  • No null bytes or control characters.
  • Logs must redact Steam credentials.
  • Admin scripts must be explicitly marked trusted.

File location safety

Managed manifests, job files, logs, and scripts should live outside customer-editable roots where possible.

If current deployment requires storing under game home temporarily:

  • Use a gsp_server_content directory.
  • Prevent customer file manager/FTP access to that control directory if possible.
  • Validate that all write targets stay under approved paths.

Credentials

  • Steam credentials should remain in admin settings or a dedicated secure credential profile.
  • Do not expose credentials in manifests shown to customers.
  • Do not write raw passwords to logs.
  • Prefer passing credentials through agent-managed secure files or redacted runscript generation.

Avoid arbitrary process/file damage

  • Install/uninstall should act only on paths from the per-item manifest.
  • Remove only files/folders GSP installed or explicitly owns.
  • Do not recursively delete broad paths such as {GAME_PATH}.
  • Do not remove shared keys unless manifest ownership is clear.

Cross-platform Considerations

Linux

  • Use steamcmd.sh.
  • Use POSIX path handling.
  • Use screen-backed jobs for consistency with existing agent update behavior.
  • Use rsync if available, otherwise safe recursive copy.
  • Preserve file ownership for the game server user.

Cygwin/Windows

  • Use steamcmd.exe.
  • Convert paths with cygpath -wa when passing to SteamCMD.
  • Be careful with spaces and backslashes.
  • Use Cygwin shell for shared scripts where practical.
  • Avoid Linux-only tools unless bundled or checked.
  • Use safe copy behavior that works under Cygwin.

Common risks

  • Quoting differences.
  • Case-insensitive paths on Windows.
  • Long paths.
  • File locks while server is running.
  • Different SteamCMD output.
  • Different permission/ownership behavior.
  • Symlink/junction behavior is not portable enough for first version.

DayZ-Specific Plan

DayZ should be the first full strategy because it exercises the hard cases.

XML:

<workshop_support>
  <enabled>1</enabled>
  <provider>steam</provider>
  <steam_app_id>221100</steam_app_id>
  <workshop_app_id>221100</workshop_app_id>
  <download_method>steamcmd</download_method>
  <install_strategy>dayz_mod_folder</install_strategy>
  <install_path>{GAME_PATH}</install_path>
  <mod_folder_format>@{SAFE_TITLE}</mod_folder_format>
  <startup_param_format>-mod={MOD_LIST}</startup_param_format>
  <mod_separator>;</mod_separator>
  <copy_keys enabled="1">
    <source_pattern>{MOD_PATH}/keys/*.bikey</source_pattern>
    <target_path>{GAME_PATH}/keys</target_path>
  </copy_keys>
</workshop_support>

Panel behavior:

  • Accept Workshop URL/ID.
  • Resolve title when possible.
  • Store desired item row.
  • Allow reorder.
  • Allow enabled/disabled.
  • Generate -mod= from enabled ordered mods.
  • Prompt restart after changes.

Agent behavior:

  • Download item.
  • Install to @ModName.
  • Copy keys.
  • Write install manifest.
  • Report clear failures:
    • SteamCMD missing.
    • Steam login required.
    • Workshop item not found.
    • Download path missing.
    • Disk full.
    • Key copy failed.

Generic Game Support Plan

Each game should choose a strategy:

  • Source/Garry's Mod: game_managed_workshop
  • Rust: likely server-managed or game-specific strategy depending on current Rust mod ecosystem support.
  • DayZ: dayz_mod_folder
  • Arma 2/3: arma_mod_folder
  • Generic file content: copy_to_mod_folder or copy_to_game_root
  • Config packs: config_only

The UI can stay mostly the same while XML changes behavior behind the scenes.

Commercial-Quality Expected Result

Customer experience:

  • Paste Workshop URL or ID.
  • See item title if available.
  • Install one or many mods.
  • See progress and logs.
  • Enable/disable mods without deleting them.
  • Reorder mods.
  • Update selected or update all.
  • Uninstall cleanly.
  • See restart-required prompts.
  • See clear errors that explain what failed.

Admin experience:

  • Game XML declares support.
  • Install strategy is selected by game.
  • Steam app IDs are configured centrally.
  • Startup parameter format is configured centrally.
  • Key copy behavior is configured centrally.
  • Cache policy is configured centrally.
  • No customer-editable helper batch files are needed.

Phase 1: Inventory/report only

  • Complete this report.
  • Do not modify code.

Phase 2: XML schema design

  • Add workshop_support schema.
  • Add parser helpers.
  • Add validation warnings in config editor.
  • Preserve old XML behavior.

Phase 3: DB schema

  • Add normalized Workshop metadata table.
  • Add per-server Workshop item table.
  • Add install job and job item tables.
  • Migrate existing server_content_workshop rows.

Phase 4: Panel UI

  • Replace Phase 1 Workshop page with:
    • URL/ID entry.
    • Installed list.
    • Enable/disable.
    • Load order.
    • Update/uninstall/repair.
    • Live job log.

Phase 5: Agent job runner

  • Add first-class agent job actions.
  • Return job ID immediately.
  • Poll status/logs.
  • Keep screen as shared backend.

Phase 6: SteamCMD download job

  • Implement cross-platform SteamCMD runner.
  • Support anonymous and configured login.
  • Redact credentials.
  • Write progress/result files.

Phase 7: DayZ strategy

  • Download Workshop items.
  • Install @ModName folders.
  • Copy .bikey files.
  • Generate -mod= startup fragment.
  • Test install/update/disable/uninstall.

Phase 8: Startup parameter integration

  • Add structured generated startup fragments.
  • Avoid duplicate raw params.
  • Preserve existing game startup behavior.

Phase 9: Update/uninstall/reorder

  • Add update all/selected.
  • Add repair.
  • Add safe uninstall.
  • Add load order persistence.

Phase 10: Cache/clone support

  • Add optional global cache.
  • Add cleanup policy.
  • Add clone mod list workflow.

Phase 11: Additional game strategies

  • Add Arma strategy.
  • Add game-managed Workshop strategy for Garry's Mod/Source where appropriate.
  • Add generic copy/config strategies.

Open Questions

  • Should the agent expose a new XML-RPC job API, or should the Panel continue to use exec for Phase 2?
  • What should the canonical agent control path be outside customer-editable files?
  • Can the current file manager/FTP layer hide gsp_server_content reliably if control files remain under game home?
  • Which DB migration system should own the new tables?
  • How should existing server_content_workshop rows be migrated to the proposed normalized tables?
  • Where should Steam credentials be stored for Workshop items requiring login?
  • Should per-server Steam credentials ever be supported, or admin-only credential profiles?
  • Which games require authenticated Workshop downloads?
  • How should Workshop metadata be fetched without a Steam Web API key?
  • Should title resolution be required before install, or should @<workshop_id> always be allowed?
  • How should conflicts be handled when two Workshop items normalize to the same folder name?
  • Should DayZ keys be copied recursively by default or only from configured paths?
  • How should key ownership be tracked for safe uninstall?
  • Should updating mods while a server is running be blocked, staged, or allowed with restart required?
  • Should global cache be enabled in commercial deployments by default?
  • How should disk quota account for downloaded cache and installed copies?
  • Should symlinks/junctions ever be used to avoid duplicate copies?
  • How should Windows long paths be handled?
  • Should Workshop install logs be exposed through the existing log viewer or a dedicated Server Content job log page?

Summary Recommendation

Use the current Phase 1 Server Content work as a starting point, but do not keep extending it as a synchronous script runner.

The professional path is:

  • Add XML-declared Workshop/content capabilities.
  • Store desired Workshop state in the Panel database.
  • Add pollable agent jobs for install/update/remove.
  • Use SteamCMD on the agent.
  • Implement game-specific strategies, starting with DayZ.
  • Generate startup parameters from structured enabled/load-ordered rows.
  • Treat LGSL/GameQ and game query systems as unrelated to content installation.
  • Keep customers away from raw commands, helper scripts, and managed control files.