mirror of
https://github.com/rmoren97/mc-manager.git
synced 2026-03-28 17:26:47 -07:00
updates
This commit is contained in:
parent
2478a29fd0
commit
c7e9540cba
@ -1,409 +0,0 @@
|
|||||||
# 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
|
|
||||||
213
.github/copilot-instructions.md
vendored
213
.github/copilot-instructions.md
vendored
@ -1,213 +0,0 @@
|
|||||||
# MC-Manager — Copilot Instructions
|
|
||||||
|
|
||||||
## Project Overview
|
|
||||||
Minecraft Server Manager — a full-stack Next.js 15+ (App Router) web application for managing Minecraft server instances. Built following Rezzect's organization-wide Next.js standards.
|
|
||||||
|
|
||||||
## Tech Stack
|
|
||||||
- **Framework:** Next.js 15+ (App Router, Turbopack), React 19+, TypeScript 5.9+ (strict mode)
|
|
||||||
- **Styling:** Tailwind CSS 3.4+ with dark glassmorphism design system (cyan-500 primary, `bg-gray-900/80 backdrop-blur-lg`)
|
|
||||||
- **Database:** MongoDB 6+ via Mongoose 8+ with serverless-safe cached connections (`src/lib/mongodb.ts`)
|
|
||||||
- **Auth:** Dual-token JWT (1h access + 7d refresh) in HTTP-only cookies, mandatory 2FA via email, bcryptjs (12 rounds)
|
|
||||||
- **Containers:** Docker via dockerode — each MC server runs as its own container
|
|
||||||
- **Icons:** Lucide React only — no other icon libraries
|
|
||||||
- **Email:** Microsoft Graph API primary, SMTP (nodemailer) fallback — always implement dual-provider
|
|
||||||
|
|
||||||
## Project Structure
|
|
||||||
```
|
|
||||||
src/app/ — Pages ('use client' on every page) and API routes
|
|
||||||
src/components/ — Shared components; templates/ for reusable CRUD patterns (DataManagementTemplate)
|
|
||||||
src/contexts/ — AuthContext, ToastContext, ConfirmationContext
|
|
||||||
src/lib/ — Server utilities: auth.ts, mongodb.ts, models.ts, docker.ts, date-utils.ts, input-validation.ts, audit.ts
|
|
||||||
src/hooks/ — Custom React hooks
|
|
||||||
src/types/ — TypeScript interfaces and types
|
|
||||||
```
|
|
||||||
|
|
||||||
## Critical Conventions
|
|
||||||
- **Every page** must start with `'use client'`; add `export const dynamic = 'force-dynamic'` if using `useSearchParams`
|
|
||||||
- **Date formatting:** NEVER create custom formatters — always use `formatDate()`, `formatDateTime()`, `formatDateForInput()` from `@/lib/date-utils`
|
|
||||||
- **Images:** Always `next/image` — never raw `<img>` tags
|
|
||||||
- **CRUD pages:** Use `DataManagementTemplate` from `@/components/templates/` for consistent data management UIs
|
|
||||||
- **Drawers** over modals for detail views (`max-w-3xl`, `animate-slide-in-right`)
|
|
||||||
|
|
||||||
## API Route Pattern (every route must follow this order)
|
|
||||||
1. `validateSession(request)` → 401 if missing
|
|
||||||
2. `getClientIP(request)` for audit trail
|
|
||||||
3. `sanitizeObject(await request.json())` — sanitize ALL input
|
|
||||||
4. `isValidObjectId()` / `isValidEmail()` — validate params
|
|
||||||
5. Permission check → 403 + audit log if denied
|
|
||||||
6. `connectToDatabase()` then business logic
|
|
||||||
7. `createAuditLog()` for every CREATE/UPDATE/DELETE (success AND failure)
|
|
||||||
8. Return proper HTTP status: 200/201/400/401/403/404/409/500
|
|
||||||
|
|
||||||
## Security (non-negotiable)
|
|
||||||
- Server-side permission checks are **authoritative**; client-side checks are UI hints only
|
|
||||||
- Permission format: `resource:action` (e.g., `servers:edit`, `servers:view:department`)
|
|
||||||
- Sanitize all inputs via `sanitizeObject()` from `@/lib/input-validation.ts`
|
|
||||||
- Parameterized DB queries only — never string concatenation
|
|
||||||
- Audit log ALL mutations with previous/new values and client IP
|
|
||||||
|
|
||||||
## UI Patterns (Dark Theme)
|
|
||||||
- **Base background:** `bg-gray-950` (page), `bg-gray-900` (surfaces), `bg-gray-800` (elevated elements)
|
|
||||||
- **Color semantics:** cyan=primary, emerald=success/active, amber=warning/pending, red=error/danger, gray=neutral
|
|
||||||
- **Text colors:** `text-gray-100` (primary), `text-gray-400` (secondary), `text-gray-500` (muted)
|
|
||||||
- **Animations:** Only animate `transform` and `opacity`, keep under 300ms
|
|
||||||
- **Buttons:** `bg-cyan-500 hover:bg-cyan-600 text-white rounded-lg` (primary), `bg-red-500 hover:bg-red-600` (danger), `bg-gray-700 hover:bg-gray-600 text-gray-200` (secondary)
|
|
||||||
- **Inputs:** `bg-gray-800 border-gray-700 text-gray-100 rounded-lg focus:ring-2 focus:ring-cyan-500 focus:border-transparent placeholder-gray-500`
|
|
||||||
- **Cards:** `bg-gray-900/80 backdrop-blur-lg rounded-lg shadow-lg border border-gray-700/50 p-6`
|
|
||||||
- **Glassmorphism card:** `bg-gray-800/60 backdrop-blur-lg rounded-lg border border-gray-700/50 shadow-xl`
|
|
||||||
- **Tables:** `bg-gray-900` body, `bg-gray-800/50` header, `divide-gray-700`, `hover:bg-gray-800` rows
|
|
||||||
- **Badges:** `bg-cyan-500/20 text-cyan-400` (info), `bg-emerald-500/20 text-emerald-400` (success), `bg-amber-500/20 text-amber-400` (warning), `bg-red-500/20 text-red-400` (error)
|
|
||||||
- **Borders:** `border-gray-700` (standard), `border-gray-700/50` (subtle)
|
|
||||||
- **Import order:** React hooks → Next.js hooks → contexts → lib utilities → components
|
|
||||||
|
|
||||||
## Minecraft Domain & Server Types
|
|
||||||
|
|
||||||
Each server instance has a **type** that determines its capabilities:
|
|
||||||
|
|
||||||
| Type | Examples | Supports Plugins | Supports Mods |
|
|
||||||
|------|----------|:-:|:-:|
|
|
||||||
| `vanilla` | Official Mojang JAR | ✗ | ✗ |
|
|
||||||
| `bukkit` | Spigot, PaperMC, Purpur, etc. | ✓ | ✗ |
|
|
||||||
| `forge` | Forge | ✗ | ✓ |
|
|
||||||
| `fabric` | Fabric | ✗ | ✓ |
|
|
||||||
|
|
||||||
**Feature matrix by type — gate UI and API logic on `server.type`:**
|
|
||||||
- **All types:** start/stop/restart, console streaming, command execution, server.properties editing, JVM args, backup/restore, player management (whitelist, ops, bans)
|
|
||||||
- **Bukkit-based only:** plugin install/remove/enable/disable (JAR-based in `plugins/` directory)
|
|
||||||
- **Forge/Fabric only:** mod install/remove/enable/disable (JAR-based in `mods/` directory)
|
|
||||||
- **Vanilla:** no extension management — only core server features
|
|
||||||
|
|
||||||
### Core Feature Areas
|
|
||||||
1. **Server Lifecycle** — create, start, stop, restart, delete instances; real-time status (online/offline/starting/stopping)
|
|
||||||
2. **Console** — live log streaming (tail server stdout), send commands to server stdin
|
|
||||||
3. **Configuration** — edit server.properties, JVM memory/flags, world settings per instance
|
|
||||||
4. **Backups** — manual + scheduled backups of world data; restore to point-in-time
|
|
||||||
5. **Plugins** (bukkit only) — upload/install/remove JARs; enable/disable without removing
|
|
||||||
6. **Mods** (forge/fabric only) — upload/install/remove JARs; enable/disable without removing
|
|
||||||
7. **Player Management** — whitelist add/remove, op/deop, ban/unban, view online players
|
|
||||||
|
|
||||||
## Data Models (Mongoose schemas in `src/lib/models.ts`)
|
|
||||||
|
|
||||||
### User
|
|
||||||
- `username`, `email`, `passwordHash` (bcrypt 12 rounds)
|
|
||||||
- `roles: [ObjectId]` → references Role
|
|
||||||
- `twoFactorCode`, `twoFactorExpiry` — for mandatory 2FA
|
|
||||||
- `loginAttempts`, `lockUntil` — account lockout (5 attempts → 30 min)
|
|
||||||
- `status`: active | inactive | locked
|
|
||||||
- `lastLogin`, `createdAt`, `updatedAt`
|
|
||||||
|
|
||||||
### Role
|
|
||||||
- `name` (e.g., Admin, Operator, Viewer)
|
|
||||||
- `permissions: [{ resource: string, actions: string[] }]`
|
|
||||||
- `description`, `isDefault`, `createdAt`
|
|
||||||
|
|
||||||
### Server
|
|
||||||
- `name`, `type`: vanilla | bukkit | forge | fabric
|
|
||||||
- `version` (MC version, e.g., "1.21.4")
|
|
||||||
- `dockerImage` (default `itzg/minecraft-server`, or a custom image)
|
|
||||||
- `containerId` (Docker container ID — set after creation)
|
|
||||||
- `containerName` (e.g., `mc-{serverId}`)
|
|
||||||
- `port` (host port mapped to container 25565)
|
|
||||||
- `rconPort` (host port mapped to container 25575, optional)
|
|
||||||
- `status`: online | offline | starting | stopping | crashed
|
|
||||||
- `maxPlayers`, `memory` (min/max heap in MB)
|
|
||||||
- `jvmArgs: [string]`
|
|
||||||
- `autoStart: boolean`, `autoRestart: boolean`
|
|
||||||
- `backupSchedule`: cron expression (e.g., `0 */6 * * *` = every 6h) or `null` if manual-only
|
|
||||||
- `backupRetention`: max number of backups to keep per server (oldest auto-deleted)
|
|
||||||
- `createdBy: ObjectId` → User who created it
|
|
||||||
- `createdAt`, `updatedAt`
|
|
||||||
|
|
||||||
### Backup
|
|
||||||
- `serverId: ObjectId` → Server
|
|
||||||
- `filename`, `filePath`, `fileSize`
|
|
||||||
- `type`: manual | scheduled
|
|
||||||
- `status`: completed | in_progress | failed
|
|
||||||
- `createdBy: ObjectId`, `createdAt`
|
|
||||||
|
|
||||||
### AuditLog
|
|
||||||
- Standard org pattern: `action`, `entityType`, `entityId`, `entityName`, `userId`, `userName`, `userEmail`, `previousValues`, `newValues`, `changes`, `clientIP`, `status`, `statusCode`
|
|
||||||
|
|
||||||
## Permissions
|
|
||||||
|
|
||||||
Format: `resource:action`. Relevant resources for this project:
|
|
||||||
|
|
||||||
```
|
|
||||||
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
|
|
||||||
```
|
|
||||||
|
|
||||||
Admin role gets `*:*` (wildcard). Always check `server.type` before allowing plugin/mod actions — return 400 if mismatched (e.g., plugin install on a Forge server).
|
|
||||||
|
|
||||||
## Docker Architecture
|
|
||||||
|
|
||||||
Every Minecraft server runs as an isolated Docker container. Use **dockerode** (`src/lib/docker.ts`) to interact with the Docker Engine API via Unix socket.
|
|
||||||
|
|
||||||
### Docker Images
|
|
||||||
- **Default:** `itzg/minecraft-server` — supports all server types via `TYPE` env var (e.g., `VANILLA`, `PAPER`, `SPIGOT`, `FORGE`, `FABRIC`)
|
|
||||||
- **Custom:** Users can specify any Docker image in `server.dockerImage`; when custom, the app still bind-mounts the same volume layout but skips `itzg`-specific env vars — the user is responsible for image compatibility
|
|
||||||
- Store `dockerImage` on the Server model; default to `itzg/minecraft-server` if not provided
|
|
||||||
|
|
||||||
### Container Lifecycle
|
|
||||||
- **Create:** `docker.createContainer()` with `server.dockerImage`, bind-mount a host volume for persistent data
|
|
||||||
- **Start/Stop/Restart:** `container.start()`, `container.stop()`, `container.restart()` — update `server.status` in MongoDB to match
|
|
||||||
- **Delete:** `container.remove({ force: true })`, then optionally clean up host volume
|
|
||||||
- **Status sync:** On app startup and periodically, reconcile `server.status` with `container.inspect()` state
|
|
||||||
|
|
||||||
### Volume Layout
|
|
||||||
Each server gets a host-mounted volume at a configurable base path (env `MC_SERVERS_PATH`, default `/opt/mc-servers/`):
|
|
||||||
```
|
|
||||||
/opt/mc-servers/{serverId}/
|
|
||||||
├── server.properties # MC config (editable via API)
|
|
||||||
├── world/ # World data (backed up)
|
|
||||||
├── plugins/ # Bukkit-type only
|
|
||||||
├── mods/ # Forge/Fabric only
|
|
||||||
├── logs/ # Server logs
|
|
||||||
└── backups/ # Backup archives
|
|
||||||
```
|
|
||||||
|
|
||||||
### Console & Logs
|
|
||||||
- **Log streaming:** `container.logs({ follow: true, stdout: true, stderr: true })` → stream to client via SSE or WebSocket
|
|
||||||
- **Command execution:** `container.exec()` to run `rcon-cli` or attach to stdin to send commands
|
|
||||||
- Alternative: use RCON protocol directly on the `rconPort` if RCON is enabled in server.properties
|
|
||||||
|
|
||||||
### Key Patterns for `src/lib/docker.ts`
|
|
||||||
- Export a singleton `getDockerClient()` that returns a cached dockerode instance
|
|
||||||
- All container operations must catch Docker API errors and map to proper HTTP status codes
|
|
||||||
- Container names follow convention: `mc-{server._id}` for easy lookup
|
|
||||||
- Always set resource limits: `--memory` from `server.memory.max`, CPU shares as needed
|
|
||||||
- Use `RestartPolicy: { Name: 'unless-stopped' }` when `server.autoRestart` is true
|
|
||||||
|
|
||||||
### Backups
|
|
||||||
- Pause world saving (`save-off` + `save-all` via RCON/exec) → tar/gzip the `world/` directory → resume (`save-on`)
|
|
||||||
- Store backup archives in `{serverDir}/backups/` and record metadata in the Backup model
|
|
||||||
- Restore: stop container → replace `world/` with extracted backup → start container
|
|
||||||
|
|
||||||
### Scheduled Backups (node-cron)
|
|
||||||
- Use **node-cron** running inside the Next.js process — no external scheduler needed
|
|
||||||
- Each server stores a `backupSchedule` cron expression (e.g., `0 */6 * * *`) and `backupRetention` count
|
|
||||||
- On app startup, query all servers with a `backupSchedule` and register cron jobs via `cron.schedule()`
|
|
||||||
- When a server's schedule is created/updated/deleted via API, dynamically add/update/remove the cron job
|
|
||||||
- Keep a `Map<serverId, CronJob>` in memory for lifecycle management
|
|
||||||
- `backupRetention`: after each successful backup, delete oldest archives exceeding the limit
|
|
||||||
- Scheduled backups set `backup.type = 'scheduled'`; manual backups set `backup.type = 'manual'`
|
|
||||||
|
|
||||||
### Environment Variables (Docker-specific)
|
|
||||||
```env
|
|
||||||
MC_SERVERS_PATH=/opt/mc-servers # Base path for server volumes
|
|
||||||
DOCKER_SOCKET=/var/run/docker.sock # Docker socket path (default)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Commands
|
|
||||||
```bash
|
|
||||||
npm run dev # Start dev server (port 3000, Turbopack)
|
|
||||||
npm run build # Production build
|
|
||||||
npm run lint # ESLint check
|
|
||||||
```
|
|
||||||
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"editor.fontFamily": "'CaskaydiaMono Nerd Font Mono'",
|
|
||||||
"editor.allowVariableFonts": true
|
|
||||||
}
|
|
||||||
Loading…
x
Reference in New Issue
Block a user