Feature · 02
Live console
Logs stream from the container's stdio over a single WebSocket per server, multiplexed across every connected client. Commands typed in the panel land directly in the JVM.
Handshake
Connect to /ws/console with the session JWT in the Socket.IO auth payload (a query-string token also works). The gateway validates it in handleConnection and disconnects on failure before any room is joined.
// Token comes from the panel session; fetch it from GET /api/auth/token,
// then hand it to Socket.IO in the auth payload.
const ws = io("https://cexi.my.id", {
path: "/ws/console",
transports: ["websocket", "polling"],
auth: { token },
});
ws.on("connect", () => ws.emit("join", { serverId }));Replay buffer
The first event after join is history: the last 200 lines of the container's combined stdout/stderr, fetched via container.logs. This bridges the gap before the live attach stream takes over.
ws.on("history", ({ lines }) => term.write(lines));
ws.on("log", ({ data }) => term.write(data));Multiplexing
M.A.F Cloud holds at most one Docker attach stream per running container and emits its output to a Socket.io room (server:<id>). Joining as a second client increments a refcount; leaving decrements it; the stream is torn down when the count reaches zero. This avoids the multi-attach buffering quirks the Docker daemon has when several clients hold their own stream concurrently. If that stream drops while the container is still running, the agent re-attaches on its own so the room keeps receiving output.
Commands
A command event is bounded at 1 KB and stripped of trailing newlines server-side, then a single \n is appended before the bytes are written to the attach stream's stdin half. Anything you would type at the server console works:say hello, op steve, stop.
ws.emit("command", { serverId, text: "say hello" });Resilience & auto-reconnect
The console heals itself on both ends. The browser client prefers a raw WebSocket but falls back to HTTP long-polling behind proxies that refuse the upgrade, and it reconnects with backoff — re-joining the room and replaying the tail on every reconnect.
When an agent reconnects to the API after an update, crash, or network blip, the panel re-attaches every open console for that node, so live output resumes without a page reload.
Proxies & idle timeouts
Cloudflare's free plan caps proxied WebSocket idle time at roughly 100 seconds. For a hosting panel where a session stays open while you read logs, that manifests as the console dropping every couple of minutes; keep the API hostname on DNS-only mode (grey cloud) so traffic hits the origin directly and the origin timeout governs.
Platforms that sleep idle instances — such as Render's free tier after about 15 minutes — will drop the agent and console while asleep. Both reconnect automatically the next time the API is hit and wakes.