How to Create Web Based Terminal Using Xterm.Js to Ssh into a System on Local Network

How to create web based terminal using xterm.js to ssh into a system on local network

After a bit of research here is working code.

Libraries:

1) https://socket.io/

This library is used for transmit package from client to server.

2) https://github.com/staltz/xstream

This library is used for terminal view.

3) https://github.com/mscdex/ssh2

This is the main library which is used for establishing a connection with your remote server.

Step 1: Install Library 3 into your project folder

Step 2: Start from node side create a server.js file for open socket

Step 3:
Connection client socket to node server (both are in local machine)

The tricky logic is how to use socket and ssh2.

On emission of socket you need to trigger an SSH command using the ssh2 library. On response of the ssh2 library (from server) you need to transmit the socket package to the client. That's it.

Click here to find an example.

That example will have these files & folders:

Type    Name
------------
FILE server.js
FILE package.json
FOLDER src
FOLDER xtream

First you need to configure your server IP, user and password or cert file on server.js and just run node server.js.

P.S.: Don't forget to run npm install

Let me know if you have any questions!

how to create interactive ssh terminal and enter commands from the browser using Node JS in a Meteor app

I got this working by following these instructions for creating an interactive terminal in the browser and these instructions for using socket.io with Meteor.

Both sets of instructions needed some updating due to changes in packages:

  • meteor-node-stubs now uses stream-http instead of http-browserify
    https://github.com/meteor/node-stubs/issues/14 so don't use the hack for socket

  • xterm addons (fit) are now separate packages

  • xterm API has changed, use term.onData(...) instead of term.on('data'...)

I used these packages:

ssh2

xterm

xterm-addon-fit

socket.io

socket.io-client

and also had to uninstall meteor-mode-stubs and reinstall it to get a recent version that doesn't rely on the Buffer polyfill.

Here's my code.

Front end:

myterminal.html

<template name="myterminal">
<div id="terminal-container"></div>
</template>

myterminal.js

import { Template } from 'meteor/templating';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';

import './xterm.css'; // copy of node_modules/xterm/css/xterm.css
// xterm css is not imported:
// https://github.com/xtermjs/xterm.js/issues/1418
// This is a problem in Meteor because Webpack won't import files from node_modules: https://github.com/meteor/meteor-feature-requests/issues/278

const io = require('socket.io-client');

Template.fileExplorer.onRendered(function () {
// Socket io client
const PORT = 8080;

const terminalContainer = document.getElementById('terminal-container');
const term = new Terminal({ 'cursorBlink': true });
const fitAddon = new FitAddon();
term.loadAddon(fitAddon);
term.open(terminalContainer);
fitAddon.fit();

const socket = io(`http://localhost:${PORT}`);
socket.on('connect', () => {
console.log('socket connected');
term.write('\r\n*** Connected to backend***\r\n');

// Browser -> Backend
term.onData((data) => {
socket.emit('data', data);
});

// Backend -> Browser
socket.on('data', (data) => {
term.write(data);
});

socket.on('disconnect', () => {
term.write('\r\n*** Disconnected from backend***\r\n');
});
});
});

Server:

server/main.js

const server = require('http').createServer();

// https://github.com/mscdex/ssh2
const io = require('socket.io')(server);
const SSHClient = require('ssh2').Client;

Meteor.startup(() => {
io.on('connection', (socket) => {
const conn = new SSHClient();
conn.on('ready', () => {
console.log('*** ready');
socket.emit('data', '\r\n*** SSH CONNECTION ESTABLISHED ***\r\n');
conn.shell((err, stream) => {
if (err) {
return socket.emit('data', `\r\n*** SSH SHELL ERROR: ' ${err.message} ***\r\n`);
}
socket.on('data', (data) => {
stream.write(data);
});
stream.on('data', (d) => {
socket.emit('data', d.toString('binary'));
}).on('close', () => {
conn.end();
});
});
}).on('close', () => {
socket.emit('data', '\r\n*** SSH CONNECTION CLOSED ***\r\n');
}).on('error', (err) => {
socket.emit('data', `\r\n*** SSH CONNECTION ERROR: ${err.message} ***\r\n`);
}).connect({
'host': process.env.URL,
'username': process.env.USERNAME,
'agent': process.env.SSH_AUTH_SOCK, // for server which uses private / public key
// in my setup, already has working value /run/user/1000/keyring/ssh
});
});

server.listen(8080);
});

Note that I am connecting from a machine that has ssh access via public key to the remote server. You may need different credentials depending on your setup. The environment variables are loaded from a file at Meteor runtime.

How do I connect xterm.js(in electron) to a real working command prompt?

I figured it out, code on github: https://github.com/77Z/electron-local-terminal-prototype

Web terminal emulator with control over i/o

What you have to do is to create an xterm.js add-on similar to the attach addon, but a bit more interventional.

In a few points, you will have to:

  1. Connect xterm.js and your back-end via a WebSocket
  2. Listen to the xterm.js data event and after processing it, send the data to the socket
  3. Listen to the message WebSocket event and process the message, before writing it to the terminal, using #write

A quick look to the attach addon source code could also be enlightening

Connecting to remote SSH server (via Node.js/html5 console)

This is easily doable with modules like ssh2, xterm, and socket.io.

Here's an example:

  1. npm install ssh2 xterm socket.io
  2. Create index.html:
<html>
<head>
<title>SSH Terminal</title>
<link rel="stylesheet" href="/src/xterm.css" />
<script src="/src/xterm.js"></script>
<script src="/addons/fit/fit.js"></script>
<script src="/socket.io/socket.io.js"></script>
<script>
window.addEventListener('load', function() {
var terminalContainer = document.getElementById('terminal-container');
var term = new Terminal({ cursorBlink: true });
term.open(terminalContainer);
term.fit();

var socket = io.connect();
socket.on('connect', function() {
term.write('\r\n*** Connected to backend***\r\n');

// Browser -> Backend
term.on('data', function(data) {
socket.emit('data', data);
});

// Backend -> Browser
socket.on('data', function(data) {
term.write(data);
});

socket.on('disconnect', function() {
term.write('\r\n*** Disconnected from backend***\r\n');
});
});
}, false);
</script>
<style>
body {
font-family: helvetica, sans-serif, arial;
font-size: 1em;
color: #111;
}
h1 {
text-align: center;
}
#terminal-container {
width: 960px;
height: 600px;
margin: 0 auto;
padding: 2px;
}
#terminal-container .terminal {
background-color: #111;
color: #fafafa;
padding: 2px;
}
#terminal-container .terminal:focus .terminal-cursor {
background-color: #fafafa;
}
</style>
</head>
<body>
<div id="terminal-container"></div>
</body>
</html>

  1. Create server.js:
var fs = require('fs');
var path = require('path');
var server = require('http').createServer(onRequest);

var io = require('socket.io')(server);
var SSHClient = require('ssh2').Client;

// Load static files into memory
var staticFiles = {};
var basePath = path.join(require.resolve('xterm'), '..');
[ 'addons/fit/fit.js',
'src/xterm.css',
'src/xterm.js'
].forEach(function(f) {
staticFiles['/' + f] = fs.readFileSync(path.join(basePath, f));
});
staticFiles['/'] = fs.readFileSync('index.html');

// Handle static file serving
function onRequest(req, res) {
var file;
if (req.method === 'GET' && (file = staticFiles[req.url])) {
res.writeHead(200, {
'Content-Type': 'text/'
+ (/css$/.test(req.url)
? 'css'
: (/js$/.test(req.url) ? 'javascript' : 'html'))
});
return res.end(file);
}
res.writeHead(404);
res.end();
}

io.on('connection', function(socket) {
var conn = new SSHClient();
conn.on('ready', function() {
socket.emit('data', '\r\n*** SSH CONNECTION ESTABLISHED ***\r\n');
conn.shell(function(err, stream) {
if (err)
return socket.emit('data', '\r\n*** SSH SHELL ERROR: ' + err.message + ' ***\r\n');
socket.on('data', function(data) {
stream.write(data);
});
stream.on('data', function(d) {
socket.emit('data', d.toString('binary'));
}).on('close', function() {
conn.end();
});
});
}).on('close', function() {
socket.emit('data', '\r\n*** SSH CONNECTION CLOSED ***\r\n');
}).on('error', function(err) {
socket.emit('data', '\r\n*** SSH CONNECTION ERROR: ' + err.message + ' ***\r\n');
}).connect({
host: '192.168.100.105',
username: 'foo',
password: 'barbaz'
});
});

server.listen(8000);

  1. Edit the SSH server configuration passed to .connect() in server.js
  2. node server.js
  3. Visit http://localhost:8000 in your browser


Related Topics



Leave a reply



Submit