Skip to content

Develop SharpMUSH

This guide is for changing SharpMUSH itself: the server’s C# code, the web portal, or the tests. If you only want to run a game, Run with Docker is quicker and needs no toolchain. Softcode, packages and plugins don’t need a source build either; see Softcode Packages and Writing Plugins.

SharpMUSH is not one program. A working game on your machine is four .NET processes plus NATS, the message bus the engine and the connection server talk through:

The four SharpMUSH processes, NATS and the world folder, and how a browser and a MU* client reach them

ProcessWhat it doesAddress
Game server, SharpMUSH.ServerThe engine, the world, the APIhttps://localhost:8081
Connection server, SharpMUSH.SocketServerHolds telnet and browser-terminal connectionstelnet 4201, /ws on 4202
Rendering worker, SharpMUSH.RenderingWorkerFormats output for each kind of clienta socket file, no port
Portal dev server, SharpMUSH.ClientServes the web portalhttps://localhost:7102
NATS, in DockerCarries commands and output between the game and connection serversnats://localhost:4222

In a published image the game server serves the portal itself. A source build doesn’t bundle it, so during development the portal comes from its own dev server and calls the game server’s API at https://localhost:8081.

The Architecture page explains why the parts are split this way.

  • The .NET SDK the repository pins, currently 11.0.100-rc.2. The global.json file at the root of the repository names the version; an older SDK stops with “A compatible .NET SDK was not found” before it builds anything. Microsoft’s install script fetches that exact version:

    Terminal window
    curl -sSL https://dot.net/v1/dotnet-install.sh | bash -s -- --version 11.0.100-rc.2.26504.105

    The script doesn’t change your PATH. Add the folder it reports (~/.dotnet on Linux and macOS, %LOCALAPPDATA%\Microsoft\dotnet on Windows) to your PATH before running dotnet.

  • Docker, running. NATS runs in a container, and the test suite starts its own containers.

  • Git, to clone the repository.

  • A trusted HTTPS development certificate, so your browser accepts https://localhost. Run this once:

    Terminal window
    dotnet dev-certs https --trust
  • An editor. Visual Studio, JetBrains Rider and VS Code with the C# Dev Kit all work, as long as they use the SDK above. None is required: everything below works from a terminal.

This is the setup for day-to-day work: each process runs from your checkout, so you can stop one, change it, and start it again while the others keep running.

  1. Clone the repository and build it.

    Terminal window
    git clone https://github.com/SharpMUSH/SharpMUSH.git
    cd SharpMUSH
    dotnet build

    The first build restores packages and takes a few minutes.

  2. Start NATS.

    Terminal window
    docker compose up -d nats

    This starts only the nats service from the repository’s Compose file, on port 4222, and leaves it running in the background.

  3. Point the game server and the connection server at it.

    Every terminal that starts one of them needs NATS_URL:

    Terminal window
    export NATS_URL=nats://localhost:4222
  4. Start the processes, each in its own terminal, from the repository root.

    Terminal window
    dotnet run --project SharpMUSH.RenderingWorker
    dotnet run --project SharpMUSH.SocketServer
    dotnet run --project SharpMUSH.Server
    dotnet run --project SharpMUSH.Client

    Start the rendering worker first, because the connection server sends all output through it. The game server is ready when https://localhost:8081/api/health answers {"status":"ready"}.

    You don’t need to set anything else. Each project’s launch settings already choose its ports, the Development environment, and the same rendering socket path (/tmp/sharpmush-render.sock) for the connection server and the rendering worker.

  5. Claim the game.

    Open https://localhost:7102. A new world opens on the setup wizard: choose a username and password for the administrator account, which is linked to God (#1). The wizard then offers to import a PennMUSH, RhostMUSH or TinyMUX database, asks which objects handle HTTP requests and events, and which bundled packages to install.

  6. Connect a MU* client.

    Point it at localhost, port 4201, or use the terminal in the portal.

To stop, press Ctrl+C in each terminal, then docker compose stop nats.

The game server keeps the world in a folder named lightning-data, in the folder you started it from: the repository root, if you followed the steps above. The keys that protect sign-in cookies go beside it, in lightning-data.dataprotection-keys.

To start again from an empty world, stop the game server and delete both folders. The next start creates a fresh world and the setup wizard appears again.

To keep the world somewhere else, set SHARPMUSH_LIGHTNING_PATH before starting the game server.

Debug whichever process holds the code you’re changing. Commands, functions and everything else the game does run in SharpMUSH.Server; telnet and the browser terminal are SharpMUSH.SocketServer; how output looks in each client is SharpMUSH.RenderingWorker; pages are SharpMUSH.Client.

  • Rider: the repository ships a run configuration named Servers and Client that starts all four.
  • Visual Studio: right-click the solution, choose Configure Startup Projects, pick Multiple startup projects, and set the four projects to Start.
  • VS Code: use the terminal commands above.

Start NATS yourself first, as in step 2. An IDE doesn’t read NATS_URL from your shell, so set it as a user environment variable (or in each project’s run settings) and restart the IDE.

To see your change the way it ships, build images from your checkout and run them together:

Terminal window
docker compose up --build

This builds three images (the game server with the portal inside it, the connection server, and the rendering worker) and starts them with NATS. The portal is at http://localhost:8080 and telnet on port 4201. The world is in the app-data Docker volume, so it survives a rebuild. Run the same command again after a change.

Open the portal on the same computer, at localhost. Browsers run it only over https or at localhost, so from another computer on your network, over plain http, it shows this page instead of the portal. Telnet works from anywhere.

A dark page headed "This portal needs a secure connection". It says the portal was opened at http://192.0.2.2:8080, and to put the game behind a certificate or open it on the server at http://localhost, and that telnet clients can connect without it.

To try the latest build of main without building anything, use the published images instead:

Terminal window
docker compose -f docker-compose-hub.yml up

Neither file is meant for hosting a game. For that, use the deploy/ folder described in Run with Docker.

Each process reads environment variables, or the same settings from its appsettings.json (a double underscore in a variable name stands for a nested setting, so ConnectionServer__TelnetPort is ConnectionServer:TelnetPort).

VariableWhat it doesDefault with dotnet run
NATS_URLWhere the game server and connection server find NATSeach starts a NATS container
SHARPMUSH_LIGHTNING_PATHThe game server’s world folderlightning-data
PENNMUSH_DATABASE_PATHA PennMUSH flatfile the game server imports when it startsnone
ASPNETCORE_URLSThe game server’s addresseshttps://localhost:8081, http://localhost:8080
ConnectionServer__TelnetPortThe connection server’s telnet port4201
ConnectionServer__HttpPortThe connection server’s port for /ws4202
Rendering__SocketPathThe socket file the connection server and rendering worker share; set both to the same file/tmp/sharpmush-render.sock
ASPNETCORE_ENVIRONMENTDevelopment turns on detailed errors and development-only endpointsDevelopment

Everything about running a real game (backups, durability, storage limits, history retention) is in the Deployment Reference.

The test framework is TUnit. Keep Docker running: the main suite starts its own NATS container, separate from the one you started above, and a MySQL container that the sql() and @sql tests query as an external database.

Terminal window
# The main suite: engine, commands, functions, server
dotnet run --project SharpMUSH.Tests
# One test class, or one test
dotnet run --project SharpMUSH.Tests -- --treenode-filter "/*/*/BuildingCommandTests/*"
dotnet run --project SharpMUSH.Tests -- --treenode-filter "/*/*/BuildingCommandTests/CreateObject"
# Portal components (no Docker needed)
dotnet run --project SharpMUSH.Tests.BUnit
# HTTP, sign-in, packages and other end-to-end checks
dotnet run --project SharpMUSH.Tests.Integration
# The Scene plugin
dotnet run --project SharpMUSH.Tests.ScenePlugin
# The portal's startup scripts
node --test tools/client-tests/*.test.mjs

The filter is /<assembly>/<namespace>/<class>/<test>, with * matching anything. Add --output detailed to see each test as it runs, and set SHARPMUSH_ENABLE_TEST_CONSOLE_LOGGING=true to see the server’s log while investigating a failure. The full main suite takes several minutes; while working, run the classes you touched.

dotnet test also runs the tests, with less readable output.

The repository includes a harness, tools/parity/run.sh, that runs the same scripted telnet sessions against a reference PennMUSH and against SharpMUSH and reports every difference in output. It needs a built PennMUSH checkout and Python 3.10 or later, and starts its own NATS without Docker. Each fix for a PennMUSH difference adds a scenario under tools/parity/scenarios/.

  • Formatting is checked on every build. C# files use tabs. If a build stops with FORMAT001, run the command in the message, which looks like this, until it reports no changes (it can take two passes):

    Terminal window
    dotnet format whitespace --folder SharpMUSH.Server --exclude "**/bin/**" --exclude "**/obj/**"
  • Warnings are errors in most projects, so a build with a new warning fails.

  • Some files are generated. The softcode parser’s grammar comes from SharpMUSH.Parser.Generated/generate-parser.py; change that script and run it rather than editing SharpMUSHParser.g4.

  • Run the tests for what you changed, and add one that fails without your change.