원클릭으로
mtdedimod-deploy
Building, packaging, and deploying MTDediMod releases (server and client mods)
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Building, packaging, and deploying MTDediMod releases (server and client mods)
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Debugging crash dumps, PDB symbols, and UE4SS Lua GC issues in MTDediMod
SSH access to AMC servers and debugging amc-backend services
Managing ragenix-encrypted secrets in the AMC monorepo
| name | mtdedimod-deploy |
| description | Building, packaging, and deploying MTDediMod releases (server and client mods) |
MTDediMod is the UE4SS C++ mod for Motor Town. The repo produces two independent artifacts:
| Artifact | Tag prefix | Source | Proxy DLL | Distribution |
|---|---|---|---|---|
Server mod (MotorTownMods-package.zip) | server/v* | src/, Scripts/, shared/ | version.dll | Downloaded by game server at startup via curl |
Client mod (MotorTownClientMod-package.zip) | client/v* | ClientScripts/, shared/ | dwmapi.dll | Distributed to players (manual install / Discord) |
Each artifact has independent semver — a server hotfix does not bump the client version and vice versa.
[!IMPORTANT] Every deploy — test or production — must have a corresponding git commit and tag. Do not build from a dirty tree. Do not reuse version numbers with different code.
server/v0.34.0-rc5 # server mod releases
client/v0.1.0 # client mod releases
| Change | Server tag | Client tag |
|---|---|---|
Server Lua fix (Scripts/) | bump RC | no change |
Client Lua fix (ClientScripts/) | no change | bump RC or patch |
New C++ hook (src/) | bump minor | bump minor (if proxy DLL rebuilt) |
Shared Lua change (shared/) | bump both | bump both |
| Config-only tweak | bump RC on affected side | — |
Exploratory builds that live on exp/* branches and are never promoted to production. The pattern is:
server/v<version>-exp.<topic>.<N>
<version> — the target base version (what it would become if merged to main)<topic> — short slug identifying the experiment branch (e.g. cargo-rewrite, live-fuel)<N> — sequential build number within that experimentExamples:
server/v0.35.0-exp.cargo-rewrite.1 # branch: exp/cargo-rewrite
server/v0.35.0-exp.live-fuel.1 # branch: exp/live-fuel
server/v0.35.0-exp.live-fuel.2 # second iteration of same experiment
Rules:
exp/* branches, not mainmain and tag as -rc1 (fresh sequence)main, the second one bumps to 0.36.0-rc1git tag -d + git push origin :refs/tags/...)### Experimental subsectioncd MTDediMod
git tag -l 'server/*' --sort=-v:refname | head -5 # Server versions
git tag -l 'client/*' --sort=-v:refname | head -5 # Client versions
[!NOTE] Legacy tags (
v0.20.xthroughv0.34.0-rc4) predate the split and cover server-only releases.
Every release must update CHANGELOG.md before tagging.
The changelog uses Keep a Changelog format with separate ## Server and ## Client sections. Each version entry goes under its respective section:
## Server
### [server/v0.35.0-rc1] — 2026-04-10
#### Added
- New webhook endpoint for cargo events
#### Fixed
- Race condition in player teleport handler
---
## Client
### [client/v0.2.0] — 2026-04-10
#### Added
- New keybind for quick vehicle spawn
#### Changed
- Improved gamepad shortcut responsiveness
Use these categories (omit empty ones):
## Server / ## Client heading### [tag] — date block### [server/v0.35.0-rc1] — 2026-04-10YYYY-MM-DDAll changes must be committed before building. The tree must be clean.
cd MTDediMod
git status # Must show "nothing to commit, working tree clean"
git tag -l 'server/*' --sort=-v:refname | head -5
# Example: server/v0.34.0-rc4 → Next: server/v0.34.0-rc5
git tag server/v0.34.0-rc5
From the MTDediMod directory, check if C++ sources changed since the previous server tag. Only run the build if they did — Lua-only changes skip this step.
[!IMPORTANT] You do not need to run
.#configurebefore every build. Configure is only required once (whenbuild-cross/does not yet exist). Ifbuild-cross/already exists, run.#builddirectly.
cd MTDediMod
PREV_TAG=$(git tag -l 'server/*' --sort=-v:refname | sed -n '2p')
git diff --name-only "$PREV_TAG" HEAD | grep -q '^src/' && nix run .#build || echo "No C++ changes, skipping build"
[!WARNING] If you see
warning: Git tree is dirty, stop. Go back to step 1. Building from a dirty tree means the zip won't match the tag.If you see
CMake Error: add_subdirectory given source "src" which is not an existing directory, you ran the command from the parentamc-server/directory instead ofMTDediMod/. The wrapperCMakeLists.txtandbuild-cross/must be created insideMTDediMod/, not the repo root.cd MTDediModfirst.
cd MTDediMod
nix run .#package
# Output: MotorTownMods-package.zip
This automatically:
version.dll, UE4SS.dll, mod DLL, Lua scripts, shared depsfetchzip derivationscp MotorTownMods-package.zip root@amc-peripheral:/var/lib/mod-releases/MotorTownMods_server-v0.34.0-rc5.zip
Mod zips are served from /var/lib/mod-releases/ on amc-peripheral (via nginx at aseanmotorclub.com/releases/). Both the main server and test server download mods at startup via curl from this URL.
Update mod-versions.nix (in the repo root) for the target server only:
{
main = "server-v0.34.0-rc5"; # Main server (asean-mt-server)
staging = "server-v0.33.0-rc7"; # Staging/test server (amc-peripheral)
}
Or use the deploy-mod script which handles this automatically:
deploy-mod --server test --version server-v0.34.0-rc5 # Updates staging key
deploy-mod --server main --version server-v0.34.0-rc5 # Updates main key
[!NOTE]
mod-versions.nixis read byflake.nixviaimport ./mod-versions.nix. Each key maps to one server —mainforasean-mt-server,stagingforamc-peripheral. Thesedcommand targets the specific key, so there's no risk of updating the wrong server.
No hash calculation needed — the mod is downloaded at runtime via curl during service preStart.
# From amc-server root (inside nix develop or with direnv)
deploy root@amc-peripheral # Test server
deploy root@asean-mt-server # Main server (when promoting)
Then purge the old cache and restart:
# 1. Remove cached zip AND extracted directory for the OLD version
ssh steam@amc-peripheral "\
rm -f /var/lib/motortown-server/.mod-cache/MotorTownMods_server-v0.34.0-rc4.zip && \
rm -rf /var/lib/motortown-server/.mod-cache/extracted-server-v0.34.0-rc4"
# 2. Remove the version marker so preStart does a full re-install
# (mods.nix skips extraction if .installed-mod-version matches modVersion)
ssh steam@amc-peripheral "\
rm -f /var/lib/motortown-server/MotorTown/Binaries/Win64/ue4ss/.installed-mod-version"
# 3. Restart the service (this triggers preStart → download → extract → launch)
# The preStart script removes old files before copying, so root-owned files
# from manual installs or hot-reload are handled automatically.
ssh root@amc-peripheral "systemctl restart motortown-server"
# 4. Wait ~20s for the game server to boot, then check UE4SS logs
ssh steam@amc-peripheral "tail -n 50 /var/lib/motortown-server/MotorTown/Binaries/Win64/ue4ss/UE4SS.log | grep MotorTownMods"
Important:
mods.nixuses a version marker file ($UE4SS_DIR/.installed-mod-version) to skip extraction when the version hasn't changed. If you re-package the samemodVersion(e.g. during RC iteration), you must remove this marker (step 2) to force a full re-install. OtherwisepreStartwill print "already installed, skipping" and use the stale files.
Ownership-safe installs: The preStart script removes old
ue4ss/,version.dll,Engine.ini, and.pakfiles before copying new ones. This means root-owned files from hot-reload debugging, manualscp, orunzipare automatically cleaned up — no manualchownneeded.
Success indicators:
INFO: Webserver listening to host 0.0.0.0 on port XXXXXINFO: Mod loadedERROR lines about missing modulesAfter verifying the deploy is healthy:
cd MTDediMod
git push origin server/v0.34.0-rc5
When a test RC is validated and ready for the main server:
main key in mod-versions.nix to match the tested RC versiondeploy root@asean-mt-serverssh steam@asean-mt-server "\
rm -f /var/lib/motortown-server/.mod-cache/MotorTownMods_server-v0.34.0-rc4.zip && \
rm -rf /var/lib/motortown-server/.mod-cache/extracted-server-v0.34.0-rc4 && \
rm -f /var/lib/motortown-server/MotorTown/Binaries/Win64/ue4ss/.installed-mod-version"
ssh root@asean-mt-server "systemctl restart motortown-server"
For exploratory builds on exp/* branches that are not destined for production.
cd MTDediMod
git checkout -b exp/cargo-rewrite
git add -A && git commit
git tag server/v0.35.0-exp.cargo-rewrite.1
cd MTDediMod
PREV_TAG=$(git tag -l 'server/*' --sort=-v:refname | sed -n '2p')
git diff --name-only "$PREV_TAG" HEAD | grep -q '^src/' && nix run .#build || echo "No C++ changes, skipping build"
nix run .#package
scp MotorTownMods-package.zip root@amc-peripheral:/var/lib/mod-releases/MotorTownMods_server-v0.35.0-exp.cargo-rewrite.1.zip
Update mod-versions.nix (in the repo root) — change the staging key:
staging = "server-v0.35.0-exp.cargo-rewrite.1";
deploy root@amc-peripheral
# Then purge cache and restart as usual (see Server Mod Release Workflow step 7)
git push -u origin exp/cargo-rewrite
git push origin server/v0.35.0-exp.cargo-rewrite.1
Subsequent builds increment the sequence number:
cd MTDediMod
git tag server/v0.35.0-exp.cargo-rewrite.2
PREV_TAG=$(git tag -l 'server/*' --sort=-v:refname | sed -n '2p')
git diff --name-only "$PREV_TAG" HEAD | grep -q '^src/' && nix run .#build || echo "No C++ changes, skipping build"
nix run .#package
scp MotorTownMods-package.zip root@amc-peripheral:/var/lib/mod-releases/MotorTownMods_server-v0.35.0-exp.cargo-rewrite.2.zip
# Update mod-versions.nix (staging key), deploy, restart
When the experiment is ready:
main-rc1 on main: git tag server/v0.35.0-rc1Same as server — tree must be clean.
git tag -l 'client/*' --sort=-v:refname | head -5
# Example: client/v0.1.0 → Next: client/v0.2.0
git tag client/v0.2.0
From the MTDediMod directory, check if C++ sources changed since the previous client tag. Only run the build if they did.
cd MTDediMod
PREV_TAG=$(git tag -l 'client/*' --sort=-v:refname | sed -n '2p')
git diff --name-only "$PREV_TAG" HEAD | grep -q '^src/' && nix run .#build-client || echo "No C++ changes, skipping build"
cd MTDediMod
nix run .#package-client
# Output: MotorTownClientMod-package.zip
This automatically:
dwmapi.dll (proxy), UE4SS.dll, Lua scripts from ClientScripts/, shared depsUE4SS_Signatures for Motor Town clientUpload to the releases server for player download:
scp MotorTownClientMod-package.zip \
root@amc-peripheral:/var/lib/mod-releases/MotorTownClientMod_client-v0.2.0.zip
The zip is then available at https://www.aseanmotorclub.com/releases/MotorTownClientMod_client-v0.2.0.zip.
Share the download link with players (e.g. via Discord announcement).
cd MTDediMod
git push origin client/v0.2.0
When a commit touches both server and client code (e.g. changes in shared/):
git tag server/v0.35.0-rc1 && git tag client/v0.2.0MTDediMod directory:
cd MTDediMod
PREV_SERVER=$(git tag -l 'server/*' --sort=-v:refname | sed -n '2p')
git diff --name-only "$PREV_SERVER" HEAD | grep -q '^src/' && nix run .#build || echo "No C++ changes, skipping server build"
nix run .#package
PREV_CLIENT=$(git tag -l 'client/*' --sort=-v:refname | sed -n '2p')
git diff --name-only "$PREV_CLIENT" HEAD | grep -q '^src/' && nix run .#build-client || echo "No C++ changes, skipping client build"
nix run .#package-client
git push origin server/v0.35.0-rc1 client/v0.2.0[!CAUTION] Hot reload is for live debugging only. If the fix is good, commit → tag → deploy properly before calling it done. Untracked hot reloads create ghost versions that can't be reproduced.
For Lua-only changes (no C++ DLL rebuild), skip the full deploy cycle. SCP scripts directly to the installed directory and hit the reload endpoint:
# 1. SCP changed scripts (test server on amc-peripheral)
# Use steam@ to create files with correct ownership — avoids permission issues on restart
scp MTDediMod/Scripts/*.lua \
steam@amc-peripheral:/var/lib/motortown-server/MotorTown/Binaries/Win64/ue4ss/Mods/MotorTownMods/Scripts/
# 2. Hot-reload (connection resets — that's expected, mod restarts in ~5s)
ssh root@amc-peripheral "curl -s -X POST http://localhost:5001/mods/reload"
[!WARNING] For the main server on
asean-mt-server, usesteam@asean-mt-serverfor file operations androot@asean-mt-serverfor systemctl/curl. Never call the main server's mod port directly from your local machine — always SSH to the host first.
[!NOTE] Always use
steam@(notroot@) forscpto the state directory. Files created asroot:rootcan't be overwritten by thesteam-owned service on restart. The preStart script'srm-before-cphandles stale root-owned files as a safety net, butsteam@prevents the problem at the source.
Players stay connected. The mod webserver restarts and re-registers all handlers. No service restart, no cache purge, no NixOS deploy needed.
Client mods support UE4SS hot reload via Ctrl+R in-game (enabled in client UE4SS settings). For testing:
Ctrl+R in-game to reloadAfter hot-reload debugging: If the changes are keepers, go back and do a proper versioned release.
The .github/workflows/nix-release.yml workflow triggers on tag pushes matching v*. This will need updating to support the new server/v* and client/v* prefixes:
on:
push:
tags:
- 'server/v*'
- 'client/v*'
Currently, CI only builds the server mod. A separate job or conditional matrix will be needed if client builds should also be automated via CI.
mods.nix generates an install-mt-mods script that runs in systemd preStart:
modVersion to a download URL:
v0.2* → GitHub releases (github.com/ASEAN-Motor-Club/MTDediMod/releases/)aseanmotorclub.com/releases/"dev" → Skips download, uses bind mount from /var/lib/mtdedimod-dev/ue4ss$STATE_DIRECTORY/.mod-cache/| Server | Host | State Directory |
|---|---|---|
| Test server | amc-peripheral | /var/lib/motortown-server/ |
| Main server | asean-mt-server | /var/lib/motortown-server/ |
Common subdirectories (under state directory):
| Path | Description |
|---|---|
MotorTown/Binaries/Win64/ue4ss/ | Installed UE4SS + mod files |
MotorTown/Binaries/Win64/ue4ss/UE4SS.log | UE4SS log file |
.mod-cache/ | Cached mod downloads |
The test server runs directly on amc-peripheral (bare-metal, no container).
| Port | Service | Access Pattern |
|---|---|---|
| 5000 | C++ management server (events) | ssh root@amc-peripheral "curl -s http://localhost:5000/events" |
| 5001 | Lua webserver (HTTP endpoints) | ssh root@amc-peripheral "curl -s http://localhost:5001/..." |
# From your local machine (SSH to host):
ssh root@amc-peripheral "curl -s http://localhost:5000/events" | jq '.events[]'
# For the Lua webserver:
ssh root@amc-peripheral "curl -s http://localhost:5001/players"
ssh root@amc-peripheral "grep 'MotorTownMods' /var/lib/motortown-server/MotorTown/Binaries/Win64/ue4ss/UE4SS.log | tail -20"
External game content mods (.pak files from mod.io or elsewhere) are served from the same releases directory as MTDediMod zips. The game server downloads them at startup via curl.
scp <mod_name>.pak root@amc-peripheral:/var/lib/mod-releases/mods/
Files are served at https://www.aseanmotorclub.com/releases/mods/<mod_name>.pak.
In flake.nix, under the relevant server's enableExternalMods:
enableExternalMods = {
MajasDetailWorks7_17_P = true;
# ...
};
The server caches downloaded paks in $STATE_DIRECTORY/.mod-cache/paks/. When replacing a pak file on the hosting server, purge the cache and restart:
ssh steam@amc-peripheral "rm -rf /var/lib/motortown-server/.mod-cache/paks/"
ssh steam@asean-mt-server "rm -rf /var/lib/motortown-server/.mod-cache/paks/"
# Then restart the relevant server (via root@ for systemctl)
The Motor Town game server exposes a native HTTP API (port 8081, password configured via HostWebAPIServerPassword in DedicatedServerConfig). All endpoints accept a password query parameter.
Use /delivery/sites to inspect delivery point inventory, production, and cargo types. This is useful for verifying new recipes, mod pak changes, or storage configs.
# All delivery sites (verbose)
ssh root@amc-peripheral 'curl -s "http://localhost:8081/delivery/sites?password=" | python3 -m json.tool'
# Find a specific site by name
ssh root@amc-peripheral 'curl -s "http://localhost:8081/delivery/sites?password=" | python3 -c "
import json, sys
data = json.load(sys.stdin)
for site in data.get(\"data\", {}).values():
name = site.get(\"name\", \"\")
if \"copper\" in name.lower():
print(f\"=== {name} ===\")
print(json.dumps(site, indent=2))
"'
# Check output inventory (production) at a site
ssh root@amc-peripheral 'curl -s "http://localhost:8081/delivery/sites?password=" | python3 -c "
import json, sys
data = json.load(sys.stdin)
for site in data.get(\"data\", {}).values():
output = site.get(\"OutputInventory\", {})
if output:
name = site.get(\"name\", \"\")
for slot in output.values():
cargo = slot.get(\"cargo\", {})
print(f\"{name}: {slot.get(\\\"amount\\\", 0)}x {cargo.get(\\\"name\\\", \\\"?\\\")} ({cargo.get(\\\"cargo_key\\\", \\\"?\\\")})\")
"'
# Check input inventory (requirements) at a site
ssh root@amc-peripheral 'curl -s "http://localhost:8081/delivery/sites?password=" | python3 -c "
import json, sys
data = json.load(sys.stdin)
for site in data.get(\"data\", {}).values():
inp = site.get(\"InputInventory\", {})
if inp:
name = site.get(\"name\", \"\")
for slot in inp.values():
cargo = slot.get(\"cargo\", {})
print(f\"{name}: needs {slot.get(\\\"amount\\\", 0)}x {cargo.get(\\\"name\\\", \\\"?\\\")} ({cargo.get(\\\"cargo_key\\\", \\\"?\\\")})\")
"'
Key fields in /delivery/sites response:
| Field | Description |
|---|---|
name | Delivery point display name (e.g. "Copper Mine -G") |
guid | Unique identifier for the site |
Deliveries | Active delivery missions from this site |
InputInventory | Items the site consumes (production inputs) |
OutputInventory | Items the site produces (with current amount and capacity) |
OutputInventory[].cargo.cargo_key | Internal cargo key (e.g. "Moonshine", "CopperOre") |
OutputInventory[].amount | Current stock of that cargo type |
OutputInventory[].cargo.spawn_probability | Likely a production chance (0–10 scale) |
# Player list (native API)
ssh root@amc-peripheral 'curl -s "http://localhost:8081/player/list?password=" | python3 -m json.tool'
# Mod API (port 5001) - delivery points with more detail
ssh root@amc-peripheral 'curl -s "http://localhost:5001/delivery/points" | python3 -m json.tool'
# Player vehicles and cargo
ssh root@amc-peripheral 'curl -s "http://localhost:5001/players" | python3 -m json.tool'
For the main server on asean-mt-server, replace localhost:8081 with localhost:8080 and localhost:5001 with localhost:5001 (same ports, different host).
| Version | UE4SS | Notes |
|---|---|---|
v12 | v4 | Legacy event server mod, includes bcrypt.dll, ssl.dll |
v0.20.x | v5 | Stable releases on GitHub |
v0.30.0 | v5 | Last GitHub release |
v0.31.0+ | v5 | Hosted on aseanmotorclub.com (legacy v* tags) |
server/v0.34.0-rc5 | v5 | First tag under new server/ prefix scheme |
client/v0.1.0 | v5 | First tag under new client/ prefix scheme |
server/v0.35.0-exp.cargo-rewrite.1 | v5 | First experimental tag (example) |