Running an SSH shell script using an npm command

I’m working on an npm script intended to run an SSH command directly from the terminal. Currently, my approach involves using an osascript to trigger Terminal to execute the command. However, I’m looking to modify this so that the command runs in the active terminal instead. I have both shelljs and executive in my script. When attempting to use executive, it exits without any actions. On the other hand, using shelljs leads to the error message:

Pseudo-terminal will not be allocated because stdin is not a terminal.
the input device is not a TTY

The command I’m trying to execute is: ssh -i [ssh-key] -t ubuntu@[ip-address] eval $(base64 -D <<< [encoded command]), where the original command is sudo docker exec -i -t $(sudo docker ps -aqf “ancestor=’ + containerName + '”) /bin/bash. When manually copying and pasting the command, it functions correctly, allowing me to SSH into a remote server and run a docker exec operation.

If I omit the -t flag, the warnings cease, but there’s no output in the console, leaving the script hanging. Conversely, if I exclude the eval … part, I receive output resembling an SSH session but with no interactive terminal.

How can I run this SSH command interactively within the same terminal or in a new tab? If using an osascript is necessary, I am open to that. Note that I will be executing the command from the terminal in PhpStorm.

Code Block:

const dockerCommand = 'sudo docker exec -i -t $(sudo docker ps -aqf "ancestor=nginx") /bin/bash';
const encodedCommand = Buffer.from(dockerCommand).toString('base64');
const sshCommand = `ssh -i ${this.keyPath} -t ubuntu@${ip} eval $(base64 -D <<< ${encodedCommand})`;

shell.exec(sshCommand);

Update:

I can now successfully SSH into the server, but I encounter a the input device is not a TTY error upon adding the eval command.

const dockerExecCommand = 'sudo docker exec -it $(sudo docker ps -aqf "ancestor=' + containerName + '") /bin/bash';
const encodedDockerExec = Buffer.from(dockerExecCommand).toString('base64');

const sshProcess = spawn('ssh', [
    '-i',
    this.keyPath,
    'ubuntu@' + ip,
    'eval',
    'eval $(base64 -D < {
    process.exit(0);
});

You’ll want to utilize the -tt flag in your SSH command to ensure it allocates a pseudo-terminal correctly for interactive use. By avoiding the base64 encoding and making the SSH call directly in your script, you streamline the process. Here’s how you can structure your script:

const { spawn } = require('child_process');

const dockerCommand = `sudo docker exec -it $(sudo docker ps -aqf "ancestor=${containerName}") /bin/bash`;
const sshProcess = spawn('ssh', [
  '-i', this.keyPath,
  '-tt',
  `ubuntu@${ip}`,
  dockerCommand
], {
  stdio: 'inherit',
  shell: true
});

sshProcess.on('exit', () => {
  process.exit(0);
});

This should allow the command to run interactively in your terminal.

Hello ExcitedGamer85,

To address your issue with running the SSH command interactively, you can indeed use the -tt flag as suggested by AdventurousHiker17. Additionally, it is crucial to bypass the encoding to avoid unnecessary complications. This adjustment should allow you to run your commands smoothly in an interactive shell. Here's a practical solution:

Code Block:

const { spawn } = require('child_process');

// Construct the Docker command without encoding
const dockerCommand = sudo docker exec -it $(sudo docker ps -aqf "ancestor=${containerName}") /bin/bash;

// Setup the SSH command with ‘-tt’ to force a pseudo-terminal
const sshProcess = spawn(‘ssh’, [
‘-i’, this.keyPath,
‘-tt’,
ubuntu@${ip},
dockerCommand
], {
stdio: ‘inherit’,
shell: true
});

sshProcess.on(‘exit’, () => {
process.exit(0);
});

This solution should improve your workflow by allowing your SSH session to allocate a pseudo-terminal correctly. Remember, using -tt is essential when you require interactive features or TTY allocation.

Let me know if this solves your issue or if there’s anything else I can assist with.

To address the interactive SSH command execution, it seems you’re heading in the right direction but might be encountering issues with TTY allocation and script complexity. You’ve already received excellent advice on using the -tt SSH flag to enforce pseudo-terminal allocation. Let’s explore a slightly different approach and context.

When using Node.js to handle SSH commands for interactive sessions, it’s crucial to ensure that the child process has proper access to the terminal’s input/output streams. If your development environment allows it, another method to consider is leveraging the pty.js library, which can manage TTYs more efficiently than Node.js’s standard libraries.

Here is how you can achieve it using pty.js:

const pty = require('node-pty');

// Prepare the command
const dockerCommand = `sudo docker exec -it $(sudo docker ps -aqf "ancestor=${containerName}") /bin/bash`;

// Initialize the pseudo-terminal using pty.js
const sshProcess = pty.spawn('ssh', [
    '-i', this.keyPath,
    '-tt',
    `ubuntu@${ip}`,
    dockerCommand
], {
    cols: process.stdout.columns,
    rows: process.stdout.rows,
    cwd: process.env.HOME,
    env: process.env
});

// Bind the process I/O
sshProcess.onData(data => process.stdout.write(data));
process.stdin.on('data', data => sshProcess.write(data));

// Handle exit
sshProcess.on('exit', (code) => {
    process.exit(code);
});

Explanation:

  • pty.spawn creates a pseudo-terminal, effectively connecting the input/output streams, which is crucial for interactive sessions.
  • Use the -tt flag to force TTY allocation, enabling interactive operations inside your executed command.
  • This setup replicates terminal behavior more closely than using child_process.spawn, offering a seamless experience when executing interactive commands like ssh with docker exec.

Applying these suggestions, your scripts should work fluidly, allowing interactive SSH command execution within your development workflow.