как создать интерактивный терминал s sh и вводить команды из браузера, используя Node JS в приложении Meteor - PullRequest
0 голосов
/ 05 мая 2020

Я пытаюсь создать веб-страницу, где пользователь может аутентифицироваться на удаленном сервере через s sh с именем пользователя / паролем, а затем взаимодействовать с удаленным сервером.

Я не ищу для создания полностью интерактивного терминала: сервер приложений будет выполнять ограниченный набор команд на основе ввода пользователя, а затем передавать ответы обратно в браузер.

Разные пользователи должны взаимодействовать с разными s sh сеансами.

Мое приложение построено на Meteor 1.8.1, поэтому серверная часть работает под Node JS версии 9.16.0. Он развернут в Ubuntu с помощью Phusion Passenger.

Я просмотрел несколько пакетов, которые могут создать интерактивный сеанс s sh, но мне не хватает чего-то c о том, как их использовать.

Например https://github.com/mscdex/ssh2#start -an-interactive-shell-session

В примере показан следующий код:

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')
});

В этом примере выполняется подключение к удаленному серверу, выполняется команда 'ls', а затем закрывает сеанс. Это не «интерактивный» в том смысле, который я ищу. Чего я не вижу, так это того, как сохранить сеанс и отправить новую команду?

Этот пример готового терминала выглядит излишним для моих нужд, и я не буду используя Docker.

В этом примере используется socket.io, и я не уверен, как это будет взаимодействовать с моим приложением Meteor? В настоящее время я использую методы и публикации Meteor для передачи информации между клиентом и сервером, поэтому я ожидал, что мне понадобится решение типа Meteor, использующее инфраструктуру Meteor?

child_process.spawn работает, но будет отправлять только одна команда, она не поддерживает сеанс.

Я знаю, что другие люди задавали похожие вопросы, но я не вижу решения для моего конкретного случая. Спасибо за любую помощь.

1 Ответ

0 голосов
/ 06 мая 2020

Я получил эту работу, выполнив эти инструкции по созданию интерактивного терминала в браузере и эти инструкции по использованию socket.io с Meteor .

Оба набора инструкций потребовалось некоторое обновление из-за изменений в пакетах:

  • meteor-node-stubs теперь использует stream-http вместо http-browserify https://github.com/meteor/node-stubs/issues/14 так что не надо ' t используйте хак для socket

  • xterm addons (fit) теперь являются отдельными пакетами

  • API xterm изменился, используйте term.onData (...) вместо term.on ('data' ...)

Я использовал эти пакеты:

ssh2

xterm

xterm-addon -fit

socket.io

socket.io-client

, а также пришлось удалить заглушки режима метеора и переустановить его, чтобы получить последнюю версию, которая не полагаться на полифил Buffer.

Вот мой код.

Внешний интерфейс:

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

Обратите внимание, что я подключаюсь с машины, имеющей доступ s sh через ключ publi c на удаленный сервер. В зависимости от вашей настройки вам могут потребоваться разные учетные данные. Переменные среды загружаются из файла во время выполнения Meteor.

...