156 lines
7.3 KiB
Markdown
156 lines
7.3 KiB
Markdown
# Wolloseum
|
|
|
|
Wolloseum is a deterministic, procedurally generated game server. Players
|
|
upload untrusted WASM agents that control spawned agents in a tick-based
|
|
simulation. Agent code runs in a sandboxed Extism/Wasmtime runtime with fuel
|
|
and memory limits.
|
|
|
|
Identity is based on Ed25519 keypairs. The server stores public keys; private
|
|
keys stay with their owners. HTTPS and WSS termination are intended to be
|
|
provided by a reverse proxy such as nginx or Caddy. The Wolloseum process
|
|
serves HTTP and does not create TLS certificates.
|
|
|
|
## Build and Run
|
|
|
|
```sh
|
|
cargo build --release
|
|
cargo run --release -- serve --data-dir data --bind 127.0.0.1:8080
|
|
```
|
|
|
|
### Docker
|
|
|
|
Build and start the server with Docker Compose:
|
|
|
|
```sh
|
|
docker compose up --build -d
|
|
docker compose logs -f wolloseum
|
|
```
|
|
|
|
The HTTP service is published on `127.0.0.1:8080`; configure nginx or Caddy to
|
|
proxy HTTP and WebSocket traffic to that address and terminate TLS. Compose
|
|
stores the SQLite database and generated administrator keypair in `./data`.
|
|
The entrypoint sets the data tree's ownership before starting the server as an
|
|
unprivileged user.
|
|
|
|
Set `WOLLOSEUM_UID` and `WOLLOSEUM_GID` in the environment or a Compose `.env`
|
|
file to choose the container process identity. Both default to `1000`. The
|
|
ownership of the host `./data` tree is changed to these IDs at startup.
|
|
|
|
The first startup logs the administrator identity ID and the key path inside
|
|
the container. Copy the key to a secure host location for admin sign-in:
|
|
|
|
```sh
|
|
docker compose cp wolloseum:/var/lib/wolloseum/keys/admin-private.pem /secure/path/admin-private.pem
|
|
chmod 600 /secure/path/admin-private.pem
|
|
```
|
|
|
|
Back up the key and database volume. To stop the server while preserving data,
|
|
run `docker compose down`. To remove all persistent identity and game data as
|
|
well, run `docker compose down --volumes`.
|
|
|
|
The server bootstraps an administrator keypair on first start:
|
|
|
|
- `data/keys/admin-private.pem` is the owner-only Ed25519 PKCS#8 private key.
|
|
- The server prints the corresponding identity ID at startup.
|
|
- Keep a secure backup of the private key. The server cannot recover it.
|
|
|
|
Existing installations continue to use `data/certificates/admin-client-key.pem`
|
|
and the old certificate fingerprint as the admin identity ID when those legacy
|
|
files are present. Existing player identities and uploaded data remain in the
|
|
SQLite database. HTTPS certificates, if needed, belong to the reverse proxy.
|
|
|
|
Configuration flags and environment variables:
|
|
|
|
| Flag | Environment variable | Default | Purpose |
|
|
| ------------ | -------------------- | ---------------- | ------------------------------------------- |
|
|
| `--data-dir` | `WOLLOSEUM_DATA_DIR` | `data` | Keypairs and SQLite database directory |
|
|
| `--bind` | `WOLLOSEUM_BIND` | `127.0.0.1:8080` | HTTP listener, usually private to the proxy |
|
|
|
|
## Web Interfaces
|
|
|
|
- `/` is the player console. Users sign in with their identity key and upload or replace their agent WASM.
|
|
- `/admin` is the administrator console. It creates and removes users, pauses or resumes the simulation, and replaces the core WASM module.
|
|
- `/viewer` is a public live event viewer. It connects to `/api/events`, tracks event sequence numbers, and restores state from snapshots after reconnecting.
|
|
|
|
The admin console generates a fresh Ed25519 keypair when creating a player.
|
|
The server receives only the public key. The browser downloads a JSON identity
|
|
bundle containing the new identity ID and private key; deliver that bundle to
|
|
the player through a secure channel. The private key is not stored by the
|
|
server.
|
|
|
|
On the admin page, enter the startup identity ID and select
|
|
`data/keys/admin-private.pem`. For an existing installation, select the legacy
|
|
admin key and use the identity ID printed by the server.
|
|
|
|
## Browser Authentication
|
|
|
|
1. The browser requests `POST /api/auth/challenge` with `{ "client_id": "..." }`.
|
|
2. The server returns a random, short-lived challenge.
|
|
3. The browser signs the decoded challenge bytes using Ed25519 and posts the identity ID, challenge, and base64 signature to `POST /api/auth/login`.
|
|
4. The server verifies the signature against the stored public key and returns an opaque session token.
|
|
5. The browser stores the token and sends it as `Authorization: Bearer <token>` on protected API requests.
|
|
|
|
Challenges expire after five minutes and are single-use. Session tokens expire
|
|
seven days after the last authenticated API interaction. Each authenticated
|
|
request extends the expiry. Only a SHA-256 hash of each token is persisted.
|
|
Revoking an identity by deleting it also removes its sessions.
|
|
|
|
## API
|
|
|
|
| Method | Path | Access | Purpose |
|
|
| ------ | --------------------- | ---------------- | ------------------------------------------------- |
|
|
| `POST` | `/api/auth/challenge` | Public | Request an identity challenge |
|
|
| `POST` | `/api/auth/login` | Public | Verify a signed challenge and issue a token |
|
|
| `GET` | `/api/me` | Session | Read the signed-in identity and agent status |
|
|
| `GET` | `/api/users` | Admin session | List identities and agent digests |
|
|
| `GET` | `/api/status` | Public | Read the simulation running state |
|
|
| `GET` | `/api/events` | Public WebSocket | Receive snapshots and ordered state events |
|
|
| `POST` | `/api/create_user` | Admin session | Register a username and base64 Ed25519 public key |
|
|
| `POST` | `/api/delete_user` | Admin session | Delete a player and its uploaded agent |
|
|
| `POST` | `/api/start_game` | Admin session | Resume the simulation |
|
|
| `POST` | `/api/stop_game` | Admin session | Pause the simulation |
|
|
| `POST` | `/api/update_agent` | User session | Upload or replace the caller's agent WASM |
|
|
| `POST` | `/api/upload_core` | Admin session | Upload or replace the core simulation WASM |
|
|
|
|
Agent and core WASM uploads are limited to 1 MiB and validated in the
|
|
sandboxed runtime before they are persisted. Existing signed request headers
|
|
remain accepted for compatibility with older clients.
|
|
|
|
## CLI
|
|
|
|
The binary also provides an authenticated client. New installations use the
|
|
admin keypair directly:
|
|
|
|
```sh
|
|
cargo run --release -- cli \
|
|
--client-key data/keys/admin-private.pem \
|
|
create-user alice
|
|
```
|
|
|
|
`create-user` prints the identity ID and private key as JSON. For commands run
|
|
as a player, use that player's private key and identity ID:
|
|
|
|
```sh
|
|
cargo run --release -- cli \
|
|
--client-id <identity-id> \
|
|
--client-key alice-private.pem \
|
|
update-agent example-wasm-agent/target/wasm32-unknown-unknown/release/example_wasm_agent.wasm
|
|
```
|
|
|
|
Legacy installations can pass `--client-cert` and `--client-key` to derive the
|
|
historical certificate-based admin ID. The CLI exchanges a challenge for a
|
|
session token before making protected requests.
|
|
|
|
## Example Agent
|
|
|
|
`example-wasm-agent/` is a standalone crate implementing the Extism plugin ABI
|
|
used by `src/wasm_agent.rs`. Build it with:
|
|
|
|
```sh
|
|
cd example-wasm-agent
|
|
rustup target add wasm32-unknown-unknown
|
|
cargo build --release --target wasm32-unknown-unknown
|
|
```
|
|
|
|
The compiled module is at
|
|
`example-wasm-agent/target/wasm32-unknown-unknown/release/example_wasm_agent.wasm`. |