mc-dashboard/app/api/mods/remove/route.ts
hurkicorgi dd69c17c3b Initial commit: Minecraft dashboard
Next.js 16 + Tailwind v4 + shadcn v4 dashboard for managing a modded
Forge 1.20.1 server. Includes server controls, player management, mod
manager with Modrinth search and dependency resolution, world backups,
snapshots, analytics, logs, and chat bridge.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:46:58 -06:00

101 lines
2.8 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import { existsSync } from "fs";
import { join } from "path";
import { exec } from "child_process";
import { auth } from "@/lib/auth";
import { removeMod, isClientOnlyMod, waitForServer, rebuildModpack } from "@/lib/mods";
import { createSnapshot, restoreSnapshot, listSnapshots } from "@/lib/snapshots";
import { MODS_DIR, CLIENT_MODS_DIR } from "@/lib/constants";
function restartServer(): Promise<void> {
return new Promise((resolve, reject) => {
exec("sudo systemctl restart minecraft.service", (err) => {
if (err) reject(err);
else resolve();
});
});
}
export async function POST(req: NextRequest) {
const session = await auth();
if (!session) {
return NextResponse.json({ error: "Unauthorized" }, { status: 403 });
}
const { filename } = await req.json();
if (!filename || !filename.endsWith(".jar")) {
return NextResponse.json({ error: "Invalid filename" }, { status: 400 });
}
if (filename.includes("/") || filename.includes("\\")) {
return NextResponse.json({ error: "Invalid filename" }, { status: 400 });
}
const inServerDir = existsSync(join(MODS_DIR, filename));
const inClientDir = existsSync(join(CLIENT_MODS_DIR, filename));
if (!inServerDir && !inClientDir) {
return NextResponse.json({ error: "Mod not found" }, { status: 404 });
}
// Create snapshot before removing
const snapName = `before-remove-${filename.replace(".jar", "")}`;
createSnapshot(snapName);
const clientOnly = !inServerDir && inClientDir;
// Remove the mod
removeMod(filename);
if (clientOnly) {
// Client-only mod — no restart needed
try { rebuildModpack(); } catch {}
return NextResponse.json({
success: true,
message: `Removed client-only mod "${filename}". No server restart needed.`,
});
}
// Server mod — restart and verify
try {
await restartServer();
} catch (e) {
const snaps = listSnapshots();
const snap = snaps[0];
if (snap) restoreSnapshot(snap.dirName);
return NextResponse.json({
success: false,
message: `Restart failed: ${(e as Error).message}`,
rolledBack: true,
});
}
const online = await waitForServer(90000);
if (online) {
try { rebuildModpack(); } catch {}
return NextResponse.json({
success: true,
message: `Removed "${filename}". Server is online.`,
});
} else {
// Rollback
const snaps = listSnapshots();
const snap = snaps[0];
if (snap) {
restoreSnapshot(snap.dirName);
try {
await restartServer();
await waitForServer(90000);
try { rebuildModpack(); } catch {}
} catch {}
}
return NextResponse.json({
success: false,
message: `Server failed after removing "${filename}". Rolled back.`,
rolledBack: true,
});
}
}