nexus-dashboard/server.mjs
2026-02-01 18:42:22 +00:00

156 lines
4.3 KiB
JavaScript

// 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}`);
});