Using Deno to interact through SSH hangs on p.output() - ssh

I'm looking to create an SSH sub-process and then interact with the server. I'm hung up on a basic step which is to simply wait until the SSH process has connected. I know that this ssh command connects fine because when I run it with inherit instead of piped, the ssh shell shows up as expected.
If I understand correctly, p.output() listens for stdout until it reaches EOF. I'm assuming that when SSH has connected, it streams the stdout, but does not EOF, and so p.output() never gets called.
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const p=Deno.run({
cmd: ["ssh", "root#mywebsite"],
stdout: "piped",
stderr: "piped",
stdin: "piped"
});
const command = (cmd : string) => p.stdin.write(encoder.encode(cmd))
const getOutput = async () => decoder.decode(await p.output())
await p.output() // <----- Hangs here
await command("cd /home/dev/www")
await command("ls -la")
console.log(await getOutput())
await p.status()
console.log("done")

It hangs because .output will resolve once the entire process output has been read, meaning that until ssh command has finished, it will not resolve.
Also have in mind that you need to add \n at the end of each command, otherwise it'll never be triggered.
await command("cd /home\n");
await command("ls -la\n");
// if you don't finish the ssh session, .output will never resolve
await command("exit\n");
// now it will work correctly
console.log(await getOutput());
In any case if you don't want to close the session to read the output of a given command, what you need to do is use p.stdout.readable or p.stdout.read(buf) instead.
for await const(const chunk of p.stdout.readable) {
// parse chunk and do something
}

Related

Ask users to input value for npm script

I have an npm script, which is run like that:
npm run start:local -- -target.location https://192.1.1.1:8052/
The URL param is the local IP of a user.
What I would love to have is to ask users to input this value, because it's different for everybody.
Is it possible? Would be great to do it with vanila npm scripts.
Simply speaking, an npm script will run the desired command in your shell environment.
In a shell script, the arguments passed can be accessed using $N where N = Position of the argument.
Talking about your case, the command you want to run is
npm run start:local -- -target.location USER_INPUT
USER_INPUT needs to replaced with the argument that the user has passed. Assuming that the user will pass location as the first argument to the script, it can be accessed using $1.
I have created this gist to demonstrate the same.
As you can clearly see, I have defined start:local to access the first argument and then, pass it to the start script which then echoes out the passed in argument.
UPDATE:
Here is the script for ASKING a value from a user in a prompt format.
Basically, first I am asking for user input then, storing the same in a variable and passing the variable as an argument to npm start
References
Accessing Positional Arguments
Asking User Input
Use readline to get the ip value then use exec to spawn this process. This is a pure JS solution, and OS agnostic.
Example:
package.json
"scripts": {
"start": "npm run start:local -- -target.location",
"prompt": "node prompt.js"
},
prompt.js
const { spawn, execSync } = require('child_process');
const exec = commands => {
execSync(commands, { stdio: 'inherit', shell: true });
};
const spawnProcess = commands => {
spawn(commands, { stdio: 'inherit', shell: true });
};
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
rl.question('What is your current ip? example: https://192.168.1.10:9009 ', (ip) => {
console.log(`Starting server on: ${ip}`);
exec(`npm run start -- ${ip}`);
rl.close();
});
Example: If we want to run below three commands in sequence with userinput
git add .
git commit -m "With git commit message at run time"
git push
Add below command in your package.json file under the scripts
"gitPush": "git add . && echo 'Enter Commit Message' && read message && git commit -m \"$message\" && git push"
And run commands via npm run gitPush
References: ask-users-to-input-value-for-npm-script
Use Node's readline? it has methods for interactive IO.
If you're trying to ask for users to input ther response you could do something like this.
const readline = require("readline");
const reader = readline.createInterface({
input: process.stdin,
output: process.stdout,
error: process.stderr
});
const ask = (message, default_value = null) => new Promise(resolve => {
reader.question(message, (response)=>{
return resolve(response.length >= 1 ? response : default_value);
});
});
(()=>{
let ip = await ask(`Please enter your public ip: `);
})();

Error: Timed out while waiting for handshake

I am using scp2 to copy a file to targetPath. config contains host, username, privateKey, path and port.
const client = require('scp2');
export function scpAsync(config, targetPath) {
return new Promise((resolve, reject) => {
client.scp(config, targetPath, err => {
if (!err){
resolve();
} else {
const errorMessage = err;
reject(errorMessage);
}
});
});
}
When doing so I am getting the error:
Error: Timed out while waiting for handshake
I tried to pass also
promptForPass: false
but it did not change anything. Besides that I used debug mode which told me that I am connected to the server and I put a higher setTimeout but then the error is just coming later. I was checking the documentation of scp2 and their GitHub. I use the function like explained there (https://www.npmjs.com/package/scp2) and regarding the error they could fix it with an higher setTimeout (https://github.com/spmjs/node-scp2/issues/107). I tried with a local ftp server, ngrok and ftp on ec2 instance. All with the same problem.
I would be happy to get help. I asked this question also on superuser but did not get an answer:
https://superuser.com/questions/1576964/error-timed-out-while-waiting-for-handshake

Gulp task to SSH and then mysqldump

So I've got this scenario where I have separate Web server and MySQL server, and I can only connect to the MySQL server from the web server.
So basically everytime I have to go like:
step 1: 'ssh -i ~/somecert.pem ubuntu#1.2.3.4'
step 2: 'mysqldump -u root -p'password' -h 6.7.8.9 database_name > output.sql'
I'm new to gulp and my aim was to create a task that could automate all this, so running one gulp task would automatically deliver me the SQL file.
This would make the developer life a lot easier since it would just take a command to download the latest db dump.
This is where I got so far (gulpfile.js):
////////////////////////////////////////////////////////////////////
// Run: 'gulp download-db' to get latest SQL dump from production //
// File will be put under the 'dumps' folder //
////////////////////////////////////////////////////////////////////
// Load stuff
'use strict'
var gulp = require('gulp')
var GulpSSH = require('gulp-ssh')
var fs = require('fs');
// Function to get home path
function getUserHome() {
return process.env.HOME || process.env.USERPROFILE;
}
var homepath = getUserHome();
///////////////////////////////////////
// SETTINGS (change if needed) //
///////////////////////////////////////
var config = {
// SSH connection
host: '1.2.3.4',
port: 22,
username: 'ubuntu',
//password: '1337p4ssw0rd', // Uncomment if needed
privateKey: fs.readFileSync( homepath + '/certs/somecert.pem'), // Uncomment if needed
// MySQL connection
db_host: 'localhost',
db_name: 'clients_db',
db_username: 'root',
db_password: 'dbp4ssw0rd',
}
////////////////////////////////////////////////
// Core script, don't need to touch from here //
////////////////////////////////////////////////
// Set up SSH connector
var gulpSSH = new GulpSSH({
ignoreErrors: true,
sshConfig: config
})
// Run the mysqldump
gulp.task('download-db', function(){
return gulpSSH
// runs the mysql dump
.exec(['mysqldump -u '+config.db_username+' -p\''+config.db_password+'\' -h '+config.db_host+' '+config.db_name+''], {filePath: 'dump.sql'})
// pipes output into local folder
.pipe(gulp.dest('dumps'))
})
// Run search/replace "optional"
SSH into the web server runs fine, but I have an issue when trying to get the mysqldump, I'm getting this message:
events.js:85
throw er; // Unhandled 'error' event
^
Error: Warning:
If I try the same mysqldump command manually from the server SSH, I get:
Warning: mysqldump: unknown variable 'loose-local-infile=1'
Followed by the correct mylsql dump info.
So I think this warning message is messing up my script, I would like to ignore warnings in cases like this, but don't know how to do it or if it's possible.
Also I read that using the password directly in the command line is not really good practice.
Ideally, I would like to have all the config vars loaded from another file, but this is my first gulp task and not really familiar with how I would do that.
Can someone with experience in Gulp orient me towards a good way of getting this thing done? Or do you think I shouldn't be using Gulp for this at all?
Thanks!
As I suspected, that warning message was preventing the gulp task from finalizing, I got rid of it by commenting the: loose-local-infile=1 From /etc/mysql/my.cnf

How to let Gulp wait on SSH before proceeding

I'm trying to delete files on a server with SSH, before copying new versions with SCP. The filenames include a hash for cache busting, so the old files have to go first.
But it seems that the transfer task doesn't wait for the SSH in clean-remote to finish first.
Is there a way to wait for the SSH session to complete before proceeding?
var gulp = require('gulp');
var scp = require('gulp-scp');
var ssh = require('gulp-ssh');
gulp.task('transfer', ['clean-remote'], function () {
return gulp.src('...')
.pipe(scp({
host: '...',
path: '...'
})
);
})
gulp.task('clean-remote', function () {
ssh.exec({
command: ['cd ...', 'rm *.js', 'exit'],
sshConfig: {
host: '...'
}
})
});

Socket Hang Up when using https.request in node.js

When using https.request with node.js v04.7, I get the following error:
Error: socket hang up
at CleartextStream.<anonymous> (http.js:1272:45)
at CleartextStream.emit (events.js:61:17)
at Array.<anonymous> (tls.js:617:22)
at EventEmitter._tickCallback (node.js:126:26)
Simplified code that will generate the error:
var https = require('https')
, fs = require('fs')
var options = {
host: 'localhost'
, port: 8000
, key: fs.readFileSync('../../test-key.pem')
, cert: fs.readFileSync('../../test-cert.pem')
}
// Set up server and start listening
https.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'})
res.end('success')
}).listen(options.port, options.host)
// Wait a second to let the server start up
setTimeout(function() {
var clientRequest = https.request(options, function(res) {
res.on('data', function (chunk) {
console.log('Called')
})
})
clientRequest.write('')
clientRequest.end()
}, 1000)
I get the error even with the server and client running on different node instances and have tested with port 8000, 3000, and 443 and with and without the SSL certificates. I do have libssl and libssl-dev on my Ubuntu machine.
Any ideas on what could be the cause?
In
https.createServer(function (req, res) {
you are missing options when you create the server, should be:
https.createServer(options, function (req, res) {
with your key and cert inside
I had a very similar problem where the response's end event never fired.
Adding this line fixed the problem:
// Hack to emit end on close because of a core bug that never fires end
response.on('close', function () {response.emit('end')});
I found an example of this in the request library mentioned in the previous answer.
Short answer: Use the the latest source code instead of the one you have. Store it where you will and then require it, you are good to go.
In the request 1.2.0 source code, main.js line 76, I see
http.createClient(options.uri.port, options.uri.hostname, options.uri.protocol === 'https:');
Looking at the http.js source code, I see
exports.createClient = function(port, host) {
var c = new Client();
c.port = port;
c.host = host;
return c;
};
It is requesting with 3 params but the actual function only has 2. The functionality is replaced with a separate module for https.
Looking at the latest main.js source code, I see dramatic changes. The most important is the addition of require('https').
It appears that request has been fixed but never re-released. Fortunately, the fix seems to work if you just copy manually from the raw view of the latest main.js source code and use it instead.
I had a similar problem and i think i got a fix. but then I have another socket problem.
See my solution here: http://groups.google.com/group/nodejs/browse_thread/thread/9189df2597aa199e/b83b16c08a051706?lnk=gst&q=hang+up#b83b16c08a051706
key point: use 0.4.8, http.request instead of http.createClient.
However, the new problem is, if I let the program running for long time, (I actually left the program running but no activity during weekend), then I will get socket hang up error when I send a request to http Server. (not even reach the http.request). I don't know if it is because of my code, or it is different problem with http Server