简体   繁体   English

如何创建交互式 ssh 终端并在 Meteor 应用程序中使用 Node JS 从浏览器输入命令

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

I'm trying to create a web page where the user can authenticate to a remote server via ssh with username/password, and then interact with the remote server.我正在尝试创建一个 web 页面,用户可以在其中使用用户名/密码通过 ssh 对远程服务器进行身份验证,然后与远程服务器交互。

I'm not looking to create a full interactive terminal: the app server will execute a limited set of commands based on user input and then pass the responses back to the browser.我不希望创建一个完整的交互式终端:应用服务器将根据用户输入执行一组有限的命令,然后将响应传递回浏览器。

Different users should interact with different ssh sessions.不同的用户应该与不同的 ssh 会话进行交互。

My app is built in Meteor 1.8.1, so the back end runs under Node JS, version 9.16.0.我的应用程序是在 Meteor 1.8.1 中构建的,因此后端在 Node JS 版本 9.16.0 下运行。 It's deployed to Ubuntu using Phusion Passenger.它使用 Phusion Passenger 部署到 Ubuntu。

I have looked at several packages that can create an interactive ssh session but I am missing something basic about how to use them.我查看了几个可以创建交互式 ssh session 的软件包,但我缺少有关如何使用它们的基本知识。

For example https://github.com/mscdex/ssh2#start-an-interactive-shell-session例如https://github.com/mscdex/ssh2#start-an-interactive-shell-session

The example shows this code:该示例显示了以下代码:

var Client = require('ssh2').Client;

var conn = new Client();
conn.on('ready', function() {
  console.log('Client :: ready');
  conn.shell(function(err, stream) {
    if (err) throw err;
    stream.on('close', function() {
      console.log('Stream :: close');
      conn.end();
    }).on('data', function(data) {
      console.log('OUTPUT: ' + data);
    });
    stream.end('ls -l\nexit\n');
  });
}).connect({
  host: '192.168.100.100',
  port: 22,
  username: 'frylock',
  privateKey: require('fs').readFileSync('/here/is/my/key')
});

This example connects to the remote server, executes a command 'ls' and then closes the session.此示例连接到远程服务器,执行命令“ls”,然后关闭 session。 It isn't 'interactive' in the sense I'm looking for.在我正在寻找的意义上,它不是“交互式”的。 What I can't see is how to keep the session alive and send a new command?我看不到的是如何让 session 保持活动状态并发送新命令?

This example of a complete terminal looks like overkill for my needs, and I won't be using Docker.这个完整终端的示例对于我的需求来说似乎有点过头了,我不会使用 Docker。

This example uses socket.io and I'm not sure how that would interact with my Meteor app? 此示例使用 socket.io,我不确定这将如何与我的 Meteor 应用程序交互? I'm currently using Meteor methods and publications to pass information between client and server, so I'd expect to need a "Meteor-type" solution using the Meteor infrastructure?我目前正在使用 Meteor 方法和出版物在客户端和服务器之间传递信息,所以我希望需要使用 Meteor 基础设施的“流星型”解决方案?

child_process.spawn works but will only send a single command, it doesn't maintain a session. child_process.spawn 有效,但只会发送一个命令,它不维护 session。

I know other people have asked similar questions but I don't see a solution for my particular case.我知道其他人也问过类似的问题,但我没有看到针对我的特定案例的解决方案。 Thank you for any help.感谢您的任何帮助。

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 .我按照这些在浏览器中创建交互式终端的说明以及使用 socket.io 和 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 meteor-node-stubs 现在使用 stream-http 而不是 http- browserify https://github.com/meteor/node-stubs/issues/14所以不要使用hack for socket

  • xterm addons (fit) are now separate packages xterm 插件(fit)现在是单独的包

  • xterm API has changed, use term.onData(...) instead of term.on('data'...) xterm API 已更改,使用 term.onData(...) 代替 term.on('data'...)

I used these packages:我使用了这些软件包:

ssh2 SSH2

xterm xterm

xterm-addon-fit xterm-addon-fit

socket.io socket.io

socket.io-client socket.io 客户端

and also had to uninstall meteor-mode-stubs and reinstall it to get a recent version that doesn't rely on the Buffer polyfill.并且还必须卸载 meteor-mode-stubs 并重新安装它以获得不依赖 Buffer polyfill 的最新版本。

Here's my code.这是我的代码。

Front end:前端:

myterminal.html我的终端.html

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

myterminal.js我的终端.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服务器/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.请注意,我是从一台通过公钥访问 ssh 的机器连接到远程服务器。 You may need different credentials depending on your setup.根据您的设置,您可能需要不同的凭据。 The environment variables are loaded from a file at Meteor runtime.环境变量从 Meteor 运行时的文件中加载。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM