From 3fb1dac5ba21750d80d33f24347247b0deb52abf Mon Sep 17 00:00:00 2001 From: "Patrick Asmus (scriptos)" Date: Sun, 22 Mar 2026 20:58:46 +0100 Subject: [PATCH] feat: Forced Mods (Skins) per Umgebungsvariable beim Containerstart setzen --- .env.example | 14 + Dockerfile | 13 + README.md | 1 + assets/bin/RunTrackmaniaServer.sh | 261 ++++++++++++++++-- .../config/remotecp/plugins/Mods/settings.xml | 93 +++++++ docs/README.md | 8 +- docs/remotecp.md | 55 ++++ docs/umgebungsvariablen.md | 35 +++ 8 files changed, 461 insertions(+), 19 deletions(-) create mode 100644 assets/config/remotecp/plugins/Mods/settings.xml diff --git a/.env.example b/.env.example index 66cd619..cd5a740 100644 --- a/.env.example +++ b/.env.example @@ -55,6 +55,20 @@ SERVER_MODE=internet # In einer Produktionsumgebung sollte dieser Wert jedoch auf false belassen werden, um zu verhindern, dass die Konfiguration versehentlich überschrieben wird. FORCE_CONFIG_UPDATE=false +# --- Forced Mods (Skins) --- +# Beim Containerstart kann automatisch ein Mod (Skin) pro Umgebung forciert werden. +# Der Wert ist die vollständige URL zu einer Mod-ZIP-Datei, die Spieler beim Betreten des Servers herunterladen. +# Verfügbare Skins findest du unter: https://assets.techniverse.net/tm/skins/ +# Beispiel: FORCE_MOD_STADIUM=https://assets.techniverse.net/tm/skins/Portal.zip +# Leer lassen = kein Mod für diese Umgebung. +FORCE_MOD_STADIUM= +FORCE_MOD_ISLAND= +FORCE_MOD_BAY= +FORCE_MOD_COAST= +FORCE_MOD_SPEED= +FORCE_MOD_ALPINE= +FORCE_MOD_RALLY= + # --- MatchSettings --- # Steuert, welche MatchSettings-Datei beim Serverstart geladen wird. # "auto" = die neueste .txt-Datei in data/gamedata/Tracks/MatchSettings/ wird automatisch erkannt. diff --git a/Dockerfile b/Dockerfile index ec1486a..3cc1eda 100644 --- a/Dockerfile +++ b/Dockerfile @@ -76,6 +76,10 @@ RUN unzip /var/www/html/remoteCP_v4.0.3.5.zip -d /var/www/html \ COPY assets/config/remotecp/plugins/CustomPoints/index.php /var/www/html/remotecp/plugins/CustomPoints/index.php RUN chown www-data:www-data /var/www/html/remotecp/plugins/CustomPoints/index.php +# RemoteCP Mods-Plugin: Vorkonfigurierte Skin-Liste (techniverse.net) +COPY assets/config/remotecp/plugins/Mods/settings.xml /var/www/html/remotecp/plugins/Mods/settings.xml +RUN chown www-data:www-data /var/www/html/remotecp/plugins/Mods/settings.xml + # Fix AdminServ MatchSettings-Bugs fuer TmForever: # 1) get_matchset_mapimport.php: Falscher Pfad-Praefix (MatchSettings/ statt # des tatsaechlichen Map-Ordners) beim Erstellen von MatchSettings. @@ -155,6 +159,15 @@ ENV FORCE_CONFIG_UPDATE=false # Spieleinstellungen (MatchSettings) ENV ALLWARMUPDURATION=0 +# Forced Mods (Skins) - URL zu ZIP-Dateien, die beim Start forciert werden +ENV FORCE_MOD_STADIUM="" +ENV FORCE_MOD_ISLAND="" +ENV FORCE_MOD_BAY="" +ENV FORCE_MOD_COAST="" +ENV FORCE_MOD_SPEED="" +ENV FORCE_MOD_ALPINE="" +ENV FORCE_MOD_RALLY="" + # RemoteCP ENV REMOTECP_DB_HOST=mariadb ENV REMOTECP_DB_NAME=remotecp diff --git a/README.md b/README.md index 4ccf5d9..cafe7d4 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ Ein vollständiges Docker-Setup für einen **TrackMania Nations Forever**-Server - **[XAseco](docs/xaseco.md)** – Server-Controller, der lokale Rekorde, Dedimania-Weltrekorde, Karma/Votes und eine Track-Jukebox direkt im Spielchat verwaltet - **[AdminServ](docs/adminserv.md)** – Web-Oberfläche zur komfortablen Verwaltung und Konfiguration des Servers - **[RemoteCP](docs/remotecp.md)** – alternative Web-Verwaltungsoberfläche mit eigenem Login- und Benutzersystem +- **[Mods / Skins](docs/remotecp.md#mods--skins)** – über 50 vorkonfigurierte Stadium-Skins (gehostet auf [assets.techniverse.net](https://assets.techniverse.net/tm/skins/)), automatisch beim Start forcierbar Alle Komponenten laufen in einem einzigen Container und werden über Umgebungsvariablen konfiguriert. diff --git a/assets/bin/RunTrackmaniaServer.sh b/assets/bin/RunTrackmaniaServer.sh index a3e7afe..5e60d82 100644 --- a/assets/bin/RunTrackmaniaServer.sh +++ b/assets/bin/RunTrackmaniaServer.sh @@ -397,6 +397,22 @@ if [ -f "$ADMINSERV_CREATEMATCHSET" ] && grep -q "query('GetModeScriptInfo')" "$ echo " maps-creatematchset.php erfolgreich gepatcht." fi +# ============================================================ +# RemoteCP: Mods-Plugin settings.xml aktualisieren (fuer bestehende Volumes) +# ============================================================ +# Die vorkonfigurierte Skin-Liste aus dem Image wird in das Volume +# kopiert, falls die alte Standard-settings.xml noch vorhanden ist +# (erkennbar am Beispiel-Eintrag "blacksunonline.com"). +# ============================================================ +MODS_SETTINGS_FILE="/var/www/html/remotecp/plugins/Mods/settings.xml" +MODS_SETTINGS_DEFAULT="/opt/tmserver/default-controlpanel/remotecp/plugins/Mods/settings.xml" +if [ -f "$MODS_SETTINGS_FILE" ] && grep -q 'blacksunonline.com' "$MODS_SETTINGS_FILE"; then + echo "==> Aktualisiere RemoteCP Mods-Plugin (Skin-Liste von techniverse.net)..." + cp "$MODS_SETTINGS_DEFAULT" "$MODS_SETTINGS_FILE" + chown www-data:www-data "$MODS_SETTINGS_FILE" + echo " Mods/settings.xml erfolgreich aktualisiert." +fi + echo "Starting apache server" service apache2 start @@ -673,35 +689,248 @@ TM_PID=$! echo "TrackmaniaServer gestartet (PID: ${TM_PID})" # ============================================================ -# XAseco starten (nach TrackmaniaServer) +# Warte auf XMLRPC-Verfuegbarkeit # ============================================================ -if [ "${XASECO_ENABLED:-true}" = "true" ] && [ -f "/opt/tmserver/xaseco/aseco.php" ]; then - echo "==> Warte auf TrackmaniaServer XMLRPC..." - XMLRPC_PORT="${SERVER_XMLRPC_PORT:-5000}" - XMLRPC_READY=false - for i in $(seq 1 30); do - if php -r "@fsockopen('127.0.0.1', ${XMLRPC_PORT}, \$e, \$m, 2) ? exit(0) : exit(1);" 2>/dev/null; then - XMLRPC_READY=true +# Sowohl XAseco als auch Forced Mods benoetigen eine aktive +# XMLRPC-Verbindung zum TrackmaniaServer. +# ============================================================ +echo "==> Warte auf TrackmaniaServer XMLRPC..." +XMLRPC_PORT="${SERVER_XMLRPC_PORT:-5000}" +XMLRPC_READY=false +for i in $(seq 1 30); do + if php -r "@fsockopen('127.0.0.1', ${XMLRPC_PORT}, \$e, \$m, 2) ? exit(0) : exit(1);" 2>/dev/null; then + XMLRPC_READY=true + break + fi + sleep 2 +done + +if [ "$XMLRPC_READY" = "true" ]; then + echo " XMLRPC-Port ${XMLRPC_PORT} erreichbar." +else + echo " WARNUNG: XMLRPC-Port ${XMLRPC_PORT} nicht erreichbar nach 60s!" +fi + +# ============================================================ +# Forced Mods (Skins) per XMLRPC setzen +# ============================================================ +# Wenn FORCE_MOD_*-Variablen gesetzt sind, werden die +# entsprechenden Mods per SetForcedMods-XMLRPC-Aufruf beim +# Serverstart automatisch aktiviert. Dies funktioniert bei +# jedem Containerstart und ist unabhaengig von FORCE_CONFIG_UPDATE. +# ============================================================ + +# Pruefen, ob mindestens ein Mod gesetzt ist (Variable fuer spaeter) +HAS_MODS=false +if [ "$XMLRPC_READY" = "true" ]; then + for ENV_NAME in FORCE_MOD_STADIUM FORCE_MOD_ISLAND FORCE_MOD_BAY FORCE_MOD_COAST FORCE_MOD_SPEED FORCE_MOD_ALPINE FORCE_MOD_RALLY; do + eval "MOD_VAL=\${$ENV_NAME:-}" + if [ -n "$MOD_VAL" ]; then + HAS_MODS=true break fi - sleep 2 done +fi - if [ "$XMLRPC_READY" = "true" ]; then +# ============================================================ +# XAseco starten (nach TrackmaniaServer, VOR Forced Mods) +# ============================================================ +# XAseco muss zuerst starten und sich initialisieren, damit +# es die Forced Mods nicht ueberschreibt oder zuruecksetzt. +# ============================================================ +if [ "$XMLRPC_READY" = "true" ]; then + if [ "${XASECO_ENABLED:-true}" = "true" ] && [ -f "/opt/tmserver/xaseco/aseco.php" ]; then echo "==> Starte XAseco..." cd /opt/tmserver/xaseco php aseco.php TMN >aseco.log 2>&1 & XASECO_PID=$! echo " XAseco gestartet (PID: ${XASECO_PID})" cd /opt/tmserver - else - echo " WARNUNG: XMLRPC-Port ${XMLRPC_PORT} nicht erreichbar nach 60s!" - echo " XAseco wurde NICHT gestartet. Bitte manuell starten." - fi -else - if [ "${XASECO_ENABLED:-true}" != "true" ]; then + elif [ "${XASECO_ENABLED:-true}" != "true" ]; then echo "==> XAseco ist deaktiviert (XASECO_ENABLED=${XASECO_ENABLED})." fi +else + echo " WARNUNG: XMLRPC nicht erreichbar - XAseco und Forced Mods wurden NICHT gestartet." +fi + +# ============================================================ +# Forced Mods (Skins) per XMLRPC setzen +# ============================================================ +# Wird NACH XAseco-Start ausgefuehrt, damit XAseco die Mods +# nicht bei seiner Initialisierung zuruecksetzt. +# ============================================================ +if [ "$XMLRPC_READY" = "true" ] && [ "$HAS_MODS" = "true" ]; then + # Warten, damit XAseco und der TM-Server sich vollstaendig initialisieren + echo "==> Forced Mods: Warte 10 Sekunden auf vollstaendige Server-Initialisierung..." + sleep 10 + + echo "==> Forced Mods: Setze Mods per XMLRPC..." + + # JSON-Array der Mods aufbauen + MODS_JSON="[" + MODS_FIRST=true + for PAIR in "FORCE_MOD_STADIUM:Stadium" "FORCE_MOD_ISLAND:Island" "FORCE_MOD_BAY:Bay" "FORCE_MOD_COAST:Coast" "FORCE_MOD_SPEED:Speed" "FORCE_MOD_ALPINE:Alpine" "FORCE_MOD_RALLY:Rally"; do + VAR_NAME="${PAIR%%:*}" + ENV_NAME="${PAIR##*:}" + eval "MOD_URL=\${$VAR_NAME:-}" + if [ -n "$MOD_URL" ]; then + if [ "$MODS_FIRST" = "true" ]; then + MODS_FIRST=false + else + MODS_JSON="${MODS_JSON}," + fi + # URL fuer JSON escapen (Backslash und Anfuehrungszeichen) + SAFE_URL=$(printf '%s' "$MOD_URL" | sed 's/\\/\\\\/g; s/"/\\"/g') + MODS_JSON="${MODS_JSON}{\"Env\":\"${ENV_NAME}\",\"Url\":\"${SAFE_URL}\"}" + echo " ${ENV_NAME} => ${MOD_URL}" + fi + done + MODS_JSON="${MODS_JSON}]" + + # GBXRemote2-Protokoll: Authenticate + SetForcedMods + SA_PW_MODS="${SERVER_SA_PASSWORD:-SuperAdmin}" + php -r ' + $port = (int)$argv[1]; + $password = $argv[2]; + $modsJson = $argv[3]; + + $mods = json_decode($modsJson, true); + if (empty($mods)) { echo " Keine Mods zu setzen.\n"; exit(0); } + + // GBXRemote2: Verbindung herstellen + $fp = @fsockopen("127.0.0.1", $port, $errno, $errstr, 5); + if (!$fp) { echo " FEHLER: Verbindung zu XMLRPC fehlgeschlagen ($errno: $errstr).\n"; exit(1); } + stream_set_timeout($fp, 10); + + // Handshake lesen (4 Bytes Laenge + Protokollstring) + $data = fread($fp, 4); + if (strlen($data) < 4) { echo " FEHLER: Handshake fehlgeschlagen.\n"; fclose($fp); exit(1); } + $info = unpack("Vsize", $data); + $handshake = fread($fp, $info["size"]); + if (strpos($handshake, "GBXRemote") === false) { + echo " FEHLER: Kein GBXRemote-Protokoll.\n"; fclose($fp); exit(1); + } + echo " Protokoll: $handshake\n"; + + $reqhandle = 0x80000001; + + // XML-RPC-Wert kodieren + function encodeVal($v) { + if (is_bool($v)) return "" . ($v ? "1" : "0") . ""; + if (is_int($v)) return "" . $v . ""; + if (is_string($v)) return "" . htmlspecialchars($v, ENT_XML1) . ""; + if (is_array($v)) { + if (array_keys($v) !== range(0, count($v) - 1)) { + $x = ""; + foreach ($v as $k => $val) $x .= "" . $k . "" . encodeVal($val) . ""; + return $x . ""; + } else { + $x = ""; + foreach ($v as $val) $x .= encodeVal($val); + return $x . ""; + } + } + return "" . htmlspecialchars((string)$v, ENT_XML1) . ""; + } + + // Ein einzelnes Paket vom Server lesen (Header + Body) + function readPacket($fp) { + $header = ""; + while (strlen($header) < 8) { + $chunk = @fread($fp, 8 - strlen($header)); + if ($chunk === false || strlen($chunk) === 0) return false; + $header .= $chunk; + } + $info = unpack("Vsize/Vhandle", $header); + $size = $info["size"]; + $handle = $info["handle"]; + if ($size > 4194304 || $size == 0) return false; + + $body = ""; + $remaining = $size; + while ($remaining > 0) { + $chunk = @fread($fp, min($remaining, 8192)); + if ($chunk === false || strlen($chunk) === 0) break; + $body .= $chunk; + $remaining -= strlen($chunk); + } + return ["handle" => $handle, "body" => $body]; + } + + // XMLRPC-Request senden und Antwort lesen + // Callbacks (handle < 0x80000000) werden uebersprungen + function gbxQuery($fp, &$reqhandle, $method, $params) { + $xml = "" + . "" . $method . ""; + foreach ($params as $p) $xml .= "" . encodeVal($p) . ""; + $xml .= ""; + + $myHandle = $reqhandle++; + $packet = pack("VV", strlen($xml), $myHandle) . $xml; + $written = @fwrite($fp, $packet); + if ($written === false || $written === 0) { + echo " FEHLER: Konnte Request nicht senden ($method).\n"; + return false; + } + + // Auf Antwort warten, Callbacks ueberspringen + for ($attempt = 0; $attempt < 30; $attempt++) { + $pkt = readPacket($fp); + if ($pkt === false) { + echo " FEHLER: Keine Antwort fuer $method.\n"; + return false; + } + // Callback? (Handle < 0x80000000) -> ueberspringen + if ($pkt["handle"] < 0x80000000) { + continue; + } + // Response gefunden + return $pkt["body"]; + } + echo " FEHLER: Zu viele Callbacks, keine Antwort fuer $method.\n"; + return false; + } + + // 1. Authenticate + echo " Authentifiziere als SuperAdmin...\n"; + $resp = gbxQuery($fp, $reqhandle, "Authenticate", ["SuperAdmin", $password]); + if ($resp === false || strpos($resp, "1") === false) { + echo " FEHLER: Authentifizierung fehlgeschlagen.\n"; + if ($resp) echo " Antwort: " . substr(trim($resp), 0, 500) . "\n"; + fclose($fp); exit(1); + } + echo " Authentifizierung erfolgreich.\n"; + + // 2. EnableCallbacks deaktivieren (weniger Rauschen) + gbxQuery($fp, $reqhandle, "EnableCallbacks", [false]); + + // 3. SetForcedMods(override=true, mods=[{Env, Url}, ...]) + // Debug: Zeige das XML das wir senden + $setXml = "" + . "SetForcedMods" + . "" . encodeVal(true) . "" + . "" . encodeVal($mods) . "" + . ""; + echo " Debug SetForcedMods-XML:\n " . $setXml . "\n"; + + echo " Sende SetForcedMods (" . count($mods) . " Mod(s))...\n"; + $resp = gbxQuery($fp, $reqhandle, "SetForcedMods", [true, $mods]); + echo " SetForcedMods-Antwort: " . trim($resp) . "\n"; + if ($resp !== false && strpos($resp, "1") !== false) { + echo " SetForcedMods: OK\n"; + } else { + echo " FEHLER: SetForcedMods fehlgeschlagen.\n"; + } + + // 4. GetForcedMods zur Verifikation (vollstaendige Antwort) + echo " Verifiziere mit GetForcedMods...\n"; + $resp = gbxQuery($fp, $reqhandle, "GetForcedMods", []); + echo " GetForcedMods-Antwort:\n " . trim($resp) . "\n"; + + fclose($fp); + ' "$XMLRPC_PORT" "$SA_PW_MODS" "$MODS_JSON" +elif [ "$XMLRPC_READY" = "true" ]; then + echo "==> Forced Mods: Keine FORCE_MOD_*-Variablen gesetzt. Ueberspringe." fi # Auf TrackmaniaServer warten (Hauptprozess) diff --git a/assets/config/remotecp/plugins/Mods/settings.xml b/assets/config/remotecp/plugins/Mods/settings.xml new file mode 100644 index 0000000..8337ee2 --- /dev/null +++ b/assets/config/remotecp/plugins/Mods/settings.xml @@ -0,0 +1,93 @@ + + + + + + + https://assets.techniverse.net/tm/skins/ARomanorumSuperbia.zip + https://assets.techniverse.net/tm/skins/Bluemod.zip + https://assets.techniverse.net/tm/skins/CANDY_BOX_II.zip + https://assets.techniverse.net/tm/skins/Egyptian_Stadium.zip + https://assets.techniverse.net/tm/skins/FC-mod.zip + https://assets.techniverse.net/tm/skins/FoResT_MoD.zip + https://assets.techniverse.net/tm/skins/ImperialPalace.zip + https://assets.techniverse.net/tm/skins/Inca3.zip + https://assets.techniverse.net/tm/skins/JurassicPark.zip + https://assets.techniverse.net/tm/skins/LMDSMatrix.zip + https://assets.techniverse.net/tm/skins/LegoMod.zip + https://assets.techniverse.net/tm/skins/LegoModv2.zip + https://assets.techniverse.net/tm/skins/Lego_City.zip + https://assets.techniverse.net/tm/skins/MarioMod.zip + https://assets.techniverse.net/tm/skins/MoonBase.zip + https://assets.techniverse.net/tm/skins/Portal.zip + https://assets.techniverse.net/tm/skins/QuantumLeap.zip + https://assets.techniverse.net/tm/skins/RDV-Urban01.zip + https://assets.techniverse.net/tm/skins/Rainbow%20Road.zip + https://assets.techniverse.net/tm/skins/StarWarsMod.zip + https://assets.techniverse.net/tm/skins/ThePirateBay.zip + https://assets.techniverse.net/tm/skins/Toxic.zip + https://assets.techniverse.net/tm/skins/TransparenceV1.zip + https://assets.techniverse.net/tm/skins/WesternFortress.zip + https://assets.techniverse.net/tm/skins/Wood%20Mod.zip + https://assets.techniverse.net/tm/skins/Wooden%20Domnann.zip + https://assets.techniverse.net/tm/skins/Xmas.zip + https://assets.techniverse.net/tm/skins/_N64_%20Rainbow%20Road.zip + https://assets.techniverse.net/tm/skins/bluelight.zip + https://assets.techniverse.net/tm/skins/bluewater.zip + https://assets.techniverse.net/tm/skins/construct.zip + https://assets.techniverse.net/tm/skins/darkmirror.zip + https://assets.techniverse.net/tm/skins/formel1.zip + https://assets.techniverse.net/tm/skins/future.zip + https://assets.techniverse.net/tm/skins/hypercube.zip + https://assets.techniverse.net/tm/skins/icebraker.zip + https://assets.techniverse.net/tm/skins/justblack.zip + https://assets.techniverse.net/tm/skins/lego_II.zip + https://assets.techniverse.net/tm/skins/mars.zip + https://assets.techniverse.net/tm/skins/modernizer.zip + https://assets.techniverse.net/tm/skins/neonglow.zip + https://assets.techniverse.net/tm/skins/pioneer.zip + https://assets.techniverse.net/tm/skins/push.zip + https://assets.techniverse.net/tm/skins/puzzle.zip + https://assets.techniverse.net/tm/skins/robotmod2.zip + https://assets.techniverse.net/tm/skins/rubik.zip + https://assets.techniverse.net/tm/skins/smarties.zip + https://assets.techniverse.net/tm/skins/sonic.zip + https://assets.techniverse.net/tm/skins/stadium2010.zip + https://assets.techniverse.net/tm/skins/stadium_storm_mod.zip + https://assets.techniverse.net/tm/skins/tomb.zip + https://assets.techniverse.net/tm/skins/tronblue.zip + https://assets.techniverse.net/tm/skins/trongreen.zip + https://assets.techniverse.net/tm/skins/tronred.zip + https://assets.techniverse.net/tm/skins/tronyellow.zip + https://assets.techniverse.net/tm/skins/wipeout.zip + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/README.md b/docs/README.md index 7e000d5..bb10a03 100644 --- a/docs/README.md +++ b/docs/README.md @@ -13,7 +13,7 @@ | [Umgebungsvariablen](umgebungsvariablen.md) | Alle verfügbaren Umgebungsvariablen | | [Server-Modi](server-modi.md) | LAN- und Internet-Dedicated-Modus | | [AdminServ](adminserv.md) | Einrichtung der Server-Verwaltungsoberfläche | -| [RemoteCP](remotecp.md) | Alternative Server-Verwaltungsoberfläche | +| [RemoteCP](remotecp.md) | Alternative Server-Verwaltungsoberfläche (inkl. Mods/Skins) | | [XAseco](xaseco.md) | Server-Controller für Rekorde, Karma und Jukebox | | [Ports](ports.md) | Freigegebene Ports und deren Verwendung | @@ -32,8 +32,10 @@ │ │ ├── dedicated_cfg.txt # Server-Config-Template (mit Platzhaltern) │ │ └── remotecp/ │ │ └── plugins/ -│ │ └── CustomPoints/ -│ │ └── index.php # CustomPoints-Plugin fuer RemoteCP +│ │ ├── CustomPoints/ +│ │ │ └── index.php # CustomPoints-Plugin fuer RemoteCP +│ │ └── Mods/ +│ │ └── settings.xml # Skin-Bibliothek (techniverse.net) │ └── db/ │ └── init-xaseco-db.sh # MariaDB Init-Script fuer XAseco-DB ├── docs/ # Dokumentation diff --git a/docs/remotecp.md b/docs/remotecp.md index 13c7860..6283e41 100644 --- a/docs/remotecp.md +++ b/docs/remotecp.md @@ -93,6 +93,61 @@ Die Konfigurationsdateien befinden sich unter `./data/controlpanel/remotecp/xml/ | `admins.xml` | Benutzer und Zugangsdaten | | `groups.xml` | Berechtigungsgruppen | +## Mods / Skins + +RemoteCP enthält ein **Mods-Plugin**, mit dem Texturpakete (Skins) pro Spielumgebung auf dem Server forciert werden können. Spieler laden den jeweiligen Mod automatisch beim Betreten des Servers herunter. + +### Wie funktionieren Mods? + +In TrackMania Forever sind Mods ZIP-Archive mit alternativen Texturen für eine Spielumgebung (Stadium, Island, Bay, etc.). Der Ablauf: + +1. Der Mod liegt als `.zip`-Datei auf einem **Webserver**, der für die Spieler erreichbar ist +2. Der Serveradmin forciert den Mod über den XML-RPC-Befehl `SetForcedMods` (pro Umgebung eine URL) +3. Wenn ein Spieler dem Server beitritt, lädt sein Client den Mod automatisch von der angegebenen URL herunter +4. Mods sind **flüchtig** – nach einem Serverneustart müssen sie erneut gesetzt werden (das Startup-Script übernimmt das automatisch, siehe unten) + +### Vorkonfigurierte Skin-Bibliothek + +Das Image wird mit einer vorkonfigurierten Skin-Bibliothek ausgeliefert, die über **50 Skins** für die Stadium-Umgebung enthält. Alle Skins werden von [assets.techniverse.net](https://assets.techniverse.net/tm/skins/) bereitgestellt und sind im RemoteCP-Mods-Plugin als Dropdown-Auswahl verfügbar. + +Die Konfiguration befindet sich unter: + +| Host-Pfad | Container-Pfad | Beschreibung | +|-----------|----------------|-------------| +| `./data/controlpanel/remotecp/plugins/Mods/settings.xml` | `/var/www/html/remotecp/plugins/Mods/settings.xml` | Mod-Katalog (URLs pro Umgebung) | + +> **Hinweis:** Bei bestehenden Installationen (Volumes) wird die alte Standard-`settings.xml` (mit den Original-Beispiel-URLs von blacksunonline.com) beim nächsten Containerstart automatisch durch die neue Version mit den techniverse.net-Skins ersetzt. + +### Mods über RemoteCP verwalten + +1. RemoteCP öffnen: `http:///remotecp/` +2. Mit SuperAdmin-Zugangsdaten einloggen +3. Im Seitenmenü das **Mods**-Plugin aufrufen +4. Pro Umgebung (Stadium, Island, etc.) einen Skin aus dem Dropdown auswählen +5. "Submit" klicken – der Mod wird sofort auf dem Server aktiviert + +### Mods automatisch beim Start forcieren + +Über die Umgebungsvariablen `FORCE_MOD_*` kann ein Mod pro Umgebung automatisch bei **jedem** Containerstart gesetzt werden – unabhängig von `FORCE_CONFIG_UPDATE`. Siehe [Umgebungsvariablen – Forced Mods](umgebungsvariablen.md#forced-mods-skins) für Details. + +**Beispiel** (in der `.env`-Datei): + +```bash +FORCE_MOD_STADIUM=https://assets.techniverse.net/tm/skins/Portal.zip +``` + +### Eigene Skins hinzufügen + +Um eigene Skins in das RemoteCP-Dropdown aufzunehmen, bearbeite die Datei `data/controlpanel/remotecp/plugins/Mods/settings.xml` und füge innerhalb der gewünschten Umgebung einen neuen Eintrag hinzu: + +```xml + + https://example.com/mods/mein_skin.zip + +``` + +> **Wichtig:** Die ZIP-Datei muss von den Spielern über HTTP/HTTPS erreichbar sein. + ## Sicherheit RemoteCP liefert eine `.htaccess`-Datei mit, die den direkten Zugriff auf XML-Konfigurationsdateien über den Browser verhindert. Apache `mod_rewrite` und `AllowOverride` sind im Image aktiviert, damit dieser Schutz funktioniert. diff --git a/docs/umgebungsvariablen.md b/docs/umgebungsvariablen.md index c1d7068..c439ea4 100644 --- a/docs/umgebungsvariablen.md +++ b/docs/umgebungsvariablen.md @@ -103,6 +103,41 @@ RemoteCP verwendet die SuperAdmin-Zugangsdaten (`SERVER_SA_PASSWORD`) des TM-Ser > **Hinweis:** Diese Werte werden nur beim ersten Start (leeres Volume) angewendet. Weitere Details unter [RemoteCP](remotecp.md). +## Forced Mods (Skins) + +Mods sind Texturpakete (Skins), die das Aussehen einer Spielumgebung komplett verändern. Über `FORCE_MOD_*`-Variablen kann beim Containerstart automatisch ein Mod pro Umgebung forciert werden. Spieler laden den Mod dann automatisch beim Betreten des Servers herunter. + +| Variable | Beschreibung | Standard | +|----------|-------------|----------| +| `FORCE_MOD_STADIUM` | Mod-URL für die Stadium-Umgebung | *(leer)* | +| `FORCE_MOD_ISLAND` | Mod-URL für die Island-Umgebung | *(leer)* | +| `FORCE_MOD_BAY` | Mod-URL für die Bay-Umgebung | *(leer)* | +| `FORCE_MOD_COAST` | Mod-URL für die Coast-Umgebung | *(leer)* | +| `FORCE_MOD_SPEED` | Mod-URL für die Speed-Umgebung | *(leer)* | +| `FORCE_MOD_ALPINE` | Mod-URL für die Alpine-Umgebung | *(leer)* | +| `FORCE_MOD_RALLY` | Mod-URL für die Rally-Umgebung | *(leer)* | + +> **Hinweis:** Die Mods werden per XML-RPC (`SetForcedMods`) bei **jedem** Containerstart gesetzt – unabhängig von `FORCE_CONFIG_UPDATE`. Die URL muss auf eine gültige Mod-ZIP-Datei zeigen, die für die Spieler erreichbar ist. + +### Verfügbare Skins + +Eine Auswahl vorkonfigurierter Skins steht unter `https://assets.techniverse.net/tm/skins/` bereit und ist auch im RemoteCP-Mods-Plugin als Dropdown auswählbar. + +**Beispiel:** + +```bash +# Portal-Mod für Stadium forcieren +FORCE_MOD_STADIUM=https://assets.techniverse.net/tm/skins/Portal.zip + +# Mehrere Umgebungen gleichzeitig +FORCE_MOD_STADIUM=https://assets.techniverse.net/tm/skins/Xmas.zip +FORCE_MOD_ISLAND=https://example.com/mods/island_mod.zip +``` + +### Mods über RemoteCP verwalten + +Zusätzlich zur automatischen Konfiguration per Umgebungsvariable können Mods auch zur Laufzeit über das RemoteCP-Web-Interface (`http:///remotecp/`) im Mods-Plugin per Dropdown ausgewählt und aktiviert werden. + ## MariaDB | Variable | Beschreibung | Standard |