From 41c139097269a2a8346c055a2da6764823b2519f Mon Sep 17 00:00:00 2001 From: MrFastwind Date: Thu, 23 Jul 2026 22:29:22 +0200 Subject: [PATCH] docs: update PLAN, CODEBASE, README with env vars, stubs, tests, atomic state PLAN.md: - Add SERVER_BINARY, SERVER_PARAMS, STEAMCMD_PATH to env vars table - Add testdata/ to project structure - Add testing section (backend tests, stubs, frontend tests, make targets) - Update process start flow with env var overrides CODEBASE.md: - Add testdata/ stubs to directory map - Replace zustand with vitest/testing-library/jsdom in frontend deps - Update CI to include test steps - Update Makefile targets description - Add atomic state machine to ProcessManager description - Add env overrides and atomic state to design decisions - Update data flow with env var overrides README.md: - Add SERVER_BINARY, SERVER_PARAMS, STEAMCMD_PATH to env vars table - Remove FRONTEND_DIR (embedded frontend) - Update Development section with make targets --- CODEBASE.md | 23 ++++++++++++++------ PLAN.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++-- README.md | 25 ++++++++++++++++------ 3 files changed, 93 insertions(+), 16 deletions(-) diff --git a/CODEBASE.md b/CODEBASE.md index 138e3cb..6d725d5 100644 --- a/CODEBASE.md +++ b/CODEBASE.md @@ -40,10 +40,13 @@ arma3-web-server/ │ │ ├── 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 +│ │ ├── server_process.go # Process lifecycle, arg builder, mod path resolver (atomic state machine) │ │ ├── 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 +│ │ ├── log_streamer.go # Pub/sub fan-out for stdout/stderr via channels +│ │ └── testdata/ # Stub binaries for testing +│ │ ├── arma3server_x64 # Server stub: logs args, heartbeat, -t N exit, RPT writes +│ │ └── steamcmd # SteamCMD stub: fake downloads, creates mod dirs │ ├── go.mod / go.sum │ └── serverfiles/ # Default SERVERFILE_DIR (created at startup) ├── frontend/ # React SPA @@ -107,11 +110,14 @@ arma3-web-server/ | `tailwindcss` | styling | ^4.3.2 | Utility-first CSS | | `@monaco-editor/react` | editor | ^4.7.0 | Monaco code editor for .cfg files | | `@xterm/xterm` / `@xterm/addon-fit` | terminal | ^6.0.0 / ^0.11.0 | In-browser xterm.js terminal | -| `zustand` | state | ^5.0.14 | Declared but **unused** (no stores exist) | | `vite` | bundler | ^8.1.1 | Dev server + production build | | `@vitejs/plugin-react` | tooling | ^6.0.3 | React fast-refresh / JSX transform | | `typescript` | language | ~6.0.2 | Type checking | | `oxlint` | linter | ^1.71.0 | Linting | +| `vitest` | test runner | ^4.1.10 | Frontend unit/integration tests | +| `@testing-library/react` | test utility | ^16.3.0 | React component testing | +| `@testing-library/jest-dom` | test matcher | ^6.6.3 | DOM assertion matchers | +| `jsdom` | test env | ^26.1.0 | Browser environment for tests | ## Architecture @@ -133,7 +139,7 @@ Browser -- REST/WS --> api.Handler --> SettingsManager --> data/settings.j - **Handler** (api package) -- stateless; receives service pointers via constructor injection. All handlers are methods on Handler. - ***Manager** (services package) -- stateful structs for each concern. Hold their own `sync.Mutex` for thread safety. - **LogStreamer** -- pub/sub per named stream (`"server"`, `"steamcmd"`). Subscribe returns a `chan string`; Stream reads from an `io.Reader` (process stdout/stderr) and broadcasts to all subscribers of that key. -- **ProcessManager** -- manages the Arma 3 server child process lifecycle. Builds the command line from settings, resolves mod paths, monitors process exit. +- **ProcessManager** -- manages the Arma 3 server child process lifecycle. Uses `atomic.Int32` state machine (idle/starting/running/stopping) with `CompareAndSwap` for lock-free state transitions. Builds the command line from settings, resolves mod paths, monitors process exit. ### Notable design decisions 1. **No database** -- everything is files: `settings.json`, `*.cfg` in `$CFG_DIR`, modlist JSON files. Backup means copying directories. @@ -141,7 +147,8 @@ Browser -- REST/WS --> api.Handler --> SettingsManager --> data/settings.j 3. **`%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 (`-config=`, `-mod=`, `-profiles=`, `-port=`) appended. Enables Wine wrappers. 4. **Two-tier mod resolution** -- resolveModPath checks `$MODS_DIR/@name` first (manual/symlinked), then falls back to `$SERVERFILE_DIR/steamapps/workshop/content/107410/` for workshop downloads. 5. **SteamCMD async with live streaming** -- all steamcmd operations run in background goroutines, output broadcast to `"steamcmd"` key. -6. **Env-defined paths** -- all dirs set via env vars with hardcoded defaults, resolved to absolute paths at startup. +6. **Env-defined paths** -- all dirs set via env vars with hardcoded defaults, resolved to absolute paths at startup. `SERVER_BINARY`, `SERVER_PARAMS`, `STEAMCMD_PATH` override defaults at runtime. +7. **Atomic state machine** -- ProcessManager and SteamCmdManager use `atomic.Int32`/`atomic.Bool` with `CompareAndSwap` for lock-free state transitions, eliminating race conditions between concurrent Start/Stop calls. ## Data flow (example: Start server) @@ -152,6 +159,8 @@ User clicks "Start" (Settings.tsx) --> Handler.StartServer() (settings.go) --> process.Start() (server_process.go) --> Load settings.json + --> SERVER_PARAMS env overrides ServerParameters if set + --> SERVER_BINARY env overrides platform default binary name if set --> If %command% in ServerParameters: --> Replace %command% with binPath --> Split into command + args @@ -174,7 +183,7 @@ User clicks "Start" (Settings.tsx) ### Gitea Actions (`/.gitea/workflows/`) -- **`ci.yml`** — Triggered on push to any branch or PR. Runs `go build ./...` and `npm ci && npm run build`. Build-only; no tests. +- **`ci.yml`** — Triggered on push to any branch or PR. Runs `go build ./...`, `npm ci && npm run build`, `go test ./...`, and `npm test`. Build + test CI. - **`release.yml`** — Triggered on `v*` tag push. Runs GoReleaser v2 with `release --clean`, which builds Go binaries (linux/amd64 + windows/amd64), builds frontend via `before.hooks`, creates archives, builds + pushes a Docker image using `Dockerfile.goreleaser`, and publishes a Gitea release. ### Docker @@ -184,4 +193,4 @@ User clicks "Start" (Settings.tsx) ### Local dev -- **Makefile targets** — `make run` starts backend, `make dev` starts both backend + Vite dev server with hot reload. +- **Makefile targets** — `make run` starts backend, `make dev` starts both backend + Vite dev server with hot reload (copies test stubs to dev-deploy, passes `SERVER_BINARY`/`SERVER_PARAMS`/`STEAMCMD_PATH` env vars). `make test` runs both backend and frontend tests. diff --git a/PLAN.md b/PLAN.md index 48a2643..7d4d39b 100644 --- a/PLAN.md +++ b/PLAN.md @@ -51,7 +51,10 @@ arma3-web-server/ │ │ ├── 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 +│ │ ├── log_streamer.go # Pub/sub fan-out for stdout/stderr via channels +│ │ └── testdata/ # Stub binaries for testing +│ │ ├── arma3server_x64 # Server stub: logs args, heartbeat, -t N exit, RPT writes +│ │ └── steamcmd # SteamCMD stub: fake downloads, creates mod dirs │ ├── go.mod │ └── go.sum ├── frontend/ @@ -91,7 +94,7 @@ arma3-web-server/ ├── 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 +└── Makefile # Convenience targets: backend, frontend, build, run, dev, clean, test ``` --- @@ -106,6 +109,9 @@ arma3-web-server/ | `PROFILES_DIR` | Server profile/save/log path | `$SERVERFILE_DIR/profiles` | | `DATA_DIR` | Internal data (settings.json, modlists/) | `./data` | | `LISTEN` | HTTP listen address | `:8080` | +| `SERVER_BINARY` | Server binary filename (overrides platform default) | `arma3server_x64` (`.exe` on Windows) | +| `SERVER_PARAMS` | Override server launch parameters (overrides settings.json) | `-server -world=empty -loadMissionToMemory -noPause` | +| `STEAMCMD_PATH` | Path to steamcmd binary | `steamcmd` | > Note: `FRONTEND_DIR` was removed — the frontend is now embedded into the Go binary via `//go:embed`. @@ -237,6 +243,8 @@ GET /ws/server/rpt # tail latest .rpt crash dump (250ms pollin User clicks Start → Settings loaded from data/settings.json → WasRunning set to true, saved to disk + → SERVER_PARAMS env overrides ServerParameters if set + → SERVER_BINARY env overrides platform default binary name if set → If ServerParameters contains %command%: → Replace %command% with binary path (enables Wine wrappers) → Split into command + args @@ -286,3 +294,52 @@ A cron expression in the `scheduled_update` field runs game + mod updates on a s 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. + +--- + +## Testing + +### Backend tests + +Run with `make test-backend` or `cd backend && go test ./...`. + +Test files in `backend/internal/services/`: +- `settings_test.go` — SettingsManager load/save/defaults +- `config_manager_test.go` — ConfigManager CRUD + path traversal protection +- `modlist_manager_test.go` — ModlistManager CRUD + duplicate +- `modlist_parser_test.go` — HTML preset parser + renderer +- `log_streamer_test.go` — Pub/sub subscribe/unsubscribe/broadcast/stream +- `server_process_test.go` — Process lifecycle, arg building, mod path resolution, env var overrides, stub-based integration tests +- `steamcmd_test.go` — SteamCMD state machine, CheckWorkshopMod, stub-based download tests + +### Test stubs + +Stub binaries in `backend/internal/services/testdata/` replace real server/steamcmd during tests: + +**`arma3server_x64` stub:** +- Logs binary path and all arguments to stdout +- Prints heartbeat every 5 seconds +- Accepts `-t N` to exit after N seconds (for auto-exit and timeout tests) +- Parses `-profiles=` and writes RPT log entries to `profilesDir/arma3server_x64_*.rpt` + +**`steamcmd` stub:** +- Parses `+force_install_dir` and `+workshop_download_item` args +- Creates fake mod directories at the expected workshop content path +- Prints fake download progress and success logs + +### Frontend tests + +Run with `make test-frontend` or `cd frontend && npm test`. + +Uses Vitest + React Testing Library + jsdom: +- `utils/format.test.ts` — fmtSize utility +- `components/Layout.test.tsx` — Sidebar navigation +- `pages/Mods.test.tsx` — Mods page with mods present + +### Makefile test targets + +| Target | Description | +|--------|-------------| +| `make test` | Run both backend and frontend tests | +| `make test-backend` | Run `go test ./...` in backend | +| `make test-frontend` | Run `vitest run` in frontend | diff --git a/README.md b/README.md index e8697f3..5755ac3 100644 --- a/README.md +++ b/README.md @@ -47,8 +47,10 @@ All paths are configurable via environment variables: | `MODS_DIR` | `serverfiles/mods` | Local mod symlinks/copies | | `CFG_DIR` | `serverfiles/cfg` | Server configuration `.cfg` files | | `PROFILES_DIR` | `serverfiles/profiles` | Arma 3 profile and log directory | -| `FRONTEND_DIR` | `../frontend/dist` | Built frontend assets | | `LISTEN` | `:8080` | HTTP listen address | +| `SERVER_BINARY` | `arma3server_x64` | Server binary filename (overrides platform default) | +| `SERVER_PARAMS` | `-server -world=empty ...` | Override server launch parameters | +| `STEAMCMD_PATH` | `steamcmd` | Path to steamcmd binary | ## Automation @@ -150,14 +152,23 @@ frontend/ ## Development ```bash -# Backend -cd backend && go run ./cmd/server +# Start both backend + frontend with hot reload (copies stubs to dev-deploy) +make dev -# Frontend (hot reload) -cd frontend && npm run dev +# Start backend only +make run -# Lint -cd frontend && npm run lint +# Run all tests +make test + +# Run backend tests only +make test-backend + +# Run frontend tests only +make test-frontend + +# Build for production +make build ``` ## License