如何在 Meteor 应用程序中使用 Node JS 创建交互式 ssh 终端并从浏览器输入命令
how to create interactive ssh terminal and enter commands from the browser using Node JS in a Meteor app
我正在尝试创建一个网页,用户可以在其中使用 username/password 通过 ssh 向远程服务器进行身份验证,然后与远程服务器进行交互。
我不打算创建一个完整的交互式终端:应用服务器将根据用户输入执行一组有限的命令,然后将响应传回浏览器。
不同的用户应该与不同的 ssh 会话交互。
我的应用程序是在 Meteor 1.8.1 中构建的,因此后端在 Node JS 9.16.0 版本下运行。它已使用 Phusion Passenger 部署到 Ubuntu。
我看过几个可以创建交互式 ssh 会话的软件包,但我缺少一些关于如何使用它们的基本知识。
例如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',然后关闭会话。它不是我正在寻找的 'interactive' 。我看不到的是如何使会话保持活动状态并发送新命令?
完整终端的 This example 对我的需求来说似乎有点矫枉过正,我不会使用 Docker.
使用 socket.io,我不确定它会如何与我的 Meteor 应用交互?我目前正在使用 Meteor 方法和发布在客户端和服务器之间传递信息,所以我希望需要一个使用 Meteor 基础结构的 "Meteor-type" 解决方案?
child_process.spawn 有效,但只会发送一个命令,它不会保持会话。
我知道其他人也问过类似的问题,但我没有找到适合我的具体情况的解决方案。感谢您的帮助。
我通过关注 and these instructions for using socket.io with Meteor.
完成了这项工作
由于包的变化,两组指令都需要更新:
meteor-node-stubs 现在使用 stream-http 而不是 http-browserify
https://github.com/meteor/node-stubs/issues/14 所以不要对套接字使用 hack
xterm 插件 (fit) 现在是单独的包
- xterm API 已更改,使用 term.onData(...) 代替 term.on('data'...)
我使用了这些软件包:
ssh2
xterm
xterm-addon-fit
socket.io
socket.io-客户端
并且还必须卸载 meteor-mode-stubs 并重新安装它以获得不依赖于 Buffer polyfill 的最新版本。
这是我的代码。
前端:
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);
});
请注意,我正在通过 public 密钥从具有 ssh 访问权限的计算机连接到远程服务器。根据您的设置,您可能需要不同的凭据。环境变量在 Meteor 运行时从文件加载。
我正在尝试创建一个网页,用户可以在其中使用 username/password 通过 ssh 向远程服务器进行身份验证,然后与远程服务器进行交互。
我不打算创建一个完整的交互式终端:应用服务器将根据用户输入执行一组有限的命令,然后将响应传回浏览器。
不同的用户应该与不同的 ssh 会话交互。
我的应用程序是在 Meteor 1.8.1 中构建的,因此后端在 Node JS 9.16.0 版本下运行。它已使用 Phusion Passenger 部署到 Ubuntu。
我看过几个可以创建交互式 ssh 会话的软件包,但我缺少一些关于如何使用它们的基本知识。
例如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',然后关闭会话。它不是我正在寻找的 'interactive' 。我看不到的是如何使会话保持活动状态并发送新命令?
完整终端的This example 对我的需求来说似乎有点矫枉过正,我不会使用 Docker.
child_process.spawn 有效,但只会发送一个命令,它不会保持会话。
我知道其他人也问过类似的问题,但我没有找到适合我的具体情况的解决方案。感谢您的帮助。
我通过关注
由于包的变化,两组指令都需要更新:
meteor-node-stubs 现在使用 stream-http 而不是 http-browserify https://github.com/meteor/node-stubs/issues/14 所以不要对套接字使用 hack
xterm 插件 (fit) 现在是单独的包
- xterm API 已更改,使用 term.onData(...) 代替 term.on('data'...)
我使用了这些软件包:
ssh2
xterm
xterm-addon-fit
socket.io
socket.io-客户端
并且还必须卸载 meteor-mode-stubs 并重新安装它以获得不依赖于 Buffer polyfill 的最新版本。
这是我的代码。
前端:
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);
});
请注意,我正在通过 public 密钥从具有 ssh 访问权限的计算机连接到远程服务器。根据您的设置,您可能需要不同的凭据。环境变量在 Meteor 运行时从文件加载。