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.UnitTest/ConfigurationTests.cs b/LocalMultiplayerAgent.UnitTest/ConfigurationTests.cs
index bf355e3..cc7fbd9 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 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");
+ }
+
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
{
@@ -438,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")]
@@ -452,19 +445,13 @@ 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());
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
{
@@ -499,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")]
@@ -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))
{
@@ -579,6 +563,91 @@ public void LinuxOsRejectsContainerMode()
config.AssetDetails = new JArray();
config.ContainerStartParameters.StartGameCommand = "/game/server";
+ MultiplayerSettings settings = JsonConvert.DeserializeObject(config.ToString());
+ settings.SetDefaultsIfNotSpecified();
+ 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
+ {
+ 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();
@@ -588,6 +657,35 @@ public void LinuxOsRejectsContainerMode()
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/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/Config/MultiplayerSettingsValidator.cs b/LocalMultiplayerAgent/Config/MultiplayerSettingsValidator.cs
index 4624ef9..c4ca882 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)
@@ -121,15 +124,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;
}
@@ -154,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("PreserveNewest
True
-
+
PreserveNewest
True
-
+
+ PreserveNewest
+ True
+
+
+ PreserveNewest
+ True
+
+
PreserveNewest
True
@@ -45,6 +53,10 @@
Always
True
+
+ Always
+ True
+
Always
True
diff --git a/LocalMultiplayerAgent/MultiplayerServerManager.cs b/LocalMultiplayerAgent/MultiplayerServerManager.cs
index b0130d2..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))
+ // 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/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/Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json
new file mode 100644
index 0000000..e0305c6
--- /dev/null
+++ b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json
@@ -0,0 +1,41 @@
+{
+ "RunContainer": true,
+ "ForcePullContainerImageFromRegistry": 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/MultiplayerSettingsLinuxContainersOnMacOSSample.json b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json
similarity index 95%
rename from LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnMacOSSample.json
rename to LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json
index 464453b..e0305c6 100644
--- a/LocalMultiplayerAgent/MultiplayerSettingsLinuxContainersOnMacOSSample.json
+++ b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxContainersOnMacOSSample.json
@@ -1,5 +1,6 @@
{
"RunContainer": true,
+ "ForcePullContainerImageFromRegistry": true,
"OutputFolder": "",
"NumHeartBeatsForActivateResponse": 10,
"NumHeartBeatsForTerminateResponse": 60,
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/Samples/MultiplayerSettingsLinuxProcessOnLinuxSample.json b/LocalMultiplayerAgent/Samples/MultiplayerSettingsLinuxProcessOnLinuxSample.json
new file mode 100644
index 0000000..db5b7ab
--- /dev/null
+++ b/LocalMultiplayerAgent/Samples/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/readme.md b/LocalMultiplayerAgent/readme.md
index 443e0d6..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).
@@ -39,11 +39,11 @@ 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.
-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/LocalMultiplayerAgent/setup_linux.sh b/LocalMultiplayerAgent/setup_linux.sh
new file mode 100644
index 0000000..243c5f8
--- /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 Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json (container mode)"
+echo "or Samples/MultiplayerSettingsLinuxProcessOnLinuxSample.json (process mode) as a reference."
diff --git a/README.md b/README.md
index 316b81c..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** 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) (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.
@@ -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/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);
}
}
diff --git a/lcow.md b/docs/lcow.md
similarity index 97%
rename from lcow.md
rename to docs/lcow.md
index 14c2dcb..cbd3fd2 100644
--- a/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
new file mode 100644
index 0000000..93ab109
--- /dev/null
+++ b/docs/linux.md
@@ -0,0 +1,163 @@
+# 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 `Samples/MultiplayerSettingsLinuxContainersOnLinuxSample.json`:
+
+```json
+{
+ "RunContainer": true,
+ "ForcePullContainerImageFromRegistry": 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. 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:
+
+```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 `Samples/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` 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:
+
+```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`
diff --git a/macos.md b/docs/macos.md
similarity index 86%
rename from macos.md
rename to docs/macos.md
index ebe5620..3a7275c 100644
--- a/macos.md
+++ b/docs/macos.md
@@ -15,11 +15,12 @@ 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
{
"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: