8.2 KiB
8.2 KiB
Arma 3 Web Server — Architecture Plan
Overview
A web-based control panel to configure, update, install, and execute a single Arma 3 dedicated server instance. Supports both Windows and Linux server binaries via SteamCMD.
Tech Stack
| Layer | Choice | Rationale |
|---|---|---|
| Backend | Go 1.24+ (gin, gorilla/websocket) |
Single binary, cross-compiles, goroutines for process streaming |
| Storage | File-based (JSON on disk) | No database dependency; settings, configs, and modlists are files |
| Frontend | React 19 + TypeScript + Vite | Fast iteration, rich ecosystem |
| UI Kit | Tailwind CSS 4 | Dark-theme UI, utility-first styling |
| Code Editor | Monaco (VS Code) | Syntax-highlighted config editing |
| Terminal | xterm.js | Live log display from server stdout |
Project Structure
arma3-web-server/
├── backend/
│ ├── cmd/server/main.go # Entry point
│ ├── internal/
│ │ ├── api/ # HTTP handlers (Gin routes)
│ │ │ ├── router.go # Route registration
│ │ │ ├── settings.go # Server settings + start/stop/restart
│ │ │ ├── configs.go # .cfg file CRUD
│ │ │ ├── modlists.go # Modlist CRUD
│ │ │ └── logs.go # WebSocket handler for live logs
│ │ ├── models/ # Data structs
│ │ │ ├── settings.go # ServerSettings (singleton)
│ │ │ └── modlist.go # Modlist + ModEntry
│ │ └── services/ # Business logic
│ │ ├── settings.go # Singleton settings load/save
│ │ ├── config_manager.go # .cfg file I/O in $CFG_DIR
│ │ ├── modlist_manager.go # Modlist CRUD (JSON files)
│ │ ├── server_process.go # Process lifecycle (start/stop/restart)
│ │ └── log_streamer.go # stdout/stderr → WebSocket fan-out
│ ├── go.mod
│ └── go.sum
├── frontend/
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Dashboard.tsx
│ │ │ ├── Settings.tsx
│ │ │ ├── Configs.tsx
│ │ │ ├── ConfigEditor.tsx
│ │ │ ├── Modlists.tsx
│ │ │ ├── ModlistEditor.tsx
│ │ │ └── Logs.tsx
│ │ ├── components/
│ │ │ ├── ConfigEditor.tsx # Monaco wrapper
│ │ │ ├── LiveTerminal.tsx # xterm.js wrapper
│ │ │ └── Layout.tsx # Sidebar nav shell
│ │ ├── api/client.ts # Typed fetch wrapper
│ │ └── types/index.ts # TypeScript types
│ ├── package.json
│ └── vite.config.ts
├── data/ # Runtime data
│ ├── settings.json # Singleton server settings
│ └── modlists/ # Modlist JSON files (*.json)
├── docker-compose.yml
├── Dockerfile
└── Makefile
Environment Variables
| Env | Purpose | Default |
|---|---|---|
SERVERFILE_DIR |
Base dir where arma3server_x64 binary + userconfig/ live |
"" (must be set) |
MODS_DIR |
Base dir where @modname folders are stored |
"" (must be set) |
CFG_DIR |
Base dir where .cfg config files are stored |
"" (must be set) |
PROFILES_DIR |
Server profile/save path (ignored in UI) | $SERVERFILE_DIR/Profiles |
DATA_DIR |
Internal data (settings.json, modlists/) | ./data |
LISTEN |
HTTP listen address | :8080 |
FRONTEND_DIR |
Path to built frontend files | ../frontend/dist |
Data Model
Server Settings (singleton) — data/settings.json
{
"ip_port": "0.0.0.0:2302",
"server_parameters": "-server -world=empty -loadMissionToMemory -noPause",
"steam_branch": "stable",
"steam_user": "anonymous",
"platform": "linux",
"cba_settings": "",
"ai_level_presets": "",
"difficulty_presets": "",
"active_config": "server",
"active_modlist": "uuid-of-modlist",
"updated_at": "2026-01-01T00:00:00Z"
}
Configs — $CFG_DIR/*.cfg
Each config is a plain .cfg file stored in the env-defined CFG_DIR.
CRUD operations create/rename/delete these files directly on disk.
Modlists — data/modlists/{uuid}.json
{
"id": "uuid",
"name": "My Modlist",
"mods": [
{ "id": "450814997", "name": "CBA_A3", "enabled": true },
{ "id": "463939057", "name": "ACE", "enabled": false }
],
"created_at": "...",
"updated_at": "..."
}
Server Configuration
The three userconfig files are singletons — only one version can exist on disk at a time:
| File | Disk location | Managed in |
|---|---|---|
cba_settings.sqf |
$SERVERFILE_DIR/userconfig/cba_settings.sqf |
Settings page (textarea) |
CfgAILevelPresets.sqf |
$SERVERFILE_DIR/userconfig/CfgAILevelPresets.sqf |
Settings page (textarea) |
CfgDifficultyPresets.sqf |
$SERVERFILE_DIR/userconfig/CfgDifficultyPresets.sqf |
Settings page (textarea) |
These are written to disk immediately when settings are saved.
API Routes
# Server Settings (singleton)
GET /api/server/settings
PUT /api/server/settings # also writes userconfig files to disk
POST /api/server/start
POST /api/server/stop
POST /api/server/restart
GET /api/server/status
GET /api/server/steamcmd # returns current branch/user/platform
POST /api/server/steamcmd/update-game
POST /api/server/steamcmd/download-mod
GET /api/server/steamcmd/status
# Configs (.cfg files in $CFG_DIR)
GET /api/configs
POST /api/configs
GET /api/configs/:name
PUT /api/configs/:name
DELETE /api/configs/:name
POST /api/configs/:name/duplicate
# Modlists
GET /api/modlists
POST /api/modlists
GET /api/modlists/:id
PUT /api/modlists/:id
DELETE /api/modlists/:id
POST /api/modlists/:id/duplicate
POST /api/modlists/import (multipart form: file=*.html — parses Arma Launcher HTML preset)
GET /api/modlists/:id/check (returns mod entries with downloaded: bool)
POST /api/modlists/:id/download-missing
POST /api/modlists/:id/update-all
# Logs
GET /ws/server/logs (WebSocket — live server stream)
GET /ws/steamcmd/logs (WebSocket — live steamcmd stream)
GET /api/server/logs (list log files)
GET /api/server/logs/:file (read log file)
Process Start Flow
User clicks Start
→ Settings loaded from data/settings.json
→ Args built from server_parameters + -config=<active_config>.cfg
→ -profiles=$PROFILES_DIR auto-appended (unless already in server_parameters)
→ -port= derived from ip_port field (unless -port= already in server_parameters)
→ Mod path built from active modlist (enabled mods → @modname paths)
→ arma3server_x64 spawned via exec.CommandContext
→ stdout/stderr piped to LogStreamer fan-out
→ WebSocket clients receive live output
→ On exit, process reference cleaned up
Key Design Decisions
- File-based storage — No database. Settings, configs, and modlists are files on disk. Portable, backup-able with simple file copy.
- Env-defined paths —
SERVERFILE_DIR,MODS_DIR,CFG_DIR,PROFILES_DIRare environment variables, not stored in the UI. - Configs are real .cfg files — Stored in
$CFG_DIR, directly usable by the Arma 3 server's-config=parameter. - Modlist = mod references + enabled state — The
-mod=parameter is built at start time by resolving enabled mods. For each mod,$MODS_DIR/@nameis tried first (manually placed or symlinked); if not found and a Steam Workshopidexists,$SERVERFILE_DIR/steamapps/workshop/content/107410/<id>is used as fallback. - Single server instance — Only one server at a time. No multi-instance support.
- No auth for v1 — JWT auth can be added later without breaking the API design.
- Docker-first deployment — Single docker-compose.yml bundles SteamCMD, backend, and frontend serving.