wolloseum/README.md

6.8 KiB

Wolloseum

Wolloseum is a deterministic, procedurally generated game server. Players upload untrusted WASM agents (run in a sandboxed Extism/Wasmtime runtime with fuel and memory limits) that control spawned agents in a tick-based simulation. All identity and request authentication is certificate-based (Ed25519 client certificates signed by a locally issued root CA); there are no passwords or bearer tokens.

Building the server

cargo build --release

The binary is produced at target/release/wolloseum.

Configuration

The server takes its configuration from CLI flags (each with an environment variable fallback), defined in src/cli.rs:

Flag Env var Default Purpose
--data-dir WOLLOSEUM_DATA_DIR data Directory for certificates and the SQLite database
--bind WOLLOSEUM_BIND 127.0.0.1:8080 HTTP listener address

On first start the server bootstraps a PKI under <data-dir>/certificates/:

  • root-ca.pem / root-ca-key.pem — self-signed root CA
  • server.pem / server-key.pem — HTTP server certificate (issued for localhost)
  • admin-client.pem / admin-client-key.pem — the initial admin client identity

The SQLite database is created at <data-dir>/wolloseum.sqlite3 and stores identities, used request nonces, per-player agent WASM, and the core simulation WASM module. Private key files and the certificate directory are written with restrictive (owner-only) permissions on Unix.

Running the server

cargo run --release -- serve --data-dir data --bind 127.0.0.1:8080

On startup the server prints the admin certificate ID and the paths to the admin client certificate/key and root CA certificate — you'll need these to issue authenticated CLI/API requests.

API endpoints

All /api/* endpoints except /api/status and /api/events require request signing. Each signed request must include:

X-Client-Id:          <sha256 hex of the client's certificate PEM>
X-Request-Timestamp:  <unix seconds, within 60s of server time>
X-Request-Nonce:      <unique, ASCII alphanumeric/-/_, replay-checked>
X-Request-Signature:  <base64 Ed25519 signature>

The signed message is METHOD\nPATH\nTIMESTAMP\nNONCE\n followed by the raw request body bytes.

Method Path Auth Description
GET / none Player web UI
GET /admin none Admin web UI
GET /api/status none Current game running state
GET /api/events none (WebSocket) Live game/agent/core state change events
POST /api/create_user admin Create a player identity, returns certificate + private key
POST /api/delete_user admin Delete a player identity and their uploaded agent
POST /api/start_game admin Resume the simulation
POST /api/stop_game admin Pause the simulation
POST /api/update_agent any active identity Upload/replace the caller's agent WASM module
POST /api/upload_core admin Upload/replace the core simulation WASM module

/api/upload_core

Replaces the core simulation logic module. The request body is the raw WASM bytes (not JSON), following the same conventions as /api/update_agent:

  • Body limit: 1 MiB (Content-Length over the limit is rejected)
  • The module is sandboxed-loaded (no WASI imports, bounded memory) to confirm it is valid before being persisted
  • Requires admin authentication (signed request headers as above)
  • On success the module is stored in the core_module table (single active row) and a core_uploaded event is published over /api/events
curl -k http://127.0.0.1:8080/api/upload_core \
  -X POST \
  --data-binary @core_simulation.wasm \
  -H "x-client-id: $ADMIN_ID" \
  -H "x-request-timestamp: $TS" \
  -H "x-request-nonce: $NONCE" \
  -H "x-request-signature: $SIGNATURE"

In practice, use the CLI (below) rather than constructing signatures by hand.

Using the CLI client

The same binary provides a signing HTTP client:

cargo run --release -- cli \
  --server http://127.0.0.1:84080\
  --root-ca data/certificates/root-ca.pem \
  --client-cert data/certificates/admin-client.pem \
  --client-key data/certificates/admin-client-key.pem \
  status

Available subcommands (see src/cli.rs): status, create-user <username>, delete-user <client_id>, stop-game, start-game, update-agent <path>.

Building the example WASM agent

example-wasm-agent/ is a standalone crate implementing a minimal Extism plugin agent matching the ABI expected by src/wasm_agent.rs (a JSON decide call taking {abi_version, tick, x, y, adjacent_terrain} and returning {abi_version, action}). Movement is a deterministic function of the tick and position — no RNG, clock, or host access is used, so replays are reproducible.

Build it to a .wasm file:

cd example-wasm-agent
rustup target add wasm32-unknown-unknown # if not already installed
cargo build --release --target wasm32-unknown-unknown

The compiled module is at example-wasm-agent/target/wasm32-unknown-unknown/release/example_wasm_agent.wasm.

Uploading the example agent

First create a player identity (as admin), then upload the agent WASM signed with that player's own certificate:

# As admin, create a player and save the returned certificate/private key:
cargo run --release -- cli \
  --client-cert data/certificates/admin-client.pem \
  --client-key data/certificates/admin-client-key.pem \
  create-user alice
# -> write the returned certificate_pem/private_key_pem to alice.pem / alice-key.pem

# As alice, upload the compiled agent:
cargo run --release -- cli \
  --client-cert alice.pem \
  --client-key alice-key.pem \
  update-agent example-wasm-agent/target/wasm32-unknown-unknown/release/example_wasm_agent.wasm

Core simulation WASM can be uploaded the same way by an admin using POST /api/upload_core (no dedicated CLI subcommand yet — use curl as shown above, or extend the CLI client following the same pattern as update-agent).