- Elixir 92.7%
- Dockerfile 7.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| config | ||
| lib | ||
| priv | ||
| test | ||
| .dockerignore | ||
| .formatter.exs | ||
| Dockerfile | ||
| mix.exs | ||
| README.md | ||
ServiceDiscovery
A tiny, decentralized service-discovery registry for Black Lobster game worlds — and the source of the world list the RS468 client shows on its world-select screen.
Game worlds self-register by POSTing their status (fire-and-forget); the registry keeps whoever reported recently and publishes the aggregated list. It owns no static roster — worlds appear by reporting and disappear by going silent (TTL eviction) — so the network is decentralized by default: boot a world, point it here, done.
The state is in-memory and ephemeral. On restart the registry is empty and refills from the next round of heartbeats, so it's crash-safe with no persistence.
Runs on OTP built-ins only (:inets for HTTP, :json for reports, :crypto
for the secret compare) — zero external dependencies.
HTTP surface
| Method | Path | For | Body / notes |
|---|---|---|---|
GET |
/slr.ws |
clients | Binary RS468 world list. The game client fetches …/slr.ws?order=LPWM. |
POST |
/report |
worlds | JSON self-registration (see below). |
GET |
/worldlist |
operators | The live worlds as JSON. |
GET |
/health |
probes | 200 ok. |
Report (world → registry)
POST /report
Content-Type: application/json
x-bl-secret: <SD_REPORT_SECRET> # only if a secret is configured
{"id": 1, "host": "world1.example", "port": 43594,
"population": 42, "max_pop": 2000, "members": true, "activity": "Black Lobster"}
Required: id (1–32767), host, port (1–65535). Optional: population (0),
max_pop (2000), members (false), activity (""). Re-reporting an id
replaces its row (last-writer-wins). Worlds should report every few seconds; the
TTL must be comfortably larger than the report interval.
Advertise the reachable address.
host/portare what the client dials, so report the externally-reachable address (behind NAT / a container, this is not the address the process sees for itself). Configure it on each world; don't auto-detect.
World list (registry → client)
GET /slr.ws returns the length-prefixed binary the client parses — see
ServiceDiscovery.Encoder for the exact wire format (a Black Lobster extension
of the stock 468 format that carries host/port per world).
Configuration (env, prod)
| Var | Default | Meaning |
|---|---|---|
SD_HTTP_PORT |
8080 |
Port for /slr.ws + /report. |
SD_TTL_SECONDS |
30 |
A world unseen this long drops off the list. |
SD_REPORT_SECRET |
(unset) | If set, /report requires header x-bl-secret. |
Binds all interfaces. Run it next to (or apart from) the game worlds; point each
world's BL_WORLD_LIST_ENDPOINT at http://<this-host>:<port>/report, and the
client's worldlist.url at http://<this-host>:<port>/slr.ws?order=LPWM.
Deploy
CI (.forgejo/workflows/ci.yml) builds the release image
(git.dunk.works/blacklobster/servicediscovery:latest) on push to main. It
runs as the worldlist service in the Server's docker-compose.yml — that
is the deployment most operators use; there is no separate compose here. Each
game world reports to it via BL_WORLD_LIST_ENDPOINT.
Develop
mix deps.get # nothing to fetch — zero deps
mix test
mix run --no-halt # boots on :8080 (dev)