Stage and commit remaining 4 title renames that were left as unstaged working-tree changes: - API.md: Languard Server Manager → Languard Servers Manager - DATABASE.md: Languard Server Manager → Languard Servers Manager - MODULES.md: Languard Server Manager → Languard Servers Manager - THREADING.md: Languard Server Manager → Languard Servers Manager
587 lines
20 KiB
Markdown
587 lines
20 KiB
Markdown
# Languard Servers 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()`
|