Backend: - Complete FastAPI backend with 42+ REST endpoints (auth, servers, config, players, bans, missions, mods, games, system) - Game adapter architecture with Arma 3 as first-class adapter - WebSocket real-time events for status, metrics, logs, players - Background thread system (process monitor, metrics, log tail, RCon poller) - Fernet encryption for sensitive config fields at rest - JWT auth with admin/viewer roles, bcrypt password hashing - SQLite with WAL mode, parameterized queries, migration system - APScheduler cleanup jobs for logs, metrics, events Frontend: - Server Detail page with 7 tabs (overview, config, players, bans, missions, mods, logs) - Settings page with password change and admin user management - Create Server wizard (4-step; known bug: silent validation failure) - New hooks: useServerDetail, useAuth, useGames - New components: ServerHeader, ConfigEditor, PlayerTable, BanTable, MissionList, ModList, LogViewer, PasswordChange, UserManager - WebSocket onEvent callback for real-time log accumulation - 120 unit tests passing (Vitest + React Testing Library) Docs: - Added .gitignore, CLAUDE.md, README.md - Updated FRONTEND.md, ARCHITECTURE.md with current implementation state - Added .env.example for backend configuration Known issues: - Create Server form: "Next" buttons don't validate before advancing, causing silent submit failure when fields are invalid - Config sub-tabs need UX redesign for non-technical users
1488 lines
32 KiB
Markdown
1488 lines
32 KiB
Markdown
# Languard Server Manager — API Reference
|
||
|
||
## Base URL
|
||
|
||
```
|
||
http://localhost:8000/api
|
||
```
|
||
|
||
All HTTP endpoints are prefixed with `/api`. The WebSocket endpoint is at `/ws` (no `/api` prefix).
|
||
|
||
## Authentication
|
||
|
||
All endpoints except `POST /auth/login` and `GET /system/health` require a JWT bearer token:
|
||
|
||
```
|
||
Authorization: Bearer <access_token>
|
||
```
|
||
|
||
For WebSocket, pass the token as a query parameter:
|
||
|
||
```
|
||
ws://localhost:8000/ws?token=<access_token>&server_id=1
|
||
```
|
||
|
||
JWT payload:
|
||
|
||
```json
|
||
{
|
||
"sub": "user_id",
|
||
"username": "string",
|
||
"role": "admin|viewer",
|
||
"exp": 1745000000
|
||
}
|
||
```
|
||
|
||
### Roles
|
||
|
||
| Role | Access |
|
||
|---------|-----------------------------------------------------------|
|
||
| `admin` | Full access: create, update, delete, start/stop servers |
|
||
| `viewer`| Read-only: list servers, view config (sensitive masked) |
|
||
|
||
## Standard Response Envelope
|
||
|
||
All responses use a consistent envelope:
|
||
|
||
**Success:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { ... },
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error:**
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"data": null,
|
||
"error": {
|
||
"code": "NOT_FOUND",
|
||
"message": "Server with id 5 not found"
|
||
}
|
||
}
|
||
```
|
||
|
||
Some error responses (from FastAPI HTTPException) use a `detail` object instead:
|
||
|
||
```json
|
||
{
|
||
"detail": {
|
||
"code": "NOT_FOUND",
|
||
"message": "Server 5 not found"
|
||
}
|
||
}
|
||
```
|
||
|
||
## HTTP Status Codes
|
||
|
||
| Code | Meaning |
|
||
|------|------------------------------------------------------------|
|
||
| 200 | Success |
|
||
| 201 | Created (resource successfully created) |
|
||
| 204 | No content (successful DELETE) |
|
||
| 400 | Bad request (validation error, capability not supported) |
|
||
| 401 | Unauthenticated (missing or invalid token) |
|
||
| 403 | Forbidden (insufficient role — admin required) |
|
||
| 404 | Not found (resource or capability not available) |
|
||
| 409 | Conflict (already running, port in use, version conflict) |
|
||
| 413 | Payload too large (file upload exceeds 500 MB) |
|
||
| 422 | Unprocessable entity (Pydantic validation failure) |
|
||
| 500 | Internal server error |
|
||
| 503 | Service unavailable (RCon connection failed) |
|
||
|
||
## Capability-Based Routing
|
||
|
||
Some endpoints depend on the server's game adapter supporting a specific capability. If the adapter does not support the capability, the endpoint returns **400** with:
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"data": null,
|
||
"error": {
|
||
"code": "NOT_SUPPORTED",
|
||
"message": "Game type 'rust' does not support mission management"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Auth Endpoints
|
||
|
||
### POST /auth/login
|
||
|
||
Authenticate and receive a JWT token. Public endpoint (no auth required).
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"username": "admin",
|
||
"password": "secret"
|
||
}
|
||
```
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"access_token": "eyJhbGciOiJIUzI1NiIs...",
|
||
"token_type": "bearer",
|
||
"expires_in": 86400,
|
||
"user": {
|
||
"id": 1,
|
||
"username": "admin",
|
||
"role": "admin"
|
||
}
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 401:** Invalid credentials.
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"data": null,
|
||
"error": {
|
||
"code": "UNAUTHORIZED",
|
||
"message": "Invalid credentials"
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### POST /auth/logout
|
||
|
||
Invalidate session (client-side token deletion; no server-side blacklist).
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { "message": "Logged out" },
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET /auth/me
|
||
|
||
Return the currently authenticated user.
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": 1,
|
||
"username": "admin",
|
||
"role": "admin"
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### PUT /auth/password
|
||
|
||
Change the authenticated user's own password. Any authenticated user can change their own password (not admin-only).
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"current_password": "old_password",
|
||
"new_password": "new_password"
|
||
}
|
||
```
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { "message": "Password changed" },
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 401:** Current password is incorrect.
|
||
|
||
---
|
||
|
||
### GET /auth/users
|
||
|
||
List all users.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"username": "admin",
|
||
"role": "admin",
|
||
"created_at": "2026-04-16T00:00:00",
|
||
"last_login": "2026-04-17T10:30:00"
|
||
}
|
||
],
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### POST /auth/users
|
||
|
||
Create a new user.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"username": "viewer1",
|
||
"password": "secure_password",
|
||
"role": "viewer"
|
||
}
|
||
```
|
||
|
||
`role` defaults to `"viewer"` if omitted. Valid values: `"admin"`, `"viewer"`.
|
||
|
||
**Response 201:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": 2,
|
||
"username": "viewer1",
|
||
"role": "viewer",
|
||
"created_at": "2026-04-17T12:00:00"
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 409:** Username already taken.
|
||
|
||
---
|
||
|
||
### DELETE /auth/users/{user_id}
|
||
|
||
Delete a user. Cannot delete yourself.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Response 204:** No content.
|
||
|
||
**Error 400:** Attempting to delete your own account.
|
||
|
||
---
|
||
|
||
## Games Endpoints
|
||
|
||
### GET /games
|
||
|
||
List all registered game types with their capabilities.
|
||
|
||
**Auth:** Not required (public)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"game_type": "arma3",
|
||
"display_name": "Arma 3",
|
||
"version": "1.0.0",
|
||
"capabilities": [
|
||
"config_generator",
|
||
"process_config",
|
||
"log_parser",
|
||
"remote_admin",
|
||
"mission_manager",
|
||
"mod_manager",
|
||
"ban_manager"
|
||
]
|
||
}
|
||
],
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET /games/{game_type}
|
||
|
||
Get details for a specific game type, including capabilities, config sections, and allowed executables.
|
||
|
||
**Auth:** Not required (public)
|
||
|
||
**Path params:**
|
||
|
||
| Parameter | Type | Description |
|
||
|-------------|--------|------------------------|
|
||
| `game_type` | string | Game type identifier |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"game_type": "arma3",
|
||
"display_name": "Arma 3",
|
||
"version": "1.0.0",
|
||
"schema_version": "1.0",
|
||
"capabilities": [
|
||
"config_generator",
|
||
"process_config",
|
||
"log_parser",
|
||
"remote_admin",
|
||
"mission_manager",
|
||
"mod_manager",
|
||
"ban_manager"
|
||
],
|
||
"config_sections": ["server", "basic", "profile", "launch", "rcon"],
|
||
"allowed_executables": ["arma3server_x64.exe", "arma3server.exe"]
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 404:** Unknown game type.
|
||
|
||
---
|
||
|
||
### GET /games/{game_type}/config-schema
|
||
|
||
Returns JSON Schema for each config section defined by the adapter. Used by the frontend to build dynamic config forms.
|
||
|
||
**Auth:** Not required (public)
|
||
|
||
**Path params:**
|
||
|
||
| Parameter | Type | Description |
|
||
|-------------|--------|------------------------|
|
||
| `game_type` | string | Game type identifier |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"server": { "...": "JSON Schema for server.cfg params" },
|
||
"basic": { "...": "JSON Schema for basic.cfg params" },
|
||
"profile": { "...": "JSON Schema for Arma3Profile params" },
|
||
"launch": { "...": "JSON Schema for launch params" },
|
||
"rcon": { "...": "JSON Schema for RCon params" }
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET /games/{game_type}/defaults
|
||
|
||
Default config values for new server creation.
|
||
|
||
**Auth:** Not required (public)
|
||
|
||
**Path params:**
|
||
|
||
| Parameter | Type | Description |
|
||
|-------------|--------|------------------------|
|
||
| `game_type` | string | Game type identifier |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"server": { "hostname": "My Arma 3 Server", "max_players": 40 },
|
||
"basic": { "min_bandwidth": 800000 },
|
||
"profile": { "reduced_damage": 0 },
|
||
"launch": { "world": "empty", "limit_fps": 50 },
|
||
"rcon": { "max_ping": 200, "enabled": 1 }
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## System Endpoints
|
||
|
||
### GET /system/health
|
||
|
||
Public health check. Used by load balancers and Docker health probes.
|
||
|
||
**Auth:** Not required
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"status": "ok"
|
||
}
|
||
```
|
||
|
||
Note: This endpoint does not use the standard envelope.
|
||
|
||
---
|
||
|
||
### GET /system/status
|
||
|
||
System status including version and running server counts.
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"version": "1.0.0",
|
||
"running_servers": 2,
|
||
"total_servers": 3,
|
||
"supported_games": ["arma3"]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Server Endpoints
|
||
|
||
### GET /servers
|
||
|
||
List all servers with current status and live metrics.
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Query params:**
|
||
|
||
| Parameter | Type | Description |
|
||
|-------------|--------|------------------------------|
|
||
| `game_type` | string | Filter servers by game type |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"name": "Main Server",
|
||
"description": "Primary COOP server",
|
||
"game_type": "arma3",
|
||
"status": "running",
|
||
"pid": 12345,
|
||
"game_port": 2302,
|
||
"rcon_port": 2306,
|
||
"auto_restart": true,
|
||
"max_restarts": 3,
|
||
"restart_count": 0,
|
||
"player_count": 15,
|
||
"cpu_percent": 34.2,
|
||
"ram_mb": 1850.5,
|
||
"started_at": "2026-04-16T10:00:00Z"
|
||
}
|
||
],
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
`cpu_percent`, `ram_mb`, and `player_count` are merged from live metrics. They will be `null` if no metrics are available.
|
||
|
||
---
|
||
|
||
### POST /servers
|
||
|
||
Create a new server. The `game_type` determines which adapter handles this server.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"name": "Main Server",
|
||
"description": "Primary COOP server",
|
||
"game_type": "arma3",
|
||
"exe_path": "C:/Arma3Server/arma3server_x64.exe",
|
||
"game_port": 2302,
|
||
"rcon_port": 2306,
|
||
"auto_restart": true,
|
||
"max_restarts": 3
|
||
}
|
||
```
|
||
|
||
| Field | Type | Required | Default | Validation |
|
||
|----------------|---------|----------|----------|---------------------------|
|
||
| `name` | string | Yes | — | |
|
||
| `description` | string | No | `null` | |
|
||
| `game_type` | string | No | `"arma3"`| Must be a registered type |
|
||
| `exe_path` | string | Yes | — | Filename must be in allowlist |
|
||
| `game_port` | integer | Yes | — | 1024–65535, must not be in use |
|
||
| `rcon_port` | integer | No | auto | 1024–65535, auto = game_port + 3 |
|
||
| `auto_restart` | boolean | No | `false` | |
|
||
| `max_restarts` | integer | No | `3` | 0–20 |
|
||
|
||
The adapter provides default config values for all sections. Auto-generated credentials (e.g., `password_admin`, `rcon_password` for Arma 3) are returned in the creation response only.
|
||
|
||
**Response 201:** Returns the full server object.
|
||
|
||
**Errors:**
|
||
|
||
- **400** `GAME_TYPE_NOT_FOUND` — Unknown game type
|
||
- **400** `EXE_NOT_ALLOWED` — Executable filename not in adapter allowlist
|
||
- **409** `PORT_IN_USE` — Game port or RCon port already in use
|
||
|
||
---
|
||
|
||
### GET /servers/{server_id}
|
||
|
||
Get server detail with full status.
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": 1,
|
||
"name": "Main Server",
|
||
"description": "Primary COOP server",
|
||
"game_type": "arma3",
|
||
"status": "running",
|
||
"pid": 12345,
|
||
"exe_path": "C:/Arma3Server/arma3server_x64.exe",
|
||
"game_port": 2302,
|
||
"rcon_port": 2306,
|
||
"auto_restart": true,
|
||
"max_restarts": 3,
|
||
"restart_count": 0,
|
||
"player_count": 15,
|
||
"cpu_percent": 34.2,
|
||
"ram_mb": 1850.5,
|
||
"started_at": "2026-04-16T10:00:00Z",
|
||
"uptime_seconds": 3600
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 404:** Server not found.
|
||
|
||
---
|
||
|
||
### PUT /servers/{server_id}
|
||
|
||
Update server metadata (name, description, exe_path, ports). Only provided fields are updated; omitted fields keep their current values.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"name": "Updated Server Name",
|
||
"description": "New description",
|
||
"exe_path": "C:/Arma3Server/arma3server_x64.exe",
|
||
"game_port": 2302,
|
||
"rcon_port": 2306,
|
||
"auto_restart": false,
|
||
"max_restarts": 5
|
||
}
|
||
```
|
||
|
||
All fields are optional. Port validation (1024–65535) applies if provided.
|
||
|
||
**Response 200:** Returns the updated server object.
|
||
|
||
---
|
||
|
||
### DELETE /servers/{server_id}
|
||
|
||
Delete a server. The server must be in a stopped, crashed, or error state.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Response 204:** No content.
|
||
|
||
**Error 409:** `SERVER_NOT_STOPPED` — Server is currently running.
|
||
|
||
Note: Also deletes the server's directory on disk (`servers/{id}/`).
|
||
|
||
---
|
||
|
||
### POST /servers/{server_id}/start
|
||
|
||
Start the server. The system resolves the adapter, writes config files atomically, builds launch arguments, starts the process, and attaches monitoring threads.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"status": "starting",
|
||
"pid": 12345
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
- **400** `EXE_NOT_ALLOWED` — Executable not in adapter allowlist
|
||
- **400** `INVALID_CONFIG` — Config validation or launch args failed
|
||
- **409** `SERVER_ALREADY_RUNNING` — Server is already running or starting
|
||
- **409** `PORT_IN_USE` — Game port or RCon port already in use
|
||
- **500** `CONFIG_WRITE_ERROR` — Failed to write config files to disk
|
||
|
||
---
|
||
|
||
### POST /servers/{server_id}/stop
|
||
|
||
Graceful stop. Sends a shutdown signal via the adapter's remote admin, then force-kills after 30 seconds if the process has not exited.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Request (optional body):**
|
||
|
||
```json
|
||
{
|
||
"force": false,
|
||
"reason": "Scheduled maintenance"
|
||
}
|
||
```
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|----------|---------|----------|---------|------------------------------------------|
|
||
| `force` | boolean | No | `false` | Skip graceful shutdown, terminate immediately |
|
||
| `reason` | string | No | `null` | Optional reason for the stop |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { "status": "stopped" },
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 409:** `SERVER_NOT_RUNNING` — Server is already stopped or crashed.
|
||
|
||
---
|
||
|
||
### POST /servers/{server_id}/restart
|
||
|
||
Stop then start the server. Equivalent to calling stop then start sequentially.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Response 200:** Returns the start result (same shape as start).
|
||
|
||
---
|
||
|
||
### POST /servers/{server_id}/kill
|
||
|
||
Force-kill the server process immediately. Use for emergency situations only; does not attempt graceful shutdown.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { "status": "stopped" },
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Server Config Endpoints
|
||
|
||
Config sections are defined by the server's game adapter. The core `game_configs` table stores them as JSON. Each section tracks a `config_version` for optimistic locking.
|
||
|
||
### GET /servers/{server_id}/config
|
||
|
||
Get all config sections combined. Sensitive fields (passwords) are masked with `"***"`.
|
||
|
||
**Auth:** Required (any role — sensitive fields are masked for non-admin viewers)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"server": {
|
||
"hostname": "My Server",
|
||
"max_players": 40,
|
||
"_meta": { "config_version": 3, "schema_version": "1.0" }
|
||
},
|
||
"basic": {
|
||
"max_bandwidth": 50000000,
|
||
"_meta": { "config_version": 1, "schema_version": "1.0" }
|
||
},
|
||
"profile": {
|
||
"third_person_view": 0,
|
||
"_meta": { "config_version": 2, "schema_version": "1.0" }
|
||
},
|
||
"launch": {
|
||
"world": "empty",
|
||
"limit_fps": 50,
|
||
"_meta": { "config_version": 1, "schema_version": "1.0" }
|
||
},
|
||
"rcon": {
|
||
"rcon_password": "***",
|
||
"max_ping": 200,
|
||
"enabled": 1,
|
||
"_meta": { "config_version": 1, "schema_version": "1.0" }
|
||
}
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET /servers/{server_id}/config/preview
|
||
|
||
Rendered config for preview. Admin only because it may contain plaintext credentials.
|
||
|
||
**Auth:** Admin required
|
||
|
||
Returns a dict of `{label: rendered_content}` where labels are filenames for file-based configs, variable names for env-var configs, or argument names for CLI configs.
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"server.cfg": "hostname = \"My Server\";\nmaxPlayers = 40;\n...",
|
||
"basic.cfg": "// Generated basic.cfg\n...",
|
||
"server.Arma3Profile": "// Generated profile\n..."
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET /servers/{server_id}/config/{section}
|
||
|
||
Get a single config section. Section names are defined by the adapter (e.g., `server`, `basic`, `profile`, `launch`, `rcon` for Arma 3).
|
||
|
||
**Auth:** Required (any role — sensitive fields are masked)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"hostname": "My Server",
|
||
"max_players": 40,
|
||
"_meta": { "config_version": 3, "schema_version": "1.0" }
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 404:** Config section not found (not a valid section for this adapter).
|
||
|
||
---
|
||
|
||
### PUT /servers/{server_id}/config/{section}
|
||
|
||
Update a config section. Uses **optimistic locking** — the request must include `config_version` from the last read. Validated against the adapter's Pydantic model for that section. Omitted fields keep their current values (partial update).
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"hostname": "Updated Server Name",
|
||
"max_players": 64,
|
||
"config_version": 3
|
||
}
|
||
```
|
||
|
||
`config_version` is required for conflict detection. Any omitted field retains its current value.
|
||
|
||
**Arma 3 section examples:**
|
||
|
||
`server` section:
|
||
|
||
```json
|
||
{
|
||
"hostname": "My Arma Server",
|
||
"max_players": 40,
|
||
"battleye": 1,
|
||
"verify_signatures": 2,
|
||
"motd_lines": ["Welcome!", "Have fun"],
|
||
"motd_interval": 5.0,
|
||
"config_version": 3
|
||
}
|
||
```
|
||
|
||
`basic` section:
|
||
|
||
```json
|
||
{
|
||
"max_bandwidth": 50000000,
|
||
"max_msg_send": 256,
|
||
"config_version": 1
|
||
}
|
||
```
|
||
|
||
`profile` section:
|
||
|
||
```json
|
||
{
|
||
"third_person_view": 0,
|
||
"weapon_crosshair": 0,
|
||
"ai_level_preset": 3,
|
||
"skill_ai": 0.7,
|
||
"precision_ai": 0.6,
|
||
"config_version": 2
|
||
}
|
||
```
|
||
|
||
`launch` section:
|
||
|
||
```json
|
||
{
|
||
"world": "empty",
|
||
"limit_fps": 50,
|
||
"auto_init": false,
|
||
"load_mission_to_memory": true,
|
||
"config_version": 1
|
||
}
|
||
```
|
||
|
||
`rcon` section:
|
||
|
||
```json
|
||
{
|
||
"rcon_password": "newpassword",
|
||
"rcon_port": 2306,
|
||
"max_ping": 300,
|
||
"enabled": 1,
|
||
"config_version": 1
|
||
}
|
||
```
|
||
|
||
Note: `rcon_port` is stored in the `servers` table, not in the config JSON. The service layer updates both tables as needed.
|
||
|
||
**Response 200:** Returns the updated section (sensitive fields masked).
|
||
|
||
**Error 409 — Optimistic locking conflict:**
|
||
|
||
```json
|
||
{
|
||
"success": false,
|
||
"data": null,
|
||
"error": {
|
||
"code": "CONFIG_VERSION_CONFLICT",
|
||
"message": "Config was modified by another user. Re-read and merge.",
|
||
"current_config": { "...": "latest values" },
|
||
"current_version": 5
|
||
}
|
||
}
|
||
```
|
||
|
||
**Error 422:** Pydantic validation failure — the submitted values do not match the adapter's schema.
|
||
|
||
---
|
||
|
||
## RCon Endpoints
|
||
|
||
### POST /servers/{server_id}/rcon/command
|
||
|
||
Send an RCon command to a running server. Requires the adapter to support the `remote_admin` capability.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"command": "#restart"
|
||
}
|
||
```
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"response": "Command executed"
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
- **400** `NOT_SUPPORTED` — Game type does not support RCon
|
||
- **400** `NO_RCON_PASSWORD` — RCon password not configured for this server
|
||
- **503** `RCON_ERROR` — RCon connection or command failed
|
||
|
||
**Arma 3 available RCon commands:**
|
||
|
||
| Command | Description |
|
||
|---------|-------------|
|
||
| `#restart` | Restart current mission |
|
||
| `#reassign` | Restart with roles unassigned |
|
||
| `#missions` | Open mission selection |
|
||
| `#lock` | Lock the server |
|
||
| `#unlock` | Unlock the server |
|
||
| `#mission NAME.TERRAIN [difficulty]` | Load a specific mission |
|
||
| `#shutdown` | Shut down the server |
|
||
| `#monitor N` | Toggle performance monitoring |
|
||
| `say -1 MESSAGE` | Message all players |
|
||
|
||
---
|
||
|
||
## Player Endpoints
|
||
|
||
### GET /servers/{server_id}/players
|
||
|
||
List currently connected players (cached from RemoteAdminPollerThread).
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"server_id": 1,
|
||
"player_count": 2,
|
||
"players": [
|
||
{
|
||
"slot_id": "1",
|
||
"name": "PlayerOne",
|
||
"ping": 45
|
||
},
|
||
{
|
||
"slot_id": "2",
|
||
"name": "PlayerTwo",
|
||
"ping": 78
|
||
}
|
||
]
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### GET /servers/{server_id}/players/history
|
||
|
||
Get historical player session records.
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Query params:**
|
||
|
||
| Parameter | Type | Default | Description |
|
||
|-----------|---------|---------|------------------------------|
|
||
| `limit` | integer | 100 | Maximum records to return |
|
||
| `offset` | integer | 0 | Pagination offset |
|
||
| `search` | string | — | Filter by player name |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"total": 150,
|
||
"items": [
|
||
{
|
||
"id": 1,
|
||
"player_name": "PlayerOne",
|
||
"guid": "abc123...",
|
||
"joined_at": "2026-04-16T10:15:00Z",
|
||
"left_at": "2026-04-16T11:30:00Z"
|
||
}
|
||
]
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## Ban Endpoints
|
||
|
||
### GET /servers/{server_id}/bans
|
||
|
||
List all active bans for a server.
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"server_id": 1,
|
||
"guid": "abc123...",
|
||
"name": null,
|
||
"reason": "Cheating",
|
||
"banned_by": "admin",
|
||
"ban_type": "GUID",
|
||
"duration_minutes": 0,
|
||
"expires_at": null,
|
||
"created_at": "2026-04-16T10:00:00Z"
|
||
}
|
||
],
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### POST /servers/{server_id}/bans
|
||
|
||
Create a new ban. Writes to the database and syncs to the game's `bans.txt` file (if adapter supports `ban_manager`).
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"player_uid": "abc123...",
|
||
"ban_type": "GUID",
|
||
"reason": "Cheating",
|
||
"duration_minutes": 0
|
||
}
|
||
```
|
||
|
||
| Field | Type | Required | Default | Description |
|
||
|--------------------|---------|----------|-----------|---------------------------------|
|
||
| `player_uid` | string | Yes | — | GUID or IP to ban |
|
||
| `ban_type` | string | No | `"GUID"` | Must be `"GUID"` or `"IP"` |
|
||
| `reason` | string | No | `""` | Reason for the ban |
|
||
| `duration_minutes` | integer | No | `0` | 0 = permanent, >0 = timed ban |
|
||
|
||
**Response 201:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"id": 2,
|
||
"server_id": 1,
|
||
"guid": "abc123...",
|
||
"reason": "Cheating",
|
||
"ban_type": "GUID",
|
||
"expires_at": null
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
Note: If the `bans.txt` file sync fails, the database ban is still created. The error is logged but does not fail the request.
|
||
|
||
---
|
||
|
||
### DELETE /servers/{server_id}/bans/{ban_id}
|
||
|
||
Revoke (deactivate) a ban. Removes from `bans.txt` if the adapter supports `ban_manager`.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { "message": "Ban 2 revoked" },
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 404:** Ban not found or does not belong to this server.
|
||
|
||
---
|
||
|
||
## Mission Endpoints
|
||
|
||
All mission endpoints require the adapter to support the `mission_manager` capability. Returns **400** `NOT_SUPPORTED` if the game type does not support missions.
|
||
|
||
### GET /servers/{server_id}/missions
|
||
|
||
List all available mission/scenario files on disk.
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"server_id": 1,
|
||
"total": 2,
|
||
"missions": [
|
||
{
|
||
"filename": "MyMission.Altis.pbo",
|
||
"mission_name": "MyMission.Altis",
|
||
"terrain": "Altis",
|
||
"file_size": 102400
|
||
}
|
||
]
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### POST /servers/{server_id}/missions
|
||
|
||
Upload a mission file. **Multipart form-data**. Maximum file size: **500 MB**. File extension is validated by the adapter (e.g., `.pbo` for Arma 3).
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Request:** `multipart/form-data` with field `file`.
|
||
|
||
**Response 201:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"filename": "NewMission.Stratis.pbo",
|
||
"mission_name": "NewMission.Stratis",
|
||
"terrain": "Stratis",
|
||
"file_size": 51200
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Errors:**
|
||
|
||
- **400** `NO_FILENAME` — No filename provided in upload
|
||
- **400** `ADAPTER_ERROR` — Upload failed (wrong extension, etc.)
|
||
- **413** `FILE_TOO_LARGE` — File exceeds 500 MB
|
||
|
||
---
|
||
|
||
### DELETE /servers/{server_id}/missions/{filename}
|
||
|
||
Delete a mission file by filename. Removes the file from disk.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Path params:**
|
||
|
||
| Parameter | Type | Description |
|
||
|------------|--------|---------------------------------|
|
||
| `filename` | string | Mission filename (e.g. `MyMission.Altis.pbo`) |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": { "message": "Mission 'MyMission.Altis.pbo' deleted" },
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 404:** Mission file not found.
|
||
|
||
---
|
||
|
||
## Mod Endpoints
|
||
|
||
All mod endpoints require the adapter to support the `mod_manager` capability. Returns **400** `NOT_SUPPORTED` if the game type does not support mods.
|
||
|
||
### GET /servers/{server_id}/mods
|
||
|
||
List all available mods and which are currently enabled for this server.
|
||
|
||
**Auth:** Required (any role)
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"server_id": 1,
|
||
"enabled_count": 2,
|
||
"mods": [
|
||
{
|
||
"name": "@CBA_A3",
|
||
"folder_path": "C:/Arma3Server/@CBA_A3",
|
||
"enabled": true
|
||
},
|
||
{
|
||
"name": "@ACRE2",
|
||
"folder_path": "C:/Arma3Server/@ACRE2",
|
||
"enabled": true
|
||
},
|
||
{
|
||
"name": "@USAF",
|
||
"folder_path": "C:/Arma3Server/@USAF",
|
||
"enabled": false
|
||
}
|
||
]
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### PUT /servers/{server_id}/mods/enabled
|
||
|
||
Set the list of enabled mods. This **replaces** the entire enabled list — send the complete list every time. The server must be restarted for changes to take effect.
|
||
|
||
**Auth:** Admin required
|
||
|
||
**Request:**
|
||
|
||
```json
|
||
{
|
||
"mods": ["@CBA_A3", "@ACRE2"]
|
||
}
|
||
```
|
||
|
||
| Field | Type | Required | Description |
|
||
|--------|---------------|----------|------------------------------------|
|
||
| `mods` | array[string] | Yes | Complete list of mod names to enable |
|
||
|
||
**Response 200:**
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"data": {
|
||
"message": "Enabled mods updated. Restart the server for changes to take effect.",
|
||
"enabled_mods": ["@CBA_A3", "@ACRE2"]
|
||
},
|
||
"error": null
|
||
}
|
||
```
|
||
|
||
**Error 409** `VERSION_CONFLICT` — Config was modified by another request while updating mods.
|
||
|
||
---
|
||
|
||
## WebSocket API
|
||
|
||
### Connection
|
||
|
||
```
|
||
ws://localhost:8000/ws?token=<JWT>&server_id=1&server_id=2
|
||
```
|
||
|
||
**Parameters:**
|
||
|
||
| Parameter | Type | Required | Description |
|
||
|-------------|-------------------|----------|----------------------------------------------|
|
||
| `token` | string | Yes | JWT access token |
|
||
| `server_id` | integer (repeatable) | No | Server IDs to subscribe to. Omit for all servers |
|
||
|
||
**Authentication:** JWT is passed as a query parameter because the browser WebSocket API does not support custom headers. If the token is missing or invalid, the connection is closed with code **4001**.
|
||
|
||
**Welcome message on connect:**
|
||
|
||
```json
|
||
{
|
||
"type": "connected",
|
||
"data": {
|
||
"user": "1",
|
||
"subscriptions": [1, 2]
|
||
}
|
||
}
|
||
```
|
||
|
||
If `server_id` is omitted, `subscriptions` will be `"all"`.
|
||
|
||
### Server-Sent Event Types
|
||
|
||
All events follow this format:
|
||
|
||
```json
|
||
{
|
||
"type": "<event_type>",
|
||
"server_id": 1,
|
||
"data": { ... }
|
||
}
|
||
```
|
||
|
||
#### server_status
|
||
|
||
Emitted when a server's status changes (starting, running, stopping, stopped, crashed, error).
|
||
|
||
```json
|
||
{
|
||
"type": "server_status",
|
||
"server_id": 1,
|
||
"data": {
|
||
"status": "running",
|
||
"pid": 12345,
|
||
"started_at": "2026-04-16T10:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### log
|
||
|
||
Emitted for each log line from the server process.
|
||
|
||
```json
|
||
{
|
||
"type": "log",
|
||
"server_id": 1,
|
||
"data": {
|
||
"timestamp": "2026-04-16T10:05:23Z",
|
||
"level": "info",
|
||
"message": "BattlEye Server: Initialized (v1.240)"
|
||
}
|
||
}
|
||
```
|
||
|
||
#### players
|
||
|
||
Emitted when the player list changes.
|
||
|
||
```json
|
||
{
|
||
"type": "players",
|
||
"server_id": 1,
|
||
"data": {
|
||
"players": [
|
||
{ "slot_id": "1", "name": "PlayerOne", "ping": 45 }
|
||
],
|
||
"count": 1
|
||
}
|
||
}
|
||
```
|
||
|
||
#### metrics
|
||
|
||
Periodic resource usage update.
|
||
|
||
```json
|
||
{
|
||
"type": "metrics",
|
||
"server_id": 1,
|
||
"data": {
|
||
"cpu_percent": 34.2,
|
||
"ram_mb": 1850.5,
|
||
"player_count": 1,
|
||
"timestamp": "2026-04-16T10:05:25Z"
|
||
}
|
||
}
|
||
```
|
||
|
||
### Subscription Model
|
||
|
||
- Each connection subscribes to zero or more `server_id` values.
|
||
- Subscribing to `server_id=None` (omitting the parameter) means "all servers."
|
||
- `broadcast(server_id, message)` delivers to all clients subscribed to that `server_id` plus all global subscribers.
|
||
- If the event loop is closed, events are silently dropped.
|
||
|
||
---
|
||
|
||
## Rate Limiting
|
||
|
||
| Endpoint | Limit |
|
||
|-----------------------|------------------------------|
|
||
| `POST /auth/login` | 5 attempts per minute per IP |
|
||
| All other endpoints | 60 requests per minute per token |
|
||
|
||
Exceeded limits return **429 Too Many Requests**.
|
||
|
||
Implemented via `slowapi` middleware.
|
||
|
||
---
|
||
|
||
## Error Codes Reference
|
||
|
||
| Code | HTTP Status | Description |
|
||
|-----------------------------|-------------|------------------------------------------------------|
|
||
| `UNAUTHORIZED` | 401 | Missing or invalid token |
|
||
| `FORBIDDEN` | 403 | Insufficient role (admin required) |
|
||
| `NOT_FOUND` | 404 | Resource not found |
|
||
| `NOT_SUPPORTED` | 400 | Adapter lacks required capability |
|
||
| `CONFLICT` | 409 | Duplicate resource (e.g., username taken) |
|
||
| `SERVER_ALREADY_RUNNING` | 409 | Start called on running server |
|
||
| `SERVER_NOT_RUNNING` | 409 | Stop/command on stopped server |
|
||
| `SERVER_NOT_STOPPED` | 409 | Delete called on running server |
|
||
| `PORT_IN_USE` | 409 | Game or RCon port already occupied |
|
||
| `CONFIG_VERSION_CONFLICT` | 409 | Optimistic locking conflict on config update |
|
||
| `VERSION_CONFLICT` | 409 | Config modified by another request during mod update |
|
||
| `GAME_TYPE_NOT_FOUND` | 404/400 | No adapter registered for this game type |
|
||
| `EXE_NOT_ALLOWED` | 400 | Executable not in adapter allowlist |
|
||
| `INVALID_CONFIG` | 422/400 | Config validation failed (adapter-specific) |
|
||
| `CONFIG_WRITE_ERROR` | 500 | Config file write failed (disk, permissions) |
|
||
| `NO_RCON_PASSWORD` | 400 | RCon password not configured |
|
||
| `RCON_ERROR` | 503 | RCon connection or command failed |
|
||
| `ADAPTER_ERROR` | 400/500 | Generic adapter error |
|
||
| `FILE_TOO_LARGE` | 413 | Upload exceeds 500 MB |
|
||
| `NO_FILENAME` | 400 | No filename in upload request |
|
||
| `VALIDATION_ERROR` | 400 | General validation failure |
|
||
| `INTERNAL_ERROR` | 500 | Unexpected server error | |