A full web interface to manage your BeamMP server without touching the terminal. Accessible from any device on your network, or from anywhere in the world using Cloudflare Tunnel.
Forked from assetto-server-panel and kept structurally identical so the two panels feel like siblings — same auth model, same audit chain, same Docker shape. The game-specific half (server lifecycle, config editor, content catalogue, live state) is rewritten end-to-end for BeamMP's free-roam multiplayer model.
Live host metrics (CPU, RAM, uptime) and BeamMP-Server status pushed via Server-Sent Events. A second row of BeamMP-specific KPI widgets carries the active map (rendered as the official BeamNG.drive screenshot under a darkening gradient with the map's display name + theme badge on top — like a small map-card preview), joins in the last 24 h (from the persistent player_events history), mods on disk (Resources/Client + Server total), and the BeamMP-Server binary version. Polled at /api/dashboard/extra every 30 s.
The Dashboard also surfaces a BeamMP-Server update notifier banner: queries the GitHub releases API once per 6 h, compares against the local BeamMP-Server --version, and shows a per-version-dismissable banner with a link to the release notes when a newer server is out. Best-effort: invisible if GitHub is unreachable or the binary doesn't expose --version.
Browse the 15 official BeamNG.drive levels (Acantilado, Costa Este, Costa Oeste, Italia, Utah, Johnson Valley, Isla Jungle Rock, Pista de carreras Hirochi, Automation Test Track, Polígono industrial, Centro ETK de formación de pilotos, Circuitos de demolición, Isla pequeña, Cuadrícula y Mapa de prueba v2) and any custom map mods auto-detected from Resources/Client/*.zip. The catalogue uses the official BeamNG.drive map screenshots sourced from documentation.beamng.com/official_content/levels/images/ (normalised to 800×450 q82 strip-metadata via ImageMagick mogrify) so the grid reads as the real maps rather than placeholder art.
Click a card → modal with full details (display name, level path, theme, size, description) → activate-with-restart-prompt. The Map field in Server Config is no longer a free-text input — operators pick from the catalogue and the panel writes the canonical /levels/<id>/info.json path into the TOML. Custom map mods fall back to a themed SVG illustration when there's no canonical screenshot available.
Names switch to Spanish display strings ("Costa Oeste, EE. UU.", "Centro ETK de formación de pilotos", …) when the panel is set to ES locale. The list sorts alphabetically with a locale-aware Intl.Collator so accented characters land in the right place.
Persisted list + interval in the same Maps page. An in-process timer activates the next map every N minutes and restarts BeamMP-Server. Operators build the rotation from the same vanilla+custom catalogue; no separate config screen. Each rotation step is audit-logged.
The Players page shows currently connected drivers (id, in-game name, optional operator nickname, ping, joined-at) plus the full connection history with join/leave counters across every session. Per-player nicknames — pencil button opens a modal to attach a real name to an in-game id; the panel renders rows as "Apodo (in-game)" everywhere from then on.
Per-row buttons: Kick (immediate, uses the live BeamMP player id), Ban (writes to the bans SQLite table with optional duration, kicks immediately, refuses re-connect via PanelBridge once the plugin gates onPlayerAuth), Whitelist (adds to the whitelist allowlist). All audit-logged.
Live state comes from the PanelBridge Lua plugin — a ~90-LOC plugin that ships in tools/beammp-plugin/PanelBridge/ and writes state.json + chat.json to disk every 5 s. The panel watches the directory with fs.watch + a safety-net poll, diffs against the in-memory map, and persists join/leave rows into a player_events table. The plugin also reads a commands.json for moderation commands the panel writes back. See docs/live-state.md.
A live chat feed on the Players page that polls chat.json every 5 s with ?since=<ts> for incremental rendering, 200-line ring buffer, autoscroll-when-pinned-to-bottom, Clear button that hides the current backlog without touching the server-side log. Fills the moderation gap where an admin had to ssh into the BeamMP host and tail ServerLog.txt by hand to read what players were saying.
Broadcast templates modal — five starter templates (welcome, restart-5m, restart-1m, warn, rules) prefill the broadcast field, then any tweaks before sending. Broadcasts render inline in the chat feed with kind:"broadcast" star-prefix styling so the chat view shows both sides of the conversation.
MOTD loop in Settings — periodic re-broadcast every N minutes for server rules, Discord links, or rotation reminders. Reuses the same /api/broadcast + PanelBridge command pipeline.
Fixed time + weekday picker in Settings, persisted in panel_settings. An in-process timer fires once per minute window. Each scheduled restart is audit-logged.
Upload BeamMP mods directly from the browser. The classifier matches BeamMP's mod model: archives with a main.lua at the root are server plugins (extracted into Resources/Server/<plugin>/), everything else is a client mod copied verbatim as .zip into Resources/Client/ without re-packing — matching BeamMP's spec (the server hands the original .zip to connecting clients, not a re-packed one).
Wrong-game uploads (AC car/track archives with data.acd / ui/ui_car.json) are rejected with a clear "this looks like an Assetto Corsa mod, use the AC panel" message. Decompression-bomb guards (50 000 entries max, 2 GB per entry, 5 GB cumulative) and zip-slip protection across all paths. Chunked upload for Cloudflare and other proxies that block large binary POSTs.
The Mods catalogue mirrors the Maps page UX: unified .car-grid of thumbnail cards, kind-based filter chips, click-to-modal with full mod details, and a per-card preview image extracted on demand from inside the .zip when the mod carries one.
Edit the live ServerConfig.toml through a visual UI — three cards: Server identity (Name / Description / Port / MaxPlayers / MaxCars / Tags), Privacy & logging (Private / AllowGuests / InformationPacket / LogChat / Debug toggles), AuthKey (write-only rotation with eye-toggle + Reveal current value button for admins, audit-logged each reveal). Backed by an embedded line-edit TOML parser that preserves comments + key order + inline whitespace — saves substitute matching lines in place rather than re-emitting from an AST.
Each PUT validates against TOML_FIELD_SCHEMA (Port 1024-65535, MaxPlayers/MaxCars 1-1000, Name ≤ 250, Description ≤ 1000, Map ≤ 100, Tags ≤ 100, UpdateReminderTime as \d+(s|min|h|d), …), atomic-writes via temp + rename(2), rotates ServerConfig.toml.bak + timestamped .<datetime>.bak backups with CFG_BACKUPS_KEEP retention, audits the changed key names (never values — secrets never land in the audit chain), and auto-restarts the BeamMP-Server when it's currently up (mirrors the AC panel because BeamMP has no SIGHUP reload).
Export / Import lives in the same page: one-click TOML download with AuthKey redacted to <REDACTED-PASTE-NEW-KEY-HERE> for backup or cross-host migration, and upload of a saved TOML through the same _validateTomlUpdates pipeline so an operator can lift a config from one host to another without copying secrets by hand.
Create, edit and delete panel users. Each user has their own profile with password change and a built-in secure password generator (uses crypto.getRandomValues). The panel refuses to delete or demote the last remaining admin and revokes a user's active sessions when an admin resets their password.
The user role is gated by eight independent toggles edited from the Users page: serverControl, serverConfig, whitelistManage, playerModeration, modUpload, discordWebhook, auditView, dbBackup. Admin always passes every check. Defaults preserve the open grants from earlier deployments (server control + mod upload on; the rest off). Editing a permission takes effect on the affected user's next request — no re-login needed. New permissions added in a later release auto-backfill into pre-existing role rows with their intended defaults so upgrades don't silently demote users. Panel-user CRUD, AuthKey rotation and the permission set itself are reserved to admins by design (cannot be exposed as toggles without enabling privilege escalation).
- Sessions: scrypt password hashing with cost params pinned in
SCRYPT_PARAMS(constant-time compare),HttpOnly; SameSite=Strictcookies with 7-day TTL plus theSecureflag when the request arrived over HTTPS, automatic 401 → logout interceptor on the client. - Forced first-login change: seeded
Admin / Admin1234!is locked into a blocking modal until the password is changed; server-side gate refuses every authenticated endpoint until the flag clears. - TOTP 2FA: opt-in per-user, RFC-6238 SHA-1 30-second window, QR provisioning via vendored
qrcode-generator. The same setup/confirm/disable flow as the AC fork. - CSRF: unsafe methods require
Origin/Refererto matchHost. WhenTRUST_PROXY=1, the panel also readsX-Forwarded-Hostso reverse-proxies that rewriteHost(Caddy defaultreverse_proxy, nginx withoutproxy_set_header Host, cloudflared withhttpHostHeader: localhost) don't trigger false-positive rejections. Combined withSameSite=Strictcookies, cross-site requests are rejected at two layers. - Headers: CSP without
unsafe-eval/unsafe-inline, Permissions-Policy, X-Frame-Options, Referrer-Policy on every response. HSTS auto-enabled when behind an HTTPS-terminating proxy. - Rate-limited login, change-password, mod uploads, server start/stop/restart, config writes, AuthKey reveals and chat broadcasts. Per-user concurrent SSE log-stream cap (6) caps file-descriptor usage. Optional
TRUST_PROXY=1to honourCF-Connecting-IP/X-Forwarded-Forso the limiter sees real client IPs through Cloudflare. ADMIN_TOKENheader (when configured) is compared in constant time viacrypto.timingSafeEqual.- Mod uploads: strict zip-slip abort, archive entry-count and aggregate-size caps, wrong-game rejection signals (AC archives caught before extraction).
- Per-user audit log of every admin action, with cursor pagination. Rows are SHA-256 hash-chained with a JSON-canonicalised payload (
chain_version = 1) so a|inside a field cannot collide with a different field assignment; legacy rows still validate viatools/verify-audit.js.
Sidebar collapses into a drawer with a hamburger toggle below 900 px wide; layouts re-flow to single columns on phones. Tested on portrait phones down to 360 px.
Full interface available in English and Spanish. The Spanish translation covers the panel chrome + every BeamMP-specific feature, including the in-game map names ("Costa Oeste, EE. UU." / "Acantilado" / "Cuadrícula, pequeño, sencillo" — the strings BeamNG.drive itself uses in the Spanish UI). The translation dictionary is checked by tools/i18n-coverage.js, which reports any key used in code but missing from a language (or vice versa).
git clone https://github.com/ayozetr/beamng-server-panel.git
cd beamng-server-panel
nvm use 20
npm install
cp .env.example .env # fill in BEAMMP_SERVER_DIR and BEAMMP_BIN
npm start # automatically pre-builds dist/ via esbuild then runs server.jsThe first time you run npm start, esbuild transpiles src/*.jsx to dist/*.js. Subsequent starts re-build (it takes ~20 ms — much cheaper than transpiling in the browser on every page load).
To rebuild manually after editing JSX without restarting the server:
npm run buildOpen http://<server-ip>:3000 in your browser.
The default
HOST=0.0.0.0listens on every network interface. Combine that with the wrong network config and the panel becomes reachable from places you didn't intend. Pick one of:
- Cloudflare Tunnel (recommended). Set
HOST=127.0.0.1andTRUST_PROXY=1in.env. The tunnel terminates TLS and forwards traffic to localhost; no firewall ports are opened. Seedocs/deployment.md.- LAN-only access. Leave
HOST=0.0.0.0but block port 3000 from the internet at the router / firewall. Do not setTRUST_PROXY=1— without a real proxy in front it lets any LAN client spoof their IP inCF-Connecting-IP.- Reverse proxy (nginx / Caddy / Traefik). Same shape as Cloudflare Tunnel:
HOST=127.0.0.1+TRUST_PROXY=1+TRUST_PROXY_FROM=<proxy IP CIDRs>in.env.The BeamMP game traffic on UDP+TCP 30814 still needs port-forwarding on your router regardless of how the panel itself is exposed — Cloudflare Tunnel doesn't proxy UDP. If the panel is publicly reachable you must change the default
Adminpassword (the first login forces it, but a port scanner can still see the login screen).
Default credentials: Admin / Admin1234!. The first login forces a password change in a blocking modal — the rest of the panel is unreachable (server-side enforced) until the password is changed. If you ever lock yourself out, run:
UPDATE panel_users SET must_change_password = 0 WHERE username = 'Admin';in panel.db and log in normally.
| Document | Description |
|---|---|
| Install the panel | Step-by-step Ubuntu install for the panel itself: prerequisites, .env, systemd, backups |
| Install BeamMP-Server — single-tree layout | Greenfield Ubuntu install for BeamMP-Server under /srv/beammp/ with a dedicated system user |
| Install BeamMP-Server — split layout | Alternative layout: $HOME/beamng_server/ + /srv/beamng/{cfg,content} via symlinks (for shared hosts) |
| Docker deployment | Containerised setup with docker compose up -d, volumes, networking, reverse-proxy front-end, troubleshooting |
| Production deployment | Systemd service, Cloudflare Tunnel, firewall, log rotation, hardened systemd unit, safe update procedure |
| Server lifecycle | How the panel manages BeamMP-Server (spawn semantics, PID adoption, TCP health-check, locking, rate limits) |
| Server config | /api/config reference (field schema, AuthKey handling, comment-preservation, backups, atomic writes, audit, auto-restart) |
| Live state (PanelBridge) | Live player state via the Lua plugin (install, wire protocol, what works with vs. without it, limitations) |
| Authentication & users | Session system, 2FA TOTP, granular role permissions, audit chain |
| Mod installer | BeamMP's two-category mod model (client zips vs server plugins), classifier signals, chunked upload |
| Database | SQLite schema and what gets stored (tables + columns + indices + migration list) |
| API reference | All server endpoints with request/response shapes |
| Troubleshooting | Common issues and how to fix them |
| Tools | Scripts under tools/ (setup wizard, smoke test, supply-chain audit, audit-log verifier, coverage reporters) |
| Tested on | Exact OS / Node / npm / SQLite versions the panel is known to run on |
| Security policy | How to report vulnerabilities and what is in / out of scope |
| Roadmap | Project status, comparison with the upstream BeamMP world, prioritised backlog |
| Changelog | Per-release summary of every notable change since 0.1.0 |
The docs/beammp/ directory is a mirror of the official BeamMP docs at https://docs.beammp.com — kept locally as a reference vendor; it's not part of this panel's documentation.
This is a single-tenant admin tool, not a multi-tenant web app. Trust assumptions:
- Admins are fully trusted. Admin role always passes every permission check. Exclusively allowed actions: managing panel users (create / delete / change role / reset password), rotating the BeamMP
AuthKey, revealing the current AuthKey in cleartext, wiping mod history, downloading the SQLite DB and reading or writing the role-permissions set itself. These are reserved by design — exposing them as toggles would let auserescalate to admin. - The
userrole is configurable per-permission, not "read-only". An admin can grant a user the ability to start/stop the BeamMP server, editServerConfig.toml(everything except the AuthKey), apply map changes, kick/ban players, manage the whitelist, upload mods, edit the Discord webhook, view the audit log and download DB backups — one toggle each. Trust accordingly: grantingserverConfiglets the user reshape the running server; grantingmodUploadlets the user push Lua server plugins that run inside the BeamMP-Server process (there is no sandbox between plugins and the host). - Do not expose the panel to the public internet without HTTPS and credentialled access. Do not give panel accounts to anyone you would not trust with the equivalent shell-level capability. Always set
TRUST_PROXY=1when behind Cloudflare/Tunnel/reverse-proxy so rate limits, audit logs and CSRF origin matching see real client IPs and hostnames. - The audit log is hash-chained but deletable. Each row stores a SHA-256 of the previous row's hash, so silent edits are detectable with
node tools/verify-audit.jsagainst an external backup. Anyone with shell access topanel.dbcan still wipe rows entirely — keep periodic backups via/api/admin/backupif you need provable history. Every permission-gated action is recorded with the actor's username, so a per-user trail survives even when a permission set is broad.
What the panel does defend against:
- Anonymous attackers (CSRF, brute-force on login, path traversal, decompression bombs, malformed archives, wrong-game upload signals).
- Privilege escalation from a compromised user account — even with every toggle on, the user role cannot create/delete panel users, change another user's role or password, reveal the AuthKey in cleartext, wipe mod history, or edit the role-permissions set.
- Stolen old SW caches (network-first navigation strategy ensures security fixes propagate without manual cache bumps).
What the panel does not defend against:
- Malicious Lua server plugins (no sandboxing — only grant
modUploadto people you trust to vet upload sources). - A compromised admin account (full control by design).
- An over-permissioned user account — e.g. a user granted
serverConfigcan rewrite the BeamMP-Server port, MaxPlayers, Private flag and Tags; a user grantedmodUploadcan ship arbitrary Lua that runs server-side. Defaults exist; the configured permission set is the operator's responsibility. - Filesystem access via the host shell or other services.
- Frontend: React 18 (production CDN) + esbuild build step transpiling JSX → plain JS at startup
- Backend: Node.js native HTTP (no Express)
- Database: SQLite via
better-sqlite3 - Mod extraction:
node-stream-zip,node-unrar-js,node-7z - Live state bridge: BeamMP
Resources/Server/PanelBridge/Lua plugin (read/write JSON on a 5 s tick) - Real-time logs: Server-Sent Events (SSE)
The combinations below are the ones the maintainer actually runs day-to-day; the panel is reasonably portable but these are the ones that are known to work. Full version matrix and bundled-dependency lockfile excerpt in docs/tested-on.md.
| Role | OS | Kernel | Node.js | npm | SQLite (system) | BeamMP-Server |
|---|---|---|---|---|---|---|
| Production | Ubuntu 24.04.4 LTS (Noble Numbat) | 6.8.0-117-generic | v20.20.2 | 11.13.0 | 3.45.1 | v3.9.2 |
| Development | CachyOS (Arch-based, rolling) | 7.0.9-1-cachyos | v20.20.2 | 11.14.1 | 3.53.0 | (remote prod) |
Bundled npm packages on both hosts (production identical, dev installs the same lockfile):
7zip-bin@5.2.0 · @resvg/resvg-js@2.6.2 · better-sqlite3@12.10.0 · dotenv@16.4.5 · node-7z@3.0.0 · node-stream-zip@1.15.0 · node-unrar-js@2.0.2 · esbuild@0.28.0
Other Linux distributions on Node 20 LTS should work without changes — the panel only depends on a POSIX filesystem, a recent SQLite, and the libraries above. Windows/macOS will likely run the panel itself but the BeamMP-Server Linux binary won't run there; for development on those platforms point the panel at a remote BeamMP host. Please open an issue if you hit something distro-specific.
Source-available. The full text — which is what binds you, not this summary — is in LICENSE. Read it before deploying.
Short version (informative, not legally operative — only the LICENSE file is):
-
Free to download and run anywhere, for anything lawful. Personal hobby use, private servers, friend groups, amateur clubs, educational and research use are all fine. So is operating the panel for public servers, including servers advertised on the BeamMP public listing, including commercial use (paid leagues, sponsorships, ads, donations, for-profit organizations). You don't need a separate agreement to charge for participation or run a paid event on top of a server the panel manages.
-
No redistribution. You may not republish the source code, fork it to a public repository as your own, upload it to a package registry, bundle it into another product, host it as a service for third parties to download, or otherwise hand copies to other people. If someone wants to use the panel, point them at the official repository — they can clone it themselves under their own acceptance of the LICENSE.
-
Attribution Marks are irremovable. The "Developed by ayozetr" credit in the sidebar, the project name "BeamNG Server Panel", the link to the official repository, the copyright notice, and every other identifier referring to the author or the project must stay intact in every copy you operate. This applies even to commercial deployments. A re-skin, white-label, or theme that hides the marks is a breach and terminates your rights automatically.
-
Local modifications are allowed. Patch bugs, translate, add integrations, restyle (without touching the Attribution Marks) — keep them for yourself. You may submit them upstream via pull request; you may not publish them as your own fork.
-
No affiliation with anyone. Not BeamNG GmbH, not BeamMP, not Valve, not any car / track manufacturer whose brand appears in bundled assets. The LICENSE has a long disclaimer section spelling this out.
-
No warranty, no liability. "AS IS". Each tagged release is published with good-faith effort against known vulnerabilities at the time of publication, but the author makes no ongoing warranty and accepts no liability for damages, data loss, or downtime. See LICENSE sections 7 and 8.
For licensing questions, redistribution requests, or anything else: ayozetr@proton.me. For security disclosure see SECURITY.md.