Compare commits

..
88 Commits
Author SHA1 Message Date
Patrick Asmus 9a19e40e3d fix(ci): Image explizit fuer linux/amd64 bauen
Build & Push Docker Image / build-and-push (push) Successful in 1m58s
Das Image v1.4.0 wurde auf einem arm64-Runner gebaut und startet auf
amd64-Hosts nicht (exec format error). Ich baue jetzt per buildx mit
--platform linux/amd64 (QEMU als Fallback). docker-compose.yml zeigt auf
1.4.1.
2026-11-01 17:22:27 +01:00
Patrick Asmus 7578e72334 docs: PHP-8-Hinweis in README, Image-Tag auf 1.4.0
Build & Push Docker Image / build-and-push (push) Successful in 1m45s
Ich weise in der README auf den Umstieg auf PHP 8.4 hin und bitte darum,
auftretende Fehler als Issue zu melden. docker-compose.yml zeigt jetzt
auf das Image 1.4.0.
2026-11-01 17:14:51 +01:00
Patrick Asmus 8b6a34e8d1 fix(xaseco): Neustart-Schleifen durch TS3-Plugin und Dedimania beheben
- tsstatus.php: ungefangene Exception bei fehlerhafter TS3-Query-Antwort
  beendete XAseco (auch unter PHP 7, live 58x im Log); jetzt leere Antwort
- XAsecoHealthcheck.sh: Webaccess-/Dedimania-Verbindungsfehler nicht mehr
  als XAseco-Fehler werten; das nicht erreichbare dedimania.net loeste
  sonst alle ~30 Minuten einen Neustart aus
2026-11-01 17:06:59 +01:00
Patrick Asmus 96798c2db6 fix(php8): AdminServ MatchSettings-Erstellung und Zeitlimit-Ueberlauf beheben
- folder.class.php: @array_slice(@stat()) brach unter PHP 8 mit TypeError
  ab, wenn Map-Dateinamen Sonderzeichen enthalten (MatchSettings erstellen
  -> HTTP 500)
- timedate.class.php: secToMillisec() auf int32 begrenzen; LapsTimeLimit
  wird in ms angezeigt und beim Speichern erneut mit 1000 multipliziert,
  der Server lehnte 2147483647000 ab (bestehender AdminServ-Bug)
2026-11-01 14:07:01 +01:00
Patrick Asmus 9f8f0f00d6 fix(php8): RemoteCP-Log-Rauschen reduzieren und errors.log rotieren
Unter PHP 8 sind viele fruehere Notices (Undefined array key, Attempt to
read property ...) Warnings. Der RemoteCP-Errorhandler ignoriert diese
ohne Debug-Modus wie bisher unter PHP 7; im Test wuchs errors.log sonst
um rund 500 KB in 15 Minuten. Zusaetzlich rotiere ich remotecp/cache/*.log
per logrotate (10 MB, 5 Dateien).
2026-11-01 13:51:55 +01:00
Patrick Asmus 1ecb6c93b3 fix(php8): statische Aufrufe, XAseco-Webaccess und TS3-count() fuer PHP 8 anpassen
- Php8Compat.php: nicht-statische Methoden, die als Klasse::methode()
  aufgerufen werden und kein $this nutzen, als static markieren
  (RemoteCP Core/Plugins, AdminServ Zip)
- web_access.inc.php: Response als array() statt '' initialisieren
  (behebt Illegal string offset in web_access, xmlrpc_db, mania_karma)
- plugin.teamspeak3.php: count() auf null entschaerft
2026-11-01 13:25:23 +01:00
Patrick Asmus b13b524ad5 feat(php8): Umstieg auf Debian Trixie mit PHP 8.4 (WIP)
PHP 7.4 ist seit 2022 EOL und bekommt keine Fixes mehr (u. a. PHP2Shell,
CVE-2026-109618/109619). Ich stelle das Image deshalb auf debian:trixie-slim
mit PHP 8.4.27 um.

- Php8Compat.php: ergaenzt __construct() fuer PHP-4-Konstruktoren und stellt
  String-Offsets $str{0} auf $str[0] um (AdminServ, RemoteCP, XAseco)
- RunTrackmaniaServer.sh: Patcher bei jedem Start auf die Volumes anwenden,
  mysqli_report(MYSQLI_REPORT_OFF) fuer den mysql_*-Shim von XAseco
- Bullseye-Archive-Repositories entfernt
2026-11-01 13:16:00 +01:00
Patrick Asmus 5ca2f1587f fix(remotecp): PHP-7.4-Deprecations beheben und PHP_DISPLAY_ERRORS respektieren
RemoteCP 4.0.3.5 hat nach jedem Neustart Deprecation-Meldungen oben auf der
Seite ausgegeben, obwohl PHP_DISPLAY_ERRORS=false gesetzt war.

Ich patche RemoteCP jetzt bei jedem Containerstart idempotent (greift auch
bei bestehenden Volumes):
- rcp_textparser.class.php: $hex{n} -> $hex[n], TMFColorParser-Konstruktor
  auf __construct() umgestellt
- GbxRemote.inc.php: $f_v{n} -> $f_v[n]
- core.functions.php: display_errors/display_startup_errors an
  PHP_DISPLAY_ERRORS gekoppelt statt hart auf 1; PHP-Warnings werden ohne
  Debug-Modus nur noch in cache/errors.log geschrieben
2026-11-01 12:47:14 +01:00
Patrick Asmus 592f047274 fix(docker): Debian Bullseye Archive-Repositories fuer Paketinstallation nutzen
Build & Push Docker Image / build-and-push (push) Successful in 1m18s
2026-10-31 11:36:15 +01:00
Patrick Asmus d550e814e2 fix: AdminServ Schreibrechte fuer ./logs/ und Configs bei Containerstart sicherstellen (Fixes #19)
Build & Push Docker Image / build-and-push (push) Failing after 20s
AdminServ erfordert zwingend Schreibrechte auf ./logs/ (777) und die Konfigurationsdateien (666). Das fruehere automatische Zuruecksetzen auf 755 fuehrte nach Reboots oder Wiederherstellung von Backups zum Fehler 'path was not required rights: ./logs/'. Bei jedem Start werden nun 777 und www-data Ownership fuer ./logs/ sichergestellt.
2026-10-31 11:27:30 +01:00
Patrick Asmus 9ac5c997a2 feat: Upgrade ip-watcher Image auf alpine:3.24 und Netzwerk-Ports aktualisiert
Upgrade des ip-watcher Hilfscontainers von alpine:3.21 auf alpine:3.24. Zudem Web-Port auf 16080 umgestellt, P2P UDP 3450 hinzugefügt und lokales Subnetz aktualisiert.
2026-10-31 11:06:48 +01:00
scriptos 49bc08129a Update Readme.md 2026-04-23 13:28:58 +02:00
scriptos 7325400175 Update Readme.md 2026-04-23 13:27:28 +02:00
scriptos 22733ce651 Merge pull request 'release-1.3.2' (#18) from release-1.3.2 into master
Build & Push Docker Image / build-and-push (push) Successful in 51s
Reviewed-on: #18
2026-03-26 22:08:33 +00:00
scriptos 2382546308 Release: Docker Image Version 1.3.2 gepusht 2026-03-26 23:05:50 +01:00
scriptos e89ac156ce fix(ip-watcher): IP-Prüfung bei unveränderter IP im Log ausgeben 2026-03-26 23:03:09 +01:00
scriptos 32a09ba808 docs: Update-Anleitung hinzugefügt, .env.example um fehlende XAseco-Healthcheck-Variablen ergänzt 2026-03-26 22:59:26 +01:00
scriptos 931c62eb6b docs: IP-Watcher in Dokumentationsübersicht der README ergänzt 2026-03-26 22:54:39 +01:00
scriptos 44dde46067 fix: logrotate-Fehler durch unsichere Permissions am logs-Verzeichnis beheben 2026-03-26 22:52:21 +01:00
scriptos 14c47bbdd7 Merge pull request 'release-1.3.1' (#17) from release-1.3.1 into master
Build & Push Docker Image / build-and-push (push) Successful in 4m6s
Reviewed-on: #17
2026-03-26 20:27:09 +00:00
scriptos 5601b1469d Release: Docker Image Version 1.3.1 gepusht 2026-03-26 21:24:26 +01:00
scriptos d77961818a ci: Gitea Action für automatischen Docker-Build und Registry-Push hinzugefügt 2026-03-26 21:21:22 +01:00
scriptos 7bf847d31c feat: IP-Watcher – automatischer Neustart bei IP-Wechsel 2026-03-26 21:09:23 +01:00
scriptos 7539e18a46 Merge pull request 'feat: Map-Shuffle beim Containerstart (SHUFFLE_MAPLIST) - Variable ergänzt' (#15) from release-1.3.0 into master
Reviewed-on: #15
2026-03-23 22:23:01 +00:00
scriptos 2c3aaaee23 Merge branch 'master' into release-1.3.0 2026-03-23 22:22:48 +00:00
scriptos 69bfa07457 feat: Map-Shuffle beim Containerstart (SHUFFLE_MAPLIST) - Variable ergänzt 2026-03-23 23:20:48 +01:00
scriptos af44a273fa Merge pull request 'release-1.3.0' (#14) from release-1.3.0 into master
Reviewed-on: #14
2026-03-23 22:12:34 +00:00
scriptos e60cc0804c Release: Docker Image Version 1.3.0 gepusht 2026-03-23 23:08:54 +01:00
scriptos e453ab6dbd Startup-Zusammenfassung: HOST_IP-Variable entfernt, Platzhalter beibehalten 2026-03-23 23:01:02 +01:00
scriptos 6cb5f783bf Startup-Zusammenfassung: Servername aus Config lesen, Host-IP ermitteln 2026-03-23 22:55:17 +01:00
scriptos 4ec60113f7 Fix: Signal-Handler dash-kompatibel (SIGTERM -> TERM) 2026-03-23 22:49:01 +01:00
scriptos 8b61571ac5 Doku sauber formatiert 2026-03-23 22:33:55 +01:00
scriptos d7be0b37ef Startup-Zusammenfassung am Ende des Startscripts hinzugefuegt 2026-03-23 22:28:13 +01:00
scriptos 7eb2b6ff0b Graceful Shutdown: Signal-Handler für sauberes Herunterfahren aller Dienste 2026-03-23 22:20:05 +01:00
scriptos cbb918d6b2 Log-Rotation fuer Apache-, PHP- und XAseco-Logs eingerichtet 2026-03-23 22:14:38 +01:00
scriptos 7710a032d9 XAseco-Healthcheck: Automatische Überwachung und Neustart bei Absturz 2026-03-23 22:06:58 +01:00
scriptos 12a8e0f778 feat: Map-Shuffle beim Containerstart (SHUFFLE_MAPLIST) 2026-03-23 21:55:27 +01:00
scriptos ca6c0faaa0 fix: TS3-Konfiguration wird bei Update nicht mehr überschrieben 2026-03-23 21:47:22 +01:00
scriptos 77ac9cc37a Merge pull request 'release-1.2.2' (#13) from release-1.2.2 into master
Reviewed-on: #13
2026-03-23 08:51:05 +00:00
scriptos 8a3ef5241e Release: Docker Image Version 1.2.2 gepusht 2026-03-23 09:50:21 +01:00
scriptos 634cd8c0a6 docs(xaseco): umfassende Chat-Befehlsreferenz ergänzt
Bisherige Kurztabelle durch vollständige Befehlsübersicht ersetzt,
gegliedert nach Kategorien: Hilfe, Rekorde, Rankings, Strecken,
Karma/Voting, Kommunikation, Spieleroptionen und Admin-Befehle.
Verweis auf offizielle Dokumentation (docs.xaseco.org) hinzugefügt.
2026-03-23 09:47:52 +01:00
scriptos daaace417b fix(security): AdminServ-Konfigurationspasswort durch zufälligen Hash ersetzen 2026-03-23 09:43:28 +01:00
scriptos f7d3a9ce83 docs: Doku an Code anpassen, AdminServ-Konfigurationspasswort automatisch absichern 2026-03-23 09:25:48 +01:00
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
scriptos bef71bd924 xaseco implementiert + Doku erweitert + Lizenz erweitert. 2026-03-20 23:11:15 +01:00
scriptos 5d626a7c9d Kosmetische Änderungen. 2026-03-20 19:21:09 +01:00
scriptos 65568e9f59 RemoteCP hinzugefügt. 2026-03-20 18:51:49 +01:00
scriptos c1c8ce5b3d Einige Anpassungen und Verbesserungen 2026-03-19 22:04:43 +01:00
scriptos 67b0fafeea PHP debug. 2026-03-18 22:36:34 +01:00
scriptos 3774e46924 Internet Mode neu konfiguriert. Neues BaseImage gewählt. 2026-03-18 21:54:28 +01:00
scriptos 7c3c228e47 Docker Image bauen beschrieben. 2026-03-18 19:30:48 +01:00
scriptos 8d4f858b31 Dokumentation aktualisiert. 2026-03-18 18:32:06 +01:00
scriptos 86e53d4233 Commit Konvention entfernt. 2026-03-18 18:31:13 +01:00
scriptos 705fe4f0bc Docker Netzwerk hinzugefügt. 2026-03-18 13:12:02 +01:00
scriptos 28b8ba3ac3 Internet-Dedicated hinzugefügt + Docs erstellt. 2026-03-18 13:06:46 +01:00
scriptos 8dad2637f8 Readme übersetzt und LICENSE hinzugefügt. 2026-03-18 12:56:05 +01:00
scriptos f57da6009d Files verschoben und Docker-Compose erstellt. 2026-03-18 12:49:22 +01:00
39 changed files with 5403 additions and 85 deletions

No files matched your search

+18
View File
@@ -0,0 +1,18 @@
# Git
.git
.gitignore
.gitattributes
# IDE
.vscode
# Docs
README.md
docs
# Umgebungsvariablen (Secrets!)
.env
.env.example
# Persistente Daten
data
+131
View File
@@ -0,0 +1,131 @@
# ============================================================
# tmserver-docker – Umgebungsvariablen
# ============================================================
# Diese Datei wird von docker-compose.yml eingelesen.
# Kopiere diese Vorlage nach .env und passe die Werte an:
# cp .env.example .env
#
# ⚠ SICHERHEITSHINWEIS:
# Die unten stehenden Passwörter sind vorgenerierte Beispielwerte
# und öffentlich einsehbar! Ändere ALLE Passwörter, bevor du
# den Server produktiv einsetzt.
# ============================================================
# --- Authentifizierung ---
# User: SuperAdmin, Admin, User
SERVER_SA_PASSWORD="3HbQc2EVarykEPX"
SERVER_ADM_PASSWORD="YhNDuChmzoaHP72"
SERVER_USER_PASSWORD="8e9J8AKwS6YDXmT"
# --- Masterserver-Account ---
# Wenn du deinen Server im Internet-Modus betreiben möchtest, musst du hier die Zugangsdaten für deinen Masterserver-Account eingeben.
# Diese Informationen erhältst du, wenn du deinen Server auf der Trackmania-Webseite registrierst.
SERVER_LOGIN=
SERVER_LOGIN_PASSWORD=
SERVER_VALIDATION_KEY=
# --- Server-Optionen ---
# Bitte ändere den Server-Namen und die Beschreibung, um deinen Server von anderen zu unterscheiden.
# Du kannst auch die Anzahl der Spieler und Zuschauer anpassen, sowie ein Passwort setzen, um den Zugang zu beschränken.
SERVER_NAME='Trackmania Server'
SERVER_DESC="Powered by tmserver-docker"
SERVER_HIDE=0
SERVER_MAX_PLAYERS=40
SERVER_PASSWORD=
SERVER_MAX_SPECTATORS=40
SERVER_SPEC_PASSWORD=
SERVER_LADDER_MODE=forced
SERVER_LADDER_LIMIT_MAX=60000
# --- Netzwerk ---
# Bitte ändere die Ports, wenn sie in deinem Netzwerk bereits verwendet werden.
SERVER_PORT=2350
SERVER_P2P_PORT=3450
SERVER_XMLRPC_PORT=5000
SERVER_UPLOAD_RATE=512
SERVER_DOWNLOAD_RATE=8192
# --- Server-Modus und Config-Steuerung ---
# Der Server-Modus kann auf "internet" oder "lan" gesetzt werden. Im Internet-Modus wird eine Verbindung zum Masterserver hergestellt, um den Server öffentlich sichtbar zu machen.
# Im LAN-Modus wird keine Verbindung zum Masterserver hergestellt, und der Server ist nur im lokalen Netzwerk sichtbar.
SERVER_MODE=internet
# Wenn FORCE_CONFIG_UPDATE auf true gesetzt ist, wird die Server-Konfiguration bei jedem Start des Containers überschrieben.
# Dies kann nützlich sein, wenn du Änderungen an der .env-Datei vornimmst und sicherstellen möchtest, dass diese Änderungen angewendet werden.
# In einer Produktionsumgebung sollte dieser Wert jedoch auf false belassen werden, um zu verhindern, dass die Konfiguration versehentlich überschrieben wird.
FORCE_CONFIG_UPDATE=false
# --- Forced Mods (Skins) ---
# Beim Containerstart kann automatisch ein Mod (Skin) pro Umgebung forciert werden.
# Der Wert ist die vollständige URL zu einer Mod-ZIP-Datei, die Spieler beim Betreten des Servers herunterladen.
# Verfügbare Skins findest du unter: https://assets.techniverse.net/tm/skins/
# Beispiel: FORCE_MOD_STADIUM=https://assets.techniverse.net/tm/skins/Portal.zip
# Leer lassen = kein Mod für diese Umgebung.
FORCE_MOD_STADIUM=
FORCE_MOD_ISLAND=
FORCE_MOD_BAY=
FORCE_MOD_COAST=
FORCE_MOD_SPEED=
FORCE_MOD_ALPINE=
FORCE_MOD_RALLY=
# --- Spieleinstellungen (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
# Warmup-Dauer für alle Runden (0 = deaktiviert, 1 = eine Runde Warmup)
ALLWARMUPDURATION=0
# Map-Reihenfolge beim Containerstart zufällig mischen (true = aktiviert, false = deaktiviert)
# Bei jedem Start werden die Maps in der aktiven MatchSettings-Datei neu durchgemischt,
# sodass der Server jedes Mal mit einer anderen Map beginnt.
SHUFFLE_MAPLIST=false
# --- Debugging ---
# Setze diesen Wert auf true, um PHP-Fehlermeldungen anzuzeigen. Dies kann bei der Fehlersuche hilfreich sein, sollte aber in einer Produktionsumgebung auf false belassen werden.
PHP_DISPLAY_ERRORS=false
# --- RemoteCP-Datenbank ---
# --- MariaDB ---
# Bitte ändere das Root-Passwort für MariaDB, um die Sicherheit deiner Datenbank zu gewährleisten.
MARIADB_ROOT_PASSWORD="RAPjY2FLtLoLDYU"
# --- RemoteCP-Datenbankzugang ---
# Diese Zugangsdaten werden von RemoteCP verwendet, um sich mit der Datenbank zu verbinden. Ändere diese Werte, um die Sicherheit deiner Datenbank zu gewährleisten.
REMOTECP_DB_HOST=mariadb
REMOTECP_DB_NAME=remotecp
REMOTECP_DB_USER=remotecp
REMOTECP_DB_PASSWORD="6MP2mBdF9TBtdmN"
# --- XAseco ---
# XAseco ist ein Server-Controller, der Rekorde, Karma, Jukebox und vieles mehr verwaltet.
# Setze XASECO_ENABLED auf false, um XAseco zu deaktivieren.
XASECO_ENABLED=true
# Dein Spieler-Login (wird als MasterAdmin und Admin in XAseco eingetragen)
XASECO_MASTERADMIN_LOGIN=
# XAseco-Datenbankzugang
# Bitte ändere die Zugangsdaten für die XAseco-Datenbank, um die Sicherheit deiner Datenbank zu gewährleisten.
XASECO_DB_HOST=mariadb
XASECO_DB_NAME=xaseco
XASECO_DB_USER=xaseco
XASECO_DB_PASSWORD="4KpL8mWnR3xYvBq"
# Dedimania-Nation (3-Zeichen IOC-Code, z.B. DEU, AUT, CHE)
# Server-Login und -Passwort werden automatisch aus SERVER_LOGIN / SERVER_LOGIN_PASSWORD übernommen.
XASECO_DEDIMANIA_NATION=DEU
# XAseco-Healthcheck: Überwacht XAseco und startet es automatisch neu bei Absturz oder Verbindungsverlust.
XASECO_HEALTHCHECK=true
# Prüfintervall des Healthchecks in Sekunden (Standard: 60)
XASECO_HEALTHCHECK_INTERVAL=60
# --- IP-Watcher ---
# Der IP-Watcher überwacht die ausgehende öffentliche IP des Containers und startet tmserver
# automatisch neu, wenn sich die IP ändert – damit er sich beim Masterserver neu registriert.
# Intervall in Sekunden, in dem die IP geprüft wird (Standard: 300 = 5 Minuten).
IP_WATCHER_INTERVAL=300
+1
View File
@@ -0,0 +1 @@
*.sh text eol=lf
+49
View File
@@ -0,0 +1,49 @@
name: Build & Push Docker Image
# Wird ausgeloest, wenn ein Tag wie "v1.3.0" gepusht wird
on:
push:
tags:
- 'v*'
jobs:
build-and-push:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
# "v1.3.0" -> "1.3.0" (fuehrendes "v" entfernen)
- name: Version aus Tag extrahieren
id: version
run: |
VERSION=$(echo "${{ gitea.ref_name }}" | sed 's/^v//')
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
- name: Bei Gitea Registry einloggen
run: |
echo "${{ secrets.REGISTRY_TOKEN }}" | \
docker login git.techniverse.net \
--username "${{ secrets.REGISTRY_USER }}" \
--password-stdin
# Der TM-Dedicated-Server ist ein x86-Binary -> Image immer fuer
# linux/amd64 bauen, auch wenn der Job auf einem arm64-Runner landet
# (v1.4.0 wurde versehentlich als arm64 gebaut: "exec format error").
- name: QEMU einrichten
uses: docker/setup-qemu-action@v3
with:
platforms: amd64
- name: Buildx einrichten
uses: docker/setup-buildx-action@v3
- name: Docker Image bauen und pushen (linux/amd64)
run: |
docker buildx build \
--platform linux/amd64 \
-t git.techniverse.net/scriptos/trackmania-server:${{ steps.version.outputs.VERSION }} \
-t git.techniverse.net/scriptos/trackmania-server:latest \
--push \
.
+10
View File
@@ -0,0 +1,10 @@
# Umgebungsvariablen (enthaelt Passwoerter!)
.env
# Persistente Server-Daten
data/
.ki-workspace/
backups/
dockernet-inspector.v1.sh
script.sh
+216 -19
View File
@@ -1,29 +1,222 @@
FROM debian:stretch-slim # Debian 13 Trixie liefert PHP 8.4 mit regulaeren Security-Updates
FROM debian:trixie-slim
RUN mkdir /opt/tmserver RUN mkdir /opt/tmserver
WORKDIR /opt/tmserver WORKDIR /opt/tmserver
COPY TrackmaniaServer_2011-02-21.zip /opt/tmserver # Alle benoetigten Pakete in einem Layer installieren und Cache aufraeumen
RUN apt-get update && apt-get install -y unzip RUN apt-get update && apt-get install -y --no-install-recommends \
RUN unzip /opt/tmserver/TrackmaniaServer_2011-02-21.zip -d /opt/tmserver unzip \
COPY custom_game_settings.txt /opt/tmserver/GameData/Tracks/MatchSettings/ apache2 \
COPY RunTrackmaniaServer.sh /opt/tmserver/ php \
php-zip \
php-xml \
php-mbstring \
php-mysql \
php-curl \
default-mysql-client \
logrotate \
&& rm -rf /var/lib/apt/lists/*
RUN apt-get -y install apache2 php php-zip php-xml # Apache mod_rewrite aktivieren und AllowOverride fuer .htaccess (RemoteCP-Sicherheit)
COPY AdminServ_v2.1.1.zip /var/www/html RUN a2enmod rewrite \
RUN unzip /var/www/html/AdminServ_v2.1.1.zip -d /var/www/html && sed -i '/<Directory \/var\/www\/>/,/<\/Directory>/ s/AllowOverride None/AllowOverride All/' /etc/apache2/apache2.conf
RUN chmod -R 777 /var/www/html/
RUN rm -f /var/www/html/index.html
ARG SERVER_NAME='Trackmania Server' COPY assets/bin/TrackmaniaServer_2011-02-21.zip /opt/tmserver
ARG SERVER_DESC='This is a Trackmania Server' RUN unzip /opt/tmserver/TrackmaniaServer_2011-02-21.zip -d /opt/tmserver \
ARG SERVER_SA_PASSWORD='SuperAdmin' && rm -f /opt/tmserver/TrackmaniaServer_2011-02-21.zip
ARG SERVER_ADM_PASSWORD='Admin'
ENV SERVER_NAME $SERVER_NAME # Custom-Konfiguration ueber die Standard-Config aus dem ZIP kopieren
ENV SERVER_DESC $SERVER_DESC COPY assets/config/dedicated_cfg.txt /opt/tmserver/GameData/Config/dedicated_cfg.txt
ENV SERVER_SA_PASSWORD $SERVER_SA_PASSWORD COPY assets/config/custom_game_settings.txt /opt/tmserver/GameData/Tracks/MatchSettings/
ENV SERVER_ADM_PASSWORD $SERVER_ADM_PASSWORD
# Config-Verzeichnis rekursiv beschreibbar machen (Server benoetigt Schreibzugriff)
RUN chmod -R 777 /opt/tmserver/GameData/Config/
# Tracks-Verzeichnis fuer AdminServ beschreibbar machen (Maps-Upload/Download)
RUN chown -R www-data:www-data /opt/tmserver/GameData/Tracks/ \
&& chmod -R 755 /opt/tmserver/GameData/Tracks/
# AdminServ-Verzeichnisse im Config-Ordner anlegen
RUN mkdir -p /opt/tmserver/GameData/Config/AdminServ/ServerOptions \
&& chown -R www-data:www-data /opt/tmserver/GameData/Config/AdminServ
# Gesamtes GameData als Default-Template sichern (wird beim ersten Start ins Volume kopiert)
RUN cp -a /opt/tmserver/GameData /opt/tmserver/default-gamedata
COPY assets/bin/RunTrackmaniaServer.sh /opt/tmserver/
RUN sed -i 's/\r$//' /opt/tmserver/RunTrackmaniaServer.sh \
&& chmod +x /opt/tmserver/RunTrackmaniaServer.sh
# PHP-8-Kompatibilitaet: ergaenzt __construct() fuer PHP-4-Konstruktoren
# (wird bei jedem Start auf die Volumes angewendet)
COPY assets/bin/Php8Compat.php /opt/tmserver/
COPY assets/bin/XAsecoHealthcheck.sh /opt/tmserver/
RUN sed -i 's/\r$//' /opt/tmserver/XAsecoHealthcheck.sh \
&& chmod +x /opt/tmserver/XAsecoHealthcheck.sh
COPY assets/bin/AdminServ_v2.1.1.zip /var/www/html
RUN unzip /var/www/html/AdminServ_v2.1.1.zip -d /var/www/html \
&& rm -f /var/www/html/AdminServ_v2.1.1.zip \
&& rm -f /var/www/html/index.html \
&& mkdir -p /var/www/html/logs \
&& chmod 755 /var/www/html/logs \
&& chmod 666 /var/www/html/config/adminlevel.cfg.php \
&& chmod 666 /var/www/html/config/servers.cfg.php \
&& chmod 666 /var/www/html/config/adminserv.cfg.php \
&& chown -R www-data:www-data /var/www/html/
# RemoteCP installieren (als Subpath /remotecp/)
COPY assets/bin/remoteCP_v4.0.3.5.zip /var/www/html
RUN unzip /var/www/html/remoteCP_v4.0.3.5.zip -d /var/www/html \
&& mv /var/www/html/remoteCP_4.0.3.5-1 /var/www/html/remotecp \
&& rm -f /var/www/html/remoteCP_v4.0.3.5.zip \
&& mkdir -p /var/www/html/remotecp/cache \
&& chmod -R 777 /var/www/html/remotecp/cache \
&& chmod -R 666 /var/www/html/remotecp/xml/*.xml \
&& chmod -R 777 /var/www/html/remotecp/xml \
&& chmod -R 777 /var/www/html/remotecp/xml/settings \
&& 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)
RUN cp -a /var/www/html /opt/tmserver/default-controlpanel
# XAseco installieren
COPY assets/bin/xaseco_v1.16.zip /opt/tmserver/
RUN unzip /opt/tmserver/xaseco_v1.16.zip -d /opt/tmserver/ \
&& rm -f /opt/tmserver/xaseco_v1.16.zip \
# newinstall-Dateien in den Hauptordner verschieben
&& cp /opt/tmserver/xaseco/newinstall/access.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/adminops.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/Aseco.sh /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/AsecoF.sh /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/autotime.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/bannedips.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/config.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/dedimania.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/html.tpl /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/localdatabase.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/matchsave.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/musicserver.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/plugins.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/rasp.xml /opt/tmserver/xaseco/ \
&& cp /opt/tmserver/xaseco/newinstall/text.tpl /opt/tmserver/xaseco/ \
# Die drei PHP-Dateien nach includes/
&& cp /opt/tmserver/xaseco/newinstall/jfreu.config.php /opt/tmserver/xaseco/includes/ \
&& cp /opt/tmserver/xaseco/newinstall/rasp.settings.php /opt/tmserver/xaseco/includes/ \
&& cp /opt/tmserver/xaseco/newinstall/votes.config.php /opt/tmserver/xaseco/includes/ \
# Start-Scripte ausfuehrbar machen
&& chmod +x /opt/tmserver/xaseco/Aseco.sh \
&& chmod +x /opt/tmserver/xaseco/AsecoF.sh \
# newinstall-Ordner aufraeumen
&& rm -rf /opt/tmserver/xaseco/newinstall
# Nicht mehr verfuegbares freezone-Plugin entfernen
RUN sed -i '/<plugin>plugin\.freezone\.php<\/plugin>/d' /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)
RUN cp -a /opt/tmserver/xaseco /opt/tmserver/default-xaseco
# PHP-Debug-Konfiguration: wird zur Laufzeit vom Startup-Script gesetzt
# (kein Rebuild noetig – nur Container neustarten)
# Log-Rotation: logrotate-Konfiguration ins Image kopieren
# Wird zur Laufzeit stuendlich per Background-Loop ausgefuehrt (kein cron noetig).
COPY assets/config/logrotate.conf /etc/logrotate.d/tmserver
RUN chmod 644 /etc/logrotate.d/tmserver
# --- Umgebungsvariablen ---
# Sensible Werte (Passwoerter, Keys) werden NICHT im Image hinterlegt,
# sondern muessen zur Laufzeit uebergeben werden (z.B. via .env-Datei).
# Server-Optionen (nicht-sensible Standardwerte)
ENV SERVER_NAME="Trackmania Server"
ENV SERVER_DESC="Powered by tmserver-docker"
ENV SERVER_HIDE=0
ENV SERVER_MAX_PLAYERS=32
ENV SERVER_MAX_SPECTATORS=32
ENV SERVER_LADDER_MODE=forced
# Netzwerk
ENV SERVER_PORT=2350
ENV SERVER_P2P_PORT=3450
ENV SERVER_XMLRPC_PORT=5000
ENV SERVER_UPLOAD_RATE=512
ENV SERVER_DOWNLOAD_RATE=8192
# Server-Modus und Config-Steuerung
ENV SERVER_MODE=internet
ENV FORCE_CONFIG_UPDATE=false
# Spieleinstellungen (MatchSettings)
ENV ALLWARMUPDURATION=0
ENV SHUFFLE_MAPLIST=false
# Forced Mods (Skins) - URL zu ZIP-Dateien, die beim Start forciert werden
ENV FORCE_MOD_STADIUM=""
ENV FORCE_MOD_ISLAND=""
ENV FORCE_MOD_BAY=""
ENV FORCE_MOD_COAST=""
ENV FORCE_MOD_SPEED=""
ENV FORCE_MOD_ALPINE=""
ENV FORCE_MOD_RALLY=""
# RemoteCP
ENV REMOTECP_DB_HOST=mariadb
ENV REMOTECP_DB_NAME=remotecp
ENV REMOTECP_DB_USER=remotecp
# XAseco
ENV XASECO_ENABLED=true
ENV XASECO_MASTERADMIN_LOGIN=""
ENV XASECO_DB_HOST=mariadb
ENV XASECO_DB_NAME=xaseco
ENV XASECO_DB_USER=xaseco
ENV XASECO_DEDIMANIA_NATION=DEU
ENV XASECO_HEALTHCHECK=true
ENV XASECO_HEALTHCHECK_INTERVAL=60
# Debugging
ENV PHP_DISPLAY_ERRORS=false
# Volumes fuer persistente Daten
VOLUME /opt/tmserver/GameData
VOLUME /var/www/html
VOLUME /opt/tmserver/xaseco
EXPOSE 5000/tcp EXPOSE 5000/tcp
EXPOSE 2350/tcp EXPOSE 2350/tcp
@@ -31,4 +224,8 @@ EXPOSE 2350/udp
EXPOSE 3450/tcp EXPOSE 3450/tcp
EXPOSE 80/tcp EXPOSE 80/tcp
# Graceful Shutdown: SIGTERM wird vom Signal-Handler im Startscript abgefangen
# und alle Dienste (XAseco, TM-Server, Apache) sauber heruntergefahren.
STOPSIGNAL SIGTERM
CMD ["/opt/tmserver/RunTrackmaniaServer.sh"] CMD ["/opt/tmserver/RunTrackmaniaServer.sh"]
+86
View File
@@ -0,0 +1,86 @@
MIT-Lizenz
Copyright © 2019 lduriez (Original: https://github.com/lduriez/tmserver-docker)
Copyright © 2026 Patrick Asmus
---
Lizenzinhaber
Name: Patrick Asmus (scriptos)
Email: support@techniverse.net
Website: https://www.patrick-asmus.de
Blog: https://www.cleveradmin.de
---
Hiermit wird jeder Person, die eine Kopie dieser Software und der zugehörigen
Dokumentationsdateien (die „Software") erhält, kostenlos die Erlaubnis erteilt,
uneingeschränkt mit der Software zu handeln, einschließlich und ohne
Einschränkung der Rechte, die Software zu verwenden, zu kopieren, zu
verändern, zusammenzuführen, zu veröffentlichen, zu verbreiten,
unterzulizenzieren und/oder Kopien der Software zu verkaufen, und Personen,
denen die Software zur Verfügung gestellt wird, dies unter den folgenden
Bedingungen zu gestatten:
Der obige Urheberrechtshinweis einschließlich der Angaben zum Lizenzinhaber
sowie dieser Genehmigungshinweis müssen in allen Kopien oder wesentlichen
Teilen der Software enthalten sein.
DIE SOFTWARE WIRD „WIE BESEHEN" OHNE JEGLICHE AUSDRÜCKLICHE ODER
STILLSCHWEIGENDE GEWÄHRLEISTUNG ZUR VERFÜGUNG GESTELLT, EINSCHLIESSLICH,
ABER NICHT BESCHRÄNKT AUF DIE GEWÄHRLEISTUNG DER MARKTGÄNGIGKEIT, DER
EIGNUNG FÜR EINEN BESTIMMTEN ZWECK UND DER NICHTVERLETZUNG VON RECHTEN
DRITTER. IN KEINEM FALL HAFTEN DIE AUTOREN ODER URHEBERRECHTSINHABER FÜR
JEGLICHE ANSPRÜCHE, SCHÄDEN ODER SONSTIGE HAFTUNG, OB AUS VERTRAG,
UNERLAUBTER HANDLUNG ODER ANDERWEITIG, DIE SICH AUS ODER IN VERBINDUNG MIT
DER SOFTWARE ODER DER NUTZUNG ODER ANDEREN GESCHÄFTEN MIT DER SOFTWARE
ERGEBEN.
===============================================================================
Hinweise zu enthaltenen Drittanbieter-Tools
===============================================================================
Die oben genannte MIT-Lizenz gilt ausschließlich für den Code, die
Konfiguration und die Container-Arbeit dieses Repositories (tmserver-docker)
von Patrick Asmus. Die folgenden Drittanbieter-Tools werden in diesem Projekt
eingebunden und unterliegen jeweils ihren eigenen Lizenzbedingungen. Alle
Rechte an diesen Tools verbleiben bei den jeweiligen Entwicklern bzw.
Rechteinhabern.
-------------------------------------------------------------------------------
1. AdminServ
-------------------------------------------------------------------------------
Beschreibung: Web-Interface zur Verwaltung von TrackMania Forever und
ManiaPlanet Dedicated Servern.
Ursprünglicher Entwickler: Kev717
Weiterentwicklung: Chris92de
Repository: https://github.com/Chris92de/AdminServ
Lizenz: Keine Lizenzdatei im Repository vorhanden.
Alle Rechte verbleiben bei den jeweiligen Entwicklern
(Kev717, Chris92de und weitere Mitwirkende).
-------------------------------------------------------------------------------
2. remoteCP
-------------------------------------------------------------------------------
Beschreibung: Remote-Control-Panel für TrackMania Server.
Entwickler: hal.sascha
Lizenz: GNU Lesser General Public License (LGPL)
https://www.gnu.org/licenses/lgpl-3.0.html
Hinweis: Alle Rechte verbleiben beim Entwickler hal.sascha.
-------------------------------------------------------------------------------
3. XASECO
-------------------------------------------------------------------------------
Beschreibung: Server-Controller für TrackMania (Nations/Forever).
Basiert auf ASECO von Florian Schnell und RASP von
Eyez (Iain Surgey) & Assembler Maniac (Brad Mettee).
Entwickler: Xymph (Frans P. de Vries)
Copyright: © 2007-2026 Frans P. de Vries
Website: https://www.xaseco.org
Kontakt: tm@gamers.org
Lizenz: Keine explizite Open-Source-Lizenz im Projekt angegeben.
Alle Rechte verbleiben bei Frans P. de Vries (Xymph)
und den ursprünglichen Autoren von ASECO und RASP.
===============================================================================
+92 -48
View File
@@ -1,77 +1,121 @@
# tmserver-docker <p align="center">
<a href="https://techniverse.net">
<img src="https://assets.techniverse.net/f1/git/graphics/repo-techniverse-logo.png" alt="Techniverse Community" height="70" />
</a>
</p>
Trackmania Nation Forever Docker Server <h1 align="center">TM-Server Docker</h1>
You can find it on Docker Hub [lduriez/tmserver](https://hub.docker.com/r/lduriez/tmserver) <h4 align="center">
Ein vollständiges Docker-Setup für einen TrackMania Nations Forever-Server – inklusive Web-Verwaltung und Server-Controller
</h4>
Only lan dedicated is enable (internet dedicated will be added in future version) <h6 align="center">
<a href="https://www.cleveradmin.de">🏰 Website</a>
·
<a href="https://techniverse.net">📰 Community</a>
·
<a href="https://social.techniverse.net/@donnerwolke">🐘 Mastodon</a>
·
<a href="https://matrix.to/#/#support:techniverse.net">💬 Support</a>
</h6>
<br><br>
Server management webui provided base on [AdminServ](https://github.com/Chris92de/AdminServ) ## ✨ Übersicht
## Running - **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
On a docker environment simply do the following 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).
Der Server unterstützt sowohl den **Internet-Dedicated-Modus** (Standard) als auch den **LAN-Dedicated-Modus**.
## ⚠️ PHP 8 – bitte lesen (ab v1.4.1)
Seit **v1.4.0** (lauffähiges Image ab **v1.4.1**) basiert das Image auf **Debian 13 (Trixie) mit PHP 8.4**. PHP 7.4 ist seit 2022 ohne Sicherheitsupdates und von kritischen Lücken wie *PHP2Shell* (CVE-2026-109618, CVE-2026-109619) betroffen – deshalb habe ich den Umstieg vorgenommen.
AdminServ, RemoteCP und XAseco stammen allerdings aus PHP-5-Zeiten. Damit sie unter PHP 8 laufen, patcht der Container sie beim Start automatisch (PHP-4-Konstruktoren, veraltete Syntax, statische Aufrufe und einige bekannte Laufzeitfehler). Die Anpassungen sind idempotent und greifen auch bei bestehenden Volumes.
Ich habe die gängigen Funktionen getestet, aber **es kann durchaus noch zu Fehlern kommen** – vor allem in selten genutzten Plugins oder Menüs. Wenn dir etwas auffällt (weiße Seite, HTTP 500, XAseco-Absturz, Funktion ohne Wirkung), eröffne bitte ein [Issue](https://git.techniverse.net/scriptos/tmserver-docker/issues) und gib möglichst an:
- was du gemacht hast (Seite/Plugin/Chat-Befehl) und ungefähr wann,
- die passende Meldung aus `docker logs tmserver`, dem Apache-Log (`docker exec tmserver tail -n 50 /var/log/apache2/error.log`), `data/xaseco/aseco.log` oder `data/controlpanel/remotecp/cache/errors.log`.
Ich schaue mir das dann an. Wer PHP 7.4 behalten möchte, bleibt vorerst beim Image-Tag `1.3.3`.
## 🚀 Schnellstart
### 1. Umgebungsvariablen einrichten
```bash ```bash
docker run -d -p 2350:2350 -p 3450:3450 -p 80:80 --name tm-server lduriez/tmserver cp .env.example .env
``` ```
You can add environment variable to modify default values : [#Environment variables](#environment-variables) Passe die Werte in der `.env`-Datei an deine Umgebung an (Passwörter, Masterserver-Account, etc.).
### Configure Server Management Webui > **⚠ Sicherheitshinweis:** Die `.env.example` enthält **vorgenerierte Beispiel-Passwörter**. Diese dienen nur als Platzhalter und sind öffentlich einsehbar! **Ändere unbedingt alle Passwörter**, bevor du den Server produktiv einsetzt.
Visit `http:<host-server-of-the-conntainer>` and start configuration by setting the password of your choice. ### 2. Server starten
This password will be the AdminServ password for configurations (add TM server)
Then add TM server information (you can let all by default). Be sure that `Address` is set to `localhost` in order to manage our embed server. Das fertige Docker Image kann direkt verwendet werden – kein eigener Build nötig:
Once you saved, you can go to Servers list by pushing "Servers" button and then go to management server list with button "Back". ```bash
docker compose up -d
```
You should see the server you added, in the to banner you ca go to the management environment. Das Image wird automatisch aus der Container-Registry geladen:
Select the server you want to manage, the admin level you want to go in, and tape the password of this admion level.
By default `SuperAdmin` password is `SuperAdmin`, `Admin` password is `Admin`, `User` password is `User`. (You can modify the admin level in configuration settings `http:<host-server-of-the-conntainer>/config`) ```
git.techniverse.net/scriptos/trackmania-server:latest
```
Congratulation you can now manage your TM server. > **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).
Enjoy your game. ### 3. Verwaltungsoberflächen öffnen
## Exposed ports - **AdminServ:** `http://<host-ip>/`
- **RemoteCP:** `http://<host-ip>/remotecp/`
* 2350/tcp game server port > **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`.
* 2350/udp game server port
* 3450/tcp p2p game server port
* 80/tcp server management webui port
## Environment variables ## 📚 Dokumentation
* SERVER_NAME name of your server (default is 'Trackmania Server') Die vollständige Dokumentation befindet sich im Ordner [`docs/`](docs/README.md):
* SERVER_DESC description of your server (default is 'This is a Trackmania Server')
* SERVER_SA_PASSWORD superadmin management password (default is 'SuperAdmin')
* SERVER_ADM_PASSWORD admin management password (default is 'Admin')
## Commit convention - [Schnellstart](docs/schnellstart.md) – Erste Schritte und minimale Konfiguration
- [Konfiguration](docs/konfiguration.md) – Persistente Serverkonfiguration (dedicated_cfg.txt)
- [Umgebungsvariablen](docs/umgebungsvariablen.md) – Alle verfügbaren Umgebungsvariablen
- [Server-Modi](docs/server-modi.md) – LAN- und Internet-Dedicated-Modus
- [AdminServ](docs/adminserv.md) – Einrichtung der Server-Verwaltungsoberfläche
- [RemoteCP](docs/remotecp.md) – Alternative Server-Verwaltungsoberfläche
- [XAseco](docs/xaseco.md) – Server-Controller für Rekorde, Karma und Jukebox
- [IP-Watcher](docs/ip-watcher.md) – Automatischer Neustart bei IP-Wechsel
- [Ports](docs/ports.md) – Freigegebene Ports und deren Verwendung
- [Update](docs/update.md) – Bestehende Installation aktualisieren
Format : `<type>(<portée>): <sujet>` ## 🙏 Danksagung
### type Ein herzliches Dankeschön an **[Thomas](https://retronerd.at)** – für seine tatkräftige Unterstützung, sein wertvolles Wissen und seine Mitwirkung an diesem Projekt. Ohne ihn wäre dieses Projekt nicht das, was es heute ist!
* `build` : changements qui affectent le système de build ou des dépendances externes (npm, make…) ## 🔄 Spiegelung
* `ci` : changements concernant les fichiers et scripts d’intégration ou de configuration (Travis, Ansible, BrowserStack…)
* `feat` : ajout d’une nouvelle fonctionnalité
* `fix` : correction d’un bug
* `perf` : amélioration des performances
* `refactor` : modification qui n’apporte ni nouvelle fonctionnalité ni d’amélioration de performances
* `style` : changement qui n’apporte aucune altération fonctionnelle ou sémantique (indentation, mise en forme, ajout d’espace, renommante d’une variable…)
* `docs` : rédaction ou mise à jour de documentation
* `test` : ajout ou modification de tests
* `revert` : annuler un précédent commit, forme `revert sujet du commit annulé hash du commit annulé`
### sujet Dieses Repository wird von **Gitea** auf **GitHub** gespiegelt. Das Master-Repository befindet sich auf Gitea:
* `add` - **Gitea (Master):** [git.techniverse.net/scriptos/tmserver-docker](https://git.techniverse.net/scriptos/tmserver-docker.git)
* `change` - **GitHub (Spiegel):** [github.com/pscriptos/tmserver-docker](https://github.com/pscriptos/tmserver-docker.git)
* `update`
* `remove`
[source](https://buzut.net/git-bien-nommer-ses-commits/)
<br><br>
<p align="center">
<img src="https://assets.techniverse.net/f1/git/graphics/gray0-catonline.svg" alt="">
</p>
<p align="center">
<sub>
© Patrick Asmus · Techniverse Network · <a href="./LICENSE">Lizenz</a>
</sub>
</p>
-17
View File
@@ -1,17 +0,0 @@
#!/bin/sh
echo "Starting apache server"
service apache2 start
echo "Setting ENV/ARG variables"
sed -i "s/<password>SuperAdmin/<password>${SERVER_SA_PASSWORD}/" /opt/tmserver/GameData/Config/dedicated_cfg.txt
sed -i "s/<password>Admin/<password>${SERVER_ADM_PASSWORD}/" /opt/tmserver/GameData/Config/dedicated_cfg.txt
sed -i "s/<name></<name>${SERVER_NAME}</" /opt/tmserver/GameData/Config/dedicated_cfg.txt
sed -i "s/<comment></<comment>${SERVER_DESC}</" /opt/tmserver/GameData/Config/dedicated_cfg.txt
sed -i "s/<xmlrpc_allowremote>False/<xmlrpc_allowremote>True/" /opt/tmserver/GameData/Config/dedicated_cfg.txt
echo "Server config dedicated_cfg.txt is"
cat /opt/tmserver/GameData/Config/dedicated_cfg.txt
echo "Launching Server"
exec ./TrackmaniaServer /dedicated_cfg=dedicated_cfg.txt /game_settings=MatchSettings/custom_game_settings.txt /nodaemon /lan
View File
File renamed without changes.
+325
View File
@@ -0,0 +1,325 @@
<?php
/**
* Php8Compat.php - PHP-8-Kompatibilitaet fuer AdminServ, RemoteCP und XAseco
*
* Aufruf: php Php8Compat.php <verzeichnis> [<verzeichnis> ...]
*
* PHP 8 behandelt Methoden mit dem Namen der Klasse (PHP-4-Konstruktoren)
* nicht mehr als Konstruktor. Die alten Tools nutzen das an vielen Stellen,
* Objekte blieben dadurch uninitialisiert.
*
* Fuer jede Klasse mit PHP-4-Konstruktor und ohne __construct() wird direkt
* nach der oeffnenden Klammer ein __construct() eingefuegt, der die alte
* Methode aufruft. Die alte Methode bleibt erhalten, damit Aufrufe wie
* parent::IXR_Client(...) weiter funktionieren.
*
* Ausserdem werden String-Offsets mit geschweiften Klammern ($str{0}) auf
* eckige Klammern umgestellt (unter PHP 8 ein Parse-Error).
*
* Nicht-statische Methoden, die statisch aufgerufen werden (Core::getObject()),
* sind unter PHP 8 ein Fatal Error. Solche Methoden werden als static markiert,
* sofern sie $this nicht verwenden und keine Unterklasse sie nicht-statisch
* ueberschreibt.
*
* Idempotent: Klassen mit vorhandenem __construct() werden uebersprungen.
* Dateien werden in-place geschrieben (Besitzer und Rechte bleiben erhalten).
*/
if ($argc < 2) {
fwrite(STDERR, "Aufruf: php {$argv[0]} <verzeichnis> [...]\n");
exit(1);
}
$files = array();
for ($i = 1; $i < $argc; $i++) {
if (!is_dir($argv[$i])) continue;
$it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($argv[$i], FilesystemIterator::SKIP_DOTS));
foreach ($it as $file) {
if (strtolower($file->getExtension()) === 'php') $files[] = $file->getPathname();
}
}
$changed = array();
$totalClasses = 0;
$totalCurly = 0;
$totalStatic = 0;
// Durchlauf 1: Konstruktoren und String-Offsets (pro Datei)
foreach ($files as $path) {
$src = file_get_contents($path);
if ($src === false) continue;
list($new, $count) = patchConstructors($src);
list($new, $curly) = patchCurlyOffsets($new);
if ($new !== $src) {
file_put_contents($path, $new);
$changed[$path] = true;
$totalClasses += $count;
$totalCurly += $curly;
}
}
// Durchlauf 2: statisch aufgerufene Methoden (dateiuebergreifend)
foreach (patchStaticCalls($files) as $path => $count) {
$changed[$path] = true;
$totalStatic += $count;
}
echo " PHP-8-Kompatibilitaet: $totalClasses Konstruktor(en), $totalCurly Offset(s), $totalStatic static-Methode(n) in " . count($changed) . " Datei(en) angepasst.\n";
/**
* Markiert nicht-statische Methoden als static, wenn sie irgendwo als
* Klasse::methode() aufgerufen werden, $this nicht verwenden und von keiner
* Unterklasse nicht-statisch ueberschrieben werden.
*
* Liefert [Pfad => Anzahl geaenderter Methoden].
*/
function patchStaticCalls($files)
{
$methods = array(); // "klasse::methode" => [pfad, token-index, static, usesThis]
$parents = array(); // "klasse" => "elternklasse"
$calls = array(); // "klasse::methode" => true
$tokensByFile = array();
foreach ($files as $path) {
$src = file_get_contents($path);
if ($src === false || strpos($src, '::') === false && stripos($src, 'class') === false) continue;
$t = @token_get_all($src);
$tokensByFile[$path] = $t;
$n = count($t);
$depth = 0;
$cls = null;
$clsDepth = -1;
for ($i = 0; $i < $n; $i++) {
$x = $t[$i];
if ($x === '{' || (is_array($x) && in_array($x[0], array(T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES), true))) {
$depth++;
} elseif ($x === '}') {
$depth--;
if ($depth === $clsDepth) { $cls = null; $clsDepth = -1; }
} elseif (is_array($x) && $x[0] === T_CLASS) {
$prev = prevSignificant($t, $i);
if ($prev !== null && is_array($t[$prev]) && in_array($t[$prev][0], array(T_DOUBLE_COLON, T_NEW), true)) continue;
$j = nextSignificant($t, $i);
if ($j !== null && is_array($t[$j]) && $t[$j][0] === T_STRING) {
$cls = strtolower($t[$j][1]);
$clsDepth = $depth;
$e = nextSignificant($t, $j);
if ($e !== null && is_array($t[$e]) && $t[$e][0] === T_EXTENDS) {
$pc = nextSignificant($t, $e);
if ($pc !== null && is_array($t[$pc])) $parents[$cls] = strtolower(ltrim($t[$pc][1], '\\'));
}
}
} elseif ($cls !== null && is_array($x) && $x[0] === T_FUNCTION && $depth === $clsDepth + 1) {
$static = false;
for ($k = $i - 1; $k >= 0; $k--) {
if (is_array($t[$k]) && $t[$k][0] === T_STATIC) $static = true;
if ($t[$k] === ';' || $t[$k] === '{' || $t[$k] === '}') break;
}
$j = nextSignificant($t, $i);
if ($j !== null && $t[$j] === '&') $j = nextSignificant($t, $j);
if ($j === null || !is_array($t[$j])) continue;
$m = strtolower($t[$j][1]);
$usesThis = false;
$k = $j;
while ($k < $n && $t[$k] !== '{' && $t[$k] !== ';') $k++;
if ($k < $n && $t[$k] === '{') {
$d = 0;
for ($q = $k; $q < $n; $q++) {
$y = $t[$q];
if ($y === '{' || (is_array($y) && in_array($y[0], array(T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES), true))) $d++;
elseif ($y === '}') { $d--; if ($d === 0) break; }
elseif (is_array($y) && $y[0] === T_VARIABLE && $y[1] === '$this') $usesThis = true;
}
}
$methods["$cls::$m"] = array($path, $i, $static, $usesThis);
} elseif (is_array($x) && $x[0] === T_DOUBLE_COLON && $i > 0 && isset($t[$i + 1])) {
$p = $t[$i - 1];
$nx = $t[$i + 1];
if (is_array($p) && $p[0] === T_STRING && is_array($nx) && $nx[0] === T_STRING) {
$j = nextSignificant($t, $i + 1);
$c = strtolower($p[1]);
if ($j !== null && $t[$j] === '(' && !in_array($c, array('parent', 'self', 'static'), true)) {
$calls[$c . '::' . strtolower($nx[1])] = true;
}
}
}
}
}
// Kandidaten je Datei sammeln (Token-Index der function-Deklaration)
$todo = array();
foreach ($calls as $key => $_) {
if (!isset($methods[$key])) continue;
list($path, $idx, $static, $usesThis) = $methods[$key];
if ($static || $usesThis) continue;
// Nicht anfassen, wenn eine Unterklasse die Methode nicht-statisch ueberschreibt
list($target, $m) = explode('::', $key);
$overridden = false;
foreach ($methods as $other => $info) {
list($oc, $om) = explode('::', $other);
if ($om !== $m || $oc === $target || $info[2]) continue;
for ($a = $oc, $guard = 0; isset($parents[$a]) && $guard < 20; $a = $parents[$a], $guard++) {
if ($parents[$a] === $target) { $overridden = true; break 2; }
}
}
if ($overridden) continue;
$todo[$path][] = $idx;
}
$result = array();
foreach ($todo as $path => $indexes) {
$t = $tokensByFile[$path];
foreach ($indexes as $idx) {
$t[$idx] = array(T_FUNCTION, 'static ' . $t[$idx][1], 0);
}
$out = '';
foreach ($t as $tok) $out .= is_array($tok) ? $tok[1] : $tok;
file_put_contents($path, $out);
$result[$path] = count($indexes);
}
return $result;
}
/**
* Ersetzt String-Offsets mit geschweiften Klammern ($str{0}) durch eckige
* Klammern ($str[0]); unter PHP 8 ist die alte Syntax ein Parse-Error.
* Erfasst wird nur "{" direkt nach einer Variablen oder "]" (ohne
* Leerzeichen), dort hat "{" keine andere Bedeutung.
*
* Liefert [neuer Quelltext, Anzahl ersetzter Offsets].
*/
function patchCurlyOffsets($src)
{
if (strpos($src, '{') === false) return array($src, 0);
$tokens = @token_get_all($src);
$n = count($tokens);
$count = 0;
for ($i = 1; $i < $n; $i++) {
if ($tokens[$i] !== '{') continue;
$prev = $tokens[$i - 1];
if (!($prev === ']' || (is_array($prev) && $prev[0] === T_VARIABLE))) continue;
// Passende schliessende Klammer suchen
$depth = 0;
for ($j = $i; $j < $n; $j++) {
$t = $tokens[$j];
if ($t === '{' || (is_array($t) && in_array($t[0], array(T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES), true))) $depth++;
elseif ($t === '}') { $depth--; if ($depth === 0) break; }
}
if ($j >= $n) continue;
$tokens[$i] = '[';
$tokens[$j] = ']';
$count++;
}
if ($count === 0) return array($src, 0);
$out = '';
foreach ($tokens as $t) $out .= is_array($t) ? $t[1] : $t;
return array($out, $count);
}
/**
* Liefert [neuer Quelltext, Anzahl eingefuegter Konstruktoren].
*/
function patchConstructors($src)
{
if (stripos($src, 'class') === false) return array($src, 0);
$eol = (strpos($src, "\r\n") !== false) ? "\r\n" : "\n";
$tokens = @token_get_all($src);
$n = count($tokens);
$inserts = array(); // Byte-Offset => einzufuegender Text
// Byte-Offsets aller Tokens berechnen
$offsets = array();
$pos = 0;
for ($i = 0; $i < $n; $i++) {
$offsets[$i] = $pos;
$pos += strlen(is_array($tokens[$i]) ? $tokens[$i][1] : $tokens[$i]);
}
for ($i = 0; $i < $n; $i++) {
if (!is_array($tokens[$i]) || $tokens[$i][0] !== T_CLASS) continue;
// "Foo::class" und anonyme Klassen ("new class") ignorieren
$prev = prevSignificant($tokens, $i);
if ($prev !== null && is_array($tokens[$prev]) && in_array($tokens[$prev][0], array(T_DOUBLE_COLON, T_NEW), true)) continue;
$nameIdx = nextSignificant($tokens, $i);
if ($nameIdx === null || !is_array($tokens[$nameIdx]) || $tokens[$nameIdx][0] !== T_STRING) continue;
$className = $tokens[$nameIdx][1];
// Oeffnende Klammer des Klassenkoerpers suchen
$open = $nameIdx;
while ($open < $n && $tokens[$open] !== '{') $open++;
if ($open >= $n) continue;
// Methoden auf Klassenebene einsammeln
$depth = 0;
$hasConstruct = false;
$oldCtor = null;
for ($j = $open; $j < $n; $j++) {
$t = $tokens[$j];
if ($t === '{' || (is_array($t) && in_array($t[0], array(T_CURLY_OPEN, T_DOLLAR_OPEN_CURLY_BRACES), true))) {
$depth++;
} elseif ($t === '}') {
$depth--;
if ($depth === 0) break;
} elseif ($depth === 1 && is_array($t) && $t[0] === T_FUNCTION) {
$k = nextSignificant($tokens, $j);
if ($k !== null && $tokens[$k] === '&') $k = nextSignificant($tokens, $k);
if ($k === null || !is_array($tokens[$k])) continue;
$method = $tokens[$k][1];
if (strcasecmp($method, '__construct') === 0) $hasConstruct = true;
if (strcasecmp($method, $className) === 0) $oldCtor = $k;
}
}
if ($hasConstruct || $oldCtor === null) continue;
// Parameterliste der alten Methode uebernehmen
$p = $oldCtor;
while ($p < $n && $tokens[$p] !== '(') $p++;
$params = '';
$vars = array();
$byRef = false;
$pd = 0;
for ($q = $p; $q < $n; $q++) {
$t = $tokens[$q];
if ($t === '(') { $pd++; if ($pd === 1) continue; }
if ($t === ')') { $pd--; if ($pd === 0) break; }
$txt = is_array($t) ? $t[1] : $t;
$params .= $txt;
if ($pd === 1 && is_array($t) && $t[0] === T_VARIABLE) $vars[] = $t[1];
if ($pd === 1 && $t === '&') $byRef = true;
}
// Ohne Referenzparameter func_get_args() nutzen (gleiche Semantik bei
// optionalen Parametern), sonst die Variablen direkt durchreichen.
$call = $byRef ? implode(', ', $vars) : '...func_get_args()';
$shim = "{$eol}\t// tmserver-docker: Php8Compat{$eol}"
. "\tfunction __construct(" . trim($params) . ") { \$this->{$tokens[$oldCtor][1]}($call); }";
$inserts[$offsets[$open] + 1] = $shim;
}
if (empty($inserts)) return array($src, 0);
krsort($inserts);
foreach ($inserts as $off => $txt) {
$src = substr($src, 0, $off) . $txt . substr($src, $off);
}
return array($src, count($inserts));
}
function nextSignificant($tokens, $i)
{
for ($j = $i + 1, $n = count($tokens); $j < $n; $j++) {
if (is_array($tokens[$j]) && in_array($tokens[$j][0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT), true)) continue;
return $j;
}
return null;
}
function prevSignificant($tokens, $i)
{
for ($j = $i - 1; $j >= 0; $j--) {
if (is_array($tokens[$j]) && in_array($tokens[$j][0], array(T_WHITESPACE, T_COMMENT, T_DOC_COMMENT), true)) continue;
return $j;
}
return null;
}
File diff suppressed because it is too large. Load diff
File renamed without changes.
+33
View File
@@ -0,0 +1,33 @@
#!/bin/sh
# Überwacht die ausgehende öffentliche IP des Containers und startet tmserver neu,
# wenn sich die IP ändert, damit er sich mit der neuen IP beim Masterserver registriert.
set -e
apk add --no-cache curl docker-cli > /dev/null 2>&1
set +e
INTERVAL="${IP_WATCHER_INTERVAL:-300}"
LAST_IP=""
echo "[ip-watcher] Gestartet. Prüfintervall: ${INTERVAL}s"
while true; do
CURRENT_IP=$(curl -s --max-time 10 https://api.ipify.org 2>/dev/null)
if [ -z "$CURRENT_IP" ]; then
echo "[ip-watcher] Öffentliche IP konnte nicht ermittelt werden. Neuer Versuch in ${INTERVAL}s."
elif [ "$CURRENT_IP" != "$LAST_IP" ]; then
if [ -n "$LAST_IP" ]; then
echo "[ip-watcher] IP geändert: ${LAST_IP} -> ${CURRENT_IP}. Starte tmserver neu..."
docker restart tmserver
echo "[ip-watcher] tmserver erfolgreich neu gestartet."
else
echo "[ip-watcher] Initiale öffentliche IP: ${CURRENT_IP}"
fi
LAST_IP="$CURRENT_IP"
else
echo "[ip-watcher] IP-Prüfung OK: ${CURRENT_IP} (unverändert). Nächste Prüfung in ${INTERVAL}s."
fi
sleep "$INTERVAL"
done
+186
View File
@@ -0,0 +1,186 @@
#!/bin/sh
# ============================================================
# XAseco Healthcheck / Watchdog
# ============================================================
# Ueberwacht den XAseco-Prozess und startet ihn automatisch
# neu, wenn er abgestuerzt ist oder die Verbindung zum
# TrackmaniaServer verloren hat (kein Overlay mehr sichtbar).
#
# Pruefungen:
# 1) PID-Check: Ist der XAseco-PHP-Prozess noch aktiv?
# 2) XMLRPC-Check: Kann XAseco den TM-Server noch erreichen?
# (Erkennt haengende Prozesse, die das Overlay verloren haben)
#
# Umgebungsvariablen:
# XASECO_HEALTHCHECK - true/false (Standard: true)
# XASECO_HEALTHCHECK_INTERVAL - Pruefintervall in Sekunden (Standard: 60)
# SERVER_XMLRPC_PORT - XMLRPC-Port des TM-Servers (Standard: 5000)
# ============================================================
XASECO_DIR="/opt/tmserver/xaseco"
HEALTHCHECK_INTERVAL="${XASECO_HEALTHCHECK_INTERVAL:-60}"
XMLRPC_PORT="${SERVER_XMLRPC_PORT:-5000}"
XASECO_PID_FILE="/tmp/xaseco.pid"
RESTART_COUNT=0
MAX_CONSECUTIVE_FAILURES=3
FAILURE_COUNT=0
log() {
echo "[XAseco-Healthcheck] $(date '+%Y-%m-%d %H:%M:%S') $1"
}
# ============================================================
# XAseco starten und PID speichern
# ============================================================
start_xaseco() {
cd "$XASECO_DIR"
php aseco.php TMN </dev/null >>aseco.log 2>&1 &
NEW_PID=$!
echo "$NEW_PID" > "$XASECO_PID_FILE"
cd /opt/tmserver
RESTART_COUNT=$((RESTART_COUNT + 1))
FAILURE_COUNT=0
log "XAseco gestartet (PID: ${NEW_PID}, Neustart #${RESTART_COUNT})"
}
# ============================================================
# Pruefen, ob der XAseco-Prozess noch laeuft
# ============================================================
check_pid() {
if [ ! -f "$XASECO_PID_FILE" ]; then
return 1
fi
CURRENT_PID=$(cat "$XASECO_PID_FILE")
if [ -z "$CURRENT_PID" ]; then
return 1
fi
# Pruefen ob der Prozess existiert und ein PHP-Prozess ist
if kill -0 "$CURRENT_PID" 2>/dev/null; then
return 0
fi
return 1
}
# ============================================================
# XMLRPC-Verbindungscheck: Pruefen, ob der TM-Server noch
# erreichbar ist (erkennt verlorene Verbindungen)
# ============================================================
check_xmlrpc_connection() {
php -r '
$port = (int)$argv[1];
// Testen ob der XMLRPC-Port noch erreichbar ist
$fp = @fsockopen("127.0.0.1", $port, $errno, $errstr, 5);
if (!$fp) { exit(1); }
// Handshake lesen
$data = @fread($fp, 4);
if (strlen($data) < 4) { fclose($fp); exit(1); }
$info = unpack("Vsize", $data);
$handshake = @fread($fp, $info["size"]);
if (strpos($handshake, "GBXRemote") === false) { fclose($fp); exit(1); }
fclose($fp);
exit(0);
' "$XMLRPC_PORT" 2>/dev/null
return $?
}
# ============================================================
# XAseco-Log auf aktuelle Fehler pruefen
# ============================================================
check_xaseco_log() {
LOG_FILE="$XASECO_DIR/aseco.log"
if [ ! -f "$LOG_FILE" ]; then
return 0
fi
# Letzte 20 Zeilen auf fatale Fehler / Verbindungsabbrueche pruefen.
# Fehlschlaege externer Webdienste (Dedimania, ManiaKarma, ...) laufen ueber
# Webaccess/fsockopen und betreffen nicht die Verbindung zum TM-Server.
# Ohne Filter loeste das nicht erreichbare dedimania.net alle ~30 Minuten
# einen XAseco-Neustart aus.
LAST_LINES=$(tail -20 "$LOG_FILE" 2>/dev/null | grep -v -i "webaccess\|fsockopen\|dedimania")
# Typische Fehlermeldungen bei XAseco-Verbindungsverlust
if echo "$LAST_LINES" | grep -qi "connection refused\|broken pipe\|server not responding\|transport error\|socket error\|fatal error"; then
return 1
fi
return 0
}
# ============================================================
# Hauptschleife: Regelmaeige Ueberwachung
# ============================================================
log "Watchdog gestartet (Intervall: ${HEALTHCHECK_INTERVAL}s, XMLRPC-Port: ${XMLRPC_PORT})"
while true; do
sleep "$HEALTHCHECK_INTERVAL"
# Pruefen ob der TM-Server selbst noch laeuft
if ! check_xmlrpc_connection; then
log "XMLRPC-Port ${XMLRPC_PORT} nicht erreichbar - TM-Server vermutlich beendet. Watchdog stoppt."
break
fi
NEED_RESTART=false
REASON=""
# 1) PID-Check: Prozess noch aktiv?
if ! check_pid; then
NEED_RESTART=true
REASON="Prozess nicht mehr aktiv (PID: $(cat "$XASECO_PID_FILE" 2>/dev/null || echo 'unbekannt'))"
fi
# 2) Log-Check: Fatale Fehler erkannt?
if [ "$NEED_RESTART" = "false" ] && ! check_xaseco_log; then
FAILURE_COUNT=$((FAILURE_COUNT + 1))
log "WARNUNG: Fehler im XAseco-Log erkannt (${FAILURE_COUNT}/${MAX_CONSECUTIVE_FAILURES})"
if [ "$FAILURE_COUNT" -ge "$MAX_CONSECUTIVE_FAILURES" ]; then
NEED_RESTART=true
REASON="Wiederholte Fehler im Log (${FAILURE_COUNT}x)"
fi
else
# Kein Fehler -> Zaehler zuruecksetzen (nur wenn PID OK)
if [ "$NEED_RESTART" = "false" ]; then
FAILURE_COUNT=0
fi
fi
# Neustart durchfuehren
if [ "$NEED_RESTART" = "true" ]; then
log "NEUSTART ERFORDERLICH: ${REASON}"
# Alten Prozess sicherheitshalber beenden
OLD_PID=$(cat "$XASECO_PID_FILE" 2>/dev/null)
if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then
log "Beende haengenden Prozess (PID: ${OLD_PID})..."
kill "$OLD_PID" 2>/dev/null
sleep 3
# Falls noch aktiv: SIGKILL
if kill -0 "$OLD_PID" 2>/dev/null; then
kill -9 "$OLD_PID" 2>/dev/null
sleep 1
fi
fi
# Log rotieren (altes Log sichern fuer Debugging)
if [ -f "$XASECO_DIR/aseco.log" ]; then
TIMESTAMP=$(date '+%Y%m%d_%H%M%S')
cp "$XASECO_DIR/aseco.log" "$XASECO_DIR/aseco_crash_${TIMESTAMP}.log"
log "Crash-Log gesichert: aseco_crash_${TIMESTAMP}.log"
# Maximal 5 Crash-Logs aufbewahren
ls -t "$XASECO_DIR"/aseco_crash_*.log 2>/dev/null | tail -n +6 | xargs rm -f 2>/dev/null
fi
# Kurz warten, damit evtl. Ressourcen freigegeben werden
sleep 2
# XAseco neu starten
start_xaseco
log "XAseco erfolgreich neugestartet."
# Nach Neustart etwas laenger warten, damit XAseco sich initialisieren kann
sleep 10
fi
done
log "Watchdog beendet."
Binary file not shown.
Binary file not shown.
@@ -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;
}
?>
@@ -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>
+92
View File
@@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8" ?>
<dedicated>
<authorization_levels>
<level>
<name>SuperAdmin</name>
<password>%%SERVER_SA_PASSWORD%%</password>
</level>
<level>
<name>Admin</name>
<password>%%SERVER_ADM_PASSWORD%%</password>
</level>
<level>
<name>User</name>
<password>%%SERVER_USER_PASSWORD%%</password>
</level>
</authorization_levels>
<masterserver_account>
<login>%%SERVER_LOGIN%%</login>
<password>%%SERVER_LOGIN_PASSWORD%%</password>
<validation_key>%%SERVER_VALIDATION_KEY%%</validation_key>
</masterserver_account>
<server_options>
<name>%%SERVER_NAME%%</name>
<comment>%%SERVER_DESC%%</comment>
<hide_server>%%SERVER_HIDE%%</hide_server> <!-- value is 0 (always shown), 1 (always hidden), 2 (hidden from nations) -->
<max_players>%%SERVER_MAX_PLAYERS%%</max_players>
<password>%%SERVER_PASSWORD%%</password>
<max_spectators>%%SERVER_MAX_SPECTATORS%%</max_spectators>
<password_spectator>%%SERVER_SPEC_PASSWORD%%</password_spectator>
<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_max>%%SERVER_LADDER_LIMIT_MAX%%</ladder_serverlimit_max>
<enable_p2p_upload>True</enable_p2p_upload>
<enable_p2p_download>True</enable_p2p_download>
<callvote_timeout>60000</callvote_timeout>
<callvote_ratio>0.5</callvote_ratio> <!-- default ratio. value in [0..1], or -1 to forbid. -->
<callvote_ratios>
<voteratio command="Ban" ratio="0.65"/>
<!-- commands can be "Ban", "Kick", "ChallengeRestart", "NextChallenge", ... -->
</callvote_ratios>
<allow_challenge_download>False</allow_challenge_download>
<autosave_replays>True</autosave_replays>
<autosave_validation_replays>True</autosave_validation_replays>
<referee_password></referee_password>
<referee_validation_mode>0</referee_validation_mode> <!-- value is 0 (only validate top3 players), 1 (validate all players) -->
<use_changing_validation_seed>False</use_changing_validation_seed>
</server_options>
<system_config>
<connection_uploadrate>%%SERVER_UPLOAD_RATE%%</connection_uploadrate> <!-- Kbps (kilo bits per second) -->
<connection_downloadrate>%%SERVER_DOWNLOAD_RATE%%</connection_downloadrate> <!-- Kbps -->
<force_ip_address></force_ip_address>
<server_port>%%SERVER_PORT%%</server_port>
<server_p2p_port>%%SERVER_P2P_PORT%%</server_p2p_port>
<client_port>0</client_port>
<bind_ip_address></bind_ip_address>
<use_nat_upnp></use_nat_upnp>
<p2p_cache_size>600</p2p_cache_size>
<xmlrpc_port>%%SERVER_XMLRPC_PORT%%</xmlrpc_port>
<xmlrpc_allowremote>True</xmlrpc_allowremote> <!-- If you specify an ip adress here, it'll be the only accepted adress. this will improve security. -->
<blacklist_url></blacklist_url>
<guestlist_filename></guestlist_filename>
<blacklist_filename></blacklist_filename>
<packmask>stadium</packmask>
<allow_spectator_relays>False</allow_spectator_relays>
<!-- <minimum_client_build>2009-10-01</minimum_client_build> -->
<!-- <disable_coherence_checks>laps</disable_coherence_checks> -->
<use_proxy>False</use_proxy>
<proxy_login></proxy_login>
<proxy_password></proxy_password>
</system_config>
</dedicated>
+69
View File
@@ -0,0 +1,69 @@
# ============================================================
# Log-Rotation fuer tmserver-docker
# ============================================================
# Wird stuendlich per Background-Loop ausgefuehrt.
# Rotation: groessenbasiert (10 MB), max. 5 rotierte Dateien.
# ============================================================
# Apache Access- und Error-Log
/var/log/apache2/access.log
/var/log/apache2/error.log
{
size 10M
rotate 5
missingok
notifempty
compress
delaycompress
copytruncate
}
# PHP Error-Log
/var/log/php_errors.log
{
size 10M
rotate 5
missingok
notifempty
compress
delaycompress
copytruncate
}
# XAseco-Log (liegt im persistenten Volume)
/opt/tmserver/xaseco/aseco.log
{
size 10M
rotate 5
missingok
notifempty
compress
delaycompress
copytruncate
}
# RemoteCP-Logs (liegen im persistenten Volume)
/var/www/html/remotecp/cache/*.log
{
su www-data www-data
size 10M
rotate 5
missingok
notifempty
compress
delaycompress
copytruncate
}
# AdminServ-Logs (liegen im persistenten Volume)
/var/www/html/logs/*.log
{
su www-data www-data
size 10M
rotate 5
missingok
notifempty
compress
delaycompress
copytruncate
}
@@ -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>
+29
View File
@@ -0,0 +1,29 @@
#!/bin/bash
# ============================================================
# MariaDB Init-Script: XAseco-Datenbank erstellen
# ============================================================
# Wird automatisch beim ersten Start des MariaDB-Containers
# ausgefuehrt (via /docker-entrypoint-initdb.d/).
# Erstellt Datenbank und Benutzer fuer XAseco, sofern die
# entsprechenden Umgebungsvariablen gesetzt sind.
# ============================================================
XASECO_DB_NAME="${XASECO_DB_NAME:-xaseco}"
XASECO_DB_USER="${XASECO_DB_USER:-xaseco}"
XASECO_DB_PASSWORD="${XASECO_DB_PASSWORD:-}"
if [ -z "$XASECO_DB_PASSWORD" ]; then
echo "HINWEIS: XASECO_DB_PASSWORD nicht gesetzt – ueberspringe XAseco-DB-Erstellung."
exit 0
fi
echo "Erstelle XAseco-Datenbank '${XASECO_DB_NAME}' und Benutzer '${XASECO_DB_USER}'..."
mariadb -u root -p"${MYSQL_ROOT_PASSWORD}" <<-EOSQL
CREATE DATABASE IF NOT EXISTS \`${XASECO_DB_NAME}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS '${XASECO_DB_USER}'@'%' IDENTIFIED BY '${XASECO_DB_PASSWORD}';
GRANT ALL PRIVILEGES ON \`${XASECO_DB_NAME}\`.* TO '${XASECO_DB_USER}'@'%';
FLUSH PRIVILEGES;
EOSQL
echo "XAseco-Datenbank erfolgreich erstellt."
+83
View File
@@ -0,0 +1,83 @@
services:
tmserver:
image: git.techniverse.net/scriptos/trackmania-server:1.4.1
build:
context: .
container_name: tmserver
restart: unless-stopped
stop_grace_period: 30s
depends_on:
mariadb:
condition: service_healthy
ports:
- "${SERVER_PORT:-2350}:${SERVER_PORT:-2350}/tcp"
- "${SERVER_PORT:-2350}:${SERVER_PORT:-2350}/udp"
- "${SERVER_P2P_PORT:-3450}:${SERVER_P2P_PORT:-3450}/tcp"
- "16080:80/tcp"
- "3450:3450/udp"
env_file:
- .env
dns:
- 1.1.1.1
volumes:
- ./data/gamedata:/opt/tmserver/GameData
- ./data/controlpanel:/var/www/html
- ./data/xaseco:/opt/tmserver/xaseco
networks:
tmserver_net:
ipv4_address: 172.20.72.10
mariadb:
image: mariadb:11.4
container_name: tmserver-mariadb
restart: unless-stopped
env_file:
- .env
environment:
MYSQL_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
# RemoteCP-Datenbank (via MariaDB-Init)
MYSQL_DATABASE: ${REMOTECP_DB_NAME:-remotecp}
MYSQL_USER: ${REMOTECP_DB_USER:-remotecp}
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:
- ./data/mariadb:/var/lib/mysql
- ./assets/db/init-xaseco-db.sh:/docker-entrypoint-initdb.d/20-init-xaseco-db.sh:ro
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 10s
timeout: 5s
retries: 5
start_period: 30s
networks:
tmserver_net:
ipv4_address: 172.20.72.11
ip-watcher:
image: alpine:3.24
container_name: tmserver-ip-watcher
restart: unless-stopped
depends_on:
- tmserver
command: ["/bin/sh", "/opt/WatchPublicIP.sh"]
env_file:
- .env
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./assets/bin/WatchPublicIP.sh:/opt/WatchPublicIP.sh:ro
networks:
tmserver_net:
ipv4_address: 172.20.72.12
networks:
tmserver_net:
name: tmserver.dockernetwork.local
driver: bridge
ipam:
config:
- subnet: 172.20.72.0/25
gateway: 172.20.72.1
ip_range: 172.20.72.64/26
+70
View File
@@ -0,0 +1,70 @@
# Dokumentation
Übersicht aller verfügbaren Dokumentationen für den Trackmania Nations Forever Docker Server.
> **Wichtig:** Vor dem ersten Start muss eine `.env`-Datei aus der Vorlage erstellt werden: `cp .env.example .env`
> **⚠ Sicherheitshinweis:** Die `.env.example` enthält **vorgenerierte Beispiel-Passwörter**. Diese dienen nur als Platzhalter und sind öffentlich einsehbar! **Ändere unbedingt alle Passwörter** in der `.env`-Datei, bevor du den Server produktiv einsetzt.
| Dokument | Beschreibung |
|----------|-------------|
| [Schnellstart](schnellstart.md) | Erste Schritte und minimale Konfiguration |
| [Konfiguration](konfiguration.md) | Persistente Serverkonfiguration (dedicated_cfg.txt), Graceful Shutdown |
| [Umgebungsvariablen](umgebungsvariablen.md) | Alle verfügbaren Umgebungsvariablen |
| [Server-Modi](server-modi.md) | LAN- und Internet-Dedicated-Modus |
| [AdminServ](adminserv.md) | Einrichtung der Server-Verwaltungsoberfläche |
| [RemoteCP](remotecp.md) | Alternative Server-Verwaltungsoberfläche (inkl. Mods/Skins) |
| [XAseco](xaseco.md) | Server-Controller für Rekorde, Karma und Jukebox |
| [IP-Watcher](ip-watcher.md) | Automatischer Neustart bei IP-Wechsel |
| [Ports](ports.md) | Freigegebene Ports und deren Verwendung |
| [Update](update.md) | Bestehende Installation aktualisieren |
## Projektstruktur
```
├── assets/
│ ├── bin/ # Binaries und Startscript
│ │ ├── AdminServ_v2.1.1.zip # AdminServ Web-UI
│ │ ├── remoteCP_v4.0.3.5.zip # RemoteCP Web-UI
│ │ ├── RunTrackmaniaServer.sh # Container-Startscript
│ │ ├── TrackmaniaServer_2011-02-21.zip # Trackmania Server Binary
│ │ ├── WatchPublicIP.sh # IP-Watcher-Script
│ │ └── xaseco_v1.16.zip # XAseco Server-Controller
│ ├── config/
│ │ ├── adminserv/ # AdminServ-Konfiguration
│ │ │ ├── get_matchset_mapimport.php # MatchSet Map-Import Script
│ │ │ └── maps-creatematchset.php # MatchSet-Erstellung Script
│ │ ├── custom_game_settings.txt # MatchSettings (Spielmodus, Map-Rotation)
│ │ ├── dedicated_cfg.txt # Server-Config-Template (mit Platzhaltern)
│ │ ├── logrotate.conf # Log-Rotation-Konfiguration (groessenbasiert)
│ │ ├── remotecp/
│ │ │ └── plugins/
│ │ │ ├── CustomPoints/
│ │ │ │ └── index.php # CustomPoints-Plugin fuer RemoteCP
│ │ │ └── Mods/
│ │ │ ├── index.php # Mods-Plugin fuer RemoteCP
│ │ │ └── settings.xml # Skin-Bibliothek (techniverse.net)
│ │ └── xaseco/
│ │ └── teamspeak3.xml # TeamSpeak3-Konfiguration fuer XAseco
│ └── db/
│ └── init-xaseco-db.sh # MariaDB Init-Script fuer XAseco-DB
├── .gitea/
│ └── workflows/
│ └── docker-publish.yml # CI/CD: Docker Image Build & Push bei neuem Release-Tag
├── docs/ # Dokumentation (siehe Tabelle oben)
│ └── update.md # Update-Anleitung
├── docker-compose.yml # Docker Compose Konfiguration
├── Dockerfile # Docker Build-Definition
├── .dockerignore # Docker-Ignore-Regeln
├── .env.example # Vorlage fuer Umgebungsvariablen
├── .env # Lokale Umgebungsvariablen (nicht im Git!)
├── .gitattributes # Git-Attribut-Konfiguration
├── .gitignore # Git-Ignore-Regeln
├── 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
```
+116
View File
@@ -0,0 +1,116 @@
# AdminServ – Server-Verwaltungsoberfläche
Die Server-Verwaltungsoberfläche basiert auf [AdminServ](https://github.com/Chris92de/AdminServ) und ist über Port 80 erreichbar.
## Einrichtung
AdminServ wird beim ersten Container-Start **vollständig automatisch konfiguriert**:
- Serververbindung (Adresse `127.0.0.1`, XML-RPC-Port)
- Server-Eintrag (Name, DisplayServ-Passwort)
- Konfigurationspasswort (siehe [Konfigurationsseite (`/config`)](#konfigurationsseite-config))
Es ist kein manuelles Setup nötig.
## Verbindung zum Server
1. `http://<host-server-des-containers>` im Browser aufrufen
2. Den Server aus der Liste auswählen
3. Admin-Stufe wählen (SuperAdmin, Admin oder User) und das zugehörige Passwort eingeben
## Standard-Passwörter
| Stufe | Standard-Passwort |
|-------|-------------------|
| SuperAdmin | `SuperAdmin` |
| Admin | `Admin` |
| User | `User` |
Diese Passwörter werden über die `.env`-Datei gesetzt (`SERVER_SA_PASSWORD`, `SERVER_ADM_PASSWORD`, `SERVER_USER_PASSWORD`) und beim ersten Start in die `dedicated_cfg.txt` geschrieben. AdminServ liest die Passwörter über die XML-RPC-Verbindung direkt vom TM-Server.
Die Admin-Stufen können unter `http://<host-server-des-containers>/config` geändert werden.
> **Hinweis:** Die Standard-Passwörter in der `.env.example` sind öffentlich einsehbar. Ändere sie unbedingt, bevor du den Server produktiv einsetzt. Siehe [Umgebungsvariablen](umgebungsvariablen.md).
## Persistente Speicherung
Alle AdminServ-Daten (Passwort, Server-Einträge, Konfiguration, Logs) werden über einen Bind-Mount (`./data/controlpanel`) persistent auf dem Host gespeichert. Beim ersten Start werden die Dateien automatisch aus dem Image ins Volume kopiert.
| Host-Pfad | Container-Pfad | Beschreibung |
|-----------|----------------|-------------|
| `./data/controlpanel` | `/var/www/html` | AdminServ- und RemoteCP-Installation |
> **Hinweis:** Im selben Volume befindet sich auch [RemoteCP](remotecp.md) unter `./data/controlpanel/remotecp/`.
## Fehlerbehebung
### Login funktioniert nicht nach dem Anlegen eines Servers
Falls der Login nicht funktioniert oder ein Authentifizierungsfehler angezeigt wird, können die PHP-Logs Aufschluss geben:
1. In der `.env`-Datei `PHP_DISPLAY_ERRORS=true` setzen
2. Container neu starten: `docker compose up -d`
3. AdminServ im Browser öffnen und den Fehler reproduzieren
4. Die PHP-Fehlermeldung wird direkt auf der Seite angezeigt
Alternativ können die PHP-Logs eingesehen werden:
```bash
docker exec tmserver cat /var/log/php_errors.log
```
> **Hinweis:** Alle Logs (Apache, PHP, AdminServ) werden automatisch per logrotate rotiert (max. 10 MB pro Datei, 5 rotierte Dateien). Siehe [Konfiguration – Log-Rotation](konfiguration.md#log-rotation).
### AdminServ komplett zurücksetzen
Falls AdminServ in einen inkonsistenten Zustand geraten ist:
```bash
# AdminServ-Daten auf dem Host löschen
rm -rf ./data/controlpanel/*
# Container neu starten – AdminServ wird frisch initialisiert
docker compose up -d
```
## Konfigurationsseite (`/config`)
AdminServ bringt unter `http://<host-ip>/config` eine eigene Konfigurationsseite mit, über die Server-Einträge hinzugefügt, geändert oder gelöscht werden können. Diese Seite ist durch ein separates Passwort geschützt (`OnlineConfig::PASSWORD` in `adminserv.cfg.php`, MD5-gehasht).
Da dieser Container als **Standalone-Setup** läuft und ausschließlich den einen lokalen TrackMania-Server bedient, wird die `/config`-Seite **nicht benötigt** – der Server-Eintrag wird beim ersten Start automatisch angelegt (Adresse, XML-RPC-Port, Passwörter).
Zur Absicherung wird das Konfigurationspasswort beim ersten Container-Start automatisch durch einen **zufällig generierten MD5-Hash** ersetzt. Damit ist die `/config`-Seite vor unbefugtem Zugriff geschützt, ohne dass ein Benutzer ein Passwort vergeben oder sich merken muss.
> **Hinweis:** Falls du dennoch Zugriff auf die `/config`-Seite benötigst (z.B. für Debugging), kannst du den MD5-Hash in `data/controlpanel/config/adminserv.cfg.php` manuell auf ein bekanntes Passwort setzen. Beispiel: `const PASSWORD = '5f4dcc3b5aa765d61d8327deb882cf99';` entspricht dem Passwort `password`.
## 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`
+36
View File
@@ -0,0 +1,36 @@
# IP-Watcher
Der IP-Watcher ist ein Service (`tmserver-ip-watcher`), der automatisch die öffentliche IP des Hosts überwacht und den `tmserver`-Container neu startet, sobald sich die IP ändert.
## Hintergrund
Trackmania-Server registrieren sich beim Start mit ihrer aktuellen öffentlichen IP beim Nadeo-Masterserver. Bei dynamischen IP-Adressen kann sich diese IP jederzeit ändern – der Server bleibt dann unter der veralteten IP registriert und ist für Spieler nicht mehr erreichbar.
Der IP-Watcher erkennt solche IP-Wechsel automatisch und startet `tmserver` neu, sodass er sich mit der neuen IP beim Masterserver neu registriert.
## Funktion
Der Watcher läuft als eigener Docker-Container und:
1. Prüft regelmäßig die ausgehende öffentliche IP (identisch mit der IP, die auch `tmserver` nach außen verwendet)
2. Erkennt Abweichungen zur zuletzt bekannten IP
3. Startet `tmserver` über den Docker-Socket automatisch neu
## Konfiguration
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `IP_WATCHER_INTERVAL` | Prüfintervall in Sekunden | `300` (5 Minuten) |
## Deaktivieren
Um den IP-Watcher zu deaktivieren, kommentiere den `ip-watcher`-Service in der `docker-compose.yml` aus:
```yaml
# ip-watcher:
# ...
```
## Sicherheitshinweis
Der IP-Watcher benötigt Zugriff auf den Docker-Socket (`/var/run/docker.sock`), um den `tmserver`-Container neu starten zu können. Dieser Zugriff ermöglicht volle Kontrolle über alle Docker-Container und -Images auf dem Host. Stelle sicher, dass der Host ausreichend abgesichert ist und der Zugriff auf den Docker-Socket auf vertrauenswürdige Dienste beschränkt bleibt.
+308
View File
@@ -0,0 +1,308 @@
# Konfiguration (dedicated_cfg.txt)
Die Server-Konfiguration wird in der Datei `dedicated_cfg.txt` im Verzeichnis `GameData/Config/` gespeichert. Diese Datei enthält alle wichtigen Parameter für den Serverbetrieb.
Die Template-Konfiguration befindet sich im Repository unter `assets/config/dedicated_cfg.txt`. Sie enthält `%%PLATZHALTER%%` anstelle von echten Werten – keine Passwörter oder Zugangsdaten im Repo! Die Platzhalter werden beim ersten Container-Start durch die [Umgebungsvariablen](umgebungsvariablen.md) ersetzt.
## Persistente Speicherung
Das gesamte **GameData-Verzeichnis** wird über ein Bind-Mount (`./data/gamedata`) persistent auf dem Host gespeichert. Das bedeutet:
- Alle Daten (Konfiguration, Tracks, Skins, Scores, Cache, etc.) bleiben erhalten
- Manuelle Änderungen gehen nicht verloren, auch wenn der Container neu erstellt wird
- Dateien können direkt auf dem Host bearbeitet werden
### Volume-Pfade
| Host-Pfad | Container-Pfad | Beschreibung |
|-----------|----------------|-------------|
| `./data/gamedata` | `/opt/tmserver/GameData` | Gesamtes GameData-Verzeichnis |
| `./data/controlpanel` | `/var/www/html` | AdminServ- und RemoteCP-Daten |
| `./data/xaseco` | `/opt/tmserver/xaseco` | XAseco-Konfiguration und Logs |
| `./data/mariadb` | `/var/lib/mysql` | MariaDB-Datenbankdateien |
### Enthaltene Unterordner
| Ordner | Beschreibung |
|--------|-------------|
| `Config/` | Server-Konfiguration (`dedicated_cfg.txt`, Blacklist, Guestlist) |
| `Tracks/` | Strecken und MatchSettings |
| `Skins/` | Spieler- und Fahrzeug-Skins |
| `Scores/` | Gespeicherte Punktestände |
| `Cache/` | Server-Cache |
| `Profiles/` | Spielerprofile |
| `Replays/` | Gespeicherte Replays |
## Reihenfolge beim Docker-Build
1. ZIP-Archiv wird entpackt → Standard-`dedicated_cfg.txt` aus dem ZIP landet in `GameData/Config/`
2. Template aus `assets/config/dedicated_cfg.txt` (mit `%%PLATZHALTERN%%`) wird darüber kopiert
3. Gesamtes `GameData/`-Verzeichnis wird als Default-Template gesichert
## First-Run-Verhalten
Beim **ersten Start** des Containers (leeres Volume) passiert Folgendes:
1. Das gesamte Default-GameData-Template wird aus dem Image ins Volume kopiert
2. Alle `%%PLATZHALTER%%` in der `dedicated_cfg.txt` werden durch die [Umgebungsvariablen](umgebungsvariablen.md) ersetzt
3. Der Server startet mit der so erzeugten Konfiguration
Bei **weiteren Starts** wird die vorhandene Konfiguration **nicht überschrieben**. Umgebungsvariablen haben dann keine Wirkung mehr auf die `dedicated_cfg.txt`.
## Konfiguration manuell bearbeiten
Nach dem ersten Start kann die Konfiguration direkt auf dem Host bearbeitet werden:
```bash
# Konfiguration direkt auf dem Host bearbeiten
nano ./data/gamedata/Config/dedicated_cfg.txt
# Alternativ: im Container anzeigen
docker exec tmserver cat /opt/tmserver/GameData/Config/dedicated_cfg.txt
# Server neustarten, damit die Änderungen wirksam werden
docker restart tmserver
```
> **Hinweis:** Da `GameData/` als Bind-Mount eingebunden ist, sind Dateien direkt unter `./data/gamedata/` auf dem Host verfügbar – ohne `docker cp` oder `docker exec`.
## Konfiguration zurücksetzen (FORCE_CONFIG_UPDATE)
Falls die Umgebungsvariablen erneut auf die Konfiguration angewendet werden sollen (z.B. nach einer Passwortänderung), kann die Variable `FORCE_CONFIG_UPDATE` verwendet werden.
In der `.env`-Datei:
```bash
FORCE_CONFIG_UPDATE=true
```
Oder per `docker run`:
```bash
docker run -d \
--env-file .env \
-e FORCE_CONFIG_UPDATE=true \
-p 2350:2350/tcp \
-p 2350:2350/udp \
-p 3450:3450/tcp \
-p 80:80/tcp \
-v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \
--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.
## Wichtige Dateien im Config-Ordner
Der Ordner `GameData/Config/` enthält:
| Datei | Beschreibung |
|-------|-------------|
| `dedicated_cfg.txt` | Haupt-Konfigurationsdatei des Servers |
| `blacklist.txt` | Liste gesperrter Spieler |
| `guestlist.txt` | Liste erlaubter Spieler |
| `Default.SystemConfig.Gbx` | System-Konfiguration |
| `AdminServ/ServerOptions/` | Von AdminServ exportierte Server-Einstellungen |
## Graceful Shutdown
Beim Stoppen des Containers (`docker compose stop`, `docker compose down` oder `docker stop`) werden alle Dienste **sauber und in der richtigen Reihenfolge** heruntergefahren:
1. **XAseco-Healthcheck** – wird zuerst beendet, damit XAseco nicht während des Shutdowns neu gestartet wird
2. **XAseco** – beendet sich ordentlich und schließt alle Datenbank-Connections (verhindert DB-Korruption)
3. **TrackmaniaServer** – der Spielserver wird sauber gestoppt
4. **Apache** – AdminServ und RemoteCP werden beendet
5. **Log-Rotation** – Hintergrundprozess wird gestoppt
Jeder Dienst hat maximal 10 Sekunden Zeit, sich sauber zu beenden. Falls ein Prozess nicht reagiert, wird er zwangsweise beendet (SIGKILL). Die `stop_grace_period` in der `docker-compose.yml` ist auf 30 Sekunden gesetzt, um genügend Zeit für den gesamten Shutdown-Prozess zu geben.
Der Shutdown-Fortschritt wird in der Konsole protokolliert und kann mit `docker logs tmserver` nachvollzogen werden.
> **Hinweis:** Der Graceful Shutdown ist nach einem Image-Update automatisch aktiv – auch bei bestehenden Installationen. Es sind keine manuellen Schritte nötig.
## Log-Rotation
Alle Log-Dateien im Container werden automatisch per `logrotate` rotiert, damit sie nicht unbegrenzt wachsen. Die Rotation läuft **größenbasiert** als Hintergrundprozess (stündliche Prüfung, kein Cron nötig).
### Einstellungen
| Parameter | Wert |
|-----------|------|
| Maximale Dateigröße | 10 MB |
| Rotierte Dateien behalten | 5 |
| Komprimierung | Ja (gzip, verzögert) |
| Leere Logs überspringen | Ja |
### Rotierte Log-Dateien
| Log | Pfad im Container | Persistenz |
|-----|--------------------|------------|
| Apache Access | `/var/log/apache2/access.log` | Nur im Container |
| Apache Error | `/var/log/apache2/error.log` | Nur im Container |
| PHP Errors | `/var/log/php_errors.log` | Nur im Container |
| XAseco | `/opt/tmserver/xaseco/aseco.log` | Volume (`./data/xaseco/`) |
| AdminServ | `/var/www/html/logs/*.log` | Volume (`./data/controlpanel/logs/`) |
Rotierte Dateien werden als `*.1` (vorherige), `*.2.gz`, `*.3.gz` usw. aufbewahrt.
### Konfigurationsdatei
Die logrotate-Konfiguration liegt im Image unter `/etc/logrotate.d/tmserver` (Quelle: `assets/config/logrotate.conf`). Sie wird beim Bau des Images fest eingebettet und erfordert keine manuelle Anpassung.
> **Hinweis:** Die Log-Rotation ist nach einem Image-Update automatisch aktiv – auch bei bestehenden Installationen. Es sind keine manuellen Schritte nötig.
## Startup-Zusammenfassung
Nach Abschluss des gesamten Startprozesses wird automatisch eine übersichtliche Zusammenfassung aller wichtigen Server-Informationen als formatierte Box in der Konsole ausgegeben. Die Box-Breite passt sich dynamisch an den längsten Inhalt an.
Alle angezeigten Werte (Servername, Spielerzahl, Ladder-Modus etc.) werden direkt aus der `dedicated_cfg.txt` gelesen – nicht aus den Umgebungsvariablen. So werden auch nachträgliche Änderungen (z.B. über AdminServ oder manuelles Editieren) korrekt angezeigt.
**Angezeigte Informationen:**
| Bereich | Details | Quelle |
|---------|---------|--------|
| **Server** | Servername, Modus (Internet/LAN), Ladder, Spieler-/Zuschauerlimit | `dedicated_cfg.txt` |
| **Netzwerk** | Server-Port, P2P-Port, XMLRPC-Port | Umgebungsvariablen |
| **Maps** | Aktive MatchSettings-Datei, Anzahl geladener Maps, Shuffle-Status | MatchSettings-XML |
| **Dienste** | XAseco-Status (mit PID), Healthcheck, Forced Mods | Laufzeit-PIDs |
| **Web-Interfaces** | AdminServ- und RemoteCP-URLs | Platzhalter |
| **System** | Log-Rotation, PHP-Debug-Modus, TM-Server-PID | Laufzeit |
**Beispielausgabe:**
```
╔════════════════════════════════════════════════════════════════════╗
║ TrackMania Nations Forever - Server gestartet ║
╠════════════════════════════════════════════════════════════════════╣
║ Servername: Mein Trackmania Server ║
║ Modus: Internet (Ladder: forced) ║
║ Spieler: max. 32 Spieler / 32 Zuschauer ║
║ Server-Port: 2350 (TCP/UDP) | P2P: 3450 (TCP) ║
║ XMLRPC-Port: 5000 ║
╠════════════════════════════════════════════════════════════════════╣
║ MatchSettings: custom_game_settings.txt ║
║ Maps: 24 Maps geladen ║
║ Map-Shuffle: Deaktiviert ║
╠════════════════════════════════════════════════════════════════════╣
║ XAseco: Aktiv (PID 1234) ║
║ Healthcheck: Aktiv (PID 5678) ║
║ Forced Mods: Keine ║
╠════════════════════════════════════════════════════════════════════╣
║ AdminServ: http://<host-ip>/ ║
║ RemoteCP: http://<host-ip>/remotecp/ ║
╠════════════════════════════════════════════════════════════════════╣
║ Log-Rotation: Aktiv (stuendlich, max. 10 MB) ║
║ PHP-Debug: Deaktiviert ║
║ TM-Server: PID 42 ║
╚════════════════════════════════════════════════════════════════════╝
```
Die Zusammenfassung kann jederzeit mit `docker logs tmserver` erneut eingesehen werden.
> **Hinweis:** `<host-ip>` ist ein Platzhalter – ersetze ihn durch die tatsächliche IP oder Domain deines Hosts (z.B. `http://192.168.1.100/`).
> **Hinweis:** Die Startup-Zusammenfassung ist nach einem Image-Update automatisch aktiv – auch bei bestehenden Installationen. Es sind keine manuellen Schritte nötig.
## 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
Alle diese Parameter können über [Umgebungsvariablen](umgebungsvariablen.md) gesetzt werden:
| Parameter | Umgebungsvariable | Standard |
|-----------|------------------|----------|
| `<name>` (SuperAdmin-PW) | `SERVER_SA_PASSWORD` | `SuperAdmin` |
| `<name>` (Admin-PW) | `SERVER_ADM_PASSWORD` | `Admin` |
| `<name>` (User-PW) | `SERVER_USER_PASSWORD` | `User` |
| `<login>` | `SERVER_LOGIN` | *(leer)* |
| `<validation_key>` | `SERVER_VALIDATION_KEY` | *(leer)* |
| `<name>` (Servername) | `SERVER_NAME` | `Trackmania Server` |
| `<comment>` | `SERVER_DESC` | `Powered by tmserver-docker` |
| `<hide_server>` | `SERVER_HIDE` | `0` |
| `<max_players>` | `SERVER_MAX_PLAYERS` | `32` |
| `<password>` | `SERVER_PASSWORD` | *(leer)* |
| `<max_spectators>` | `SERVER_MAX_SPECTATORS` | `32` |
| `<ladder_mode>` | `SERVER_LADDER_MODE` | `forced` |
| `<server_port>` | `SERVER_PORT` | `2350` |
| `<server_p2p_port>` | `SERVER_P2P_PORT` | `3450` |
| `<xmlrpc_port>` | `SERVER_XMLRPC_PORT` | `5000` |
| `<connection_uploadrate>` | `SERVER_UPLOAD_RATE` | `512` |
| `<connection_downloadrate>` | `SERVER_DOWNLOAD_RATE` | `8192` |
## MatchSettings (Spieleinstellungen)
Die MatchSettings-Dateien liegen im Verzeichnis `data/gamedata/Tracks/MatchSettings/` und definieren Spielmodus, Regeln und die aktive Track-Liste des Servers. Sie werden als `.txt`-Dateien im XML-Format gespeichert.
### Automatische Erkennung (Standard)
Standardmäßig wird beim Serverstart automatisch die **neueste** `.txt`-Datei im MatchSettings-Ordner anhand des Änderungsdatums geladen. So werden z.B. über AdminServ erstellte oder bearbeitete MatchSettings beim nächsten Neustart automatisch aktiv.
```bash
# In der .env-Datei (Standardwert):
MATCHSETTINGS_FILE=auto
```
**Ablauf bei jedem Containerstart:**
1. Der Ordner `data/gamedata/Tracks/MatchSettings/` wird nach `.txt`-Dateien durchsucht
2. Die Datei mit dem neuesten Änderungsdatum wird ermittelt
3. Diese Datei wird als `/game_settings`-Parameter an den TM-Server übergeben
4. Dateiname und Änderungsdatum werden in der Konsole ausgegeben
### Bestimmte Datei verwenden
Alternativ kann eine bestimmte MatchSettings-Datei direkt angegeben werden:
```bash
# In der .env-Datei:
MATCHSETTINGS_FILE=turnier_settings.txt
```
Der Dateiname bezieht sich immer auf den Ordner `data/gamedata/Tracks/MatchSettings/`.
### Fallback
Falls die angegebene oder automatisch ermittelte Datei nicht existiert, wird auf die mitgelieferte Standard-Datei `custom_game_settings.txt` zurückgefallen.
### Neue MatchSettings über AdminServ erstellen
1. In AdminServ mit SuperAdmin einloggen
2. Unter „Maps" → „MatchSettings" → „Create" eine neue Datei anlegen
3. Tracks aus den gewünschten Ordnern importieren (z.B. `Challenges/Downloaded/`)
4. Spielmodus und Regeln konfigurieren
5. Speichern – die Datei wird in `MatchSettings/` abgelegt
6. Container neustarten (`docker compose restart`) – die neue Datei wird automatisch als neueste erkannt und geladen
> **Hinweis:** Die aktive MatchSettings-Datei wird beim Serverstart in der Konsole ausgegeben. Mit `docker logs tmserver` kann überprüft werden, welche Datei geladen wurde.
+38
View File
@@ -0,0 +1,38 @@
# Freigegebene Ports
| Port | Protokoll | Beschreibung |
|------|-----------|-------------|
| 2350 | TCP | Gameserver-Port |
| 2350 | UDP | Gameserver-Port |
| 3450 | TCP | P2P-Gameserver-Port |
| 5000 | TCP | XML-RPC-Port (nur containerintern, nicht nach außen freigegeben) |
| 80 | TCP | Server-Verwaltungsoberflächen (AdminServ + RemoteCP) |
## Minimale Port-Freigabe
Für den reinen Spielbetrieb ohne Verwaltungsoberfläche reichen die Ports 2350 und 3450:
```bash
docker run -d \
--env-file .env \
-p 2350:2350/tcp \
-p 2350:2350/udp \
-p 3450:3450/tcp \
-v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver git.techniverse.net/scriptos/trackmania-server:latest
```
> **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
> ```
+170
View File
@@ -0,0 +1,170 @@
# RemoteCP – Alternative Server-Verwaltungsoberfläche
RemoteCP ist eine weitere Web-Verwaltungsoberfläche für Trackmania-Server, die parallel zu AdminServ installiert ist. Es handelt sich um ein älteres Tool mit eigenem Login-System und eigener Benutzerverwaltung.
## Zugriff
RemoteCP ist als Subpath unter der gleichen Adresse wie AdminServ erreichbar:
- **AdminServ:** `http://<host-ip>/`
- **RemoteCP:** `http://<host-ip>/remotecp/`
## Standard-Zugangsdaten
RemoteCP verwendet die SuperAdmin-Zugangsdaten des Trackmania-Servers:
| Benutzer | Passwort | Beschreibung |
|----------|----------|-------------|
| `SuperAdmin` | Wert aus `SERVER_SA_PASSWORD` | Administrator (volle Rechte) |
| `Guest` | `Guest` | Gast-Zugang (nur Lesen) |
Der Admin-Login für RemoteCP ist identisch mit dem SuperAdmin-Passwort des TM-Servers (`SERVER_SA_PASSWORD`). Es werden keine separaten Zugangsdaten benötigt.
## Umgebungsvariablen
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `REMOTECP_DB_HOST` | Hostname des Datenbankservers | `mariadb` |
| `REMOTECP_DB_NAME` | Name der RemoteCP-Datenbank | `remotecp` |
| `REMOTECP_DB_USER` | Datenbank-Benutzername | `remotecp` |
| `REMOTECP_DB_PASSWORD` | Datenbank-Passwort | *(muss gesetzt werden)* |
## Datenbank (MariaDB)
RemoteCP benötigt eine MariaDB-Datenbank für erweiterte Funktionen (Spielerstatistiken, Records, Chat-Nachrichten, etc.). Ein MariaDB-Container wird automatisch als Teil des Docker-Setups bereitgestellt.
### Automatische Einrichtung
Wenn die Datenbank-Variablen in der `.env`-Datei gesetzt sind (`REMOTECP_DB_HOST`, `REMOTECP_DB_PASSWORD`), wird beim ersten Start:
1. Die **MariaDB** erstellt automatisch die Datenbank und den Benutzer (über `MYSQL_DATABASE`, `MYSQL_USER`, `MYSQL_PASSWORD`)
2. Das **Startup-Script** wartet, bis MariaDB erreichbar ist
3. Die **Datenbank-Tabellen** werden automatisch aus den SQL-Schema-Dateien importiert
4. Die **Installer-Markierung** (`cache/installed`) wird gesetzt, damit der Web-Installer übersprungen wird
Es ist kein manuelles Setup über den Web-Installer nötig.
### Manuelle Einrichtung
Falls die automatische Einrichtung nicht gewünscht ist oder fehlschlägt (z.B. MariaDB nicht erreichbar), kann RemoteCP auch manuell eingerichtet werden:
1. Die Datenbank-Variablen (`REMOTECP_DB_HOST`, `REMOTECP_DB_PASSWORD`) **müssen trotzdem in der `.env`-Datei gesetzt sein**, damit die MariaDB die Datenbank und den Benutzer beim ersten Start erstellt
2. RemoteCP-Installer im Browser aufrufen: `http://<host-ip>/remotecp/index.php?page=install`
3. „Install with remoteCP-Database" wählen
4. Die Datenbank-Zugangsdaten eingeben:
- **Database DSN:** `mysql:dbname=remotecp;host=mariadb` (bzw. die Werte aus der `.env`)
- **Database Username:** Wert aus `REMOTECP_DB_USER`
- **Database Password:** Wert aus `REMOTECP_DB_PASSWORD`
5. Server-Daten eingeben (XMLRPC-Port, SuperAdmin-Passwort)
6. Installation abschließen
### Persistente Daten
Die MariaDB-Daten werden über ein eigenes Bind-Mount persistent gespeichert:
| Host-Pfad | Container-Pfad | Beschreibung |
|-----------|----------------|-------------|
| `./data/mariadb` | `/var/lib/mysql` | MariaDB-Datenbankdateien |
> **Hinweis:** Der MariaDB-Container kann auch für weitere Tools genutzt werden. Zusätzliche Datenbanken und Benutzer können über den Root-Zugang (`MARIADB_ROOT_PASSWORD`) erstellt werden.
## Automatische Konfiguration
Beim ersten Start wird RemoteCP automatisch konfiguriert:
- Die **Serververbindung** wird mit dem XML-RPC-Port und dem SuperAdmin-Passwort (`SERVER_SA_PASSWORD`) aus der `.env`-Datei eingerichtet
- Der **Admin-Zugang** verwendet den Benutzernamen `SuperAdmin` mit dem Passwort aus `SERVER_SA_PASSWORD`
- Das Passwort wird als MD5-Hash in der Konfiguration gespeichert
- Die **Datenbankverbindung** wird automatisch in `servers.xml` eingetragen (wenn DB-Variablen gesetzt sind)
## Persistente Speicherung
RemoteCP wird im gleichen Volume wie AdminServ gespeichert:
| Host-Pfad | Container-Pfad | Beschreibung |
|-----------|----------------|-------------|
| `./data/controlpanel/remotecp` | `/var/www/html/remotecp` | RemoteCP-Installation |
Die Konfigurationsdateien befinden sich unter `./data/controlpanel/remotecp/xml/`:
| Datei | Beschreibung |
|-------|-------------|
| `servers.xml` | Server-Verbindungsdaten (Host, Port, Passwort, Datenbank) |
| `admins.xml` | Benutzer und Zugangsdaten |
| `groups.xml` | Berechtigungsgruppen |
## Mods / Skins
RemoteCP enthält ein **Mods-Plugin**, mit dem Texturpakete (Skins) pro Spielumgebung auf dem Server forciert werden können. Spieler laden den jeweiligen Mod automatisch beim Betreten des Servers herunter.
### Wie funktionieren Mods?
In TrackMania Forever sind Mods ZIP-Archive mit alternativen Texturen für eine Spielumgebung (Stadium, Island, Bay, etc.). Der Ablauf:
1. Der Mod liegt als `.zip`-Datei auf einem **Webserver**, der für die Spieler erreichbar ist
2. Der Serveradmin forciert den Mod über den XML-RPC-Befehl `SetForcedMods` (pro Umgebung eine URL)
3. Wenn ein Spieler dem Server beitritt, lädt sein Client den Mod automatisch von der angegebenen URL herunter
4. Mods sind **flüchtig** – nach einem Serverneustart müssen sie erneut gesetzt werden (das Startup-Script übernimmt das automatisch, siehe unten)
### Vorkonfigurierte Skin-Bibliothek
Das Image wird mit einer vorkonfigurierten Skin-Bibliothek ausgeliefert, die über **50 Skins** für die Stadium-Umgebung enthält. Alle Skins werden von [assets.techniverse.net](https://assets.techniverse.net/tm/skins/) bereitgestellt und sind im RemoteCP-Mods-Plugin als Dropdown-Auswahl verfügbar.
Die Konfiguration befindet sich unter:
| Host-Pfad | Container-Pfad | Beschreibung |
|-----------|----------------|-------------|
| `./data/controlpanel/remotecp/plugins/Mods/settings.xml` | `/var/www/html/remotecp/plugins/Mods/settings.xml` | Mod-Katalog (URLs pro Umgebung) |
> **Hinweis:** Bei bestehenden Installationen (Volumes) wird die alte Standard-`settings.xml` (mit den Original-Beispiel-URLs von blacksunonline.com) beim nächsten Containerstart automatisch durch die neue Version mit den techniverse.net-Skins ersetzt.
### Mods über RemoteCP verwalten
1. RemoteCP öffnen: `http://<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
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.
## Fehlerbehebung
Falls RemoteCP nicht erreichbar ist oder Fehler anzeigt:
1. Prüfe, ob beide Container laufen: `docker ps`
2. MariaDB-Verbindung testen: `docker exec tmserver mysql -h mariadb -u remotecp -p remotecp -e "SELECT 1"`
3. PHP-Debug-Modus aktivieren: `PHP_DISPLAY_ERRORS=true` in der `.env`-Datei setzen
4. Container neu starten: `docker compose restart`
5. PHP-Logs prüfen: `docker exec tmserver cat /var/log/php_errors.log`
6. MariaDB-Logs prüfen: `docker logs tmserver-mariadb`
### Bekannte Hinweise
- RemoteCP ist ein älteres Tool (Version 4.0.3.5) und wurde für PHP 5.x entwickelt. Ab v1.4.0 läuft es dank automatischer Kompatibilitäts-Patches beim Containerstart unter PHP 8.4 (siehe README)
- Die Live-Funktionen (`remoteCP[Live]`) benötigen eine laufende Serververbindung
- **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
+112
View File
@@ -0,0 +1,112 @@
# Schnellstart
## Voraussetzungen
- Docker und Docker Compose müssen installiert sein
## 1. Umgebungsvariablen einrichten
Kopiere die Vorlage und passe die Werte an:
```bash
cp .env.example .env
```
Bearbeite die `.env`-Datei und setze mindestens die gewünschten Passwörter. Für den Internet-Modus müssen zusätzlich `SERVER_LOGIN` und `SERVER_VALIDATION_KEY` gesetzt werden.
> **⚠ Sicherheitshinweis:** Die `.env.example` enthält **vorgenerierte Beispiel-Passwörter**. Diese dienen nur als Platzhalter und sind öffentlich einsehbar! **Ändere unbedingt alle Passwörter**, bevor du den Server produktiv einsetzt.
> **Wichtig:** Die `.env`-Datei enthält sensible Daten (Passwörter, Keys) und wird über die `.gitignore` vom Einchecken ausgeschlossen.
## 2. Server starten
### Fertiges Docker Image verwenden (empfohlen)
Es steht ein fertiges Docker Image in der Container-Registry bereit – kein eigener Build nötig:
```
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/).
#### Mit Docker Compose
```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 .
```
Anschließend den Server starten:
```bash
docker compose up -d --build
```
### 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.
```bash
docker run -d \
--env-file .env \
-p 2350:2350/tcp \
-p 2350:2350/udp \
-p 3450:3450/tcp \
-p 80:80/tcp \
-v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver git.techniverse.net/scriptos/trackmania-server:latest
```
### LAN-Modus (docker run)
Setze in der `.env`-Datei `SERVER_MODE=lan` oder übergib es direkt:
```bash
docker run -d \
--env-file .env \
-e SERVER_MODE=lan \
-p 2350:2350/tcp \
-p 2350:2350/udp \
-p 3450:3450/tcp \
-p 80:80/tcp \
-v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver git.techniverse.net/scriptos/trackmania-server:latest
```
## 3. Verwaltungsoberflächen öffnen
| Tool | URL | Beschreibung |
|------|-----|-------------|
| AdminServ | `http://<host-ip>/` | Server-Verwaltungsoberfläche |
| RemoteCP | `http://<host-ip>/remotecp/` | Alternative Verwaltungsoberfläche |
Weitere Details unter [AdminServ](adminserv.md) und [RemoteCP](remotecp.md).
## Persistente Konfiguration
Alle Server- und AdminServ-Daten werden über Bind-Mounts persistent auf dem Host gespeichert:
| Host-Pfad | Container-Pfad | Beschreibung |
|-----------|----------------|-------------|
| `./data/gamedata` | `/opt/tmserver/GameData` | TM-Server-Daten (Config, Tracks, Skins, etc.) |
| `./data/controlpanel` | `/var/www/html` | AdminServ- und RemoteCP-Daten |
| `./data/xaseco` | `/opt/tmserver/xaseco` | XAseco-Konfiguration und Logs |
| `./data/mariadb` | `/var/lib/mysql` | MariaDB-Datenbankdateien |
Beim ersten Start werden die Verzeichnisse automatisch aus dem Image erzeugt und die Umgebungsvariablen aus der `.env`-Datei angewendet. Bei weiteren Starts bleibt alles erhalten.
Weitere Details unter [Konfiguration](konfiguration.md).
+60
View File
@@ -0,0 +1,60 @@
# Server-Modi
Der Trackmania-Server kann in zwei Modi betrieben werden.
## Internet-Dedicated (Standard)
Im Internet-Modus ist der Server über das Trackmania-Masterserver-Netzwerk erreichbar und in der öffentlichen Serverliste sichtbar.
### Voraussetzungen
- Ein Server-Account auf der [Trackmania Players-Seite](https://players.trackmaniaforever.com)
- Die Umgebungsvariablen `SERVER_LOGIN` und `SERVER_VALIDATION_KEY` müssen gesetzt sein
### Konfiguration
In der `.env`-Datei:
```bash
SERVER_MODE=internet
SERVER_LOGIN=dein_login
SERVER_LOGIN_PASSWORD=dein_passwort
SERVER_VALIDATION_KEY=dein_key
```
Wenn `SERVER_LOGIN` oder `SERVER_VALIDATION_KEY` nicht gesetzt sind, bricht der Server mit einer Fehlermeldung ab.
### Server-Account erstellen
1. [players.trackmaniaforever.com](https://players.trackmaniaforever.com) aufrufen
2. Einloggen oder einen neuen Account erstellen
3. Unter „Dedicated Server" einen neuen Server-Account anlegen
4. Login und Validation Key notieren und als Umgebungsvariablen setzen
## LAN-Dedicated
Im LAN-Modus ist der Server nur im lokalen Netzwerk erreichbar. Es werden keine Masterserver-Zugangsdaten benötigt.
### Konfiguration
In der `.env`-Datei:
```bash
SERVER_MODE=lan
```
Oder per Docker-Run:
```bash
docker run -d \
--env-file .env \
-e SERVER_MODE=lan \
-p 2350:2350/tcp \
-p 2350:2350/udp \
-p 3450:3450/tcp \
-p 80:80/tcp \
-v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver git.techniverse.net/scriptos/trackmania-server:latest
```
+253
View File
@@ -0,0 +1,253 @@
# Umgebungsvariablen
Alle Umgebungsvariablen werden über die `.env`-Datei konfiguriert, die von `docker-compose.yml` automatisch eingelesen wird. Alternativ können sie beim `docker run` über `--env-file .env` oder einzeln mit `-e` gesetzt werden.
Die Variablen werden beim **ersten Start** (leeres GameData-Volume) in die `dedicated_cfg.txt` eingetragen. Bei weiteren Starts bleiben manuelle Änderungen erhalten. Siehe [Konfiguration](konfiguration.md) für Details.
> **Sicherheitshinweis:** Sensible Werte (Passwörter, Keys) sind **nicht** im Docker-Image hinterlegt. Sie werden ausschließlich zur Laufzeit über die `.env`-Datei übergeben. Die `.env`-Datei ist über `.gitignore` und `.dockerignore` geschützt und wird weder ins Git noch in den Docker-Build-Context aufgenommen.
## Einrichtung
```bash
# Vorlage kopieren
cp .env.example .env
# Werte anpassen
nano .env
```
> **⚠ Sicherheitshinweis:** Die `.env.example` enthält **vorgenerierte Beispiel-Passwörter**. Diese dienen nur als Platzhalter und sind öffentlich einsehbar! **Ändere unbedingt alle Passwörter**, bevor du den Server produktiv einsetzt. Betroffen sind: `SERVER_SA_PASSWORD`, `SERVER_ADM_PASSWORD`, `SERVER_USER_PASSWORD`, `MARIADB_ROOT_PASSWORD`, `REMOTECP_DB_PASSWORD` und `XASECO_DB_PASSWORD`.
## Authentifizierung
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `SERVER_SA_PASSWORD` | SuperAdmin-Passwort | `SuperAdmin` |
| `SERVER_ADM_PASSWORD` | Admin-Passwort | `Admin` |
| `SERVER_USER_PASSWORD` | User-Passwort | `User` |
## Masterserver-Account
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `SERVER_LOGIN` | Masterserver-Login (nur Internet-Modus) | *(leer)* |
| `SERVER_LOGIN_PASSWORD` | Masterserver-Passwort | *(leer)* |
| `SERVER_VALIDATION_KEY` | Masterserver-Validierungsschlüssel | *(leer)* |
> **Wichtig:** Im Internet-Modus müssen `SERVER_LOGIN` und `SERVER_VALIDATION_KEY` gesetzt sein, andernfalls startet der Server nicht.
## Server-Optionen
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `SERVER_NAME` | Servername (im Spiel sichtbar) | `Trackmania Server` |
| `SERVER_DESC` | Serverbeschreibung | `Powered by tmserver-docker` |
| `SERVER_HIDE` | Server verstecken (`0` = sichtbar, `1` = versteckt, `2` = vor Nations versteckt) | `0` |
| `SERVER_MAX_PLAYERS` | Maximale Spieleranzahl | `32` |
| `SERVER_PASSWORD` | Passwort zum Beitreten (leer = offen) | *(leer)* |
| `SERVER_MAX_SPECTATORS` | Maximale Zuschaueranzahl | `32` |
| `SERVER_SPEC_PASSWORD` | Zuschauer-Passwort (leer = offen) | *(leer)* |
| `SERVER_LADDER_MODE` | Ladder-Modus (`inactive` oder `forced`) | `forced` |
| `SERVER_LADDER_LIMIT_MAX` | Oberes Ladder-Serverlimit (Punktegrenze) | `60000` |
## Netzwerk
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `SERVER_PORT` | Gameserver-Port | `2350` |
| `SERVER_P2P_PORT` | P2P-Port | `3450` |
| `SERVER_XMLRPC_PORT` | XML-RPC-Port (intern für AdminServ) | `5000` |
| `SERVER_UPLOAD_RATE` | Upload-Rate in Kbps | `512` |
| `SERVER_DOWNLOAD_RATE` | Download-Rate in Kbps | `8192` |
## Server-Modus & Steuerung
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `SERVER_MODE` | Server-Modus (`internet` oder `lan`) | `internet` |
| `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` |
| `SHUFFLE_MAPLIST` | Map-Reihenfolge beim Containerstart zufällig mischen (`true` = aktiviert, `false` = deaktiviert) | `false` |
### 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.
### Map-Shuffle
Mit `SHUFFLE_MAPLIST=true` wird die Reihenfolge aller Maps in der aktiven MatchSettings-Datei beim **jedem Containerstart** zufällig durchgemischt. So startet der Server jedes Mal mit einer anderen Map, statt immer bei Map #1 zu beginnen.
- Die `<challenge>`-Einträge in der MatchSettings-XML werden zufällig neu angeordnet
- Der `<startindex>` wird automatisch auf `0` gesetzt
- Die aktive MatchSettings-Datei wird dabei direkt überschrieben
- Die ersten 3 Maps der neuen Reihenfolge werden beim Start in der Konsole angezeigt
**Beispiel:**
```bash
# Map-Reihenfolge bei jedem Start mischen
SHUFFLE_MAPLIST=true
# Deaktiviert (Standard) – Reihenfolge bleibt wie in der Datei
SHUFFLE_MAPLIST=false
```
> **Hinweis:** Der Shuffle wird auf die MatchSettings-Datei angewendet, die durch `MATCHSETTINGS_FILE` bestimmt wird (entweder automatisch oder explizit). Die Änderung ist persistent – die Datei wird tatsächlich umgeschrieben. Bei jedem Neustart wird erneut gemischt.
## 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.
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `REMOTECP_DB_HOST` | Hostname des Datenbankservers | `mariadb` |
| `REMOTECP_DB_NAME` | Name der RemoteCP-Datenbank | `remotecp` |
| `REMOTECP_DB_USER` | Datenbank-Benutzername | `remotecp` |
| `REMOTECP_DB_PASSWORD` | Datenbank-Passwort | *(muss gesetzt werden)* |
> **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
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `MARIADB_ROOT_PASSWORD` | Root-Passwort für den MariaDB-Server | *(muss gesetzt werden)* |
> **Wichtig:** `MARIADB_ROOT_PASSWORD` muss gesetzt sein, damit der MariaDB-Container startet. Die Datenbank und der Benutzer für RemoteCP werden automatisch aus `REMOTECP_DB_NAME`, `REMOTECP_DB_USER` und `REMOTECP_DB_PASSWORD` erstellt.
> **Hinweis:** Der MariaDB-Container kann mehrere Datenbanken beherbergen. Zusätzliche Datenbanken und Benutzer können über den Root-Zugang erstellt werden.
## XAseco
XAseco ist ein Server-Controller für Rekorde, Karma, Jukebox und mehr. Siehe [XAseco](xaseco.md) für Details.
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `XASECO_ENABLED` | XAseco aktivieren/deaktivieren | `true` |
| `XASECO_MASTERADMIN_LOGIN` | Dein Spieler-Login (MasterAdmin) | *(muss gesetzt werden)* |
| `XASECO_DB_HOST` | Hostname des Datenbankservers | `mariadb` |
| `XASECO_DB_NAME` | Name der XAseco-Datenbank | `xaseco` |
| `XASECO_DB_USER` | Datenbank-Benutzername | `xaseco` |
| `XASECO_DB_PASSWORD` | Datenbank-Passwort | *(muss gesetzt werden)* |
| `XASECO_DEDIMANIA_NATION` | Dedimania-Nation (IOC-Code) | `DEU` |
| `XASECO_HEALTHCHECK` | Automatische Überwachung und Neustart von XAseco bei Absturz/Verbindungsverlust | `true` |
| `XASECO_HEALTHCHECK_INTERVAL` | Prüfintervall des Healthchecks in Sekunden | `60` |
> **Hinweis:** Die Server-Zugangsdaten (`SERVER_SA_PASSWORD`, `SERVER_XMLRPC_PORT`) und Dedimania-Daten (`SERVER_LOGIN`, `SERVER_LOGIN_PASSWORD`) werden automatisch aus der bestehenden Konfiguration übernommen.
## IP-Watcher
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `IP_WATCHER_INTERVAL` | Prüfintervall in Sekunden, in dem die öffentliche IP geprüft wird | `300` |
> **Hinweis:** Der IP-Watcher verwendet den Docker-Socket (`/var/run/docker.sock`), um `tmserver` bei einer IP-Änderung automatisch neu zu starten. Weitere Details unter [IP-Watcher](ip-watcher.md).
## Debugging
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `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.
>
> 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.
## Beispiel
### Docker Compose (empfohlen)
Passe die Werte in der `.env`-Datei an und starte mit:
```bash
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
```bash
docker run -d \
--env-file .env \
-p 2350:2350/tcp \
-p 2350:2350/udp \
-p 3450:3450/tcp \
-p 80:80/tcp \
-v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver git.techniverse.net/scriptos/trackmania-server:latest
```
Einzelne Werte können zusätzlich überschrieben werden:
```bash
docker run -d \
--env-file .env \
-e SERVER_NAME="Mein Server" \
-e SERVER_MAX_PLAYERS=64 \
-p 2350:2350/tcp \
-p 2350:2350/udp \
-p 3450:3450/tcp \
-p 80:80/tcp \
-v ./data/gamedata:/opt/tmserver/GameData \
-v ./data/controlpanel:/var/www/html \
-v ./data/xaseco:/opt/tmserver/xaseco \
--name tmserver git.techniverse.net/scriptos/trackmania-server:latest
```
+105
View File
@@ -0,0 +1,105 @@
# Update-Anleitung
Diese Anleitung beschreibt, wie du eine **bestehende Installation** auf den neuesten Stand bringst – ohne Daten zu verlieren und ohne alles neu aufsetzen zu müssen.
> **Hinweis:** Alle persistenten Daten (Konfiguration, Tracks, Datenbanken, Logs) liegen im Ordner `./data/` und werden bei einem Update nicht berührt.
---
## 1. Repository aktualisieren
Wechsle in den Projektordner und lade die neuesten Änderungen:
```bash
git pull
```
Damit werden aktualisierte Konfigurationsdateien, Skripte und Dokumentation aus dem Repository übernommen.
---
## 2. Neue Umgebungsvariablen prüfen
Mit neuen Versionen können neue Variablen in der `.env.example` hinzugekommen sein. Deine persönliche `.env`-Datei wird dabei **nicht überschrieben** – du musst neue Variablen manuell nachtragen.
Vergleiche `.env.example` mit deiner `.env`, um fehlende Einträge zu finden:
```bash
diff .env.example .env
```
Zeilen, die in `.env.example` vorhanden sind, aber nicht in deiner `.env`, erscheinen mit `<` am Anfang. Diese solltest du in deine `.env` übernehmen und die Werte anpassen.
> **Tipp:** Neue Variablen haben in der Regel sinnvolle Standardwerte, die du oft einfach übernehmen kannst. Achte nur auf sicherheitsrelevante Werte wie Passwörter.
---
## 3. Docker Image aktualisieren
Lade das neueste Image aus der Registry:
```bash
docker compose pull
```
---
## 4. Container neu starten
Starte die Container mit dem neuen Image neu. Docker Compose erkennt automatisch, ob ein Rebuild nötig ist:
```bash
docker compose up -d
```
> **Hinweis:** Wenn sich die `docker-compose.yml` geändert hat (z.B. neue Services oder geänderte Konfiguration), werden betroffene Container automatisch neu erstellt. Deine Daten in `./data/` bleiben dabei vollständig erhalten.
---
## Zusammenfassung
```bash
# Im Projektordner ausführen:
git pull
docker compose pull
docker compose up -d
```
Das war's – der Server läuft jetzt auf dem neuesten Stand.
---
## Was passiert mit meinen Daten?
| Ordner | Inhalt | Beim Update |
|--------|--------|-------------|
| `./data/gamedata/` | TM-Server-Daten, Konfiguration, Tracks | Bleibt erhalten |
| `./data/controlpanel/` | AdminServ- und RemoteCP-Daten | Bleibt erhalten |
| `./data/xaseco/` | XAseco-Konfiguration und Logs | Bleibt erhalten |
| `./data/mariadb/` | Datenbankdateien | Bleibt erhalten |
| `.env` | Deine Umgebungsvariablen | Wird nicht überschrieben |
---
## Häufige Situationen
### Neue Umgebungsvariable hat keinen Effekt
Einige Variablen (z.B. `SERVER_SA_PASSWORD`) werden nur beim **ersten Start** in die `dedicated_cfg.txt` geschrieben. Falls du eine neue Variable nachträglich setzen möchtest, kannst du das erzwingen:
```bash
# In der .env setzen:
FORCE_CONFIG_UPDATE=true
```
Dann Container neu starten. Danach unbedingt wieder auf `false` setzen, damit manuelle Änderungen erhalten bleiben. Weitere Details unter [Konfiguration](konfiguration.md).
### Welches Image-Tag wird verwendet?
In der `docker-compose.yml` ist das Image-Tag angegeben (z.B. `1.3.2` oder `latest`). Du kannst auf `latest` umstellen, um immer automatisch das neueste Image zu bekommen:
```yaml
image: git.techniverse.net/scriptos/trackmania-server:latest
```
Alle verfügbaren Tags findest du in der [Container-Registry](https://git.techniverse.net/scriptos/-/packages/container/trackmania-server/).
+388
View File
@@ -0,0 +1,388 @@
# XAseco
XAseco ist ein Server-Controller für TrackMania Nations Forever. Er verwaltet lokale Rekorde, Dedimania-Weltrekorde, Karma/Votes, eine Track-Jukebox und vieles mehr direkt im Spielchat.
Im Container wird die modifizierte Version **XAseco 1.16** verwendet, die für PHP 7.x angepasst wurde (Patches von Bueddel, Xymph, undef.de und reaby). Ab v1.4.0 wird sie beim Containerstart zusätzlich automatisch für PHP 8.4 gepatcht (siehe README).
## Funktionsweise
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
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.
### Erforderliche Variablen
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `XASECO_MASTERADMIN_LOGIN` | Dein Spieler-Login (MasterAdmin mit allen Rechten) | *(muss gesetzt werden)* |
| `XASECO_DB_PASSWORD` | Passwort für die XAseco-Datenbank | *(muss gesetzt werden)* |
### Optionale Variablen
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `XASECO_ENABLED` | XAseco aktivieren/deaktivieren | `true` |
| `XASECO_DB_HOST` | Hostname des Datenbankservers | `mariadb` |
| `XASECO_DB_NAME` | Name der XAseco-Datenbank | `xaseco` |
| `XASECO_DB_USER` | Datenbank-Benutzername | `xaseco` |
| `XASECO_DEDIMANIA_NATION` | Dedimania-Nation ([IOC-Code](https://en.wikipedia.org/wiki/List_of_IOC_country_codes), z.B. `DEU`, `AUT`, `CHE`) | `DEU` |
| `XASECO_HEALTHCHECK` | Automatische Überwachung und Neustart bei Absturz | `true` |
| `XASECO_HEALTHCHECK_INTERVAL` | Prüfintervall des Healthchecks in Sekunden | `60` |
### Automatisch übernommene Variablen
Diese Werte werden aus der bestehenden Server-Konfiguration übernommen und müssen **nicht** separat gesetzt werden:
| Quelle | Verwendung in XAseco |
|--------|---------------------|
| `SERVER_SA_PASSWORD` | SuperAdmin-Passwort für die XML-RPC-Verbindung |
| `SERVER_XMLRPC_PORT` | XML-RPC-Port für die Verbindung zum TM-Server |
| `SERVER_LOGIN` | Dedimania Server-Login |
| `SERVER_LOGIN_PASSWORD` | Dedimania Server-Passwort |
## Datenbank
XAseco verwendet eine **eigene Datenbank** im selben MariaDB-Container. Die Datenbank und der Benutzer werden beim ersten Start des MariaDB-Containers automatisch erstellt (über ein Init-Script).
Das Datenbankschema (drei Tabellen-Dateien) wird beim ersten Start des tmserver-Containers automatisch importiert.
### Manueller Zugriff
Falls nötig, kann die XAseco-Datenbank über den MariaDB-Root-Zugang administriert werden:
```bash
docker exec -it tmserver-mariadb mysql -u root -p
```
### Nachträgliche Installation (bestehende MariaDB)
Das Init-Script (`assets/db/init-xaseco-db.sh`) wird von MariaDB nur beim **allerersten Start** (leeres `data/mariadb/`-Volume) ausgeführt. Wenn der MariaDB-Container bereits Daten enthält (z.B. weil er zuvor ohne XAseco lief), muss die Datenbank **einmalig manuell** erstellt werden:
```bash
docker exec -it tmserver-mariadb mysql -u root -p -e "
CREATE DATABASE IF NOT EXISTS xaseco CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'xaseco'@'%' IDENTIFIED BY 'DEIN_XASECO_DB_PASSWORT';
GRANT ALL PRIVILEGES ON xaseco.* TO 'xaseco'@'%';
FLUSH PRIVILEGES;"
```
Ersetze `DEIN_XASECO_DB_PASSWORT` durch den Wert von `XASECO_DB_PASSWORD` aus der `.env`-Datei. Das Tabellen-Schema wird anschließend beim nächsten Container-Start automatisch importiert, sofern das XAseco-Volume noch leer ist.
## Admin-System
XAseco kennt drei Berechtigungsstufen:
| Stufe | Beschreibung |
|-------|-------------|
| **MasterAdmin** | Volle Kontrolle (wird über `XASECO_MASTERADMIN_LOGIN` gesetzt) |
| **Admin** | Erweiterte Befehle (ebenfalls über `XASECO_MASTERADMIN_LOGIN` gesetzt) |
| **Operator** | Eingeschränkte Moderationsbefehle |
Weitere Admins/Operatoren können nach dem ersten Start manuell in `data/xaseco/adminops.xml` eingetragen werden.
## Persistente Daten
Alle XAseco-Daten werden im Volume `./data/xaseco/` gespeichert:
- Konfigurationsdateien (`config.xml`, `localdatabase.xml`, `dedimania.xml`, ...)
- Log-Datei (`aseco.log`)
- Admin-/Operator-Listen (`adminops.xml`)
## Deaktivieren
XAseco kann über die `.env`-Datei deaktiviert werden:
```env
XASECO_ENABLED=false
```
Der TrackMania-Server läuft dann ohne Server-Controller.
## Healthcheck / Watchdog
XAseco wird automatisch durch einen Watchdog-Prozess überwacht. Dieser erkennt Abstürze und verlorene Verbindungen (z.B. wenn das Overlay im Spiel verschwindet) und startet XAseco selbstständig neu.
### Funktionsweise
Der Watchdog prüft regelmäßig (Standard: alle 60 Sekunden):
1. **PID-Check:** Läuft der XAseco-PHP-Prozess noch?
2. **Log-Check:** Enthält das XAseco-Log fatale Fehler oder Verbindungsabbrüche?
3. **XMLRPC-Check:** Ist der TM-Server noch erreichbar?
Bei erkannten Problemen wird XAseco automatisch beendet und neu gestartet. Crash-Logs werden zur Fehleranalyse unter `data/xaseco/aseco_crash_<TIMESTAMP>.log` gesichert (max. 5 Dateien).
### Konfiguration
| Variable | Beschreibung | Standard |
|----------|-------------|----------|
| `XASECO_HEALTHCHECK` | Watchdog aktivieren/deaktivieren | `true` |
| `XASECO_HEALTHCHECK_INTERVAL` | Prüfintervall in Sekunden | `60` |
```env
# Healthcheck deaktivieren
XASECO_HEALTHCHECK=false
# Prüfintervall auf 30 Sekunden verkürzen
XASECO_HEALTHCHECK_INTERVAL=30
```
> **Hinweis:** Der Watchdog ist standardmäßig aktiviert und erfordert keine zusätzliche Konfiguration. Bei bestehenden Installationen wird er nach dem nächsten Image-Update automatisch aktiv.
## Logs
Die XAseco-Logdatei befindet sich unter:
```
./data/xaseco/aseco.log
```
Bei Problemen ist dies die erste Anlaufstelle für die Fehlersuche.
> **Hinweis:** Die XAseco-Logdatei wird automatisch per logrotate rotiert (max. 10 MB pro Datei, 5 rotierte Dateien). Alte Logs werden komprimiert als `aseco.log.1.gz`, `aseco.log.2.gz` usw. aufbewahrt. Siehe [Konfiguration – Log-Rotation](konfiguration.md#log-rotation).
## Chat-Befehle
Nachfolgend eine Übersicht der wichtigsten Befehle, die im Spielchat verfügbar sind. Eine vollständige Liste aller Befehle findest du in der offiziellen Dokumentation unter: https://docs.xaseco.org/commands.php
### Hilfe & Info
| Befehl | Beschreibung |
|--------|-------------|
| `/help` | Zeigt alle verfügbaren Befehle |
| `/helpall` | Zeigt ausführliche Hilfe zu allen Befehlen |
| `/xaseco` | Zeigt Infos über die XAseco-Version |
| `/server` | Zeigt Infos über den Server |
| `/plugins` | Zeigt Liste der aktiven Plugins |
| `/time` | Zeigt aktuelle Serverzeit und Datum |
### Rekorde & Statistiken
| Befehl | Beschreibung |
|--------|-------------|
| `/recs` | Zeigt alle lokalen Rekorde auf der aktuellen Strecke |
| `/recs pb` | Zeigt deine persönliche Bestzeit |
| `/recs new` | Zeigt neu gefahrene Rekorde |
| `/recs live` | Zeigt Rekorde der Online-Spieler |
| `/pb` | Zeigt deine persönliche Bestzeit auf der aktuellen Strecke |
| `/dedirecs` | Zeigt Dedimania-Rekorde auf der aktuellen Strecke |
| `/dedipb` | Zeigt deine persönliche Dedimania-Bestzeit |
| `/dedistats` | Zeigt Dedimania-Streckenstatistiken |
| `/best` | Zeigt deine besten Rekorde |
| `/worst` | Zeigt deine schlechtesten Rekorde |
| `/summary` | Zeigt eine Zusammenfassung aller deiner Rekorde |
| `/stats` | Zeigt Statistiken des aktuellen Spielers |
| `/wins` | Zeigt Siege des aktuellen Spielers |
### Rankings & Toplisten
| Befehl | Beschreibung |
|--------|-------------|
| `/rank` | Zeigt deinen aktuellen Serverrang |
| `/nextrank` | Zeigt den nächst besser platzierten Spieler |
| `/top10` | Zeigt die 10 bestplatzierten Spieler |
| `/top100` | Zeigt die 100 bestplatzierten Spieler |
| `/topwins` | Zeigt die 100 siegreichsten Spieler |
| `/toprecs` | Zeigt Top 100 der Rekord-Halter |
| `/topsums` | Zeigt Top 100 der Top-3-Rekord-Halter |
| `/active` | Zeigt die 100 aktivsten Spieler |
| `/topclans` | Zeigt die 10 bestplatzierten Clans |
### Strecken & Jukebox
| Befehl | Beschreibung |
|--------|-------------|
| `/track` | Zeigt Infos über die aktuelle Strecke |
| `/nextmap` | Zeigt den Namen der nächsten Strecke |
| `/playtime` | Zeigt, wie lange die aktuelle Strecke läuft |
| `/list` | Listet Strecken auf dem Server (siehe `/list help`) |
| `/list nofinish` | Strecken, auf denen du keinen Rang hast |
| `/list newest` | Die neuesten Strecken |
| `/list <name>` | Suche nach Strecken- oder Autorennamen |
| `/jukebox` | Track-Jukebox (siehe `/jukebox help`) |
| `/jukebox list` | Zeigt kommende Strecken |
| `/jukebox <#>` | Fügt Strecke Nr. `<#>` aus `/list` hinzu |
| `/jukebox drop` | Entfernt deine hinzugefügte Strecke |
| `/add <ID>` | Fügt eine Strecke direkt von TMX hinzu |
| `/history` | Zeigt die 10 zuletzt gespielten Strecken |
### Karma & Voting
| Befehl | Beschreibung |
|--------|-------------|
| `/karma` | Zeigt Karma der aktuellen Strecke |
| `/++` | Positive Bewertung für die aktuelle Strecke |
| `/--` | Negative Bewertung für die aktuelle Strecke |
| `/helpvote` | Zeigt Infos zum Chat-Voting-System |
| `/endround` | Startet Vote zum Beenden der aktuellen Runde |
| `/replay` | Startet Vote zum Wiederholen der Strecke |
| `/skip` | Startet Vote zum Überspringen der Strecke |
| `/kick` | Startet Vote zum Kicken eines Spielers |
| `/y` | Stimmt mit Ja bei einem laufenden Vote |
| `/cancel` | Bricht deinen aktuellen Vote ab |
### Kommunikation
| Befehl | Beschreibung |
|--------|-------------|
| `/pm <login> <msg>` | Sendet eine private Nachricht |
| `/pmlog` | Zeigt Verlauf deiner privaten Nachrichten |
| `/chatlog` | Zeigt Verlauf der letzten Chat-Nachrichten |
| `/me` | Drückt eine Aktion/Emotion aus |
| `/hi` | Sendet eine Hallo-Nachricht an alle |
| `/bye` | Sendet eine Tschüss-Nachricht an alle |
| `/gg` | Sendet "Good Game" an alle |
| `/n1` | Sendet "Nice One" an alle |
| `/brb` | Sendet "Be Right Back" an alle |
| `/afk` | Sendet "Away From Keyboard" an alle |
### Spieleroptionen
| Befehl | Beschreibung |
|--------|-------------|
| `/settings` | Zeigt deine persönlichen Einstellungen |
| `/cps` | Setzt Checkpoint-Tracking für lokale Rekorde |
| `/dedicps` | Setzt Checkpoint-Tracking für Dedimania-Rekorde |
| `/mute <login>` | Chat eines Spielers stummschalten |
| `/unmute <login>` | Stummschaltung aufheben |
| `/mutelist` | Zeigt Liste der stummgeschalteten Spieler |
| `/players` | Zeigt aktuelle Spieler-Liste (Nicks/Logins) |
| `/ranks` | Zeigt Liste der Online-Ränge |
| `/bootme` | Kickt dich selbst vom Server |
### Admin-Befehle (`/admin`)
Diese Befehle sind nur für Admins und MasterAdmins verfügbar.
| Befehl | Beschreibung |
|--------|-------------|
| `/admin help` | Zeigt alle Admin-Befehle |
| `/admin helpall` | Zeigt ausführliche Hilfe zu allen Admin-Befehlen |
**Server-Einstellungen:**
| Befehl | Beschreibung |
|--------|-------------|
| `/admin setservername <name>` | Ändert den Servernamen |
| `/admin setpwd <pwd>` | Ändert das Spieler-Passwort |
| `/admin setspecpwd <pwd>` | Ändert das Zuschauer-Passwort |
| `/admin setmaxplayers <#>` | Setzt maximale Spieleranzahl |
| `/admin setmaxspecs <#>` | Setzt maximale Zuschauerzahl |
| `/admin setgamemode <mode>` | Setzt Spielmodus (ta/rounds/team/laps/stunts/cup) |
**Strecken-Verwaltung:**
| Befehl | Beschreibung |
|--------|-------------|
| `/admin nextmap` | Erzwingt nächste Strecke |
| `/admin restartmap` | Startet aktuelle Strecke neu |
| `/admin replaymap` | Wiederholt aktuelle Strecke (via Jukebox) |
| `/admin endround` | Erzwingt Ende der aktuellen Runde |
| `/admin add <ID>` | Fügt Strecke von TMX hinzu |
| `/admin addlocal <datei>` | Fügt lokale Strecke hinzu |
| `/admin remove <#>` | Entfernt Strecke aus der Rotation |
| `/admin erasethis` | Entfernt aktuelle Strecke und löscht Datei |
| `/admin shuffle` | Mischt die Streckenliste zufällig |
| `/admin writetracklist` | Speichert aktuelle Streckenliste |
| `/admin readtracklist` | Lädt Streckenliste aus Datei |
**Jukebox:**
| Befehl | Beschreibung |
|--------|-------------|
| `/admin dropjukebox <#>` | Entfernt eine Strecke aus der Jukebox |
| `/admin clearjukebox` | Leert die gesamte Jukebox |
| `/admin pass` | Genehmigt einen laufenden Vote |
| `/admin cancel` | Bricht einen laufenden Vote ab |
**Spieler-Moderation:**
| Befehl | Beschreibung |
|--------|-------------|
| `/admin warn <login>` | Sendet eine Warnung an einen Spieler |
| `/admin kick <login>` | Kickt einen Spieler vom Server |
| `/admin kickghost <login>` | Kickt einen Ghost-Spieler |
| `/admin ban <login>` | Bannt einen Spieler |
| `/admin unban <login>` | Entbannt einen Spieler |
| `/admin black <login>` | Setzt einen Spieler auf die Blacklist |
| `/admin unblack <login>` | Entfernt einen Spieler von der Blacklist |
| `/admin mute <login>` | Schaltet einen Spieler global stumm |
| `/admin unmute <login>` | Hebt globale Stummschaltung auf |
| `/admin forcespec <login>` | Erzwingt Zuschauer-Modus |
| `/admin forceteam <login>` | Erzwingt Team-Zuordnung (Blue/Red) |
**Admin-Verwaltung:**
| Befehl | Beschreibung |
|--------|-------------|
| `/admin addadmin <login>` | Fügt einen neuen Admin hinzu |
| `/admin removeadmin <login>` | Entfernt einen Admin |
| `/admin addop <login>` | Fügt einen neuen Operator hinzu |
| `/admin removeop <login>` | Entfernt einen Operator |
| `/admin listmasters` | Zeigt MasterAdmin-Liste |
| `/admin listadmins` | Zeigt Admin-Liste |
| `/admin listops` | Zeigt Operator-Liste |
**Rekorde & System:**
| Befehl | Beschreibung |
|--------|-------------|
| `/admin delrec <#>` | Löscht einen bestimmten Rekord auf der aktuellen Strecke |
| `/admin wall <msg>` | Zeigt Popup-Nachricht für alle Spieler |
| `/admin pm <msg>` | Sendet private Nachricht an alle Admins |
| `/admin server` | Zeigt detaillierte Server-Einstellungen |
| `/admin shutdown` | Fährt XAseco herunter |
| `/admin shutdownall` | Fährt Server und XAseco herunter |
> **Tipp:** Eine vollständige Referenz aller Befehle (inkl. `/jfreu`-Befehle für erweiterte Moderation) findest du unter: https://docs.xaseco.org/commands.php