A discovery server that allows Servers to make their presence known to clients (World List impl).
  • Elixir 92.7%
  • Dockerfile 7.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Patrick W. 399f3f7a77
All checks were successful
ci / image (push) Successful in 15s
chore: add some logging
2026-06-28 16:35:43 -04:00
.forgejo/workflows feat: add ci and dockerfile 2026-06-28 16:14:04 -04:00
config feat: initial commit 2026-06-28 15:49:47 -04:00
lib chore: add some logging 2026-06-28 16:35:43 -04:00
priv feat: initial commit 2026-06-28 15:49:47 -04:00
test feat: initial commit 2026-06-28 15:49:47 -04:00
.dockerignore feat: add ci and dockerfile 2026-06-28 16:14:04 -04:00
.formatter.exs feat: initial commit 2026-06-28 15:49:47 -04:00
Dockerfile feat: add ci and dockerfile 2026-06-28 16:14:04 -04:00
mix.exs feat: initial commit 2026-06-28 15:49:47 -04:00
README.md feat: add ci and dockerfile 2026-06-28 16:14:04 -04:00

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 (132767), host, port (165535). 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/port are 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)