Постоянно запускать команды SSH в child_process

Я пытаюсь запустить удаленный сеанс 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_processspawn в постоянном сеансе и запускать команды асинхронно одну за другой?

Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
71
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Примечание. Это не идеально, и могут возникнуть проблемы с большими выходными данными, например. передача файлов или что-то подобное из-за обработки потоков/буферов узлов. Но он должен выполнять эту работу для большинства сценариев, например. перезапуск службы или выполнение некоторых команд.

Еще я хочу отметить, что мне не удалось заставить его работать с аутентификацией по паролю. В настоящее время это работает только с аутентификацией с открытым ключом.

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

Другие вопросы по теме