Files
languard-servers-manager/API.md
Tran G. (Revernomad) Khoa 6511353b55 feat: implement full backend + frontend server detail, settings, and create server pages
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
2026-04-17 11:58:34 +07:00

1488 lines
32 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 | — | 102465535, must not be in use |
| `rcon_port` | integer | No | auto | 102465535, auto = game_port + 3 |
| `auto_restart` | boolean | No | `false` | |
| `max_restarts` | integer | No | `3` | 020 |
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 (102465535) 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 |