mc-manager/.claude/CLAUDE.md
2026-02-13 15:16:54 -08:00

410 lines
16 KiB
Markdown

# 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 `<img>` — 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 <input type="date">)
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<boolean>)
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