docs: rewrite PLAN.md and update CODEBASE.md, README.md
- Full rewrite of PLAN.md to reflect current architecture (27 REST + 3 WS endpoints, embedded frontend, automation, scheduler, health check, GoReleaser CI) - Added health.go, mods.go, scheduler.go, robfig/cron dep to CODEBASE.md - Added Gitea Actions CI/CD section to CODEBASE.md - Added conventional commits to code style section - Added missing API routes and embed/ to README.md
This commit is contained in:
@@ -10,9 +10,11 @@ A web-based control panel to configure, update, install, and execute a **single*
|
||||
|
||||
| Layer | Choice | Rationale |
|
||||
|-------|--------|-----------|
|
||||
| **Backend** | Go 1.24+ (`gin`, `gorilla/websocket`) | Single binary, cross-compiles, goroutines for process streaming |
|
||||
| **Backend** | Go 1.25 (`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 |
|
||||
| **Scheduling** | `robfig/cron/v3` | Cron-based scheduled updates (game + mods) |
|
||||
| **Frontend** | React 19 + TypeScript 6 + Vite 8 | Fast iteration, rich ecosystem |
|
||||
| **Data Fetching** | TanStack Query v5 | Server state, caching, polling, mutations |
|
||||
| **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 |
|
||||
@@ -24,49 +26,72 @@ A web-based control panel to configure, update, install, and execute a **single*
|
||||
```
|
||||
arma3-web-server/
|
||||
├── backend/
|
||||
│ ├── cmd/server/main.go # Entry point
|
||||
│ ├── cmd/server/main.go # Entry point, env parsing, dir creation, startup auto-tasks
|
||||
│ ├── embed/
|
||||
│ │ ├── embed.go # //go:embed dist — embeds frontend into Go binary
|
||||
│ │ └── dist/ # Pre-built frontend SPA assets
|
||||
│ ├── 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
|
||||
│ │ │ ├── router.go # Route registration, Handler struct + New()
|
||||
│ │ │ ├── settings.go # Settings CRUD + server start/stop/restart + steamcmd
|
||||
│ │ │ ├── configs.go # .cfg file CRUD handlers
|
||||
│ │ │ ├── modlists.go # Modlist CRUD + import + export + check/download-missing/update-all
|
||||
│ │ │ ├── mods.go # Mod listing, deletion, bulk cleanup
|
||||
│ │ │ ├── health.go # Comprehensive health check endpoint
|
||||
│ │ │ └── logs.go # WS streaming (server, steamcmd, rpt) + log file listing
|
||||
│ │ ├── models/ # Data structs
|
||||
│ │ │ ├── settings.go # ServerSettings (singleton)
|
||||
│ │ │ └── modlist.go # Modlist + ModEntry
|
||||
│ │ └── services/ # Business logic
|
||||
│ │ ├── settings.go # Singleton settings load/save
|
||||
│ │ │ ├── settings.go # ServerSettings (singleton, 15 fields)
|
||||
│ │ │ └── modlist.go # Modlist + ModEntry + ModlistListItem
|
||||
│ │ └── services/ # Business logic layer
|
||||
│ │ ├── settings.go # JSON load/save from data/settings.json
|
||||
│ │ ├── 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
|
||||
│ │ ├── modlist_manager.go # Modlist CRUD against data/modlists/*.json
|
||||
│ │ ├── modlist_parser.go # HTML Arma Launcher preset parser + renderer
|
||||
│ │ ├── mod_manager.go # Workshop + local mod discovery + usage map
|
||||
│ │ ├── server_process.go # Process lifecycle, arg builder, mod path resolver
|
||||
│ │ ├── steamcmd.go # SteamCMD manager (UpdateGame, DownloadMod, DownloadMods)
|
||||
│ │ ├── scheduler.go # Cron-based scheduled updates
|
||||
│ │ └── log_streamer.go # Pub/sub fan-out for stdout/stderr via channels
|
||||
│ ├── go.mod
|
||||
│ └── go.sum
|
||||
├── frontend/
|
||||
│ ├── src/
|
||||
│ │ ├── App.tsx # Router setup (React Router v7)
|
||||
│ │ ├── main.tsx # Entry point (QueryClient + BrowserRouter)
|
||||
│ │ ├── index.css # Tailwind v4 imports
|
||||
│ │ ├── api/
|
||||
│ │ │ └── client.ts # Typed fetch wrappers + WS URL builders
|
||||
│ │ ├── types/
|
||||
│ │ │ └── index.ts # ServerSettings, Modlist, ModEntry, ConfigInfo, ModInfo, ServerHealth
|
||||
│ │ ├── 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
|
||||
│ │ │ ├── Dashboard.tsx # Overview cards (status, configs, modlists)
|
||||
│ │ │ ├── Settings.tsx # Main settings tab + userconfig tabs + SteamCMD + automation
|
||||
│ │ │ ├── Configs.tsx # List/create/duplicate/delete configs
|
||||
│ │ │ ├── ConfigEditor.tsx # Full-page Monaco editor for a single config
|
||||
│ │ │ ├── Modlists.tsx # List/create/duplicate/delete + HTML import
|
||||
│ │ │ ├── ModlistEditor.tsx # Mod list reorder + enable/disable + check/download-missing/update-all
|
||||
│ │ │ ├── Mods.tsx # Installed mods table + search + delete + cleanup
|
||||
│ │ │ ├── Logs.tsx # Tabbed LiveTerminal (Server Console / RPT / SteamCMD) + file browser
|
||||
│ │ │ └── Status.tsx # Health check / deploy status dashboard
|
||||
│ │ └── components/
|
||||
│ │ ├── ConfigEditor.tsx # Monaco editor wrapper
|
||||
│ │ ├── LiveTerminal.tsx # xterm.js + auto-reconnect WebSocket
|
||||
│ │ ├── Layout.tsx # Sidebar nav shell
|
||||
│ │ └── ui/ # (empty — reserved for future shared UI primitives)
|
||||
│ ├── package.json
|
||||
│ └── vite.config.ts
|
||||
├── data/ # Runtime data
|
||||
│ ├── settings.json # Singleton server settings
|
||||
│ └── modlists/ # Modlist JSON files (*.json)
|
||||
│ └── vite.config.ts # Proxy /api + /ws to :8080
|
||||
├── data/ # Runtime data (mounted volume in Docker)
|
||||
│ ├── settings.json # Singleton server settings
|
||||
│ └── modlists/ # Modlist JSON files (*.json)
|
||||
├── dev-deploy/ # Local development runtime data (git-ignored)
|
||||
├── .gitea/workflows/
|
||||
│ ├── ci.yml # Build-only CI (Go + frontend)
|
||||
│ └── release.yml # GoReleaser-based release on tag push
|
||||
├── docker-compose.yml
|
||||
├── Dockerfile
|
||||
└── Makefile
|
||||
├── Dockerfile # Multi-stage: Go build -> npm build -> alpine runtime + SteamCMD
|
||||
├── Dockerfile.goreleaser # Single-stage for GoReleaser (pre-built artifacts injected)
|
||||
├── .goreleaser.yaml # GoReleaser v2 config (Gitea release target)
|
||||
└── Makefile # Convenience targets: backend, frontend, build, run, dev, clean
|
||||
```
|
||||
|
||||
---
|
||||
@@ -75,13 +100,14 @@ arma3-web-server/
|
||||
|
||||
| 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` |
|
||||
| `SERVERFILE_DIR` | Base dir where `arma3server_x64` binary + `userconfig/` live | `./serverfiles` |
|
||||
| `MODS_DIR` | Base dir where `@modname` folders are stored | `$SERVERFILE_DIR/mods` |
|
||||
| `CFG_DIR` | Base dir where `.cfg` config files are stored | `$SERVERFILE_DIR/cfg` |
|
||||
| `PROFILES_DIR` | Server profile/save/log path | `$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` |
|
||||
|
||||
> Note: `FRONTEND_DIR` was removed — the frontend is now embedded into the Go binary via `//go:embed`.
|
||||
|
||||
---
|
||||
|
||||
@@ -101,6 +127,11 @@ arma3-web-server/
|
||||
"difficulty_presets": "",
|
||||
"active_config": "server",
|
||||
"active_modlist": "uuid-of-modlist",
|
||||
"auto_update_on_startup": false,
|
||||
"auto_start_on_startup": false,
|
||||
"auto_update_mods_on_startup": false,
|
||||
"was_running": false,
|
||||
"scheduled_update": "",
|
||||
"updated_at": "2026-01-01T00:00:00Z"
|
||||
}
|
||||
```
|
||||
@@ -151,11 +182,19 @@ POST /api/server/start
|
||||
POST /api/server/stop
|
||||
POST /api/server/restart
|
||||
GET /api/server/status
|
||||
GET /api/server/paths # returns configured directory paths
|
||||
GET /api/server/health # comprehensive health check (binary, paths, disk, mods, steamcmd, frontend)
|
||||
|
||||
# SteamCMD
|
||||
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
|
||||
|
||||
# Logs
|
||||
GET /api/server/logs # list log files (.log, .rpt)
|
||||
GET /api/server/logs/:file # read log file content
|
||||
|
||||
# Configs (.cfg files in $CFG_DIR)
|
||||
GET /api/configs
|
||||
POST /api/configs
|
||||
@@ -172,17 +211,24 @@ 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/export (returns downloadable 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)
|
||||
# Mods (installed on disk)
|
||||
GET /api/mods # combined workshop + local mods with usage info
|
||||
DELETE /api/mods # delete mod by path (validates path prefix)
|
||||
POST /api/mods/cleanup # bulk-delete all mods not referenced by any modlist
|
||||
|
||||
# WebSocket Endpoints
|
||||
GET /ws/server/logs # live server process stdout/stderr
|
||||
GET /ws/steamcmd/logs # live steamcmd output
|
||||
GET /ws/server/rpt # tail latest .rpt crash dump (250ms polling)
|
||||
```
|
||||
|
||||
**Total: 27 REST endpoints + 3 WebSocket endpoints**
|
||||
|
||||
---
|
||||
|
||||
## Process Start Flow
|
||||
@@ -190,24 +236,53 @@ GET /api/server/logs/:file (read log file)
|
||||
```
|
||||
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
|
||||
→ WasRunning set to true, saved to disk
|
||||
→ If ServerParameters contains %command%:
|
||||
→ Replace %command% with binary path (enables Wine wrappers)
|
||||
→ Split into command + args
|
||||
→ Append auto-args: -config=, -mod=, -profiles=, -port=
|
||||
→ exec.CommandContext(ctx, parts[0], parts[1:]..., autoArgs...)
|
||||
Else:
|
||||
→ Split server_parameters into args
|
||||
→ Append auto-args
|
||||
→ exec.CommandContext(ctx, binPath, args...)
|
||||
→ cmd.Dir = serverfileDir
|
||||
→ stdout/stderr piped to LogStreamer fan-out
|
||||
→ cmd.Start()
|
||||
→ WebSocket clients receive live output
|
||||
→ On exit, process reference cleaned up
|
||||
→ On exit, process reference cleaned up, [SERVER_PROCESS_EXITED] broadcast
|
||||
→ Frontend polls GET /api/server/status every 3-5s → shows "Running" badge
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Startup Auto-Tasks
|
||||
|
||||
Configured via the Automation section of the Settings UI. All run asynchronously at boot:
|
||||
|
||||
| Setting | Behavior |
|
||||
|---------|----------|
|
||||
| **Auto-update server on startup** | Runs `steamcmd +app_update` if `steam_user` is set |
|
||||
| **Auto-update mods on startup** | Downloads workshop updates for every enabled mod in the active modlist |
|
||||
| **Auto-start server on startup** | Restarts the game server if `was_running` was true when the service last stopped |
|
||||
|
||||
### Scheduled Updates
|
||||
|
||||
A cron expression in the `scheduled_update` field runs game + mod updates on a schedule (e.g. `"0 4 * * *"` for daily at 4 AM). Uses `robfig/cron/v3`. The scheduled update runs even if the game server is currently running.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
3. **Embedded frontend** — The built React SPA is embedded into the Go binary via `//go:embed`, creating a single self-contained deployment artifact. The `NoRoute` handler serves `index.html` for client-side routing.
|
||||
4. **Configs are real .cfg files** — Stored in `$CFG_DIR`, directly usable by the Arma 3 server's `-config=` parameter.
|
||||
5. **Modlist = mod references + enabled state** — The `-mod=` parameter is built at start time by resolving enabled mods. Two-tier resolution: `$MODS_DIR/@name` first (manually placed or symlinked); fallback to `$SERVERFILE_DIR/steamapps/workshop/content/107410/<id>` for workshop downloads.
|
||||
6. **`%command%` substitution** — If `ServerParameters` contains `%command%`, the entire textarea is treated as a shell template with `%command%` replaced by the binary path and auto-args appended. Enables Wine wrappers.
|
||||
7. **Single server instance** — Only one server at a time. No multi-instance support.
|
||||
8. **No auth for v1** — JWT auth can be added later without breaking the API design.
|
||||
9. **Docker-first deployment** — Single docker-compose.yml bundles SteamCMD, backend, and frontend serving. Also supports GoReleaser for automated releases to Gitea.
|
||||
10. **Crash recovery** — `WasRunning` flag persists across restarts. If `auto_start_on_startup` is enabled, the server restarts automatically.
|
||||
11. **SteamCMD async with live streaming** — All steamcmd operations run in background goroutines, output broadcast to `"steamcmd"` key via LogStreamer pub/sub.
|
||||
12. **Health endpoint** — `/api/server/health` checks binary existence, directory writability, disk usage, mod counts, SteamCMD status, and frontend serving.
|
||||
|
||||
Reference in New Issue
Block a user