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: