Major improvements: - Created startup orchestration system with health monitoring and graceful shutdown - Fixed user registration and login with simplified authentication flow - Rebuilt authentication forms from scratch with direct API integration - Implemented comprehensive debugging and error handling - Added Redis fallback functionality for disabled environments - Fixed CORS configuration for cross-origin frontend requests - Simplified password validation to 6+ characters (removed complexity requirements) - Added toast notifications at app level for better UX feedback - Created comprehensive startup/shutdown scripts with OODA methodology - Fixed database validation and connection issues - Implemented TokenService memory fallback when Redis is disabled Technical details: - New SimpleLoginForm.tsx and SimpleRegisterForm.tsx components - Enhanced CORS middleware with additional allowed origins - Simplified auth validators and removed strict password requirements - Added extensive logging and diagnostic capabilities - Fixed authentication middleware token validation - Implemented graceful Redis error handling throughout the stack - Created modular startup system with configurable health checks 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
377 lines
No EOL
14 KiB
JavaScript
Executable file
377 lines
No EOL
14 KiB
JavaScript
Executable file
#!/usr/bin/env node
|
|
|
|
/**
|
|
* Shattered Void MMO Server Shutdown Script
|
|
* Gracefully stops all running game services
|
|
*/
|
|
|
|
const { spawn, exec } = require('child_process');
|
|
const { promisify } = require('util');
|
|
const execAsync = promisify(exec);
|
|
|
|
// Console colors for better output
|
|
const colors = {
|
|
reset: '\x1b[0m',
|
|
bright: '\x1b[1m',
|
|
red: '\x1b[31m',
|
|
green: '\x1b[32m',
|
|
yellow: '\x1b[33m',
|
|
blue: '\x1b[34m',
|
|
magenta: '\x1b[35m',
|
|
cyan: '\x1b[36m',
|
|
white: '\x1b[37m',
|
|
};
|
|
|
|
function log(level, message, data = {}) {
|
|
const timestamp = new Date().toISOString();
|
|
const logData = Object.keys(data).length > 0 ? ` ${JSON.stringify(data, null, 2)}` : '';
|
|
|
|
let color = colors.white;
|
|
let levelStr = level.toUpperCase().padEnd(7);
|
|
|
|
switch (level.toLowerCase()) {
|
|
case 'info':
|
|
color = colors.cyan;
|
|
break;
|
|
case 'success':
|
|
color = colors.green;
|
|
break;
|
|
case 'warn':
|
|
color = colors.yellow;
|
|
break;
|
|
case 'error':
|
|
color = colors.red;
|
|
break;
|
|
}
|
|
|
|
console.log(`${colors.bright}[${timestamp}] [PID:${process.pid}] [${color}${levelStr}${colors.reset}${colors.bright}]${colors.reset} ${color}${message}${colors.reset}${logData}`);
|
|
}
|
|
|
|
function displayHeader() {
|
|
console.log(`${colors.cyan}╔═══════════════════════════════════════════════════════════════╗
|
|
║ ║
|
|
║ ${colors.bright}SHATTERED VOID MMO SHUTDOWN${colors.reset}${colors.cyan} ║
|
|
║ ${colors.white}Post-Collapse Galaxy Strategy Game${colors.reset}${colors.cyan} ║
|
|
║ ║
|
|
║ ${colors.yellow}Gracefully stopping all running services...${colors.reset}${colors.cyan} ║
|
|
║ ║
|
|
╚═══════════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
console.log();
|
|
}
|
|
|
|
async function findProcesses() {
|
|
log('info', 'Scanning for running game processes...');
|
|
|
|
const processes = [];
|
|
|
|
try {
|
|
// Look for the main startup script
|
|
const { stdout: startupProcs } = await execAsync('ps aux | grep "node.*start-game.js" | grep -v grep || true');
|
|
if (startupProcs.trim()) {
|
|
const lines = startupProcs.trim().split('\n');
|
|
for (const line of lines) {
|
|
const parts = line.trim().split(/\s+/);
|
|
const pid = parts[1];
|
|
processes.push({
|
|
pid,
|
|
command: 'start-game.js',
|
|
type: 'main',
|
|
description: 'Main startup orchestrator'
|
|
});
|
|
}
|
|
}
|
|
|
|
// Look for Node.js processes on our ports
|
|
const { stdout: nodeProcs } = await execAsync('ps aux | grep "node" | grep -E "(3000|5173)" | grep -v grep || true');
|
|
if (nodeProcs.trim()) {
|
|
const lines = nodeProcs.trim().split('\n');
|
|
for (const line of lines) {
|
|
const parts = line.trim().split(/\s+/);
|
|
const pid = parts[1];
|
|
if (!processes.find(p => p.pid === pid)) {
|
|
processes.push({
|
|
pid,
|
|
command: 'node (port 3000/5173)',
|
|
type: 'server',
|
|
description: 'Backend/Frontend server'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for npm processes
|
|
const { stdout: npmProcs } = await execAsync('ps aux | grep "npm.*dev" | grep -v grep || true');
|
|
if (npmProcs.trim()) {
|
|
const lines = npmProcs.trim().split('\n');
|
|
for (const line of lines) {
|
|
const parts = line.trim().split(/\s+/);
|
|
const pid = parts[1];
|
|
if (!processes.find(p => p.pid === pid)) {
|
|
processes.push({
|
|
pid,
|
|
command: 'npm dev',
|
|
type: 'dev',
|
|
description: 'NPM development server'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for Vite processes
|
|
const { stdout: viteProcs } = await execAsync('ps aux | grep "vite" | grep -v grep || true');
|
|
if (viteProcs.trim()) {
|
|
const lines = viteProcs.trim().split('\n');
|
|
for (const line of lines) {
|
|
const parts = line.trim().split(/\s+/);
|
|
const pid = parts[1];
|
|
if (!processes.find(p => p.pid === pid)) {
|
|
processes.push({
|
|
pid,
|
|
command: 'vite',
|
|
type: 'frontend',
|
|
description: 'Vite development server'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// Look for Python servers (static file serving)
|
|
const { stdout: pythonProcs } = await execAsync('ps aux | grep "python.*http.server" | grep -v grep || true');
|
|
if (pythonProcs.trim()) {
|
|
const lines = pythonProcs.trim().split('\n');
|
|
for (const line of lines) {
|
|
const parts = line.trim().split(/\s+/);
|
|
const pid = parts[1];
|
|
if (!processes.find(p => p.pid === pid)) {
|
|
processes.push({
|
|
pid,
|
|
command: 'python http.server',
|
|
type: 'static',
|
|
description: 'Static file server'
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
} catch (error) {
|
|
log('warn', 'Error scanning for processes:', { error: error.message });
|
|
}
|
|
|
|
return processes;
|
|
}
|
|
|
|
async function checkPorts() {
|
|
log('info', 'Checking port usage...');
|
|
|
|
const ports = [];
|
|
|
|
try {
|
|
const { stdout } = await execAsync('ss -tlnp | grep ":3000\\|:5173" || true');
|
|
if (stdout.trim()) {
|
|
const lines = stdout.trim().split('\n');
|
|
for (const line of lines) {
|
|
const match = line.match(/:(\d+)\s.*users:\(\("([^"]+)",pid=(\d+)/);
|
|
if (match) {
|
|
const [, port, process, pid] = match;
|
|
ports.push({ port, process, pid });
|
|
}
|
|
}
|
|
}
|
|
} catch (error) {
|
|
log('warn', 'Error checking ports:', { error: error.message });
|
|
}
|
|
|
|
return ports;
|
|
}
|
|
|
|
async function stopProcess(process) {
|
|
log('info', `Stopping ${process.description}`, { pid: process.pid, command: process.command });
|
|
|
|
try {
|
|
// Try graceful shutdown first (SIGTERM)
|
|
await execAsync(`kill -TERM ${process.pid}`);
|
|
|
|
// Wait a moment for graceful shutdown
|
|
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
|
|
// Check if process is still running
|
|
try {
|
|
await execAsync(`kill -0 ${process.pid}`);
|
|
log('warn', `Process ${process.pid} still running, forcing shutdown...`);
|
|
await execAsync(`kill -KILL ${process.pid}`);
|
|
} catch (error) {
|
|
// Process already stopped
|
|
}
|
|
|
|
log('success', `Stopped ${process.description}`, { pid: process.pid });
|
|
return true;
|
|
} catch (error) {
|
|
if (error.message.includes('No such process')) {
|
|
log('info', `Process ${process.pid} already stopped`);
|
|
return true;
|
|
}
|
|
log('error', `Failed to stop process ${process.pid}:`, { error: error.message });
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function verifyShutdown() {
|
|
log('info', 'Verifying complete shutdown...');
|
|
|
|
const remainingProcesses = await findProcesses();
|
|
const remainingPorts = await checkPorts();
|
|
|
|
if (remainingProcesses.length === 0 && remainingPorts.length === 0) {
|
|
log('success', '✅ All game services successfully stopped');
|
|
return true;
|
|
} else {
|
|
if (remainingProcesses.length > 0) {
|
|
log('warn', `${remainingProcesses.length} processes still running:`, {
|
|
processes: remainingProcesses.map(p => ({ pid: p.pid, command: p.command }))
|
|
});
|
|
}
|
|
if (remainingPorts.length > 0) {
|
|
log('warn', `${remainingPorts.length} ports still in use:`, {
|
|
ports: remainingPorts.map(p => ({ port: p.port, process: p.process, pid: p.pid }))
|
|
});
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const startTime = Date.now();
|
|
|
|
try {
|
|
displayHeader();
|
|
|
|
// Phase 1: Discovery
|
|
log('info', 'Phase 1: Discovering running services');
|
|
const processes = await findProcesses();
|
|
const ports = await checkPorts();
|
|
|
|
if (processes.length === 0 && ports.length === 0) {
|
|
log('info', '🎯 No running game services found');
|
|
log('success', '✅ System is already clean');
|
|
return;
|
|
}
|
|
|
|
log('info', `Found ${processes.length} processes and ${ports.length} active ports`);
|
|
|
|
if (processes.length > 0) {
|
|
console.log('\n📋 Processes to stop:');
|
|
processes.forEach(proc => {
|
|
console.log(` • ${colors.yellow}${proc.description}${colors.reset} (PID: ${proc.pid}) - ${proc.command}`);
|
|
});
|
|
}
|
|
|
|
if (ports.length > 0) {
|
|
console.log('\n🔌 Ports to free:');
|
|
ports.forEach(port => {
|
|
console.log(` • Port ${colors.cyan}${port.port}${colors.reset} used by ${port.process} (PID: ${port.pid})`);
|
|
});
|
|
}
|
|
|
|
console.log();
|
|
|
|
// Phase 2: Graceful shutdown
|
|
log('info', 'Phase 2: Graceful service shutdown');
|
|
|
|
let stopCount = 0;
|
|
let failCount = 0;
|
|
|
|
// Stop processes in order of importance (main process first)
|
|
const processOrder = ['main', 'server', 'dev', 'frontend', 'static'];
|
|
for (const type of processOrder) {
|
|
const processesOfType = processes.filter(p => p.type === type);
|
|
for (const process of processesOfType) {
|
|
const success = await stopProcess(process);
|
|
if (success) {
|
|
stopCount++;
|
|
} else {
|
|
failCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Phase 3: Verification
|
|
log('info', 'Phase 3: Verification and cleanup');
|
|
const cleanShutdown = await verifyShutdown();
|
|
|
|
// Final summary
|
|
const duration = Date.now() - startTime;
|
|
console.log();
|
|
|
|
if (cleanShutdown) {
|
|
console.log(`${colors.green}╔═══════════════════════════════════════════════════════════════╗
|
|
║ SHUTDOWN COMPLETE ║
|
|
╠═══════════════════════════════════════════════════════════════╣${colors.reset}
|
|
${colors.white}║ Duration: ${duration}ms${' '.repeat(50 - duration.toString().length)}║
|
|
║ ║${colors.reset}
|
|
${colors.cyan}║ Services Stopped: ║${colors.reset}
|
|
${colors.white}║ ✅ All processes terminated ║
|
|
║ ✅ All ports freed ║
|
|
║ ✅ System clean ║${colors.reset}
|
|
${colors.green}║ ║
|
|
╚═══════════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
|
|
log('info', '🎮 Game services stopped successfully');
|
|
log('info', '💡 Run "node start-game.js" to restart the game');
|
|
} else {
|
|
console.log(`${colors.yellow}╔═══════════════════════════════════════════════════════════════╗
|
|
║ SHUTDOWN INCOMPLETE ║
|
|
╠═══════════════════════════════════════════════════════════════╣${colors.reset}
|
|
${colors.white}║ Duration: ${duration}ms${' '.repeat(50 - duration.toString().length)}║
|
|
║ ║${colors.reset}
|
|
${colors.white}║ Stopped: ${stopCount} processes${' '.repeat(42 - stopCount.toString().length)}║
|
|
║ Failed: ${failCount} processes${' '.repeat(43 - failCount.toString().length)}║${colors.reset}
|
|
${colors.yellow}║ ║
|
|
║ Some services may still be running. ║
|
|
║ Check the warnings above for details. ║
|
|
║ ║
|
|
╚═══════════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
|
|
log('warn', '⚠️ Some services may still be running');
|
|
log('info', '💡 You may need to manually stop remaining processes');
|
|
process.exit(1);
|
|
}
|
|
|
|
} catch (error) {
|
|
const duration = Date.now() - startTime;
|
|
log('error', 'Shutdown script failed:', {
|
|
error: error.message,
|
|
stack: error.stack,
|
|
duration: `${duration}ms`
|
|
});
|
|
|
|
console.log(`${colors.red}╔═══════════════════════════════════════════════════════════════╗
|
|
║ SHUTDOWN FAILED ║
|
|
╠═══════════════════════════════════════════════════════════════╣${colors.reset}
|
|
${colors.white}║ Duration: ${duration}ms${' '.repeat(50 - duration.toString().length)}║
|
|
║ ║${colors.reset}
|
|
${colors.red}║ An error occurred during shutdown. ║
|
|
║ Some services may still be running. ║
|
|
║ ║
|
|
╚═══════════════════════════════════════════════════════════════╝${colors.reset}`);
|
|
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Handle script interruption
|
|
process.on('SIGINT', () => {
|
|
log('warn', 'Shutdown script interrupted');
|
|
process.exit(1);
|
|
});
|
|
|
|
process.on('SIGTERM', () => {
|
|
log('warn', 'Shutdown script terminated');
|
|
process.exit(1);
|
|
});
|
|
|
|
// Run the shutdown script
|
|
if (require.main === module) {
|
|
main();
|
|
}
|
|
|
|
module.exports = { main }; |