Running a server

From nothing to a Worlds server other people can log into. Both servers in this project build and run the same way; where they differ is called out.

1. Build it

There are no external dependencies. A C++20 compiler and a standard library is the whole list.

git clone <the repo> && cd WorldServer
./build.sh        # configure, compile, run the test suite
./run.sh          # world server + upgrade server + admin console

build.sh runs the tests as part of building, on purpose. If it finishes, the protocol serialiser round-trips and the known byte sequences still match.

2. Check it without Wine

Before involving a real client, prove the server works on its own. This is the step that saves the most time and almost nobody does it first.

./bin/fakeclient 127.0.0.1 6650 mynick   # a minimal client, no Wine needed
./bin/worldsctl who                      # who is online

fakeclient reproduces the handshake and prints what comes back. If it gets in, the server is fine and any remaining problem is in the client's configuration. If it does not, you have a server problem and a readable dump of where it stopped.

3. Configure it

Two files. Copy the .example versions and edit those.

FileHolds
server.confPorts, the message of the day, account policy, where content lives. Everyday settings.
worldsd.ini (aiko.ini on AikoWorlds)Administrators, IP pinning, rate limits, connection caps.

AikoWorlds adds its own section on top of that:

ANNOUNCE=1                  announce the extensions
WARN_LEGACY=1               tell older clients, once
CLIENT_URL="https://..."    where they can get a client that understands
DATABASE="data/aiko.db"     SQLite instead of text files
CONTENT="../WorldServer/content"

ANNOUNCE=0 makes AikoWorlds indistinguishable from the original protocol — not one extra byte goes on the wire. That switch exists so you can always answer the question "is my server doing something non-standard" with a definite no.

Storage: text files or SQLite

AikoWorlds can keep accounts, buddies, rooms, bans, mutes and stored properties in SQLite instead of a directory of text files. They behave identically — everything is held in memory and written out whole after each change — so the choice is only about what else can read it while the server is running. Text files are easier to inspect and to back up with cp; SQLite is easier to query.

4. Accounts and moderation

Everything is done with worldsctl, which talks to the running server:

./bin/worldsctl who              # who is online, with addresses
./bin/worldsctl kick <name>
./bin/worldsctl ban <name>
./bin/worldsctl mute <name>
./bin/worldsctl announce "text"  # to everyone

Accounts are created on first login by default, or you can make them ahead of time. Administrators are listed in the .ini, shown in blue in the client, and can optionally be pinned to an IP address, so an admin name is useless to someone who steals the password from somewhere else.

Set rate limits and connection caps before you expose anything to the internet, and put it behind a firewall. They are in the .ini and they exist because the protocol has no protection of its own.

5. Passwords, honestly

The Worlds protocol transmits passwords in clear text. That is how it was designed in 1995 and the client supports nothing else. This server stores them hashed — SHA-256, salted, 20 000 iterations — which protects the account file but not the session. Anyone on the network path can read them, and so can you, as the operator.

Tell your users this. Nobody should reuse a password on a Worlds server, on yours or anyone else's. See Trust & safety.

6. Serving content

The server has an HTTP server built in for worlds, avatars and textures, so you do not need a second one. CONTENT= points at the tree.

Two things live under it that matter more than they look:

Users can host their own worlds and avatars on their own web space; the server does not have to hold them. docs/15-hosting-content.md covers that.

7. Rooms

Room ids must be deterministic. The client caches the mapping from a room's name to its number forever, and no command invalidates that cache. If your server restarts and hands out ids from a counter, returning clients will subscribe to an id that now means a different room, or nothing at all.

Derive the number from a stable hash of the long name, or from a persisted table. Both servers here do; if you write your own, this is the mistake to avoid.

8. Going public

  1. Forward port 6650 (or whatever you configured) and your HTTP content port.
  2. Set connection caps and rate limits first.
  3. Install the systemd unit so it comes back after a reboot — systemd/ has a user service ready.
  4. Tell people the address in the form the client wants: worldserver://your.host:6650. The port is mandatory on the client side.
  5. Remember that 90% of the worlds in a stock installation redirect to your server with one override.ini edit, and the other 10% have different hosts baked in and need their files patched. There is a script for that.

9. Reading what is happening

QuestionHow to answer it
Is the server itself fine?./bin/fakeclient. If it logs in, the server is fine.
Who is on and from where?./bin/worldsctl who
What did the client actually receive?Set debug=2048 in the client's worlds.ini and read Gamma.Log. Every packet, in hex.
Why does a change to a world do nothing?The client's 72-hour cache. NetCacheModifiedCheck=0.

Comparing the server's own log against the client's hex dump, byte for byte, is the technique that resolves almost everything. That is what debug=2048 is for.