UE Linux Server 交叉编译 + 远程部署
平台约定:本机主力环境是 Linux(Arch)——命令以 bash 为先、可直接执行;Windows 专属步骤一律收进「Windows(PowerShell)」小节,不在 Linux 段落里混用。
两条路径,先选一条(原文没有断言「只能在 Windows 上交叉编译」;第 1 节的整套 LINUX_MULTIARCH_ROOT 机制是「Windows 主机 → Linux 目标」这条交叉编译路径专用的):
- A. Linux 本机原生构建(当前主力):本机 Arch + 源码引擎直接
Build.sh <Project>Server Linux <Config>,不需要LINUX_MULTIARCH_ROOT、不需要交叉工具链、不需要 Windows;产物就是本机 Linux 二进制,第 4 节起的远程部署照旧。见各节 Linux(bash)。 - B. Windows 交叉编译(历史路径,保留):Windows 源码引擎构建 Linux Server 并部署到远程(
<RemoteIP>实证,2026-08-05)。见各节 Windows(PowerShell)。
占位符约定:<Project> = 项目名(如 <Project>.uproject、<Project>Server 目标名),<project> = 小写项目名(路径与 PM2 进程名用),<RemoteIP> = 远程服务器 IP,<user> = 远程非 root 运行用户。引擎根用 $UE_ROOT 占位:本机源码检出真实路径 /home/sx/projects/unrealengine/ue5.8(UE 5.8;旧兼容入口 /home/sx/UnrealEngine 已于 2026-09-23 删除;即便重建这类入口,脚本里的 dirname $0/../../.. 也会算错引擎根,请用真实路径)。
工具链版本以 "$UE_ROOT/Engine/Config/Linux/Linux_SDK.json" 的 MainVersion 为准(本机当前 v26_clang-20.1.8-rockylinux8)——下文出现的 v25_clang-18.1.0-rockylinux8 是当时 Windows 侧的记录,别照抄。
1. Linux SDK(工具链)
Windows(PowerShell)— 交叉编译路径
- 环境变量名必须是
LINUX_MULTIARCH_ROOT(UBT 只认这个;LINUX_MULTIARCH_TOOLS无效)。指向工具链根(如C:\UnrealToolchains\v25_clang-18.1.0-rockylinux8),UBT 期望{root}/bin/clang++.exe(x86_64-unknown-linux-gnu 子目录;.exe后缀只在 Windows 主机上成立)。 - User 级变量对已开终端无效——必须在同一命令内设好再跑 Build.bat:
$env:LINUX_MULTIARCH_ROOT = 'C:\UnrealToolchains\v25_clang-18.1.0-rockylinux8' & 'C:\Program Files\Epic Games\UE_5.7\Engine\Build\BatchFiles\Build.bat' <Project>Server Linux DebugGame -Project=<...> -WaitMutex -FromMSBuild - 版本查
Engine/Config/Linux/Linux_SDK.json(如 v25_clang-18.1.0-rockylinux8)。 - AutoSDK 不在 GitDeps 清单——Setup.bat 交互下载(Epic 凭据)或手动下载解压。
- 工具链缺失症状:
Engine/Extras/ThirdPartyNotUE/SDKs目录为空(本机实测被清理过)→ 构建报Platform Linux is not a valid platform to build/Unable to find valid SDK(s) for Linux: Required=v25_clang-18.1.0-rockylinux8。 - 下载安装:CDN 不可用 curl 内联(被工具拦截),用 eval/Bun fetch 写文件:
https://cdn.unrealengine.com/CrossToolchain_Linux/v25_clang-18.1.0-rockylinux8.exe(946MB)。静默安装Start-Process -FilePath 'xxx.exe' -ArgumentList '/S' -Wait(7z 不识别该 PE 安装器;默认装到C:/UnrealToolchains/v25_clang-18.1.0-rockylinux8/)。 - 验证构建:
Build.bat <Project>Server Linux DebugGame -Project=... -WaitMutex -FromMSBuild(~2 分钟,clang 18.1.0 加载日志确认)。
Linux(bash)— 本机原生构建
本机原生构建不必设 LINUX_MULTIARCH_ROOT:UBT 未读到该变量时回退到 in-tree SDK(源码实证 Engine/Source/Programs/UnrealBuildTool/Platform/Linux/LinuxPlatformSDK.cs::GetSDKLocation()),路径为
$UE_ROOT/Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/<MainVersion>/x86_64-unknown-linux-gnu
<MainVersion> 取自 Engine/Config/Linux/Linux_SDK.json。Linux 主机上 clang 可执行名是 bin/clang++(.exe 后缀只对 Windows 主机——同文件 IsValidClangPath())。
export UE_ROOT=/home/sx/projects/unrealengine/ue5.8 # 真实路径(/home/sx/UnrealEngine 旧入口已删除)
cat "$UE_ROOT/Engine/Config/Linux/Linux_SDK.json" # 看 MainVersion(本机当前 v26_clang-20.1.8-rockylinux8)
"$UE_ROOT/Engine/Build/BatchFiles/Linux/SetupToolchain.sh" # in-tree SDK 缺失时:按 Linux_SDK.json 下载解包到上面的路径
- 工具链下载(Linux 侧等价物):
SetupToolchain.sh取https://cdn.unrealengine.com/Toolchain_Linux/native-linux-<MainVersion>.tar.gz解到Engine/Extras/ThirdPartyNotUE/SDKs/HostLinux/Linux_x64/;脚本自身需要curl或wget,缓存目录基于GIT_DIR(脚本内自述)。 - 对照原文的「下载安装」:那条
CrossToolchain_Linux/*.exe(946MB)+Start-Process /S是 Windows 交叉工具链的自解压 PE 安装器 —— Linux 上无对应方案:Linux 不跑 PE 安装器;可替代做法 = 上面的SetupToolchain.sh(原生 Linux 工具链 tar.gz 通道)。 - AutoSDK:
Setup.bat(Windows 批处理)在 Linux 侧不适用;源码版引擎用SetupToolchain.sh,或手动下载同一个 tar.gz 解到同一路径。 - 工具链缺失症状:与 Windows 侧同款——
Engine/Extras/ThirdPartyNotUE/SDKs为空时构建报Platform Linux is not a valid platform to build/Unable to find valid SDK(s) for Linux: Required=<MainVersion>。 - 验证构建:见第 3 节「Linux(bash)」。
2. BuildConfiguration.xml
Windows(PowerShell)
%APPDATA%\Unreal Engine\UnrealBuildTool\BuildConfiguration.xml,根元素 <Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">(<BuildConfiguration> 报错;缺 xmlns 报 namespace 错):
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
<bAllowUBALocalExecutor>false</bAllowUBALocalExecutor>
</Configuration>
Linux(bash)
路径不同(UBT 源码 Configuration/Xml/XmlConfig.cs::InputFiles 实证:这些位置经 .NET Environment.SpecialFolder 解析,没有平台分支):
- 推荐(源码版引擎):
"$UE_ROOT/Engine/Saved/UnrealBuildTool/BuildConfiguration.xml"—— 源码里标了「缺失时自动创建」,最稳。 - 逐用户:
~/.config/Unreal Engine/UnrealBuildTool/BuildConfiguration.xml(.NET 在 Unix 上把ApplicationData映射到$XDG_CONFIG_HOME,默认~/.config) - 系统级:
/usr/share/Unreal Engine/UnrealBuildTool/BuildConfiguration.xml
export UE_ROOT=/home/sx/projects/unrealengine/ue5.8
CFG="$UE_ROOT/Engine/Saved/UnrealBuildTool/BuildConfiguration.xml"
mkdir -p "$(dirname "$CFG")"
cat > "$CFG" <<'EOF'
<Configuration xmlns="https://www.unrealengine.com/BuildConfiguration">
<bAllowUBALocalExecutor>false</bAllowUBALocalExecutor>
</Configuration>
EOF
待验证:~/.config/... 与 /usr/share/... 两条是按 .NET SpecialFolder 映射推出来的,未在 Linux 实跑确认;不确定就用 Engine/Saved/... 那条。同理,bAllowUBALocalExecutor=false 的成因(KB5058499/UbaDetours)是 Windows 侧已知问题,Linux 上是否同样需要关 UBA 待验证——非必要先别关。
3. 构建
Linux(bash)
export UE_ROOT=/home/sx/projects/unrealengine/ue5.8
"$UE_ROOT/Engine/Build/BatchFiles/Linux/Build.sh" <Project>Server Linux Development \
-Project="$PWD/<Project>.uproject" -WaitMutex -FromMSBuild
本机原生构建,无 LINUX_MULTIARCH_ROOT。大构建量级参考:约 30 分钟(979 目标)。
Windows(PowerShell)
$env:LINUX_MULTIARCH_ROOT = 'C:\UnrealToolchains\v25_clang-18.1.0-rockylinux8'
& 'C:\Program Files\Epic Games\UE_5.7\Engine\Build\BatchFiles\Build.bat' `
<Project>Server Linux Development -Project=<...> -WaitMutex -FromMSBuild
约 30 分钟(979 目标)。
4. 远程部署(unrealcli deploy remote)
平台说明:从本节起,命令全部跑在远程 Linux 服务器上(ssh <user>@<RemoteIP> 之后的 bash),本机是 Windows 时同样如此——这些步骤本来就只在 Linux 侧执行,没有 PowerShell 版本。本机(Arch)可直接复制执行。
- 配置:
config/main.toml[deploy] production_ip / production_user(不是[remote]/ssh_user;键名必须精确,RemoteAppRoot 自动 = /opt/<project>)。 - 后端 net10:远程需
apt-get install -y aspnetcore-runtime-10.0(否则启动报 Framework 10.0.0 缺失)。 - 旧进程占端口:
- 后端旧 dotnet 占 5021(示例端口)→
ps aux | grep <Project>Server.dll找到 kill。 - 旧 UE 服务器占 7777 UDP(示例端口)→ kill 后新服务器才能绑定。
- 后端旧 dotnet 占 5021(示例端口)→
- scp 大目录失败:改用
tar -czf压缩 → scp → 远程解压(tar -xzf+chown -R <user>:<user>)。 - UE 服务器拒绝 root:PM2(root)跑 UE 服务器报 "Refusing to run with the root privileges"。启动脚本必须:
sudo 直接执行脚本报 command not found——必须 /bin/sh 显式。# run_<project>.sh exec sudo -u <user> /bin/sh /opt/<project>/ue-server/<Project>Server.sh -port=7777 -log -unattended -NoSound -NullRHI - 验证:
ss -ulnp | grep 7777(UDP——ss -tlnp看不到);5021(TCP)用ss -tlnp | grep 5021(Linux,远程/本机皆可)——Windows 侧的等价物是Test-NetConnection <RemoteIP> -Port 5021。 - 远程服务器内部 HTTP 指向旧 IP 会降级 mock——不影响端口连通。
5. 服务器版本验证
pgrep -af <Project>Server+ls -la /proc/PID/exe确认指向<Project>Server-Linux-DebugGame(新上传时间戳)。- 服务器二进制旧 = 客户端所有修复不生效(服务器端行为旧:冷却、bOrient、RPC 等)——多轮"客户端修了没用"先查服务器版本;客户端与服务器版本不一致会 NetChecksumMismatch 断线。
- 服务器日志:
/opt/<project>/ue-server/<Project>/Saved/Logs/<Project>.log(pm2 out log 二进制混杂,直接读工程 Saved/Logs)。
6. 双 PM2 实例重启风暴
症状:pm2 ls 显示 <project>-api errored、restarts 200+;日志 Socket.Bind Address already in use(5021 被占)。
根因:双 PM2 实例(root + 应用用户各一个 daemon)——应用用户 daemon(/home/<user>/.pm2,进程名含 "God Daemon")的 dotnet 占 5021 且自动重启,root pm2 新进程绑定失败 → 循环。
修复(按序):
pm2 kill # root pm2
kill -9 <daemon pid> # 查:pgrep -af 'God Daemon'
pkill -9 -f <Project>Server.dll # 杀全部残留 dotnet
ss -tlnp | grep 5021 # 确认空
cd /opt/<project>/backend && pm2 start run_api.sh --name <project>-api
cd /opt/<project>/ue-server && pm2 start run_<project>.sh --name <project>-ue-server
pm2 save # 固化(root dump)
陷阱:
pm2 kill(root)会连带杀 ue-server——重启后必须重新 pm2 save- 后端验证:业务 API 返回 401 = 正常(服务活着、未认证)
7. PM2 fork 模式崩循环(后端)
症状:pm2 status 显示 errored / restarts(↺)持续增长,但端口仍有孤儿进程监听(ss -tlnp 可见 5021 被占)。
根因:ecosystem 用 script: dotnet, args: <Project>Server.dll(fork 模式)——PM2 只杀包装进程,dotnet 子进程残留占端口 → 重启绑定失败(Failed to bind 0.0.0.0:5021: address already in use)→ 判崩重启 → 死循环。pkill 后再 startOrRestart 仍复现(exec 残留竞态)。
修复 1:exec 包装脚本。写 /opt/<project>/backend/run_api.sh:
#!/bin/bash
exec dotnet /opt/<project>/backend/<Project>Server.dll --urls http://0.0.0.0:5021
ecosystem 改 script=run_api.sh——exec 让 dotnet 成为 PM2 直接子进程,可被正确回收(注意文件是 python3 改 json 时确保 script 字段指向 shell 脚本)。
修复 2(仍不稳则弃 PM2):pm2 delete <project>-api + pkill -f <Project>Server.dll + 手动 nohup:
cd /opt/<project>/backend && env ASPNETCORE_ENVIRONMENT=Production JWT_SECRET='<secret>' ConnectionStrings__DefaultConnection='Host=127.0.0.1;Database=<db>;Username=postgres;Password=<pw>' nohup dotnet <Project>Server.dll --urls http://0.0.0.0:5021 >> /opt/<project>/backend/api.log 2>&1 &
- ssh 会话里 nohup 重定向可能丢——fd 指向 socket;确认
ls -la /proc/<pid>/fd/1 - 环境变量(JWT_SECRET / ConnectionStrings__DefaultConnection / ASPNETCORE_ENVIRONMENT)从
/proc/<pid>/environ或原 ecosystem 拿 - 走 PM2 时环境变量放 ecosystem 的 env 节;孤儿进程清理
pkill -f '<Project>Server.dll'→npx pm2 startOrRestart /opt/<project>/ecosystem.config.json
(unrealcli-pm2-pattern 讲 UnrealCli 代码内 ecosystem 生成与 INI 持久化补丁;本节是远程服务器上 PM2 fork 模式缺陷的运维修复,互补不重复。)
8. UE 代码 LoadObject 不触发 cook
代码里 LoadObject/ConstructorHelpers 引用的资产不会自动 cook 进包(cook 按资产引用收集)。修复:DefaultGame.ini 加 +DirectoriesToAlwaysCook=(Path="/Game/VRTemplate")。
验证:Saved/StagedBuilds/Android_ASTC/Manifest_UFSFiles_Android.txt grep 资产名。
9. 远程 DB 缺表缺列(接口 500 通用错误)
症状:接口返回 {"title": "通用错误"...} 500 或 401;后端日志无异常(PM2 out/error 被绑定失败刷屏或孤儿进程 stdout 丢失)。
根因:远程 DB 与代码迁移不一致——后端启动 db.Database.Migrate() 在 try/catch(失败仅 Warning),远程表是手动建的(无 __EFMigrationsHistory)→ 新实体列/表缺失 → EF 查询全列失败 → 通用 500。
诊断流程:
- 设备日志定位:
adb shell grep -E '<HttpClientTag>' /sdcard/Android/data/<包名>/files/UnrealGame/<Project>/<Project>/Saved/Logs/<Project>.log | tail——看 URL+状态码(500=DB 缺列/表,401=未认证/token 问题) - 本机复现对比(可选):本机后端带 env(JWT_SECRET + ConnectionStrings__DefaultConnection)启动,本机 200 而远程 500 = 远程 DB 差异
- 对比实体 vs 远程表:本地
<ProjectRoot>/<ServerDir>/Models/*.cs;远程ssh <user>@<RemoteIP> "PGPASSWORD=<pw> psql -h 127.0.0.1 -U <user> -d <db> -c '\d \"<TableName>\"'"(表名必须加引号,裸名转小写误报不存在);表清单\dt(注意大小写) - 补齐:
ALTER TABLE "X" ADD COLUMN IF NOT EXISTS "Col" type NULL;/CREATE TABLE IF NOT EXISTS "X" (...)
已实证示例(表名仅举例,2026-08-05 补过):Players 缺 Level integer NOT NULL DEFAULT 1;Friendships 缺 IsCloseFriend boolean NOT NULL DEFAULT false;EnemyRelations 缺 IsBlocked boolean NOT NULL DEFAULT false;MasterDiscipleRelations 缺 CooldownEndTime timestamp NULL;ChatMessages 整表缺失(Id uuid PK / PlayerId / DisplayName / Message / SentAt)。
10. curl 测试脚本绕拦截
HTTP 工具拦截内联 curl——用 write 写脚本 + scp 上传远程 /tmp/ 再 ssh bash 执行,输出到文件再 cat。
脚本模式:auth 拿 token → 带认证头(如 X-Auth-Token)请求目标接口 → 看 200/500。
11. 平台配置(ini 覆盖)
- 服务器(Linux)连本地后端:
Config/Linux/LinuxGame.ini的[/Script/<Project>Social.SocialHttpClient] BaseURL=127.0.0.1:<port>(平台覆盖,只影响 Linux 服务器;客户端不受影响) - 客户端(Windows/Android)连远程:
Config/DefaultGame.ini的 BaseURL=<RemoteIP>:<port> + RemoteServerURL=<RemoteIP>:<gameport> + bUseLocal=false(客户端默认配置直连远程) - 改 ini 后必须重新构建客户端(配置编译进 APK/包)
Scan to join WeChat group