Skip to content

Releases: ayozetr/beamng-server-panel

v0.7.0 — in-game map voting + IP-keyed guest identity + upload/parser/config fixes

02 Jun 21:39

Choose a tag to compare

Follow-up release after v0.5.0, jumping straight to 0.7 at the operator's
request (no 0.6 intermediate). Headlined by in-game chat map voting
end-to-end (PanelBridge v4) and a guest-identity overhaul that keys
returning guests by IP so the same person stops spawning a fresh players
row on every relaunch — with an admin-only manual merge for the residual
duplicates. The rest is a batch of upload / parser / UI correctness fixes
surfaced by real mods on the live server: BeamNG character packs are no
longer badged as cars, vehicle mods carrying client-side Lua are no longer
mistagged as server plugins, and the malformed-info.json tolerance now
also recovers dropped commas between array elements. Rounded out by the
long-broken Server config page finally being editable again, a
warning-free i18n build, the sidebar credit centred, and the voting
feature documented end-to-end.

Added

  • In-game map voting end-to-end. Players run /vote map <name>,
    /vote yes / /vote no, /vote status, /vote cancel in BeamMP's
    chat. PanelBridge v4 handles the state machine (one active vote at a
    time, proposer auto-counts as yes, tally re-announced every 15 s)
    and on a passing quorum writes vote_result.json. The panel reads
    it, broadcasts a countdown via commands.json, then runs the same
    _doActivateMap(..., {autoRestart:true}) primitive the Maps page
    uses for manual activation. Plugin also broadcasts a periodic
    "tip: start a vote with /vote map" every suggestIntervalSec when
    no vote is in flight, listing the maps the operator allowed.
    Configurable from the Maps page (Votación de mapa en juego card):
    enabled toggle, duration, quorum %, min votes, countdown, suggestion
    interval, optional allowed-maps whitelist. Dashboard shows a live
    tally + countdown when a vote is open; admins get a Cancel button.
    New endpoints GET /api/maps/vote, PUT /api/maps/vote/config
    (admin), POST /api/maps/vote/cancel (admin). Voter identity uses
    the same stable-id scheme as players (bmp:<uid> / guest:<name> /
    …) so a guest can't double-vote and a player reconnecting on a
    different slot still maps to the same ballot. Full wire protocol +
    defaults documented in docs/live-state.md and docs/api.md.
  • /vote map with no name now lists the available maps. Running
    /vote map with no argument (or a name not on the allowed list) used
    to reply with a bare usage/error line, leaving the player to guess
    the exact name. Both replies now append "Available maps: "
    from a single allowedMapNames() helper (shared with the periodic
    tease); when the operator hasn't restricted the list, the line says
    any level is allowed instead of showing an empty list.
  • Guest players keyed by IP, with an admin-only manual merge. With
    BeamMP forum registration disabled everyone connects as a guest, and
    _stableIdFor was keying guests on BeamMP's guestXXXXXXX handle —
    which re-rolls on every game relaunch, so one person spawned a fresh
    players row each session (16 live rows were really 4 people / 4 IPs).
    identifiers.ip now wins right after the forum UID, so a returning
    guest collapses back onto one row (the bmp: branch still takes over
    automatically if registration ever returns). For the residual drift
    (two people behind one NAT, or an ISP rotating a dynamic IP) a new
    POST /api/players/merge (admin-only, irreversible, audit-logged)
    re-points player_events / player_nicknames / bans onto a chosen
    survivor, widens its first_seen/last_seen, drops the source rows
    and republishes bans — all in one transaction. The Players history
    table gains an admin-only "Fusionar" toggle with per-row checkboxes,
    a keep-which selector and a ConfirmModal.

Fixed

  • Vehicle mods with client-side Lua no longer mistagged as server
    plugins.
    The upload classifier flagged any main.lua at any
    depth as a BeamMP server plugin, which sent vehicle mods that ship
    a client-side script under vehicles/<X>/lua/main.lua
    (turbo / traction-control / scripted gimmicks — the Nissan 180
    RPS13 mod was the trigger) into the server-plugin extraction
    branch. That branch enforces a "single root folder" rule, so the
    upload was rejected with Server plugin archive must contain exactly one root folder. Found: art, ui, vehicles. Classifier now
    ignores main.lua files that live under the vehicles/ or
    levels/ subtrees, so client-side car / track scripts no longer
    derail the upload — the archive falls into the client-mod branch
    and is copied verbatim to Resources/Client/<name>.zip as
    intended.
  • BeamNG character packs are classified as their own kind, not
    "Coche".
    A character pack ships vehicles/<x>/info.json because the
    engine models the walking avatar as a vehicle, so the Mods page
    badged it "Coche" and the Cars catalogue would have listed it as a
    drivable car. _classifyClientMod now reads each
    vehicles/<x>/info.json's Type: "Characters" → new 'character'
    kind, anything else stays 'car' (stops at the first real car, so
    multi-car packs cost one read). enumerateCustomCars skips
    Type=="Characters" the same way, so the Mods badge and Cars
    catalogue stay aligned. Frontend gains a "Personajes"/"Characters"
    filter chip, badge label and an IconUsers palette.
  • The Server config page is editable again. Changing any field
    (MaxCars, Tags, Name…) reverted instantly and the Save bar never
    appeared, so saving was unreachable — which is why the live
    ServerConfig.toml still held all-default values. In
    useServerConfig the initial-load effect depended on reload, a
    useCallback over [toast, t] where t is a fresh
    window.AppI18n.t.bind() every render, so reload's identity
    changed each render, re-firing the effect → reload()setDirty({})
    and wiping the operator's edit (plus an unbounded /api/config
    refetch loop). The effect now depends only on canEdit, so config
    loads once (and on permission flips) and dirty state survives until
    Save.
  • Malformed-info.json recovery now handles dropped commas between
    array elements
    , not just between property keys. 2K-Happogahara
    ships an info.json with two missing commas — one between
    "features" and "suitablefor" (the original Sadamine fix) and a
    second between two adjacent {} objects inside the spawnPoints
    array — so it parsed half the file then died at position 509, meta
    came back null and the catalogue card stayed on the SVG. The regex
    now matches any value-end token (string / } / ] / true /
    false / null / number) followed by whitespace+newline and a
    value-start token, so Happogahara parses to its 3 spawn points and
    picks up its quickrace/TH.jpg thumbnail. Sadamine still parses
    identically and Nordschleife (trailing-comma-only) is unchanged;
    parser-level fix, no frontend changes.
  • Removed 6 duplicate i18n keys (profile.copied, config.lang,
    config.lang_sub, pl.col.last, common.delete, common.refresh)
    that were each defined twice in en + es, triggering 12 esbuild
    duplicate-key warnings and silently dropping the earlier definition.
    Build is now warning-free; the only user-visible change is es
    common.refresh, which now resolves to the intended "Actualizar"
    instead of the stray "Refrescar".
  • Sidebar credit centred. The "Desarrollado por ayozetr" link in
    the sidebar footer is now horizontally centred (justifyContent: center on the flex anchor).

Docs

  • Voting documented end-to-end. docs/live-state.md gains the
    vote_config.json / vote_state.json / vote_result.json wire
    formats + a player-side chat-command table; docs/api.md documents
    the three /api/maps/vote* endpoints; the PanelBridge README bumps
    to v4 with an in-game voting commands table for operators.
  • ROADMAP Phase 1 ticked. The AC purge was long done but never
    checked off — verified each named symbol/file/regex returns zero
    hits in the tree, marked the section "(done)" and added
    forward-pointers (apiCars / SteamIDs / LiveMapCard) to the phases
    where their BeamMP replacements landed. Phase 8 also picked up the
    voting + classifier bullets.

v0.5.0 — content quality of life + ops + perms + audit-driven hardening

28 May 01:27

Choose a tag to compare

Big batch release rolling up the entire ops-quality-of-life pass driven by
the external audit + several follow-up feature drops the operator asked for
during a single working session: granular permissions (mapActivate so a
non-admin user can change maps without inheriting full ServerConfig.toml
rights), one-click BeamMP-Server binary update from the dashboard banner,
a Cars catalogue mirroring Mapas, custom-map metadata + thumbnails read
from info.json (including info.json files BeamMP-Server's own parser
tolerates but JSON.parse rejects), CPU temperature on the Dashboard,
four new KPI tiles (mods-on-disk excluding Kunos vanilla, joins / laps /
total drivers), strictly admin-only mod deletion + ConfirmModal, the
Mods catalogue collapsed by default, a stable per-player id that doesn't
collapse two guests behind the same NAT into one entry, and the active-
map tile in the Dashboard now shows modder title + screenshot for custom
maps too. Plus the security / health / audit-export hardening from the
external audit and a fast unit-test layer in front of the existing smoke
run. Two coordinated panels — BeamMP + Assetto Corsa siblings — got the
shared Dashboard tiles (mods-on-disk, joins / laps / drivers, CPU temp)
identically, with the AC panel filtering Kunos vanilla out of the disk
total.

Changed

  • Mod deletion is now strictly admin-only. DELETE /api/mods/<loc>/<name>
    switched from the modUpload permission gate to checkAdminAuth.
    modUpload still controls who can push new archives in, but the
    destructive side moves up to admin — the wrong delete on a busy
    server (operator picks the wrong row, modder's hash mismatches
    mid-rotation, …) kicks every connected player and forces a manual
    reupload, and the panel's risk model treats that as a single-actor
    operation. Frontend mirrors the change: the Mods catalogue's delete
    button only renders when the viewer is admin (was: when they had
    upload rights), and the existing ConfirmModal continues to gate
    the click. Same approach as the assetto-server-panel sibling, where
    car / track delete has always required admin.
  • Mod classifier no longer tags vehicle-extension mods as "Coche".
    The previous heuristic flagged any zip containing vehicles/<X>/<Y>.jbeam
    as a car — which caught full-vehicle mods but also license-plate packs,
    body kits, bumpers and trim that ship plate / tuning .jbeam files
    for stock vehicles without an info.json of their own. EuroPlates_JorgePinto.zip
    was the prompting case: Mods page said "Coche", Cars catalogue
    (which requires vehicles/<X>/info.json) said "no car" — inconsistent.
    Strict criterion now: kind=car iff at least one vehicles/<X>/info.json
    exists. Vehicle-extension mods classify as other.

Added

  • Collapsible "Installed mods" card on the Mods page. Catalogue
    is informational; the uploader below is the action surface. Card
    starts collapsed; click the header to expand. Total-mods badge stays
    visible even when collapsed. Preference persisted in
    localStorage['bp-mods-installed-open']. Same idiom the Dashboard
    activity card already uses (chevron indicator, keyboard handler,
    aria-expanded).
  • Stable per-player id (no more "everyone is UUID 0"). PanelBridge
    consumer was using BeamMP-Server's per-session slot number (0, 1,
    2…) as the canonical player_id, collapsing every guest that ever
    held slot 0 into a single DB row. New _stableIdFor(p) returns
    bmp:<uid> for registered BeamMP users (forum UID — durable across
    sessions / IPs / name changes), guest:<name> for guests (BeamMP
    assigns each guest a session-unique handle like "guest8099544" so
    two guests behind the same NAT no longer collapse into one panel
    entry — the operator pointed this out as the genuine common case
    during BeamMP-auth maintenance windows when AllowGuests has to stay
    true), and named:<name> / slot:<n> as deeper fallbacks.
    _connectedPlayers is rekeyed by stableId; each entry carries
    slotId so MP.DropPlayer still works for kicks. Migration #13
    purges legacy slot-numeric rows from players / player_events / bans.
    apiPlayerKick + apiPlayerBan resolve the stableId → slotId via the
    live map. _publishBans writes each ban under both keys (prefixed +
    raw) so the in-tree PanelBridge keeps matching without a reload.
  • Player nickname rendered in Dashboard players-online list.
    Matches the Players page's "Nickname (in-game)" shape so the
    admin-set nickname is visible from both views at a glance. Falls
    back to the in-game name when no nickname is set. Single inline
    ternary; no new fetch.
  • Active-map Dashboard tile now shows modder title + thumbnail for
    custom maps.
    Vanilla maps were already rendering bundled
    screenshots — but the moment the operator switched to a Nürburgring
    mod the tile fell back to the raw directory name + the technical
    /levels/ks_nord/info.json path, with no image. apiDashboardExtra
    now resolves custom maps via enumerateCustomMaps and surfaces
    { source, levelName, hasPreview }. Frontend builds the preview
    URL from /api/maps/preview/<source>/<levelName> — same endpoint
    the Maps page card uses, so the active-map thumbnail and the
    catalogue thumbnail stay in sync.
  • mapActivate granular permission. The User role can now be
    granted access to the Maps + Cars pages and to changing the active
    map without inheriting full ServerConfig.toml editing rights. Closes
    the gap that "let a user pick the next map" required handing over
    the whole config editor (port, max players, AuthKey rotation
    scope, etc). ROLE_PERMISSIONS grows a mapActivate entry which
    is checked by apiMaps, apiMapsActivate, apiMapPreview,
    apiCars and apiCarPreview instead of serverConfig.
    Map rotation (apiMapsRotation + apiMapsRotationUpdate) keeps
    the serverConfig gate — the rotation timer rewrites the live
    config and restarts the server, so it's a config-level operation
    not an operator-level one. New _applyImplications helper makes
    serverConfig automatically grant mapActivate at runtime so an
    existing admin or any user already trusted with serverConfig keeps
    Maps + Cars access without the operator having to tick a second
    checkbox. UI: a new row in Users > Permissions explains the
    difference; the sidebar entries for Maps + Cars now hide via
    requires:'mapActivate'; the Map rotation card on the Maps page
    is only rendered when the viewer has full serverConfig. New i18n
    keys perm.mapActivate + perm.mapActivate_hint (en + es).
  • CPU temperature badge on the Dashboard's "Uso CPU" tile. Small
    number in the top-right corner of the existing CPU card, no layout
    changes. Sourced from /sys/class/thermal/thermal_zone*/temp — pure
    kernel surface, no extra package, no sudo, plug-and-play on every
    modern Linux host. _getCpuTempC walks each zone, prefers ones whose
    type matches a CPU sensor (x86_pkg_temp, coretemp,
    cpu[-_]thermal, core_N, package_N) and reports the highest
    reading among the preferred set so a multi-package box shows the
    hottest die. On hosts where the thermal tree doesn't exist (some VPS,
    LXC containers, FreeBSD, Windows) or every reading is out of plausible
    range, the helper returns null and the badge simply doesn't render —
    the tile looks exactly like before.
  • One-click BeamMP-Server binary update from the dashboard banner.
    POST /api/server/update-binary (gated on serverControl) downloads
    the asset matching the host's distro/arch from the latest GitHub
    release, makes a <bin>.bak of the running binary, atomically
    replaces it (after stopping the service), restarts, verifies
    --version on the new binary, and rolls back to the .bak if the
    new one is corrupted. Process-wide flag rejects concurrent calls with
    409. _detectLinuxAsset maps /etc/os-release + process.arch to
    BeamMP's BeamMP-Server.<id>.<version_id>.<arch> shape (Debian
    12/13, Ubuntu 22.04/24.04, x86_64 + arm64); other distros refuse
    with a 400 rather than ship a libc-mismatched binary. The Dashboard
    update banner grows an "Actualizar" button next to "Ver release"
    with a ConfirmModal that spells out the disruption.
  • Cars catalogue page. New Coches entry in the sidebar's
    Contenido group, mirroring the Mapas page UX but read-only — BeamMP
    auto-streams car mods to every connecting client, so there is no
    activate flow. enumerateCustomCars walks Resources/Client/*.zip
    pairing vehicles/<X>/info.json with vehicles/<X>/default.(png|jpg),
    emits one card per vehicle with brand / country / year / type
    badges, and surfaces authors + description in the modal. Vanilla
    BeamNG.drive cars are deliberately NOT enumerated — they ship with
    each player's local game install — and the page leads with an
    explanatory card making that explicit.
  • Custom maps now read their levels/<X>/info.json so the
    catalogue shows the modder's title, description, authors, country
    (last segment of the levels.common.country.* translation key),
    biome and features rather than the bare directory name. Per-level
    pairing means a track pack that ships multiple levels in one .zip
    gets one card with the correct thumbnail per level. _parseLooseJson
    tolerates the JSONC dialect BeamNG uses (trailing commas, occasional
    comments) so files like the Nordschleife mod's info.json don't
    reject under strict JSON.parse.
  • Per-level preview extractor. /api/maps/preview/<source>/<level>
    extracts the targeted levels/<level>/preview.(png|jpg|jpeg) from
    inside the zip — separate from the mods page's first-preview-found
    endpoint because a multi-level archive needs one thumbnail per
    level, not one for the whole archive.
  • Ban enforcement at join (PanelBridge v3). The plugin gains an
    onPlayerAuth handler that reads Resources/Server/PanelBridge/bans.json
    on every auth attempt and refuses the connectio...
Read more

v0.2.0 — first feature release

21 May 23:38

Choose a tag to compare

First feature release of the BeamMP fork. v0.1.x got the panel functionally equivalent to the AC original on the BeamMP side; v0.2.0 is the wave of operator-facing convenience features built on top of that base — visual catalogues for maps and mods, automated lifecycle (map rotation, restart scheduler, MOTD loop), live moderation surface (chat viewer, broadcast templates), and dashboard polish (active-map screenshot card, BeamMP-Server update notifier, BeamMP-specific KPI widgets). One CSRF fix and a second documented deploy layout round it out.

Added

  • Visual map catalogue at /maps — grid of cards with the official BeamNG.drive screenshots (from documentation.beamng.com/official_content/levels/images/, normalised to 800×450 q82), click-to-modal, activate-with-restart-prompt. The Map field in Server Config is now a read-only display + "Choose in Maps" button.
  • Visual mods catalogue at /mods — unified .car-grid of thumbnail cards, kind-based filter chips, click-to-modal, per-card preview image extracted on demand from inside the .zip.
  • Live chat viewer on the Players page + PanelBridge v2 Lua plugin. Plugin records every onChatMessage into a 200-line ring-buffered chat.json; panel polls every 5 s with ?since=<ts> for incremental refresh.
  • Broadcast button + templates modal in Players + MOTD loop in Settings. Five starter templates and an optional re-broadcast every N minutes for server rules / Discord links. Reuses /api/broadcast + PanelBridge command pipeline.
  • Automatic map rotation in Maps — persisted list + interval, in-process timer activates the next map every N minutes and restarts BeamMP-Server.
  • Cron-simple restart scheduler in Settings — fixed time + weekday picker, persisted in panel_settings. Audit-logged each scheduled restart.
  • BeamMP-Server update notifier banner on the Dashboard — queries GitHub releases once per 6 h, compares against local BeamMP-Server --version, shows a per-version-dismissable banner when a newer server is out.
  • BeamMP-specific KPI widgets on the Dashboard — second row with active map (rendered as a screenshot card background under a darkening gradient overlay), joins in last 24 h, mods on disk, binary version. Polled at /api/dashboard/extra every 30 s.
  • ServerConfig.toml export / import in Settings — one-click download (AuthKey redacted) and upload through the existing _validateTomlUpdates pipeline. Cross-host migration + disaster-recovery workflow with the same per-key validation + audit logging.
  • Quick map switcher dropdown in the topbar — current map name + popover with vanilla and custom maps, click-to-activate with confirm-restart. Gated on serverConfig permission.

Fixed

  • checkOrigin honours X-Forwarded-Host when sitting behind a trusted reverse proxy (TRUST_PROXY=1). The original implementation compared against req.headers.host alone, which rejected every cookie-authenticated POST/PUT/DELETE/PATCH when the upstream proxy rewrote Host (Caddy default reverse_proxy, nginx without proxy_set_header Host, cloudflared with httpHostHeader: localhost). Every same-origin rejection now logs peer + headers.

Docs

  • docs/install-beammp-split-layout.md + docs/install-panel.md — alternative deploy layout validated end-to-end by a real prod deploy on a shared host that already runs assetto-server-panel. The original install-ubuntu.md (dedicated beammp user under /srv/beammp/) stays as the greenfield default.
  • .env.example TRUST_PROXY block expanded to describe the new CSRF / X-Forwarded-Host gate.
  • maps.attribution i18n string (EN + ES) rewritten now that thumbnails are real screenshots.

Polish

  • Service worker CACHE_NAME bumped bp-panel-v4bp-panel-v5 so PWA installs drop the v4 bundle on next visit.
  • Broadcast button relocated from the player-list card header into the live-chat card header.

Full changelog: CHANGELOG.md · Diff: v0.1.1...v0.2.0

v0.1.1 — port AC v1.6.0→v1.7.1 game-neutral fixes

20 May 01:40

Choose a tag to compare

Game-neutral fixes ported from upstream assetto-server-panel v1.6.0→v1.7.1. 13 of the 15 upstream commits in that range were AC saved-session-presets work that doesn't apply to BeamMP and were skipped (see CHANGELOG.md for the explicit list with reasoning).

Fixed

  • dist/ auto-rebuild when node server.js is invoked directly — cherry-picked from assetto-dashboard@c18f40d. The package.json prestart hook that regenerates dist/ from src/ only fires under npm start. The systemd unit in docs/install-ubuntu.md runs node server.js directly, so git pull && systemctl restart beamng-panel was serving a stale UI bundle until someone ran node build.js by hand. Adds an ensureBuildFresh() IIFE at the top of server.js that walks src/, takes the max mtime, compares against dist/app.js's mtime, and runs build.js via spawnSync only when stale or missing. Warm restarts pay one stat per source file and zero subprocess. Build failures log a warning but don't block the boot.

  • 7za binary +x bit self-heal — cherry-picked from assetto-dashboard@c1d9c9f. npm install --ignore-scripts, tarball restores and node_modules copies between hosts all silently drop the executable bit on node_modules/7zip-bin/linux/x64/7za, which made .7z server-plugin uploads fail with HTTP 500 from a downstream spawn EACCES. Adds an idempotent fs.chmodSync(sevenBin.path7za, 0o755) right after the require('7zip-bin') so every panel boot self-heals the bit.

Skipped from upstream

13 commits in the AC v1.6.0..v1.7.1 window are part of the saved-session-presets feature (the session_presets table, the Plantillas/Presets sidebar entry, the inline builder modal, the presetManage permission, the export/import wire format, and four CSS / hook-count / icon-padding fixes against that same modal stack). Session+Preset doesn't map to BeamMP's free-roam multiplayer model, so the whole feature was left behind during the Phase 0/1 AC purge and stays out of scope for this fork.

Polish

  • Service worker CACHE_NAME bumped bp-panel-v3bp-panel-v4.

v0.1.0 — first proper release of the BeamMP fork

20 May 01:40

Choose a tag to compare

First proper release of the BeamMP fork. Closes the seven-phase migration from assetto-server-panel v1.6.0 — see ROADMAP.md for the per-phase audit and CHANGELOG.md for the full release notes.

What shipped

  • Server lifecycle — Start / Stop / Restart against a real BeamMP-Server binary with detach + log-FD survive-restart trick + adopt-by-pgrep + TCP-port health-check. Audit + rate-limit + global mutex. Auto-restart on config save.
  • ServerConfig.toml editor — Line-edit parser that preserves comments, key order, inline whitespace. 17-field validated PUT with atomic write + timestamped backups. AuthKey redacted in GET, write-only in PUT, never logged in audit.
  • Mods catalogue — Listing of Resources/Client/*.zip + Resources/Server/<plugin>/ folders with per-mod size + file count + hasMainLua flag + delete-with-confirm. Upload classifier matches BeamMP's spec (client mods stay as .zip verbatim, server plugins extract into a folder).
  • Live players + moderation — Optional PanelBridge Lua plugin streams the player list into the panel via filesystem; the panel writes kick/broadcast commands back. Bans persist in SQLite. Join/leave history.
  • Carry-over from the AC fork — Multi-user accounts with role-based granular permissions, TOTP 2FA, audit-log hash chain with retention sweeper + boundary rows, scrypt + constant-time compare, HttpOnly+SameSite+Secure cookies, CSRF Origin check, CSP without unsafe-eval/inline, rate-limited login + change-password + server-control + mod-upload + per-user SSE cap, forced password change for seeded admin, refusal to delete the last admin, dark/light themes, EN/ES UI, Docker-ready.

Docs

Known follow-ups (not in v0.1.0)

  • PanelBridge v2 plugin: enforce bans at onPlayerAuth, push chat events back to the panel's Logs page, optional Discord webhook per join/leave/ban.
  • Surface live player count in the topbar widget (the /api/metrics players field still reads 0).
  • "Restore from backup" UI for ServerConfig.toml (the .bak files exist on disk; just needs a list + restore endpoint pair).
  • Retention sweeper for player_events.

Status

The fork was end-to-end smoke-tested against fake fixtures but has NOT been deployed against a real BeamMP-Server in production yet — the 0.1.0 number is intentional. v1.0 would follow after at least one production deploy + one external user exercising the moderation surfaces.