MoH Arena GUARD
Login
Server Owner Guide

Add MoH Arena Guard to your existing server.

You already run an AA, Spearhead, or Breakthrough server on Linux. This guide adds Guard on top of it: paste a few cvars into server.cfg and run one installer that sets the Python sidecar up as a systemd service — it survives reboots, restarts itself, and logs to journald. Nothing about your existing server changes — not the launcher, not the binaries, not your firewall. Guard needs no game files either: since Guard 1.0.0 the AC badge is drawn in the players' own game. Private stats are optional and take two small PK3s from the bundle.

What this guide assumes

Your server runs

You already start the game and it accepts players. Guard doesn't manage the game process for you.

It's Linux

The sidecar is Python and the optional firewall layer uses nft. Ubuntu 24.04 is the tested target.

You have RCON

rconPassword "..." is set in server.cfg. The sidecar uses it to kick; the password never leaves your box.

Where the files should live

Where things end up

You extract the bundle anywhere (your home directory is fine) and run the installer from there. The installer copies the sidecar to a fixed system location and keeps your secrets in a root-only config file — never inside the game folder.

/home/your-user/
  moharena-guard-server-owner/        # extracted bundle — run the installer from here
    install-systemd.sh                # the one script you run (as root)
    server-owner-install.sh           # apt packages helper (the installer calls apt too)
    sidecar.py                        # copied to /opt by the installer
    moharena-guard@.service           # systemd unit template
    SERVER_OWNER_GUIDE.md             # offline copy of this guide
    stats/                            # optional private stats PK3s, one folder per game and runtime

  your-existing-game-server/
    main/ | mainta/ | maintt/
      qconsole.log                    # game log stays with the game

/opt/moharena-guard/sidecar.py        # installed sidecar (self-updates live here)
/etc/moharena/guard-<game>.env        # your settings — root-only, mode 600
/etc/systemd/system/moharena-guard@.service

The handoff with mtc

You mtc

Send mtc a DM with these five values

  • Your Discord user IDSo mtc can grant you owner access on the Guard website.
  • A name for the serverShown in the Guard console — "VSUK Spearhead Public #1", etc.
  • Game + runtimeOne of aa-original, aa-openmohaa, sh-original, sh-openmohaa, bt-original, bt-openmohaa.
  • Public IP + game porte.g. 203.0.113.10:12205. The token mtc generates is bound to this exact address.
mtc You

mtc sends back three secrets

  • Guard server IDAn integer like 7. You'll paste it as MOHARENA_GUARD_SERVER_ID.
  • Sidecar tokenStarts with mohg_…. Shown on the website — treat it like a password.
  • Server addressThe same <ip>:<port> you sent — mtc echoes it back so you paste the exact string.

The four steps

  1. Download the bundle and put it on your box

    The bundle contains the installer (install-systemd.sh), the systemd unit template, sidecar.py, the optional stats PK3s and an offline copy of this guide. Download it straight onto the VPS (as whatever user already runs your game):

  2. Add the logging cvars to your server.cfg

    The sidecar reads qconsole.log, so the engine has to write the connect / chatter lines it parses. Paste these at the end of your existing server.cfg (logfile only controls flushing). Kicks and the rest of the policy are set on the Guard website, not in server.cfg.

    logfile values: 0 = off, 1 = on but buffered (the newest lines can be lost if the server crashes), 2 = on and flushed after every line. Any value above 1 behaves the same as 2, so 2 and 3 are equivalent. Use 2 (or 3) so Guard sees events in real time — 1 works but can lag behind.

    qconsole.log must be where Guard expects it. Point MOHARENA_QCONSOLE (next step) at the file the engine actually writes — usually your game's main folder (main/ for AA, mainta/ for SH, maintt/ for BT). If your engine writes it somewhere else (e.g. the server root), symlink it into place so both paths point at the same file:
    ln -s /home/mohaa/server2/qconsole.log /home/mohaa/server2/main/qconsole.log

    Upgrading from an older Guard setup? Since Guard 1.0.0 the AC badge is drawn in the players' own game, so the old server AC HUD can go at your next game server restart: delete zz_moharena_ac_hud*.pk3, remove the exec global/ac_player_hud.scr line if you added it to a precache script of your own, and drop the moh_ac_hud, moh_ac_active and moh_ac_status* lines from server.cfg. A zzzzzz_stats*.pk3 or zzzzzzzzzz_DMPrecacheFix*.pk3 from an older Guard bundle goes too; to keep private stats, use both PK3s from the new bundle instead (see Optional: private stats). The other moh_ac_* lines, such as moh_ac_kick_no_ac, do nothing on Guard servers (the Policy tab decides), so they can go too. Until the HUD PK3 is gone, keep its badge off with seta moh_ac_hud "0"; the Config tab can edit server.cfg.

    Optional: round countdown. Players who run the MoH Arena native client see a short countdown at every round start and are held still until it ends. It is off by default; set a length from 1 to 10 seconds to turn it on. It can be changed while the server runs.

  3. Run the installer — the sidecar becomes a systemd service

    Pass the three values from mtc plus your existing RCON password, qconsole.log path, and game as flags. It installs and starts moharena-guard@<game> — which survives reboots, restarts itself after crashes and self-updates, and logs to journald.

    --server-cfg-path points at your server.cfg. Set it now — it's what powers the Config tab on the website (view and edit your live config from the dashboard). Without it that tab can't fetch anything.

    Recovery copies. The first time you save an edit from the Config tab, Guard keeps two backups next to your server.cfg so you can recover over SSH if an edit ever breaks the server: server.cfg.moharena-bak (the pristine original) and server.cfg.moharena-prev (the version from just before your last edit).

    --runtime is original for the retail game (the default) or openmohaa if you run the OpenMoHAA engine. It must match how you registered the server with mtc, and it also picks your folder in stats/ if you add private stats.

    Prefer not to type flags? Open install-systemd.sh, fill in the CONFIG block at the top (same field names), and run sudo ./install-systemd.sh with no flags. Migrating from the old tmux wrapper? See MIGRATION.md in the bundle.

    CPU safety limit. New installations cap Guard at 30% of one CPU core. For an existing service, apply the same persistent limit immediately with sudo systemctl set-property moharena-guard@sh.service CPUQuota=30%, replacing sh with its instance name and repeating for each Guard instance. Sidecar 1.1.13 also lowers its own scheduler priority to nice level 10, which reaches existing clients through the normal Guard update and makes Guard yield CPU to the game without needing root.

    Several servers on one box? Run the installer once per server. Different games are automatic (each --game is its own service). For two servers of the same game (e.g. two aa servers), give each a distinct --instance name — otherwise the installer stops to stop you overwriting the first:
    sudo ./install-systemd.sh --game aa --instance aa1 --server-id 7 …
    sudo ./install-systemd.sh --game aa --instance aa2 --server-id 8 …

    About the firewall layer. Guard blocks players two ways: Layer 1 RCON-kicks (always on, no setup), and Layer 2 kernel-level packet drops via nftables. With the systemd service this just works: the unit grants the sidecar the one kernel capability nft needs (CAP_NET_ADMIN), so there is no sudoers entry and no running as root. Set MOHARENA_GUARD_FIREWALL_BLOCKS=0 in the env file if you want RCON kicks only.

    All your settings live in /etc/moharena/guard-<game>.env (root-only, mode 600) — the same options the old wrapper had, now with comments. Edit any value, then apply it with sudo systemctl restart moharena-guard@<game>:

  4. Check it on the Guard website

    Open your server's page on Guard. The Overview tab shows the last heartbeat (should refresh every few seconds). Connect a player and the Players tab fills in within 10 seconds. Players with Guard 1.0.0 or later see AC Active - N/M in their own game.

    The badge counts come from your sidecar's player list on the Guard website. If players see AC status unavailable, the website has no fresh player list from your server: check that the sidecar runs and that the heartbeat is recent.

    Open Guard servers Heartbeat refreshes every few seconds Policy changes apply within ~30 s, no restart

Optional: private stats

Two small PK3s from the bundle

Private stats fill the Stats tab of your server page on the Guard website. Only you and the Guard owners see them, unless you pick Public opt-in under Server analytics on the Policy tab to show them on the public Live Servers page too. Guard works the same without them. They take two PK3s from the stats/ folder of the bundle, in the folder for your game and runtime (the same --game and --runtime you gave the installer):

moharena-guard-server-owner/stats/
  aa-original/    # AA, retail game     zzzzzz_stats_aa.pk3 + zzzzzzzzzz_DMPrecacheFix_aa.pk3
  sh-original/    # SH, retail game     zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
  bt-original/    # BT, retail game     zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
  aa-openmohaa/   # AA on OpenMoHAA     zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
  sh-openmohaa/   # SH on OpenMoHAA     zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
  bt-openmohaa/   # BT on OpenMoHAA     zzzzzz_stats.pk3 + zzzzzzzzzz_DMPrecacheFix.pk3
  README.txt
  1. Pick your folder. OpenMoHAA servers use the -openmohaa folder for their game: its zzzzzz_stats.pk3 is the OpenMoHAA version. Every other server uses the -original folder.
  2. Copy both PK3s from it into your game folder: main/ for AA, mainta/ for SH, maintt/ for BT. Delete any older zzzzzz_stats*.pk3 and zzzzzzzzzz_DMPrecacheFix*.pk3 there first, so only these copies load.
  3. Restart the game server.
  4. Turn stats on. On the website, open your server's Policy tab and set Guard mode to AC + private stats. The sidecar picks it up within about 30 seconds, and the Stats tab fills as people play.

Your server already runs its own global/DMprecache.scr, for example from another mod? Keep it, copy only the stats PK3, and add these two lines at the end of that script, after level waittill spawn. The precache PK3 does the same for servers without one.

The stats PK3 also replaces global/obj_dm.scr to log bomb plants and defuses. The sidecar sends stats in batches: every 30 seconds, or sooner once 100 events are waiting (MOHARENA_GUARD_STATS_FLUSH_INTERVAL and MOHARENA_GUARD_STATS_MAX_BUFFER in the env file). To turn stats off, set Guard mode back to AC only and delete both PK3s at the next restart. Verification, blocks, player lists and kicks work either way.

Day-to-day commands (optional)

Here are some useful commands that you might want to use.

systemctl status moharena-guard@shIs the sidecar running? Swap sh for aa/bt on those games.
journalctl -u moharena-guard@sh -fFollow the live sidecar log.
journalctl -u moharena-guard@sh | grep FIREWALL_LAYERDid the Layer 2 nftables probe succeed at startup?
sudo nft list table inet moharena_guardShow the live kernel set of dropped IPs/CIDRs.
sudo systemctl restart moharena-guard@shApply changes after editing /etc/moharena/guard-sh.env or rotating the token.
sudo systemctl stop moharena-guard@shStop the sidecar. start brings it back; it also starts on boot.

FAQ

What exactly does Guard need from me, in one sentence?

Paste the logging cvars into server.cfg and run install-systemd.sh from the bundle with the values mtc sends you; the sidecar does the rest. Game files are only for optional private stats.

Do I need to give mtc shell access to my box?

No. Setup is entirely self-serve. mtc only registers a Guard record on the website and DMs you three values. Nothing on your machine talks to mtc directly — the sidecar talks only to guard.moharena.com.

Does Guard touch my RCON password?

No. The password stays in /etc/moharena/guard-<game>.env on your VPS — readable by root only. The sidecar uses it locally to issue kicks. The website never sees it.

Will Guard kick non-Guard players?

Only if you tell it to. New servers start with "log only, no kicks". You can change the policy on the website's Policy tab. Changes apply within ~30 seconds without restarting anything.

What's the difference between Blocks and Rules?

Blocks are exact-match, per-player or per-IP, manual entries (one button on the Players tab). Rules are patterns — IP exact, IP range, CIDR, wildcard prefix, country code, ASN, or "every VPN/proxy IP". A matching rule with action Permanent block auto-creates a block.

Players see "AC status unavailable" instead of "AC Active".

Their game gets no fresh player list for your server from Guard. Check that the sidecar runs (systemctl status moharena-guard@<game>) and that the Overview tab shows a recent heartbeat. The badge comes back on its own once the sidecar is sending again.

Players see no AC badge at all.

Since Guard 1.0.0 the badge is drawn by Guard in each player's own game, not by your server, so players need Guard 1.0.0 or later running. Players without it see no badge; no file or cvar on your server changes that.

The Stats tab stays empty.

Check that Guard mode on the Policy tab is AC + private stats, that both PK3s from your stats/ folder are in the game folder with no older copies next to them, and that the game server was restarted after you copied them. OpenMoHAA servers need the -openmohaa folder. See Optional: private stats.

Guard shows my Mac as AC-verified even though I'm not running Guard.

Your client has the auth_token cvar saved from a previous launcher session. The sidecar now only marks players as verified when the launcher is actively heartbeating — if you see this, make sure you are on the latest sidecar (mtc can push it remotely, or update by hand) and run sudo systemctl restart moharena-guard@<game>.

Does the sidecar run as root?

No. The installer runs as root once (to write the system files), but the service itself runs as your game-server user. The nftables firewall layer works anyway: the systemd unit grants the one kernel capability nft needs (CAP_NET_ADMIN) — no sudoers entry, no root process.

What's MOHARENA_GUARD_FIREWALL_LAYER mode=rcon-only?

The sidecar tried nft at startup and couldn't call it. RCON-kick still works fine; you just don't get the kernel-level packet drop. With the systemd service this shouldn't happen (the unit grants the capability and the right PATH) — check nft --version is installed, or set MOHARENA_GUARD_FIREWALL_BLOCKS=0 in the env file to silence the message.

How do I update the sidecar later?

Usually you don't have to do anything — mtc pushes releases from the website, the sidecar swaps its own file, and systemd restarts it on the new code within seconds. To update by hand: replace /opt/moharena-guard/sidecar.py with the latest from the bundle and run sudo systemctl restart moharena-guard@<game>. To refuse remote pushes entirely, set MOHARENA_SIDECAR_AUTO_UPDATE=0 in the env file.

How do I roll back if something breaks?

Run sudo systemctl disable --now moharena-guard@<game>. Your server is back to its pre-Guard state. The logging cvars in server.cfg are harmless when the sidecar isn't running. If you copied the stats PK3s, or an older bundle had you copy Guard PK3s or precache lines, you can delete those too.

I'm stuck. What should I send mtc?

Send the output of journalctl -u moharena-guard@<game> -n 50 --no-pager, the Guard server ID, and whatever the in-game HUD reads. Don't paste your mohg_... token or the RCON password.