# 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` ```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` ```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=.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 1. **File-based storage** — No database. Settings, configs, and modlists are files on disk. Portable, backup-able with simple file copy. 2. **Env-defined paths** — `SERVERFILE_DIR`, `MODS_DIR`, `CFG_DIR`, `PROFILES_DIR` are environment variables, not stored in the UI. 3. **Configs are real .cfg files** — Stored in `$CFG_DIR`, directly usable by the Arma 3 server's `-config=` parameter. 4. **Modlist = mod references + enabled state** — The `-mod=` parameter is built at start time by resolving enabled mods. For each mod, `$MODS_DIR/@name` is tried first (manually placed or symlinked); if not found and a Steam Workshop `id` exists, `$SERVERFILE_DIR/steamapps/workshop/content/107410/` is used as fallback. 5. **Single server instance** — Only one server at a time. No multi-instance support. 6. **No auth for v1** — JWT auth can be added later without breaking the API design. 7. **Docker-first deployment** — Single docker-compose.yml bundles SteamCMD, backend, and frontend serving.