A GameAP panel plugin that adds a Plugins tab to Source engine (Source 1) servers — Counter-Strike: Source, Garry's Mod, Team Fortress 2, Left 4 Dead / 2, Day of Defeat: Source, Half-Life 2: Deathmatch, Black Mesa, Synergy and legacy CS:GO — for managing Metamod:Source and SourceMod.
List plugins with live statuses, enable/disable them persistently, hot load/unload/reload without a restart, edit per-plugin comments, group plugins, upload new ones, and edit their configs. Plus:
- one-click installs of Metamod:Source and SourceMod (the current AlliedModders build, downloaded and unpacked on the node) with an update button when a newer build ships;
- a loader repair: writes
addons/metamod.vdfnaming the Metamod binary actually on disk, and re-checks every Source server on a 6-hour schedule — a Steam validate or a hand-editedgameinfo.txtotherwise leaves Metamod sitting on disk with nothing starting it; - both Metamod plugin mechanisms in one list: the
addons/metamod/*.vdfaliases and themetaplugins.inilines, each with the same on/off switch; - a catalog of well-known projects installable straight from their GitHub releases, with update badges on installed rows (versions checked nightly and cached);
- archive installs of
.zipand.tar.gz— the layout (addons/-rooted,plugins/-rooted, loose.smx) is detected and unpacked to the right place; - automatic safety snapshots before every platform install, catalog install, archive install and restore — every overwrite is reversible;
- a SourceMod admins editor for
admins_simple.inithat preserves the file's comments, plus a raw editor foradmins.cfg; - the SourceMod logs with filtering and following, switchable between the error log and the general log;
- snapshots of the whole SourceMod setup (tarballs on the server, newest 5 kept) with one-click restore — also the transfer format for copying a setup between servers;
- an audit history of panel actions and a restart banner whenever changes are waiting for one;
- a Doctor dialog running every health check in one pass.
This is a Source 1 port of plugin-cs2-addons, which is itself based on gameap/plugin-goldsrc-addons by the GameAP project — the equivalent panel plugin for GoldSource (CS 1.6) servers managing Metamod and AMX Mod X. The architecture (Rust/WASM backend on the gameap-plugin-sdk + embedded Vue 3 frontend), the route design, the UI and most of the code structure come from them. All credit for the concept and design goes to the GameAP authors.
- Get
source-addons.wasmfrom the latest release. (Unreleased builds are also on every Actions run as thesource-addons-wasmartifact, but those expire and need a signed-in GitHub account.) Or build it yourself — see Build. - In your GameAP panel: Administration → Plugins → upload the
.wasm(or copy it into the panel's plugins directory by hand). - Restart the panel.
- Open any Source 1 server — a Plugins tab appears next to the server's other tabs (admins only).
Panel: a GameAP installation recent enough to support WASM panel plugins
AND the gameap-scheduler host module (current
gameap/gameap main). Plugin HTTP must allow
https — release lookups query api.github.com, sm.alliedmods.net and
mms.alliedmods.net.
Node: platform installs and snapshots run curl/wget and tar on a
linux node. Both AlliedModders drops are plain .tar.gz, so no unzip is
needed. A stock Debian/Ubuntu box has everything.
Game server (for full functionality; the tab degrades gracefully without):
- A Source 1 server created in GameAP with one of the stock games (see Which games).
- Metamod:Source 1.12 in
<mod>/addons/metamod, loaded by<mod>/addons/metamod.vdf. - SourceMod 1.12 in
<mod>/addons/sourcemod. - An RCON password configured on the server in GameAP. Without it the tab still works — you just lose live data (versions, Running/Paused states, hot load/unload); everything file-based keeps working and a hint line explains what's unavailable.
Unlike its CS2 sibling this plugin registers no RCON protocol: Source 1 speaks the classic Source RCON that the panel's built-in client already implements correctly.
The plugin runs unchanged on 4.5. Three things there are worth knowing, and one of them is a limit you can actually hit.
Permissions. 4.5 gates the privileged host libraries behind grants a plugin declares in its manifest, and this one declares three:
| Grant | For |
|---|---|
node_commands |
gameap-nodecmd.execute_command - the node-side unpack and platform installs |
files |
the gameap-nodefs writes (mk_dir, move, remove, upload). It covers files_read, so the reads are not listed separately - the panel treats a broader grant as satisfying a narrower one and drops the subsumed entry |
manage_servers |
gameap-servercontrol.restart_server |
Nothing else needs one. gameap-nodes.get_node and gameap-servers'
find_servers/get_server are read-only and ungated - it is the writes on
those modules that need manage_nodes and manage_servers, and this plugin
makes none. gameap-http, gameap-storage, gameap-scheduler, gameap-games
and gameap-log are not gated at all.
The list is not a judgement call: the panel derives what a plugin uses from
its wasm import section, matched against its own policy table, and shows
anything used-but-undeclared as a missing grant. REQUIRED_PERMISSIONS in
src/lib.rs is checked against the panel's twelve known names by a unit test,
because ParsePluginPermissions silently drops a name it does not
recognise - a typo there would not fail an install, it would grant less than
intended and surface much later as a denied call.
PLUGINS_PERMISSIONS_ENFORCE still defaults to false in 4.5, so nothing is
enforced yet; a later release turns it on.
Panel variable names changed during 4.5. Every plugin setting was renamed
PLUGIN_*→PLUGINS_*inv4.5.0-rc.1(PR #85). The old spelling keeps working for one release - the panel applies it and logs a deprecation warning naming the replacement, and the new name wins if both are set - so this is worth getting ahead of rather than racing. This README uses the rc.1 names throughout. Onv4.5.0-beta.1drop theS:PLUGIN_NODEFS_MAX_INLINE. On 4.4.x none of these exist at all, because the limits they control arrived with 4.5.
A 32 MiB ceiling on single file transfers. PLUGINS_NODEFS_MAX_INLINE
caps what one gameap-nodefs download or upload may carry, and there was no
equivalent before. It binds two paths here:
- Archive installs. The plugin's own
MAX_ARCHIVE_BYTESis 33,554,432 bytes - the panel's default32Mto the byte, since its size parser reads every suffix as binary. Both refuse at 33,554,433. The plugin stats the file first, so its own message is what you see rather than the panel's generic file too large. Note the margin is exactly zero: loweringPLUGINS_NODEFS_MAX_INLINEputs the panel's limit under this gate, and the refusal then comes from the panel with less to say. Both constants derive from onePANEL_MAX_INLINE_BYTESso they cannot drift apart. - A single file inside an archive. Extraction bounds the total
uncompressed size at twice the cap and bounds no individual member, so an
oversized file can arrive inside a perfectly acceptable archive. Since entries
are uploaded one at a time, the panel would refuse that one partway through
and leave a half-installed plugin. Every entry is therefore checked before
anything is written, and the whole archive refused with
ENTRY_TOO_LARGE.
Logs over 32 MiB are the one real regression, and it is not fixable here. The log tail is read by downloading the whole file and keeping the last 256 KB. That was fine while the daemon had no ranged reads. In 4.5 a whole-file download past the inline limit is refused outright rather than truncated, so a server whose newest SourceMod log has grown past 32 MiB loses the logs view, and the doctor route with it - both with the panel's file too large error.
4.5 shipped the fix in the same release: offset and length on the nodefs
DownloadRequest, which is exactly the windowed read this wants. The plugin
cannot reach it. Those fields exist in the panel's own proto, while the
gameap-proto commit this crate pins - still the tip of that repo's main -
declares DownloadRequest as node_id + path alone. When the SDK catches up
this becomes a windowed read of the tail and the ceiling goes away; until then,
rotate the logs or raise PLUGINS_NODEFS_MAX_INLINE.
Rate limits are new and on by default - nodefs 50/s with a burst of 200, nodecmd and servercontrol 5/s burst 20, http 20/s burst 50. The install and repair flows do bursts of nodefs calls, and this is the one limit here that has not been measured: unlike the size caps it cannot be read off the source, and wants a real install against a live panel.
The tab is shown for these stock GameAP game codes:
cssource, cssv34, csgo, dods, garrysmod, hl2mp, l4d, l4d2,
tf2, bms, synergy
They are listed explicitly rather than gated on the engine name, because the
panel's tab gate is engines OR codes with no version component and the engine
string alone is too coarse in both directions: cs2 is also engine "Source"
(Source 2 — that is the CS2 Addons
plugin's job) and ark is mislabeled "Source" in the stock game list while
being Unreal Engine.
To use this plugin with a custom Source 1 game entry, add its code to
SOURCE_GAME_CODES in frontend/src/index.ts and src/maintenance.rs and
rebuild — a test fails if the two lists drift. The backend gate is
version-based (engine source, version not starting with 2) and already
accepts any such entry.
The mod directory is found from the launch command's -game <dir> first, then
from a table of the stock games' mod dirs, then by scanning for a
gameinfo.txt — skipping the base-content directories (hl2/, platform/, …)
that every Source install ships with a gameinfo.txt of their own.
-
Platform cards — Metamod:Source (installed / not active when
addons/metamodexists but nothing loads it, version via RCONmeta version, plus which loader method is in force) and SourceMod (installed / not active when unpacked but not registered with Metamod, version viameta listorsm version). -
Metamod plugin list on the Metamod card, merging the
.vdfaliases and themetaplugins.inilines. Each row says which file registers it, and SourceMod's own entry carries a platform badge. -
Plugin table — every
.smxfound on disk (addons/sourcemod/plugins/*), merged with live runtime state from RCONsm plugins list:Status Meaning Running on disk, loaded and running Paused loaded but paused ( sm plugins pause) — resumable in one clickEnabled / Stopped file state, when the console is unreachable Awaiting load on disk but not loaded (or disabled but still in memory) Error SourceMod reports the plugin failed File missing tracked in plugins_meta.jsonbut the.smxis gone -
Plugins are grouped by their
Groupfromplugins_meta.json, with inline-editable per-plugin comments.
sm plugins list prints a loaded plugin by its title — free text chosen by
the author, with no mechanical relationship to its file name ("Admin File
Reader" is admin-flatfile.smx). A failed plugin is printed by its file
instead, because there is no loaded plugin to ask for a title.
So rows are paired in three passes: the file the console named outright, then a
table of SourceMod's ~25 bundled plugin titles, then an exact normalized
title↔file match. Whatever is left is asked about directly with
sm plugins info <#>, which names the file — one round trip per unplaced
plugin, cached for the session. Nothing is guessed: a wrong pairing would show
one plugin's version and status on another plugin's row.
| Action | How it works |
|---|---|
| Enable / disable (persistent) | Moves the .smx between plugins/ and plugins/disabled/ — SourceMod's own convention, since it never descends into a subdirectory |
| Load / Unload / Reload / Unpause (hot) | sm plugins load / unload / reload / unpause — no restart needed |
| Install | Upload a .smx, or a whole release .zip / .tar.gz — the layout is detected and unpacked to the right place |
| Delete | Removes the .smx; configs, translations and gamedata are kept |
| Comments / groups | Stored in addons/sourcemod/configs/gameap/plugins_meta.json |
| Config editing | Modal editor for cfg/sourcemod/<name>.cfg, or addons/sourcemod/configs/<name>.cfg |
| Admins | Structured editor for admins_simple.ini, raw editor for admins.cfg |
The two switches mean different things:
- The On/Off switch on a row is persistent: it moves the
.smxin or out ofplugins/disabled/. It does not touch the running server — after switching, either use Load/Unload for an immediate effect or let the next restart apply it. - The Load / Unload button is immediate but temporary: it runs
sm plugins load/unloadover RCON. An unloaded plugin comes back at the next restart unless you also switch it off.
Source 1 supports two ways of starting Metamod, and this plugin understands both:
addons/metamod.vdf— a Valve server-plugin manifest naming Metamod's loader binary. This is what the official installer generates, it is the only file involved, and a game update does not touch it. Repairs write this one, choosingbin/serverorbin/linux64/serverto match the binary actually installed.- The
gameinfo.txtsearch path — aGame |gameinfo_path|addons/metamodline in the SearchPaths block. Detected so an existing install is never reported broken, and patchable on request ({"gameinfo": true}on the repair route).
Either one is enough. The scheduled sweep deliberately leaves a gameinfo-only install alone rather than adding a vdf on top of it.
All admin-only, under /api/plugins/onzggylemrxw4:
GET /servers/{id}/state assembled Metamod/SourceMod state
POST /servers/{id}/plugins/toggle {name, enabled} file move
POST /servers/{id}/plugins/attributes {name, comment, group}
POST /servers/{id}/plugins {name, force?} register upload
DELETE /servers/{id}/plugins {name} or ?name= delete
POST /servers/{id}/metamod/repair {gameinfo?} write the loader
POST /servers/{id}/metamod/toggle {name, enabled, source, line?, force?}
GET /servers/{id}/logs tail (?kind=errors|general)
POST /servers/{id}/restart restart via servercontrol
GET /servers/{id}/updates latest upstream versions (?refresh=1)
GET /servers/{id}/catalog curated catalog for this game
POST /servers/{id}/catalog/install {key} install from GitHub
POST /servers/{id}/platform/install {kind: metamod|sourcemod}
POST /servers/{id}/snapshots create snapshot
GET /servers/{id}/snapshots list snapshots
POST /servers/{id}/snapshots/restore {name}
DELETE /servers/{id}/snapshots {name}
GET /servers/{id}/audit recent panel actions
POST /servers/{id}/plugins/install-archive {path, force?}
(.zip or .tar.gz, 32 MiB max, and no
single file inside it over that)
GET /servers/{id}/doctor server-side health checks
Beyond HTTP the plugin exports one optional panel service, a
ScheduledTaskHandler with two tasks: srcaddons-metamod-loader-sweep (6h)
and srcaddons-update-check (24h).
A snapshot tars these, relative to the mod directory, into
addons/sourcemod/backups/:
addons/sourcemod/plugins addons/sourcemod/gamedata
addons/sourcemod/configs addons/sourcemod/translations
addons/sourcemod/data addons/sourcemod/extensions
cfg/sourcemod
cfg/sourcemod is why the tar runs from the mod directory rather than from
addons/sourcemod: SourceMod's generated per-plugin configs live outside
addons/ entirely.
gamedata and extensions are included because a plugin's release ships them
alongside its .smx and it will not run without them. The cost: restoring a
snapshot taken before a SourceMod update also reverts the gamedata that
update brought — re-run the SourceMod install afterwards if a game update is
involved.
- The Plugins tab doesn't appear — check, in order: the plugin is listed
under Administration → Plugins and the panel was restarted; the server's game
code is one of the supported ones; your user has admin rights
(the
plugin:onzggylemrxw4:manageability is granted to admins automatically). - "could not locate the mod directory" (422 on load) — no
gameinfo.txtwas found in the server directory. The server probably isn't installed yet, or its launch command has no-gameand its game code is not a stock one. - Metamod shows "Not active" —
addons/metamodexists but nothing loads it. Press Fix loader; it writesaddons/metamod.vdffor the binary you have. - SourceMod shows "Not active" — it is unpacked but not registered with Metamod. Re-enable its entry on the Metamod card, or reinstall SourceMod. This is the single most common "SourceMod does nothing" cause, and the Doctor names it too.
- A grey hint line about RCON — live statuses are unavailable for the stated reason (offline / no password / wrong password / empty response). Fix the RCON password in the server settings; file operations work regardless.
- A row shows "Awaiting load" for a plugin that is clearly running — the
console could not be paired with the file. Check the Doctor for a load
failure; if there is none, the plugin's title resolution may have hit the
sm plugins infocap (12 per refresh). - Toggle fails with "already exists; refusing to overwrite" — the same
.smxsits in bothplugins/andplugins/disabled/. Delete one of them in the file manager. The Doctor flags this too. - A catalog install fails with NO_MATCHING_ASSET — that release has no
.zipor.tar.gzmatching the entry's pattern. Download it from the linked release page and use Upload file, which takes the same archives. - Platform install fails with DOWNLOAD_FAILED or EXTRACT_FAILED — the node
is missing
curl/wgetortar; the error message names what was tried. Both features require a linux node. - I uploaded a plugin and nothing happened — SourceMod loads
.smxfiles only. A.spis source; compile it withaddons/sourcemod/scripting/compile.shfirst. The upload dialog and the Doctor both say so. - Where is the manifest?
<mod>/addons/sourcemod/configs/gameap/plugins_meta.json— plain JSON, safe to hand-edit.
Remove the plugin in Administration → Plugins (or delete the .wasm from the
panel's plugins directory) and restart the panel. Nothing on the game servers
is changed by uninstalling; anything you disabled stays in plugins/disabled/
until you move it back.
src/ Rust backend (compiled to wasm32-wasip1)
├── lib.rs plugin entry: info, routes, frontend bundle
├── router.rs route table + dispatch
├── consistency.rs tests that the Rust and TS halves still agree
├── handlers/ one file per route (+ tests.rs against a mock node)
├── source/ Source 1 domain: mod dir, metamod.vdf, metaplugins.ini,
│ gameinfo.txt, plugins_meta.json, archives, catalog
├── host_api.rs trait over the SDK host calls + MockHost for native tests
└── http.rs JSON response/error helpers
frontend/ Vue 3 frontend (vite lib build, embedded into the wasm)
├── src/index.ts plugin definition, translations (en/ru), tab registration
├── src/components/ ModsTab, PlatformCard, PluginList, and the modals
├── src/lib/ rcon-parse, status, admins (all unit-tested)
└── src/api/ clients for the wasm routes and existing panel endpoints
Tests: cargo test (104 backend tests run natively against the mock node — no
panel needed) and cd frontend && npm test (53 vitest tests for the parsers,
status logic and the admins round-trip).
Requirements: Rust (the version pinned in rust-toolchain.toml, with the
wasm32-wasip1 target), Node.js 22+, and a checkout of
gameap/gameap as a sibling directory
named gameap-api (the frontend depends on its web/plugin-sdk). The Rust
side pulls gameap-plugin-sdk as a cargo git dependency — no sibling checkout
needed for it.
# one-time: SDK build (sibling directory)
git clone https://github.com/gameap/gameap ../gameap-api
cd ../gameap-api/web/plugin-sdk
npm ci && npm install --no-save ../frontend/packages/gameap-ui && npx vite build
cd -
make build # frontend (vite) + wasm (cargo, wasm32-wasip1) → source-addons.wasm
make test # cargo test + vitest
make lint # clippy, both targets, -D warningsOn Windows, .\build.ps1 does the same without make (-Test also runs both
suites, -SkipFrontend builds only the wasm).
The version lives in four places that must agree: Cargo.toml,
frontend/package.json, frontend/package-lock.json (two root entries) and
frontend/src/index.ts. cargo test fails if any of them drifts — npm ci
does not check the root version field, so the lockfile is the copy most likely
to be left behind.
See CHANGELOG.md. Releases are tagged vX.Y.Z; each carries
the built source-addons.wasm.
MIT, same as the original.