Files
CK-X/app/server.js
2025-04-02 10:39:59 +05:30

238 lines
7.7 KiB
JavaScript

const express = require('express');
const cors = require('cors');
const path = require('path');
const fs = require('fs');
const { createProxyMiddleware } = require('http-proxy-middleware');
const http = require('http');
const socketio = require('socket.io');
const { Client } = require('ssh2');
// Server configuration
const PORT = process.env.PORT || 3000;
// VNC service configuration from environment variables
const VNC_SERVICE_HOST = process.env.VNC_SERVICE_HOST || 'remote-desktop-service';
const VNC_SERVICE_PORT = process.env.VNC_SERVICE_PORT || 6901;
const VNC_PASSWORD = process.env.VNC_PASSWORD || 'bakku-the-wizard'; // Default password
// SSH service configuration
const SSH_HOST = process.env.SSH_HOST || 'remote-terminal'; // Use remote-terminal service
const SSH_PORT = process.env.SSH_PORT || 22;
const SSH_USER = process.env.SSH_USER || 'candidate';
const SSH_PASSWORD = process.env.SSH_PASSWORD || 'password';
const app = express();
const server = http.createServer(app);
const io = socketio(server);
// SSH terminal namespace
const sshIO = io.of('/ssh');
// Handle SSH connections
sshIO.on('connection', (socket) => {
console.log('New SSH terminal connection established');
let ssh = new Client();
// Connect to the SSH server
ssh.on('ready', () => {
console.log('SSH connection established');
// Create shell session
ssh.shell((err, stream) => {
if (err) {
console.error('SSH shell error:', err);
socket.emit('data', `Error: ${err.message}\r\n`);
socket.disconnect();
return;
}
// Handle incoming data from SSH server
stream.on('data', (data) => {
socket.emit('data', data.toString('utf-8'));
});
// Handle errors on stream
stream.on('close', () => {
console.log('SSH stream closed');
ssh.end();
socket.disconnect();
});
stream.on('error', (err) => {
console.error('SSH stream error:', err);
socket.emit('data', `Error: ${err.message}\r\n`);
});
// Handle incoming data from browser
socket.on('data', (data) => {
stream.write(data);
});
// Handle resize events
socket.on('resize', (dimensions) => {
if (dimensions && dimensions.cols && dimensions.rows) {
stream.setWindow(dimensions.rows, dimensions.cols, 0, 0);
}
});
// Handle socket disconnection
socket.on('disconnect', () => {
console.log('Client disconnected from SSH terminal');
stream.close();
ssh.end();
});
});
});
// Handle SSH connection errors
ssh.on('error', (err) => {
console.error('SSH connection error:', err);
socket.emit('data', `SSH connection error: ${err.message}\r\n`);
socket.disconnect();
});
// Connect to SSH server
ssh.connect({
host: SSH_HOST,
port: SSH_PORT,
username: SSH_USER,
password: SSH_PASSWORD,
readyTimeout: 30000,
keepaliveInterval: 10000
});
});
// Create the public directory if it doesn't exist
const publicDir = path.join(__dirname, 'public');
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
console.log('Created public directory');
}
// Copy index.html to public directory if it doesn't exist
const indexHtmlSrc = path.join(__dirname, 'index.html');
const indexHtmlDest = path.join(publicDir, 'index.html');
if (fs.existsSync(indexHtmlSrc) && !fs.existsSync(indexHtmlDest)) {
fs.copyFileSync(indexHtmlSrc, indexHtmlDest);
console.log('Copied index.html to public directory');
}
// Enable CORS
app.use(cors());
// Serve static files from the public directory
app.use(express.static(path.join(__dirname, 'public')));
// Configure VNC proxy middleware
const vncProxyConfig = {
target: `http://${VNC_SERVICE_HOST}:${VNC_SERVICE_PORT}`,
changeOrigin: true,
ws: true,
secure: false,
pathRewrite: {
'^/vnc-proxy': ''
},
onProxyReq: (proxyReq, req, res) => {
// Log HTTP requests being proxied
console.log(`Proxying HTTP request to VNC server: ${req.url}`);
},
onProxyReqWs: (proxyReq, req, socket, options, head) => {
// Log WebSocket connections
console.log(`WebSocket connection established to VNC server: ${req.url}`);
},
onProxyRes: (proxyRes, req, res) => {
// Log the responses from VNC server
console.log(`Received response from VNC server for: ${req.url}`);
},
onError: (err, req, res) => {
console.error(`Proxy error: ${err.message}`);
if (res && res.writeHead) {
res.writeHead(500, {
'Content-Type': 'text/plain'
});
res.end(`Proxy error: ${err.message}`);
}
}
};
// Middleware to enhance VNC URLs with authentication if needed
app.use('/vnc-proxy', (req, res, next) => {
// Check if the URL already has a password parameter
if (!req.query.password) {
// If no password provided, add default password
console.log('Adding default VNC password to request');
const separator = req.url.includes('?') ? '&' : '?';
req.url = `${req.url}${separator}password=${VNC_PASSWORD}`;
}
next();
}, createProxyMiddleware(vncProxyConfig));
// Direct WebSocket proxy to handle the websockify endpoint
app.use('/websockify', createProxyMiddleware({
...vncProxyConfig,
pathRewrite: {
'^/websockify': '/websockify'
},
ws: true,
onProxyReqWs: (proxyReq, req, socket, options, head) => {
// Log WebSocket connections to websockify
console.log(`WebSocket connection to websockify established: ${req.url}`);
// Add additional headers if needed
proxyReq.setHeader('Origin', `http://${VNC_SERVICE_HOST}:${VNC_SERVICE_PORT}`);
},
onError: (err, req, res) => {
console.error(`Websockify proxy error: ${err.message}`);
if (res && res.writeHead) {
res.writeHead(500, {
'Content-Type': 'text/plain'
});
res.end(`Websockify proxy error: ${err.message}`);
}
}
}));
// API endpoint to get VNC server info
app.get('/api/vnc-info', (req, res) => {
res.json({
host: VNC_SERVICE_HOST,
port: VNC_SERVICE_PORT,
wsUrl: `/websockify`,
defaultPassword: VNC_PASSWORD,
status: 'connected'
});
});
// Health check endpoint
app.get('/health', (req, res) => {
res.status(200).json({ status: 'ok', message: 'Service is healthy' });
});
// Catch-all route to serve index.html for any other requests
app.get('*', (req, res) => {
// Special handling for exam page
if (req.path === '/exam') {
res.sendFile(path.join(__dirname, 'public', 'exam.html'));
}
// Special handling for results page
else if (req.path === '/results') {
res.sendFile(path.join(__dirname, 'public', 'results.html'));
}
else {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
}
});
// Handle errors
app.use((err, req, res, next) => {
console.error('Server error:', err);
res.status(500).sendFile(path.join(__dirname, 'public', '50x.html'));
});
// Start the server
server.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
console.log(`VNC proxy configured to ${VNC_SERVICE_HOST}:${VNC_SERVICE_PORT}`);
console.log(`SSH service configured to ${SSH_HOST}:${SSH_PORT}`);
});