Steam Workshop – Mod Manager'; $home_id = isset($_REQUEST['home_id']) ? (int)$_REQUEST['home_id'] : 0; if (!$home_id) { sw_error('No server selected. Please access this page from your game server manager.'); return; } // Ownership check if (!sw_user_owns_home($db, (int)$_SESSION['user_id'], $home_id)) { sw_error('Access denied. You do not own this server.'); return; } // Load server info $home = sw_get_home_info($db, $home_id); if (!$home) { sw_error('Server not found.'); return; } // Find matching Workshop profile $profile = sw_get_profile_for_home($db, $home_id); if (!$profile) { echo '
Steam Workshop is not enabled for this game type (' . sw_h($home['game_name']) . ').
'; echo 'An administrator must enable Workshop support for this game under ' . 'Steam Workshop › Admin.
'; return; } $action = $_POST['action'] ?? ($_GET['action'] ?? ''); // ── POST handlers ───────────────────────────────────────────────── if ($_SERVER['REQUEST_METHOD'] === 'POST') { switch ($action) { case 'add_mod': sw_user_add_mod($db, $home_id, $profile); break; case 'save_mod': sw_user_save_mod($db, $home_id); break; case 'delete_mod': sw_user_delete_mod($db, $home_id); break; case 'toggle_mod': sw_user_toggle_mod($db, $home_id); break; case 'move_up': case 'move_down': sw_user_reorder_mod($db, $home_id, $action); break; case 'queue_update': sw_user_queue_update($db, $home_id); break; case 'save_settings': sw_user_save_settings($db, $home_id); break; } } // ── Render page ─────────────────────────────────────────────────── sw_user_render($db, $home_id, $home, $profile); } // ───────────────────────────────────────────────────────────────────────── // POST action handlers // ───────────────────────────────────────────────────────────────────────── function sw_user_add_mod($db, $home_id, array $profile) { $workshop_id = trim($_POST['workshop_id'] ?? ''); if (!preg_match('/^\d{1,20}$/', $workshop_id)) { sw_error('Invalid Workshop ID – must be a numeric Steam Workshop item ID.'); return; } // Prevent duplicates $safe_wid = $db->realEscapeSingle($workshop_id); $exists = $db->resultQuery( "SELECT id FROM " . sw_table('steam_workshop_server_mods') . " WHERE `home_id` = $home_id AND `workshop_id` = '$safe_wid' LIMIT 1" ); if ($exists) { sw_error("Workshop ID $workshop_id is already in the list."); return; } // Determine next sort_order $last = $db->resultQuery( "SELECT MAX(`sort_order`) AS m FROM " . sw_table('steam_workshop_server_mods') . " WHERE `home_id` = $home_id" ); $sort = ($last && isset($last[0]['m'])) ? ((int)$last[0]['m'] + 1) : 0; $mod_name = $db->realEscapeSingle(trim($_POST['mod_name'] ?? '')); $mod_type = (($_POST['mod_type'] ?? 'client') === 'server') ? 'server' : 'client'; $profile_id = (int)$profile['id']; // Auto-generate folder name from naming format template $folder_name = sw_apply_template( $profile['folder_naming_format'], array( 'MOD_NAME' => !empty($mod_name) ? $mod_name : $workshop_id, 'WORKSHOP_ID' => $workshop_id, 'WORKSHOP_APP_ID'=> $profile['workshop_app_id'], ) ); $safe_fname = $db->realEscapeSingle($folder_name); $safe_mname = $mod_name; // already escaped above via realEscapeSingle $ok = $db->query( "INSERT INTO " . sw_table('steam_workshop_server_mods') . " (`home_id`, `profile_id`, `workshop_id`, `mod_name`, `folder_name`, `mod_type`, `sort_order`, `enabled`, `install_status`, `created_at`) VALUES ($home_id, $profile_id, '$safe_wid', '$safe_mname', '$safe_fname', '$mod_type', $sort, 1, '', NOW())" ); if ($ok) { sw_success("Workshop mod $workshop_id added."); } else { sw_error('Failed to add mod.'); } } function sw_user_save_mod($db, $home_id) { $mod_id = (int)($_POST['mod_id'] ?? 0); if (!$mod_id) { return; } $mod = sw_get_mod_by_id($db, $mod_id); if (!$mod || (int)$mod['home_id'] !== $home_id) { sw_error('Mod not found or access denied.'); return; } $mod_name = $db->realEscapeSingle(trim($_POST['mod_name'] ?? '')); $folder_name = $db->realEscapeSingle(trim($_POST['folder_name'] ?? '')); $mod_type = (($_POST['mod_type'] ?? 'client') === 'server') ? 'server' : 'client'; if (empty($folder_name)) { sw_error('Folder name cannot be empty.'); return; } $ok = $db->query( "UPDATE " . sw_table('steam_workshop_server_mods') . " SET `mod_name` = '$mod_name', `folder_name` = '$folder_name', `mod_type` = '$mod_type', `updated_at` = NOW() WHERE `id` = $mod_id AND `home_id` = $home_id LIMIT 1" ); if ($ok) { sw_success('Mod updated.'); } else { sw_error('Failed to update mod.'); } } function sw_user_delete_mod($db, $home_id) { $mod_id = (int)($_POST['mod_id'] ?? 0); if (!$mod_id) { return; } $mod = sw_get_mod_by_id($db, $mod_id); if (!$mod || (int)$mod['home_id'] !== $home_id) { sw_error('Mod not found or access denied.'); return; } $db->query( "DELETE FROM " . sw_table('steam_workshop_server_mods') . " WHERE `id` = $mod_id AND `home_id` = $home_id LIMIT 1" ); sw_success('Mod removed from list.'); } function sw_user_toggle_mod($db, $home_id) { $mod_id = (int)($_POST['mod_id'] ?? 0); if (!$mod_id) { return; } $mod = sw_get_mod_by_id($db, $mod_id); if (!$mod || (int)$mod['home_id'] !== $home_id) { sw_error('Mod not found or access denied.'); return; } $new_state = $mod['enabled'] ? 0 : 1; $db->query( "UPDATE " . sw_table('steam_workshop_server_mods') . " SET `enabled` = $new_state, `updated_at` = NOW() WHERE `id` = $mod_id AND `home_id` = $home_id LIMIT 1" ); } function sw_user_reorder_mod($db, $home_id, $direction) { $mod_id = (int)($_POST['mod_id'] ?? 0); if (!$mod_id) { return; } $mod = sw_get_mod_by_id($db, $mod_id); if (!$mod || (int)$mod['home_id'] !== $home_id) { return; } $mods = sw_get_server_mods($db, $home_id); if (!$mods) { return; } // Normalise sort_order to 0-based sequential integers $sorted = array_values($mods); foreach ($sorted as $idx => $m) { $db->query( "UPDATE " . sw_table('steam_workshop_server_mods') . " SET `sort_order` = $idx WHERE `id` = " . (int)$m['id'] . " AND `home_id` = $home_id LIMIT 1" ); } // Find the position of the target mod $pos = -1; foreach ($sorted as $idx => $m) { if ((int)$m['id'] === $mod_id) { $pos = $idx; break; } } if ($pos < 0) { return; } if ($direction === 'move_up' && $pos > 0) { $swap_pos = $pos - 1; } elseif ($direction === 'move_down' && $pos < (count($sorted) - 1)) { $swap_pos = $pos + 1; } else { return; // already at boundary } $swap_id = (int)$sorted[$swap_pos]['id']; // Swap sort_order values $db->query( "UPDATE " . sw_table('steam_workshop_server_mods') . " SET `sort_order` = $swap_pos WHERE `id` = $mod_id AND `home_id` = $home_id LIMIT 1" ); $db->query( "UPDATE " . sw_table('steam_workshop_server_mods') . " SET `sort_order` = $pos WHERE `id` = $swap_id AND `home_id` = $home_id LIMIT 1" ); } function sw_user_queue_update($db, $home_id) { // Mark all enabled mods as 'queued' so the agent picks them up. $db->query( "UPDATE " . sw_table('steam_workshop_server_mods') . " SET `install_status` = 'queued', `updated_at` = NOW() WHERE `home_id` = $home_id AND `enabled` = 1" ); sw_success('All enabled mods queued for update. Run the agent to process downloads.'); } function sw_user_save_settings($db, $home_id) { $ok = sw_save_server_settings($db, $home_id, array( 'update_mode' => $_POST['update_mode'] ?? 'manual', 'restart_behavior' => $_POST['restart_behavior'] ?? 'none', 'hot_load' => $_POST['hot_load'] ?? 'disabled', 'warning_minutes' => $_POST['warning_minutes'] ?? 10, 'schedule_interval' => $_POST['schedule_interval'] ?? 'daily', )); if ($ok) { sw_success('Workshop behavior settings saved.'); } else { sw_error('Failed to save settings.'); } } // ───────────────────────────────────────────────────────────────────────── // Render // ───────────────────────────────────────────────────────────────────────── function sw_user_render($db, $home_id, array $home, array $profile) { $mods = sw_get_server_mods($db, $home_id) ?: array(); $settings = sw_get_server_settings($db, $home_id); // Generate launch params from enabled mods $enabled_mods = array_filter($mods, function ($m) { return !empty($m['enabled']); }); $params = sw_generate_launch_params(array_values($enabled_mods), $profile); $base_url = 'home.php?m=steam_workshop&p=user&home_id=' . $home_id; ?>Server: = sw_h($home['home_name']) ?> Game: = sw_h($home['game_name']) ?> Workshop Profile: = sw_h($profile['config_name']) ?>
No mods added yet. Use the form above to add Workshop IDs.
Based on the enabled mods above (sorted by order). Copy these into your server startup command.
No enabled mods – launch parameters will be empty.
Client mods (-mod=):
Server-side mods (-serverMod=):
Combined:
Clicking Queue Update marks all enabled mods as queued.
Then run the agent on the game server host (where SteamCMD and the game files are located)
to download and install the mods. Adjust the path to the panel's
modules/steam_workshop/agent_update_workshop.php for your server:
php /path/to/panel/modules/steam_workshop/agent_update_workshop.php --home-id== $home_id ?>
Configure how Workshop mods are installed and updated for this server. All options default to the safest setting (manual only, no automatic restarts).
| Setting | Value | Help |
|---|---|---|
| Install / Update Mode |
Manual only – mods are only updated when you click “Queue Update” above. On next restart – queued updates are applied the next time the server restarts. Before every start – the update check runs automatically each time the server starts. Scheduled – the update check runs on the interval set below (requires cron / agent). |
|
| Restart Behavior |
Controls what happens when new mod updates are found. Do not restart – updates are staged but the server keeps running (safe default). If empty – the server is restarted only when there are zero players connected. Immediate with warning – a countdown warning is broadcast, then the server restarts. Next manual restart – updates are installed the next time you manually stop/start the server. |
|
| Hot-Load |
Disabled – no hot-loading; mod changes take effect only after a server restart (safe default). Attempt – if the game supports live mod reloading (e.g. via RCON), try to hot-load instead of restarting. |
|
| Warning Countdown | minutes |
Minutes of advance warning broadcast to players before an automatic restart. Only used when Restart Behavior is set to Restart immediately after warning. Default: 10 minutes. |
| Scheduled Check Interval |
How often the scheduled update check runs. Only used when Install / Update Mode is set to Scheduled update check. Requires the Workshop agent to be running via cron on the game server host. |