Minecraft 26.2 Fabric server stack with CraftDeck management panel

- docker-compose: itzg/minecraft-server (Fabric, offline mode, whitelist
  enforced, Modrinth-managed mods), itzg/mc-backup sidecar, custom dashboard
- Gated auth for offline mode: approve username -> whitelisted with a short
  registration window -> EasyAuth /register + /login; auto-revoke on expiry
- CraftDeck panel (Next.js 16, zero native deps): live console (SSE), players
  & stats, lag diagnostics (server TPS vs player ping via spark), performance
  graphs, Modrinth mod search/manager, backups & restore, sandboxed file
  browser/editor, .env config editor with compose apply, cron schedules,
  Discord alerts, scrypt-hashed panel login
- scripts/setup.sh generates secrets; docs/ covers auth, mods, updating,
  backups, networking (Tailscale), troubleshooting

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Aryan SinghandClaude Fable 5 committed 2026-07-10 11:07:07 -05:00
commit 279d137ce5
98 files changed
+9925

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
import { NextRequest } from "next/server";
import fs from "node:fs";
import path from "node:path";
import { readWorldInfo } from "@/server/playerstats";
import { getWorldSizeBytes } from "@/server/sampler";
import { inspectContainer, powerContainer } from "@/server/docker";
import { safetyBackup } from "@/server/backups";
import { logActivity } from "@/server/db";
import { notify } from "@/server/notify";
import { env } from "@/server/env";
import { ok, fail, readJson, handle } from "@/server/api";
export const dynamic = "force-dynamic";
export async function GET() {
return handle(async () => ok(readWorldInfo(getWorldSizeBytes())));
}
/** Danger zone: archive the current world and generate a fresh one. */
export async function POST(req: NextRequest) {
return handle(async () => {
const { op, confirm } = await readJson<{ op?: string; confirm?: string }>(req);
if (op !== "reset") return fail("Unknown operation");
if (confirm !== "reset the world") return fail('Type "reset the world" to confirm');
const worldDir = path.join(env.mcDataDir, "world");
if (!fs.existsSync(worldDir)) return fail("No world folder found");
await safetyBackup("world reset");
const state = await inspectContainer(env.mcContainer);
if (state.running) await powerContainer(env.mcContainer, "stop");
const archived = `world.old-${new Date().toISOString().replace(/[:.]/g, "-")}`;
fs.renameSync(worldDir, path.join(env.mcDataDir, archived));
await powerContainer(env.mcContainer, "start");
logActivity("world_reset", null, `old world kept as ${archived}`);
notify("warn", "World reset", `A fresh world is generating. Old world kept as ${archived}.`);
return ok({ archived });
});
}