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

+73
View File
@@ -0,0 +1,73 @@
import { NextRequest } from "next/server";
import {
listApproved,
approvePlayer,
revokePlayer,
reopenWindow,
resetPassword,
forceReregister,
importFromWhitelist,
} from "@/server/authflow";
import { whitelistList, easyauthList } from "@/server/mc";
import { ok, fail, readJson, handle } from "@/server/api";
export const dynamic = "force-dynamic";
export async function GET() {
return handle(async () => {
let whitelist: string[] | null = null;
let registered: string[] | null = null;
try {
whitelist = await whitelistList();
} catch {}
try {
registered = await easyauthList();
} catch {}
return ok({ approved: listApproved(), whitelist, registered });
});
}
type Body = {
op: "approve" | "revoke" | "reopen" | "reset-password" | "force-reregister" | "import-whitelist";
username?: string;
note?: string;
};
export async function POST(req: NextRequest) {
return handle(async () => {
const body = await readJson<Body>(req);
switch (body.op) {
case "approve": {
if (!body.username) return fail("Username required");
const player = await approvePlayer(body.username.trim(), body.note);
return ok(player);
}
case "revoke": {
if (!body.username) return fail("Username required");
await revokePlayer(body.username);
return ok({ revoked: true });
}
case "reopen": {
if (!body.username) return fail("Username required");
await reopenWindow(body.username);
return ok({ reopened: true });
}
case "reset-password": {
if (!body.username) return fail("Username required");
const temp = await resetPassword(body.username);
return ok({ tempPassword: temp });
}
case "force-reregister": {
if (!body.username) return fail("Username required");
await forceReregister(body.username);
return ok({ pending: true });
}
case "import-whitelist": {
const added = await importFromWhitelist();
return ok({ imported: added });
}
default:
return fail("Unknown operation");
}
});
}