From bd2df967921e6cf7a7e00a9165a61ba6b9358146 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Apr 2026 07:56:09 +0000
Subject: [PATCH 1/8] Add Linux support for LocalMultiplayerAgent (containers
and processes)
- Remove Linux container mode restriction in MultiplayerSettingsValidator
- Set GameServerEnvironment to Linux on native Linux OS in Program.cs
- Allow Linux process mode on native Linux in validator
- Force docker pull on Linux for container mode
- Fix hardcoded Windows path separator in ProcessRunner
- Create setup_linux.sh setup script
- Create sample configs for Linux containers and processes
- Create linux.md documentation
- Update README.md with Linux platform support
- Update tests to reflect Linux now supports both modes
Agent-Logs-Url: https://github.com/PlayFab/MpsAgent/sessions/ef46bb0d-1884-40a4-a3be-8f778e0967be
Co-authored-by: dgkanatsios <8256138+dgkanatsios@users.noreply.github.com>
---
.../ConfigurationTests.cs | 87 ++++++----
.../Config/MultiplayerSettingsValidator.cs | 11 +-
.../MultiplayerServerManager.cs | 2 +-
...rSettingsLinuxContainersOnLinuxSample.json | 40 +++++
...ayerSettingsLinuxProcessOnLinuxSample.json | 40 +++++
LocalMultiplayerAgent/Program.cs | 4 +-
LocalMultiplayerAgent/setup_linux.sh | 42 +++++
README.md | 10 +-
VmAgent.Core/Interfaces/ProcessRunner.cs | 2 +-
linux.md | 161 ++++++++++++++++++
10 files changed, 350 insertions(+), 49 deletions(-)
create mode 100644 LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnLinuxSample.json
create mode 100644 LocalMultiplayerAgent/MultiplayerSettingsLinuxProcessOnLinuxSample.json
create mode 100644 LocalMultiplayerAgent/setup_linux.sh
create mode 100644 linux.md
diff --git a/LocalMultiplayerAgent.UnitTest/ConfigurationTests.cs b/LocalMultiplayerAgent.UnitTest/ConfigurationTests.cs
index bf355e3..611f93b 100644
--- a/LocalMultiplayerAgent.UnitTest/ConfigurationTests.cs
+++ b/LocalMultiplayerAgent.UnitTest/ConfigurationTests.cs
@@ -171,12 +171,6 @@ public void SessionHostStartWithProcess()
[TestCategory("BVT")]
public void SessionHostStartWithContainer()
{
- // Container mode is not supported on Linux OS, so this test only applies on Windows/MacOS
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- Assert.Inconclusive("Container mode validation is not supported on Linux OS");
- }
-
dynamic config = GetValidConfig();
config.RunContainer = true;
@@ -329,12 +323,6 @@ public void StartGameCommandThatDoesNotContainMountPathShouldFail(string startGa
[DataRow("C:\\Assets\\GameServer.bat")]
public void StartGameCommandThatContainsMountPathShouldSucceed(string startGameCommand)
{
- // Container mode is not supported on Linux OS, so this test only applies on Windows/MacOS
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
- {
- Assert.Inconclusive("Container mode validation is not supported on Linux OS");
- }
-
dynamic config = GetValidConfig();
config.RunContainer = true;
config.AssetDetails[0].MountPath = "C:\\Assets";
@@ -348,12 +336,25 @@ public void StartGameCommandThatContainsMountPathShouldSucceed(string startGameC
///
/// When Globals.GameServerEnvironment is Linux and RunContainer is false,
- /// validation should fail because Linux game servers require container mode.
+ /// validation should fail on non-Linux OS because Linux game servers require container mode there.
+ /// On native Linux, process mode is allowed.
///
[TestMethod]
[TestCategory("BVT")]
public void LinuxGameServerEnvironmentWithoutContainerFails()
{
+ // On native Linux OS, process mode is allowed for Linux game servers
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ {
+ Assert.Inconclusive("On native Linux OS, Linux process mode is valid");
+ }
+
+ // On MacOS, process mode is rejected by a different check
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ {
+ Assert.Inconclusive("Process mode validation is not supported on MacOS");
+ }
+
var previousEnv = Globals.GameServerEnvironment;
try
{
@@ -406,19 +407,11 @@ public void WindowsProcessModeIsValid()
///
/// When Globals.GameServerEnvironment is Windows and RunContainer is true (container mode),
/// validation should succeed — this is the standard Windows container scenario.
- /// This test is skipped on Linux OS because the validator rejects RunContainer=true on Linux.
///
[TestMethod]
[TestCategory("BVT")]
public void WindowsContainerModeIsValid()
{
- if (System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
- {
- // On Linux OS, RunContainer=true is rejected by the validator (container mode not yet supported on Linux)
- // This test validates Windows container mode which only applies on Windows/MacOS
- return;
- }
-
var previousEnv = Globals.GameServerEnvironment;
try
{
@@ -458,13 +451,7 @@ public void LinuxGameServerEnvironmentAllowsEmptyStartGameCommand()
MultiplayerSettings settings = JsonConvert.DeserializeObject(config.ToString());
settings.SetDefaultsIfNotSpecified();
- // On non-Linux OS, RunContainer=true with Linux env should work
- // On Linux OS, RunContainer=true is rejected (container mode not supported on Linux OS)
- // We only test the StartGameCommand validation logic here
- if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
- {
- new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeTrue();
- }
+ new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeTrue();
}
finally
{
@@ -518,11 +505,7 @@ public void LinuxGameServerAssetsAreOptional()
MultiplayerSettings settings = JsonConvert.DeserializeObject(config.ToString());
settings.SetDefaultsIfNotSpecified();
- // On Linux OS, RunContainer=true is rejected. On Windows/MacOS, this should be valid.
- if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
- {
- new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeTrue();
- }
+ new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeTrue();
}
finally
{
@@ -557,11 +540,12 @@ public void WindowsGameServerAssetsAreRequired()
}
///
- /// On Linux OS, RunContainer=true should be rejected (container mode not yet supported on Linux).
+ /// On Linux OS, both RunContainer=true (container mode) and RunContainer=false (process mode)
+ /// should be accepted.
///
[TestMethod]
[TestCategory("BVT")]
- public void LinuxOsRejectsContainerMode()
+ public void LinuxOsAcceptsContainerMode()
{
if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
{
@@ -581,7 +565,38 @@ public void LinuxOsRejectsContainerMode()
MultiplayerSettings settings = JsonConvert.DeserializeObject(config.ToString());
settings.SetDefaultsIfNotSpecified();
- new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeFalse();
+ new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeTrue();
+ }
+ finally
+ {
+ Globals.GameServerEnvironment = previousEnv;
+ }
+ }
+
+ ///
+ /// On Linux OS, process mode (RunContainer=false) should also be accepted.
+ ///
+ [TestMethod]
+ [TestCategory("BVT")]
+ public void LinuxOsAcceptsProcessMode()
+ {
+ if (!System.Runtime.InteropServices.RuntimeInformation.IsOSPlatform(System.Runtime.InteropServices.OSPlatform.Linux))
+ {
+ // This test only applies when running on Linux OS
+ return;
+ }
+
+ var previousEnv = Globals.GameServerEnvironment;
+ try
+ {
+ Globals.GameServerEnvironment = GameServerEnvironment.Linux;
+
+ dynamic config = GetValidConfig();
+ config.RunContainer = false;
+
+ MultiplayerSettings settings = JsonConvert.DeserializeObject(config.ToString());
+ settings.SetDefaultsIfNotSpecified();
+ new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeTrue();
}
finally
{
diff --git a/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs b/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
index 4624ef9..7fe235a 100644
--- a/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
+++ b/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
@@ -121,15 +121,10 @@ public bool IsValid(bool createBuild = false)
}
}
- if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && _settings.RunContainer)
+ if (Globals.GameServerEnvironment == GameServerEnvironment.Linux && !_settings.RunContainer
+ && !RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
- Console.WriteLine("Running LocalMultiplayerAgent as container mode is not yet supported on Linux. Please set RunContainer to false in MultiplayerSettings.json");
- return false;
- }
-
- if (Globals.GameServerEnvironment == GameServerEnvironment.Linux && !_settings.RunContainer)
- {
- Console.WriteLine("The specified settings are invalid. Using Linux Game Servers requires running in a container.");
+ Console.WriteLine("The specified settings are invalid. Using Linux Game Servers requires running in a container (except on native Linux where process mode is also supported).");
return false;
}
diff --git a/LocalMultiplayerAgent/MultiplayerServerManager.cs b/LocalMultiplayerAgent/MultiplayerServerManager.cs
index b0130d2..febaddb 100644
--- a/LocalMultiplayerAgent/MultiplayerServerManager.cs
+++ b/LocalMultiplayerAgent/MultiplayerServerManager.cs
@@ -67,7 +67,7 @@ public async Task CreateAndStartContainerWaitForExit(SessionHostsStartInfo start
// In another case, we might have built the image locally but tagged with a fake registry name (e.g. myacr.io/mygame:0.1),
// Then make sure to change the value to false if you want to use the image from local.
if (Globals.GameServerEnvironment == GameServerEnvironment.Windows || Globals.Settings.ForcePullFromAcrOnLinuxContainersOnWindows
- || RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ || RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
await sessionHostRunner.RetrieveResources(startParameters);
}
diff --git a/LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnLinuxSample.json b/LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnLinuxSample.json
new file mode 100644
index 0000000..464453b
--- /dev/null
+++ b/LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnLinuxSample.json
@@ -0,0 +1,40 @@
+{
+ "RunContainer": true,
+ "OutputFolder": "",
+ "NumHeartBeatsForActivateResponse": 10,
+ "NumHeartBeatsForTerminateResponse": 60,
+ "TitleId": "",
+ "BuildId": "00000000-0000-0000-0000-000000000000",
+ "Region": "WestUs",
+ "AgentListeningPort": 56001,
+ "ContainerStartParameters": {
+ "ImageDetails": {
+ "Registry": "mydockerregistry.io",
+ "ImageName": "mygame",
+ "ImageTag": "0.1",
+ "Username": "",
+ "Password": ""
+ }
+ },
+ "DeploymentMetadata": {
+ "Environment": "LOCAL",
+ "FeaturesEnabled": "List,Of,Features,Enabled"
+ },
+ "PortMappingsList": [
+ [
+ {
+ "NodePort": 56100,
+ "GamePort": {
+ "Name": "gameport",
+ "Number": 7777,
+ "Protocol": "TCP"
+ }
+ }
+ ]
+ ],
+ "SessionConfig": {
+ "SessionId": "ba67d671-512a-4e7d-a38c-2329ce181946",
+ "SessionCookie": null,
+ "InitialPlayers": [ "Player1", "Player2" ]
+ }
+}
diff --git a/LocalMultiplayerAgent/MultiplayerSettingsLinuxProcessOnLinuxSample.json b/LocalMultiplayerAgent/MultiplayerSettingsLinuxProcessOnLinuxSample.json
new file mode 100644
index 0000000..3541fe4
--- /dev/null
+++ b/LocalMultiplayerAgent/MultiplayerSettingsLinuxProcessOnLinuxSample.json
@@ -0,0 +1,40 @@
+{
+ "RunContainer": false,
+ "OutputFolder": "",
+ "NumHeartBeatsForActivateResponse": 10,
+ "NumHeartBeatsForTerminateResponse": 60,
+ "TitleId": "",
+ "BuildId": "00000000-0000-0000-0000-000000000000",
+ "Region": "WestUs",
+ "AgentListeningPort": 56001,
+ "AssetDetails": [
+ {
+ "MountPath": "",
+ "LocalFilePath": ""
+ }
+ ],
+ "DeploymentMetadata": {
+ "Environment": "LOCAL",
+ "FeaturesEnabled": "List,Of,Features,Enabled"
+ },
+ "PortMappingsList": [
+ [
+ {
+ "NodePort": 56100,
+ "GamePort": {
+ "Name": "gameport",
+ "Number": 7777,
+ "Protocol": "TCP"
+ }
+ }
+ ]
+ ],
+ "ProcessStartParameters": {
+ "StartGameCommand": ""
+ },
+ "SessionConfig": {
+ "SessionId": "ba67d671-512a-4e7d-a38c-2329ce181946",
+ "SessionCookie": null,
+ "InitialPlayers": [ "Player1", "Player2" ]
+ }
+}
diff --git a/LocalMultiplayerAgent/Program.cs b/LocalMultiplayerAgent/Program.cs
index d690596..6030d2f 100644
--- a/LocalMultiplayerAgent/Program.cs
+++ b/LocalMultiplayerAgent/Program.cs
@@ -34,8 +34,8 @@ public static async Task Main(string[] args)
Console.WriteLine($"Check this page for debugging tips: {debuggingUrl}");
// lcow stands for Linux Containers On Windows => https://docs.microsoft.com/en-us/virtualization/windowscontainers/deploy-containers/linux-containers
- // On MacOS, we only support Linux containers, so -lcow is not needed
- if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+ // On MacOS and Linux, we only support Linux game servers, so -lcow is not needed
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
Globals.GameServerEnvironment = GameServerEnvironment.Linux;
}
diff --git a/LocalMultiplayerAgent/setup_linux.sh b/LocalMultiplayerAgent/setup_linux.sh
new file mode 100644
index 0000000..4fa157e
--- /dev/null
+++ b/LocalMultiplayerAgent/setup_linux.sh
@@ -0,0 +1,42 @@
+#!/bin/bash
+
+# Copyright (c) Microsoft Corporation.
+# Licensed under the MIT License.
+
+# This script sets up your Linux machine to run the PlayFab Multiplayer Server LocalMultiplayerAgent
+# Prerequisites: Docker must be installed and running (only needed for container mode)
+
+set -euo pipefail
+
+AGENT_PORT=${1:-56001}
+
+echo "Setting up LocalMultiplayerAgent for Linux..."
+
+# Check if Docker is installed and running (only required for container mode)
+if command -v docker &> /dev/null; then
+ if docker info &> /dev/null 2>&1; then
+ # Create the playfab Docker network if it doesn't exist
+ EXISTING_NETWORK=$(docker network ls --filter name=^playfab$ --format "{{.Name}}")
+ if [ -z "$EXISTING_NETWORK" ]; then
+ echo "Creating 'playfab' Docker network..."
+ docker network create playfab --driver bridge
+ echo "Docker network 'playfab' created successfully."
+ else
+ echo "Docker network 'playfab' already exists."
+ fi
+ else
+ echo "Warning: Docker is not running. Docker is required for container mode."
+ echo "If you plan to use process mode only, you can ignore this warning."
+ fi
+else
+ echo "Warning: Docker is not installed. Docker is required for container mode."
+ echo "If you plan to use process mode only, you can ignore this warning."
+fi
+
+echo ""
+echo "Setup complete! You can now run LocalMultiplayerAgent."
+echo "Agent will listen on port $AGENT_PORT."
+echo ""
+echo "Make sure to update MultiplayerSettings.json with your game server details."
+echo "You can use MultiplayerSettingsLinuxContainersOnLinuxSample.json (container mode)"
+echo "or MultiplayerSettingsLinuxProcessOnLinuxSample.json (process mode) as a reference."
diff --git a/README.md b/README.md
index 316b81c..4bde6e4 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ This repository contains source code for the following projects:
- LocalMultiplayerAgent
-An executable that mimics PlayFab Multiplayer Servers (MPS) operations to aid in local debugging. It runs on **Windows** and **MacOS (Apple Silicon) (beta)**. See [PlayFab documentation - LocalMultiplayerAgent Overview](https://docs.microsoft.com/en-us/gaming/playfab/features/multiplayer/servers/localmultiplayeragent/local-multiplayer-agent-overview) to learn how to debug your game servers using LocalMultiplayerAgent. If you want to develop for Linux Containers on Windows, check out [this document](lcow.md). If you want to run on MacOS (Apple Silicon), check out [this document](macos.md).
+An executable that mimics PlayFab Multiplayer Servers (MPS) operations to aid in local debugging. It runs on **Windows**, **MacOS (Apple Silicon) (beta)**, and **Linux (x64)**. See [PlayFab documentation - LocalMultiplayerAgent Overview](https://docs.microsoft.com/en-us/gaming/playfab/features/multiplayer/servers/localmultiplayeragent/local-multiplayer-agent-overview) to learn how to debug your game servers using LocalMultiplayerAgent. If you want to develop for Linux Containers on Windows, check out [this document](lcow.md). If you want to run on MacOS (Apple Silicon), check out [this document](macos.md). If you want to run on Linux, check out [this document](linux.md).
> This repository replaces and deprecates the project in [this GitHub repo](https://github.com/PlayFab/LocalMultiplayerAgent). The executable produced by that project was called `MockAgent`. This has been renamed to `LocalMultiplayerAgent` for consistency.
@@ -45,6 +45,14 @@ cd ./MpsAgent/LocalMultiplayerAgent
dotnet publish --runtime osx-arm64 -c Release -o LocalMultiplayerAgentPublishFolder -p:PublishSingleFile=true --self-contained true
```
+**Linux x64** (Terminal):
+
+```bash
+git clone https://github.com/PlayFab/MpsAgent.git
+cd ./MpsAgent/LocalMultiplayerAgent
+dotnet publish --runtime linux-x64 -c Release -o LocalMultiplayerAgentPublishFolder -p:PublishSingleFile=true --self-contained true
+```
+
## Contributing
We are more than happy to accept external contributions! If you want to propose a small code change to LocalMultiplayerAgent, feel free to open a Pull Request directly. If you plan to do a bigger change to LocalMultiplayerAgent, it would be better if you open an issue describing your proposed design in order to get feedback from project maintainers.
diff --git a/VmAgent.Core/Interfaces/ProcessRunner.cs b/VmAgent.Core/Interfaces/ProcessRunner.cs
index e1dcd01..77998cf 100644
--- a/VmAgent.Core/Interfaces/ProcessRunner.cs
+++ b/VmAgent.Core/Interfaces/ProcessRunner.cs
@@ -126,7 +126,7 @@ public void CreateStartGameExceptionLogs(ProcessOutputLogger processOutputLogger
// Replacing the mount path is for back compat when we didn't have first class support for process based servers
// (and were based off of the parameters for containers).
string executablePath = sessionHostsStartInfo.AssetDetails[0].MountPath?.Length > 0
- ? parts[0].Replace(sessionHostsStartInfo.AssetDetails[0].MountPath, $"{localPathForAsset0}\\")
+ ? parts[0].Replace(sessionHostsStartInfo.AssetDetails[0].MountPath, $"{localPathForAsset0}{Path.DirectorySeparatorChar}")
: Path.Combine(localPathForAsset0, parts[0]);
string args = parts.Length > 1 ? string.Join(' ', parts.Skip(1)) : string.Empty;
return (executablePath, args);
diff --git a/linux.md b/linux.md
new file mode 100644
index 0000000..314f3f8
--- /dev/null
+++ b/linux.md
@@ -0,0 +1,161 @@
+# Linux Support
+
+You can use LocalMultiplayerAgent on Linux to debug your game servers using either **container mode** (Linux containers) or **process mode** (bare processes).
+
+## Container Mode (Linux Containers on Linux)
+
+Container mode runs your game server inside a Docker container natively on Linux.
+
+### Prerequisites
+
+- [Install Docker Engine](https://docs.docker.com/engine/install/) on your Linux machine
+- Make sure Docker is running (`sudo systemctl start docker`)
+- Your game server image can be published on a container registry or can be locally built
+
+### Setup
+
+1. Download the latest version of LocalMultiplayerAgent for Linux (linux-x64) from the [Releases](https://github.com/PlayFab/MpsAgent/releases/) page on GitHub
+
+2. Run the `setup_linux.sh` script which will create a Docker network called "playfab":
+
+```bash
+chmod +x setup_linux.sh
+./setup_linux.sh
+```
+
+3. Configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `MultiplayerSettingsLinuxContainersOnLinuxSample.json`:
+
+```json
+{
+ "RunContainer": true,
+ "OutputFolder": "",
+ "NumHeartBeatsForActivateResponse": 10,
+ "NumHeartBeatsForTerminateResponse": 60,
+ "TitleId": "",
+ "BuildId": "00000000-0000-0000-0000-000000000000",
+ "Region": "WestUs",
+ "AgentListeningPort": 56001,
+ "ContainerStartParameters": {
+ "ImageDetails": {
+ "Registry": "mydockerregistry.io",
+ "ImageName": "mygame",
+ "ImageTag": "0.1",
+ "Username": "",
+ "Password": ""
+ }
+ },
+ "PortMappingsList": [
+ [
+ {
+ "NodePort": 56100,
+ "GamePort": {
+ "Name": "gameport",
+ "Number": 7777,
+ "Protocol": "TCP"
+ }
+ }
+ ]
+ ],
+ "DeploymentMetadata": {
+ "Environment": "LOCAL",
+ "FeaturesEnabled": "List,Of,Features,Enabled"
+ },
+ "SessionConfig": {
+ "SessionId": "ba67d671-512a-4e7d-a38c-2329ce181946",
+ "SessionCookie": null,
+ "InitialPlayers": [ "Player1", "Player2" ]
+ }
+}
+```
+
+> Notes:
+> 1. Set `RunContainer` to true for container mode.
+> 2. Modify `ImageDetails` with your game server Docker image details. The image may be built locally (using [docker build](https://docs.docker.com/engine/reference/commandline/build/) command) or be hosted in a remote container registry.
+> 3. `StartGameCommand` and `AssetDetails` are optional. You don't normally use them when you use a Docker container since all game assets + start game server command can be packaged in the corresponding [Dockerfile](https://docs.docker.com/engine/reference/builder/). `DeploymentMetadata` is also optional and can contain up to 30 dictionary mappings of custom metadata to be passed to the container as the `buildMetadata` field.
+
+4. Run LocalMultiplayerAgent:
+
+```bash
+./LocalMultiplayerAgent
+```
+
+The agent will automatically detect that it is running on Linux and configure itself for Linux game servers.
+
+## Process Mode (Bare Processes on Linux)
+
+Process mode runs your game server as a native Linux process without Docker.
+
+### Prerequisites
+
+- Your game server must be a Linux executable
+- Docker is **not** required for process mode
+
+### Setup
+
+1. Download the latest version of LocalMultiplayerAgent for Linux (linux-x64) from the [Releases](https://github.com/PlayFab/MpsAgent/releases/) page on GitHub
+
+2. Configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `MultiplayerSettingsLinuxProcessOnLinuxSample.json`:
+
+```json
+{
+ "RunContainer": false,
+ "OutputFolder": "",
+ "NumHeartBeatsForActivateResponse": 10,
+ "NumHeartBeatsForTerminateResponse": 60,
+ "TitleId": "",
+ "BuildId": "00000000-0000-0000-0000-000000000000",
+ "Region": "WestUs",
+ "AgentListeningPort": 56001,
+ "AssetDetails": [
+ {
+ "MountPath": "",
+ "LocalFilePath": ""
+ }
+ ],
+ "PortMappingsList": [
+ [
+ {
+ "NodePort": 56100,
+ "GamePort": {
+ "Name": "gameport",
+ "Number": 7777,
+ "Protocol": "TCP"
+ }
+ }
+ ]
+ ],
+ "ProcessStartParameters": {
+ "StartGameCommand": ""
+ },
+ "SessionConfig": {
+ "SessionId": "ba67d671-512a-4e7d-a38c-2329ce181946",
+ "SessionCookie": null,
+ "InitialPlayers": [ "Player1", "Player2" ]
+ }
+}
+```
+
+> Notes:
+> 1. Set `RunContainer` to false for process mode.
+> 2. `AssetDetails` is required for process mode. Set `LocalFilePath` to the path of your game server zip package.
+> 3. `StartGameCommand` in `ProcessStartParameters` should be the path to your game server executable (relative to the extracted assets).
+
+3. Run LocalMultiplayerAgent:
+
+```bash
+./LocalMultiplayerAgent
+```
+
+## Building from Source
+
+```bash
+git clone https://github.com/PlayFab/MpsAgent.git
+cd ./MpsAgent/LocalMultiplayerAgent
+dotnet publish --runtime linux-x64 -c Release -o LocalMultiplayerAgentPublishFolder -p:PublishSingleFile=true --self-contained true
+```
+
+## Troubleshooting
+
+- If you encounter permission issues running LocalMultiplayerAgent, make it executable: `chmod +x ./LocalMultiplayerAgent`
+- For container mode, ensure your user is in the `docker` group or use `sudo`: `sudo usermod -aG docker $USER`
+- If the Docker socket is not accessible, verify Docker is running: `sudo systemctl status docker`
From 5e281228f390cfc53630ab3b701d7f04b905cd10 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Apr 2026 08:25:37 +0000
Subject: [PATCH 2/8] Move OS-related md files into docs/ folder and update all
references
Agent-Logs-Url: https://github.com/PlayFab/MpsAgent/sessions/d05f2051-b8cb-49dc-be6d-723ac8c87075
Co-authored-by: dgkanatsios <8256138+dgkanatsios@users.noreply.github.com>
---
LocalMultiplayerAgent/readme.md | 2 +-
README.md | 2 +-
lcow.md => docs/lcow.md | 0
linux.md => docs/linux.md | 0
macos.md => docs/macos.md | 0
5 files changed, 2 insertions(+), 2 deletions(-)
rename lcow.md => docs/lcow.md (100%)
rename linux.md => docs/linux.md (100%)
rename macos.md => docs/macos.md (100%)
diff --git a/LocalMultiplayerAgent/readme.md b/LocalMultiplayerAgent/readme.md
index 443e0d6..001043b 100644
--- a/LocalMultiplayerAgent/readme.md
+++ b/LocalMultiplayerAgent/readme.md
@@ -43,7 +43,7 @@ Configure `MultiplayerSettings.json` with `RunContainer: true` (see `Multiplayer
> **Troubleshooting:** If MacOS prevents you from running LocalMultiplayerAgent, run `xattr -d com.apple.quarantine ./LocalMultiplayerAgent` to remove the quarantine attribute.
-For detailed instructions, see [Linux Containers on MacOS](../macos.md).
+For detailed instructions, see [Linux Containers on MacOS](../docs/macos.md).
## MultiplayerSettings.json
diff --git a/README.md b/README.md
index 4bde6e4..fe8a6da 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ This repository contains source code for the following projects:
- LocalMultiplayerAgent
-An executable that mimics PlayFab Multiplayer Servers (MPS) operations to aid in local debugging. It runs on **Windows**, **MacOS (Apple Silicon) (beta)**, and **Linux (x64)**. See [PlayFab documentation - LocalMultiplayerAgent Overview](https://docs.microsoft.com/en-us/gaming/playfab/features/multiplayer/servers/localmultiplayeragent/local-multiplayer-agent-overview) to learn how to debug your game servers using LocalMultiplayerAgent. If you want to develop for Linux Containers on Windows, check out [this document](lcow.md). If you want to run on MacOS (Apple Silicon), check out [this document](macos.md). If you want to run on Linux, check out [this document](linux.md).
+An executable that mimics PlayFab Multiplayer Servers (MPS) operations to aid in local debugging. It runs on **Windows**, **MacOS (Apple Silicon) (beta)**, and **Linux (x64)**. See [PlayFab documentation - LocalMultiplayerAgent Overview](https://docs.microsoft.com/en-us/gaming/playfab/features/multiplayer/servers/localmultiplayeragent/local-multiplayer-agent-overview) to learn how to debug your game servers using LocalMultiplayerAgent. If you want to develop for Linux Containers on Windows, check out [this document](docs/lcow.md). If you want to run on MacOS (Apple Silicon), check out [this document](docs/macos.md). If you want to run on Linux, check out [this document](docs/linux.md).
> This repository replaces and deprecates the project in [this GitHub repo](https://github.com/PlayFab/LocalMultiplayerAgent). The executable produced by that project was called `MockAgent`. This has been renamed to `LocalMultiplayerAgent` for consistency.
diff --git a/lcow.md b/docs/lcow.md
similarity index 100%
rename from lcow.md
rename to docs/lcow.md
diff --git a/linux.md b/docs/linux.md
similarity index 100%
rename from linux.md
rename to docs/linux.md
diff --git a/macos.md b/docs/macos.md
similarity index 100%
rename from macos.md
rename to docs/macos.md
From 3a221575666dc21d181a288836fefcaf0eff3488 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Apr 2026 08:42:04 +0000
Subject: [PATCH 3/8] Revert ProcessRunner.cs change: MountPath back-compat
branch is never taken on Linux
Agent-Logs-Url: https://github.com/PlayFab/MpsAgent/sessions/1c40afcc-5189-4bdd-86d5-6c86de49f0d6
Co-authored-by: dgkanatsios <8256138+dgkanatsios@users.noreply.github.com>
---
VmAgent.Core/Interfaces/ProcessRunner.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/VmAgent.Core/Interfaces/ProcessRunner.cs b/VmAgent.Core/Interfaces/ProcessRunner.cs
index 77998cf..e1dcd01 100644
--- a/VmAgent.Core/Interfaces/ProcessRunner.cs
+++ b/VmAgent.Core/Interfaces/ProcessRunner.cs
@@ -126,7 +126,7 @@ public void CreateStartGameExceptionLogs(ProcessOutputLogger processOutputLogger
// Replacing the mount path is for back compat when we didn't have first class support for process based servers
// (and were based off of the parameters for containers).
string executablePath = sessionHostsStartInfo.AssetDetails[0].MountPath?.Length > 0
- ? parts[0].Replace(sessionHostsStartInfo.AssetDetails[0].MountPath, $"{localPathForAsset0}{Path.DirectorySeparatorChar}")
+ ? parts[0].Replace(sessionHostsStartInfo.AssetDetails[0].MountPath, $"{localPathForAsset0}\\")
: Path.Combine(localPathForAsset0, parts[0]);
string args = parts.Length > 1 ? string.Join(' ', parts.Skip(1)) : string.Empty;
return (executablePath, args);
From 2cc8e4c6158ef7275f20a890a371074f3bf03983 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Apr 2026 08:50:30 +0000
Subject: [PATCH 4/8] Move MultiplayerSettings sample files into Samples/
folder and update all references
Agent-Logs-Url: https://github.com/PlayFab/MpsAgent/sessions/61f546a2-ffa9-4df1-a391-95ea4fb3bf80
Co-authored-by: dgkanatsios <8256138+dgkanatsios@users.noreply.github.com>
---
LocalMultiplayerAgent/LocalMultiplayerAgent.csproj | 12 ++++++++++--
...tiplayerSettingsLinuxContainersOnLinuxSample.json | 0
...tiplayerSettingsLinuxContainersOnMacOSSample.json | 0
...playerSettingsLinuxContainersOnWindowsSample.json | 0
...MultiplayerSettingsLinuxProcessOnLinuxSample.json | 0
LocalMultiplayerAgent/readme.md | 2 +-
docs/lcow.md | 2 +-
docs/linux.md | 4 ++--
docs/macos.md | 2 +-
9 files changed, 15 insertions(+), 7 deletions(-)
rename LocalMultiplayerAgent/{ => Samples}/MultiplayerSettingsLinuxContainersOnLinuxSample.json (100%)
rename LocalMultiplayerAgent/{ => Samples}/MultiplayerSettingsLinuxContainersOnMacOSSample.json (100%)
rename LocalMultiplayerAgent/{ => Samples}/MultiplayerSettingsLinuxContainersOnWindowsSample.json (100%)
rename LocalMultiplayerAgent/{ => Samples}/MultiplayerSettingsLinuxProcessOnLinuxSample.json (100%)
diff --git a/LocalMultiplayerAgent/LocalMultiplayerAgent.csproj b/LocalMultiplayerAgent/LocalMultiplayerAgent.csproj
index 70fe147..8be07d9 100644
--- a/LocalMultiplayerAgent/LocalMultiplayerAgent.csproj
+++ b/LocalMultiplayerAgent/LocalMultiplayerAgent.csproj
@@ -29,11 +29,19 @@
PreserveNewest
True
-
+
PreserveNewest
True
-
+
+ PreserveNewest
+ True
+
+
+ PreserveNewest
+ True
+
+
PreserveNewest
True
diff --git a/LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnLinuxSample.json b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json
similarity index 100%
rename from LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnLinuxSample.json
rename to LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json
diff --git a/LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnMacOSSample.json b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json
similarity index 100%
rename from LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnMacOSSample.json
rename to LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json
diff --git a/LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnWindowsSample.json b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnWindowsSample.json
similarity index 100%
rename from LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnWindowsSample.json
rename to LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnWindowsSample.json
diff --git a/LocalMultiplayerAgent/MultiplayerSettingsLinuxProcessOnLinuxSample.json b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxProcessOnLinuxSample.json
similarity index 100%
rename from LocalMultiplayerAgent/MultiplayerSettingsLinuxProcessOnLinuxSample.json
rename to LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxProcessOnLinuxSample.json
diff --git a/LocalMultiplayerAgent/readme.md b/LocalMultiplayerAgent/readme.md
index 001043b..a9f461b 100644
--- a/LocalMultiplayerAgent/readme.md
+++ b/LocalMultiplayerAgent/readme.md
@@ -39,7 +39,7 @@ chmod +x setup_macos.sh
./setup_macos.sh
```
-Configure `MultiplayerSettings.json` with `RunContainer: true` (see `MultiplayerSettingsLinuxContainersOnMacOSSample.json` for a reference configuration). The agent automatically detects MacOS and configures for Linux containers — no additional flags are needed.
+Configure `MultiplayerSettings.json` with `RunContainer: true` (see `Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json` for a reference configuration). The agent automatically detects MacOS and configures for Linux containers — no additional flags are needed.
> **Troubleshooting:** If MacOS prevents you from running LocalMultiplayerAgent, run `xattr -d com.apple.quarantine ./LocalMultiplayerAgent` to remove the quarantine attribute.
diff --git a/docs/lcow.md b/docs/lcow.md
index 14c2dcb..cbd3fd2 100644
--- a/docs/lcow.md
+++ b/docs/lcow.md
@@ -10,7 +10,7 @@ To run your containerized Linux game servers on Windows, you'll need to perform
- You should mount one of your hard drives, instructions [here](https://docs.docker.com/docker-for-windows/#file-sharing)
- Your game server image can be published on a container registry or can be locally built.
- You should run `SetupLinuxContainersOnWindows.ps1` Powershell file which will create a Docker network called "PlayFab"
-- You should properly configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `MultiplayerSettingsLinuxContainersOnWindowsSample.json`:
+- You should properly configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `Samples/MultiplayerSettingsLinuxContainersOnWindowsSample.json`:
```json
{
diff --git a/docs/linux.md b/docs/linux.md
index 314f3f8..25b8780 100644
--- a/docs/linux.md
+++ b/docs/linux.md
@@ -23,7 +23,7 @@ chmod +x setup_linux.sh
./setup_linux.sh
```
-3. Configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `MultiplayerSettingsLinuxContainersOnLinuxSample.json`:
+3. Configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json`:
```json
{
@@ -94,7 +94,7 @@ Process mode runs your game server as a native Linux process without Docker.
1. Download the latest version of LocalMultiplayerAgent for Linux (linux-x64) from the [Releases](https://github.com/PlayFab/MpsAgent/releases/) page on GitHub
-2. Configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `MultiplayerSettingsLinuxProcessOnLinuxSample.json`:
+2. Configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `Samples/MultiplayerSettingsLinuxProcessOnLinuxSample.json`:
```json
{
diff --git a/docs/macos.md b/docs/macos.md
index ebe5620..28536d0 100644
--- a/docs/macos.md
+++ b/docs/macos.md
@@ -15,7 +15,7 @@ chmod +x setup_macos.sh
./setup_macos.sh
```
-- Properly configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `MultiplayerSettingsLinuxContainersOnMacOSSample.json`:
+- Properly configure your *MultiplayerSettings.json* file. Below you can see a sample, included in `Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json`:
```json
{
From c9b6044dc8f638a2e9d66e77e1189e04ce32f855 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 8 Apr 2026 09:00:14 +0000
Subject: [PATCH 5/8] Mark Linux support as beta in README and
LocalMultiplayerAgent readme
Agent-Logs-Url: https://github.com/PlayFab/MpsAgent/sessions/78085e91-d9bf-4b9a-9085-b72d3f40747c
Co-authored-by: dgkanatsios <8256138+dgkanatsios@users.noreply.github.com>
---
LocalMultiplayerAgent/readme.md | 2 +-
README.md | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/LocalMultiplayerAgent/readme.md b/LocalMultiplayerAgent/readme.md
index a9f461b..5fe5853 100644
--- a/LocalMultiplayerAgent/readme.md
+++ b/LocalMultiplayerAgent/readme.md
@@ -1,6 +1,6 @@
# Development instructions for LocalMultiplayerAgent
-LocalMultiplayerAgent runs on **Windows** and **MacOS (Apple Silicon) (beta)**. Please see the start up scripts for each platform for setting up firewalls and installing additional dependencies.
+LocalMultiplayerAgent runs on **Windows**, **MacOS (Apple Silicon) (beta)**, and **Linux (x64) (beta)**. Please see the start up scripts for each platform for setting up firewalls and installing additional dependencies.
Instructions for using LocalMultiplayerAgent can be found in the [LocalMultiplayerAgent documentation](https://learn.microsoft.com/en-us/gaming/playfab/features/multiplayer/servers/localmultiplayeragent/local-multiplayer-agent-overview).
diff --git a/README.md b/README.md
index fe8a6da..2f861a8 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ This repository contains source code for the following projects:
- LocalMultiplayerAgent
-An executable that mimics PlayFab Multiplayer Servers (MPS) operations to aid in local debugging. It runs on **Windows**, **MacOS (Apple Silicon) (beta)**, and **Linux (x64)**. See [PlayFab documentation - LocalMultiplayerAgent Overview](https://docs.microsoft.com/en-us/gaming/playfab/features/multiplayer/servers/localmultiplayeragent/local-multiplayer-agent-overview) to learn how to debug your game servers using LocalMultiplayerAgent. If you want to develop for Linux Containers on Windows, check out [this document](docs/lcow.md). If you want to run on MacOS (Apple Silicon), check out [this document](docs/macos.md). If you want to run on Linux, check out [this document](docs/linux.md).
+An executable that mimics PlayFab Multiplayer Servers (MPS) operations to aid in local debugging. It runs on **Windows**, **MacOS (Apple Silicon) (beta)**, and **Linux (x64) (beta)**. See [PlayFab documentation - LocalMultiplayerAgent Overview](https://docs.microsoft.com/en-us/gaming/playfab/features/multiplayer/servers/localmultiplayeragent/local-multiplayer-agent-overview) to learn how to debug your game servers using LocalMultiplayerAgent. If you want to develop for Linux Containers on Windows, check out [this document](docs/lcow.md). If you want to run on MacOS (Apple Silicon), check out [this document](docs/macos.md). If you want to run on Linux, check out [this document](docs/linux.md).
> This repository replaces and deprecates the project in [this GitHub repo](https://github.com/PlayFab/LocalMultiplayerAgent). The executable produced by that project was called `MockAgent`. This has been renamed to `LocalMultiplayerAgent` for consistency.
From c96fcfefad914e81a820db1749d5b86397418192 Mon Sep 17 00:00:00 2001
From: Dimitris Gkanatsios
Date: Wed, 15 Apr 2026 11:36:19 -0700
Subject: [PATCH 6/8] Fix Linux support gaps: packaging, line endings, CI,
error messages
- Add setup_linux.sh to .csproj so it is included in publish output
- Add .gitattributes to enforce LF line endings for .sh files
- Fix CRLF line endings in setup_linux.sh and setup_macos.sh
- Add ubuntu-22.04 to CI matrix with linux-x64 publish and artifact
- Make validator and Docker network error messages platform-aware
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.gitattributes | 2 ++
.github/workflows/main.yml | 11 ++++++++++-
.../Config/MultiplayerSettingsValidator.cs | 5 ++++-
LocalMultiplayerAgent/LocalMultiplayerAgent.csproj | 4 ++++
VmAgent.Core/Interfaces/DockerContainerEngine.cs | 2 +-
5 files changed, 21 insertions(+), 3 deletions(-)
create mode 100644 .gitattributes
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..7c75d96
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+# Ensure shell scripts always use LF line endings
+*.sh text eol=lf
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index e0e7f19..d228c79 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -25,7 +25,7 @@ jobs:
strategy:
matrix:
dotnet-version: [ '10.x' ]
- os: [windows-2022, macos-14]
+ os: [windows-2022, macos-14, ubuntu-22.04]
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
@@ -47,6 +47,9 @@ jobs:
- name: Publish LocalMultiplayerAgent (MacOS ARM)
if: matrix.os == 'macos-14'
run: dotnet publish LocalMultiplayerAgent --runtime osx-arm64 -c Release -o LocalMultiplayerAgentPublishFolder-macos -p:PublishSingleFile=true --self-contained true
+ - name: Publish LocalMultiplayerAgent (Linux x64)
+ if: matrix.os == 'ubuntu-22.04'
+ run: dotnet publish LocalMultiplayerAgent --runtime linux-x64 -c Release -o LocalMultiplayerAgentPublishFolder-linux -p:PublishSingleFile=true --self-contained true
- name: Upload Windows artifact
if: matrix.os == 'windows-2022'
uses: actions/upload-artifact@v4
@@ -59,4 +62,10 @@ jobs:
with:
name: LocalMultiplayerAgent-osx-arm64
path: LocalMultiplayerAgentPublishFolder-macos
+ - name: Upload Linux artifact
+ if: matrix.os == 'ubuntu-22.04'
+ uses: actions/upload-artifact@v4
+ with:
+ name: LocalMultiplayerAgent-linux-x64
+ path: LocalMultiplayerAgentPublishFolder-linux
diff --git a/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs b/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
index 7fe235a..ad6710e 100644
--- a/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
+++ b/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
@@ -69,7 +69,10 @@ public bool IsValid(bool createBuild = false)
if (_settings.AgentListeningPort != 56001)
{
- Console.WriteLine($"Warning: You have specified an AgentListeningPort ({_settings.AgentListeningPort}) that is not the default. Please make sure that port is open on your firewall by running setup.ps1 with the agent port specified.");
+ string setupScript = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? "setup_linux.sh"
+ : RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? "setup_macos.sh"
+ : "Setup.ps1";
+ Console.WriteLine($"Warning: You have specified an AgentListeningPort ({_settings.AgentListeningPort}) that is not the default. Please make sure that port is open on your firewall by running {setupScript} with the agent port specified.");
}
if (_settings.RunContainer)
diff --git a/LocalMultiplayerAgent/LocalMultiplayerAgent.csproj b/LocalMultiplayerAgent/LocalMultiplayerAgent.csproj
index 8be07d9..c049b29 100644
--- a/LocalMultiplayerAgent/LocalMultiplayerAgent.csproj
+++ b/LocalMultiplayerAgent/LocalMultiplayerAgent.csproj
@@ -53,6 +53,10 @@
Always
True
+
+ Always
+ True
+
Always
True
diff --git a/VmAgent.Core/Interfaces/DockerContainerEngine.cs b/VmAgent.Core/Interfaces/DockerContainerEngine.cs
index 255ef07..35033b9 100644
--- a/VmAgent.Core/Interfaces/DockerContainerEngine.cs
+++ b/VmAgent.Core/Interfaces/DockerContainerEngine.cs
@@ -124,7 +124,7 @@ public override string GetVmAgentIpAddress()
catch (Exception e)
{
throw new Exception(
- $"Failed to find network '{SessionHostContainerConfiguration.DockerNetworkName}'. Ensure you have properly setup networking via setup.ps1",
+ $"Failed to find network '{SessionHostContainerConfiguration.DockerNetworkName}'. Ensure you have properly setup networking via the appropriate setup script (Setup.ps1 on Windows, setup_macos.sh on MacOS, setup_linux.sh on Linux).",
e);
}
}
From 717dec0b277a9c861aa7a74aded9e5c97b89eae3 Mon Sep 17 00:00:00 2001
From: Dimitris Gkanatsios
Date: Wed, 15 Apr 2026 11:47:13 -0700
Subject: [PATCH 7/8] Add ForcePullContainerImageFromRegistry setting for
macOS/Linux
Replace hardcoded always-pull on macOS/Linux with an explicit opt-in setting.
When false (default), the agent skips docker pull and uses the local image
directly. When true, it pulls from the configured registry before starting.
This fixes container mode for locally-built images on macOS (pre-existing)
and Linux (new). The LCOW and Windows paths are unchanged.
- Add ForcePullContainerImageFromRegistry property to MultiplayerSettings
- Remove hardcoded OSPlatform.OSX/Linux checks from pull condition
- Update macOS and Linux container sample configs
- Update docs/linux.md and docs/macos.md with pull policy guidance
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../Config/MultiplayerSettings.cs | 7 +++++++
.../MultiplayerServerManager.cs | 16 ++++++++--------
...ayerSettingsLinuxContainersOnLinuxSample.json | 1 +
...ayerSettingsLinuxContainersOnMacOSSample.json | 1 +
docs/linux.md | 4 +++-
docs/macos.md | 6 ++++--
6 files changed, 24 insertions(+), 11 deletions(-)
diff --git a/LocalMultiplayerAgent/Config/MultiplayerSettings.cs b/LocalMultiplayerAgent/Config/MultiplayerSettings.cs
index ae20561..19d23e5 100644
--- a/LocalMultiplayerAgent/Config/MultiplayerSettings.cs
+++ b/LocalMultiplayerAgent/Config/MultiplayerSettings.cs
@@ -49,6 +49,13 @@ public class MultiplayerSettings
public bool ForcePullFromAcrOnLinuxContainersOnWindows { get; set; }
+ ///
+ /// When true, pulls the container image from the registry before starting.
+ /// Set to true when using a remote container registry on macOS or Linux.
+ /// When false (default), the agent assumes the image is available locally.
+ ///
+ public bool ForcePullContainerImageFromRegistry { get; set; }
+
public IDictionary DeploymentMetadata { get; set; }
public string MaintenanceEventType { get; set; }
diff --git a/LocalMultiplayerAgent/MultiplayerServerManager.cs b/LocalMultiplayerAgent/MultiplayerServerManager.cs
index febaddb..612fa0d 100644
--- a/LocalMultiplayerAgent/MultiplayerServerManager.cs
+++ b/LocalMultiplayerAgent/MultiplayerServerManager.cs
@@ -60,14 +60,14 @@ public async Task CreateAndStartContainerWaitForExit(SessionHostsStartInfo start
ISessionHostRunner sessionHostRunner =
_sessionHostRunnerFactory.CreateSessionHostRunner(startParameters.SessionHostType, _vmConfiguration, _logger);
- // RetrieveResources does a docker pull
- // If we're running Linux containers on Windows, we might want to pull image from ACR first to save the image on local.
- // In this case, you need to simply change the value to true for forcePullFromAcrOnLinuxContainersOnWindows in MultiplayerSettings.json
- // so if this image is not locally built, docker create will do a docker pull first
- // In another case, we might have built the image locally but tagged with a fake registry name (e.g. myacr.io/mygame:0.1),
- // Then make sure to change the value to false if you want to use the image from local.
- if (Globals.GameServerEnvironment == GameServerEnvironment.Windows || Globals.Settings.ForcePullFromAcrOnLinuxContainersOnWindows
- || RuntimeInformation.IsOSPlatform(OSPlatform.OSX) || RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ // RetrieveResources does a docker pull.
+ // Windows game servers always pull from the registry.
+ // For Linux containers on Windows (LCOW), set ForcePullFromAcrOnLinuxContainersOnWindows to true to pull.
+ // For macOS/Linux, set ForcePullContainerImageFromRegistry to true to pull from a remote registry.
+ // If your image is locally built (e.g. via "docker build"), leave both flags false to skip the pull.
+ if (Globals.GameServerEnvironment == GameServerEnvironment.Windows
+ || Globals.Settings.ForcePullFromAcrOnLinuxContainersOnWindows
+ || Globals.Settings.ForcePullContainerImageFromRegistry)
{
await sessionHostRunner.RetrieveResources(startParameters);
}
diff --git a/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json
index 464453b..e0305c6 100644
--- a/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json
+++ b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json
@@ -1,5 +1,6 @@
{
"RunContainer": true,
+ "ForcePullContainerImageFromRegistry": true,
"OutputFolder": "",
"NumHeartBeatsForActivateResponse": 10,
"NumHeartBeatsForTerminateResponse": 60,
diff --git a/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json
index 464453b..e0305c6 100644
--- a/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json
+++ b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json
@@ -1,5 +1,6 @@
{
"RunContainer": true,
+ "ForcePullContainerImageFromRegistry": true,
"OutputFolder": "",
"NumHeartBeatsForActivateResponse": 10,
"NumHeartBeatsForTerminateResponse": 60,
diff --git a/docs/linux.md b/docs/linux.md
index 25b8780..d84aef4 100644
--- a/docs/linux.md
+++ b/docs/linux.md
@@ -28,6 +28,7 @@ chmod +x setup_linux.sh
```json
{
"RunContainer": true,
+ "ForcePullContainerImageFromRegistry": true,
"OutputFolder": "",
"NumHeartBeatsForActivateResponse": 10,
"NumHeartBeatsForTerminateResponse": 60,
@@ -71,7 +72,8 @@ chmod +x setup_linux.sh
> Notes:
> 1. Set `RunContainer` to true for container mode.
> 2. Modify `ImageDetails` with your game server Docker image details. The image may be built locally (using [docker build](https://docs.docker.com/engine/reference/commandline/build/) command) or be hosted in a remote container registry.
-> 3. `StartGameCommand` and `AssetDetails` are optional. You don't normally use them when you use a Docker container since all game assets + start game server command can be packaged in the corresponding [Dockerfile](https://docs.docker.com/engine/reference/builder/). `DeploymentMetadata` is also optional and can contain up to 30 dictionary mappings of custom metadata to be passed to the container as the `buildMetadata` field.
+> 3. If your image is hosted in a remote container registry, set `ForcePullContainerImageFromRegistry` to `true` so the agent pulls it before starting. If your image is locally built, set it to `false` (or omit it) to skip the pull.
+> 4. `StartGameCommand` and `AssetDetails` are optional. You don't normally use them when you use a Docker container since all game assets + start game server command can be packaged in the corresponding [Dockerfile](https://docs.docker.com/engine/reference/builder/). `DeploymentMetadata` is also optional and can contain up to 30 dictionary mappings of custom metadata to be passed to the container as the `buildMetadata` field.
4. Run LocalMultiplayerAgent:
diff --git a/docs/macos.md b/docs/macos.md
index 28536d0..3a7275c 100644
--- a/docs/macos.md
+++ b/docs/macos.md
@@ -20,6 +20,7 @@ chmod +x setup_macos.sh
```json
{
"RunContainer": true,
+ "ForcePullContainerImageFromRegistry": true,
"OutputFolder": "",
"NumHeartBeatsForActivateResponse": 10,
"NumHeartBeatsForTerminateResponse": 60,
@@ -63,8 +64,9 @@ chmod +x setup_macos.sh
> This file is also included in the latest LocalMultiplayerAgent zip package. Some notes:
> 1. You must set `RunContainer` to true. MacOS only supports Linux containers.
> 2. Modify `ImageDetails` with your game server Docker image details. The image may be built locally (using [docker build](https://docs.docker.com/engine/reference/commandline/build/) command) or be hosted in a remote container registry.
-> 3. `StartGameCommand` and `AssetDetails` are optional. You don't normally use them when you use a Docker container since all game assets + start game server command can be packaged in the corresponding [Dockerfile](https://docs.docker.com/engine/reference/builder/). `DeploymentMetadata` is also optional and can contain up to 30 dictionary mappings of custom metadata to be passed to the container as the `buildMetadata` field.
-> 4. Process mode (bare process without containers) is not supported on MacOS. You must use `RunContainer: true`.
+> 3. If your image is hosted in a remote container registry, set `ForcePullContainerImageFromRegistry` to `true` so the agent pulls it before starting. If your image is locally built, set it to `false` (or omit it) to skip the pull.
+> 4. `StartGameCommand` and `AssetDetails` are optional. You don't normally use them when you use a Docker container since all game assets + start game server command can be packaged in the corresponding [Dockerfile](https://docs.docker.com/engine/reference/builder/). `DeploymentMetadata` is also optional and can contain up to 30 dictionary mappings of custom metadata to be passed to the container as the `buildMetadata` field.
+> 5. Process mode (bare process without containers) is not supported on MacOS. You must use `RunContainer: true`.
- After you perform all the previous steps, you can then run LocalMultiplayerAgent:
From fe0b77b8e17f48d63ed6e01a544507b11cbd4b38 Mon Sep 17 00:00:00 2001
From: Dimitris Gkanatsios
Date: Wed, 15 Apr 2026 13:52:46 -0700
Subject: [PATCH 8/8] Fix Linux process mode validation: require
StartGameCommand and AssetDetails
The PR enabling Linux support made StartGameCommand and AssetDetails optional
for all Linux game servers, but ProcessRunner crashes on null/empty values for
these fields (NullReferenceException on StartGameCommand.Split(), and
IndexOutOfRangeException on AssetDetails[0]).
Fix: Make these fields optional only for Linux containers (where the Dockerfile
provides CMD/ENTRYPOINT and packages assets), but required for Linux process
mode (matching Windows process mode behavior and production MPS expectations).
Also:
- Add 3 new tests: LinuxProcessModeRequiresStartGameCommand,
LinuxProcessModeRequiresAssets, LinuxContainerModeAllowsEmptyStartGameCommandAndAssets
- Fix setup_linux.sh sample file paths (missing Samples/ prefix)
- Improve process mode StartGameCommand docs guidance
- Fix misleading test comment about macOS guard
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---
.../ConfigurationTests.cs | 91 ++++++++++++++++++-
.../Config/MultiplayerSettingsValidator.cs | 15 ++-
...ayerSettingsLinuxProcessOnLinuxSample.json | 2 +-
LocalMultiplayerAgent/setup_linux.sh | 4 +-
docs/linux.md | 4 +-
5 files changed, 103 insertions(+), 13 deletions(-)
diff --git a/LocalMultiplayerAgent.UnitTest/ConfigurationTests.cs b/LocalMultiplayerAgent.UnitTest/ConfigurationTests.cs
index 611f93b..cc7fbd9 100644
--- a/LocalMultiplayerAgent.UnitTest/ConfigurationTests.cs
+++ b/LocalMultiplayerAgent.UnitTest/ConfigurationTests.cs
@@ -349,7 +349,7 @@ public void LinuxGameServerEnvironmentWithoutContainerFails()
Assert.Inconclusive("On native Linux OS, Linux process mode is valid");
}
- // On MacOS, process mode is rejected by a different check
+ // On MacOS, process mode is rejected by the macOS-specific check above (line 118-124), which fires first
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
Assert.Inconclusive("Process mode validation is not supported on MacOS");
@@ -431,7 +431,7 @@ public void WindowsContainerModeIsValid()
///
/// Validates that StartGameCommand is not required when using Linux game server environment
- /// (since it can be baked into the container image).
+ /// in container mode (since it can be baked into the container image via CMD/ENTRYPOINT).
///
[TestMethod]
[TestCategory("BVT")]
@@ -445,7 +445,7 @@ public void LinuxGameServerEnvironmentAllowsEmptyStartGameCommand()
dynamic config = GetValidConfig();
config.RunContainer = true;
config.ContainerStartParameters.StartGameCommand = "";
- // Remove asset details (assets are optional for Linux)
+ // Remove asset details (assets are optional for Linux containers)
config.AssetDetails = new JArray();
MultiplayerSettings settings = JsonConvert.DeserializeObject(config.ToString());
@@ -486,7 +486,7 @@ public void WindowsGameServerEnvironmentRequiresStartGameCommand()
}
///
- /// Validates that assets are optional for Linux game servers.
+ /// Validates that assets are optional for Linux game servers in container mode.
///
[TestMethod]
[TestCategory("BVT")]
@@ -603,6 +603,89 @@ public void LinuxOsAcceptsProcessMode()
Globals.GameServerEnvironment = previousEnv;
}
}
+
+ ///
+ /// Linux process mode (RunContainer=false) requires StartGameCommand.
+ /// ProcessRunner crashes on null/empty StartGameCommand, so the validator must reject it.
+ ///
+ [TestMethod]
+ [TestCategory("BVT")]
+ public void LinuxProcessModeRequiresStartGameCommand()
+ {
+ var previousEnv = Globals.GameServerEnvironment;
+ try
+ {
+ Globals.GameServerEnvironment = GameServerEnvironment.Linux;
+
+ dynamic config = GetValidConfig();
+ config.RunContainer = false;
+ config.ProcessStartParameters.StartGameCommand = "";
+
+ MultiplayerSettings settings = JsonConvert.DeserializeObject(config.ToString());
+ settings.SetDefaultsIfNotSpecified();
+ new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeFalse();
+ }
+ finally
+ {
+ Globals.GameServerEnvironment = previousEnv;
+ }
+ }
+
+ ///
+ /// Linux process mode (RunContainer=false) requires AssetDetails.
+ /// ProcessRunner crashes on empty AssetDetails, so the validator must reject it.
+ ///
+ [TestMethod]
+ [TestCategory("BVT")]
+ public void LinuxProcessModeRequiresAssets()
+ {
+ var previousEnv = Globals.GameServerEnvironment;
+ try
+ {
+ Globals.GameServerEnvironment = GameServerEnvironment.Linux;
+
+ dynamic config = GetValidConfig();
+ config.RunContainer = false;
+ config.AssetDetails = new JArray();
+
+ MultiplayerSettings settings = JsonConvert.DeserializeObject(config.ToString());
+ settings.SetDefaultsIfNotSpecified();
+ new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeFalse();
+ }
+ finally
+ {
+ Globals.GameServerEnvironment = previousEnv;
+ }
+ }
+
+ ///
+ /// Linux container mode (RunContainer=true) still allows empty StartGameCommand and empty assets.
+ /// The Dockerfile provides CMD/ENTRYPOINT and packages all assets into the image.
+ /// This test ensures the process-mode fix does not regress container mode.
+ ///
+ [TestMethod]
+ [TestCategory("BVT")]
+ public void LinuxContainerModeAllowsEmptyStartGameCommandAndAssets()
+ {
+ var previousEnv = Globals.GameServerEnvironment;
+ try
+ {
+ Globals.GameServerEnvironment = GameServerEnvironment.Linux;
+
+ dynamic config = GetValidConfig();
+ config.RunContainer = true;
+ config.ContainerStartParameters.StartGameCommand = "";
+ config.AssetDetails = new JArray();
+
+ MultiplayerSettings settings = JsonConvert.DeserializeObject(config.ToString());
+ settings.SetDefaultsIfNotSpecified();
+ new MultiplayerSettingsValidator(settings, _mockSystemOperations.Object).IsValid().Should().BeTrue();
+ }
+ finally
+ {
+ Globals.GameServerEnvironment = previousEnv;
+ }
+ }
}
///
diff --git a/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs b/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
index ad6710e..c4ca882 100644
--- a/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
+++ b/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
@@ -152,16 +152,17 @@ public bool IsValid(bool createBuild = false)
startGameCommand = _settings.ProcessStartParameters.StartGameCommand;
}
- // StartGameCommand is optional on Linux
+ // StartGameCommand is optional for Linux containers (the Dockerfile provides CMD/ENTRYPOINT),
+ // but required for process mode on any OS since ProcessRunner needs it.
if (string.IsNullOrWhiteSpace(startGameCommand))
{
- if (Globals.GameServerEnvironment == GameServerEnvironment.Windows)
+ if (Globals.GameServerEnvironment == GameServerEnvironment.Windows || !_settings.RunContainer)
{
Console.WriteLine("StartGameCommand must be specified.");
isSuccess = false;
}
}
- else if (startGameCommand.Contains(""))
+ else if (startGameCommand.Contains(""
+ "StartGameCommand": ""
},
"SessionConfig": {
"SessionId": "ba67d671-512a-4e7d-a38c-2329ce181946",
diff --git a/LocalMultiplayerAgent/setup_linux.sh b/LocalMultiplayerAgent/setup_linux.sh
index 4fa157e..243c5f8 100644
--- a/LocalMultiplayerAgent/setup_linux.sh
+++ b/LocalMultiplayerAgent/setup_linux.sh
@@ -38,5 +38,5 @@ echo "Setup complete! You can now run LocalMultiplayerAgent."
echo "Agent will listen on port $AGENT_PORT."
echo ""
echo "Make sure to update MultiplayerSettings.json with your game server details."
-echo "You can use MultiplayerSettingsLinuxContainersOnLinuxSample.json (container mode)"
-echo "or MultiplayerSettingsLinuxProcessOnLinuxSample.json (process mode) as a reference."
+echo "You can use Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json (container mode)"
+echo "or Samples/MultiplayerSettingsLinuxProcessOnLinuxSample.json (process mode) as a reference."
diff --git a/docs/linux.md b/docs/linux.md
index d84aef4..93ab109 100644
--- a/docs/linux.md
+++ b/docs/linux.md
@@ -127,7 +127,7 @@ Process mode runs your game server as a native Linux process without Docker.
]
],
"ProcessStartParameters": {
- "StartGameCommand": ""
+ "StartGameCommand": ""
},
"SessionConfig": {
"SessionId": "ba67d671-512a-4e7d-a38c-2329ce181946",
@@ -140,7 +140,7 @@ Process mode runs your game server as a native Linux process without Docker.
> Notes:
> 1. Set `RunContainer` to false for process mode.
> 2. `AssetDetails` is required for process mode. Set `LocalFilePath` to the path of your game server zip package.
-> 3. `StartGameCommand` in `ProcessStartParameters` should be the path to your game server executable (relative to the extracted assets).
+> 3. `StartGameCommand` in `ProcessStartParameters` is required for process mode. It should be the executable name relative to the extracted assets root (e.g. `MyServer` or `MyServer --port 7777`). Arguments can be appended after a space.
3. Run LocalMultiplayerAgent: