trevato/mcpanel
Public: anyone can read this; the crew can change it.
A Minecraft server hosting panel: create servers (name, version, memory, port), start/stop/restart with a supervised child process, a live console with command input, scheduled backups with restore, a players list from the server log, and per-server settings. Where a real Java server cannot run in this environment, run a faithful simulator with the same interface and say so in the UI.
- src
- static
- .gitignore 50 B · ~15 tok
- AGENTS.md 2.2 KB · ~560 tok
- forge.json 111 B · ~37 tok
- package.json 144 B · ~48 tok
- README.md 4.8 KB · ~1.2k tok
README.md
mcpanel
A hosting panel for Minecraft servers: create a server (name, version, memory, port), start, stop and restart it under a supervisor, watch its console live and type commands into it, see who is online, take and restore backups, and change its settings.
Real or simulated
A server here is a real, supervised child process. What that process is depends on the machine:
- If
javais on the PATH and a jar sits at
$FORGE_APP_DATA/jars/minecraft_server.<version>.jar, the panel runs
java -Xmx<memory>M -jar <jar> nogui — the real thing.
- Otherwise it runs
src/mcsim.ts, a simulator with the same interface: it
binds the server's TCP port and answers a Minecraft client's server list
ping with the MOTD, prints vanilla-shaped log lines to stdout, takes
commands on stdin (list, say, save-all, op, kick, whitelist,
time set, weather, difficulty, gamemode, seed, stop), keeps a
world on disk, and shuts down on stop or SIGTERM.
There is no Java runtime on the forge, so every server on the deployed panel is simulated, and the page says so at the top of the server list and next to each server's status. Everything around the process — supervision, crash restart with backoff, the console, the players list read from the log, the backups, the port — behaves the same either way.
Using it
- Servers (
/) lists what exists, with status, players and Start/Stop. - A server (
/servers/<id>) is the console (server-rendered history, then
live over server-sent events), a command box, the players list, backups and settings. Commands are written to the process's standard input one line at a time; nothing is ever handed to a shell.
- Backups are single tar files under
$FORGE_APP_DATA/backups/<id>/. You
can download one and open it with tar xf, and restore it onto a stopped
server. Set "Backup every N minutes" in Settings and the panel takes them on
a schedule, keeping the newest few. Every backup — by hand, on the schedule
or from the terminal — prunes the server down to its "Backups kept", so what
is on disk is what the settings say.
- Crashes: if a server exits on its own and "Restart if it crashes" is on,
the supervisor brings it back with a growing delay, up to five times, and says so in the console. A server that was running is started again when the panel itself restarts.
Two buttons cost real work, so both are rate limited per server, whether they are pressed on the page or written by the terminal: a backup by hand at most once every 30 seconds (and never two at once for one server), and console commands at 20 in any 10 seconds. Over the limit you get a sentence saying how long to wait — 429 for the page's own JSON send — and nothing happens.
Set MCPANEL_PASSWORD in the environment to put the panel behind a password
(session in an HttpOnly cookie). With it unset the panel is open and the header
says "open panel". No secrets live in this repository.
PORT=8080 FORGE_APP_DATA=./data bun run src/server.ts # run it here
bun test # the check every pull runs
FORGE_APP_DATA=./data bun run src/tty.ts # the terminal interface
The terminal is the same panel: j/k move, Enter opens a server's console,
s/x/r start, stop and restart, c types a command, b backs up, R
restores the newest backup, q quits. It does not own the child processes, so
each key writes an intent that the panel process picks up within a second. The
exact command that reaches it is in FORGE_APP_SSH when the app runs; the app
is the login name and the forge is the host. When FORGE_APP_SSH is unset the
terminal is off.
Shape
Zero dependencies — Bun, bun:sqlite and the standard library only.
| file | one concept |
|---|---|
src/server.ts | the floor: security headers, same-origin POSTs, static files |
src/routes.ts | the table of paths, and the console's event stream |
src/db.ts | schema and queries |
src/runtime.ts | the supervisor: spawn, read, write, stop, restart |
src/engine.ts | real Java or the simulator, and the argv for each |
src/mcsim.ts | the simulated server, run as its own process |
src/backups.ts, src/tar.ts | backups, and the tar they are made of |
src/schedule.ts | the heartbeat: intents, scheduled backups |
src/players.ts, src/slp.ts, src/fields.ts, src/session.ts, src/limit.ts | log parsing, server list ping, validation, sessions, rate limits |
src/pages/*.ts | rendering |
static/panel.css, static/panel.js | one stylesheet, one small script |
Everything the panel keeps — the database, each server's directory, the
backups — lives under FORGE_APP_DATA. Production and each preview get their
own, so a preview never touches production's worlds. forge.json says how the
app runs.