Spawning a child process with tty in node.js
Asked Answered
D

5

17

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 [email protected] -- "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 [email protected] -- "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?

Devious answered 6/8, 2015 at 21:35 Comment(0)
W
20

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.

Whiffen answered 6/8, 2015 at 21:55 Comment(3)
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.Devious
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.Devious
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?Eliathan
F
3
ssh -qt [email protected] -- "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 [email protected] -- "sudo mv ./this.thing /to/here/;"
      ^^-- Note
Fence answered 7/8, 2015 at 10:33 Comment(0)
L
2

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 [email protected] -- "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");
Liebowitz answered 24/8, 2021 at 14:28 Comment(0)
M
2

It seems that you can use FORCE_COLOR environment variable:

spawn('node', options, {
    stdio: 'pipe',
    cwd: process.cwd(),
    env: {
      ...{ FORCE_COLOR: 1 },
      ...process.env
    }
})
Martini answered 25/7, 2022 at 10:26 Comment(0)
B
0

I can use stdio: 'inherit' via child_process.spawn, but the stdout, stderr is null. And If I use node-pty, I need to use ptyProcess.on('exit', () => { process.exit(0) }); to exit the process.

import pty from "node-pty";
import child_process from "child_process"

let program = process.argv[2]
let args = process.argv.slice(3, process.argv.length)
console.info(`spawn ${program} with args: ${JSON.stringify(args)}`)

function testPty() {
    let ptyProcess = pty.spawn(program, args)
    ptyProcess.on('data', (e) => console.log(e));
    ptyProcess.on('exit', () => { process.exit(0) });
}

function testChildProcess() {
    let childProcess = child_process.spawn(program, args, { stdio: 'inherit' })
    // childProcess.stdout?.on('data', (e) => console.log(e));
    // childProcess.stderr?.on('data', (e) => console.error(e));
    // childProcess.on('close', () => { console.info('close'); process.exit(0) });
}

// testPty();
testChildProcess()
Breannabreanne answered 17/6 at 2:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.