README
Mugo
Mugo makes scripting in node.js easy and intuitive. It provides a clear API for running commands locally as well as remotely over SSH, and includes helper functions to help with common situations. It keeps the simple cases simple, while still supporting more complex uses without a heavy syntactic burden.
Mugo makes use of the new async/await
features of Javascript, and so requires
at least Node 8.
Examples
Local
The standard run
and sudo
functions return promises, so for simple cases you
can just do the following:
const { run } = require('mugo');
run('whoami').then(me => {
console.log(`Hello, ${me}!`);
})
If you want to run a series of commands and do things with the results in between,
you should use the async
/await
features.
const { run, sudo } = require('mugo');
async function main() {
let me = await run('whoami');
console.log(`Hello, local ${me}!`);
let my_info = await sudo(`grep ${me} /etc/shadow`);
console.log('My shadow entry:');
console.log(my_info);
}
main();
In addition to using the run
and sudo
functions right from the module, you
can make a new local instance to use.
const mugo = require('mugo');
let local = mugo.local({/* default options */});
local.run('whoami');
You can also change the options for the default functions, or for your local instance,
using the defaults
method.
const mugo = require('mugo');
mugo.defaults({/* new default options */});
let local = mugo.local({/* default options */});
local.defaults({/* new default options */});
Any option you provide can be overridden for a single run
or sudo
.
const { run } = require('mugo');
run('cat fstab', { cwd: '/etc' });
By default, using sudo
will prompt for your password as it would on the command
line. You can also provide a password for sudo to use. If you provide a password
and the save_pass
option, it will remember that password for subsequent sudo calls.
const { sudo } = require('mugo');
// only for this call
sudo('whoami', { sudo_pass: 'supersecret' });
// for this and all subsequent calls
sudo('ls -l /root', { sudo_pass: 'supersecret', save_pass: true });
If you pass a sudo password into the default options, it will be used for every
sudo call. This can be done up front when creating a new local
, or after the
fact using the defaults
method.
const mugo = require('mugo');
let local = mugo.local({ sudo_pass: 'supersecret' })
local.sudo('whoami') // won't prompt for password
let { sudo, defaults } = mugo;
defaults({ sudo_pass: 'supersecret' });
sudo('whoami'); // won't prompt for password
There are also local sync
methods, which have the same interface but return
the actual results of the commands, rather than a promise.
const {run_sync, sudo_sync} = require('mugo');
let me = run_sync('whoami');
console.log(`Hello, local ${me}!`);
let my_info = sudo_sync(`grep ${me} /etc/shadow`);
console.log('My shadow entry:');
console.log(my_info);
It also supports passing in streams for stdin, stdout, and/or stderr. You can also provide a string for stdin. You can read from the stdout and stderr streams and write to the stdin stream to have asynchronous communication with a running command.
const { run } = require('mugo');
let stdin = new Readable();
let stdout = new Writable();
let stderr = new Writable();
let out = run('something-interactive', {stdin, stdout, stderr});
stdout.on('data', output => {
if(/*something about 'output' is true*/){
stdin.write('some input');
}
});
await out;
Remote
The remote api mostly mirrors the local api, although there are no sync
functions,
and to do remote calls, you need to create a remote instance first. The simplest case
just requires a uri and a password.
let r = require('mugo').remote({
uri: 'me@example.com',
pass: mugo.prompt_pass_sync(),
});
r.run('whoami').then(me => {
console.log(`Hello, far-away ${me}!`);)
r.end(); // must call end or node will hang forever
});
You can also use async
/await
.
let r = require('mugo').remote({
uri: 'me@example.com',
pass: mugo.prompt_pass_sync(),
});
async function main() {
let me = await r.run('whoami');
console.log(`Hello, far-away ${me}!`);
let my_info = await r.sudo(`grep ${me} /etc/shadow`);
console.log('My far-away shadow entry:');
console.log(my_info);
r.end();
}
main();
Once you have a remote instance, you can change its default settings with the
defaults
method, but you can't change any of the SSL settings. You can also
override settings on a per-call basis, just like with the local commands.
r.defaults({/* new default options */});
r.run('some-command', {/* different options */});
The remote interface also allows you to do FTP-style put
and get
. These
methods only take individual files. Passing in a folder for either the local or
remote side will result in an error. Also note that the files should be passed
in as absolute paths. If a logger is available on the remote or provided to the
functions, it will be used to log the file transfer.
let r = require('mugo').remote({
uri: 'me@example.com',
pass: 'secret password',
});
await r.put('/local/file', '/remote/file', {/* options */});
await r.get('/remote/file', '/local/file', {/* options */});
Helper Functions
There are some helper functions available to make scripting easier.
Password Prompting
You can use the following functions to prompt for passwords
const mugo = require('mugo');
let password = mugo.prompt_pass_sync();
let r = mugo.remote({ uri: 'me@example.com', pass: password });
async function main() {
let new_password = await mugo.prompt_pass(); // async
await r.sudo(`change_password ${new_password}`);
}
main();
Handling Uncaught Exceptions
There is a function you can run that will set up a default exception handler so that even if your script crashes for unexpected reasons, you are still able to see the stack trace and respond to the issue.
const mugo = require('mugo');
mugo.setup_uncaught_exception_handler();
// do more stuff
Options
A number of options are available, any of which can be passed to an individual function call, to the defaults function, or to the constructor.
Local
Option | Used By | Description | Default Value |
---|---|---|---|
sudo_user |
sudo |
The user to run the command as (sudo -u user ) |
undefined |
sudo_prompt |
sudo |
A string to use for the sudo prompt (sudo -p prompt ) |
undefined |
sudo_pass |
sudo |
The password to provide to sudo over stdin (sudo -S ) |
undefined |
save_pass |
sudo |
Save the provided sudo password for later sudo commands | false |
cwd |
run , sudo |
The working directory from which to run commands | undefined |
encoding |
run , sudo |
The encoding to use when getting the output from the command | 'utf8' |
warn_only |
run , sudo |
Don't throw an exception if the command fails | false |
trim |
run , sudo |
Remove the trailing newline from the output | true |
flatten |
run , sudo |
Remove newlines and surrounding whitespace from cmd (for prettier printing) | false |
return_stderr_also |
run , sudo |
Return stderr along with stdout | false |
stdin |
run , sudo |
The stream or string to use for stdin. | undefined |
stdout |
run , sudo |
The stream to use for stdout. | undefined |
stderr |
run , sudo |
The stream to use for stderr. | undefined |
shell |
run , sudo |
Shell to use when running command | '/bin/bash' |
shell_wrap |
run , sudo |
Wrap cmd in shell explicitly | false |
logger |
run , sudo |
The logger object to use | new ConsoleLogger() |
Remote
For remote connections, you must provide at minimum a user, host, and password or private key. You can provide that information through the uri, the individual options, or any combination thereof.
If you provide an SSH password but not a sudo password, it will use the SSH password for sudo.
Option | Used By | Description | Default Value |
---|---|---|---|
uri |
On construction only | URI to use for SSH connection (will be overridden by explicit options below) | undefined |
host |
On construction only | Hostname to connect to | undefined |
port |
On construction only | Port to connect on | 22 |
user |
On construction only | Username to connect as | undefined |
pass |
On construction only | Password to connect with | undefined |
private_key |
On construction only | Private Key to connect using | undefined |
sudo_user |
sudo |
The user to run the command as (sudo -u user ) |
undefined |
sudo_prompt |
sudo |
A string to use for the sudo prompt (sudo -p prompt ) |
undefined |
sudo_pass |
sudo |
The password to provide to sudo over stdin (sudo -S ) |
undefined |
save_pass |
sudo |
Save the provided sudo password for later sudo commands | false |
encoding |
run , sudo |
The encoding to use when getting the output from the command | 'utf8' |
warn_only |
run , sudo |
Don't throw an exception if the command fails | false |
trim |
run , sudo |
Remove the trailing newline from the output | true |
flatten |
run , sudo |
Remove newlines and surrounding whitespace from cmd (for prettier printing) | false |
return_stderr_also |
run , sudo |
Return stderr along with stdout | false |
stdin |
run , sudo |
The stream or string to use for stdin. | undefined |
stdout |
run , sudo |
The stream to use for stdout. | undefined |
stderr |
run , sudo |
The stream to use for stderr. | undefined |
shell |
run , sudo |
Shell to use when running command | '/bin/bash' |
shell_wrap |
run , sudo |
Wrap cmd in shell explicitly | false |
logger |
run , sudo , get , put |
The logger object to use | new ConsoleLogger() |
mode |
get , put |
The mode to set on the destination file. Doesn't work if the file is empty. | undefined |
Errors
If any of the runner functions produces an error, it will have the following fields available:
Field | Description |
---|---|
err.exit_code | The exit code of the command itself |
err.stdout | The standard output text produced by the command |
err.stderr | The standard error text produced by the command |
err.message / err.toString() | Some kind of human readable error message |
Loggers
TODO flesh out logger documentation
class Logger {
constructor() {
}
on_start(cmd, info) {
}
on_out(stdout, info) {
}
on_err(stderr, info) {
}
on_end(exit_code, info) {
}
}
module.exports = {
ConsoleLogger,
};