# Server

The authoritative server executable.

## Running

```
Usage: tw_server [--quicr-port <port>] [--cluster-port <port>]
  --quicr-port    UDP port for player connections  (default: 8101)
  --cluster-port  UDP port for zone-server peering (default: 8102)
```

Metrics reporting to TimescaleDB stays off until `TIMESCALEDB_HOST` is set:

| Variable               | Default        |
|------------------------|----------------|
| `TIMESCALEDB_HOST`     | unset (off)    |
| `TIMESCALEDB_PORT`     | `5432`         |
| `TIMESCALEDB_DB`       | `mmo`          |
| `TIMESCALEDB_USER`     | `mmo`          |
| `TIMESCALEDB_PASSWORD` | empty          |
| `TIMESCALEDB_TABLE`    | `zone_metrics` |

## Building natively

See the root `README.md`. The target is `tw_server` and the binary lands in
`<build-dir>/modules/server/tw_server`.

## Building the image

`Dockerfile` has three stages: a toolchain stage holding the C++ build
environment, a build stage that compiles `tw_server` with clang, and a runtime
stage that carries only the binary and its shared libraries. The build context is
the repository root, so run it from there:

```bash
$ docker build -f modules/server/Dockerfile -t tw_server .
```

The toolchain stage installs the compiler and tools (clang, cmake, ninja, mold,
glslangValidator) and the development packages CMake looks for (Vulkan, SDL2,
protobuf, libpq, libpqxx) from apt. It touches no source, so it only rebuilds
when that package list changes.

The build stage configures a Release build on top of it with `CC=clang` /
`CXX=clang++`. glm, entt, Jolt, spdlog, expected, Catch2 and tracy are cloned
while configuring, so the build needs network access. `.dockerignore` keeps the
local build directories and those cloned sources out of the context.

## Reusing the toolchain

Layer caching already keeps apt out of a rebuild, but the cache is local and dies
with `docker builder prune`. To pin the toolchain down, build that stage on its
own and tag it:

```bash
$ docker build -f modules/server/Dockerfile --target toolchain -t tw_toolchain .
```

`TOOLCHAIN_IMAGE` then points the build stage at it, and the apt step is skipped
outright rather than cache-hit:

```bash
$ docker build -f modules/server/Dockerfile \
      --build-arg TOOLCHAIN_IMAGE=tw_toolchain -t tw_server .
```

The default is the in-file `toolchain` stage, so a plain build still works
standalone. Any registry tag works too, which is the useful form on CI. To carry
it between machines by hand:

```bash
$ docker save tw_toolchain | zstd -o tw_toolchain.tar.zst
$ zstd -dc tw_toolchain.tar.zst | docker load
```

The runtime stage is `gcr.io/distroless/cc-debian13:nonroot` — glibc, libstdc++
and a `nonroot` user, no shell and no package manager. Since nothing can be
installed there, the build stage walks `ldd` over the binary and stages every
shared library it resolved into `/rootfs`, which the runtime stage copies in
whole. glibc and the loader are filtered out of that list: the binary is built
against 2.39 and the runtime image ships 2.41, which runs it, but the two must
not be mixed.

Nothing in the image can be executed except the server, so `docker exec` and
`docker run --entrypoint` are of no use for poking around. Swap the base for
`gcr.io/distroless/cc-debian13:debug-nonroot` when a busybox shell is needed.

Run it, publishing both UDP ports:

```bash
$ docker run --rm -p 8101:8101/udp -p 8102:8102/udp tw_server
```

Arguments after the image name reach the binary, and environment variables are
passed as usual:

```bash
$ docker run --rm -p 9101:9101/udp -e TIMESCALEDB_HOST=timescale \
      tw_server --quicr-port 9101
```
