hl2dm-motd is a Node.js/TypeScript daemon for Half-Life 2: Deathmatch server integration.
It sits between:
- the SRCDS plugin over WebSocket,
- MySQL persistence,
- the in-game MOTD browser UI,
- MOTD API endpoints used by the UI.
The service handles MOTD session verification, player settings sync, stats/ranks, admin actions, ban workflows, achievements, GeoIP lookups, demo metadata, and demo uploads.
- Node.js 18+
- pnpm 10+
- MySQL 8+ or compatible
- An SRCDS plugin configured to connect to this daemon
Install dependencies:
pnpm installCreate a local environment file:
cp .env.example .envEdit .env for your server. Do not commit .env.
Run the development server:
pnpm --filter @hl2dm/motd-server run devBuild the server package:
pnpm --filter @hl2dm/motd-server run buildStart the built server:
pnpm --filter @hl2dm/motd-server run startThese must be set for a real deployment:
DB_HOST— MySQL host. Use127.0.0.1instead oflocalhostif your MySQL server only listens on IPv4.DB_PORT— MySQL port, usually3306.DB_USER— MySQL user.DB_PASS— MySQL password.DB_NAME— MySQL database name.SRCDS_WS_SECRET— shared secret used by the SRCDS WebSocket client. This must match the SRCDS-sidewebsocket_secret.STEAM_API_KEY— Steam Web API key used for Steam profile lookups.
Common optional settings:
NODE_ENV— set toproductionfor production deployments.SERVER_PORT— HTTP listen port. Default:4000.PUBLIC_URL— public base URL for generated links.TRUST_PROXY— set to1when the service is behind a trusted reverse proxy. Default:1.VERIFY_TOKEN_TIMEOUT_MS— timeout for MOTD token verification through SRCDS. Default:8000.MAP_TRANSITION_RECONNECT_GRACE_SECONDS— reconnect grace window for map changes. Default:600.WEAPON_POINTS_JSON— weapon scoring configuration.AZURE_TRANSLATOR_KEY,AZURE_TRANSLATOR_REGION,AZURE_TRANSLATOR_ENDPOINT— Azure Translator configuration.WS_DEBUG,WS_DEBUG_PAYLOADS,WS_DEBUG_LARGE_MESSAGE_BYTES— WebSocket diagnostics.SRCDS_WS_MAX_PAYLOAD_BYTES— maximum accepted SRCDS WebSocket message size. Default:65536.
Treat these as secrets or production safety switches:
SRCDS_WS_SECRET— must be long, private, and match the SRCDS plugin configuration.DEMO_UPLOAD_TOKEN— required for demo upload and demo metadata endpoints. If this is missing, demo upload requests fail closed.MOTD_BROWSER_DEBUG_TOKEN— required whenMOTD_BROWSER_DEBUG=1; must be at least 32 characters.MOTD_BROWSER_DEBUG— must stay0in production.MOTD_BROWSER_DEBUG_ALLOW_REMOTE— must stay0unless you intentionally accept the risk of remote browser-debug access.DB_PASS,STEAM_API_KEY,AZURE_TRANSLATOR_KEY— private credentials.
.env.example must contain examples only. Do not put real secrets or reusable default tokens in it.
GeoIP support uses MaxMind GeoLite2 database files through the maxmind package.
Real .mmdb files are not included in this repository and must not be committed. Download GeoLite2 City and ASN databases through your own MaxMind account, place them in a private local path, and point the service at those files:
GEOIP_ENABLED=1
GEOIP_CITY_MMDB=/var/geoip/GeoLite2-City.mmdb
GEOIP_ASN_MMDB=/var/geoip/GeoLite2-ASN.mmdbFor development without GeoIP, leave:
GEOIP_ENABLED=0The repository ignores *.mmdb and *.mmdb.gz files. Do not add MaxMind license keys, download URLs, or database files to the public repo.
Demo files are stored under:
DEMO_STORAGE_ROOT=/var/www/hl2dm-game.comDemo upload and metadata endpoints require bearer-token authentication:
Authorization: Bearer <DEMO_UPLOAD_TOKEN>Required behavior:
- If
DEMO_UPLOAD_TOKENis unset, upload and metadata requests are rejected. - If the request has no bearer token, the request is rejected.
- If the bearer token is wrong, the request is rejected.
- If the bearer token is correct, the request can continue subject to file name, file type, and size checks.
Demo upload filenames are restricted to the existing intended demo formats:
.dem.dem.bz2
Demo upload size is limited by:
DEMO_UPLOAD_MAX_BYTES=536870912The default is 536870912 bytes, which is 512 MiB. Oversized binary uploads are rejected and the upload stream is stopped. Usually will not be a problem for demos.
Browser debug mode exists for local MOTD UI development only.
Keep this disabled in production:
MOTD_BROWSER_DEBUG=0If you enable it locally, you must set a long random token:
MOTD_BROWSER_DEBUG=1
MOTD_BROWSER_DEBUG_TOKEN=<32-or-more-random-characters>
MOTD_BROWSER_DEBUG_ALLOW_REMOTE=0Debug requests must provide the token as either:
x-motd-debug-token: <token>or:
/motd/debug?debug_token=<token>Safety rules enforced by the server:
MOTD_BROWSER_DEBUG=0disables browser debug routes.MOTD_BROWSER_DEBUG=1with a missing or short token fails closed.- Browser debug requests are localhost-only by default.
- Remote browser debug requests require
MOTD_BROWSER_DEBUG_ALLOW_REMOTE=1and the configured token. - Browser debug sessions cannot resolve admin capabilities.
Never enable browser debug mode on a public deployment.
From the SRCDS plugin side, ensure these values align with the daemon:
motd_server_id↔DEFAULT_SERVER_IDor theserver_idpassed in the MOTD URL.websocket_secret↔SRCDS_WS_SECRET.websocket_addresspoints tows://<daemon-host>:<port>/ws/srcds.
The daemon tracks active SRCDS connections by server_id and verifies MOTD sessions through the connected SRCDS instance.
After configuring .env, run the available checks:
pnpm --filter @hl2dm/motd-server run buildIf you add tests later, run them before publishing. This snapshot does not include a dedicated test script for the demo upload route.
Manual demo upload checks:
DEMO_UPLOAD_TOKENunset + upload request => rejected.DEMO_UPLOAD_TOKENset + missing bearer token => rejected.DEMO_UPLOAD_TOKENset + wrong bearer token => rejected.DEMO_UPLOAD_TOKENset + correct bearer token => accepted, subject to.dem/.dem.bz2and size checks.- Upload larger than
DEMO_UPLOAD_MAX_BYTES=> rejected.
Manual browser-debug checks:
MOTD_BROWSER_DEBUG=0=>/motd/debugreturns disabled.MOTD_BROWSER_DEBUG=1with no token => rejected.MOTD_BROWSER_DEBUG=1with a short token => rejected.MOTD_BROWSER_DEBUG=1with a 32+ character token from localhost => accepted only when the request supplies the token.- Browser-debug sessions cannot use admin capabilities.
If you clone this project without forking, then before open-sourcing:
- Remove committed MaxMind
.mmdbfiles from Git history, or publish from a fresh clean repository. - Confirm no
.mmdbor.mmdb.gzfiles exist in the public repository. - Confirm
.envis not committed. - Confirm
.env.examplecontains no real secrets or reusable default tokens. - Confirm
DEMO_UPLOAD_TOKENis set if demo upload endpoints are exposed. - Confirm
DEMO_UPLOAD_MAX_BYTESis configured for your deployment. - Confirm
MOTD_BROWSER_DEBUG=0in production. - Confirm
MOTD_BROWSER_DEBUG_TOKENis blank in production, or unused because debug mode is disabled. - Run a dependency audit locally.
- Build/typecheck the project.
Important: removing .mmdb files from the working tree is not enough if the public repository keeps this Git history. Clean the history with a tool such as git filter-repo or publish from a fresh repository that never contained the database files.
- Confirm
websocket_addressis reachable from the game server host. - Confirm
SRCDS_WS_SECRETmatches the SRCDSwebsocket_secret. - Check daemon logs for WebSocket auth rejection.
- Ensure the MOTD URL includes the current
remoteIdfrom the latest WebSocket hello. - Ensure
server_idmatches an active SRCDS connection.
This means the MySQL client attempted IPv6 loopback while MySQL likely listens only on IPv4.
Use:
DB_HOST=127.0.0.1or configure MySQL to listen on IPv6 too.
Nothing special.