Compare commits

..
32 Commits
Author SHA1 Message Date
scriptos 59826b2e75 Merge pull request 'release-1.2.1' (#12) from release-1.2.1 into master
Reviewed-on: #12
2026-03-22 22:42:26 +00:00
scriptos 5a321db094 Release: Docker Image Version 1.2.1 gepusht 2026-03-22 23:41:40 +01:00
scriptos 344029c551 feat: TeamSpeak3-Plugin mit eigenem Gateway reaktivieren 2026-03-22 23:35:11 +01:00
scriptos bf3b7858ee Merge pull request 'Release: Docker Image Version 1.2.0 gepusht' (#11) from release-1.1.2 into master
Reviewed-on: #11
2026-03-22 20:30:55 +00:00
scriptos ebd20d4653 Merge branch 'master' into release-1.1.2 2026-03-22 20:30:41 +00:00
scriptos fcea5b4d41 Release: Docker Image Version 1.2.0 gepusht 2026-03-22 21:29:37 +01:00
scriptos 6f4426e080 Merge pull request 'release-1.2.0' (#10) from release-1.1.2 into master
Reviewed-on: #10
2026-03-22 20:28:48 +00:00
scriptos ed5b3d22fb fix: PHP-Warnungen im RemoteCP Mods-Plugin behoben (foreach auf leere Umgebungen) 2026-03-22 21:27:03 +01:00
scriptos 3fb1dac5ba feat: Forced Mods (Skins) per Umgebungsvariable beim Containerstart setzen 2026-03-22 20:58:46 +01:00
scriptos 61deb93273 feat: Automatische MatchSettings-Erkennung & AdminServ-Bugfixes 2026-03-22 17:05:25 +01:00
scriptos fdcc82e935 Workspace wird nicht mehr mit committed 2026-03-22 15:24:31 +01:00
scriptos 75ae777f1e feat: Auto-Import von AdminServ ServerOptions beim Containerstart 2026-03-22 15:23:44 +01:00
scriptos b7a909392e Merge pull request 'release-1.1.1' (#7) from release-1.1.1 into master
Reviewed-on: #7
2026-03-22 11:26:40 +00:00
scriptos ec6418e9ab Release: Docker Image Version 1.1.1 gepusht 2026-03-22 12:25:58 +01:00
scriptos f013ccd61e fix: XMLRPC-Port aus externem Port-Mapping entfernen, Doku anpassen 2026-03-22 12:24:08 +01:00
scriptos 0717088eb4 fix: RemoteCP settings.xml-Erstellung entfernt (verursacht Folgefehler bei fehlenden Defaults) 2026-03-22 12:06:16 +01:00
scriptos 5ccba54e84 Merge pull request 'release-1.1.0' (#6) from release-1.0.1 into master
Reviewed-on: #6
2026-03-22 00:56:53 +00:00
scriptos 9b37dfd2bc Release: Docker Image Version 1.1.0 gepusht 2026-03-22 01:55:16 +01:00
scriptos 503fadf1d6 docs: Projektstruktur aktualisiert und in Doku-Uebersicht verschoben 2026-03-22 01:47:47 +01:00
scriptos 3354ebd541 docs: Fertiges Docker Image aus Container-Registry bewerben 2026-03-22 01:44:09 +01:00
scriptos 7482071f2c README: Hinweis auf Repository-Spiegelung von Gitea nach GitHub ergänzt 2026-03-22 01:36:56 +01:00
scriptos 69940ee56b Kurzbeschreibung des Projekts zur README hinzugefügt 2026-03-22 01:32:18 +01:00
scriptos c081e43375 fix(mariadb): XAseco-DB-Variablen explizit im Compose ergänzt 2026-03-22 01:29:10 +01:00
scriptos 35e485b065 docs: Danksagung an Thomas (retronerd.at) in README ergänzt 2026-03-22 01:24:09 +01:00
scriptos b672f84f55 fix: RemoteCP-Benutzerregistrierung standardmäßig deaktivieren 2026-03-22 01:21:07 +01:00
scriptos 76e739f12a refactor(license): Redundante Namensnennung in Lizenzbedingungen entfernt 2026-03-22 01:15:37 +01:00
scriptos 5891429c83 fix: RemoteCP CustomPoints PHP-Warnungen behoben (undefined constants)
Bare-constant-Zugriffe (pt_custom, pt_points, ...) in CustomPoints/index.php
durch defined()-Prüfungen ersetzt, um PHP 7.2+ Warnungen zu vermeiden.
Zusätzlich werden im Produktivmodus (PHP_DISPLAY_ERRORS=false) Warnungen
und Notices in der PHP error_reporting unterdrückt.

- Gepatchte index.php als assets/config/remotecp/plugins/CustomPoints/index.php
- Dockerfile: COPY der gepatchten Datei statt fragiler sed-Patches
- RunTrackmaniaServer.sh: Auto-Patch für bestehende Volumes beim Container-Start
- error_reporting im Produktivmodus um ~E_WARNING & ~E_NOTICE ergänzt

Closes #4
2026-03-22 01:09:37 +01:00
scriptos ffdc11a02b feat: Warmup deaktivieren & als ENV-Variable steuerbar machen
- Default von allwarmupduration in custom_game_settings.txt auf 0 gesetzt
- Neue Umgebungsvariable ALLWARMUPDURATION (Default: 0)
- Startup-Script wendet ENV auf MatchSettings an (inkl. FORCE_CONFIG_UPDATE)
- Dokumentation ergänzt (umgebungsvariablen.md)

Closes #2
2026-03-22 00:29:12 +01:00
scriptos e05de1fbe2 feat: SERVER_LADDER_LIMIT_MAX als ENV-Variable hinzugefügt (Closes #3) 2026-03-22 00:22:15 +01:00
scriptos 495a5a2663 fix: Berechtigungen bei Volume-Kopie korrigiert (cp -a statt cp -r)
Closes #5
2026-03-22 00:09:35 +01:00
scriptos 64e76803e5 Docker-Image in Compose eingefügt. 2026-03-20 23:19:12 +01:00
scriptos 6292025389 Merge pull request 'release-1.0.0' (#1) from release-1.0.0 into master
Reviewed-on: #1
2026-03-20 22:12:00 +00:00
24 changed files with 1789 additions and 85 deletions

No files matched your search

+21
View File
@@ -35,6 +35,7 @@ SERVER_PASSWORD=
SERVER_MAX_SPECTATORS=40 SERVER_MAX_SPECTATORS=40
SERVER_SPEC_PASSWORD= SERVER_SPEC_PASSWORD=
SERVER_LADDER_MODE=forced SERVER_LADDER_MODE=forced
SERVER_LADDER_LIMIT_MAX=60000
# --- Netzwerk --- # --- Netzwerk ---
# Bitte ändere die Ports, wenn sie in deinem Netzwerk bereits verwendet werden. # Bitte ändere die Ports, wenn sie in deinem Netzwerk bereits verwendet werden.
@@ -54,6 +55,26 @@ SERVER_MODE=internet
# In einer Produktionsumgebung sollte dieser Wert jedoch auf false belassen werden, um zu verhindern, dass die Konfiguration versehentlich überschrieben wird. # In einer Produktionsumgebung sollte dieser Wert jedoch auf false belassen werden, um zu verhindern, dass die Konfiguration versehentlich überschrieben wird.
FORCE_CONFIG_UPDATE=false 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.
# Alternativ kann ein expliziter Dateiname angegeben werden (z.B. "turnier_settings.txt").
MATCHSETTINGS_FILE=auto
# --- Debugging --- # --- 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. # 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 PHP_DISPLAY_ERRORS=false
+2
View File
@@ -3,3 +3,5 @@
# Persistente Server-Daten # Persistente Server-Daten
data/ data/
.ki-workspace/
+49 -6
View File
@@ -41,7 +41,7 @@ RUN mkdir -p /opt/tmserver/GameData/Config/AdminServ/ServerOptions \
&& chown -R www-data:www-data /opt/tmserver/GameData/Config/AdminServ && chown -R www-data:www-data /opt/tmserver/GameData/Config/AdminServ
# Gesamtes GameData als Default-Template sichern (wird beim ersten Start ins Volume kopiert) # Gesamtes GameData als Default-Template sichern (wird beim ersten Start ins Volume kopiert)
RUN cp -r /opt/tmserver/GameData /opt/tmserver/default-gamedata RUN cp -a /opt/tmserver/GameData /opt/tmserver/default-gamedata
COPY assets/bin/RunTrackmaniaServer.sh /opt/tmserver/ COPY assets/bin/RunTrackmaniaServer.sh /opt/tmserver/
RUN sed -i 's/\r$//' /opt/tmserver/RunTrackmaniaServer.sh \ RUN sed -i 's/\r$//' /opt/tmserver/RunTrackmaniaServer.sh \
@@ -70,8 +70,35 @@ RUN unzip /var/www/html/remoteCP_v4.0.3.5.zip -d /var/www/html \
&& chmod -R 777 /var/www/html/remotecp/xml/settings \ && chmod -R 777 /var/www/html/remotecp/xml/settings \
&& chown -R www-data:www-data /var/www/html/remotecp/ && chown -R www-data:www-data /var/www/html/remotecp/
# Fix PHP-Warnungen in RemoteCP CustomPoints-Plugin (undefined constants)
# RemoteCP nutzt bare constants (pt_custom, pt_points, ...), die in PHP 7.2+
# Warnungen ausloesen. Das gepatchte Plugin nutzt stattdessen defined()-Pruefungen.
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 PHP-Warnungen in RemoteCP Mods-Plugin (foreach auf leere Umgebungen)
# Leere Umgebungen (Island, Bay, …) fuehrten zu "Invalid argument supplied
# for foreach()", weil $this->mods['Env'] nicht initialisiert war.
# Zusaetzlich bare-constant-Warnungen (pt_*) mit defined()-Pruefungen entschaerft.
COPY assets/config/remotecp/plugins/Mods/index.php /var/www/html/remotecp/plugins/Mods/index.php
RUN chown www-data:www-data /var/www/html/remotecp/plugins/Mods/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.
# 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) # AdminServ- und RemoteCP-Dateien als Default-Template sichern (wird beim ersten Start ins Volume kopiert)
RUN cp -r /var/www/html /opt/tmserver/default-controlpanel RUN cp -a /var/www/html /opt/tmserver/default-controlpanel
# XAseco installieren # XAseco installieren
COPY assets/bin/xaseco_v1.16.zip /opt/tmserver/ COPY assets/bin/xaseco_v1.16.zip /opt/tmserver/
@@ -103,12 +130,16 @@ RUN unzip /opt/tmserver/xaseco_v1.16.zip -d /opt/tmserver/ \
# newinstall-Ordner aufraeumen # newinstall-Ordner aufraeumen
&& rm -rf /opt/tmserver/xaseco/newinstall && rm -rf /opt/tmserver/xaseco/newinstall
# Nicht mehr verfuegbare Plugins deaktivieren (freezone entfernen, teamspeak3 auskommentieren) # Nicht mehr verfuegbares freezone-Plugin entfernen
RUN sed -i '/<plugin>plugin\.freezone\.php<\/plugin>/d' /opt/tmserver/xaseco/plugins.xml \ RUN sed -i '/<plugin>plugin\.freezone\.php<\/plugin>/d' /opt/tmserver/xaseco/plugins.xml
&& sed -i 's/<plugin>plugin\.teamspeak3\.php<\/plugin>/<!-- <plugin>plugin.teamspeak3.php<\/plugin> -->/' /opt/tmserver/xaseco/plugins.xml
# TeamSpeak3-Plugin: Eigenes Gateway einbinden (Original-Gateway nicht mehr verfuegbar)
# Die teamspeak3.xml wird direkt in den XAseco-Ordner kopiert, damit das Plugin
# beim Start automatisch den konfigurierten TS3-Server anzeigt.
COPY assets/config/xaseco/teamspeak3.xml /opt/tmserver/xaseco/teamspeak3.xml
# XAseco als Default-Template sichern (wird beim ersten Start ins Volume kopiert) # XAseco als Default-Template sichern (wird beim ersten Start ins Volume kopiert)
RUN cp -r /opt/tmserver/xaseco /opt/tmserver/default-xaseco RUN cp -a /opt/tmserver/xaseco /opt/tmserver/default-xaseco
# PHP-Debug-Konfiguration: wird zur Laufzeit vom Startup-Script gesetzt # PHP-Debug-Konfiguration: wird zur Laufzeit vom Startup-Script gesetzt
# (kein Rebuild noetig – nur Container neustarten) # (kein Rebuild noetig – nur Container neustarten)
@@ -136,6 +167,18 @@ ENV SERVER_DOWNLOAD_RATE=8192
ENV SERVER_MODE=internet ENV SERVER_MODE=internet
ENV FORCE_CONFIG_UPDATE=false 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 # RemoteCP
ENV REMOTECP_DB_HOST=mariadb ENV REMOTECP_DB_HOST=mariadb
ENV REMOTECP_DB_NAME=remotecp ENV REMOTECP_DB_NAME=remotecp
+3 -5
View File
@@ -23,11 +23,9 @@ unterzulizenzieren und/oder Kopien der Software zu verkaufen, und Personen,
denen die Software zur Verfügung gestellt wird, dies unter den folgenden denen die Software zur Verfügung gestellt wird, dies unter den folgenden
Bedingungen zu gestatten: Bedingungen zu gestatten:
Der obige Urheberrechtshinweis und dieser Genehmigungshinweis müssen in allen Der obige Urheberrechtshinweis einschließlich der Angaben zum Lizenzinhaber
Kopien oder wesentlichen Teilen der Software enthalten sein. Bei jeglicher sowie dieser Genehmigungshinweis müssen in allen Kopien oder wesentlichen
Weiterverarbeitung, Veröffentlichung oder Verbreitung der Software müssen der Teilen der Software enthalten sein.
Name „Patrick Asmus" und die E-Mail-Adresse „support@techniverse.net" des
Urhebers genannt werden.
DIE SOFTWARE WIRD „WIE BESEHEN" OHNE JEGLICHE AUSDRÜCKLICHE ODER DIE SOFTWARE WIRD „WIE BESEHEN" OHNE JEGLICHE AUSDRÜCKLICHE ODER
STILLSCHWEIGENDE GEWÄHRLEISTUNG ZUR VERFÜGUNG GESTELLT, EINSCHLIESSLICH, STILLSCHWEIGENDE GEWÄHRLEISTUNG ZUR VERFÜGUNG GESTELLT, EINSCHLIESSLICH,
+31 -29
View File
@@ -1,6 +1,14 @@
# tmserver-docker # tmserver-docker
Trackmania Nations Forever Docker Server Ein vollständiges Docker-Setup für einen **TrackMania Nations Forever**-Server – inklusive Web-Verwaltung und Server-Controller:
- **TrackMania Dedicated Server** – der eigentliche Spielserver für Internet- oder LAN-Betrieb
- **[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.
> **Hinweis:** Dieses Projekt ist ein Fork von [lduriez/tmserver-docker](https://github.com/lduriez/tmserver-docker?tab=readme-ov-file). > **Hinweis:** Dieses Projekt ist ein Fork von [lduriez/tmserver-docker](https://github.com/lduriez/tmserver-docker?tab=readme-ov-file).
@@ -20,10 +28,20 @@ Passe die Werte in der `.env`-Datei an deine Umgebung an (Passwörter, Masterser
### 2. Server starten ### 2. Server starten
Das fertige Docker Image kann direkt verwendet werden – kein eigener Build nötig:
```bash ```bash
docker compose up -d --build docker compose up -d
``` ```
Das Image wird automatisch aus der Container-Registry geladen:
```
git.techniverse.net/scriptos/trackmania-server:latest
```
> **Tipp:** Alle verfügbaren Tags findest du in der [Container-Registry](https://git.techniverse.net/scriptos/-/packages/container/trackmania-server/). Wenn du das Image lieber selbst bauen möchtest, findest du die Anleitung unter [Schnellstart – Selbst bauen](docs/schnellstart.md#docker-image-selbst-bauen).
### 3. Verwaltungsoberflächen öffnen ### 3. Verwaltungsoberflächen öffnen
- **AdminServ:** `http://<host-ip>/` - **AdminServ:** `http://<host-ip>/`
@@ -31,33 +49,6 @@ docker compose up -d --build
> **Hinweis:** Für den Internet-Modus müssen `SERVER_LOGIN` und `SERVER_VALIDATION_KEY` in der `.env`-Datei gesetzt sein. Einen Server-Account kannst du auf [players.trackmaniaforever.com](https://players.trackmaniaforever.com) erstellen. Für den LAN-Modus setze `SERVER_MODE=lan`. > **Hinweis:** Für den Internet-Modus müssen `SERVER_LOGIN` und `SERVER_VALIDATION_KEY` in der `.env`-Datei gesetzt sein. Einen Server-Account kannst du auf [players.trackmaniaforever.com](https://players.trackmaniaforever.com) erstellen. Für den LAN-Modus setze `SERVER_MODE=lan`.
## Projektstruktur
```
├── assets/
│ ├── bin/ # Binaries und Startscript
│ │ ├── AdminServ_v2.1.1.zip # AdminServ Web-UI
│ │ ├── remoteCP_v4.0.3.5.zip # RemoteCP Web-UI
│ │ ├── xaseco_v1.16.zip # XAseco Server-Controller
│ │ ├── RunTrackmaniaServer.sh # Container-Startscript
│ │ └── TrackmaniaServer_*.zip # Trackmania Server Binary
│ ├── config/
│ ├── custom_game_settings.txt # MatchSettings (Spielmodus, Map-Rotation)
│ └── dedicated_cfg.txt # Server-Config-Template (mit Platzhaltern)
│ └── db/
│ └── init-xaseco-db.sh # MariaDB Init-Script fuer XAseco-DB
├── docs/ # Dokumentation
├── docker-compose.yml # Docker Compose Konfiguration
├── Dockerfile # Docker Build-Definition
├── .env.example # Vorlage fuer Umgebungsvariablen
├── .env # Lokale Umgebungsvariablen (nicht im Git!)
└── data/ # Persistente Daten (zur Laufzeit)
├── gamedata/ # TM-Server-Daten
├── controlpanel/ # AdminServ + RemoteCP
├── xaseco/ # XAseco-Konfiguration und Logs
└── mariadb/ # MariaDB-Datenbankdateien
```
## Dokumentation ## Dokumentation
Die vollständige Dokumentation befindet sich im Ordner [`docs/`](docs/README.md): Die vollständige Dokumentation befindet sich im Ordner [`docs/`](docs/README.md):
@@ -71,6 +62,17 @@ Die vollständige Dokumentation befindet sich im Ordner [`docs/`](docs/README.md
- [XAseco](docs/xaseco.md) – Server-Controller für Rekorde, Karma und Jukebox - [XAseco](docs/xaseco.md) – Server-Controller für Rekorde, Karma und Jukebox
- [Ports](docs/ports.md) – Freigegebene Ports und deren Verwendung - [Ports](docs/ports.md) – Freigegebene Ports und deren Verwendung
## Danksagung
Danke an **Thomas** ([retronerd.at](https://retronerd.at)), dass er mir sein Wissen zur Verfügung gestellt hat und dass er hier im Projekt mitgewirkt hat.
## Spiegelung
Dieses Repository wird von **Gitea** auf **GitHub** gespiegelt. Das Master-Repository befindet sich auf Gitea:
- **Gitea (Master):** [git.techniverse.net/scriptos/tmserver-docker](https://git.techniverse.net/scriptos/tmserver-docker.git)
- **GitHub (Spiegel):** [github.com/pscriptos/tmserver-docker](https://github.com/pscriptos/tmserver-docker.git)
--- ---
📝 **Blog:** [www.cleveradmin.de](https://www.cleveradmin.de) 📝 **Blog:** [www.cleveradmin.de](https://www.cleveradmin.de)
+503 -20
View File
@@ -18,7 +18,7 @@ else
echo "==> PHP-Debug-Modus deaktiviert" echo "==> PHP-Debug-Modus deaktiviert"
cat > "$PHP_INI_DIR/99-adminserv-debug.ini" <<EOF cat > "$PHP_INI_DIR/99-adminserv-debug.ini" <<EOF
display_errors = Off display_errors = Off
error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT & ~E_WARNING & ~E_NOTICE
log_errors = On log_errors = On
error_log = /var/log/php_errors.log error_log = /var/log/php_errors.log
EOF EOF
@@ -38,7 +38,7 @@ DEFAULT_CONTROLPANEL="/opt/tmserver/default-controlpanel"
if [ ! -f "$ADMINSERV_DIR/index.php" ]; then if [ ! -f "$ADMINSERV_DIR/index.php" ]; then
echo "==> Erster Start erkannt: Kopiere AdminServ-Dateien ins Volume..." echo "==> Erster Start erkannt: Kopiere AdminServ-Dateien ins Volume..."
cp -r "$DEFAULT_CONTROLPANEL"/* "$ADMINSERV_DIR/" cp -a "$DEFAULT_CONTROLPANEL"/* "$ADMINSERV_DIR/"
chmod -R 777 "$ADMINSERV_DIR/logs/" chmod -R 777 "$ADMINSERV_DIR/logs/"
chmod 666 "$ADMINSERV_DIR/config/adminlevel.cfg.php" chmod 666 "$ADMINSERV_DIR/config/adminlevel.cfg.php"
chmod 666 "$ADMINSERV_DIR/config/servers.cfg.php" chmod 666 "$ADMINSERV_DIR/config/servers.cfg.php"
@@ -247,7 +247,7 @@ XASECO_ENABLED="${XASECO_ENABLED:-true}"
if [ "$XASECO_ENABLED" = "true" ]; then if [ "$XASECO_ENABLED" = "true" ]; then
if [ ! -f "$XASECO_DIR/aseco.php" ]; then if [ ! -f "$XASECO_DIR/aseco.php" ]; then
echo "==> Erster Start erkannt: Kopiere XAseco-Dateien ins Volume..." echo "==> Erster Start erkannt: Kopiere XAseco-Dateien ins Volume..."
cp -r "$DEFAULT_XASECO"/* "$XASECO_DIR/" cp -a "$DEFAULT_XASECO"/* "$XASECO_DIR/"
XMLRPC_PORT="${SERVER_XMLRPC_PORT:-5000}" XMLRPC_PORT="${SERVER_XMLRPC_PORT:-5000}"
XASECO_ADMIN="${XASECO_MASTERADMIN_LOGIN:-}" XASECO_ADMIN="${XASECO_MASTERADMIN_LOGIN:-}"
@@ -353,10 +353,105 @@ if [ "$XASECO_ENABLED" = "true" ]; then
fi fi
fi fi
# ============================================================
# XAseco: TeamSpeak3-Plugin Gateway aktualisieren (fuer bestehende Volumes)
# ============================================================
# Das Original-TS3-Gateway ist nicht mehr verfuegbar. Die eigene
# teamspeak3.xml mit dem Ersatz-Gateway wird in das Volume kopiert,
# falls sie fehlt oder noch das alte (nicht mehr erreichbare) Gateway
# referenziert. Gleichzeitig wird das Plugin reaktiviert, falls es
# in einer frueheren Version auskommentiert wurde.
# ============================================================
XASECO_DIR_TS3="/opt/tmserver/xaseco"
TS3_XML="$XASECO_DIR_TS3/teamspeak3.xml"
TS3_DEFAULT="/opt/tmserver/default-xaseco/teamspeak3.xml"
TS3_PLUGINS_XML="$XASECO_DIR_TS3/plugins.xml"
# teamspeak3.xml aktualisieren: Kopieren wenn fehlend oder veraltet
if [ -f "$TS3_DEFAULT" ]; then
if [ ! -f "$TS3_XML" ]; then
echo "==> TeamSpeak3-Gateway: teamspeak3.xml fehlt, kopiere aus Template..."
cp "$TS3_DEFAULT" "$TS3_XML"
echo " teamspeak3.xml erfolgreich kopiert."
elif ! diff -q "$TS3_DEFAULT" "$TS3_XML" > /dev/null 2>&1; then
echo "==> TeamSpeak3-Gateway: teamspeak3.xml wird aktualisiert..."
cp "$TS3_DEFAULT" "$TS3_XML"
echo " teamspeak3.xml erfolgreich aktualisiert."
fi
fi
# TS3-Plugin reaktivieren, falls es auskommentiert ist
if [ -f "$TS3_PLUGINS_XML" ] && grep -q '<!-- <plugin>plugin\.teamspeak3\.php</plugin> -->' "$TS3_PLUGINS_XML"; then
echo "==> TeamSpeak3-Plugin: Reaktiviere auskommentiertes Plugin..."
sed -i 's|<!-- <plugin>plugin\.teamspeak3\.php</plugin> -->|<plugin>plugin.teamspeak3.php</plugin>|' "$TS3_PLUGINS_XML"
echo " TeamSpeak3-Plugin erfolgreich aktiviert."
fi
# ============================================================
# RemoteCP: PHP-Warnungen in Plugins fixen (fuer bestehende Volumes)
# ============================================================
# RemoteCP nutzt bare constants (pt_custom, pt_points, ...), die in
# PHP 7.2+ Warnungen ausloesen. Die gepatchte Datei aus dem Image
# wird in das Volume kopiert, falls die alte Version noch vorhanden ist.
# ============================================================
CUSTOMPOINTS_FILE="/var/www/html/remotecp/plugins/CustomPoints/index.php"
CUSTOMPOINTS_DEFAULT="/opt/tmserver/default-controlpanel/remotecp/plugins/CustomPoints/index.php"
if [ -f "$CUSTOMPOINTS_FILE" ] && ! grep -q 'defined.*pt_custom' "$CUSTOMPOINTS_FILE"; then
echo "==> Patche CustomPoints-Plugin (PHP-Warnungen beheben)..."
cp "$CUSTOMPOINTS_DEFAULT" "$CUSTOMPOINTS_FILE"
chown www-data:www-data "$CUSTOMPOINTS_FILE"
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
# ============================================================
# 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" echo "Starting apache server"
service apache2 start service apache2 start
CONFIG="/opt/tmserver/GameData/Config/dedicated_cfg.txt" CONFIG="/opt/tmserver/GameData/Config/dedicated_cfg.txt"
GAME_SETTINGS="/opt/tmserver/GameData/Tracks/MatchSettings/custom_game_settings.txt"
GAMEDATA_DIR="/opt/tmserver/GameData" GAMEDATA_DIR="/opt/tmserver/GameData"
DEFAULT_GAMEDATA="/opt/tmserver/default-gamedata" DEFAULT_GAMEDATA="/opt/tmserver/default-gamedata"
@@ -375,16 +470,20 @@ FORCE_CONFIG_UPDATE="${FORCE_CONFIG_UPDATE:-false}"
if [ ! -f "$CONFIG" ]; then if [ ! -f "$CONFIG" ]; then
echo "==> Erster Start erkannt: Kopiere Default-GameData ins Volume..." echo "==> Erster Start erkannt: Kopiere Default-GameData ins Volume..."
cp -r "$DEFAULT_GAMEDATA"/* "$GAMEDATA_DIR/" cp -a "$DEFAULT_GAMEDATA"/* "$GAMEDATA_DIR/"
chmod -R 777 "$GAMEDATA_DIR/Config/" chmod -R 777 "$GAMEDATA_DIR/Config/"
mkdir -p "$GAMEDATA_DIR/Config/AdminServ/ServerOptions" mkdir -p "$GAMEDATA_DIR/Config/AdminServ/ServerOptions"
chown -R www-data:www-data "$GAMEDATA_DIR/Config/AdminServ" chown -R www-data:www-data "$GAMEDATA_DIR/Config/AdminServ"
# Tracks-Verzeichnis fuer AdminServ beschreibbar machen (Maps-Upload/Download)
chown -R www-data:www-data "$GAMEDATA_DIR/Tracks/"
chmod -R 755 "$GAMEDATA_DIR/Tracks/"
APPLY_ENV=true APPLY_ENV=true
elif [ "$FORCE_CONFIG_UPDATE" = "true" ]; then elif [ "$FORCE_CONFIG_UPDATE" = "true" ]; then
echo "==> FORCE_CONFIG_UPDATE ist aktiv: Umgebungsvariablen werden erneut angewendet..." echo "==> FORCE_CONFIG_UPDATE ist aktiv: Umgebungsvariablen werden erneut angewendet..."
echo " ACHTUNG: Manuelle Aenderungen an den betroffenen Feldern werden ueberschrieben!" echo " ACHTUNG: Manuelle Aenderungen an den betroffenen Feldern werden ueberschrieben!"
# Template neu kopieren, damit alle Platzhalter vorhanden sind # Template neu kopieren, damit alle Platzhalter vorhanden sind
cp "$DEFAULT_GAMEDATA/Config/dedicated_cfg.txt" "$CONFIG" cp "$DEFAULT_GAMEDATA/Config/dedicated_cfg.txt" "$CONFIG"
cp "$DEFAULT_GAMEDATA/Tracks/MatchSettings/custom_game_settings.txt" "$GAME_SETTINGS"
APPLY_ENV=true APPLY_ENV=true
else else
echo "==> Vorhandene Konfiguration gefunden. Umgebungsvariablen werden NICHT angewendet." echo "==> Vorhandene Konfiguration gefunden. Umgebungsvariablen werden NICHT angewendet."
@@ -417,6 +516,7 @@ if [ "$APPLY_ENV" = "true" ]; then
sed -i "s|%%SERVER_MAX_SPECTATORS%%|${SERVER_MAX_SPECTATORS}|g" "$CONFIG" sed -i "s|%%SERVER_MAX_SPECTATORS%%|${SERVER_MAX_SPECTATORS}|g" "$CONFIG"
sed -i "s|%%SERVER_SPEC_PASSWORD%%|${SERVER_SPEC_PASSWORD}|g" "$CONFIG" sed -i "s|%%SERVER_SPEC_PASSWORD%%|${SERVER_SPEC_PASSWORD}|g" "$CONFIG"
sed -i "s|%%SERVER_LADDER_MODE%%|${SERVER_LADDER_MODE}|g" "$CONFIG" sed -i "s|%%SERVER_LADDER_MODE%%|${SERVER_LADDER_MODE}|g" "$CONFIG"
sed -i "s|%%SERVER_LADDER_LIMIT_MAX%%|${SERVER_LADDER_LIMIT_MAX:-60000}|g" "$CONFIG"
# Netzwerk # Netzwerk
sed -i "s|%%SERVER_PORT%%|${SERVER_PORT}|g" "$CONFIG" sed -i "s|%%SERVER_PORT%%|${SERVER_PORT}|g" "$CONFIG"
@@ -425,9 +525,179 @@ if [ "$APPLY_ENV" = "true" ]; then
sed -i "s|%%SERVER_UPLOAD_RATE%%|${SERVER_UPLOAD_RATE}|g" "$CONFIG" sed -i "s|%%SERVER_UPLOAD_RATE%%|${SERVER_UPLOAD_RATE}|g" "$CONFIG"
sed -i "s|%%SERVER_DOWNLOAD_RATE%%|${SERVER_DOWNLOAD_RATE}|g" "$CONFIG" sed -i "s|%%SERVER_DOWNLOAD_RATE%%|${SERVER_DOWNLOAD_RATE}|g" "$CONFIG"
# Spieleinstellungen (MatchSettings)
sed -i "s|<allwarmupduration>[^<]*</allwarmupduration>|<allwarmupduration>${ALLWARMUPDURATION:-0}</allwarmupduration>|" "$GAME_SETTINGS"
echo "Platzhalter erfolgreich ersetzt." echo "Platzhalter erfolgreich ersetzt."
fi fi
# ============================================================
# AdminServ ServerOptions: Exportierte Einstellungen anwenden
# ============================================================
# Falls ein AdminServ-Export in GameData/Config/AdminServ/ServerOptions/
# vorhanden ist, werden die darin enthaltenen Werte (z.B. Servername,
# Beschreibung, Spielerzahl) in die dedicated_cfg.txt uebernommen.
# So bleiben Aenderungen, die ueber AdminServ vorgenommen und exportiert
# wurden, auch nach einem Container-Neustart erhalten.
# ============================================================
ADMINSERV_OPTIONS_DIR="$GAMEDATA_DIR/Config/AdminServ/ServerOptions"
if [ -d "$ADMINSERV_OPTIONS_DIR" ]; then
LATEST_EXPORT=$(ls -t "$ADMINSERV_OPTIONS_DIR"/*.txt "$ADMINSERV_OPTIONS_DIR"/*.xml 2>/dev/null | head -1)
if [ -n "$LATEST_EXPORT" ] && [ -f "$LATEST_EXPORT" ]; then
echo "==> AdminServ ServerOptions-Export gefunden: $(basename "$LATEST_EXPORT")"
echo " Uebernehme exportierte Einstellungen in dedicated_cfg.txt..."
php -r '
$xmlFile = $argv[1];
$cfgFile = $argv[2];
// AdminServ-Export parsen
$dom = new DOMDocument();
if (!@$dom->load($xmlFile)) {
echo " WARNUNG: AdminServ-Export konnte nicht gelesen werden.\n";
exit(0);
}
$root = $dom->documentElement;
$exportValues = [];
foreach ($root->childNodes as $node) {
if ($node->nodeType === XML_ELEMENT_NODE) {
$exportValues[$node->nodeName] = $node->nodeValue;
}
}
// Mapping: AdminServ-XML-Feld => dedicated_cfg.txt-Feld
$mapping = [
"Name" => "name",
"Comment" => "comment",
"HideServer" => "hide_server",
"NextMaxPlayers" => "max_players",
"Password" => "password",
"PasswordForSpectator" => "password_spectator",
"NextMaxSpectators" => "max_spectators",
"NextLadderMode" => "ladder_mode",
"NextCallVoteTimeOut" => "callvote_timeout",
"CallVoteRatio" => "callvote_ratio",
"AllowChallengeDownload" => "allow_challenge_download",
"AutoSaveReplays" => "autosave_replays",
"IsP2PUpload" => "enable_p2p_upload",
"IsP2PDownload" => "enable_p2p_download",
];
// Bool-Felder: 1/0 => True/False (dedicated_cfg.txt-Format)
$boolFields = [
"allow_challenge_download", "autosave_replays",
"enable_p2p_upload", "enable_p2p_download",
];
// Ladder-Modus: 0 => inactive, 1 => forced
$ladderMap = ["0" => "inactive", "1" => "forced"];
// Zu ersetzende Werte aufbauen
$replacements = [];
foreach ($mapping as $xmlField => $cfgField) {
if (isset($exportValues[$xmlField])) {
$value = $exportValues[$xmlField];
if (in_array($cfgField, $boolFields)) {
$value = ($value == "1" || strtolower($value) === "true") ? "True" : "False";
}
if ($cfgField === "ladder_mode" && isset($ladderMap[$value])) {
$value = $ladderMap[$value];
}
$replacements[$cfgField] = $value;
}
}
if (empty($replacements)) {
echo " Keine anwendbaren Einstellungen im Export gefunden.\n";
exit(0);
}
// dedicated_cfg.txt zeilenweise verarbeiten
// Nur Tags innerhalb von <server_options> werden ersetzt,
// damit <name> und <password> in <authorization_levels> unangetastet bleiben.
$lines = file($cfgFile);
$inServerOptions = false;
$updated = 0;
foreach ($lines as $i => $line) {
if (strpos($line, "<server_options>") !== false) {
$inServerOptions = true;
}
if (strpos($line, "</server_options>") !== false) {
$inServerOptions = false;
}
if ($inServerOptions) {
foreach ($replacements as $field => $value) {
$pattern = "/(<" . preg_quote($field, "/") . ">)[^<]*(<\/" . preg_quote($field, "/") . ">)/";
if (preg_match($pattern, $line)) {
$safeValue = htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, "UTF-8");
// $ und \ im Replacement escapen, damit preg_replace
// sie nicht als Backreferences interpretiert (wichtig
// fuer TM-Farbcodes wie $03F, $z, $s etc.)
$escapedValue = str_replace(["\\", "$"], ["\\\\", "\\$"], $safeValue);
$lines[$i] = preg_replace($pattern, "\${1}" . $escapedValue . "\${2}", $line, 1);
echo " " . $field . " => " . $value . "\n";
$updated++;
unset($replacements[$field]);
break;
}
}
}
}
if ($updated > 0) {
file_put_contents($cfgFile, implode("", $lines));
echo " " . $updated . " Einstellung(en) aus AdminServ-Export uebernommen.\n";
}
' "$LATEST_EXPORT" "$CONFIG"
fi
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) # Bestimme Server-Modus (Standard: internet)
SERVER_MODE="${SERVER_MODE:-internet}" SERVER_MODE="${SERVER_MODE:-internet}"
@@ -447,41 +717,254 @@ fi
echo "Server config dedicated_cfg.txt is" echo "Server config dedicated_cfg.txt is"
cat "$CONFIG" cat "$CONFIG"
echo "Launching Server in ${SERVER_MODE} mode" echo "Launching Server in ${SERVER_MODE} mode (MatchSettings: ${GAME_SETTINGS_PATH})"
./TrackmaniaServer /dedicated_cfg=dedicated_cfg.txt /game_settings=MatchSettings/custom_game_settings.txt /nodaemon ${LAUNCH_MODE} & ./TrackmaniaServer /dedicated_cfg=dedicated_cfg.txt /game_settings=${GAME_SETTINGS_PATH} /nodaemon ${LAUNCH_MODE} &
TM_PID=$! TM_PID=$!
echo "TrackmaniaServer gestartet (PID: ${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 # Sowohl XAseco als auch Forced Mods benoetigen eine aktive
echo "==> Warte auf TrackmaniaServer XMLRPC..." # XMLRPC-Verbindung zum TrackmaniaServer.
XMLRPC_PORT="${SERVER_XMLRPC_PORT:-5000}" # ============================================================
XMLRPC_READY=false echo "==> Warte auf TrackmaniaServer XMLRPC..."
for i in $(seq 1 30); do 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 if php -r "@fsockopen('127.0.0.1', ${XMLRPC_PORT}, \$e, \$m, 2) ? exit(0) : exit(1);" 2>/dev/null; then
XMLRPC_READY=true XMLRPC_READY=true
break break
fi fi
sleep 2 sleep 2
done done
if [ "$XMLRPC_READY" = "true" ]; then 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
done
fi
# ============================================================
# 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..." echo "==> Starte XAseco..."
cd /opt/tmserver/xaseco cd /opt/tmserver/xaseco
php aseco.php TMN </dev/null >>aseco.log 2>&1 & php aseco.php TMN </dev/null >>aseco.log 2>&1 &
XASECO_PID=$! XASECO_PID=$!
echo " XAseco gestartet (PID: ${XASECO_PID})" echo " XAseco gestartet (PID: ${XASECO_PID})"
cd /opt/tmserver cd /opt/tmserver
else elif [ "${XASECO_ENABLED:-true}" != "true" ]; then
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
echo "==> XAseco ist deaktiviert (XASECO_ENABLED=${XASECO_ENABLED})." echo "==> XAseco ist deaktiviert (XASECO_ENABLED=${XASECO_ENABLED})."
fi 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 "<value><boolean>" . ($v ? "1" : "0") . "</boolean></value>";
if (is_int($v)) return "<value><int>" . $v . "</int></value>";
if (is_string($v)) return "<value><string>" . htmlspecialchars($v, ENT_XML1) . "</string></value>";
if (is_array($v)) {
if (array_keys($v) !== range(0, count($v) - 1)) {
$x = "<value><struct>";
foreach ($v as $k => $val) $x .= "<member><name>" . $k . "</name>" . encodeVal($val) . "</member>";
return $x . "</struct></value>";
} else {
$x = "<value><array><data>";
foreach ($v as $val) $x .= encodeVal($val);
return $x . "</data></array></value>";
}
}
return "<value><string>" . htmlspecialchars((string)$v, ENT_XML1) . "</string></value>";
}
// 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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
. "<methodCall><methodName>" . $method . "</methodName><params>";
foreach ($params as $p) $xml .= "<param>" . encodeVal($p) . "</param>";
$xml .= "</params></methodCall>";
$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, "<boolean>1</boolean>") === 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 = "<?xml version=\"1.0\" encoding=\"utf-8\"?>"
. "<methodCall><methodName>SetForcedMods</methodName><params>"
. "<param>" . encodeVal(true) . "</param>"
. "<param>" . encodeVal($mods) . "</param>"
. "</params></methodCall>";
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, "<boolean>1</boolean>") !== 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 fi
# Auf TrackmaniaServer warten (Hauptprozess) # Auf TrackmaniaServer warten (Hauptprozess)
@@ -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;
}
?>
+1 -1
View File
@@ -4,7 +4,7 @@
<game_mode>1</game_mode> <game_mode>1</game_mode>
<chat_time>10000</chat_time> <chat_time>10000</chat_time>
<finishtimeout>1</finishtimeout> <finishtimeout>1</finishtimeout>
<allwarmupduration>1</allwarmupduration> <allwarmupduration>0</allwarmupduration>
<disablerespawn>0</disablerespawn> <disablerespawn>0</disablerespawn>
<forceshowallopponents>0</forceshowallopponents> <forceshowallopponents>0</forceshowallopponents>
<rounds_pointslimit>30</rounds_pointslimit> <rounds_pointslimit>30</rounds_pointslimit>
+1 -1
View File
@@ -35,7 +35,7 @@
<ladder_mode>%%SERVER_LADDER_MODE%%</ladder_mode> <!-- value between 'inactive', 'forced' (or '0', '1') --> <ladder_mode>%%SERVER_LADDER_MODE%%</ladder_mode> <!-- value between 'inactive', 'forced' (or '0', '1') -->
<ladder_serverlimit_min>0</ladder_serverlimit_min> <!-- Those values will be clamped to the limits authorized on http://official.trackmania.com/tmf-ladderserver/ --> <ladder_serverlimit_min>0</ladder_serverlimit_min> <!-- Those values will be clamped to the limits authorized on http://official.trackmania.com/tmf-ladderserver/ -->
<ladder_serverlimit_max>50000</ladder_serverlimit_max> <ladder_serverlimit_max>%%SERVER_LADDER_LIMIT_MAX%%</ladder_serverlimit_max>
<enable_p2p_upload>True</enable_p2p_upload> <enable_p2p_upload>True</enable_p2p_upload>
<enable_p2p_download>True</enable_p2p_download> <enable_p2p_download>True</enable_p2p_download>
@@ -0,0 +1,135 @@
<?php
/**
* remoteCP 4
* ütf-8 release
*
* @package remoteCP
* @author hal.sascha
* @copyright (c) 2006-2009
* @version 4.0.3.5
*
* Patched by tmserver-docker:
* - Bare-constant-Zugriffe (pt_custom, pt_points, ...) durch
* defined()-Pruefungen ersetzt, um PHP 7.2+ Warnungen zu vermeiden.
*/
class CustomPoints extends rcp_plugin
{
public $display = 'side';
public $title = 'Points';
public $author = 'hal.ko.sascha';
public $version = '4.0.3.5';
public $nservstatus = array(2,3,4,5);
public $vpermissions = array('editgamesettings');
public $apermissions = array(
'setPoints' => 'editgamesettings',
'setPointsPreset' => 'editgamesettings'
);
public $presets;
public function onLoad()
{
$this->presets = Core::getObject('session')->loadXML(
Core::getSetting('pluginpath') . $this->id . '/presets.xml'
);
}
public function onOutput()
{
$CustomPoints = array();
if (Core::getObject('gbx')->query('GetRoundCustomPoints')) {
$CustomPoints = Core::getObject('gbx')->getResponse();
}
if (!is_array($CustomPoints)) {
$CustomPoints = array();
}
if (!Core::getObject('session')->checkPerm('editgamesettings')) {
return;
}
echo "<form action='ajax.php' method='post' id='custompoints' name='custompoints' class='postcmd' rel='{$this->display}area'>";
echo "<fieldset>";
echo "<div class='legend'>" . (defined('pt_custom') ? pt_custom : 'Custom Points') . "</div>";
echo "<div class='f-row'>
<label for='points'>" . (defined('pt_points') ? pt_points : 'Points') . "</label>
<div class='f-field'>
<input type='text' name='points' id='points' value='" . implode(',', $CustomPoints) . "' />
<div class='small'>" . (defined('pt_commasep') ? pt_commasep : 'Comma separated') . "</div>
</div>";
echo "</div>";
echo "</fieldset>";
echo "<input type='hidden' name='plugin' value='{$this->id}' />";
echo "<input type='hidden' name='action' value='setPoints' />";
echo "<button type='submit' title='" . (defined('ct_submit') ? ct_submit : 'Submit') . "' class='wide'>" . (defined('ct_submit') ? ct_submit : 'Submit') . "</button>";
echo "</form>";
echo "<form action='ajax.php' method='post' id='custompointspreset' name='custompointspreset' class='postcmd' rel='{$this->display}area'>";
echo "<fieldset>";
echo "<div class='legend'>" . (defined('pt_presets') ? pt_presets : 'Presets') . "</div>";
if ($this->presets && is_object($this->presets)) {
foreach ($this->presets->children() as $preset) {
$name = isset($preset['name']) ? $preset['name'] : 'Preset';
$points = isset($preset['points']) ? (string)$preset['points'] : '';
echo "<div class='f-row'>
<label>{$name}</label>
<div class='f-field'>";
echo "<input style='width:25px;' type='radio' name='preset' value='{$points}' /> ";
if (strlen($points) > 25) {
echo substr($points, 0, 25) . "...";
} else {
echo $points;
}
echo "</div>";
echo "</div>";
}
}
echo "</fieldset>";
echo "<input type='hidden' name='plugin' value='{$this->id}' />";
echo "<input type='hidden' name='action' value='setPointsPreset' />";
echo "<button type='submit' title='" . (defined('ct_submit') ? ct_submit : 'Submit') . "' class='wide'>" . (defined('ct_submit') ? ct_submit : 'Submit') . "</button>";
echo "</form>";
}
public function setPoints()
{
if (!array_key_exists('points', $_REQUEST)) return;
$str = preg_replace("/[^0-9,]/", "", $_REQUEST['points']);
$array = $this->makeIntArray(explode(',', $str));
Core::getObject('actions')->add('SetRoundCustomPoints', $array, true);
}
public function setPointsPreset()
{
if (!array_key_exists('preset', $_REQUEST)) return;
$str = preg_replace("/[^0-9,]/", "", $_REQUEST['preset']);
$array = $this->makeIntArray(explode(',', $str));
Core::getObject('actions')->add('SetRoundCustomPoints', $array, true);
}
private function makeIntArray($array)
{
foreach ($array as $key => $value) {
$array[$key] = (int)$value;
}
return $array;
}
}
@@ -0,0 +1,320 @@
<?php
/**
* remoteCP 4
* ütf-8 release
*
* @package remoteCP
* @author hal.sascha
* @copyright (c) 2006-2009
* @version 4.0.2.6
*
* Patch: foreach()-Warnungen behoben – leere Umgebungen (Island, Bay, …)
* fuehrten zu "Invalid argument supplied for foreach()", weil
* $this->mods['Env'] nicht initialisiert war.
* Zusaetzlich bare-constant-Warnungen (pt_*) mit defined()-Pruefungen entschaerft.
*/
class Mods extends rcp_plugin
{
public $display = 'side';
public $title = 'Mods';
public $author = 'hal.ko.sascha';
public $version = '4.0.3.5';
public $nservstatus = array(2,3,4,5);
public $vpermissions = array('editserversettings');
public $apermissions = array(
'setMods' => 'editserversettings',
'setMusic' => 'editserversettings'
);
private $mods = array();
private $music = array();
/**
* Alle bekannten Umgebungs-Keys mit leeren Arrays vorbelegen,
* damit foreach() auch bei fehlenden Eintraegen nicht warnt.
*/
private function initModDefaults()
{
$envs = array('Stadium', 'Island', 'Bay', 'Coast', 'Speed', 'Alpine', 'Rally');
foreach($envs as $env) {
if(!isset($this->mods[$env])) {
$this->mods[$env] = array();
}
}
}
public function onLoadSettings($settings)
{
// Set defaults
$this->mods = array();
$this->music = array();
// Alle Umgebungen vorinitialisieren
$this->initModDefaults();
// Read mods settings
if(!$settings->mods) return;
foreach($settings->mods->children() AS $env)
{
if(!$env) continue;
$tmp = (string) $env->getName();
if(!isset($this->mods[$tmp])) {
$this->mods[$tmp] = array();
}
foreach($env->children() AS $item)
{
$this->mods[$tmp][] = array(
'url' => (string) $item,
'name' => (string) $item['name']
);
}
}
// Read music settings
if(!$settings->music) return;
foreach($settings->music->children() AS $song)
{
$this->music[] = array(
'url' => (string) $song,
'name' => (string) $song['name']
);
}
}
public function onOutput() {
if(Core::getObject('gbx')->query('GetForcedMods')) {
$ForcedMods = Core::getObject('gbx')->getResponse();
if(!empty($ForcedMods)) {
echo "<fieldset>";
echo "<div class='legend'>".(defined('pt_forcedmods') ? pt_forcedmods : 'Forced Mods')."</div>";
if(is_array($ForcedMods['Mods'])) {
foreach($ForcedMods['Mods'] as $mod)
{
echo "<div class='f-row'>
<label>{$mod['Env']}</label>
<div class='f-field'>". $this->getModName($mod['Env'], $mod['Url']) ."</div>";
echo "</div>";
}
}
echo "</fieldset>";
}
}
echo "<form action='ajax.php' method='post' id='forcemods' name='forcemods' class='postcmd' rel='{$this->display}area'>";
echo "<fieldset>";
echo "<div class='legend'>".(defined('pt_forcemods') ? pt_forcemods : 'Force Mods')."</div>";
// --- Stadium ---
echo " <div class='f-row'>
<label for='points'>".(defined('pt_stadium') ? pt_stadium : 'Stadium')."</label>
<div class='f-field'>
<select name='modstadium'>";
echo "<option value='0'>".(defined('pt_none') ? pt_none : 'None')."</option>";
if(!empty($this->mods['Stadium'])) {
foreach($this->mods['Stadium'] AS $mod)
{
echo "<option value='{$mod['url']}'>{$mod['name']}</option>";
}
}
echo " </select>
</div>
</div>";
// --- Island ---
echo " <div class='f-row'>
<label for='points'>".(defined('pt_island') ? pt_island : 'Island')."</label>
<div class='f-field'>
<select name='modisland'>";
echo "<option value='0'>".(defined('pt_none') ? pt_none : 'None')."</option>";
if(!empty($this->mods['Island'])) {
foreach($this->mods['Island'] AS $mod)
{
echo "<option value='{$mod['url']}'>{$mod['name']}</option>";
}
}
echo " </select>
</div>
</div>";
// --- Bay ---
echo "<div class='f-row'>
<label for='points'>".(defined('pt_bay') ? pt_bay : 'Bay')."</label>
<div class='f-field'>
<select name='modbay'>";
echo "<option value='0'>".(defined('pt_none') ? pt_none : 'None')."</option>";
if(!empty($this->mods['Bay'])) {
foreach($this->mods['Bay'] AS $mod)
{
echo "<option value='{$mod['url']}'>{$mod['name']}</option>";
}
}
echo " </select>
</div>
</div>";
// --- Coast ---
echo " <div class='f-row'>
<label for='points'>".(defined('pt_coast') ? pt_coast : 'Coast')."</label>
<div class='f-field'>
<select name='modcoast'>";
echo "<option value='0'>".(defined('pt_none') ? pt_none : 'None')."</option>";
if(!empty($this->mods['Coast'])) {
foreach($this->mods['Coast'] AS $mod)
{
echo "<option value='{$mod['url']}'>{$mod['name']}</option>";
}
}
echo " </select>
</div>
</div>";
// --- Speed ---
echo " <div class='f-row'>
<label for='points'>".(defined('pt_speed') ? pt_speed : 'Speed')."</label>
<div class='f-field'>
<select name='modspeed'>";
echo "<option value='0'>".(defined('pt_none') ? pt_none : 'None')."</option>";
if(!empty($this->mods['Speed'])) {
foreach($this->mods['Speed'] AS $mod)
{
echo "<option value='{$mod['url']}'>{$mod['name']}</option>";
}
}
echo " </select>
</div>
</div>";
// --- Alpine ---
echo " <div class='f-row'>
<label for='points'>".(defined('pt_alpine') ? pt_alpine : 'Alpine')."</label>
<div class='f-field'>
<select name='modalpine'>";
echo "<option value='0'>".(defined('pt_none') ? pt_none : 'None')."</option>";
if(!empty($this->mods['Alpine'])) {
foreach($this->mods['Alpine'] AS $mod)
{
echo "<option value='{$mod['url']}'>{$mod['name']}</option>";
}
}
echo " </select>
</div>
</div>";
// --- Rally ---
echo " <div class='f-row'>
<label for='points'>".(defined('pt_rally') ? pt_rally : 'Rally')."</label>
<div class='f-field'>
<select name='modrally'>";
echo "<option value='0'>".(defined('pt_none') ? pt_none : 'None')."</option>";
if(!empty($this->mods['Rally'])) {
foreach($this->mods['Rally'] AS $mod)
{
echo "<option value='{$mod['url']}'>{$mod['name']}</option>";
}
}
echo " </select>
</div>
</div>";
echo " <div class='f-row'>
<label for='DisableAllMods'>".(defined('pt_disableall') ? pt_disableall : 'Disable All')."</label>
<div class='f-field'><input type='checkbox' class='checkbox' name='DisableAllMods' /></div>
</div>";
echo "</fieldset>";
echo "<input type='hidden' name='plugin' value='{$this->id}' />";
echo "<input type='hidden' name='action' value='setMods' />";
echo "<button type='submit' title='".(defined('ct_submit') ? ct_submit : 'Submit')."' class='wide'>".(defined('ct_submit') ? ct_submit : 'Submit')."</button>";
echo "</form>";
if(Core::getObject('gbx')->query('GetForcedMusic')) {
$ForcedMusic = Core::getObject('gbx')->getResponse();
if(!empty($ForcedMusic)) {
echo "<fieldset>";
echo "<div class='legend'>".(defined('pt_forcedmusic') ? pt_forcedmusic : 'Forced Music')."</div>";
echo "<div class='f-row'>
<label>{$ForcedMusic['File']}</label>
<div class='f-field'>{$ForcedMusic['Url']}</div>";
echo "</div>";
echo "</fieldset>";
}
}
echo "<form action='ajax.php' method='post' id='forcemusic' name='forcemusic' class='postcmd' rel='{$this->display}area'>";
echo "<fieldset>";
echo "<div class='legend'>".(defined('pt_forcemusic') ? pt_forcemusic : 'Force Music')."</div>";
echo "<div class='f-row'>
<label for='song'>".(defined('pt_song') ? pt_song : 'Song')."</label>
<div class='f-field'>
<select name='song'>";
if(!empty($this->music)) {
foreach($this->music AS $song)
{
echo "<option value='{$song['url']}'>{$song['name']}</option>";
}
}
echo " </select>
</div>";
echo " <div class='f-row'>
<label for='DisableAllMusic'>".(defined('pt_disableall') ? pt_disableall : 'Disable All')."</label>
<div class='f-field'><input type='checkbox' class='checkbox' name='DisableAllMusic' /></div>
</div>";
echo "</fieldset>";
echo "<input type='hidden' name='plugin' value='{$this->id}' />";
echo "<input type='hidden' name='action' value='setMusic' />";
echo "<button type='submit' title='".(defined('ct_submit') ? ct_submit : 'Submit')."' class='wide'>".(defined('ct_submit') ? ct_submit : 'Submit')."</button>";
echo "</form>";
}
public function setMods()
{
$array = array();
$override = true;
if(!array_key_exists('DisableAllMods', $_REQUEST)) {
if(!empty($_REQUEST['modstadium']))
$array[] = array('Env' => 'Stadium', 'Url' => $_REQUEST['modstadium']);
if(!empty($_REQUEST['modisland']))
$array[] = array('Env' => 'Island' , 'Url' => $_REQUEST['modisland']);
if(!empty($_REQUEST['modbay']))
$array[] = array('Env' => 'Bay' , 'Url' => $_REQUEST['modbay']);
if(!empty($_REQUEST['modcoast']))
$array[] = array('Env' => 'Coast' , 'Url' => $_REQUEST['modcoast']);
if(!empty($_REQUEST['modspeed']))
$array[] = array('Env' => 'Speed' , 'Url' => $_REQUEST['modspeed']);
if(!empty($_REQUEST['modalpine']))
$array[] = array('Env' => 'Alpine' , 'Url' => $_REQUEST['modalpine']);
if(!empty($_REQUEST['modrally']))
$array[] = array('Env' => 'Rally' , 'Url' => $_REQUEST['modrally']);
} else {
$override = false;
}
Core::getObject('actions')->add('SetForcedMods', $override, $array);
}
public function setMusic()
{
$url = '';
$override = true;
if(!array_key_exists('DisableAllMusic', $_REQUEST)) {
$url = $_REQUEST['song'];
} else {
$override = false;
}
Core::getObject('actions')->add('SetForcedMusic', $override, $url);
}
private function getModName($env, $url)
{
if(!isset($this->mods[$env]) || !is_array($this->mods[$env])) {
return '';
}
foreach($this->mods[$env] AS $value)
{
if($url == $value['url']) {
return $value['name'];
}
}
return '';
}
}
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<settings>
<mods>
<!-- Mods fuer die Stadium-Umgebung -->
<!-- Alle Mods werden von https://assets.techniverse.net/tm/skins/ bereitgestellt -->
<Stadium>
<item name='A Romanorum Superbia'>https://assets.techniverse.net/tm/skins/ARomanorumSuperbia.zip</item>
<item name='Bluemod'>https://assets.techniverse.net/tm/skins/Bluemod.zip</item>
<item name='Candy Box II'>https://assets.techniverse.net/tm/skins/CANDY_BOX_II.zip</item>
<item name='Egyptian Stadium'>https://assets.techniverse.net/tm/skins/Egyptian_Stadium.zip</item>
<item name='FC Mod'>https://assets.techniverse.net/tm/skins/FC-mod.zip</item>
<item name='Forest Mod'>https://assets.techniverse.net/tm/skins/FoResT_MoD.zip</item>
<item name='Imperial Palace'>https://assets.techniverse.net/tm/skins/ImperialPalace.zip</item>
<item name='Inca 3'>https://assets.techniverse.net/tm/skins/Inca3.zip</item>
<item name='Jurassic Park'>https://assets.techniverse.net/tm/skins/JurassicPark.zip</item>
<item name='LMDS Matrix'>https://assets.techniverse.net/tm/skins/LMDSMatrix.zip</item>
<item name='Lego Mod'>https://assets.techniverse.net/tm/skins/LegoMod.zip</item>
<item name='Lego Mod v2'>https://assets.techniverse.net/tm/skins/LegoModv2.zip</item>
<item name='Lego City'>https://assets.techniverse.net/tm/skins/Lego_City.zip</item>
<item name='Mario Mod'>https://assets.techniverse.net/tm/skins/MarioMod.zip</item>
<item name='Moon Base'>https://assets.techniverse.net/tm/skins/MoonBase.zip</item>
<item name='Portal'>https://assets.techniverse.net/tm/skins/Portal.zip</item>
<item name='Quantum Leap'>https://assets.techniverse.net/tm/skins/QuantumLeap.zip</item>
<item name='RDV Urban 01'>https://assets.techniverse.net/tm/skins/RDV-Urban01.zip</item>
<item name='Rainbow Road'>https://assets.techniverse.net/tm/skins/Rainbow%20Road.zip</item>
<item name='Star Wars Mod'>https://assets.techniverse.net/tm/skins/StarWarsMod.zip</item>
<item name='The Pirate Bay'>https://assets.techniverse.net/tm/skins/ThePirateBay.zip</item>
<item name='Toxic'>https://assets.techniverse.net/tm/skins/Toxic.zip</item>
<item name='Transparence V1'>https://assets.techniverse.net/tm/skins/TransparenceV1.zip</item>
<item name='Western Fortress'>https://assets.techniverse.net/tm/skins/WesternFortress.zip</item>
<item name='Wood Mod'>https://assets.techniverse.net/tm/skins/Wood%20Mod.zip</item>
<item name='Wooden Domnann'>https://assets.techniverse.net/tm/skins/Wooden%20Domnann.zip</item>
<item name='Xmas'>https://assets.techniverse.net/tm/skins/Xmas.zip</item>
<item name='N64 Rainbow Road'>https://assets.techniverse.net/tm/skins/_N64_%20Rainbow%20Road.zip</item>
<item name='Blue Light'>https://assets.techniverse.net/tm/skins/bluelight.zip</item>
<item name='Blue Water'>https://assets.techniverse.net/tm/skins/bluewater.zip</item>
<item name='Construct'>https://assets.techniverse.net/tm/skins/construct.zip</item>
<item name='Dark Mirror'>https://assets.techniverse.net/tm/skins/darkmirror.zip</item>
<item name='Formel 1'>https://assets.techniverse.net/tm/skins/formel1.zip</item>
<item name='Future'>https://assets.techniverse.net/tm/skins/future.zip</item>
<item name='Hypercube'>https://assets.techniverse.net/tm/skins/hypercube.zip</item>
<item name='Icebreaker'>https://assets.techniverse.net/tm/skins/icebraker.zip</item>
<item name='Just Black'>https://assets.techniverse.net/tm/skins/justblack.zip</item>
<item name='Lego II'>https://assets.techniverse.net/tm/skins/lego_II.zip</item>
<item name='Mars'>https://assets.techniverse.net/tm/skins/mars.zip</item>
<item name='Modernizer'>https://assets.techniverse.net/tm/skins/modernizer.zip</item>
<item name='Neon Glow'>https://assets.techniverse.net/tm/skins/neonglow.zip</item>
<item name='Pioneer'>https://assets.techniverse.net/tm/skins/pioneer.zip</item>
<item name='Push'>https://assets.techniverse.net/tm/skins/push.zip</item>
<item name='Puzzle'>https://assets.techniverse.net/tm/skins/puzzle.zip</item>
<item name='Robot Mod 2'>https://assets.techniverse.net/tm/skins/robotmod2.zip</item>
<item name='Rubik'>https://assets.techniverse.net/tm/skins/rubik.zip</item>
<item name='Smarties'>https://assets.techniverse.net/tm/skins/smarties.zip</item>
<item name='Sonic'>https://assets.techniverse.net/tm/skins/sonic.zip</item>
<item name='Stadium 2010'>https://assets.techniverse.net/tm/skins/stadium2010.zip</item>
<item name='Stadium Storm'>https://assets.techniverse.net/tm/skins/stadium_storm_mod.zip</item>
<item name='Tomb'>https://assets.techniverse.net/tm/skins/tomb.zip</item>
<item name='Tron Blue'>https://assets.techniverse.net/tm/skins/tronblue.zip</item>
<item name='Tron Green'>https://assets.techniverse.net/tm/skins/trongreen.zip</item>
<item name='Tron Red'>https://assets.techniverse.net/tm/skins/tronred.zip</item>
<item name='Tron Yellow'>https://assets.techniverse.net/tm/skins/tronyellow.zip</item>
<item name='Wipeout'>https://assets.techniverse.net/tm/skins/wipeout.zip</item>
</Stadium>
<!-- Mods fuer die Island-Umgebung -->
<Island>
</Island>
<!-- Mods fuer die Bay-Umgebung -->
<Bay>
</Bay>
<!-- Mods fuer die Coast-Umgebung -->
<Coast>
</Coast>
<!-- Mods fuer die Speed-Umgebung -->
<Speed>
</Speed>
<!-- Mods fuer die Alpine-Umgebung -->
<Alpine>
</Alpine>
<!-- Mods fuer die Rally-Umgebung -->
<Rally>
</Rally>
</mods>
<music>
<!-- Musik-URLs hier eintragen -->
<!-- <item name='Songname'>https://example.com/song.ogg</item> -->
</music>
</settings>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<settings>
<!-- Server Configuration, can be address or ip -->
<server>ts3.techniverse.net</server>
<serverid>1</serverid>
<serverport>9987</serverport>
<queryport>10011</queryport>
<!-- Channel & Update Settings -->
<defaultchannel></defaultchannel>
<subchannel></subchannel>
<channelpassword></channelpassword>
<update_interval>30</update_interval>
<!-- Helpers and images -->
<helperURL>http://assets.techniverse.net/tm/ts3gateway/gateway3.html</helperURL>
<logoURL>http://assets.techniverse.net/tm/ts3gateway/ts3logo.jpg</logoURL>
<!-- Widget position -->
<posx>-64</posx>
<posy>45</posy>
</settings>
+6 -1
View File
@@ -1,5 +1,6 @@
services: services:
tmserver: tmserver:
image: git.techniverse.net/scriptos/trackmania-server:1.2.1
build: build:
context: . context: .
container_name: tmserver container_name: tmserver
@@ -11,7 +12,6 @@ services:
- "${SERVER_PORT:-2350}:${SERVER_PORT:-2350}/tcp" - "${SERVER_PORT:-2350}:${SERVER_PORT:-2350}/tcp"
- "${SERVER_PORT:-2350}:${SERVER_PORT:-2350}/udp" - "${SERVER_PORT:-2350}:${SERVER_PORT:-2350}/udp"
- "${SERVER_P2P_PORT:-3450}:${SERVER_P2P_PORT:-3450}/tcp" - "${SERVER_P2P_PORT:-3450}:${SERVER_P2P_PORT:-3450}/tcp"
- "${SERVER_XMLRPC_PORT:-5000}:${SERVER_XMLRPC_PORT:-5000}/tcp"
- "80:80/tcp" - "80:80/tcp"
env_file: env_file:
- .env - .env
@@ -31,9 +31,14 @@ services:
- .env - .env
environment: environment:
MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD} MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
# RemoteCP-Datenbank (via MariaDB-Init)
MYSQL_DATABASE: ${REMOTECP_DB_NAME:-remotecp} MYSQL_DATABASE: ${REMOTECP_DB_NAME:-remotecp}
MYSQL_USER: ${REMOTECP_DB_USER:-remotecp} MYSQL_USER: ${REMOTECP_DB_USER:-remotecp}
MYSQL_PASSWORD: ${REMOTECP_DB_PASSWORD} MYSQL_PASSWORD: ${REMOTECP_DB_PASSWORD}
# XAseco-Datenbank (via Init-Script init-xaseco-db.sh)
XASECO_DB_NAME: ${XASECO_DB_NAME:-xaseco}
XASECO_DB_USER: ${XASECO_DB_USER:-xaseco}
XASECO_DB_PASSWORD: ${XASECO_DB_PASSWORD}
volumes: volumes:
- ./data/mariadb:/var/lib/mysql - ./data/mariadb:/var/lib/mysql
- ./assets/db/init-xaseco-db.sh:/docker-entrypoint-initdb.d/20-init-xaseco-db.sh:ro - ./assets/db/init-xaseco-db.sh:/docker-entrypoint-initdb.d/20-init-xaseco-db.sh:ro
+36 -1
View File
@@ -13,6 +13,41 @@
| [Umgebungsvariablen](umgebungsvariablen.md) | Alle verfügbaren Umgebungsvariablen | | [Umgebungsvariablen](umgebungsvariablen.md) | Alle verfügbaren Umgebungsvariablen |
| [Server-Modi](server-modi.md) | LAN- und Internet-Dedicated-Modus | | [Server-Modi](server-modi.md) | LAN- und Internet-Dedicated-Modus |
| [AdminServ](adminserv.md) | Einrichtung der Server-Verwaltungsoberfläche | | [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 | | [XAseco](xaseco.md) | Server-Controller für Rekorde, Karma und Jukebox |
| [Ports](ports.md) | Freigegebene Ports und deren Verwendung | | [Ports](ports.md) | Freigegebene Ports und deren Verwendung |
## Projektstruktur
```
├── assets/
│ ├── bin/ # Binaries und Startscript
│ │ ├── AdminServ_v2.1.1.zip # AdminServ Web-UI
│ │ ├── remoteCP_v4.0.3.5.zip # RemoteCP Web-UI
│ │ ├── xaseco_v1.16.zip # XAseco Server-Controller
│ │ ├── RunTrackmaniaServer.sh # Container-Startscript
│ │ └── TrackmaniaServer_*.zip # Trackmania Server Binary
│ ├── config/
│ │ ├── custom_game_settings.txt # MatchSettings (Spielmodus, Map-Rotation)
│ │ ├── dedicated_cfg.txt # Server-Config-Template (mit Platzhaltern)
│ │ └── remotecp/
│ │ └── plugins/
│ │ ├── 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
├── docker-compose.yml # Docker Compose Konfiguration
├── Dockerfile # Docker Build-Definition
├── .env.example # Vorlage fuer Umgebungsvariablen
├── .env # Lokale Umgebungsvariablen (nicht im Git!)
├── LICENSE # Lizenz
├── README.md # Projektbeschreibung
└── data/ # Persistente Daten (zur Laufzeit)
├── gamedata/ # TM-Server-Daten
├── controlpanel/ # AdminServ + RemoteCP
├── xaseco/ # XAseco-Konfiguration und Logs
└── mariadb/ # MariaDB-Datenbankdateien
```
+32
View File
@@ -66,3 +66,35 @@ rm -rf ./data/controlpanel/*
# Container neu starten – AdminServ wird frisch initialisiert # Container neu starten – AdminServ wird frisch initialisiert
docker compose up -d 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`
+80 -1
View File
@@ -88,7 +88,7 @@ docker run -d \
-v ./data/gamedata:/opt/tmserver/GameData \ -v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \ -v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \ -v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver tmserver:latest --name tmserver git.techniverse.net/scriptos/trackmania-server:latest
``` ```
> **Achtung:** Bei `FORCE_CONFIG_UPDATE=true` wird die `dedicated_cfg.txt` komplett aus dem Template neu erzeugt und alle Platzhalter mit den aktuellen Umgebungsvariablen ersetzt. **Manuelle Änderungen an der Config gehen dabei verloren!** Andere Dateien im GameData-Volume (Tracks, Skins, Scores, etc.) bleiben erhalten. Nach dem Update sollte `FORCE_CONFIG_UPDATE` wieder auf `false` gesetzt werden. > **Achtung:** Bei `FORCE_CONFIG_UPDATE=true` wird die `dedicated_cfg.txt` komplett aus dem Template neu erzeugt und alle Platzhalter mit den aktuellen Umgebungsvariablen ersetzt. **Manuelle Änderungen an der Config gehen dabei verloren!** Andere Dateien im GameData-Volume (Tracks, Skins, Scores, etc.) bleiben erhalten. Nach dem Update sollte `FORCE_CONFIG_UPDATE` wieder auf `false` gesetzt werden.
@@ -103,6 +103,39 @@ Der Ordner `GameData/Config/` enthält:
| `blacklist.txt` | Liste gesperrter Spieler | | `blacklist.txt` | Liste gesperrter Spieler |
| `guestlist.txt` | Liste erlaubter Spieler | | `guestlist.txt` | Liste erlaubter Spieler |
| `Default.SystemConfig.Gbx` | System-Konfiguration | | `Default.SystemConfig.Gbx` | System-Konfiguration |
| `AdminServ/ServerOptions/` | Von AdminServ exportierte Server-Einstellungen |
## AdminServ ServerOptions-Import
Wenn über AdminServ Änderungen an den Server-Optionen vorgenommen und als Export gespeichert werden (z.B. Servername, Beschreibung, Spielerzahl), werden diese beim nächsten Container-Start **automatisch** in die `dedicated_cfg.txt` übernommen.
**Ablauf:**
1. In AdminServ unter „Server Options" die gewünschten Einstellungen ändern und „Export" klicken
2. Die exportierte Datei wird in `GameData/Config/AdminServ/ServerOptions/` gespeichert
3. Beim nächsten Start des Containers wird die **neueste** Export-Datei erkannt
4. Die darin enthaltenen Werte werden in die `dedicated_cfg.txt` geschrieben
**Unterstützte Felder:**
| AdminServ-Feld | dedicated_cfg.txt-Feld |
|----------------|----------------------|
| `Name` | `<name>` |
| `Comment` | `<comment>` |
| `HideServer` | `<hide_server>` |
| `NextMaxPlayers` | `<max_players>` |
| `Password` | `<password>` |
| `PasswordForSpectator` | `<password_spectator>` |
| `NextMaxSpectators` | `<max_spectators>` |
| `NextLadderMode` | `<ladder_mode>` |
| `NextCallVoteTimeOut` | `<callvote_timeout>` |
| `CallVoteRatio` | `<callvote_ratio>` |
| `AllowChallengeDownload` | `<allow_challenge_download>` |
| `AutoSaveReplays` | `<autosave_replays>` |
| `IsP2PUpload` | `<enable_p2p_upload>` |
| `IsP2PDownload` | `<enable_p2p_download>` |
> **Hinweis:** Die AdminServ-Exports haben **Vorrang** vor den Werten aus den Umgebungsvariablen. Beim ersten Start werden zunächst die Umgebungsvariablen angewendet, danach die AdminServ-Exports (falls vorhanden). Bei weiteren Starts werden nur die AdminServ-Exports angewendet.
## Wichtige Parameter in der dedicated_cfg.txt ## Wichtige Parameter in der dedicated_cfg.txt
@@ -127,3 +160,49 @@ Alle diese Parameter können über [Umgebungsvariablen](umgebungsvariablen.md) g
| `<xmlrpc_port>` | `SERVER_XMLRPC_PORT` | `5000` | | `<xmlrpc_port>` | `SERVER_XMLRPC_PORT` | `5000` |
| `<connection_uploadrate>` | `SERVER_UPLOAD_RATE` | `512` | | `<connection_uploadrate>` | `SERVER_UPLOAD_RATE` | `512` |
| `<connection_downloadrate>` | `SERVER_DOWNLOAD_RATE` | `8192` | | `<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.
+15 -3
View File
@@ -5,7 +5,7 @@
| 2350 | TCP | Gameserver-Port | | 2350 | TCP | Gameserver-Port |
| 2350 | UDP | Gameserver-Port | | 2350 | UDP | Gameserver-Port |
| 3450 | TCP | P2P-Gameserver-Port | | 3450 | TCP | P2P-Gameserver-Port |
| 5000 | TCP | XML-RPC-Port (interne Kommunikation) | | 5000 | TCP | XML-RPC-Port (nur containerintern, nicht nach außen freigegeben) |
| 80 | TCP | Server-Verwaltungsoberflächen (AdminServ + RemoteCP) | | 80 | TCP | Server-Verwaltungsoberflächen (AdminServ + RemoteCP) |
## Minimale Port-Freigabe ## Minimale Port-Freigabe
@@ -20,7 +20,19 @@ docker run -d \
-p 3450:3450/tcp \ -p 3450:3450/tcp \
-v ./data/gamedata:/opt/tmserver/GameData \ -v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/xaseco:/opt/tmserver/xaseco \ -v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver tmserver:latest --name tmserver git.techniverse.net/scriptos/trackmania-server:latest
``` ```
> **Hinweis:** Port 5000 (XML-RPC) wird intern von AdminServ verwendet und muss in der Regel nicht nach außen freigegeben werden. > **Hinweis:** Port 5000 (XML-RPC) wird containerintern von AdminServ, RemoteCP und XAseco verwendet und ist standardmäßig **nicht** nach außen freigegeben.
>
> Falls du den XML-RPC-Port extern benötigst (z. B. für ein externes Tool außerhalb des Containers), kannst du ihn nachträglich in der `docker-compose.yml` unter `ports:` ergänzen:
>
> ```yaml
> - "${SERVER_XMLRPC_PORT:-5000}:${SERVER_XMLRPC_PORT:-5000}/tcp"
> ```
>
> Bzw. bei `docker run`:
>
> ```bash
> -p 5000:5000/tcp
> ```
+56 -1
View File
@@ -93,6 +93,61 @@ Die Konfigurationsdateien befinden sich unter `./data/controlpanel/remotecp/xml/
| `admins.xml` | Benutzer und Zugangsdaten | | `admins.xml` | Benutzer und Zugangsdaten |
| `groups.xml` | Berechtigungsgruppen | | `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://<host-ip>/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
<Stadium>
<item name='Mein Skin'>https://example.com/mods/mein_skin.zip</item>
</Stadium>
```
> **Wichtig:** Die ZIP-Datei muss von den Spielern über HTTP/HTTPS erreichbar sein.
## Sicherheit ## 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. 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.
@@ -112,4 +167,4 @@ Falls RemoteCP nicht erreichbar ist oder Fehler anzeigt:
- RemoteCP ist ein älteres Tool (Version 4.0.3.5) und wurde für PHP 5.x entwickelt, läuft aber mit PHP 7.4 - RemoteCP ist ein älteres Tool (Version 4.0.3.5) und wurde für PHP 5.x entwickelt, läuft aber mit PHP 7.4
- Die Live-Funktionen (`remoteCP[Live]`) benötigen eine laufende Serververbindung - Die Live-Funktionen (`remoteCP[Live]`) benötigen eine laufende Serververbindung
- Die Registrierung neuer Benutzer ist standardmäßig aktiviert und kann in `xml/settings/settings.xml` über `<register>false</register>` deaktiviert werden - **Sicherheitshinweis:** Die Registrierung neuer Benutzer ist standardmäßig aktiviert. Aus Sicherheitsgründen sollte diese deaktiviert werden, damit sich keine unbefugten Nutzer einen Zugang anlegen können. Dazu in der Datei `data/controlpanel/remotecp/xml/settings/settings.xml` den Wert `<register>false</register>` setzen
+29 -11
View File
@@ -18,24 +18,42 @@ Bearbeite die `.env`-Datei und setze mindestens die gewünschten Passwörter. F
> **Wichtig:** Die `.env`-Datei enthält sensible Daten (Passwörter, Keys) und wird über die `.gitignore` vom Einchecken ausgeschlossen. > **Wichtig:** Die `.env`-Datei enthält sensible Daten (Passwörter, Keys) und wird über die `.gitignore` vom Einchecken ausgeschlossen.
## 2. Docker Image bauen ## 2. Server starten
```bash ### Fertiges Docker Image verwenden (empfohlen)
docker build -t tmserver:latest -t tmserver:1.0.0 .
Es steht ein fertiges Docker Image in der Container-Registry bereit – kein eigener Build nötig:
```
git.techniverse.net/scriptos/trackmania-server:latest
``` ```
Damit wird das Image mit zwei Tags erstellt: `tmserver:latest` und `tmserver:1.0.0`. > **Tipp:** Alle verfügbaren Tags findest du in der [Container-Registry](https://git.techniverse.net/scriptos/-/packages/container/trackmania-server/).
## 3. Server starten #### Mit Docker Compose
### Mit Docker Compose (empfohlen) ```bash
docker compose up -d
```
Die Konfiguration erfolgt über die `.env`-Datei, die automatisch eingelesen wird. Das Image wird automatisch aus der Registry geladen.
### Docker Image selbst bauen
Alternativ kannst du das Image auch selbst bauen:
```bash
docker build -t tmserver:latest -t tmserver:1.1.1 .
```
Damit wird das Image mit zwei Tags erstellt: `tmserver:latest` und `tmserver:1.1.1`.
Anschließend den Server starten:
```bash ```bash
docker compose up -d --build docker compose up -d --build
``` ```
Die Konfiguration erfolgt über die `.env`-Datei, die automatisch eingelesen wird.
### Internet-Modus (docker run) ### Internet-Modus (docker run)
Für den Internet-Modus wird ein Server-Account benötigt. Dieser kann auf der [Trackmania Players-Seite](https://players.trackmaniaforever.com) erstellt werden. Für den Internet-Modus wird ein Server-Account benötigt. Dieser kann auf der [Trackmania Players-Seite](https://players.trackmaniaforever.com) erstellt werden.
@@ -50,7 +68,7 @@ docker run -d \
-v ./data/gamedata:/opt/tmserver/GameData \ -v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \ -v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \ -v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver tmserver:latest --name tmserver git.techniverse.net/scriptos/trackmania-server:latest
``` ```
### LAN-Modus (docker run) ### LAN-Modus (docker run)
@@ -68,10 +86,10 @@ docker run -d \
-v ./data/gamedata:/opt/tmserver/GameData \ -v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \ -v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \ -v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver tmserver:latest --name tmserver git.techniverse.net/scriptos/trackmania-server:latest
``` ```
## 4. Verwaltungsoberflächen öffnen ## 3. Verwaltungsoberflächen öffnen
| Tool | URL | Beschreibung | | Tool | URL | Beschreibung |
|------|-----|-------------| |------|-----|-------------|
+1 -1
View File
@@ -56,5 +56,5 @@ docker run -d \
-v ./data/gamedata:/opt/tmserver/GameData \ -v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \ -v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \ -v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver tmserver:latest --name tmserver git.techniverse.net/scriptos/trackmania-server:latest
``` ```
+67 -4
View File
@@ -48,6 +48,7 @@ nano .env
| `SERVER_MAX_SPECTATORS` | Maximale Zuschaueranzahl | `32` | | `SERVER_MAX_SPECTATORS` | Maximale Zuschaueranzahl | `32` |
| `SERVER_SPEC_PASSWORD` | Zuschauer-Passwort (leer = offen) | *(leer)* | | `SERVER_SPEC_PASSWORD` | Zuschauer-Passwort (leer = offen) | *(leer)* |
| `SERVER_LADDER_MODE` | Ladder-Modus (`inactive` oder `forced`) | `forced` | | `SERVER_LADDER_MODE` | Ladder-Modus (`inactive` oder `forced`) | `forced` |
| `SERVER_LADDER_LIMIT_MAX` | Oberes Ladder-Serverlimit (Punktegrenze) | `60000` |
## Netzwerk ## Netzwerk
@@ -66,6 +67,29 @@ nano .env
| `SERVER_MODE` | Server-Modus (`internet` oder `lan`) | `internet` | | `SERVER_MODE` | Server-Modus (`internet` oder `lan`) | `internet` |
| `FORCE_CONFIG_UPDATE` | Erzwingt erneutes Anwenden aller Umgebungsvariablen auf die Config | `false` | | `FORCE_CONFIG_UPDATE` | Erzwingt erneutes Anwenden aller Umgebungsvariablen auf die Config | `false` |
## Spieleinstellungen (MatchSettings)
| 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
RemoteCP verwendet die SuperAdmin-Zugangsdaten (`SERVER_SA_PASSWORD`) des TM-Servers für den Web-Login. Es werden keine separaten Login-Variablen benötigt. RemoteCP verwendet die SuperAdmin-Zugangsdaten (`SERVER_SA_PASSWORD`) des TM-Servers für den Web-Login. Es werden keine separaten Login-Variablen benötigt.
@@ -79,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). > **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://<host-ip>/remotecp/`) im Mods-Plugin per Dropdown ausgewählt und aktiviert werden.
## MariaDB ## MariaDB
| Variable | Beschreibung | Standard | | Variable | Beschreibung | Standard |
@@ -109,9 +168,11 @@ XAseco ist ein Server-Controller für Rekorde, Karma, Jukebox und mehr. Siehe [X
| Variable | Beschreibung | Standard | | Variable | Beschreibung | Standard |
|----------|-------------|----------| |----------|-------------|----------|
| `PHP_DISPLAY_ERRORS` | Zeigt PHP-Fehlermeldungen im Browser an (nur zur Fehlersuche!) | `false` | | `PHP_DISPLAY_ERRORS` | Aktiviert den PHP-Debug-Modus: Fehlermeldungen im Browser + vollständige Warnungen im Log (nur zur Fehlersuche!) | `false` |
> **Hinweis:** Der Debug-Modus erfordert **keinen** Rebuild des Images. Es genügt, die Variable in der `.env`-Datei zu ändern und den Container neu zu starten (`docker compose restart`). Im Produktivbetrieb sollte `PHP_DISPLAY_ERRORS` immer auf `false` stehen. > **Hinweis:** Der Debug-Modus erfordert **keinen** Rebuild des Images. Es genügt, die Variable in der `.env`-Datei zu ändern und den Container neu zu starten (`docker compose restart`). Im Produktivbetrieb sollte `PHP_DISPLAY_ERRORS` immer auf `false` stehen.
>
> Bei `false` werden nur schwerwiegende Fehler geloggt (keine Warnungen/Notices). Bei `true` werden zusätzlich alle Warnungen und Hinweise angezeigt und geloggt – nützlich zur Fehlersuche bei Problemen mit RemoteCP oder AdminServ.
> **Hinweis:** Bei `FORCE_CONFIG_UPDATE=true` wird die `dedicated_cfg.txt` aus dem Template neu erzeugt und alle Platzhalter mit den aktuellen Umgebungsvariablen ersetzt. Manuelle Änderungen gehen dabei verloren! Nach dem Update sollte `FORCE_CONFIG_UPDATE` wieder auf `false` gesetzt werden. > **Hinweis:** Bei `FORCE_CONFIG_UPDATE=true` wird die `dedicated_cfg.txt` aus dem Template neu erzeugt und alle Platzhalter mit den aktuellen Umgebungsvariablen ersetzt. Manuelle Änderungen gehen dabei verloren! Nach dem Update sollte `FORCE_CONFIG_UPDATE` wieder auf `false` gesetzt werden.
@@ -122,9 +183,11 @@ XAseco ist ein Server-Controller für Rekorde, Karma, Jukebox und mehr. Siehe [X
Passe die Werte in der `.env`-Datei an und starte mit: Passe die Werte in der `.env`-Datei an und starte mit:
```bash ```bash
docker compose up -d --build docker compose up -d
``` ```
> **Tipp:** Das fertige Docker Image wird automatisch aus der [Container-Registry](https://git.techniverse.net/scriptos/-/packages/container/trackmania-server/) geladen. Wenn du das Image selbst bauen möchtest, verwende stattdessen `docker compose up -d --build`.
### docker run ### docker run
```bash ```bash
@@ -137,7 +200,7 @@ docker run -d \
-v ./data/gamedata:/opt/tmserver/GameData \ -v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \ -v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \ -v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver tmserver:latest --name tmserver git.techniverse.net/scriptos/trackmania-server:latest
``` ```
Einzelne Werte können zusätzlich überschrieben werden: Einzelne Werte können zusätzlich überschrieben werden:
@@ -154,5 +217,5 @@ docker run -d \
-v ./data/gamedata:/opt/tmserver/GameData \ -v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \ -v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \ -v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver tmserver:latest --name tmserver git.techniverse.net/scriptos/trackmania-server:latest
``` ```
+49
View File
@@ -8,6 +8,55 @@ Im Container wird die modifizierte Version **XAseco 1.16** verwendet, die für P
XAseco verbindet sich über XML-RPC mit dem TrackMania-Server und reagiert auf Spielereignisse (neue Rekorde, Spieler-Connects, Chat-Befehle usw.). Die Daten werden in einer eigenen MySQL-Datenbank gespeichert. XAseco verbindet sich über XML-RPC mit dem TrackMania-Server und reagiert auf Spielereignisse (neue Rekorde, Spieler-Connects, Chat-Befehle usw.). Die Daten werden in einer eigenen MySQL-Datenbank gespeichert.
## TeamSpeak 3 Integration
XAseco enthält ein Plugin (`plugin.teamspeak3.php`), das im Spiel ein Widget mit den aktuell verbundenen TeamSpeak-3-Nutzern anzeigt. Das Plugin ist **standardmäßig aktiviert** und verwendet ein eigenes Gateway, das im Container mitgeliefert wird (das Original-Gateway des Plugin-Entwicklers ist nicht mehr verfügbar).
### TS3-Server konfigurieren
Die Konfiguration erfolgt über die Datei `data/xaseco/teamspeak3.xml`. Dort kann der eigene TeamSpeak-3-Server eingetragen werden:
```xml
<?xml version="1.0" encoding="utf-8"?>
<settings>
<!-- Server Configuration, can be address or ip -->
<server>ts3.techniverse.net</server>
<serverid>1</serverid>
<serverport>9987</serverport>
<queryport>10011</queryport>
<!-- Channel & Update Settings -->
<defaultchannel></defaultchannel>
<subchannel></subchannel>
<channelpassword></channelpassword>
<update_interval>30</update_interval>
<!-- Helpers and images -->
<helperURL>http://assets.techniverse.net/tm/ts3gateway/gateway3.html</helperURL>
<logoURL>http://assets.techniverse.net/tm/ts3gateway/ts3logo.jpg</logoURL>
<!-- Widget position -->
<posx>-64</posx>
<posy>45</posy>
</settings>
```
| Feld | Beschreibung |
|------|-------------|
| `server` | Hostname oder IP des TS3-Servers |
| `serverid` | Virtuelle-Server-ID (meist `1`) |
| `serverport` | Voice-Port des TS3-Servers (Standard: `9987`) |
| `queryport` | ServerQuery-Port (Standard: `10011`) |
| `defaultchannel` | Standard-Channel (leer = Server-Default) |
| `subchannel` | Sub-Channel (optional) |
| `channelpassword` | Channel-Passwort (optional) |
| `update_interval` | Aktualisierungsintervall in Sekunden |
| `helperURL` | URL zur Gateway-HTML-Seite (nicht ändern) |
| `logoURL` | URL zum TS3-Logo im Widget (nicht ändern) |
| `posx` / `posy` | Widget-Position im Spiel |
> **Hinweis:** Standardmäßig ist der TS3-Server `ts3.techniverse.net` vorkonfiguriert. Zum Anpassen einfach nach dem ersten Start die Datei `data/xaseco/teamspeak3.xml` bearbeiten. Die Felder `helperURL` und `logoURL` verweisen auf das mitgelieferte Gateway und sollten nicht geändert werden.
## Konfiguration ## Konfiguration
Die Konfiguration erfolgt ausschließlich über Umgebungsvariablen in der `.env`-Datei. Beim **ersten Start** (leeres XAseco-Volume) werden die Werte automatisch in die XML-Konfigurationsdateien eingetragen. Die Konfiguration erfolgt ausschließlich über Umgebungsvariablen in der `.env`-Datei. Beim **ersten Start** (leeres XAseco-Volume) werden die Werte automatisch in die XML-Konfigurationsdateien eingetragen.