wolloseum/README.md

162 lines
6.8 KiB
Markdown
Raw Normal View History

2026-09-29 12:21:04 +00:00
# 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
```sh
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](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
```sh
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`
```sh
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:
```sh
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](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/](example-wasm-agent) is a standalone crate implementing
a minimal Extism plugin agent matching the ABI expected by
[src/wasm_agent.rs](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:
```sh
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:
```sh
# 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`).