# MC-Manager — Claude Code Instructions ## Project Overview Minecraft Server Manager — full-stack Next.js 16 (App Router) web app for managing Minecraft server instances. Each server runs in its own Docker container. ## Commands ```bash npm run dev # Dev server (port 3000, Turbopack) npm run build # Production build npm run lint # ESLint npm run seed # Seed DB with default roles + admin user (tsx scripts/seed.ts) ``` ## Actual Tech Stack (exact versions matter) - **Framework:** Next.js 16.1.6, React 19.2.3, TypeScript 5 (strict) - **Styling:** Tailwind CSS 4 — uses `@import "tailwindcss"` syntax, NOT `@tailwind base/components/utilities` - **Database:** MongoDB via Mongoose 9.1.6 — cached connection in `src/lib/mongodb.ts` - **Auth:** JWT dual-token (1h access + 7d refresh) in HTTP-only cookies; mandatory 2FA via email; bcryptjs (12 rounds) - **Containers:** dockerode 4.0.9 — Docker Engine API via Unix socket - **Scheduling:** node-cron 4.2.1 — runs inside Next.js process (no external scheduler) - **Icons:** Lucide React only — never use any other icon library - **Email:** Microsoft Graph API primary (`src/lib/email-graph.ts`), SMTP/nodemailer fallback — always dual-provider ## Project Structure ``` src/app/ — Pages + API routes (App Router) src/components/ — Shared UI; templates/DataManagementTemplate for CRUD pages src/contexts/ — AuthContext, ToastContext, ConfirmationContext src/hooks/ — useConsole, useServerStatus, useAuth, useToast, useConfirmation src/lib/ — Server-only utilities (never import in client components): auth.ts — validateSession, generateAccessToken/RefreshToken, verifyPassword, hashPassword, hasPermission, setAuthCookies, clearAuthCookies, generate2FACode, hash2FACode, verify2FACode, send2FAEmail, sendEmail email-graph.ts — getMsalClient, getGraphClient, sendGraphEmail (Azure MSAL) mongodb.ts — connectToDatabase() (cached Mongoose connection) models.ts — User, Role, Server, Backup, AuditLog (Mongoose schemas + TS interfaces) docker.ts — getDockerClient, createServerContainer, ensureServerDirectory, getContainerByName, getContainerById, mapContainerState, getItzgServerType, getServersBasePath, getServerPath backup-scheduler.ts — initBackupScheduler, registerBackupJob, removeBackupJob, updateBackupJob audit.ts — createAuditLog, getClientIP input-validation.ts — sanitizeString, sanitizeHtml, sanitizeObject, isValidObjectId, isValidEmail, isValidCron, isValidPort date-utils.ts — formatDate, formatDateTime, formatDateForInput, formatFileSize, formatRelativeTime src/types/ — server.ts (Server, ServerType, supportsPlugins, supportsMods), user.ts (User, AuthUser, Role, Permission, SessionPayload), backup.ts (Backup, BackupType, BackupStatus) src/middleware.ts — Next.js middleware ``` ## Critical Conventions ### Pages - Every page MUST start with `'use client'` - Add `export const dynamic = 'force-dynamic'` only if the page uses `useSearchParams()` - Never use raw `` — always `next/image` - Allowed remote image domains: `mc-heads.net`, `crafatar.com` ### Dates & Sizes — NEVER create custom formatters Always import from `@/lib/date-utils`: ```ts formatDate(date) // "Jan 15, 2026" formatDateTime(date) // "Jan 15, 2026 at 2:30 PM" formatDateForInput(date) // "2026-01-15" (for ) formatFileSize(bytes) // "1.5 GB", "256 MB", "12 KB" formatRelativeTime(date) // "2 hours ago", "just now" ``` ### CRUD Pages Use `DataManagementTemplate` from `@/components/templates/` — provides search, filter, sort, table, empty state, and create button. Never build custom data tables from scratch. ### Drawers over Modals for detail views Drawers use `max-w-3xl animate-slide-in-right`. Modals (`Modal.tsx`) are for creation/confirmation. ### Import Order (enforce this order) 1. React hooks 2. Next.js hooks (`useRouter`, `useSearchParams`, etc.) 3. Contexts (`useAuth`, `useToast`, `useConfirmation`) 4. Lib utilities 5. Components ### Server Type Helpers (from `@/types/server`) ```ts supportsPlugins(type: ServerType): boolean // true only for 'bukkit' supportsMods(type: ServerType): boolean // true for 'forge' | 'fabric' ``` Always gate plugin/mod UI and API logic behind these — never hardcode type checks. ## API Route Pattern (enforce this exact order) ```ts import { NextRequest, NextResponse } from 'next/server' import { validateSession, hasPermission } from '@/lib/auth' import { getClientIP, createAuditLog } from '@/lib/audit' import { sanitizeObject } from '@/lib/input-validation' import { isValidObjectId } from '@/lib/input-validation' import { connectToDatabase } from '@/lib/mongodb' export async function POST(request: NextRequest) { const clientIP = getClientIP(request) // 1. Auth const session = await validateSession(request) if (!session) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) // 2. Permission check — ALWAYS audit-log denials if (!hasPermission(session, 'resource:action')) { await createAuditLog({ action: '...', ..., status: 'FAILED', statusCode: 403, clientIP }) return NextResponse.json({ error: 'Forbidden' }, { status: 403 }) } // 3. Sanitize input BEFORE any use const { field1, field2 } = sanitizeObject(await request.json()) // 4. Validate params if (!isValidObjectId(someId)) return NextResponse.json({ error: '...' }, { status: 400 }) // 5. DB + business logic await connectToDatabase() // ... Mongoose queries ... // 6. Audit log every mutation (success AND failure) await createAuditLog({ action: 'entity_created', entityType: 'entity', entityId: doc._id.toString(), entityName: doc.name, userId: session._id, userName: session.username, userEmail: session.email, newValues: { ... }, clientIP, status: 'SUCCESS', statusCode: 201, }) return NextResponse.json({ success: true, data: doc }, { status: 201 }) } ``` **HTTP status codes:** 200 OK, 201 Created, 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 409 Conflict, 500 Internal Server Error ## Security — Non-Negotiable Rules - Server-side permission checks are authoritative; client-side is UI-only - `sanitizeObject()` on ALL request bodies before any use - Parameterized Mongoose queries only — never string concatenation in queries - Audit log ALL mutations (CREATE/UPDATE/DELETE) with previousValues + newValues + clientIP - Never expose `passwordHash`, `twoFactorCode`, `twoFactorExpiry`, `loginAttempts` in API responses — use `.select('-passwordHash -twoFactorCode ...')` on Mongoose queries - Permission format: `resource:action` (e.g., `servers:edit`, `backups:delete`) - Admin wildcard: `*:*` ## Permissions Reference ``` servers:view servers:create servers:edit servers:delete servers:start servers:stop servers:restart servers:console backups:view backups:create backups:restore backups:delete plugins:view plugins:install plugins:remove plugins:toggle mods:view mods:install mods:remove mods:toggle players:view players:whitelist players:op players:ban users:view users:create users:edit users:delete roles:view roles:create roles:edit roles:delete audit:view ``` ## UI Patterns — Dark Glassmorphism ### Use these CSS utility classes (defined in `globals.css`): - `.glass` — standard card/panel (rgba white 6%, blur 24px) - `.glass-lg` — hero/prominent panels (rgba white 8%, blur 40px) - `.glass-surface` — subtle surfaces (rgba white 7%, blur 16px) - `.glass-input` — form inputs with cyan glow on focus - `.glass-shine` — adds top-edge gradient highlight via `::before` - `.glow-cyan`, `.glow-emerald`, `.glow-amber`, `.glow-red` — colored box shadows ### Background layers - Page background: `#050510` (set on `body` in globals.css — do NOT override with bg-* classes) - Surfaces: `bg-white/[0.06]` or `.glass` - Elevated: `bg-white/[0.08]` or `.glass-lg` ### Color semantics (Tailwind) | Meaning | Color | |---------|-------| | Primary/action | `cyan-400` / `cyan-500` | | Success / online | `emerald-400` / `emerald-500` | | Warning / pending | `amber-400` / `amber-500` | | Error / danger / offline | `red-400` / `red-500` | | Neutral | `gray-400` through `gray-900` | ### Text - Primary: `text-gray-100` - Secondary: `text-gray-400` - Muted/placeholder: `text-gray-500` ### Buttons ``` Primary: bg-cyan-500 hover:bg-cyan-600 text-white rounded-lg Danger: bg-red-500 hover:bg-red-600 text-white rounded-lg Secondary: bg-white/[0.08] hover:bg-white/[0.12] text-gray-200 rounded-lg border border-white/[0.12] ``` ### Badges ``` Info: bg-cyan-500/20 text-cyan-400 Success: bg-emerald-500/20 text-emerald-400 Warning: bg-amber-500/20 text-amber-400 Error: bg-red-500/20 text-red-400 ``` ### Animations (all defined in globals.css) - `animate-fade-in` (200ms) - `animate-scale-in` (250ms) — modals - `animate-slide-in-right` (300ms) — drawers, toasts - `animate-slide-up` (300ms) - Only animate `transform` and `opacity`; keep under 300ms ### Tables ``` Container: .glass rounded-xl overflow-hidden Header: bg-white/[0.05] text-gray-400 text-xs uppercase tracking-wider Row: border-b border-white/[0.06] hover:bg-white/[0.04] Dividers: divide-y divide-white/[0.06] ``` ## Docker Architecture ### Key docker.ts patterns ```ts // Always use getDockerClient() singleton import { getDockerClient, getServerPath, createServerContainer, mapContainerState } from '@/lib/docker' // Container naming containerName = `mc-${server._id}` // getItzgServerType maps server type to Docker env // 'bukkit' → 'PAPER' (not 'BUKKIT') // 'vanilla' → 'VANILLA' // 'forge' → 'FORGE' // 'fabric' → 'FABRIC' // Volume mount pattern (SELinux-compatible) // ${serverPath}:/data:Z ``` ### Volume layout ``` ${MC_SERVERS_PATH}/{serverId}/ ├── server.properties ├── world/ # Backed up ├── plugins/ # bukkit only ├── mods/ # forge/fabric only ├── logs/ └── backups/ ``` ### Environment variables ```env MC_SERVERS_PATH=/opt/mc-servers # Base path for server volumes DOCKER_SOCKET=/var/run/docker.sock # Docker socket DOCKER_UID=1000 # Container user ID (chown on dir creation) DOCKER_GID=1000 # Container group ID ``` ### Container status mapping Docker state → app `ServerStatus`: - `running` → `online` - `created` / `restarting` → `starting` - `paused` / `removing` → `stopping` - `exited` → `offline` - `dead` → `crashed` ### Backup process 1. Exec RCON `save-off` + `save-all flush` (pause world saving) 2. `tar -czf "${filePath}" -C "${serverPath}" world` 3. Exec RCON `save-on` (resume) 4. Create/update `Backup` document 5. Delete oldest backups beyond `backupRetention` limit ### next.config.ts note `dockerode` and `node-cron` are in `serverExternalPackages` — they can only run server-side. ## Data Models Quick Reference ### Server.status values `online` | `offline` | `starting` | `stopping` | `crashed` ### Server.type values `vanilla` | `bukkit` | `forge` | `fabric` ### Backup.type / Backup.status - type: `manual` | `scheduled` - status: `completed` | `in_progress` | `failed` ### User.status values `active` | `inactive` | `locked` ### AuditLog.status values `SUCCESS` | `FAILED` (always uppercase) ## Contexts — How to Use ```ts // Auth const { user, loading, login, verify2FA, logout, hasPermission, refreshUser } = useAuth() // Toast notifications const { showToast } = useToast() showToast('Server started', 'success') // 'success' | 'error' | 'info' | 'warning' // Confirmation dialogs (returns Promise) const { showConfirmation } = useConfirmation() const ok = await showConfirmation({ title: 'Delete Server?', message: 'This will permanently delete', itemName: server.name, type: 'danger', // 'danger' | 'warning' | 'info' }) ``` ## Hooks Quick Reference ```ts // Console streaming + command sending const { lines, connected, sending, sendCommand, clearConsole } = useConsole(serverId) // Single server status polling (default 10s interval) const { status, players, loading, refresh } = useServerStatus(serverId) // All servers status (default 15s interval) const { servers, loading, refresh } = useAllServersStatus() ``` ## Real-Time Console (SSE) - **Stream:** GET `/api/servers/[id]/console` → EventSource / SSE - **Send command:** POST `/api/servers/[id]/console` with `{ command: string }` - Client-side: use `useConsole()` hook — never reimplement SSE manually ## Authentication Flow Summary 1. POST `/api/auth/login` → sends 2FA email → returns `{ requiresTwoFactor: true }` + sets `pending_2fa` cookie (10 min) 2. POST `/api/auth/verify-2fa` → validates code → sets `session-token` (1h) + `refresh-token` (7d) cookies 3. GET `/api/auth/me` → returns `AuthUser` from current session 4. POST `/api/auth/logout` → clears cookies 5. POST `/api/auth/refresh` → rotates access token Account lockout: 5 failed login attempts → 30-minute lockout (tracked via `loginAttempts` + `lockUntil`) ## API Routes Map ``` /api/auth/ login, logout, me, refresh, verify-2fa /api/users/ GET list, POST create /api/users/[id] PATCH update, DELETE /api/roles/ GET list, POST create /api/roles/[id] PATCH update, DELETE /api/servers/ GET list, POST create /api/servers/[id] GET, PATCH, DELETE /api/servers/[id]/start POST /api/servers/[id]/stop POST /api/servers/[id]/restart POST /api/servers/[id]/console GET (SSE stream), POST (command) /api/servers/[id]/stats GET /api/servers/[id]/configuration GET/PATCH (server.properties) /api/servers/[id]/backups GET, POST (create manual) /api/servers/[id]/backups/[backupId] DELETE /api/servers/[id]/backups/[backupId]/restore POST /api/servers/[id]/plugins GET, POST (upload JAR) /api/servers/[id]/plugins/[filename] DELETE /api/servers/[id]/plugins/[filename]/toggle PATCH /api/servers/[id]/mods GET, POST (upload JAR) /api/servers/[id]/mods/[filename] DELETE /api/servers/[id]/mods/[filename]/toggle PATCH /api/servers/[id]/players GET (online players) /api/servers/[id]/players/[name]/[action] POST (op, deop, ban, unban, whitelist) /api/servers/[id]/files GET (file browser) /api/servers/[id]/logs GET (log files) /api/audit GET /api/health GET ``` ## Pages Map (all `'use client'`) ``` / Redirect → /dashboard or /login /login Login + 2FA flow /(app)/dashboard Overview: server stats, quick-access server list /(app)/servers Server list (DataManagementTemplate) /(app)/servers/[id] Server overview / detail /(app)/servers/[id]/console /(app)/servers/[id]/configuration /(app)/servers/[id]/plugins /(app)/servers/[id]/mods /(app)/servers/[id]/backups /(app)/servers/[id]/logs /(app)/servers/[id]/files /(app)/servers/[id]/players /(app)/users User management (DataManagementTemplate) /(app)/roles Role management (DataManagementTemplate) /(app)/audit Audit log viewer (DataManagementTemplate) ``` ## Common Mistakes to Avoid - **Do NOT** import `src/lib/` utilities in client components — they're server-only (use API routes instead) - **Do NOT** use `bg-gray-950` / `bg-gray-900` for backgrounds — use `.glass`, `.glass-surface`, or `bg-white/[0.06]` per the glassmorphism system - **Do NOT** use `tailwind.config.js` extend syntax for new colors — use Tailwind 4's CSS variable approach in globals.css if needed - **Do NOT** skip sanitizing request body before using any field - **Do NOT** build custom date formatters — use `@/lib/date-utils` - **Do NOT** build custom data tables — use `DataManagementTemplate` - **Do NOT** use any icon library other than `lucide-react` - **Do NOT** allow plugin routes on non-bukkit servers or mod routes on non-forge/fabric — return 400 with type mismatch error - **Do NOT** expose sensitive user fields in API responses (`passwordHash`, `twoFactorCode`, etc.) - **Do NOT** skip audit logging on mutations — every CREATE/UPDATE/DELETE must log success AND failure