feat: initial system design documents for Languard Server Manager
Complete backend design for an Arma 3 dedicated server management panel: - ARCHITECTURE.md: System architecture, tech stack, component responsibilities, data flows - DATABASE.md: SQLite schema with WAL mode, CHECK constraints, 16+ tables - API.md: REST + WebSocket API contract with auth, CRUD, and real-time channels - MODULES.md: Python module breakdown with class definitions and dependencies - THREADING.md: Concurrency model with thread safety, auto-restart, and WS bridge - IMPLEMENTATION_PLAN.md: 7-phase implementation plan with security from Phase 1 Key design decisions: - Sync SQLAlchemy only (no aiosqlite), thread-local DB connections - Structured config builder (not f-strings) preventing config injection - RCon request multiplexer for concurrent UDP access - BackgroundScheduler for sync DB cleanup jobs - ban.txt bidirectional sync with documented field mapping - Auto-restart sequenced after thread cleanup Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
586
DATABASE.md
Normal file
586
DATABASE.md
Normal file
@@ -0,0 +1,586 @@
|
||||
# Languard Server Manager — Database Design
|
||||
|
||||
## Engine
|
||||
- **SQLite** via `SQLAlchemy Core` (sync for all access — routes and threads)
|
||||
- File: `languard.db` at project root (configurable via `LANGUARD_DB_PATH`)
|
||||
- WAL mode enabled: `PRAGMA journal_mode=WAL` — allows concurrent reads during writes
|
||||
- Foreign keys enabled: `PRAGMA foreign_keys=ON`
|
||||
- Busy timeout: `PRAGMA busy_timeout=5000` — prevents "database is locked" errors under concurrent thread writes
|
||||
|
||||
---
|
||||
|
||||
## Schema
|
||||
|
||||
### Table: `users`
|
||||
|
||||
Stores web UI admin accounts.
|
||||
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
username TEXT NOT NULL UNIQUE,
|
||||
password_hash TEXT NOT NULL, -- bcrypt hash
|
||||
role TEXT NOT NULL DEFAULT 'viewer', -- 'admin' | 'viewer'
|
||||
CHECK (role IN ('admin', 'viewer')),
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
last_login TEXT
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `servers`
|
||||
|
||||
One row per managed Arma 3 server instance.
|
||||
|
||||
```sql
|
||||
CREATE TABLE servers (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL, -- display name in UI
|
||||
description TEXT,
|
||||
status TEXT NOT NULL DEFAULT 'stopped',
|
||||
-- status values: 'stopped' | 'starting' | 'running' | 'stopping' | 'crashed' | 'error'
|
||||
CHECK (status IN ('stopped', 'starting', 'running', 'stopping', 'crashed', 'error')),
|
||||
CHECK (game_port BETWEEN 1024 AND 65535),
|
||||
CHECK (rcon_port BETWEEN 1024 AND 65535),
|
||||
|
||||
-- Process info
|
||||
pid INTEGER, -- OS process ID when running
|
||||
exe_path TEXT NOT NULL, -- path to arma3server_x64.exe
|
||||
started_at TEXT, -- ISO datetime
|
||||
stopped_at TEXT,
|
||||
|
||||
-- Network
|
||||
game_port INTEGER NOT NULL DEFAULT 2302,
|
||||
rcon_port INTEGER NOT NULL DEFAULT 2306, -- user-configurable; written to battleye/beserver.cfg
|
||||
steam_query_port INTEGER GENERATED ALWAYS AS (game_port + 1) VIRTUAL, -- convention, not enforced by engine
|
||||
|
||||
-- Auto-management
|
||||
auto_restart INTEGER NOT NULL DEFAULT 0, -- 1 = restart on crash
|
||||
max_restarts INTEGER NOT NULL DEFAULT 3, -- within restart_window_seconds
|
||||
restart_window_seconds INTEGER NOT NULL DEFAULT 300,
|
||||
restart_count INTEGER NOT NULL DEFAULT 0,
|
||||
last_restart_at TEXT,
|
||||
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_servers_status ON servers(status);
|
||||
CREATE INDEX idx_servers_game_port ON servers(game_port);
|
||||
CREATE INDEX idx_servers_rcon_port ON servers(rcon_port);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `server_configs`
|
||||
|
||||
Stores all parameters for generating `server.cfg`. One row per server.
|
||||
|
||||
```sql
|
||||
CREATE TABLE server_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL UNIQUE REFERENCES servers(id) ON DELETE CASCADE,
|
||||
|
||||
-- Basic identity
|
||||
hostname TEXT NOT NULL DEFAULT 'My Arma 3 Server',
|
||||
password TEXT, -- join password (encrypted at app layer via Fernet)
|
||||
password_admin TEXT NOT NULL, -- encrypted (no default — must be set on creation)
|
||||
server_command_password TEXT, -- encrypted
|
||||
|
||||
-- Players
|
||||
max_players INTEGER NOT NULL DEFAULT 40,
|
||||
kick_duplicate INTEGER NOT NULL DEFAULT 1,
|
||||
persistent INTEGER NOT NULL DEFAULT 1,
|
||||
|
||||
-- Voting
|
||||
vote_threshold REAL NOT NULL DEFAULT 0.33,
|
||||
vote_mission_players INTEGER NOT NULL DEFAULT 1,
|
||||
vote_timeout INTEGER NOT NULL DEFAULT 60, -- seconds
|
||||
role_timeout INTEGER NOT NULL DEFAULT 90,
|
||||
briefing_timeout INTEGER NOT NULL DEFAULT 60,
|
||||
debriefing_timeout INTEGER NOT NULL DEFAULT 45,
|
||||
lobby_idle_timeout INTEGER NOT NULL DEFAULT 300,
|
||||
|
||||
-- Voice
|
||||
disable_von INTEGER NOT NULL DEFAULT 0,
|
||||
von_codec INTEGER NOT NULL DEFAULT 1, -- 1 = OPUS
|
||||
CHECK (von_codec IN (0, 1)),
|
||||
von_codec_quality INTEGER NOT NULL DEFAULT 20, -- 1-30
|
||||
|
||||
-- Network quality kick thresholds
|
||||
max_ping INTEGER NOT NULL DEFAULT 250,
|
||||
max_packet_loss INTEGER NOT NULL DEFAULT 50,
|
||||
max_desync INTEGER NOT NULL DEFAULT 200,
|
||||
disconnect_timeout INTEGER NOT NULL DEFAULT 15,
|
||||
kick_on_ping INTEGER NOT NULL DEFAULT 1,
|
||||
kick_on_packet_loss INTEGER NOT NULL DEFAULT 1,
|
||||
kick_on_desync INTEGER NOT NULL DEFAULT 1,
|
||||
kick_on_timeout INTEGER NOT NULL DEFAULT 1,
|
||||
|
||||
-- Security
|
||||
battleye INTEGER NOT NULL DEFAULT 1,
|
||||
verify_signatures INTEGER NOT NULL DEFAULT 2, -- 0 | 1 | 2 (1 = check but don't kick)
|
||||
allowed_file_patching INTEGER NOT NULL DEFAULT 0, -- 0 | 1 | 2
|
||||
|
||||
-- Difficulty
|
||||
forced_difficulty TEXT NOT NULL DEFAULT 'Regular', -- Recruit | Regular | Veteran | Custom
|
||||
|
||||
-- Misc
|
||||
timestamp_format TEXT NOT NULL DEFAULT 'short', -- none | short | full
|
||||
auto_select_mission INTEGER NOT NULL DEFAULT 0,
|
||||
random_mission_order INTEGER NOT NULL DEFAULT 0,
|
||||
missions_to_restart INTEGER NOT NULL DEFAULT 0,
|
||||
missions_to_shutdown INTEGER NOT NULL DEFAULT 0,
|
||||
log_file TEXT NOT NULL DEFAULT 'server_console.log',
|
||||
skip_lobby INTEGER NOT NULL DEFAULT 0,
|
||||
drawing_in_map INTEGER NOT NULL DEFAULT 1,
|
||||
upnp INTEGER NOT NULL DEFAULT 0,
|
||||
loopback INTEGER NOT NULL DEFAULT 0,
|
||||
statistics_enabled INTEGER NOT NULL DEFAULT 1,
|
||||
force_rotor_lib INTEGER NOT NULL DEFAULT 0, -- 0=player, 1=AFM, 2=SFM
|
||||
CHECK (force_rotor_lib IN (0, 1, 2)),
|
||||
required_build INTEGER NOT NULL DEFAULT 0,
|
||||
steam_protocol_max_data_size INTEGER NOT NULL DEFAULT 1024,
|
||||
|
||||
-- MOTD
|
||||
motd_lines TEXT NOT NULL DEFAULT '[]', -- JSON array of strings
|
||||
motd_interval REAL NOT NULL DEFAULT 5.0,
|
||||
|
||||
-- Event scripts
|
||||
on_user_connected TEXT DEFAULT '',
|
||||
on_user_disconnected TEXT DEFAULT '',
|
||||
on_unsigned_data TEXT DEFAULT 'kick (_this select 0)',
|
||||
on_hacked_data TEXT DEFAULT 'kick (_this select 0)',
|
||||
double_id_detected TEXT DEFAULT '',
|
||||
|
||||
-- Headless clients (JSON arrays)
|
||||
headless_clients TEXT NOT NULL DEFAULT '[]', -- e.g. '["127.0.0.1"]'
|
||||
local_clients TEXT NOT NULL DEFAULT '[]',
|
||||
|
||||
-- Admin UIDs whitelist
|
||||
admin_uids TEXT NOT NULL DEFAULT '[]', -- JSON array of Steam UIDs
|
||||
|
||||
-- File extension whitelists (JSON arrays)
|
||||
allowed_load_extensions TEXT NOT NULL DEFAULT '["hpp","sqs","sqf","fsm","cpp","paa","txt","xml","inc","ext","sqm","ods","fxy","lip","csv","kb","bik","bikb","html","htm","biedi"]',
|
||||
allowed_preprocess_extensions TEXT NOT NULL DEFAULT '["hpp","sqs","sqf","fsm","cpp","paa","txt","xml","inc","ext","sqm","ods","fxy","lip","csv","kb","bik","bikb","html","htm","biedi"]',
|
||||
allowed_html_extensions TEXT NOT NULL DEFAULT '["htm","html","xml","txt"]',
|
||||
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
|
||||
CHECK (verify_signatures IN (0, 1, 2)),
|
||||
CHECK (allowed_file_patching IN (0, 1, 2)),
|
||||
CHECK (von_codec_quality BETWEEN 1 AND 30),
|
||||
CHECK (forced_difficulty IN ('Recruit', 'Regular', 'Veteran', 'Custom')),
|
||||
CHECK (vote_threshold >= 0.0 AND vote_threshold <= 1.0),
|
||||
CHECK (max_players > 0)
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `basic_configs`
|
||||
|
||||
Stores `basic.cfg` (bandwidth) settings. One row per server.
|
||||
|
||||
```sql
|
||||
CREATE TABLE basic_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL UNIQUE REFERENCES servers(id) ON DELETE CASCADE,
|
||||
|
||||
min_bandwidth INTEGER NOT NULL DEFAULT 800000,
|
||||
max_bandwidth INTEGER NOT NULL DEFAULT 25000000,
|
||||
max_msg_send INTEGER NOT NULL DEFAULT 384, -- default 128; higher = desync risk
|
||||
max_size_guaranteed INTEGER NOT NULL DEFAULT 512,
|
||||
max_size_non_guaranteed INTEGER NOT NULL DEFAULT 256,
|
||||
min_error_to_send REAL NOT NULL DEFAULT 0.003,
|
||||
max_custom_file_size INTEGER NOT NULL DEFAULT 100000,
|
||||
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `server_profiles`
|
||||
|
||||
Stores `server.Arma3Profile` difficulty settings. One row per server.
|
||||
|
||||
```sql
|
||||
CREATE TABLE server_profiles (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL UNIQUE REFERENCES servers(id) ON DELETE CASCADE,
|
||||
|
||||
-- Custom difficulty options (all 0/1 or 0/1/2)
|
||||
reduced_damage INTEGER NOT NULL DEFAULT 0,
|
||||
group_indicators INTEGER NOT NULL DEFAULT 0,
|
||||
friendly_tags INTEGER NOT NULL DEFAULT 0,
|
||||
enemy_tags INTEGER NOT NULL DEFAULT 0,
|
||||
detected_mines INTEGER NOT NULL DEFAULT 0,
|
||||
commands INTEGER NOT NULL DEFAULT 1,
|
||||
waypoints INTEGER NOT NULL DEFAULT 1,
|
||||
tactical_ping INTEGER NOT NULL DEFAULT 0,
|
||||
weapon_info INTEGER NOT NULL DEFAULT 2,
|
||||
stance_indicator INTEGER NOT NULL DEFAULT 2,
|
||||
stamina_bar INTEGER NOT NULL DEFAULT 0,
|
||||
weapon_crosshair INTEGER NOT NULL DEFAULT 0,
|
||||
vision_aid INTEGER NOT NULL DEFAULT 0,
|
||||
third_person_view INTEGER NOT NULL DEFAULT 0,
|
||||
camera_shake INTEGER NOT NULL DEFAULT 1,
|
||||
score_table INTEGER NOT NULL DEFAULT 1,
|
||||
death_messages INTEGER NOT NULL DEFAULT 1,
|
||||
von_id INTEGER NOT NULL DEFAULT 1,
|
||||
map_content_friendly INTEGER NOT NULL DEFAULT 0,
|
||||
map_content_enemy INTEGER NOT NULL DEFAULT 0,
|
||||
map_content_mines INTEGER NOT NULL DEFAULT 0,
|
||||
auto_report INTEGER NOT NULL DEFAULT 0,
|
||||
multiple_saves INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
-- AI level
|
||||
ai_level_preset INTEGER NOT NULL DEFAULT 3, -- 0=Low,1=Normal,2=High,3=Custom
|
||||
skill_ai REAL NOT NULL DEFAULT 0.5,
|
||||
precision_ai REAL NOT NULL DEFAULT 0.5,
|
||||
|
||||
CHECK (ai_level_preset BETWEEN 0 AND 3),
|
||||
CHECK (skill_ai BETWEEN 0.0 AND 1.0),
|
||||
CHECK (precision_ai BETWEEN 0.0 AND 1.0),
|
||||
CHECK (group_indicators BETWEEN 0 AND 2),
|
||||
CHECK (weapon_info BETWEEN 0 AND 2),
|
||||
CHECK (stance_indicator BETWEEN 0 AND 2),
|
||||
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `launch_params`
|
||||
|
||||
Extra command-line parameters added to the server launch command. One row per server.
|
||||
|
||||
```sql
|
||||
CREATE TABLE launch_params (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL UNIQUE REFERENCES servers(id) ON DELETE CASCADE,
|
||||
|
||||
world TEXT NOT NULL DEFAULT 'empty',
|
||||
extra_params TEXT NOT NULL DEFAULT '', -- raw extra params string
|
||||
limit_fps INTEGER NOT NULL DEFAULT 50,
|
||||
auto_init INTEGER NOT NULL DEFAULT 0,
|
||||
load_mission_to_memory INTEGER NOT NULL DEFAULT 0,
|
||||
bandwidth_alg INTEGER, -- NULL | 2
|
||||
CHECK (bandwidth_alg IS NULL OR bandwidth_alg = 2),
|
||||
enable_ht INTEGER NOT NULL DEFAULT 0,
|
||||
huge_pages INTEGER NOT NULL DEFAULT 0,
|
||||
cpu_count INTEGER, -- NULL = auto
|
||||
ex_threads INTEGER NOT NULL DEFAULT 7,
|
||||
max_mem INTEGER, -- NULL = auto
|
||||
no_logs INTEGER NOT NULL DEFAULT 0,
|
||||
netlog INTEGER NOT NULL DEFAULT 0,
|
||||
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `mods`
|
||||
|
||||
Registered mods. Many-to-many with servers.
|
||||
|
||||
```sql
|
||||
CREATE TABLE mods (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
folder_path TEXT NOT NULL UNIQUE, -- absolute or relative path
|
||||
workshop_id TEXT, -- Steam Workshop ID if applicable
|
||||
description TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE TABLE server_mods (
|
||||
server_id INTEGER NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
mod_id INTEGER NOT NULL REFERENCES mods(id) ON DELETE CASCADE,
|
||||
is_server_mod INTEGER NOT NULL DEFAULT 0, -- 1 = -serverMod (not broadcast to clients)
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
PRIMARY KEY (server_id, mod_id)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_server_mods_server ON server_mods(server_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `missions`
|
||||
|
||||
Mission PBO files tracked per server.
|
||||
|
||||
```sql
|
||||
CREATE TABLE missions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
filename TEXT NOT NULL, -- e.g. "MyMission.Altis.pbo"
|
||||
mission_name TEXT NOT NULL, -- e.g. "MyMission.Altis"
|
||||
terrain TEXT NOT NULL, -- e.g. "Altis"
|
||||
file_size INTEGER, -- bytes
|
||||
uploaded_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE (server_id, filename)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_missions_server ON missions(server_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `mission_rotation`
|
||||
|
||||
Ordered mission cycle for a server.
|
||||
|
||||
```sql
|
||||
CREATE TABLE mission_rotation (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
mission_id INTEGER NOT NULL REFERENCES missions(id) ON DELETE CASCADE,
|
||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||
difficulty TEXT NOT NULL DEFAULT 'Regular',
|
||||
CHECK (difficulty IN ('Recruit', 'Regular', 'Veteran', 'Custom')),
|
||||
params_json TEXT NOT NULL DEFAULT '{}', -- mission params override as JSON
|
||||
UNIQUE (server_id, sort_order)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_mission_rotation_server ON mission_rotation(server_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `players`
|
||||
|
||||
Currently connected players (live state, refreshed by RConPollerThread).
|
||||
|
||||
```sql
|
||||
CREATE TABLE players (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
player_num INTEGER NOT NULL, -- BE player# (slot number)
|
||||
name TEXT NOT NULL,
|
||||
guid TEXT, -- BattlEye GUID
|
||||
steam_uid TEXT,
|
||||
ip TEXT,
|
||||
ping INTEGER,
|
||||
verified INTEGER NOT NULL DEFAULT 0, -- 1 = signature verified
|
||||
joined_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UNIQUE (server_id, player_num)
|
||||
);
|
||||
|
||||
CREATE INDEX idx_players_server ON players(server_id);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `player_history`
|
||||
|
||||
Historical record of connections. Inserted when player disconnects.
|
||||
|
||||
```sql
|
||||
CREATE TABLE player_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
guid TEXT,
|
||||
steam_uid TEXT,
|
||||
ip TEXT,
|
||||
joined_at TEXT NOT NULL,
|
||||
left_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
session_duration_seconds INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX idx_player_history_server ON player_history(server_id);
|
||||
CREATE INDEX idx_player_history_steam ON player_history(steam_uid);
|
||||
```
|
||||
|
||||
### Player History Retention Cleanup (run daily via APScheduler, keep 90 days)
|
||||
```sql
|
||||
DELETE FROM player_history
|
||||
WHERE left_at < datetime('now', '-90 days');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `bans`
|
||||
|
||||
Local ban records (source of truth for the UI). **Must sync bidirectionally with `battleye/ban.txt`** — BattlEye reads only from ban.txt. On API ban add/delete: also write to ban.txt. On startup: read ban.txt and upsert into DB.
|
||||
|
||||
**ban.txt format** (one entry per line):
|
||||
```
|
||||
GUID|IP timestamp reason
|
||||
```
|
||||
Example: `a1b2c3d4e5f6|192.168.1.1 1713260000 Cheating`
|
||||
|
||||
**Sync caveats:** ban.txt does not store `banned_by`, `expires_at`, or `is_active`. Timed bans are represented by a future timestamp (not minutes); permanent bans have timestamp `0`. On startup, `banned_by` is set to `'ban.txt'` for entries read from file. Deactivated bans (`is_active=0`) are not written to ban.txt.
|
||||
|
||||
```sql
|
||||
CREATE TABLE bans (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
guid TEXT,
|
||||
steam_uid TEXT,
|
||||
name TEXT,
|
||||
reason TEXT,
|
||||
banned_by TEXT, -- admin username
|
||||
banned_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
expires_at TEXT, -- NULL = permanent
|
||||
is_active INTEGER NOT NULL DEFAULT 1,
|
||||
CHECK (is_active IN (0, 1))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_bans_server ON bans(server_id);
|
||||
CREATE INDEX idx_bans_guid ON bans(guid);
|
||||
CREATE INDEX idx_bans_steam_uid ON bans(steam_uid);
|
||||
CREATE INDEX idx_bans_active ON bans(is_active);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `logs`
|
||||
|
||||
Parsed RPT log lines (rolling retention, default 7 days).
|
||||
|
||||
```sql
|
||||
CREATE TABLE logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
timestamp TEXT NOT NULL,
|
||||
level TEXT NOT NULL DEFAULT 'info', -- 'info' | 'warning' | 'error'
|
||||
CHECK (level IN ('info', 'warning', 'error')),
|
||||
message TEXT NOT NULL,
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_logs_server_ts ON logs(server_id, timestamp);
|
||||
CREATE INDEX idx_logs_level ON logs(level); -- for ?level= filter
|
||||
CREATE INDEX idx_logs_created ON logs(created_at); -- for retention cleanup
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `metrics`
|
||||
|
||||
Time-series CPU/RAM/player count snapshots.
|
||||
|
||||
```sql
|
||||
CREATE TABLE metrics (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
timestamp TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
cpu_percent REAL,
|
||||
ram_mb REAL,
|
||||
player_count INTEGER
|
||||
);
|
||||
|
||||
CREATE INDEX idx_metrics_server_ts ON metrics(server_id, timestamp);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `server_events`
|
||||
|
||||
Audit trail of all significant events (start, stop, crash, restart, admin actions).
|
||||
|
||||
```sql
|
||||
CREATE TABLE server_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL REFERENCES servers(id) ON DELETE CASCADE,
|
||||
event_type TEXT NOT NULL,
|
||||
-- event_type values:
|
||||
-- 'started' | 'stopped' | 'crashed' | 'restarted' | 'config_updated'
|
||||
-- 'player_kicked' | 'player_banned' | 'mission_changed' | 'admin_login'
|
||||
-- 'rcon_command' | 'auto_restarted'
|
||||
actor TEXT, -- username or 'system'
|
||||
detail TEXT, -- JSON with event-specific data
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
|
||||
CREATE INDEX idx_events_server ON server_events(server_id, created_at);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Table: `rcon_configs`
|
||||
|
||||
BattlEye RCon credentials per server.
|
||||
|
||||
```sql
|
||||
CREATE TABLE rcon_configs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
server_id INTEGER NOT NULL UNIQUE REFERENCES servers(id) ON DELETE CASCADE,
|
||||
rcon_password TEXT NOT NULL, -- encrypted at app layer
|
||||
max_ping INTEGER NOT NULL DEFAULT 200,
|
||||
enabled INTEGER NOT NULL DEFAULT 1,
|
||||
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationships Diagram
|
||||
|
||||
```
|
||||
users (1) ──────────────────────────────────── (many) server_events.actor
|
||||
|
||||
servers (1) ──┬── (1) server_configs
|
||||
├── (1) basic_configs
|
||||
├── (1) server_profiles
|
||||
├── (1) launch_params
|
||||
├── (1) rcon_configs
|
||||
├── (many) server_mods ──── (many) mods
|
||||
├── (many) missions
|
||||
├── (many) mission_rotation → missions
|
||||
├── (many) players
|
||||
├── (many) player_history
|
||||
├── (many) bans
|
||||
├── (many) logs
|
||||
├── (many) metrics
|
||||
└── (many) server_events
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Maintenance Queries
|
||||
|
||||
### Log Retention Cleanup (run daily via APScheduler)
|
||||
```sql
|
||||
DELETE FROM logs
|
||||
WHERE created_at < datetime('now', '-7 days');
|
||||
```
|
||||
|
||||
### Metrics Retention Cleanup (keep 30 days)
|
||||
```sql
|
||||
DELETE FROM metrics
|
||||
WHERE timestamp < datetime('now', '-30 days');
|
||||
```
|
||||
|
||||
### Clear disconnected players on server stop
|
||||
```sql
|
||||
DELETE FROM players WHERE server_id = ?;
|
||||
```
|
||||
|
||||
### Vacuum (run weekly)
|
||||
```sql
|
||||
VACUUM;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
- Migrations are plain `.sql` files in `backend/migrations/`
|
||||
- Naming: `001_initial_schema.sql`, `002_add_bans.sql`, etc.
|
||||
- Tracked in a `schema_migrations` table:
|
||||
```sql
|
||||
CREATE TABLE schema_migrations (
|
||||
version INTEGER PRIMARY KEY,
|
||||
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
);
|
||||
```
|
||||
- Applied automatically at app startup by `database.py:run_migrations()`
|
||||
Reference in New Issue
Block a user