diff --git a/Dockerfile b/Dockerfile
index dbc418f..b503965 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -15,6 +15,7 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
php-mysql \
php-curl \
default-mysql-client \
+ logrotate \
&& rm -rf /var/lib/apt/lists/*
# Apache mod_rewrite aktivieren und AllowOverride fuer .htaccess (RemoteCP-Sicherheit)
@@ -47,6 +48,10 @@ COPY assets/bin/RunTrackmaniaServer.sh /opt/tmserver/
RUN sed -i 's/\r$//' /opt/tmserver/RunTrackmaniaServer.sh \
&& chmod +x /opt/tmserver/RunTrackmaniaServer.sh
+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 \
@@ -144,6 +149,11 @@ 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).
@@ -169,6 +179,7 @@ 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=""
@@ -191,6 +202,8 @@ 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
@@ -206,4 +219,8 @@ EXPOSE 2350/udp
EXPOSE 3450/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"]
diff --git a/assets/bin/RunTrackmaniaServer.sh b/assets/bin/RunTrackmaniaServer.sh
index c186b21..29094fe 100644
--- a/assets/bin/RunTrackmaniaServer.sh
+++ b/assets/bin/RunTrackmaniaServer.sh
@@ -368,10 +368,12 @@ fi
# ============================================================
# XAseco: TeamSpeak3-Plugin Gateway aktualisieren (fuer bestehende Volumes)
# ============================================================
-# Das Original-TS3-Gateway ist nicht mehr verfuegbar. Die eigene
-# teamspeak3.xml mit dem Ersatz-Gateway wird in das Volume kopiert,
-# falls sie fehlt oder noch das alte (nicht mehr erreichbare) Gateway
-# referenziert. Gleichzeitig wird das Plugin reaktiviert, falls es
+# Das Original-TS3-Gateway ist nicht mehr verfuegbar. Falls die
+# teamspeak3.xml fehlt, wird sie aus dem Template kopiert. Falls sie
+# bereits existiert (= Nutzer hat eigene TS3-Daten konfiguriert),
+# werden NUR die Gateway-URLs (helperURL/logoURL) gezielt aktualisiert.
+# Alle anderen Einstellungen (Server, Port, Channel etc.) bleiben
+# erhalten. Gleichzeitig wird das Plugin reaktiviert, falls es
# in einer frueheren Version auskommentiert wurde.
# ============================================================
XASECO_DIR_TS3="/opt/tmserver/xaseco"
@@ -379,16 +381,30 @@ TS3_XML="$XASECO_DIR_TS3/teamspeak3.xml"
TS3_DEFAULT="/opt/tmserver/default-xaseco/teamspeak3.xml"
TS3_PLUGINS_XML="$XASECO_DIR_TS3/plugins.xml"
-# teamspeak3.xml aktualisieren: Kopieren wenn fehlend oder veraltet
+# teamspeak3.xml aktualisieren: Kopieren wenn fehlend, Gateway-URLs gezielt patchen
+# WICHTIG: Bei bestehender Datei wird NICHT die gesamte Datei ueberschrieben,
+# damit benutzerdefinierte Einstellungen (Server, Port, Channel) erhalten bleiben.
+# Nur die Gateway-URLs (helperURL/logoURL) werden aktualisiert, falls sie noch
+# auf ein altes, nicht mehr erreichbares Gateway zeigen.
if [ -f "$TS3_DEFAULT" ]; then
if [ ! -f "$TS3_XML" ]; then
echo "==> TeamSpeak3-Gateway: teamspeak3.xml fehlt, kopiere aus Template..."
cp "$TS3_DEFAULT" "$TS3_XML"
echo " teamspeak3.xml erfolgreich kopiert."
- elif ! diff -q "$TS3_DEFAULT" "$TS3_XML" > /dev/null 2>&1; then
- echo "==> TeamSpeak3-Gateway: teamspeak3.xml wird aktualisiert..."
- cp "$TS3_DEFAULT" "$TS3_XML"
- echo " teamspeak3.xml erfolgreich aktualisiert."
+ else
+ # Gateway-URLs aus dem Template auslesen
+ NEW_HELPER_URL=$(grep -oP '(?<=).*?(?=)' "$TS3_DEFAULT")
+ NEW_LOGO_URL=$(grep -oP '(?<=).*?(?=)' "$TS3_DEFAULT")
+ # Aktuelle URLs aus der bestehenden Datei auslesen
+ CUR_HELPER_URL=$(grep -oP '(?<=).*?(?=)' "$TS3_XML")
+ CUR_LOGO_URL=$(grep -oP '(?<=).*?(?=)' "$TS3_XML")
+ # Nur patchen, wenn sich die Gateway-URLs unterscheiden
+ if [ "$CUR_HELPER_URL" != "$NEW_HELPER_URL" ] || [ "$CUR_LOGO_URL" != "$NEW_LOGO_URL" ]; then
+ echo "==> TeamSpeak3-Gateway: Aktualisiere Gateway-URLs (Server-Einstellungen bleiben erhalten)..."
+ [ -n "$NEW_HELPER_URL" ] && sed -i "s|.*|${NEW_HELPER_URL}|" "$TS3_XML"
+ [ -n "$NEW_LOGO_URL" ] && sed -i "s|.*|${NEW_LOGO_URL}|" "$TS3_XML"
+ echo " Gateway-URLs erfolgreich aktualisiert."
+ fi
fi
fi
@@ -727,6 +743,83 @@ fi
echo " Aktive MatchSettings: ${GAME_SETTINGS_PATH}"
+# ============================================================
+# MatchSettings: Map-Reihenfolge zufaellig mischen
+# ============================================================
+# Ueber die Umgebungsvariable SHUFFLE_MAPLIST kann gesteuert werden,
+# ob die Reihenfolge der Maps in der aktiven MatchSettings-Datei
+# beim Containerstart zufaellig durchgemischt wird:
+# - "false" (Standard): Reihenfolge bleibt unveraendert.
+# - "true": Alle -Eintraege werden zufaellig gemischt
+# und wird auf 0 gesetzt.
+# Die originale Datei wird dabei ueberschrieben.
+# ============================================================
+
+SHUFFLE_MAPLIST="${SHUFFLE_MAPLIST:-false}"
+
+if [ "$SHUFFLE_MAPLIST" = "true" ]; then
+ # Vollstaendigen Pfad zur aktiven MatchSettings-Datei bestimmen
+ ACTIVE_MS_FILE="$GAMEDATA_DIR/Tracks/${GAME_SETTINGS_PATH}"
+ if [ -f "$ACTIVE_MS_FILE" ]; then
+ echo "==> Map-Shuffle: Mische Maps in ${GAME_SETTINGS_PATH}..."
+ php -r '
+ $file = $argv[1];
+ $xml = file_get_contents($file);
+ if ($xml === false) {
+ echo " FEHLER: Konnte MatchSettings-Datei nicht lesen.\n";
+ exit(1);
+ }
+
+ // Alle ...-Bloecke extrahieren
+ if (!preg_match_all("/.*?<\/challenge>/s", $xml, $matches)) {
+ echo " Keine -Eintraege gefunden. Shuffle uebersprungen.\n";
+ exit(0);
+ }
+
+ $challenges = $matches[0];
+ $count = count($challenges);
+ echo " $count Maps gefunden. Mische Reihenfolge...\n";
+
+ // Zufaellig mischen
+ shuffle($challenges);
+
+ // Alle bestehenden -Bloecke aus dem XML entfernen
+ $xmlClean = preg_replace("/(\s*.*?<\/challenge>)+/s", "", $xml, 1);
+
+ // Gemischte Challenges vor wieder einfuegen
+ $challengeBlock = "";
+ foreach ($challenges as $ch) {
+ $challengeBlock .= "\t" . $ch . "\n";
+ }
+ $xmlNew = str_replace("", $challengeBlock . "", $xmlClean);
+
+ // auf 0 setzen (damit ab der ersten gemischten Map gestartet wird)
+ $xmlNew = preg_replace("/[^<]*<\/startindex>/", "0", $xmlNew);
+
+ // Datei zurueckschreiben
+ if (file_put_contents($file, $xmlNew) === false) {
+ echo " FEHLER: Konnte MatchSettings-Datei nicht schreiben.\n";
+ exit(1);
+ }
+
+ // Erste 3 Maps anzeigen
+ echo " Reihenfolge erfolgreich gemischt.\n";
+ echo " Neue Startreihenfolge (erste 3 Maps):\n";
+ for ($i = 0; $i < min(3, $count); $i++) {
+ if (preg_match("/(.*?)<\/file>/", $challenges[$i], $m)) {
+ echo " " . ($i + 1) . ". " . $m[1] . "\n";
+ }
+ }
+ if ($count > 3) echo " ... und " . ($count - 3) . " weitere Maps\n";
+ ' "$ACTIVE_MS_FILE"
+ else
+ echo "==> Map-Shuffle: MatchSettings-Datei nicht gefunden: ${ACTIVE_MS_FILE}"
+ echo " Shuffle uebersprungen."
+ fi
+else
+ echo "==> Map-Shuffle: Deaktiviert (SHUFFLE_MAPLIST=false)."
+fi
+
# Bestimme Server-Modus (Standard: internet)
SERVER_MODE="${SERVER_MODE:-internet}"
@@ -807,10 +900,22 @@ if [ "$XMLRPC_READY" = "true" ]; then
cd /opt/tmserver/xaseco
php aseco.php TMN >aseco.log 2>&1 &
XASECO_PID=$!
+ echo "$XASECO_PID" > /tmp/xaseco.pid
echo " XAseco gestartet (PID: ${XASECO_PID})"
cd /opt/tmserver
+
+ # XAseco Healthcheck / Watchdog starten
+ XASECO_HEALTHCHECK="${XASECO_HEALTHCHECK:-true}"
+ if [ "$XASECO_HEALTHCHECK" = "true" ] && [ -f "/opt/tmserver/XAsecoHealthcheck.sh" ]; then
+ echo "==> Starte XAseco-Healthcheck (Watchdog)..."
+ /opt/tmserver/XAsecoHealthcheck.sh &
+ HEALTHCHECK_PID=$!
+ echo " XAseco-Healthcheck gestartet (PID: ${HEALTHCHECK_PID})"
+ elif [ "$XASECO_HEALTHCHECK" != "true" ]; then
+ echo "==> XAseco-Healthcheck ist deaktiviert (XASECO_HEALTHCHECK=${XASECO_HEALTHCHECK})."
+ fi
elif [ "${XASECO_ENABLED:-true}" != "true" ]; then
- echo "==> XAseco ist deaktiviert (XASECO_ENABLED=${XASECO_ENABLED})."
+ echo "==> XAseco ist deaktiviert (XASECO_ENABLED=${XASECO_ENABLED})."
fi
else
echo " WARNUNG: XMLRPC nicht erreichbar - XAseco und Forced Mods wurden NICHT gestartet."
@@ -996,5 +1101,293 @@ elif [ "$XMLRPC_READY" = "true" ]; then
echo "==> Forced Mods: Keine FORCE_MOD_*-Variablen gesetzt. Ueberspringe."
fi
+# ============================================================
+# Log-Rotation: Hintergrundprozess starten
+# ============================================================
+# logrotate wird stuendlich ausgefuehrt, um Apache-, PHP- und
+# XAseco-Logs groessenbasiert zu rotieren (max. 10 MB pro Datei,
+# 5 rotierte Dateien). Da im Container kein cron laeuft, wird
+# ein einfacher Background-Loop verwendet.
+# ============================================================
+echo "==> Starte Log-Rotation (stuendlich, groessenbasiert 10 MB)..."
+(
+ while true; do
+ sleep 3600
+ /usr/sbin/logrotate /etc/logrotate.d/tmserver --state /tmp/logrotate.state
+ done
+) &
+LOGROTATE_PID=$!
+echo " Log-Rotation gestartet (PID: ${LOGROTATE_PID})"
+
+# ============================================================
+# Graceful Shutdown: Signal-Handler
+# ============================================================
+# Faengt TERM/INT ab und beendet alle Dienste sauber in
+# der richtigen Reihenfolge:
+# 1. XAseco-Healthcheck (verhindert Neustart waehrend Shutdown)
+# 2. XAseco (schliesst DB-Connections ordentlich)
+# 3. TrackmaniaServer
+# 4. Apache (AdminServ/RemoteCP)
+# 5. Log-Rotation
+# Verhindert Datenbank-Korruption beim Container-Stop.
+# ============================================================
+graceful_shutdown() {
+ echo ""
+ echo "============================================================"
+ echo "==> Graceful Shutdown eingeleitet (Signal empfangen)..."
+ echo "============================================================"
+
+ # 1. XAseco-Healthcheck beenden (verhindert Neustart waehrend Shutdown)
+ if [ -n "${HEALTHCHECK_PID:-}" ] && kill -0 "$HEALTHCHECK_PID" 2>/dev/null; then
+ echo " Beende XAseco-Healthcheck (PID: ${HEALTHCHECK_PID})..."
+ kill "$HEALTHCHECK_PID" 2>/dev/null
+ wait "$HEALTHCHECK_PID" 2>/dev/null
+ echo " XAseco-Healthcheck beendet."
+ fi
+
+ # 2. XAseco beenden (schliesst DB-Connections ordentlich)
+ XASECO_PID_CURRENT=""
+ if [ -f "/tmp/xaseco.pid" ]; then
+ XASECO_PID_CURRENT=$(cat /tmp/xaseco.pid 2>/dev/null)
+ fi
+ if [ -n "$XASECO_PID_CURRENT" ] && kill -0 "$XASECO_PID_CURRENT" 2>/dev/null; then
+ echo " Beende XAseco (PID: ${XASECO_PID_CURRENT})..."
+ kill "$XASECO_PID_CURRENT" 2>/dev/null
+ # Warte max. 10 Sekunden auf sauberes Beenden
+ WAIT_COUNT=0
+ while kill -0 "$XASECO_PID_CURRENT" 2>/dev/null && [ $WAIT_COUNT -lt 10 ]; do
+ sleep 1
+ WAIT_COUNT=$((WAIT_COUNT + 1))
+ done
+ if kill -0 "$XASECO_PID_CURRENT" 2>/dev/null; then
+ echo " XAseco reagiert nicht, sende SIGKILL..."
+ kill -9 "$XASECO_PID_CURRENT" 2>/dev/null
+ fi
+ echo " XAseco beendet."
+ rm -f /tmp/xaseco.pid
+ fi
+
+ # 3. TrackmaniaServer beenden
+ if [ -n "${TM_PID:-}" ] && kill -0 "$TM_PID" 2>/dev/null; then
+ echo " Beende TrackmaniaServer (PID: ${TM_PID})..."
+ kill "$TM_PID" 2>/dev/null
+ # Warte max. 10 Sekunden auf sauberes Beenden
+ WAIT_COUNT=0
+ while kill -0 "$TM_PID" 2>/dev/null && [ $WAIT_COUNT -lt 10 ]; do
+ sleep 1
+ WAIT_COUNT=$((WAIT_COUNT + 1))
+ done
+ if kill -0 "$TM_PID" 2>/dev/null; then
+ echo " TrackmaniaServer reagiert nicht, sende SIGKILL..."
+ kill -9 "$TM_PID" 2>/dev/null
+ fi
+ echo " TrackmaniaServer beendet."
+ fi
+
+ # 4. Apache beenden (AdminServ/RemoteCP)
+ echo " Beende Apache..."
+ service apache2 stop 2>/dev/null
+ echo " Apache beendet."
+
+ # 5. Log-Rotation beenden
+ if [ -n "${LOGROTATE_PID:-}" ] && kill -0 "$LOGROTATE_PID" 2>/dev/null; then
+ echo " Beende Log-Rotation (PID: ${LOGROTATE_PID})..."
+ kill "$LOGROTATE_PID" 2>/dev/null
+ wait "$LOGROTATE_PID" 2>/dev/null
+ echo " Log-Rotation beendet."
+ fi
+
+ echo "============================================================"
+ echo "==> Graceful Shutdown abgeschlossen."
+ echo "============================================================"
+ exit 0
+}
+
+trap graceful_shutdown TERM INT
+echo "==> Signal-Handler registriert (TERM/INT -> Graceful Shutdown)"
+
+# ============================================================
+# Startup-Zusammenfassung
+# ============================================================
+# Gibt am Ende des Startprozesses eine uebersichtliche Box mit
+# allen wichtigen Server-Informationen aus. Die Box-Breite passt
+# sich automatisch an den laengsten Inhalt an.
+# Bei bestehenden Installationen ist dieses Feature nach einem
+# Image-Update automatisch aktiv (keine manuellen Schritte noetig).
+# ============================================================
+print_startup_summary() {
+ _SUMMARY_TMP=$(mktemp /tmp/startup_summary.XXXXXX)
+
+ # Map-Anzahl aus aktiver MatchSettings-Datei zaehlen
+ _ACTIVE_MS_FULL="$GAMEDATA_DIR/Tracks/${GAME_SETTINGS_PATH}"
+ _MAP_COUNT="0"
+ if [ -f "$_ACTIVE_MS_FULL" ]; then
+ _MAP_COUNT=$(grep -c '' "$_ACTIVE_MS_FULL" 2>/dev/null)
+ _MAP_COUNT=${_MAP_COUNT:-0}
+ fi
+
+ # MatchSettings-Dateiname extrahieren
+ _MS_FILENAME=$(basename "$GAME_SETTINGS_PATH")
+
+ # XAseco-Status ermitteln
+ if [ "${XASECO_ENABLED:-true}" != "true" ]; then
+ _XASECO_STATUS="Deaktiviert"
+ elif [ -n "${XASECO_PID:-}" ] && kill -0 "$XASECO_PID" 2>/dev/null; then
+ _XASECO_STATUS="Aktiv (PID ${XASECO_PID})"
+ else
+ _XASECO_STATUS="Nicht gestartet"
+ fi
+
+ # Healthcheck-Status ermitteln
+ if [ "${XASECO_ENABLED:-true}" != "true" ]; then
+ _HC_STATUS="—"
+ elif [ "${XASECO_HEALTHCHECK:-true}" = "true" ] && [ -n "${HEALTHCHECK_PID:-}" ]; then
+ _HC_STATUS="Aktiv (PID ${HEALTHCHECK_PID})"
+ elif [ "${XASECO_HEALTHCHECK:-true}" != "true" ]; then
+ _HC_STATUS="Deaktiviert"
+ else
+ _HC_STATUS="Nicht gestartet"
+ fi
+
+ # Forced Mods zaehlen
+ _MOD_COUNT=0
+ for _ENV_NAME in FORCE_MOD_STADIUM FORCE_MOD_ISLAND FORCE_MOD_BAY FORCE_MOD_COAST FORCE_MOD_SPEED FORCE_MOD_ALPINE FORCE_MOD_RALLY; do
+ eval "_MOD_VAL=\${$_ENV_NAME:-}"
+ [ -n "$_MOD_VAL" ] && _MOD_COUNT=$((_MOD_COUNT + 1))
+ done
+ if [ "$_MOD_COUNT" -gt 0 ]; then
+ _MODS_STATUS="${_MOD_COUNT} Mod(s) aktiv"
+ else
+ _MODS_STATUS="Keine"
+ fi
+
+ # Shuffle-Status
+ if [ "${SHUFFLE_MAPLIST:-false}" = "true" ]; then
+ _SHUFFLE_STATUS="Aktiviert"
+ else
+ _SHUFFLE_STATUS="Deaktiviert"
+ fi
+
+ # Server-Modus-Anzeige
+ case "${SERVER_MODE:-internet}" in
+ internet) _MODE_DISPLAY="Internet" ;;
+ lan) _MODE_DISPLAY="LAN" ;;
+ *) _MODE_DISPLAY="${SERVER_MODE}" ;;
+ esac
+
+ # PHP-Debug-Status
+ if [ "${PHP_DISPLAY_ERRORS:-false}" = "true" ]; then
+ _PHP_DEBUG="Aktiviert"
+ else
+ _PHP_DEBUG="Deaktiviert"
+ fi
+
+ # Servername aus dedicated_cfg.txt lesen (nicht aus Env-Variable, da diese
+ # nach AdminServ-Aenderungen oder manuellen Edits veraltet sein kann)
+ _SERVER_NAME=""
+ if [ -f "$CONFIG" ]; then
+ # Nur innerhalb von lesen (nicht aus )
+ _SERVER_NAME=$(php -r '
+ $cfg = file_get_contents($argv[1]);
+ if (preg_match("/.*?([^<]*)<\/name>/s", $cfg, $m)) {
+ echo trim($m[1]);
+ }
+ ' "$CONFIG" 2>/dev/null)
+ fi
+ _SERVER_NAME=${_SERVER_NAME:-${SERVER_NAME:-Trackmania Server}}
+
+ # Ladder-Modus aus dedicated_cfg.txt lesen
+ _LADDER_MODE=""
+ if [ -f "$CONFIG" ]; then
+ _LADDER_MODE=$(grep -oP '(?<=)[^<]+' "$CONFIG" 2>/dev/null | head -1)
+ fi
+ _LADDER_MODE=${_LADDER_MODE:-${SERVER_LADDER_MODE:-forced}}
+
+ # Max-Players und Max-Spectators aus Config lesen (falls verfuegbar)
+ _MAX_PLAYERS=""
+ _MAX_SPECS=""
+ if [ -f "$CONFIG" ]; then
+ _MAX_PLAYERS=$(grep -oP '(?<=)[^<]+' "$CONFIG" 2>/dev/null | head -1)
+ _MAX_SPECS=$(grep -oP '(?<=)[^<]+' "$CONFIG" 2>/dev/null | head -1)
+ fi
+ _MAX_PLAYERS=${_MAX_PLAYERS:-${SERVER_MAX_PLAYERS:-32}}
+ _MAX_SPECS=${_MAX_SPECS:-${SERVER_MAX_SPECTATORS:-32}}
+
+ # --- Zeilen in Temp-Datei schreiben ('---' = Trennlinie) ---
+ cat > "$_SUMMARY_TMP" </
+RemoteCP: http:///remotecp/
+---
+Log-Rotation: Aktiv (stuendlich, max. 10 MB)
+PHP-Debug: ${_PHP_DEBUG}
+TM-Server: PID ${TM_PID}
+EOSUMMARY
+
+ # --- Maximale Zeilenlaenge ermitteln ---
+ _MAX_LEN=0
+ while IFS= read -r _line; do
+ if [ "$_line" != "---" ]; then
+ _cur_len=${#_line}
+ [ "$_cur_len" -gt "$_MAX_LEN" ] && _MAX_LEN=$_cur_len
+ fi
+ done < "$_SUMMARY_TMP"
+
+ # Box-Breite: Inhalt + je 2 Zeichen Padding (links/rechts)
+ _BOX_INNER=$((_MAX_LEN + 4))
+
+ # --- Horizontale Linie erzeugen ---
+ _HLINE=""
+ _i=0
+ while [ "$_i" -lt "$_BOX_INNER" ]; do
+ _HLINE="${_HLINE}═"
+ _i=$((_i + 1))
+ done
+
+ # --- Box ausgeben ---
+ echo ""
+ printf '╔%s╗\n' "$_HLINE"
+
+ _is_title=true
+ while IFS= read -r _line; do
+ if [ "$_line" = "---" ]; then
+ printf '╠%s╣\n' "$_HLINE"
+ elif [ "$_is_title" = "true" ]; then
+ # Titelzeile zentriert ausgeben
+ _title_len=${#_line}
+ _pad_total=$((_MAX_LEN - _title_len))
+ _pad_left=$((_pad_total / 2))
+ _pad_right=$((_pad_total - _pad_left))
+ printf '║ %*s%s%*s ║\n' "$_pad_left" "" "$_line" "$_pad_right" ""
+ _is_title=false
+ else
+ printf '║ %-*s ║\n' "$_MAX_LEN" "$_line"
+ fi
+ done < "$_SUMMARY_TMP"
+
+ printf '╚%s╝\n' "$_HLINE"
+ echo ""
+
+ # Aufraeumen
+ rm -f "$_SUMMARY_TMP"
+}
+
+print_startup_summary
+
# Auf TrackmaniaServer warten (Hauptprozess)
wait $TM_PID
diff --git a/assets/bin/XAsecoHealthcheck.sh b/assets/bin/XAsecoHealthcheck.sh
new file mode 100644
index 0000000..70aa443
--- /dev/null
+++ b/assets/bin/XAsecoHealthcheck.sh
@@ -0,0 +1,182 @@
+#!/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 >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
+ LAST_LINES=$(tail -20 "$LOG_FILE" 2>/dev/null)
+ # 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."
diff --git a/assets/config/logrotate.conf b/assets/config/logrotate.conf
new file mode 100644
index 0000000..7322f10
--- /dev/null
+++ b/assets/config/logrotate.conf
@@ -0,0 +1,55 @@
+# ============================================================
+# 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
+}
+
+# AdminServ-Logs (liegen im persistenten Volume)
+/var/www/html/logs/*.log
+{
+ size 10M
+ rotate 5
+ missingok
+ notifempty
+ compress
+ delaycompress
+ copytruncate
+}
diff --git a/docker-compose.yml b/docker-compose.yml
index 5a78284..c0fdd9c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,10 +1,11 @@
services:
tmserver:
- image: git.techniverse.net/scriptos/trackmania-server:1.2.2
+ image: git.techniverse.net/scriptos/trackmania-server:1.3.0
build:
context: .
container_name: tmserver
restart: unless-stopped
+ stop_grace_period: 30s
depends_on:
mariadb:
condition: service_healthy
diff --git a/docs/README.md b/docs/README.md
index 0a38fa9..6b9b40d 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -9,7 +9,7 @@
| Dokument | Beschreibung |
|----------|-------------|
| [Schnellstart](schnellstart.md) | Erste Schritte und minimale Konfiguration |
-| [Konfiguration](konfiguration.md) | Persistente Serverkonfiguration (dedicated_cfg.txt) |
+| [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 |
@@ -33,6 +33,7 @@
│ │ │ └── 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/
diff --git a/docs/adminserv.md b/docs/adminserv.md
index 364ab89..27c646f 100644
--- a/docs/adminserv.md
+++ b/docs/adminserv.md
@@ -59,6 +59,8 @@ Alternativ können die PHP-Logs eingesehen werden:
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:
diff --git a/docs/konfiguration.md b/docs/konfiguration.md
index 72e0d49..b5ddca0 100644
--- a/docs/konfiguration.md
+++ b/docs/konfiguration.md
@@ -106,6 +106,105 @@ Der Ordner `GameData/Config/` enthält:
| `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:/// ║
+║ RemoteCP: http:///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:** `` 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.
diff --git a/docs/schnellstart.md b/docs/schnellstart.md
index 496b813..02ae590 100644
--- a/docs/schnellstart.md
+++ b/docs/schnellstart.md
@@ -43,10 +43,10 @@ Die Konfiguration erfolgt über die `.env`-Datei, die automatisch eingelesen wir
Alternativ kannst du das Image auch selbst bauen:
```bash
-docker build -t tmserver:latest -t tmserver:1.2.1 .
+docker build -t tmserver:latest -t tmserver:1.3.0 .
```
-Damit wird das Image mit zwei Tags erstellt: `tmserver:latest` und `tmserver:1.2.1`.
+Damit wird das Image mit zwei Tags erstellt: `tmserver:latest` und `tmserver:1.3.0`.
Anschließend den Server starten:
diff --git a/docs/umgebungsvariablen.md b/docs/umgebungsvariablen.md
index c439ea4..fb499eb 100644
--- a/docs/umgebungsvariablen.md
+++ b/docs/umgebungsvariablen.md
@@ -73,6 +73,7 @@ nano .env
|----------|-------------|----------|
| `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
@@ -90,6 +91,27 @@ 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 ``-Einträge in der MatchSettings-XML werden zufällig neu angeordnet
+- Der `` 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.
@@ -161,6 +183,8 @@ XAseco ist ein Server-Controller für Rekorde, Karma, Jukebox und mehr. Siehe [X
| `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.
diff --git a/docs/xaseco.md b/docs/xaseco.md
index 3b82278..df2384a 100644
--- a/docs/xaseco.md
+++ b/docs/xaseco.md
@@ -77,6 +77,8 @@ Die Konfiguration erfolgt ausschließlich über Umgebungsvariablen in der `.env`
| `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
@@ -147,6 +149,37 @@ 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_.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:
@@ -157,6 +190,8 @@ Die XAseco-Logdatei befindet sich unter:
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