// WebSocket Server for Terminal // This runs as a separate process managed by PM2 import { createServer } from 'http'; import { Server } from 'socket.io'; import { Client } from 'ssh2'; import jwt from 'jsonwebtoken'; const JWT_SECRET = process.env.JWT_SECRET || '4916c430ed2682c2f023762e47531d95c6e51c4c5ab4ca8af79f6f010e203cf9'; const VM100_SSH_HOST = process.env.VM100_SSH_HOST || '192.168.178.129'; const VM100_SSH_USER = process.env.VM100_SSH_USER || 'root'; const VM100_SSH_PASSWORD = process.env.VM100_SSH_PASSWORD || 'Ba12sti34+'; const httpServer = createServer(); const io = new Server(httpServer, { cors: { origin: '*', credentials: true }, path: '/socket.io/', transports: ['websocket', 'polling'] }); // Authentication Middleware io.use((socket, next) => { const token = socket.handshake.auth.token; if (!token) { return next(new Error('Authentication error: No token provided')); } try { const payload = jwt.verify(token, JWT_SECRET); socket.data.user = payload.username; next(); } catch (err) { next(new Error('Authentication error: Invalid token')); } }); io.on('connection', (socket) => { console.log(`[Terminal] User ${socket.data.user} connected (${socket.id})`); const vmid = socket.handshake.query.vmid; const initialCols = parseInt(socket.handshake.query.cols) || 80; const initialRows = parseInt(socket.handshake.query.rows) || 24; console.log(`[Terminal] Initial terminal size: ${initialCols}x${initialRows}`); // Only allow VM 100 if (vmid !== '100') { socket.emit('error', 'Access denied: Only VM 100 console is available'); socket.disconnect(); return; } let sshClient = null; let sshStream = null; // SSH Connection const connectSSH = () => { sshClient = new Client(); sshClient.on('ready', () => { console.log(`[Terminal] SSH connected to VM ${vmid}`); socket.emit('status', 'connected'); // Create shell with correct initial size sshClient.shell({ term: 'xterm-256color', cols: initialCols, rows: initialRows }, (err, stream) => { if (err) { socket.emit('error', `Shell error: ${err.message}`); return; } sshStream = stream; // SSH → Browser stream.on('data', (data) => { socket.emit('data', data.toString('utf-8')); }); stream.on('close', () => { console.log(`[Terminal] SSH stream closed for ${socket.id}`); socket.emit('status', 'disconnected'); }); stream.stderr.on('data', (data) => { socket.emit('data', data.toString('utf-8')); }); // Browser → SSH socket.on('data', (data) => { if (sshStream && !sshStream.destroyed) { stream.write(data); } }); // Terminal Resize socket.on('resize', ({ rows, cols }) => { console.log(`[Terminal] Resize request: ${cols}x${rows}`); if (sshStream && !sshStream.destroyed) { try { stream.setWindow(rows, cols); console.log(`[Terminal] Resized to: ${cols}x${rows}`); } catch (err) { console.error(`[Terminal] Resize failed: ${err.message}`); } } }); }); }); sshClient.on('error', (err) => { console.error(`[Terminal] SSH error: ${err.message}`); socket.emit('error', `SSH connection failed: ${err.message}`); }); sshClient.on('close', () => { console.log(`[Terminal] SSH connection closed for ${socket.id}`); }); // Connect to VM 100 sshClient.connect({ host: VM100_SSH_HOST, port: 22, username: VM100_SSH_USER, password: VM100_SSH_PASSWORD, readyTimeout: 10000, }); }; // Start SSH connection connectSSH(); // Handle disconnect socket.on('disconnect', () => { console.log(`[Terminal] User ${socket.data.user} disconnected (${socket.id})`); if (sshStream) { sshStream.end(); } if (sshClient) { sshClient.end(); } }); }); const PORT = process.env.TERMINAL_PORT || 3001; httpServer.listen(PORT, () => { console.log(`[Terminal] WebSocket server running on port ${PORT}`); });