Running your own server

Bezgelor is free software (GNU AGPL v3), and anyone can run a copy: for themselves, for friends on a home network, or as a public server. This page is the shortest path from a fresh copy of the code to logging in with the game client and making yourself an administrator. Admin commands, the next page, lists everything an administrator can type in the game.

Bezgelor is a work in progress. Before you start, see Where we stand, which lists what works today, zone by zone.

The steps below come from the repository's own README, AGENTS.md and the deployment notes, which have more detail.

What you need

  • Your own WildStar client, the final live build (16042). Bezgelor ships no client files and no game data.
  • Erlang 28 and Elixir 1.19. The exact versions are pinned in mise.toml; mise install sets them up.
  • PostgreSQL 14 or newer. docker compose up -d starts one on port 5433; any other works through the POSTGRES_* settings below.
  • Python 3.8 or newer, standard library only, for the extractors.
  • Memory: the whole server uses about 1.8 GB idle with the open-world zones started; players and dungeon instances add to that. A public server wants 8 GB or more.

1. Get the code

git clone https://github.com/Bezgelor/bezgelor.git
cd bezgelor
mise install

2. Extract the game data from your client

The public repository carries the extractors, not the client's data. The creatures, items, spells, zones and text the server needs are read out of your own installation with one command (a few minutes):

python3 -I tools/extract/extract_all.py --client-dir "/path/to/WildStar/Patch" --out ~/.local/share/bezgelor/data

The server reads that folder by default; set BEZGELOR_DATA_DIR to use another. The result is the game's own data, so keep it to yourself as you keep the client. Terrain heights are optional and extracted separately (--terrain, read from BEZGELOR_TERRAIN_DIR); without them the server still runs, with no ground heights to check positions against. tools/extract explains every option.

3. Configure

Settings come from environment variables. The ones that matter for a server of your own:

Variable Default What
WORLD_PUBLIC_ADDRESS 127.0.0.1 The IPv4 address players' clients reach the world server on (an address, not a name)
POSTGRES_HOST, POSTGRES_PORT localhost, 5433 Where the database is
POSTGRES_USER, POSTGRES_PASSWORD, POSTGRES_DB bezgelor, bezgelor_dev, bezgelor_dev The database's user, password and name
BEZGELOR_DATA_DIR ~/.local/share/bezgelor/data The data you extracted
BEZGELOR_TERRAIN_DIR ~/.local/share/bezgelor/terrain The terrain heights, if you made them
REALM_NAME Bezgelor The realm's name in the client's realm list (used by a release's seeding)
AUTH_PORT, REALM_PORT, WORLD_PORT 6600, 23115, 24000 The game servers' ports
STOREFRONT_MODE paid free shows every store price as 0
DISCORD_INVITE_URL unset A Discord invite shown on the website; unset hides the Discord links
WRITE_BEHIND, WRITE_BEHIND_ASYNC_COMMIT on, on How player progress is saved; see Stopping the server

The world's public address. The client first logs in on port 6600, then the realm server tells it which address to find the world server on. That address is WORLD_PUBLIC_ADDRESS, and it is written into the realm list when the database is seeded. If the client runs on the same machine, the default is right. Otherwise set it to the server's address before step 4. If you change it later, seed again: cd apps/bezgelor_db && mix run priv/repo/seeds.exs.

Ports.

Port What Open it to
6600 Login server Players (TCP)
23115 Realm server Players (TCP)
24000 World server Players (TCP)
4000 The website: accounts, the admin pages Players, or put a web server in front of it
5433 PostgreSQL Nobody: keep it on the machine

4. Create the database and start the servers

./scripts/setup.sh     # PostgreSQL in Docker, then mix deps.get and mix ecto.setup
./scripts/start.sh     # every server and the website, with an interactive shell

With your own PostgreSQL instead of Docker, set the POSTGRES_* variables, then run mix deps.get && mix ecto.setup, and start with iex -S mix phx.server. mix ecto.setup creates the database, runs the migrations and seeds the permissions, the roles and the realm list. The development seeds add a second realm, Mikros, for testing; nothing answers on it, so pick Bezgelor.

./scripts/stop.sh stops the servers, and the log is in logs/dev.log.

5. Make an account

Open http://<server>:4000/register and sign up with an email address and a password. In the development setup no email is sent; the verification message waits at http://<server>:4000/dev/mailbox. The link in it starts with http://localhost:4000; from another machine, put your server's address in its place.

You do not have to verify the email to play. Only the website asks for it; the game login (STS), the realm server and the world server never look at it, so an account that has not been verified can log in with the client and make characters. If you run a release without working mail delivery, people can play all the same; to mark an account verified by hand, run UPDATE accounts SET email_verified_at = now() WHERE email = '...'; in psql.

6. Point the client at your server

On Windows, run this command on the machine with the client, from its Client64 folder, with your server's address in place of 127.0.0.1 when it runs elsewhere. On Linux, Steam Deck or macOS, the same command line is started through Proton or Wine: see the Linux, Steam Deck and macOS client guides. Their launch scripts take the server's address as their argument.

WildStar64.exe /auth 127.0.0.1 /authNc 127.0.0.1 /lang en /patcher 127.0.0.1 /SettingsKey WildStar /realmDataCenterId 9

Log in with the email and password of your account, pick the realm and make a character.

7. Make yourself an administrator

In the shell that ./scripts/start.sh opened, with your account's email:

BezgelorDb.Release.grant_role("you@example.com", "Super Admin")

That gives your account every permission: every in-game admin command, every admin page, and giving roles to other people. The website's admin pages (/admin) also ask you to turn on 2-Step Verification first (Settings). For the people who help you run the server, pick a smaller role: Moderator (chat, mutes, kicks, bans and reports), Game Master (helps players: teleports, levels, quests) or Senior GM (also currency, items and events), or make your own. Roles and permissions explains the roles, how to give one to someone else, how to make your own, and the development switch that makes everyone an admin.

The Field Kit add-on (optional)

The Bezgelor Field Kit is an add-on for the client: a bug report wizard for every player, and a quest setup and quick action panel for testers. Your server already speaks to it; there is nothing to configure. Your website serves it at /downloads/field-kit.zip, zipped from addons/BezgelorFieldKit when the portal is built, and the Field Kit guide tells Windows players how to install it (unzip into %APPDATA%\NCSOFT\WildStar\Addons; the add-on has not yet been tested on Linux, Steam Deck or macOS). To hand the zip out another way, mix bezgelor.field_kit.zip --out dist/ writes it to a folder. The client loads it with no setting, and /fk opens it. Players may ask why the Addons entry of the options menu is greyed out at the login screen: the stock client enables it only in the world (Esc there), whatever the server.

It talks to the server over the client's own add-on channels (ICComm), not chat, so nothing shows in anyone's chat tabs. It is optional: every request it sends stands for a /c command, needs that command's permission (admin commands) and writes the same audit log entry. A player without the add-on types the commands instead. The design notes describe the protocol.

Development or a release

./scripts/start.sh runs the development setup: the code reloads as it changes, errors show their details in the browser, and every account can use the admin commands (gm_commands_for_all: true in config/dev.exs). That is right for trying Bezgelor yourself and wrong for a server strangers play on.

For a server other people use, build a release (mix release, production settings). In a release the admin switch is off, and the world server refuses to start if it is turned on (a private test server can set BEZGELOR_ALLOW_GM_FOR_ALL=1 to allow it; see admin commands); it reads its secrets from the environment and refuses to start without the ones it needs: DATABASE_URL, SECRET_KEY_BASE, CLOAK_KEY, PHX_HOST, WORLD_PUBLIC_ADDRESS and RESEND_API_KEY. deploy/env.example lists every setting. The release carries no client data: copy your extracted folder to the server and point BEZGELOR_DATA_DIR at it. The deployment notes describe the scripts we use for our own server (one Ubuntu 24.04 machine, Caddy in front of the website, nightly database dumps) and, at the end, how to build and run a release anywhere else.

Two things are not finished for other hosts yet. A release sends its account emails through Resend only, and needs a Resend key even if you would rather use another mail service. The deployment scripts assume Ubuntu 24.04; elsewhere you build and run the release yourself.

Stopping the server and what a crash costs

Loot, experience, credits and quest progress reach the player first and are saved to the database a moment later, by one writer per character: a slow disk then slows the saving, not the game. Until a change is saved it waits in a queue in the server's memory. A clean stop saves the whole queue; killing the program loses it.

Stop it properly. With our deployment scripts, systemctl stop bezgelor@<instance> (or restart). For a release you run yourself, bin/bezgelor stop. In development, ./scripts/stop.sh, or System.stop() in the server's shell. Each of these logs every player out and saves everything queued before the database connection closes. It usually takes a few seconds; with a slow disk it can take longer, and systemd waits up to three minutes (TimeoutStopSec), which covers the slowest case. Let it finish.

Never kill -9 the server, and don't end the development shell with Ctrl+C twice or Ctrl+C then a: those end the program at once, without saving the queue. A normal reboot is fine: systemd stops the service first and waits for it.

What a crash costs players. If the program dies anyway (killed, out of memory, the machine loses power), players lose what was still queued: normally less than a second of loot, experience and quest progress. If PostgreSQL or the machine itself crashes, up to about the last half second of saved progress can go too, because those saves don't wait for the disk (WRITE_BEHIND_ASYNC_COMMIT); the database is never corrupted by it. Trades, mail, the marketplace, vendors, crafting and quest turn-ins always save first and wait for the disk, so a crash can roll a player back a little but never duplicates an item or credits.

Watching it. The admin metrics page (/admin/metrics, permission server.view_metrics) has a "Saving player progress" card: how many saves are queued and for which characters, how old the oldest is, how far behind the saving was over the last five minutes, retries, and dropped saves. The health check (http://127.0.0.1:4002/health) reports the same in its write_behind section, and the server is reported "degraded" while the saving lags more than five seconds or a save was dropped in the last ten minutes.

A dropped save is one that failed five times in a row and was given up: the player was told about a change (an item, experience, a quest step) that the database never got, and it will be missing at their next login. The log line starts with [WriteBehind] and says what it was, for which character, and why (most often the database was unreachable or the disk full). Fix the cause; if it matters to the player, give the item or progress back with an admin command.

Settings (in /etc/bezgelor/<instance>.env, or the environment):

Variable Default What
WRITE_BEHIND on off saves each change before the player is told, as older versions did: nothing waits in a queue, but a slow disk makes loot and experience slow to arrive
WRITE_BEHIND_ASYNC_COMMIT on off makes the queued saves wait for the disk: a crash of PostgreSQL or the machine then loses none of them, at some cost in speed

When something goes wrong

Type /c bug in the game, or open an issue with what you did and what happened. The server's log (logs/dev.log in development, journalctl -u bezgelor@<instance> for our deployment scripts) usually says why a client was turned away.