MeowMC

The web admin panel

The /meow panel commands, the loopback pairing service, the session store and the audit log are in the jar; the browser UI this page describes is not — the gateway, the console, the config editor, the players and worlds tabs. What follows is the design of that UI.

A web admin panel built into the MeowMC jar: off by default, opened at an unguessable URL on meowmc.org, unlocked only by a code that appears in the server console, giving a browser 30 minutes of full server administration — settings, live console, players, worlds — and turning itself off after 6 hours or on demand. It is a friendly UI over the same local config files the server and the in-game commands edit, not a second config system.

Off by default — and what "off" means

Off is not "enabled but hidden". The loopback listener is not bound, the random path does not exist in memory, and nothing is listening on the port — a packet sent there is refused by the operating system until an operator types the command. No config key auto-enables the panel: the command plus operator permission is the switch.

Restart is always off. The panel holds no file, no flag, no marker. A crash, a /stop, a JVM kill, a reboot — all leave it off, and the 6-hour ceiling can never become forever.

The commands

One subcommand under the existing /meow root (see the /meow suite), so /meow panel … and /meowmc panel … work identically:

| Command | Purpose | Permission | | --- | --- | --- | | /meow panel on | Open the panel for 6 hours and print the URL. | bukkit.command.meowmc.panel (OP default) | | /meow panel off | Close the panel now; revoke every session; the URL dies permanently. | same | | /meow panel status | On/off, remaining window, bind address, live session count, console enabled. | same |

Pairing: the URL, the confirm, the console code

The server console is the authentication factor — the panel session is a temporary, proxied console:

  1. /meow panel on prints a URL in the console only. It is 26 characters of base32 — 130 bits, unguessable, not listed anywhere, and single-purpose per opening, so the log file contains a URL that is useless alone.
  2. The operator opens that URL in an ordinary browser and presses Confirm authentication.
  3. Only now is a code issued, and it appears in the server console only: eight digits, two groups of four, a 2-minute lifetime, single-use, five attempts. A sixth submission of any kind burns it and demands a fresh Confirm.
  4. The operator types the code. A valid verify mints a session: 30 minutes, absolute — not sliding, not extended by activity, validated on every request server-side. When it ends, re-login means re-pairing.

Three bound sessions are the operator's: a laptop and a phone may both pair (capped at 5 live sessions per opening). Three revocation paths, all immediate: /meow panel off, per-session sign-out, and expiry. There is deliberately no revocation-with-grace.

One honest residual: if an attacker reads the console and the URL before the operator does, they get up to 30 minutes of full admin, fully audit-logged. The 30-minute ceiling exists precisely to cap that.

The 6-hour ceiling

A wall-clock deadline started at panel on. When it fires, the listener closes, every session is revoked and the event is audit-logged. The deadline is not extended by activity. The one allowed extension is a deliberate human act: running /meow panel on again while already open resets the deadline and reprints the same URL — it does not rotate the path, which would orphan every open browser behind the code gate.

The transport: one origin

The page is served from https://meowmc.org, and in the supported topology it talks only to meowmc.org: a stateless gateway on the web side proxies each request to the jar's loopback listener over plain HTTP on the box. The browser sees one origin, one TLS session, no mixed content, no cross-origin requests. The jar's listener is a JDK built-in HTTP server; there is no web framework and no new dependency.

  • v1 supports one topology: the jar on the same box as meowmc.org. That is deliberately the supported path.
  • A jar on the operator's own machine has a documented, best-effort fallback: the page probes the local port directly, same random path. It works in Firefox with no dialogs; in Chrome it depends on a private-network preflight the listener is specified to answer. Best-effort, not supported.
  • A jar on a third machine is unsupported in v1. The registration variant that would enable it was designed and rejected — it is an SSRF surface and it creates a second authority.

The tabs — phased

Config first, then console, then players, then worlds.

  • Overview — the /meow health numbers, live: TPS windows, MSPT against the tick budget, memory, GC, chunks, entities, per-world table. Same sampler, not a second one — an operator sees identical numbers in the panel and in chat.
  • Config — the editor below.
  • Console — a live, filterable, pausable console with a command input that is locked until the session acknowledges what it is (below).
  • Players — read-mostly: name, world, latency, gamemode, op status, plus message and kick. No player-data writes.
  • Worlds — loaded-worlds numbers plus a per-world override editor that writes only explicit overrides and never copies defaults into a world file. Read-only beyond that.

The config editor

The flagship: the whole config, honestly rendered.

  • Organised by file first — meowmc-global.yml, meowmc-world-defaults.yml, then each loaded world's meowmc-world.yml — the precedence the operator already knows (see files and precedence).
  • Every leaf shows the key path, its effective value after inheritance (absent-means-inherit and the INHERIT sentinel resolved), a type-aware control (toggle, bounded number, enum select, text, the one map editor), and the field's own comment as helper text — the same text the file carries, so the browser and the text editor explain the same key the same way.
  • An effect badge — live or restart — curated per key by citing its read site, so the operator never has to guess whether a change needs a restart. Keys not curated default to restart, the honest direction.
  • Validation runs client-side as a preflight and server-side through the real serializer — the same one the boot load uses. An invalid value never reaches the file.
  • Apply changes sends one batch, applied as one transaction: validate all, edit the raw text surgically, write the file once, atomic rename once, then one reload decision. One failed row rejects the whole batch with the offending path named — no partial writes, no lenient fallback.

The surgical edit is load-bearing, not stylistic: a full-object save through the config system's loader strips every comment and re-alphabetizes the keys of the hand-curated shipped files. The panel is forbidden that path — it replaces only the value token after the colon, preserving every other byte, and a key it cannot isolate to one line is refused, not reformatted.

Web and in-game edits share one writer service with one JVM-wide lock and one audit trail, so the two surfaces can never interleave half-writes, and an external edit that lands mid-batch is refused with "the file changed on disk — re-read and apply again" rather than clobbering a human's unsaved work.

The console tab is RCE-class

A remote command line is arbitrary command execution, and the design treats it that way — three concentric gates:

  1. One-time per-session acknowledgment. The command input is locked until the operator clicks Enable console input on a dialog whose text names exactly what it means: anyone using this panel can run any command the server console can.
  2. Typed confirmation for the destructive set — stop, restart, op, deop, whitelist, reload: the operator must type the command name before it dispatches.
  3. Every command audit-logged, with the full command text, the session and the requesting IP.

Commands dispatch onto the main thread, through the console sender, so every in-game permission check applies unmodified — the panel does not become a bypass around plugin-gated commands.

The audit log

Every state change appends one structured line to logs/panel-audit.log:

| Event | Recorded | Deliberately not recorded | | --- | --- | --- | | PANEL_ON / PANEL_OFF / AUTO_OFF | actor, window length, bind, port | — | | CONFIRM_REQUESTED, CODE_ISSUED | source IP, session | the code digits, never | | LOGIN_SUCCESS / LOGIN_FAIL | source IP, session prefix; failures record the attempt count | the submitted value | | CONFIG_WRITE | source IP, session, per-edit old → new | — values are recorded on purpose; this is the accountability layer | | COMMAND | source IP, session, the full command text, ack id | — |

The panel exposes it read-only. There is no API to delete or truncate it. The honest residual: an attacker with full session power can edit the file from the console — the log is accountability for the 30-minute ceiling, not forensic immutability.

One role, honestly

One role: operator. The pairing proves console access; the session inherits the console's authority. There are no tiers, no per-tab permissions, no read-only admins in v1 — the code gate is all-or-nothing, and pretending otherwise would be a UI lie. The accountability layer is the audit log, the 30-minute ceiling, and the requesting IP on every record.

The default binding is loopback only. A non-loopback panel.bind is refused unless allow-remote is explicitly set — the refusal names both keys, and the config comment states in the same sentence that the loopback leg is plaintext, so the session token would traverse the network. The gateway path never has this problem.

Proposed keys

Not in the jar. Each is a proposal; the panel is a server-wide service, so these would live in meowmc-global.yml under meowmc.panel.*:

| Key | Default | Purpose | | --- | --- | --- | | meowmc.panel.port | 31972 | The loopback port the listener binds. | | meowmc.panel.bind | 127.0.0.1 | The bind address. Non-loopback is refused without allow-remote. | | meowmc.panel.allow-remote | false | The explicit gate for a non-loopback bind. |

Firmly not proposed: an enabled flag (redundant with the command), an auto-off-hours knob (6 hours is fixed), a session-minutes knob (30 minutes is fixed).

The mode toggle's place

The panel surfaces exec.mode; the engine side is tick modes. The toggle is a segmented control at the top of the Global tab with a compat warning and a typed confirmation, and it writes with the restart badge — the panel never offers a live switch for it.

What the panel does not claim

  • Not a hosting control panel. No multi-server management, no user accounts, no roles, no billing, no file browser.
  • Not a persistent surface. Panel state is in memory only — which is why the panel cannot cause a config-file destruction incident from its own bookkeeping.