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 run -d --name sharpmush-dev-nats -p 127.0.0.1:4222:4222 -v "$PWD/nats.conf:/etc/nats/nats.conf:ro" nats:2-alpine -js -c /etc/nats/nats.conf

    This starts NATS in the background with the repository’s nats.conf, on port 4222 of this computer only. Next time, docker start sharpmush-dev-nats starts it again.

  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 stop sharpmush-dev-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 the images from your checkout with the Compose files in the deploy/ folder:

Terminal window
cd deploy
cp .env.example .env

In .env, set SHARPMUSH_DOMAIN=localhost. Then:

Terminal window
docker compose -f docker-compose.prod.yml up -d --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 and Caddy. The portal is at https://localhost, signed by Caddy’s own local certificate authority, so the browser warns about it until you trust that certificate. Telnet is 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.

The same files run a real game: Run with Docker covers the domain, backups and updates.

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.