Я пытаюсь запустить удаленный сеанс ssh на Linux-машине с помощью NodeJS. Мои требования — запускать несколько команд оболочки в одном и том же сеансе ssh одну за другой. В настоящее время я использую пакет simple-ssh npm, который работает, но в моих требованиях говорится, что я не должен полагаться на сторонние инструменты/пакеты. Вместо этого мне предлагается использовать child_process для удаленного сеанса ssh.
Ниже приведен пример использования пакета simple-ssh
:
var SSH = require("simple-ssh")
var ssh = new SSH({
host: 'remote_host',
user: 'username',
pass: 'password'
});
ssh.exec('pwd', {
out: console.info.bind(console)
})
.exec('ls', {
out: console.info.bind(console)
})
.exec('date', {
out: console.info.bind(console)
})
.start();
Ниже приведен тот, который я попробовал child_process
:
const { spawn } = require('child_process');
const child1 = spawn('sshpass', ['-p', 'password', 'ssh', '-T', '-o', 'StrictHostKeyChecking=no', `'username'@'remote_host'`]);
child1.stdin.write('password\n');
child1.stdout.on('data', (data) => {
console.info(`stdout: ${data}`);
});
child1.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child1.on('close', (code) => {
console.info(`child process exited with code ${code}`);
});
const child2 = spawn('sshpass', ['-p', 'password', 'ssh', '-T', '-o', 'StrictHostKeyChecking=no', `'username'@'remote_host'`]);
child2.stdin.write('password\n');
child2.stdout.on('data', (data) => {
console.info(`stdout: ${data}`);
});
child2.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child2.on('close', (code) => {
console.info(`child process exited with code ${code}`);
});
const child3 = spawn('sshpass', ['-p', 'password', 'ssh', '-T', '-o', 'StrictHostKeyChecking=no', `'username'@'remote_host'`]);
child3.stdin.write('password\n');
child3.stdout.on('data', (data) => {
console.info(`stdout: ${data}`);
});
child3.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child3.on('close', (code) => {
console.info(`child process exited with code ${code}`);
});
Кроме того, если я запускаю команды последовательно, а затем распечатываю выходные данные, я получаю один вывод для всех команд (чего мне не нужно):
const { spawn } = require('child_process');
const child = spawn('sshpass', ['-p', 'password', 'ssh', '-T', '-o', 'StrictHostKeyChecking=no', `'username'@'remote_host'`]);
child.stdin.write('pwd\n');
child.stdin.write('ls -al\n');
child.stdin.write('date\n');
child.stdout.on('data', (data) => {
console.info(`stdout: ${data}`);
});
child.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
child.on('close', (code) => {
console.info(`child process exited with code ${code}`);
});
Мой вопрос: как я могу связать три команды ssh с помощью child_process
spawn
в постоянном сеансе и запускать команды асинхронно одну за другой?
Примечание. Это не идеально, и могут возникнуть проблемы с большими выходными данными, например. передача файлов или что-то подобное из-за обработки потоков/буферов узлов. Но он должен выполнять эту работу для большинства сценариев, например. перезапуск службы или выполнение некоторых команд.
Еще я хочу отметить, что мне не удалось заставить его работать с аутентификацией по паролю. В настоящее время это работает только с аутентификацией с открытым ключом.
const { spawn } = require('child_process');
const { EOL } = require("os");
(async () => {
const queue = [];
let ssh = null;
function sleep(n) {
return new Promise((resolve) => {
setTimeout(resolve, n)
});
}
function init() {
return new Promise((resolve, reject) => {
// could not get it working with password
// use public key instead
let child = ssh = spawn("ssh", ["marc@localhost"]);
let message = (data) => {
// for feedback only
// the cmd response is returned from the send() function bewlow
console.info(`stdout/stderr > ${data}`);
if (queue.length === 0 ){
// init response
// motd/unread mails etc.
setTimeout(resolve, 1000);
}else{
// pop resolve from command queue
// return value should command response
// NOTE: "data" event could be fired more than once for larget outputs
// -> this would break this queue handling!
let done = queue.pop();
done(data);
}
};
child.once("spawn", () => {
console.info("ssh child command spawend");
// we do not care if writen to stdout or stderr
// handle both streams the same way
// if a command fails e.g. like the `cat /etc/groups` below
// a error messsage is writen to stderr, not stdout.
child.stderr.on("data", message);
child.stdout.on("data", message);
});
});
}
function close(){
return new Promise((resolve, reject) => {
ssh.once("exit", (code) => {
if (code === 0){
resolve();
}else{
reject(code)
}
});
ssh.stdin.write(`exit${EOL}`);
});
}
function send(cmd) {
return new Promise((resolve, reject) => {
ssh.stdin.write(`${cmd}${EOL}`, (err) => {
if (err) {
reject(err);
} else {
console.info(`# cmd "${cmd}" writen & queued - waiting for response`);
queue.unshift(resolve);
}
});
});
}
await init();
await sleep(1000);
await send("uptime")
await sleep(1000);
await send("cat /etc/groups"); // fails, becuase /etc/groups does not exists
await sleep(1000);
await send("cat /etc/group");
await sleep(1000);
await close();
})();
Программа порождает новый дочерний дочерний процесс ssh и оборачивает вспомогательные функции потоков stdin/stdout, такие как «отправить», которые отправляют команду через ssh на ваш сервер.
Ваша основная проблема заключалась в том, что вы не дождались ответа от дочернего процесса ssh, прежде чем отправить в него следующую команду.
Я решил эту проблему с помощью (очень, очень, очень) простой системы «очередей».
Опять же, этот код не «идеален» и был быстро составлен. Используйте его с осторожностью!
Что касается аутентификации по паролю, я поискал и нашел здесь пару похожих вопросов, но ни один из них мне не помог. (Например, опция -tt
для команды ssh).
Пакет ssh2 npm (на который опирается simple-ssh) реализует протокол ssh в js и использует сетевой сокет для передачи команд. Это не просто оболочка команды ssh. Хотя я мог бы скопировать оттуда это «решение для входа и пароля».
Установите для параметра «shell» значение «true», чтобы запускать команду внутри оболочки, которая по умолчанию имеет значение «false». Также вы можете просмотреть документацию по команде создания дочернего процесса node js
const ssh = spawn(
"sshpass",
[
"-p",
password,
"ssh",
"-T",
"-o",
"StrictHostKeyChecking=no",
`${username}@${host}`,
],
{ shell: true }
);