How to execute a command via SSH in Elixir? - ssh

I know I can open a ssh connection to a remote server:
:ssh.start
:ssh.connect("11.22.33.44", 22, user: "my_login123")
But how can I actually send a command and receive a response from it? I don't mean the interactive mode, I want to just send a command and receive a reply.

It might just be easier to use an Elixir library such as SSHex as this actually uses the erlang :ssh library but provides a much nicer interface as well as making it simpler to accomplish what you are after.
E.g. From the readme
{:ok, conn} = SSHEx.connect ip: '123.123.123.123', user: 'myuser'
SSHEx.cmd! conn, 'mkdir -p /path/to/newdir'
res = SSHEx.cmd! conn, 'ls /some/path'
Where the value of res will be the response from the command
EDIT
However, if you are set on using :ssh. Then you would need to use the :ssh_connection modules exec command which takes in the :ssh connection as a parameter.
See this link here for more detail on how to do this.

Here is an example that uses only :ssh and no external libraries. To run it you will need to have public key login set up on your target host. For more information, read the Erlang SSH User's Guide.
ssh-connect.exs
#! /usr/bin/env elixir
:ssh.start()
{:ok, conn} = :ssh.connect('raspi', 22,
silently_accept_hosts: true,
user: System.get_env("USER") |> to_charlist(),
user_dir: Path.join(System.user_home!(), ".ssh") |> to_charlist(),
user_interaction: false,
)
{:ok, chan} = :ssh_connection.session_channel(conn, :infinity)
:success = :ssh_connection.exec(conn, chan, 'uname -a', :infinity)
for _ <- 0..3 do
receive do
{:ssh_cm, ^conn, value} -> IO.inspect(value)
end
end
:ok = :ssh.close(conn)
Sample output
{:data, 0, 0, "Linux raspberrypi 4.4.50+ #970 Mon Feb 20 19:12:50 GMT 2017 armv6l GNU/Linux\n"}
{:eof, 0}
{:exit_status, 0, 0}
{:closed, 0}

Use SSHex library can very convenient to build SSH connection.
Here is example below:
defmodule SshDemo do
#moduledoc false
def connect do
{:ok, conn} = SSHEx.connect ip: 'xxx.xxx.xxx.xxx', user: 'root', password: 'xxxxx'
SSHEx.cmd! conn, 'mkdir -p newdir'
end
end
If you use mix to create your project. You just add dependency in mix.exs file and run-- "mix deps.get"
defp deps do
[{:sshex, "2.1.2"}]
end
Then, you may compile this module. use -- "mix deps.compile".
Run above example will make a folder named newdir in ~/ path

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: `);
})();

How to ask for a user input from a remote server with SSHKit?

I need to ask a user input from a ruby script on a remote server. I managed to perform it with bash with the following code
class ConfirmHandler
def on_data(command, stream_name, data, channel)
puts "data received: #{data}"
if data.to_s =~ /\?$/
prompt = Net::SSH::Prompt.default.start(type: 'confirm')
response = prompt.ask "Please enter your response (y/n)"
channel.send_data "#{response}\n"
end
end
end
require 'sshkit'
require 'sshkit/dsl'
include SSHKit::DSL
on '<ssh-server-name>' do |host|
cmd = <<-CMD
echo 'Do something?';
read response;
echo response=$response
CMD
capture(cmd.squish , interaction_handler: ConfirmHandler.new)
end
When I run this script on my local machine I see
data received: Do something?
Please enter your response (y/n)
data received: response=y
I try to wrap the bash CMD code into a ruby script:
on '<ssh-server-name>' do |host|
cmd = <<-CMD
ruby -e "
puts 'Do something?';
require 'open3';
response = Open3.capture3('read response; echo $response');
puts 'response=' + response.to_s;
"
CMD
capture(cmd.squish , interaction_handler: ConfirmHandler.new)
end
and get the following result:
data received: Do something?
Please enter your response (y/n)
data received: response=["\n", "", #<Process::Status: pid 9081 exit 0>]
I was writing the code above looking at the Interactive commands section on the SSHKit Github home page
How can I capture the user response from a ruby script with SSKKit on the remote server?
I was able to capture the user response from a ruby script on a remote server with the following code:
# ask_response.rb
puts 'Do something?';
response = `read response; echo $response`;
puts 'response=' + response.to_s;
ask_response.rb is a ruby script which is located on a remote server. And locally I run:
on '<ssh-server-name>' do |host|
capture("ruby ask_response.rb" , interaction_handler: ConfirmHandler.new)
end

Log doesn't print when running "mix test"

I'm trying to debug while a test is not running, I have my test and I'm trying to print something so I can see the values of a tuple when mix test is run. I've tried doing this:
require Logger
test "creates element", %{conn: conn} do
Logger.debug "debugging #{inspect conn}"
conn = post conn, v1_content_path(conn, :create), content: #valid_attrs
...
...
end
But nothing is printed! It's driving me nuts!
Here is where I read to do what I'm doing How to pretty print conn content?
Edit Also tried with:
IO.puts "debugging #{inspect conn}"
Edit Here the contents of my test_helper.exs
ExUnit.start
Mix.Task.run "ecto.create", ~w(-r TestApp.Repo --quiet)
Mix.Task.run "ecto.migrate", ~w(-r TestApp.Repo --quiet)
Ecto.Adapters.SQL.begin_test_transaction(TestApp.Repo)
Edit Here my whole testing file:
defmodule TestApp.ContentControllerTest do
require Logger
use TestApp.ConnCase
#valid_attrs %{title: "Content Title", url: "http://www.content.com"}
#invalid_attrs %{}
setup %{conn: conn} do
conn
|> put_req_header("accept", "application/json")
{:ok, conn: conn}
end
test "my first test", %{conn: conn} do
Logger.debug "debugging #{inspect conn}"
end
end
Edit Here is the detail of mix test:
$ mix test
.
Finished in 2.5 seconds (0.6s on load, 1.9s on tests)
1 tests, 0 failures
Randomized with seed 685273
compile_time_purge_level
As pointed out in some comments to your question, the compile_time_purge_level can be reduced to the :debug level for the test environment by changing the :logger config in config/test.exs.
test.exs
config :logger,
backends: [:console],
compile_time_purge_level: :debug
run tests again
mix test

How to specify custom host for connection in RabbitMQ?

How can I specify my custom host in send.exs file if my app which has receive.exs is hosted somewhere? I have one elixir app with send.exs and another app with receive.exs which is Phoenix app and is hosted.
send.exs
{:ok, connection} = AMQP.Connection.open
{:ok, channel} = AMQP.Channel.open(connection)
AMQP.Queue.declare(channel, "hello")
AMQP.Basic.publish(channel, "", "hello", msg)
IO.puts " [x] Sent everything"
AMQP.Connection.close(connection)
receive.exs
...
{:ok, connection} = AMQP.Connection.open
{:ok, channel} = AMQP.Channel.open(connection)
AMQP.Queue.declare(channel, "hello")
AMQP.Basic.consume(channel, "hello", nil, no_ack: true)
IO.puts " [*] Waiting for messages. To exit press CTRL+C, CTRL+C"
...
There are a few different forms of Connection.open.
The first, which you're using, takes no arguments, and uses some default settings (localhost, "guest" username, "guest" password, etc.).
The second takes a Keyword list of options, including host, port, virtual_host, and others. You may use Connection.open(host: your_host). Any settings which you don't provide will use the default setting.
The third form takes a well-formed RabbitMQ URI as a String, which is formed using a combination of host, port, username, password, and virtual host. Note that some of these values are optional in the URI.

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