← 返回 Skill 列表
extension
分类: 开发与工程API Key 暂未确认

UE Linux Server 交叉编译 + 远程部署

UE5 源码引擎交叉编译 Linux Server + unrealcli 部署:Linux v25 工具链、LINUX_MULTIARCH_ROOT、BuildConfiguration.xml/UBA、PM2 fork 崩循环、DB 缺表缺列、curl scp 绕拦截、ini 覆盖

person作者: Sxgx123hubOpenAPI

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 后新服务器才能绑定。
  • scp 大目录失败:改用 tar -czf 压缩 → scp → 远程解压(tar -xzf + chown -R <user>:<user>)。
  • UE 服务器拒绝 root:PM2(root)跑 UE 服务器报 "Refusing to run with the root privileges"。启动脚本必须:
    # run_<project>.sh
    exec sudo -u <user> /bin/sh /opt/<project>/ue-server/<Project>Server.sh -port=7777 -log -unattended -NoSound -NullRHI
    
    sudo 直接执行脚本报 command not found——必须 /bin/sh 显式。
  • 验证: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。

诊断流程:

  1. 设备日志定位:adb shell grep -E '<HttpClientTag>' /sdcard/Android/data/<包名>/files/UnrealGame/<Project>/<Project>/Saved/Logs/<Project>.log | tail——看 URL+状态码(500=DB 缺列/表,401=未认证/token 问题)
  2. 本机复现对比(可选):本机后端带 env(JWT_SECRET + ConnectionStrings__DefaultConnection)启动,本机 200 而远程 500 = 远程 DB 差异
  3. 对比实体 vs 远程表:本地 <ProjectRoot>/<ServerDir>/Models/*.cs;远程 ssh <user>@<RemoteIP> "PGPASSWORD=<pw> psql -h 127.0.0.1 -U <user> -d <db> -c '\d \"<TableName>\"'"(表名必须加引号,裸名转小写误报不存在);表清单 \dt(注意大小写)
  4. 补齐: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/包)