一键导入
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) |