Initial commit
This commit is contained in:
213
PLAN.md
Normal file
213
PLAN.md
Normal file
@@ -0,0 +1,213 @@
|
||||
# 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=<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
|
||||
|
||||
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/<id>` 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.
|
||||
Reference in New Issue
Block a user