15

I am trying to do some work on a remote server using ssh--and ssh is called on the local machine from node.js

A stripped down version of the script looks like this:

var execSync = require("child_process").execSync;
var command =
  'ssh -qt user@remote.machine -- "sudo mv ./this.thing /to/here/;"';
execSync(command,callback);

function callback(error,stdout,stderr) {

  if (error) {
    console.log(stderr);
    throw new Error(error,error.stack);
  }
  console.log(stdout);
}

I get the requiretty error sudo: sorry, you must have a tty to run sudo.

If I run ssh -qt user@remote.machine -- "sudo mv ./this.thing /to/here/;" directly from the command line--in other words, directly from a tty--I get no error, and this.thing moves /to/there/ just fine.

This is part of a deploy script where it really wouldn't be ideal to add !requiretty to the sudoers file.

Is there anyway to get node.js to run a command in a tty?

4 Answers4

19

There's a few options:

  • If you don't mind re-using stdin/stdout/stderr of the parent process (assuming it has access to a real tty) for your child process, you can use stdio: 'inherit' (or only inherit individual streams if you want) in your spawn() options.

  • Create and use a pseudo-tty via the pty module. This allows you to create a "fake" tty that allows you to run programs like sudo without actually hooking them up to a real tty. The benefit to this is that you can programmatically control/access stdin/stdout/stderr.

  • Use an ssh module like ssh2 that doesn't involve child processes at all (and has greater flexibility). With ssh2 you can simply pass the pty: true option to exec() and sudo will work just fine.

mscdex
  • 104,356
  • 15
  • 192
  • 153
  • 2
    Trying the first bullet point, running `node script.js` with `stdio: "inherit"` and `spawn` instead of `exec` leads to `null` for stdio on the spawned process--but `process.stdin.isTTY` in `script.js` returns `true`. The `mv` command _is_ run successfully on the other machine, though. –  Aug 07 '15 at 00:29
  • 3
    So, in other words, the first bullet point works, but doesn't give the developer of `script.js` access to the stdio of the remote process. –  Aug 07 '15 at 00:44
  • 1
    Most of the pseudo-tty packages are native modules which could be a hassle to install in different platforms. Is there any way to use spawn() with stdio: 'inherit' and still able to control the child process problematically? – Raathigesh Apr 04 '18 at 01:39
2
ssh -qt user@remote.machine -- "sudo mv ./this.thing /to/here/;"

Per the ssh man page:

-t
Force pseudo-terminal allocation. This can be used to execute arbitrary screen-based programs on a remote machine, which can be very useful, e.g. when implementing menu services. Multiple -t options force tty allocation, even if ssh has no local tty.

When you run ssh interactively, it has a local tty, so the "-t" option causes it to allocate a remote tty. But when you run ssh within this script, it doesn't have a local tty. The single "-t" causes it to skip allocating a remote tty. Specify "-t" twice, and it should allocate a remote tty:

ssh -qtt user@remote.machine -- "sudo mv ./this.thing /to/here/;"
      ^^-- Note
Kenster
  • 23,465
  • 21
  • 80
  • 106
1

Some programs must be running tty. "child_process" library working bad when tty inputs. I have tried for docker ( docker run command like ssh need tty), with microsoft node-pty library https://github.com/microsoft/node-pty

And this is worked me.

let command = "ssh"
let args = '-qt user@remote.machine -- "sudo mv ./this.thing /to/here/;"'
const pty = require("node-pty");
let ssh = pty.spawn( command , args.split(" ") )
ssh.on('data', (e)=>console.log(e));
ssh.write("any_input_on_runngin_this_program");
Hasan Delibaş
  • 470
  • 3
  • 14
1

It seems that you can use FORCE_COLOR environment variable:

spawn('node', options, {
    stdio: 'pipe',
    cwd: process.cwd(),
    env: {
      ...{ FORCE_COLOR: 1 },
      ...process.env
    }
})