feat: Automatische MatchSettings-Erkennung & AdminServ-Bugfixes

This commit is contained in:
Patrick Asmus (scriptos)
2026-03-22 17:05:25 +01:00
parent fdcc82e935
commit 61deb93273
8 changed files with 421 additions and 2 deletions
+6
View File
@@ -55,6 +55,12 @@ 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
# --- MatchSettings ---
# Steuert, welche MatchSettings-Datei beim Serverstart geladen wird.
# "auto" = die neueste .txt-Datei in data/gamedata/Tracks/MatchSettings/ wird automatisch erkannt.
# Alternativ kann ein expliziter Dateiname angegeben werden (z.B. "turnier_settings.txt").
MATCHSETTINGS_FILE=auto
# --- Debugging ---
# Setze diesen Wert auf true, um PHP-Fehlermeldungen anzuzeigen. Dies kann bei der Fehlersuche hilfreich sein, sollte aber in einer Produktionsumgebung auf false belassen werden.
PHP_DISPLAY_ERRORS=false
+10
View File
@@ -76,6 +76,16 @@ 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
# Fix AdminServ MatchSettings-Bugs fuer TmForever:
# 1) get_matchset_mapimport.php: Falscher Pfad-Praefix (MatchSettings/ statt
# des tatsaechlichen Map-Ordners) beim Erstellen von MatchSettings.
# 2) maps-creatematchset.php: GetModeScriptInfo-Aufruf (existiert nur in
# ManiaPlanet/TM2) wird fuer TmForever uebersprungen.
COPY assets/config/adminserv/get_matchset_mapimport.php /var/www/html/resources/ajax/get_matchset_mapimport.php
COPY assets/config/adminserv/maps-creatematchset.php /var/www/html/resources/process/maps-creatematchset.php
RUN chown www-data:www-data /var/www/html/resources/ajax/get_matchset_mapimport.php \
&& chown www-data:www-data /var/www/html/resources/process/maps-creatematchset.php
# AdminServ- und RemoteCP-Dateien als Default-Template sichern (wird beim ersten Start ins Volume kopiert)
RUN cp -a /var/www/html /opt/tmserver/default-controlpanel
+74 -2
View File
@@ -369,6 +369,34 @@ if [ -f "$CUSTOMPOINTS_FILE" ] && ! grep -q 'defined.*pt_custom' "$CUSTOMPOINTS_
echo " CustomPoints-Plugin erfolgreich gepatcht."
fi
# ============================================================
# AdminServ: MatchSettings-Bugfixes fuer bestehende Volumes
# ============================================================
# 1) get_matchset_mapimport.php: Berechnet den relativen Pfad aus dem
# absoluten Dropdown-Pfad statt den URL-Parameter 'd' zu verwenden.
# Ohne Fix wird z.B. "MatchSettings/" statt "Challenges/Downloaded/"
# als Praefix in die MatchSettings-Datei geschrieben.
# 2) maps-creatematchset.php: Ueberspringt GetModeScriptInfo fuer
# TmForever (Methode existiert nur in ManiaPlanet/TM2, Fehler -506).
# ============================================================
ADMINSERV_MAPIMPORT="/var/www/html/resources/ajax/get_matchset_mapimport.php"
ADMINSERV_MAPIMPORT_DEFAULT="/opt/tmserver/default-controlpanel/resources/ajax/get_matchset_mapimport.php"
if [ -f "$ADMINSERV_MAPIMPORT" ] && ! grep -q 'relativePath' "$ADMINSERV_MAPIMPORT"; then
echo "==> Patche AdminServ: MatchSettings Map-Import (Pfad-Fix)..."
cp "$ADMINSERV_MAPIMPORT_DEFAULT" "$ADMINSERV_MAPIMPORT"
chown www-data:www-data "$ADMINSERV_MAPIMPORT"
echo " get_matchset_mapimport.php erfolgreich gepatcht."
fi
ADMINSERV_CREATEMATCHSET="/var/www/html/resources/process/maps-creatematchset.php"
ADMINSERV_CREATEMATCHSET_DEFAULT="/opt/tmserver/default-controlpanel/resources/process/maps-creatematchset.php"
if [ -f "$ADMINSERV_CREATEMATCHSET" ] && grep -q "query('GetModeScriptInfo')" "$ADMINSERV_CREATEMATCHSET" && ! grep -q "SERVER_VERSION_NAME != 'TmForever'" "$ADMINSERV_CREATEMATCHSET"; then
echo "==> Patche AdminServ: GetModeScriptInfo-Fix fuer TmForever..."
cp "$ADMINSERV_CREATEMATCHSET_DEFAULT" "$ADMINSERV_CREATEMATCHSET"
chown www-data:www-data "$ADMINSERV_CREATEMATCHSET"
echo " maps-creatematchset.php erfolgreich gepatcht."
fi
echo "Starting apache server"
service apache2 start
@@ -576,6 +604,50 @@ else
echo "==> Kein AdminServ ServerOptions-Verzeichnis gefunden. Ueberspringe Import."
fi
# ============================================================
# MatchSettings: Neueste Datei automatisch ermitteln
# ============================================================
# Ueber die Umgebungsvariable MATCHSETTINGS_FILE kann gesteuert werden,
# welche MatchSettings-Datei beim Serverstart geladen wird:
# - "auto" (Standard): Die neueste .txt-Datei im MatchSettings-Ordner
# wird automatisch anhand des Aenderungsdatums ermittelt.
# - "<dateiname.txt>": Eine bestimmte Datei wird direkt verwendet.
# Fallback: custom_game_settings.txt (Standard-MatchSettings aus dem Image).
# ============================================================
MATCHSETTINGS_DIR="$GAMEDATA_DIR/Tracks/MatchSettings"
MATCHSETTINGS_FILE_ENV="${MATCHSETTINGS_FILE:-auto}"
if [ "$MATCHSETTINGS_FILE_ENV" = "auto" ]; then
echo "==> MatchSettings: Automatische Erkennung (MATCHSETTINGS_FILE=auto)..."
# Neueste .txt-Datei im MatchSettings-Ordner anhand des Aenderungsdatums ermitteln
NEWEST_MS=$(ls -t "$MATCHSETTINGS_DIR"/*.txt 2>/dev/null | head -1)
if [ -n "$NEWEST_MS" ] && [ -f "$NEWEST_MS" ]; then
MS_FILENAME=$(basename "$NEWEST_MS")
GAME_SETTINGS_PATH="MatchSettings/${MS_FILENAME}"
echo " Neueste MatchSettings gefunden: ${MS_FILENAME}"
echo " Aenderungsdatum: $(stat -c '%y' "$NEWEST_MS" 2>/dev/null || ls -la "$NEWEST_MS" | awk '{print $6, $7, $8}')"
else
GAME_SETTINGS_PATH="MatchSettings/custom_game_settings.txt"
echo " Keine .txt-Dateien in ${MATCHSETTINGS_DIR} gefunden."
echo " Fallback: ${GAME_SETTINGS_PATH}"
fi
else
# Explizit angegebene Datei verwenden
if [ -f "$MATCHSETTINGS_DIR/$MATCHSETTINGS_FILE_ENV" ]; then
GAME_SETTINGS_PATH="MatchSettings/${MATCHSETTINGS_FILE_ENV}"
echo "==> MatchSettings: Verwende explizit gesetzte Datei: ${MATCHSETTINGS_FILE_ENV}"
else
echo "==> WARNUNG: Angegebene MatchSettings-Datei nicht gefunden: ${MATCHSETTINGS_FILE_ENV}"
echo " Vorhandene Dateien in ${MATCHSETTINGS_DIR}:"
ls -la "$MATCHSETTINGS_DIR"/*.txt 2>/dev/null || echo " (keine .txt-Dateien vorhanden)"
GAME_SETTINGS_PATH="MatchSettings/custom_game_settings.txt"
echo " Fallback: ${GAME_SETTINGS_PATH}"
fi
fi
echo " Aktive MatchSettings: ${GAME_SETTINGS_PATH}"
# Bestimme Server-Modus (Standard: internet)
SERVER_MODE="${SERVER_MODE:-internet}"
@@ -595,8 +667,8 @@ fi
echo "Server config dedicated_cfg.txt is"
cat "$CONFIG"
echo "Launching Server in ${SERVER_MODE} mode"
./TrackmaniaServer /dedicated_cfg=dedicated_cfg.txt /game_settings=MatchSettings/custom_game_settings.txt /nodaemon ${LAUNCH_MODE} &
echo "Launching Server in ${SERVER_MODE} mode (MatchSettings: ${GAME_SETTINGS_PATH})"
./TrackmaniaServer /dedicated_cfg=dedicated_cfg.txt /game_settings=${GAME_SETTINGS_PATH} /nodaemon ${LAUNCH_MODE} &
TM_PID=$!
echo "TrackmaniaServer gestartet (PID: ${TM_PID})"
@@ -0,0 +1,76 @@
<?php
// INCLUDES
session_start();
if( !isset($_SESSION['adminserv']['sid']) ){ exit; }
$configPath = '../../'.$_SESSION['adminserv']['path'].'config/';
require_once $configPath.'adminlevel.cfg.php';
require_once $configPath.'adminserv.cfg.php';
require_once $configPath.'extension.cfg.php';
require_once $configPath.'servers.cfg.php';
require_once '../core/adminserv.php';
AdminServConfig::$PATH_RESOURCES = '../';
AdminServ::getClass();
AdminServUI::lang();
// ISSET
if( isset($_GET['path']) ){ $path = addslashes($_GET['path']); }else{ $path = null; }
if( isset($_GET['d']) ){ $directory = addslashes($_GET['d']); }else{ $directory = null; }
if( isset($_GET['op']) ){ $operation = addslashes($_GET['op']); }else{ $operation = null; }
if( isset($_GET['select']) ){ $selection = $_GET['select']; }else{ $selection = null; }
// DATA
$out = null;
if( AdminServ::initialize() && $path != null ){
// Maps
if($path == 'currentServerSelection'){
$mapsImport = AdminServ::getMapList();
}
else{
// FIX: Den relativen Pfad aus dem absoluten Pfad (Dropdown-Auswahl)
// berechnen, statt den URL-Parameter 'd' (= MatchSettings-Speicherordner)
// zu verwenden. Sonst wird z.B. "MatchSettings/" statt "Challenges/Downloaded/"
// als Pfad-Praefix in die MatchSettings-Datei geschrieben.
$mapsDirectoryPath = AdminServ::getMapsDirectoryPath();
$relativePath = str_replace($mapsDirectoryPath, '', Str::toSlash($path));
// Sicherstellen, dass der Pfad mit / endet (wenn nicht leer)
if($relativePath && substr($relativePath, -1) !== '/'){
$relativePath .= '/';
}
$currentDir = Folder::read($path, AdminServConfig::$MATCHSET_HIDDEN_FOLDERS, AdminServConfig::$MATCHSET_EXTENSION, intval(AdminServConfig::RECENT_STATUS_PERIOD * 3600) );
$mapsImport = AdminServ::getLocalMapList($currentDir, $relativePath);
}
// Faire une sélection
if($operation == 'setSelection'){
// On supprime les maps non sélectionnées
if( $selection != null && count($selection) > 0 ){
foreach($mapsImport['lst'] as $id => $values){
if( !in_array($id, $selection) ){
unset($mapsImport['lst'][$id]);
}
}
}
else{
foreach($mapsImport['lst'] as $id => $values){
unset($mapsImport['lst'][$id]);
}
}
}
// Enregistrement de la sélection du MatchSettings
if($operation != 'getSelection'){
AdminServ::saveMatchSettingSelection($mapsImport);
}
$client->Terminate();
}
// OUT
if($operation == 'getSelection'){
echo json_encode($mapsImport);
}
else{
echo json_encode($_SESSION['adminserv']['matchset_maps_selected']);
}
?>
@@ -0,0 +1,160 @@
<?php
// ENREGISTREMENT
if( isset($_POST['savematchsetting']) && isset($_SESSION['adminserv']['matchset_maps_selected']) ){
// Filename
$matchSettingName = Str::replaceChars($_POST['matchSettingName']);
$filename = $data['mapsDirectoryPath'].$args['directory'].$matchSettingName;
if(File::getExtension($matchSettingName) != 'txt'){
$filename .= '.txt';
}
$struct = array();
// Gameinfos
$gameinfos = AdminServ::getGameInfosStructFromPOST();
$struct['gameinfos'] = array(
'game_mode' => $gameinfos['GameMode'],
'chat_time' => $gameinfos['ChatTime'],
'finishtimeout' => $gameinfos['FinishTimeout'],
'allwarmupduration' => $gameinfos['AllWarmUpDuration'],
'disablerespawn' => $gameinfos['DisableRespawn'],
'forceshowallopponents' => $gameinfos['ForceShowAllOpponents'],
'rounds_pointslimit' => $gameinfos['RoundsPointsLimit'],
'rounds_custom_points' => $gameinfos['RoundCustomPoints'],
'rounds_usenewrules' => $gameinfos['RoundsUseNewRules'],
'rounds_forcedlaps' => $gameinfos['RoundsForcedLaps'],
'rounds_pointslimitnewrules' => $gameinfos['RoundsPointsLimitNewRules'],
'team_pointslimit' => $gameinfos['TeamPointsLimit'],
'team_maxpoints' => $gameinfos['TeamMaxPoints'],
'team_usenewrules' => $gameinfos['TeamUseNewRules'],
'team_pointslimitnewrules' => $gameinfos['TeamPointsLimitNewRules'],
'timeattack_limit' => $gameinfos['TimeAttackLimit'],
'timeattack_synchstartperiod' => $gameinfos['TimeAttackSynchStartPeriod'],
'laps_nblaps' => $gameinfos['LapsNbLaps'],
'laps_timelimit' => $gameinfos['LapsTimeLimit'],
'cup_pointslimit' => $gameinfos['CupPointsLimit'],
'cup_roundsperchallenge' => $gameinfos['CupRoundsPerMap'],
'cup_nbwinners' => $gameinfos['CupNbWinners'],
'cup_warmupduration' => $gameinfos['CupWarmUpDuration']
);
if(SERVER_VERSION_NAME != 'TmForever'){
$struct['gameinfos']['script_name'] = $gameinfos['ScriptName'];
}
// HotSeat
$struct['hotseat'] = array(
'game_mode' => intval($_POST['hotSeatGameMode']),
'time_limit' => TimeDate::secToMillisec( intval($_POST['hotSeatTimeLimit']) ),
'rounds_count' => intval($_POST['hotSeatCountRound'])
);
// Filter
$struct['filter'] = array(
'is_lan' => array_key_exists('filterIsLan', $_POST),
'is_internet' => array_key_exists('filterIsInternet', $_POST),
'is_solo' => array_key_exists('filterIsSolo', $_POST),
'is_hotseat' => array_key_exists('filterIsHotSeat', $_POST),
'sort_index' => intval($_POST['filterSortIndex']),
'random_map_order' => array_key_exists('filterRandomMaps', $_POST),
'force_default_gamemode' => intval($_POST['filterDefaultGameMode']),
);
// ScriptSettings (nur fuer ManiaPlanet/TM2, nicht fuer TmForever)
// TmForever kennt die Methode 'GetModeScriptInfo' nicht (Fehler -506).
if(SERVER_VERSION_NAME != 'TmForever'){
if( !$client->query('GetModeScriptInfo') ){
AdminServ::error();
}
else{
$scriptsettings = $client->getResponse();
if( !empty($scriptsettings['ParamDescs']) ){
foreach($scriptsettings['ParamDescs'] as $param){
$struct['scriptsettings'][] = array(
'name' => $param['Name'],
'type' => $param['Type'],
'value' => $param['Default']
);
}
}
}
}
// Maps
$struct['startindex'] = 1;
$maps = $_SESSION['adminserv']['matchset_maps_selected']['lst'];
if( isset($maps) && is_array($maps) && !empty($maps) ){
$mapsField = (SERVER_VERSION_NAME == 'TmForever') ? 'challenge' : 'map';
foreach($maps as $id => $values){
$struct[$mapsField][$values['UId']] = $values['FileName'];
}
}
// Enregistrement
if( ($result = AdminServ::saveMatchSettings($filename, $struct)) !== true ){
AdminServ::error(Utils::t('Unable to save the MatchSettings').' : '.$matchSettingName.' ('.$result.')');
}
else{
$action = Utils::t('The MatchSettings "!matchSettingName" was successfully created in the folder', array('!matchSettingName' => $matchSettingName)).' : '.$data['mapsDirectoryPath'].$args['directory'];
AdminServ::info($action);
AdminServLogs::add('action', $action);
Utils::redirection(false, '?p='.USER_PAGE .$hasDirectory);
}
}
else{
if( !isset($_GET['f']) ){
unset($_SESSION['adminserv']['matchset_maps_selected']);
}
}
// LECTURE
$data['directoryList'] = Folder::getArborescence($data['mapsDirectoryPath'], AdminServConfig::$MAPS_HIDDEN_FOLDERS, substr_count($data['mapsDirectoryPath'], '/'));
$data['matchSettings'] = array();
// Édition
if( isset($_GET['f']) && $_GET['f'] != null ){
$data['pageTitle'] = Utils::t('Edit');
$data['matchSettings']['name'] = $_GET['f'];
$matchSettingsData = AdminServ::getMatchSettingsData($data['mapsDirectoryPath'].$args['directory'].$data['matchSettings']['name']);
$data['gameInfos'] = array(
'curr' => null,
'next' => $matchSettingsData['gameinfos']
);
unset($matchSettingsData['gameinfos']);
$data['matchSettings'] += $matchSettingsData;
if( isset($data['matchSettings']['maps']) ){
$maps = AdminServ::getMapListFromMatchSetting($data['matchSettings']['maps']);
$data['matchSettings']['nbm'] = $maps['nbm']['count'];
$_SESSION['adminserv']['matchset_maps_selected'] = $maps;
}
else{
$data['matchSettings']['nbm'] = 0;
}
}
else{
$data['pageTitle'] = Utils::t('Create');
$data['matchSettings']['name'] = 'match_settings';
$gameInfos = AdminServ::getGameInfos();
$data['gameInfos'] = array(
'curr' => null,
'next' => $gameInfos['next']
);
$data['matchSettings']['hotseat'] = array(
'GameMode' => 1,
'TimeLimit' => 300000,
'RoundsCount' => 5
);
$data['matchSettings']['filter'] = array(
'IsLan' => 1,
'IsInternet' => 1,
'IsSolo' => 0,
'IsHotseat' => 1,
'SortIndex' => 1000,
'RandomMapOrder' => 0,
'ForceDefaultGameMode' => 1
);
$data['matchSettings']['StartIndex'] = 0;
$data['matchSettings']['nbm'] = 0;
}
?>
+32
View File
@@ -66,3 +66,35 @@ rm -rf ./data/controlpanel/*
# Container neu starten AdminServ wird frisch initialisiert
docker compose up -d
```
## Gepatchte AdminServ-Bugs (TmForever)
AdminServ (v2.1.1) enthält zwei Bugs, die speziell im Zusammenspiel mit TmForever auftreten. Diese werden beim Container-Start automatisch gepatcht auch bei bestehenden Volumes.
### Falscher Pfad in MatchSettings-Dateien
Beim Erstellen einer neuen MatchSettings-Datei über AdminServ wurden die Track-Pfade falsch geschrieben. Statt des tatsächlichen Ordners (z.B. `Challenges/Downloaded/`) wurde immer der Speicherort der MatchSettings (`MatchSettings/`) als Pfad-Präfix verwendet:
```xml
<!-- Fehlerhaft (Original) -->
<file>MatchSettings/speed vs. fullspeed.Challenge.Gbx</file>
<!-- Korrekt (nach Patch) -->
<file>Challenges/Downloaded/speed vs. fullspeed.Challenge.Gbx</file>
```
**Ursache:** Die AJAX-Funktion `get_matchset_mapimport.php` hat den URL-Parameter `d` (= MatchSettings-Speicherordner) als relativen Pfad für die Map-Dateinamen verwendet, anstatt den tatsächlichen Ordner aus der Dropdown-Auswahl zu berechnen.
**Betroffene Datei:** `resources/ajax/get_matchset_mapimport.php`
### GetModeScriptInfo-Fehler (-506)
Beim Speichern einer MatchSettings-Datei erschien die Fehlermeldung:
```
[-506] Method 'GetModeScriptInfo' not defined
```
**Ursache:** `GetModeScriptInfo` ist eine XML-RPC-Methode, die nur in ManiaPlanet/TM2 existiert. AdminServ hat sie ohne Versionsprüfung aufgerufen. An anderen Stellen im Code wurde korrekt mit `SERVER_VERSION_NAME != 'TmForever'` unterschieden nur hier fehlte die Prüfung.
**Betroffene Datei:** `resources/process/maps-creatematchset.php`
+46
View File
@@ -160,3 +160,49 @@ Alle diese Parameter können über [Umgebungsvariablen](umgebungsvariablen.md) g
| `<xmlrpc_port>` | `SERVER_XMLRPC_PORT` | `5000` |
| `<connection_uploadrate>` | `SERVER_UPLOAD_RATE` | `512` |
| `<connection_downloadrate>` | `SERVER_DOWNLOAD_RATE` | `8192` |
## MatchSettings (Spieleinstellungen)
Die MatchSettings-Dateien liegen im Verzeichnis `data/gamedata/Tracks/MatchSettings/` und definieren Spielmodus, Regeln und die aktive Track-Liste des Servers. Sie werden als `.txt`-Dateien im XML-Format gespeichert.
### Automatische Erkennung (Standard)
Standardmäßig wird beim Serverstart automatisch die **neueste** `.txt`-Datei im MatchSettings-Ordner anhand des Änderungsdatums geladen. So werden z.B. über AdminServ erstellte oder bearbeitete MatchSettings beim nächsten Neustart automatisch aktiv.
```bash
# In der .env-Datei (Standardwert):
MATCHSETTINGS_FILE=auto
```
**Ablauf bei jedem Containerstart:**
1. Der Ordner `data/gamedata/Tracks/MatchSettings/` wird nach `.txt`-Dateien durchsucht
2. Die Datei mit dem neuesten Änderungsdatum wird ermittelt
3. Diese Datei wird als `/game_settings`-Parameter an den TM-Server übergeben
4. Dateiname und Änderungsdatum werden in der Konsole ausgegeben
### Bestimmte Datei verwenden
Alternativ kann eine bestimmte MatchSettings-Datei direkt angegeben werden:
```bash
# In der .env-Datei:
MATCHSETTINGS_FILE=turnier_settings.txt
```
Der Dateiname bezieht sich immer auf den Ordner `data/gamedata/Tracks/MatchSettings/`.
### Fallback
Falls die angegebene oder automatisch ermittelte Datei nicht existiert, wird auf die mitgelieferte Standard-Datei `custom_game_settings.txt` zurückgefallen.
### Neue MatchSettings über AdminServ erstellen
1. In AdminServ mit SuperAdmin einloggen
2. Unter „Maps" → „MatchSettings" → „Create" eine neue Datei anlegen
3. Tracks aus den gewünschten Ordnern importieren (z.B. `Challenges/Downloaded/`)
4. Spielmodus und Regeln konfigurieren
5. Speichern die Datei wird in `MatchSettings/` abgelegt
6. Container neustarten (`docker compose restart`) die neue Datei wird automatisch als neueste erkannt und geladen
> **Hinweis:** Die aktive MatchSettings-Datei wird beim Serverstart in der Konsole ausgegeben. Mit `docker logs tmserver` kann überprüft werden, welche Datei geladen wurde.
+17
View File
@@ -71,8 +71,25 @@ nano .env
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `MATCHSETTINGS_FILE` | MatchSettings-Datei beim Serverstart: `auto` = neueste `.txt`-Datei im Ordner wird automatisch geladen, oder ein expliziter Dateiname (z.B. `meine_settings.txt`) | `auto` |
| `ALLWARMUPDURATION` | Warmup-Dauer für alle Runden (`0` = deaktiviert, `1` = eine Runde Warmup) | `0` |
### Automatische MatchSettings-Erkennung
Standardmäßig (`MATCHSETTINGS_FILE=auto`) wird beim Serverstart automatisch die **neueste** `.txt`-Datei im Verzeichnis `data/gamedata/Tracks/MatchSettings/` anhand des Änderungsdatums ermittelt und geladen. So werden z.B. über AdminServ exportierte MatchSettings beim nächsten Neustart automatisch aktiv.
**Beispiele:**
```bash
# Automatisch die neueste Datei laden (Standard)
MATCHSETTINGS_FILE=auto
# Eine bestimmte Datei verwenden
MATCHSETTINGS_FILE=turnier_settings.txt
```
> **Hinweis:** Falls die angegebene oder automatisch ermittelte Datei nicht existiert, wird auf `custom_game_settings.txt` zurückgefallen. Die aktiv geladene Datei wird beim Serverstart in der Konsole ausgegeben.
## RemoteCP
RemoteCP verwendet die SuperAdmin-Zugangsdaten (`SERVER_SA_PASSWORD`) des TM-Servers für den Web-Login. Es werden keine separaten Login-Variablen benötigt.