const { Server } = require('socket.io') const { Client: SSHClient } = require('ssh2') const { readSSHRecord, verifyAuth, RSADecrypt, AESDecrypt } = require('../utils') function createTerminal(socket, sshClient) { sshClient.shell({ term: 'xterm-color' }, (err, stream) => { if (err) return socket.emit('output', err.toString()) // 终端输出 stream .on('data', (data) => { socket.emit('output', data.toString()) }) .on('close', () => { consola.info('关闭终端') sshClient.end() }) // web端输入 socket.on('input', key => { if(sshClient._sock.writable === false) return consola.info('终端连接已关闭') stream.write(key) }) socket.emit('connect_terminal') // 已连接终端,web端可以执行指令了 // 监听按键重置终端大小 socket.on('resize', ({ rows, cols }) => { consola.info('更改tty终端行&列: ', { rows, cols }) stream.setWindow(rows, cols) }) }) } module.exports = (httpServer) => { const serverIo = new Server(httpServer, { path: '/terminal', cors: { origin: '*' // 'http://localhost:8080' } }) serverIo.on('connection', (socket) => { // 前者兼容nginx反代, 后者兼容nodejs自身服务 let clientIp = socket.handshake.headers['x-forwarded-for'] || socket.handshake.address let sshClient = new SSHClient() consola.success('terminal websocket 已连接') socket.on('create', ({ host: ip, token }) => { const { code } = verifyAuth(token, clientIp) if(code !== 1) { socket.emit('token_verify_fail') socket.disconnect() return } const sshRecord = readSSHRecord() let loginInfo = sshRecord.find(item => item.host === ip) if(!sshRecord.some(item => item.host === ip)) return socket.emit('create_fail', `未找到【${ ip }】凭证`) let { type, host, port, username, randomKey } = loginInfo try { // 解密放到try里面,防止报错【公私钥必须配对, 否则需要重新添加服务器密钥】 randomKey = AESDecrypt(randomKey) // 先对称解密key randomKey = RSADecrypt(randomKey) // 再非对称解密key loginInfo[type] = AESDecrypt(loginInfo[type], randomKey) // 对称解密ssh密钥 consola.info('准备连接终端:', host) const authInfo = { host, port, username, [type]: loginInfo[type] } sshClient .on('ready', () => { consola.success('已连接到终端:', host) socket.emit('connect_success', `已连接到终端:${ host }`) createTerminal(socket, sshClient) }) .on('error', (err) => { consola.error('连接终端失败:', err.level) socket.emit('connect_fail', err.message) }) .connect(authInfo) } catch (err) { consola.error('创建终端失败:', err.message) socket.emit('create_fail', err.message) } }) socket.on('disconnect', (reason) => { consola.info('终端连接断开:', reason) sshClient.end() sshClient.destroy() sshClient = null }) }) }