├── .github └── workflows │ └── docker-publish.yml ├── Dockerfile ├── trackmania_exporter ├── gbx.py └── trackmania_exporter.py ├── LICENSE ├── entrypoint.sh └── README.md /.github/workflows/docker-publish.yml: -------------------------------------------------------------------------------- 1 | name: Docker Publish 2 | on: 3 | release: 4 | types: [published] 5 | 6 | jobs: 7 | push: 8 | runs-on: ubuntu-latest 9 | if: github.event_name == 'release' 10 | steps: 11 | - 12 | name: Checkout 13 | uses: actions/checkout@v3 14 | - 15 | name: Prepare 16 | id: prep 17 | run: | 18 | DOCKER_IMAGE=evoesports/trackmania 19 | VERSION=${GITHUB_REF#refs/tags/} 20 | TAGS="${DOCKER_IMAGE}:${VERSION},${DOCKER_IMAGE}:latest" 21 | echo ::set-output name=tags::${TAGS} 22 | echo ::set-output name=version::${VERSION} 23 | echo ::set-output name=builddate::$(date -u +'%Y-%m-%dT%H:%M:%SZ') 24 | echo ::set-output name=revision::$(git rev-parse --short HEAD) 25 | - 26 | name: Set up Docker Buildx 27 | uses: docker/setup-buildx-action@v2 28 | - 29 | name: Login to DockerHub 30 | uses: docker/login-action@v2 31 | with: 32 | username: ${{ secrets.DOCKERHUB_USERNAME }} 33 | password: ${{ secrets.DOCKERHUB_TOKEN }} 34 | - 35 | name: Build and push 36 | uses: docker/build-push-action@v3 37 | with: 38 | context: . 39 | push: true 40 | tags: ${{ steps.prep.outputs.tags }} 41 | build-args: | 42 | VERSION=${{ steps.prep.outputs.version }} 43 | BUILD_DATE=${{ steps.prep.outputs.builddate }} 44 | REVISION=${{ steps.prep.outputs.revision }} 45 | - 46 | name: Docker Hub Description 47 | uses: peter-evans/dockerhub-description@v3 48 | with: 49 | username: ${{ secrets.DOCKERHUB_USERNAME }} 50 | password: ${{ secrets.DOCKERHUB_TOKEN }} 51 | repository: evoesports/trackmania -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # build trackmania_exporter 2 | FROM python:3.11-alpine3.17 AS exporter-build 3 | WORKDIR /build 4 | COPY trackmania_exporter . 5 | RUN apk upgrade && apk add binutils && pip3 install --root-user-action=ignore prometheus-client pyinstaller && pyinstaller --onefile --console --clean --strip trackmania_exporter.py 6 | 7 | # build trackmania image 8 | FROM alpine:3.17 9 | 10 | ARG GLIBC_VERSION="2.33-r0" \ 11 | TMSERVER_VERSION="Latest" \ 12 | VERSION \ 13 | BUILD_DATE \ 14 | REVISION 15 | 16 | LABEL org.opencontainers.image.title="Trackmania Server" \ 17 | org.opencontainers.image.description="Server for the game Trackmania, released in 2020 by Nadeo." \ 18 | org.opencontainers.image.authors="Nicolas Graf " \ 19 | org.opencontainers.image.vendor="Evo" \ 20 | org.opencontainers.image.licenses="Apache-2.0" \ 21 | org.opencontainers.image.version=${VERSION} \ 22 | org.opencontainers.image.created=${BUILD_DATE} \ 23 | org.opencontainers.image.revision=${REVISION} 24 | 25 | WORKDIR /server 26 | 27 | RUN true \ 28 | && set -eux \ 29 | && addgroup -g 9999 trackmania \ 30 | && adduser -u 9999 -Hh /server -G trackmania -s /sbin/nologin -D trackmania \ 31 | && install -d -o trackmania -g trackmania -m 775 /server \ 32 | && wget -q -O /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub \ 33 | && wget -q -O /etc/apk/glibc.apk https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-${GLIBC_VERSION}.apk \ 34 | && apk upgrade \ 35 | && apk add --force-overwrite --no-cache /etc/apk/glibc.apk xmlstarlet bash unzip curl jq su-exec \ 36 | && curl -so /server/server.zip http://files.v04.maniaplanet.com/server/TrackmaniaServer_${TMSERVER_VERSION}.zip \ 37 | && unzip -q /server/server.zip \ 38 | && rm -Rf /etc/apk/glibc.apk /server/server.zip /server/RemoteControlExamples /server/TrackmaniaServer.exe \ 39 | && chown trackmania:trackmania -Rf /server \ 40 | && true 41 | 42 | COPY --chmod=0755 entrypoint.sh /usr/local/bin/ 43 | COPY --chmod=0755 --from=exporter-build /build/dist/trackmania_exporter /usr/local/bin/ 44 | 45 | USER trackmania 46 | 47 | EXPOSE 2350/tcp 48 | EXPOSE 2350/udp 49 | EXPOSE 5000/tcp 50 | EXPOSE 9000/tcp 51 | 52 | HEALTHCHECK --interval=5s --timeout=5s --start-period=20s --retries=3 \ 53 | CMD nc -z -v 127.0.0.1 5000 || exit 1 54 | 55 | ENTRYPOINT [ "entrypoint.sh" ] 56 | CMD [ "./TrackmaniaServer" ] 57 | -------------------------------------------------------------------------------- /trackmania_exporter/gbx.py: -------------------------------------------------------------------------------- 1 | import socket 2 | from xmlrpc.client import loads as xmlloads 3 | from xmlrpc.client import dumps as xmldumps 4 | from signal import signal, SIGPIPE, SIG_DFL 5 | 6 | signal(SIGPIPE,SIG_DFL) 7 | 8 | class GbxRemote(): 9 | def __init__(self, host, port, user, password): 10 | self.connection_info = (host, port, user, password) 11 | 12 | def connect(self): 13 | try: 14 | host, port, user, password = self.connection_info 15 | self.handler = 0x80000000 16 | self.callback_enabled = False 17 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 18 | self.socket.connect((socket.gethostbyname(host), port)) 19 | 20 | data = self.socket.recv(4) 21 | headerLength = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24) 22 | 23 | header = self.socket.recv(headerLength) 24 | if not header.decode() == 'GBXRemote 2': 25 | print('Invalid header.') 26 | exit(0) 27 | 28 | self.callMethod('Authenticate', user, password) 29 | self.callMethod('SetApiVersion', '2013-04-16') 30 | self.callMethod('EnableCallbacks', False) 31 | return True 32 | except OSError as error: 33 | return False 34 | 35 | def _incHandler(self): 36 | self.handler += 1 37 | if self.handler > 0xFFFFFFFF: 38 | self.handler = 0x80000000 39 | 40 | def callMethod(self, method, *argv): 41 | handlerBytes = bytes([ 42 | self.handler & 0xFF, 43 | (self.handler >> 8) & 0xFF, 44 | (self.handler >> 16) & 0xFF, 45 | (self.handler >> 24) & 0xFF]) 46 | 47 | data = xmldumps(argv, method).encode('utf-8') 48 | packetLen = len(data) 49 | packet = bytes([ 50 | packetLen & 0xFF, 51 | (packetLen >> 8) & 0xFF, 52 | (packetLen >> 16) & 0xFF, 53 | (packetLen >> 24) & 0xFF 54 | ]) 55 | packet += handlerBytes 56 | packet += data 57 | 58 | self.socket.send(packet) 59 | 60 | header = self.socket.recv(8) 61 | size = header[0] | (header[1] << 8) | (header[2] << 16) | (header[3] << 24) 62 | responseHandler = header[4] | (header[5] << 8) | (header[6] << 16) | (header[7] << 24) 63 | if responseHandler != self.handler: 64 | print('Response handler does not match!') 65 | exit(0) 66 | 67 | response = self.socket.recv(size) 68 | while len(response) < size: 69 | response += self.socket.recv(size - len(response)) 70 | params, func = xmlloads(response.decode('utf-8')) 71 | 72 | self._incHandler() 73 | if func is None: 74 | return params 75 | else: 76 | return (func, params) 77 | -------------------------------------------------------------------------------- /trackmania_exporter/trackmania_exporter.py: -------------------------------------------------------------------------------- 1 | import prometheus_client 2 | from prometheus_client import Gauge 3 | from gbx import GbxRemote 4 | import time 5 | import os 6 | 7 | # connect to xmlrpc 8 | gbxremote = GbxRemote(host='127.0.0.1', port=os.environ.get('TM_SYSTEM_XMLRPC_PORT', 5000), user='SuperAdmin', password=os.environ.get('TM_AUTHORIZATION_SUPERADMIN_PASSWORD', 'SuperAdmin')) 9 | 10 | gbxconnected = False 11 | while not gbxconnected: 12 | try: 13 | time.sleep(1) 14 | if gbxremote.connect(): 15 | print('GBX connection established.') 16 | gbxconnected = True 17 | else: 18 | print('GBX connection refused or not ready yet.') 19 | except Exception as e: 20 | pass 21 | 22 | # we don't need python internal metrics 23 | prometheus_client.REGISTRY.unregister(prometheus_client.GC_COLLECTOR) 24 | prometheus_client.REGISTRY.unregister(prometheus_client.PLATFORM_COLLECTOR) 25 | prometheus_client.REGISTRY.unregister(prometheus_client.PROCESS_COLLECTOR) 26 | 27 | # register trackmania metrics 28 | TRACKMANIA_PLAYER_COUNT = Gauge('trackmania_player_count', 'Current player count by type.', ['type']) 29 | TRACKMANIA_MODERATION_COUNT = Gauge('trackmania_moderation_count', 'Current players count being moderated by type.', ['type']) 30 | TRACKMANIA_PLAYER_COUNT_MEAN = Gauge('trackmania_player_count_mean', 'The mean value of the player count.') 31 | TRACKMANIA_SERVER_UPTIME = Gauge('trackmania_server_uptime', 'Time since the TrackMania server has started in seconds.') 32 | TRACKMANIA_CONNECTION_COUNT = Gauge('trackmania_connection_count', 'Total connections made to the TrackMania server.') 33 | TRACKMANIA_CONNECTION_TIME_MEAN = Gauge('trackmania_connection_time_mean', 'The mean value of the connection time in ms.') 34 | TRACKMANIA_NET_RATE_RECV = Gauge('trackmania_net_rate_recv', 'Connection rate inbound in kbps.') 35 | TRACKMANIA_NET_RATE_SEND = Gauge('trackmania_net_rate_send', 'Connection rate outbound in kbps.') 36 | TRACKMANIA_MAPS_COUNT = Gauge('trackmania_maps_count', 'Amount of maps the server currently has loaded.') 37 | TRACKMANIA_PLAYER_MAX = Gauge('trackmania_player_max', 'Max configured amount of players the server can hold.', ['type']) 38 | 39 | # update function, to be called every x seconds 40 | def update(): 41 | response0 = gbxremote.callMethod('GetPlayerList', -1, 0) 42 | response1 = gbxremote.callMethod('GetBanList', -1, 0) 43 | response2 = gbxremote.callMethod('GetBlackList', -1, 0) 44 | response3 = gbxremote.callMethod('GetGuestList', -1, 0) 45 | response4 = gbxremote.callMethod('GetIgnoreList', -1, 0) 46 | response5 = gbxremote.callMethod('GetNetworkStats') 47 | response6 = gbxremote.callMethod('GetMapList', -1, 0) 48 | response7 = gbxremote.callMethod('GetMaxPlayers') 49 | response8 = gbxremote.callMethod('GetMaxSpectators') 50 | 51 | spectators = 0 52 | drivers = 0 53 | for player in list(response0[0]): 54 | flags = player['Flags'] 55 | specStatus = player['SpectatorStatus'] 56 | 57 | if (((flags >> 5) & 1) == 1): 58 | continue 59 | 60 | if (specStatus & 1): 61 | spectators += 1 62 | else: 63 | drivers += 1 64 | 65 | TRACKMANIA_PLAYER_COUNT.labels('online').set(spectators + drivers) 66 | TRACKMANIA_PLAYER_COUNT.labels('spectating').set(spectators) 67 | TRACKMANIA_PLAYER_COUNT.labels('driving').set(drivers) 68 | 69 | TRACKMANIA_MODERATION_COUNT.labels('banned').set(len(response1[0])) 70 | TRACKMANIA_MODERATION_COUNT.labels('blacklisted').set(len(response2[0])) 71 | TRACKMANIA_MODERATION_COUNT.labels('guestlisted').set(len(response3[0])) 72 | TRACKMANIA_MODERATION_COUNT.labels('ignored').set(len(response4[0])) 73 | 74 | TRACKMANIA_PLAYER_COUNT_MEAN.set(response5[0]['MeanNbrPlayer']) 75 | TRACKMANIA_SERVER_UPTIME.set(response5[0]['Uptime']) 76 | TRACKMANIA_CONNECTION_COUNT.set(response5[0]['NbrConnection']) 77 | TRACKMANIA_CONNECTION_TIME_MEAN.set(response5[0]['MeanConnectionTime']) 78 | TRACKMANIA_NET_RATE_RECV.set(response5[0]['RecvNetRate']) 79 | TRACKMANIA_NET_RATE_SEND.set(response5[0]['SendNetRate']) 80 | 81 | TRACKMANIA_MAPS_COUNT.set(len(response6[0])) 82 | TRACKMANIA_PLAYER_MAX.labels('players').set(response7[0]['CurrentValue']) 83 | TRACKMANIA_PLAYER_MAX.labels('spectators').set(response8[0]['CurrentValue']) 84 | 85 | if __name__ == '__main__': 86 | prometheus_client.start_http_server(int(os.environ.get('PROMETHEUS_PORT', '9000'))) 87 | # update loop 88 | while True: 89 | update() 90 | time.sleep(int(os.environ.get('PROMETHEUS_INTERVAL', '15'))) 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [2024] [Evo eSports e.V.] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | echo "[*] Evo Esports Trackmania Docker Image" 6 | 7 | # we don't want to start the server with root permissions 8 | if [ "$1" = './TrackmaniaServer' ] && [ "$(id -u)" = '0' ]; then 9 | chown -R trackmania /server/TrackmaniaServer 10 | exec su-exec trackmania "$0" "$@" 11 | fi 12 | 13 | if [ "$TM_DEDICATED_CFG" ]; then 14 | DC=$TM_DEDICATED_CFG 15 | elif [ "$DEDICATED_CONFIG" ]; then 16 | DC=$DEDICATED_CONFIG 17 | fi 18 | 19 | if [ "$TM_GAME_SETTINGS" ]; then 20 | GS=$TM_GAME_SETTINGS 21 | elif [ "$GAME_SETTINGS" ]; then 22 | GS=$GAME_SETTINGS 23 | fi 24 | 25 | # we also want to have dedicated_cfg, game_settings, and noDaemon added, no matter what the user specifies 26 | if [ "$1" = './TrackmaniaServer' ]; then 27 | set -- "$@" /dedicated_cfg="${DC:-dedicated_cfg.txt}" /game_settings=MatchSettings/"${GS:-default.txt}" /noDaemon 28 | fi 29 | 30 | # also we need to populate the config 31 | if [ "$1" = './TrackmaniaServer' ]; then 32 | if [ -z "$CONFIG_POPULATION_DISABLED" ]; then 33 | [ ! -f /server/UserData/Config/"${DC:-dedicated_cfg.txt}" ] && cp /server/UserData/Config/dedicated_cfg.default.txt /server/UserData/Config/"${DC:-dedicated_cfg.txt}" 34 | 35 | configs=() 36 | # required settings 37 | configs+=("'/dedicated/system_config/server_port' -v \"${TM_SYSTEM_SERVER_PORT:-2350}\"") 38 | configs+=("'/dedicated/system_config/xmlrpc_port' -v \"${TM_SYSTEM_XMLRPC_PORT:-5000}\"") 39 | #### deprecate soon 40 | if [ "$TM_MASTERSERVER_LOGIN" ]; then 41 | configs+=("'/dedicated/masterserver_account/login' -v \"${TM_MASTERSERVER_LOGIN}\""); 42 | elif [ "$MASTER_LOGIN" ]; then 43 | configs+=("'/dedicated/masterserver_account/login' -v \"${MASTER_LOGIN}\""); echo "[-] MASTER_LOGIN will be removed soon. Please use TM_MASTERSERVER_LOGIN instead."; 44 | fi 45 | 46 | if [ "$TM_MASTERSERVER_PASSWORD" ]; then 47 | # shellcheck disable=SC2016 48 | TM_MASTERSERVER_PASSWORD=$(echo "${TM_MASTERSERVER_PASSWORD}" | sed -e 's/\$/\\\$/g' -e 's/`/\\`/g'); 49 | configs+=("'/dedicated/masterserver_account/password' -v \"${TM_MASTERSERVER_PASSWORD}\""); 50 | elif [ "$MASTER_PASSWORD" ]; then 51 | # shellcheck disable=SC2016 52 | MASTER_PASSWORD=$(echo "${MASTER_PASSWORD}" | sed -e 's/\$/\\\$/g' -e 's/`/\\`/g'); 53 | configs+=("'/dedicated/masterserver_account/password' -v \"${MASTER_PASSWORD}\""); echo "[-] MASTER_PASSWORD will be removed soon. Please use TM_MASTERSERVER_PASSWORD instead."; 54 | fi 55 | 56 | if [ "$PLAYERS_MAX" ]; then configs+=("'/dedicated/server_options/max_players' -v \"${PLAYERS_MAX}\""); echo "[-] PLAYERS_MAX will be removed soon. Please use TM_SERVER_MAX_PLAYERS instead."; fi 57 | if [ "$PLAYERS_PASSWORD" ]; then configs+=("'/dedicated/server_options/password' -v \"${PLAYERS_PASSWORD}\""); echo "[-] PLAYERS_PASSWORD will be removed soon. Please use TM_SERVER_PASSWORD instead."; fi 58 | if [ "$SPECTATORS_MAX" ]; then configs+=("'/dedicated/server_options/max_spectators' -v \"${SPECTATORS_MAX}\""); echo "[-] SPECTATORS_MAX will be removed soon. Please use TM_SERVER_MAX_SPECTATORS instead."; fi 59 | if [ "$SPECTATORS_PASSWORD" ]; then configs+=("'/dedicated/server_options/password_spectator' -v \"${SPECTATORS_PASSWORD}\""); echo "[-] SPECTATORS_PASSWORD will be removed soon. Please use TM_SERVER_PASSWORD_SPECTATOR instead."; fi 60 | if [ "$ALLOW_MAP_DOWNLOAD" ]; then configs+=("'/dedicated/server_options/allow_map_download' -v \"${ALLOW_MAP_DOWNLOAD}\""); echo "[-] ALLOW_MAP_DOWNLOAD will be removed soon. Please use TM_SERVER_ALLOW_MAP_DOWNLOAD instead."; fi 61 | if [ "$AUTOSAVE_REPLAYS" ]; then configs+=("'/dedicated/server_options/autosave_replays' -v \"${AUTOSAVE_REPLAYS}\""); echo "[-] AUTOSAVE_REPLAYS will be removed soon. Please use TM_SERVER_AUTOSAVE_REPLAYS instead."; fi 62 | if [ "$AUTOSAVE_VALIDATION_REPLAYS" ]; then configs+=("'/dedicated/server_options/autosave_validation_replays' -v \"${AUTOSAVE_VALIDATION_REPLAYS}\""); echo "[-] AUTOSAVE_VALIDATION_REPLAYS will be removed soon. Please use TM_SERVER_AUTOSAVE_VALIDATION_REPLAYS instead."; fi 63 | if [ "$CONNECTION_UPLOADRATE" ]; then configs+=("'/dedicated/system_config/connection_uploadrate' -v \"${CONNECTION_UPLOADRATE}\""); echo "[-] CONNECTION_UPLOADRATE will be removed soon. Please use TM_SYSTEM_CONNECTION_UPLOADRATE instead."; fi 64 | if [ "$CONNECTION_DOWNLOADRATE" ]; then configs+=("'/dedicated/system_config/connection_downloadrate' -v \"${CONNECTION_DOWNLOADRATE}\""); echo "[-] CONNECTION_DOWNLOADRATE will be removed soon. Please use TM_SYSTEM_CONNECTION_DOWNLOADRATE instead."; fi 65 | if [ "$WORKERTHREADCOUNT" ]; then configs+=("'/dedicated/system_config/workerthreadcount' -v \"${WORKERTHREADCOUNT}\""); echo "[-] WORKERTHREADCOUNT will be removed soon. Please use TM_SYSTEM_WORKERTHREADCOUNT instead."; fi 66 | if [ "$PACKETASSEMBLY_MULTITHREAD" ]; then configs+=("'/dedicated/system_config/packetassembly_multithread' -v \"${PACKETASSEMBLY_MULTITHREAD}\""); echo "[-] PACKETASSEMBLY_MULTITHREAD will be removed soon. Please use TM_SYSTEM_PACKETASSEMBLY_MULTITHREAD instead."; fi 67 | if [ "$FORCE_IP_ADDRESS" ]; then configs+=("'/dedicated/system_config/force_ip_address' -v \"${FORCE_IP_ADDRESS}\""); echo "[-] FORCE_IP_ADDRESS will be removed soon. Please use TM_SYSTEM_FORCE_IP_ADDRESS instead."; fi 68 | if [ "$XMLRPC_ALLOWREMOTE" ]; then configs+=("'/dedicated/system_config/xmlrpc_allowremote' -v \"${XMLRPC_ALLOWREMOTE}\""); echo "[-] XMLRPC_ALLOWREMOTE will be removed soon. Please use TM_SYSTEM_XMLRPC_ALLOWREMOTE instead."; fi 69 | if [ "$DISABLE_COHERENCE_CHECKS" ]; then configs+=("'/dedicated/system_config/disable_coherence_checks' -v \"${DISABLE_COHERENCE_CHECKS}\""); echo "[-] DISABLE_COHERENCE_CHECKS will be removed soon. Please use TM_SYSTEM_DISABLE_COHERENCE_CHECKS instead."; fi 70 | if [ "$DISABLE_REPLAY_RECORDING" ]; then configs+=("'/dedicated/system_config/disable_replay_recording' -v \"${DISABLE_REPLAY_RECORDING}\""); echo "[-] DISABLE_REPLAY_RECORDING will be removed soon. Please use TM_SYSTEM_DISABLE_REPLAY_RECORDING instead."; fi 71 | if [ "$SAVE_ALL_INDIVIDUAL_RUNS" ]; then configs+=("'/dedicated/system_config/save_all_individual_runs' -v \"${SAVE_ALL_INDIVIDUAL_RUNS}\""); echo "[-] SAVE_ALL_INDIVIDUAL_RUNS will be removed soon. Please use TM_SYSTEM_SAVE_ALL_INDIVIDUAL_RUNS instead."; fi 72 | #### deprecate soon 73 | # /dedicated/authorization_levels/* 74 | if [ "$TM_AUTHORIZATION_SUPERADMIN_PASSWORD" ]; then configs+=("'/dedicated/authorization_levels/level[1]/password' -v \"${TM_AUTHORIZATION_SUPERADMIN_PASSWORD}\""); fi 75 | if [ "$TM_AUTHORIZATION_ADMIN_PASSWORD" ]; then configs+=("'/dedicated/authorization_levels/level[2]/password' -v \"${TM_AUTHORIZATION_ADMIN_PASSWORD}\""); fi 76 | if [ "$TM_AUTHORIZATION_USER_PASSWORD" ]; then configs+=("'/dedicated/authorization_levels/level[3]/password' -v \"${TM_AUTHORIZATION_USER_PASSWORD}\""); fi 77 | # /dedicated/server_options/* 78 | if [ "$TM_SERVER_COMMENT" ]; then configs+=("'/dedicated/server_options/login' -v \"${TM_SERVER_COMMENT}\""); fi 79 | if [ "$TM_SERVER_MAX_PLAYERS" ]; then configs+=("'/dedicated/server_options/max_players' -v \"${TM_SERVER_MAX_PLAYERS}\""); fi 80 | if [ "$TM_SERVER_PASSWORD" ]; then configs+=("'/dedicated/server_options/password' -v \"${TM_SERVER_PASSWORD}\""); fi 81 | if [ "$TM_SERVER_MAX_SPECTATORS" ]; then configs+=("'/dedicated/server_options/max_spectators' -v \"${TM_SERVER_MAX_SPECTATORS}\""); fi 82 | if [ "$TM_SERVER_PASSWORD_SPECTATOR" ]; then configs+=("'/dedicated/server_options/password_spectator' -v \"${TM_SERVER_PASSWORD_SPECTATOR}\""); fi 83 | if [ "$TM_SERVER_KEEP_PLAYER_SLOTS" ]; then configs+=("'/dedicated/server_options/keep_player_slots' -v \"${TM_SERVER_KEEP_PLAYER_SLOTS}\""); fi 84 | if [ "$TM_SERVER_CALLVOTE_TIMEOUT" ]; then configs+=("'/dedicated/server_options/callvote_timeout' -v \"${TM_SERVER_CALLVOTE_TIMEOUT}\""); fi 85 | if [ "$TM_SERVER_CALLVOTE_RATIO" ]; then configs+=("'/dedicated/server_options/callvote_ratio' -v \"${TM_SERVER_CALLVOTE_RATIO}\""); fi 86 | if [ "$TM_SERVER_ALLOW_MAP_DOWNLOAD" ]; then configs+=("'/dedicated/server_options/allow_map_download' -v \"${TM_SERVER_ALLOW_MAP_DOWNLOAD}\""); fi 87 | if [ "$TM_SERVER_AUTOSAVE_REPLAYS" ]; then configs+=("'/dedicated/server_options/autosave_replays' -v \"${TM_SERVER_AUTOSAVE_REPLAYS}\""); fi 88 | if [ "$TM_SERVER_AUTOSAVE_VALIDATION_REPLAYS" ]; then configs+=("'/dedicated/server_options/autosave_validation_replays' -v \"${TM_SERVER_AUTOSAVE_VALIDATION_REPLAYS}\""); fi 89 | if [ "$TM_SERVER_USE_CHANGING_VALIDATION_SEED" ]; then configs+=("'/dedicated/server_options/use_changing_validation_seed' -v \"${TM_SERVER_USE_CHANGING_VALIDATION_SEED}\""); fi 90 | if [ "$TM_SERVER_DISABLE_HORNS" ]; then configs+=("'/dedicated/server_options/disable_horns' -v \"${TM_SERVER_DISABLE_HORNS}\""); fi 91 | if [ "$TM_SERVER_DISABLE_PROFILE_SKINS" ]; then configs+=("'/dedicated/server_options/disable_profile_skins' -v \"${TM_SERVER_DISABLE_PROFILE_SKINS}\""); fi 92 | if [ "$TM_SERVER_CLIENTINPUTS_MAXLATENCY" ]; then configs+=("'/dedicated/server_options/clientinputs_maxlatency' -v \"${TM_SERVER_CLIENTINPUTS_MAXLATENCY}\""); fi 93 | # /dedicated/system_config/* 94 | if [ "$TM_SYSTEM_CONNECTION_UPLOADRATE" ]; then configs+=("'/dedicated/system_config/connection_uploadrate' -v \"${TM_SYSTEM_CONNECTION_UPLOADRATE}\""); fi 95 | if [ "$TM_SYSTEM_CONNECTION_DOWNLOADRATE" ]; then configs+=("'/dedicated/system_config/connection_downloadrate' -v \"${TM_SYSTEM_CONNECTION_DOWNLOADRATE}\""); fi 96 | if [ "$TM_SYSTEM_WORKERTHREADCOUNT" ]; then configs+=("'/dedicated/system_config/workerthreadcount' -v \"${TM_SYSTEM_WORKERTHREADCOUNT}\""); fi 97 | if [ "$TM_SYSTEM_PACKETASSEMBLY_MULTITHREAD" ]; then configs+=("'/dedicated/system_config/packetassembly_multithread' -v \"${TM_SYSTEM_PACKETASSEMBLY_MULTITHREAD}\""); fi 98 | if [ "$TM_SYSTEM_PACKETASSEMBLY_PACKETSPERFRAME" ]; then configs+=("'/dedicated/system_config/packetassembly_packetsperframe' -v \"${TM_SYSTEM_PACKETASSEMBLY_PACKETSPERFRAME}\""); fi 99 | if [ "$TM_SYSTEM_PACKETASSEMBLY_FULLPACKETSPERFRAME" ]; then configs+=("'/dedicated/system_config/packetassembly_fullpacketsperframe' -v \"${TM_SYSTEM_PACKETASSEMBLY_FULLPACKETSPERFRAME}\""); fi 100 | if [ "$TM_SYSTEM_DELAYEDVISUALS_S2C_SENDINGRATE" ]; then configs+=("'/dedicated/system_config/delayedvisuals_s2c_sendingrate' -v \"${TM_SYSTEM_DELAYEDVISUALS_S2C_SENDINGRATE}\""); fi 101 | if [ "$TM_SYSTEM_TRUSTCLIENTSIMU_C2S_SENDINGRATE" ]; then configs+=("'/dedicated/system_config/trustclientsimu_c2s_sendingrate' -v \"${TM_SYSTEM_TRUSTCLIENTSIMU_C2S_SENDINGRATE}\""); fi 102 | if [ "$TM_SYSTEM_FORCE_IP_ADDRESS" ]; then configs+=("'/dedicated/system_config/force_ip_address' -v \"${TM_SYSTEM_FORCE_IP_ADDRESS}\""); fi 103 | if [ "$TM_SYSTEM_BIND_IP_ADDRESS" ]; then configs+=("'/dedicated/system_config/bind_ip_address' -v \"${TM_SYSTEM_BIND_IP_ADDRESS}\""); fi 104 | if [ "$TM_SYSTEM_USE_NAT_UPNP" ]; then configs+=("'/dedicated/system_config/use_nat_upnp' -v \"${TM_SYSTEM_USE_NAT_UPNP}\""); fi 105 | if [ "$TM_SYSTEM_XMLRPC_ALLOWREMOTE" ]; then configs+=("'/dedicated/system_config/xmlrpc_allowremote' -v \"${TM_SYSTEM_XMLRPC_ALLOWREMOTE}\""); fi 106 | if [ "$TM_SYSTEM_BLACKLIST_URL" ]; then configs+=("'/dedicated/system_config/blacklist_url' -v \"${TM_SYSTEM_BLACKLIST_URL}\""); fi 107 | if [ "$TM_SYSTEM_GUESTLIST_FILENAME" ]; then configs+=("'/dedicated/system_config/guestlist_filename' -v \"${TM_SYSTEM_GUESTLIST_FILENAME}\""); fi 108 | if [ "$TM_SYSTEM_BLACKLIST_FILENAME" ]; then configs+=("'/dedicated/system_config/blacklist_filename' -v \"${TM_SYSTEM_BLACKLIST_FILENAME}\""); fi 109 | if [ "$TM_SYSTEM_DISABLE_COHERENCE_CHECKS" ]; then configs+=("'/dedicated/system_config/disable_coherence_checks' -v \"${TM_SYSTEM_DISABLE_COHERENCE_CHECKS}\""); fi 110 | if [ "$TM_SYSTEM_DISABLE_REPLAY_RECORDING" ]; then configs+=("'/dedicated/system_config/disable_replay_recording' -v \"${TM_SYSTEM_DISABLE_REPLAY_RECORDING}\""); fi 111 | if [ "$TM_SYSTEM_SAVE_ALL_INDIVIDUAL_RUNS" ]; then configs+=("'/dedicated/system_config/save_all_individual_runs' -v \"${TM_SYSTEM_SAVE_ALL_INDIVIDUAL_RUNS}\""); fi 112 | if [ "$TM_SYSTEM_USE_PROXY" ]; then configs+=("'/dedicated/system_config/use_proxy' -v \"${TM_SYSTEM_USE_PROXY}\""); fi 113 | if [ "$TM_SYSTEM_PROXY_URL" ]; then configs+=("'/dedicated/system_config/proxy_url' -v \"${TM_SYSTEM_PROXY_URL}\""); fi 114 | 115 | # If TM_SERVER_NAME_OVERWRITE is set, overwrite the server name on each restart of the container. If unset, only set the name if empty in config file. 116 | hostname=$(uname -n) 117 | if [ "$TM_SERVER_NAME_OVERWRITE" = true ]; then 118 | if [ "$TM_SERVER_NAME" ]; then 119 | echo "SERVER_NAME will be removed soon. Please use TM_SERVER_NAME" 120 | echo "[-] Server name not present in config, using \"${TM_SERVER_NAME:-Docker TrackMania Server ${hostname}}\" as servername!" 121 | configs+=("'/dedicated/server_options/name' -v \"${TM_SERVER_NAME:-Docker TrackMania Server ${hostname}}\"") 122 | elif [ "$SERVER_NAME" ]; then 123 | echo "[-] Server name not present in config, using \"${SERVER_NAME:-Docker TrackMania Server ${hostname}}\" as servername!" 124 | configs+=("'/dedicated/server_options/name' -v \"${SERVER_NAME:-Docker TrackMania Server ${hostname}}\"") 125 | fi 126 | else 127 | if [ -z "$(xml sel -t -v '/dedicated/server_options/name' /server/UserData/Config/"${DC:-dedicated_cfg.txt}")" ]; then 128 | if [ "$TM_SERVER_NAME" ]; then 129 | echo "SERVER_NAME will be removed soon. Please use TM_SERVER_NAME" 130 | echo "[-] Server name not present in config, using \"${TM_SERVER_NAME:-Docker TrackMania Server ${hostname}}\" as servername!" 131 | configs+=("'/dedicated/server_options/name' -v \"${TM_SERVER_NAME:-Docker TrackMania Server ${hostname}}\"") 132 | elif [ "$SERVER_NAME" ]; then 133 | echo "[-] Server name not present in config, using \"${SERVER_NAME:-Docker TrackMania Server ${hostname}}\" as servername!" 134 | configs+=("'/dedicated/server_options/name' -v \"${SERVER_NAME:-Docker TrackMania Server ${hostname}}\"") 135 | fi 136 | fi 137 | fi 138 | 139 | # write config parameters into config file 140 | for (( i = 0; i < ${#configs[@]} ; i++ )); do 141 | eval xml ed -L -P -u "${configs[$i]}" /server/UserData/Config/"${DC:-dedicated_cfg.txt}" 142 | done 143 | 144 | # write voteratios 145 | if [ "$TM_SERVER_CALLVOTE_RATIOS" ]; then 146 | IFS=' ' read -r -a voteratio_array <<< "$TM_SERVER_CALLVOTE_RATIOS" 147 | 148 | for voteratio in "${voteratio_array[@]}"; do 149 | command="${voteratio%%:*}" 150 | ratio="${voteratio#*:}" 151 | 152 | xmlstarlet ed -L -P \ 153 | -u "/dedicated/server_options/callvote_ratios/voteratio[@command='${command}']/@ratio" -v "${ratio}" \ 154 | -s "/dedicated/server_options/callvote_ratios[not(voteratio[@command='${command}'])]" -t elem -n "voteratio" -v "" \ 155 | -i "/dedicated/server_options/callvote_ratios/voteratio[not(@command)]" -t attr -n "command" -v "${command}" \ 156 | -i "/dedicated/server_options/callvote_ratios/voteratio[@command='${command}' and not(@ratio)]" -t attr -n "ratio" -v "${ratio}" \ 157 | /server/UserData/Config/"${DC:-dedicated_cfg.txt}" 158 | done 159 | 160 | filter_expr="" 161 | for voteratio in "${voteratio_array[@]}"; do 162 | command="${voteratio%%:*}" 163 | filter_expr+="not(@command='${command}') and " 164 | done 165 | 166 | filter_expr="${filter_expr% and }" 167 | xmlstarlet ed -L -P \ 168 | -d "/dedicated/server_options/callvote_ratios/voteratio[${filter_expr}]" \ 169 | /server/UserData/Config/"${DC:-dedicated_cfg.txt}" 170 | fi 171 | 172 | # finally populate the MatchSettings file 173 | [ ! -f /server/UserData/Maps/MatchSettings/default.txt ] && cp /server/UserData/Maps/MatchSettings/example.txt /server/UserData/Maps/MatchSettings/default.txt 174 | else 175 | echo "[!] Config population disabled, ignoring most environment variables passed through Docker." 176 | fi 177 | fi 178 | 179 | # fire up the promehteus exporter 180 | if [ "$PROMETHEUS_ENABLE" = true ]; then 181 | echo "[+] Using Prometheus exporter." 182 | /usr/local/bin/trackmania_exporter & 183 | fi 184 | 185 | # fire up the actual TM server 186 | exec "$@" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Trackmania image 3 |

4 |

5 | 6 | docker stars 8 | 9 | docker pulls 11 | 12 | docker image version 14 | 15 | docker image size 17 | 18 | chat on Discord 20 | 21 | evo website 23 |

24 | 25 | This Docker image provides an easy and efficient way to deploy a Trackmania game server. It allows for quick setup, customizable configurations, and supports persistent storage to retain server data across restarts. With this image, you can effortlessly manage your Trackmania server using Docker’s containerization benefits. 26 | 27 | ## Table of Contents 28 | - [Table of Contents](#table-of-contents) 29 | - [How to use this image](#how-to-use-this-image) 30 | - [... with 'docker run'](#-with-docker-run) 31 | - [... with 'docker compose'](#-with-docker-compose) 32 | - [Environment Variables](#environment-variables) 33 | - [Features](#features) 34 | - [Prometheus Exporter](#prometheus-exporter) 35 | - [Contributing](#contributing) 36 | 37 | 38 | ## How to use this image 39 | ### ... with 'docker run' 40 | To start a TrackMania server with `docker run`: 41 | ```shell 42 | docker run \ 43 | -e TM_MASTERSERVER_LOGIN='YourMasterserverLogin' \ 44 | -e TM_MASTERSERVER_PASSWORD='YourMasterserverPassword' \ 45 | -p 2350:2350/tcp \ 46 | -p 2350:2350/udp \ 47 | #-p 5000:5000/tcp \ # Be careful opening XMLRPC! Only if you really need to. 48 | #-p 9000:9000/tcp \ # For the prometheus exporter. 49 | -v UserData:/server/UserData \ 50 | evoesports/trackmania:latest 51 | ``` 52 | 53 | ### ... with 'docker compose' 54 | Here is the `compose.yml`: 55 | ```yaml 56 | services: 57 | trackmania: 58 | image: evoesports/trackmania:latest 59 | ports: 60 | - 2350:2350/udp 61 | - 2350:2350/tcp 62 | #- 5000:5000/tcp # Be careful opening XMLRPC! Only if you know what you're doing. 63 | #- 9000:9000/tcp # For the prometheus exporter. 64 | environment: 65 | TM_MASTERSERVER_LOGIN: "YourMasterserverLogin" 66 | TM_MASTERSERVER_PASSWORD: "YourMasterserverPassword" 67 | volumes: 68 | - UserData:/server/UserData 69 | volumes: 70 | UserData: 71 | ``` 72 | In both cases, the server will launch and be bound to port 2350 TCP & UDP. Port 5000 (XMLRPC) & 9000 (Prometheus metrics) won't usually be forwarded to the host, because apps who need it (e.g. server controllers) are supposed to run in the same stack. 73 | You need to provide server credentials you can register [here](https://players.trackmania.com/server/dedicated), and put the login into the `TM_MASTERSERVER_LOGIN` variable, and the password into the `TM_MASTERSERVER_PASSWORD` variable. 74 | The server only needs one volume to store your user data (e.g. maps, configs), which is mounted to /server/UserData. You can also use bind mounts. 75 | 76 | ## Environment Variables 77 | Below is a list of all possible environment variables that can be set through Docker. 78 | | **Environment Variable** | **Description** | **Default Value**[^1] | 79 | |--------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------|--------------------------| 80 | | `TM_AUTHORIZATION_SUPERADMIN_PASSWORD` | Sets the password for the SuperAdmin access level, granting the highest level of permissions. | SuperAdmin | 81 | | `TM_AUTHORIZATION_ADMIN_PASSWORD` | Sets the password for the Admin access level, granting intermediate-level permissions. | Admin | 82 | | `TM_AUTHORIZATION_USER_PASSWORD` | Sets the password for the User access level, granting basic user permissions. | User | 83 | | `TM_MASTERSERVER_LOGIN` | The login name for the server account on the Trackmania master server (e.g., 'yourcoolserverlogin'). If not specified, the server starts in LAN mode. | | 84 | | `TM_MASTERSERVER_PASSWORD` | The password associated with the server's master server account, obtained from the Trackmania player page. | | 85 | | `TM_SERVER_NAME` | The display name of the server as seen by players. Only used if no server name is set in the server config file. | Docker TrackMania Server | 86 | | `TM_SERVER_COMMENT` | A description or comment about the server, shown to players in server listings. | | 87 | | `TM_SERVER_MAX_PLAYERS` | The maximum number of players that can join the server simultaneously. | 32 | 88 | | `TM_SERVER_PASSWORD` | Password required for players to join the server, if set. | | 89 | | `TM_SERVER_MAX_SPECTATORS` | The maximum number of spectators that can watch the server's matches. | 32 | 90 | | `TM_SERVER_PASSWORD_SPECTATOR` | Password required for spectators to join the server, if set. | | 91 | | `TM_SERVER_KEEP_PLAYER_SLOTS` | If `True`, keeps a player's slot and records/points when they switch to spectator mode. | False | 92 | | `TM_SERVER_CALLVOTE_TIMEOUT` | Duration in milliseconds before a callvote expires if no decision is reached. | 60000 | 93 | | `TM_SERVER_CALLVOTE_RATIO` | The minimum percentage of 'Yes' votes needed for a callvote to pass. | 0.5 | 94 | | `TM_SERVER_CALLVOTE_RATIOS` | Specify a list of ratios. For example `Ban:-1 Kick:-1` | | 95 | | `TM_SERVER_ALLOW_MAP_DOWNLOAD` | If `True`, allows players to download maps directly from the server. | False | 96 | | `TM_SERVER_AUTOSAVE_REPLAYS` | If `True`, the server will automatically save replays of each match. | False | 97 | | `TM_SERVER_AUTOSAVE_VALIDATION_REPLAYS` | If `True`, the server will automatically save replays used for map validation. | False | 98 | | `TM_SERVER_USE_CHANGING_VALIDATION_SEED` | If `True`, uses a dynamic seed for server-side validation checks to enhance security. | False | 99 | | `TM_SERVER_DISABLE_PROFILE_SKINS` | If `True`, disables the use of custom player skins, enforcing default skins for all players. | False | 100 | | `TM_SERVER_CLIENTINPUTS_MAXLATENCY` | Sets the maximum latency (in milliseconds) allowed for client inputs before the server simulates physics without new inputs. If the server doesn’t receive inputs within this time frame due to lag, it assumes the player's inputs remain unchanged or maintains their last known position. This setting directly impacts players with high ping, as exceeding this value can disrupt their gameplay or lead to inaccurate physics calculations. [^2] | 200 | 101 | | `TM_SYSTEM_CONNECTION_UPLOADRATE` | The maximum upload speed (in bytes per second) the server can utilize. | 102400 | 102 | | `TM_SYSTEM_CONNECTION_DOWNLOADRATE` | The maximum download speed (in bytes per second) the server can utilize. | 102400 | 103 | | `TM_SYSTEM_WORKERTHREADCOUNT` | Specifies the number of CPU threads that the server can use to perform its tasks.[^2] | 2 | 104 | | `TM_SYSTEM_PACKETASSEMBLY_MULTITHREAD` | If `True`, enables the server to assemble data packets using multiple threads for improved performance.[^2] | True | 105 | | `TM_SYSTEM_PACKETASSEMBLY_PACKETSPERFRAME` | This setting determines how many smaller "heartbeat" packets the server sends per frame, containing only essential network information and player inputs. These packets are less costly for the server to send but offer limited benefits in improving gameplay performance. The impact of this setting can vary depending on the server’s configuration and network conditions, so it’s recommended to experiment with different values to find the optimal balance for your specific situation.[^2] | 60 | 106 | | `TM_SYSTEM_PACKETASSEMBLY_FULLPACKETSPERFRAME` | This setting defines how many full data packets the server prepares and sends to clients per frame. Each packet includes game mode options, checkpoint times, and other relevant data. Preparing these packets is resource-intensive because the server must analyze all changes since the last packet and decide what information to send to each player. If the setting is too low, it can create a "virtual ping," as players might experience an artificial delay in receiving updates. Therefore, it’s essential to balance this setting to optimize server performance and client experience without overloading the server.[^2] | 30 | 107 | | `TM_SYSTEM_DELAYEDVISUALS_S2C_SENDINGRATE` | This setting determines the frequency at which the server sends player position data to all clients when CrudeExtrapolation is enabled. Adjusting this rate affects the visual display of opponents in the game. While a higher rate can make player movements appear smoother, it can also increase the server's bandwidth usage. If performance issues arise or optimization is needed, consider lowering this rate first, as the visual display of opponents is less critical than gameplay mechanics.[^2] | 32 | 108 | | `TM_SYSTEM_TRUSTCLIENTSIMU_C2S_SENDINGRATE` | This setting controls how often clients send their physics simulation results and inputs to the server. A higher rate ensures smoother physics calculations by reducing the wait time for player inputs, leading to a more responsive game experience. However, this comes at the cost of increased server bandwidth usage and higher client CPU demand due to packet compression. A balance must be struck to optimize both gameplay quality and resource usage, as beyond a certain point, further increasing the rate yields minimal gameplay benefits while significantly increasing resource consumption.[^2] | 64 | 109 | | `TM_SYSTEM_FORCE_IP_ADDRESS` | Forces the server to bind to a specific IP address and port (e.g., `127.0.0.1:2350`).[^3] | | 110 | | `TM_SYSTEM_BIND_IP_ADDRESS` | Specifies the IP address that the server should bind to for incoming connections. | | 111 | | `TM_SYSTEM_USE_NAT_UPNP` | If `True`, allows the server to use NAT traversal via UPnP for better connectivity in complex network setups. | | 112 | | `TM_SYSTEM_XMLRPC_ALLOWREMOTE` | If `True`, permits the server to accept external connections via XML-RPC for remote management and integration. | False[^4] | 113 | | `TM_SYSTEM_BLACKLIST_URL` | URL pointing to a remote blacklist of banned players, which the server uses to enforce bans. | | 114 | | `TM_SYSTEM_GUESTLIST_FILENAME` | The filename of the guest list, which contains users who are allowed special access or privileges on the server. | | 115 | | `TM_SYSTEM_BLACKLIST_FILENAME` | The filename of the blacklist, containing the names of players who are banned from the server. | | 116 | | `TM_SYSTEM_DISABLE_COHERENCE_CHECKS` | If `True`, disables the built-in anti-cheat measures, allowing more flexibility but less security. | False | 117 | | `TM_SYSTEM_DISABLE_REPLAY_RECORDING` | If `True`, disables the recording of replays, potentially improving performance but losing gameplay records. | False | 118 | | `TM_SYSTEM_SAVE_ALL_INDIVIDUAL_RUNS` | If `True`, saves the replay of each individual player's run, useful for detailed analysis and reviews. | False | 119 | | `TM_DEDICATED_CFG` | Specifies a custom server configuration file to use instead of the default settings. | dedicated_cfg.txt | 120 | | `TM_GAME_SETTINGS` | Specifies a custom match settings file to use, allowing detailed control over game rules and behavior. | default.txt | 121 | | `PROMETHEUS_ENABLE` | If `True`, enables the Prometheus exporter for monitoring the server, providing performance metrics and stats. | False | 122 | | `PROMETHEUS_PORT` | The network port on which the Prometheus exporter listens for requests, used for gathering server metrics. | 9000 | 123 | | `PROMETHEUS_SUPERADMIN_PASSWORD` | The SuperAdmin password required by the Prometheus exporter to authenticate and access the server metrics if the default was changed. | SuperAdmin | 124 | | `PROMETHEUS_INTERVAL` | The frequency, in seconds, at which the Prometheus exporter collects metrics from the Trackmania server. | 15 | 125 | [^1]: Default values are specific to this Docker image setup and may differ from those provided by the official TrackMania server from Ubisoft Nadeo. 126 | [^2]: More information to this can be gathered from the Trackmania Wiki page about the [Dedicated Config](https://wiki.trackmania.io/en/dedicated-server/Usage/DedicatedConfig). 127 | [^3]: If not set, the TrackMania server may report its internal Docker IP address to the master server, which can prevent external users from connecting to it. 128 | [^4]: Setting this to `True` allows only other Docker containers, such as server controllers like EvoSC or PyPlanet, to connect to the XML-RPC interface, not public external connections. 129 | 130 | ## Features 131 | ### Prometheus Exporter 132 | The image contains a small (~6MB) prometheus exporter. It can be enabled through the `PROMETHEUS_ENABLE` variable. The container will then expose metrics about the TrackMania server on port 9000. 133 | 134 | Example output: 135 | ``` 136 | # HELP trackmania_player_count Current player count by type. 137 | # TYPE trackmania_player_count gauge 138 | trackmania_player_count{type="online"} 8.0 139 | trackmania_player_count{type="spectating"} 0.0 140 | trackmania_player_count{type="driving"} 8.0 141 | # HELP trackmania_moderation_count Current players count being moderated by type. 142 | # TYPE trackmania_moderation_count gauge 143 | trackmania_moderation_count{type="banned"} 1.0 144 | trackmania_moderation_count{type="blacklisted"} 1.0 145 | trackmania_moderation_count{type="guestlisted"} 0.0 146 | trackmania_moderation_count{type="ignored"} 0.0 147 | # HELP trackmania_player_count_mean The mean value of the player count. 148 | # TYPE trackmania_player_count_mean gauge 149 | trackmania_player_count_mean 6.0 150 | # HELP trackmania_server_uptime Time since the TrackMania server has started in seconds. 151 | # TYPE trackmania_server_uptime gauge 152 | trackmania_server_uptime 459307.0 153 | # HELP trackmania_connection_count Total connections made to the TrackMania server. 154 | # TYPE trackmania_connection_count gauge 155 | trackmania_connection_count 1397.0 156 | # HELP trackmania_connection_time_mean The mean value of the connection time in ms. 157 | # TYPE trackmania_connection_time_mean gauge 158 | trackmania_connection_time_mean 2041.0 159 | # HELP trackmania_net_rate_recv Connection rate inbound in kbps. 160 | # TYPE trackmania_net_rate_recv gauge 161 | trackmania_net_rate_recv 137.0 162 | # HELP trackmania_net_rate_send Connection rate outbound in kbps. 163 | # TYPE trackmania_net_rate_send gauge 164 | trackmania_net_rate_send 76.0 165 | # HELP trackmania_maps_count Amount of maps the server currently has loaded. 166 | # TYPE trackmania_maps_count gauge 167 | trackmania_maps_count 99.0 168 | # HELP trackmania_player_max Max configured amount of players the server can hold. 169 | # TYPE trackmania_player_max gauge 170 | trackmania_player_max{type="players"} 150.0 171 | trackmania_player_max{type="spectators"} 32.0 172 | ``` 173 | ## Contributing 174 | If you have any questions, issues, bugs or suggestions, don't hesitate and open an [Issue](https://github.com/EvoTM/docker-trackmania/issues/new)! You can also join our [Discord](https://discord.gg/evotm) for questions. 175 | 176 | You may also help with development by creating a pull request. 177 | --------------------------------------------------------------------------------