ssh_tunnel_proxy 1.2.10 → 1.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,111 +1,107 @@
1
1
  # ssh_tunnel_proxy
2
2
  Initiate a ssh reverse tunnel proxy with forwarding ports
3
3
 
4
- ssh_tunnel_proxy can function as a stand-alone api, nodejs command line script or as part of an electron_js app. To setup a ssh tunnel, parameters are suppled for host, port, authentication and a list of proxy ports. If a ngrok api key is provided the host and port of the ngrok tunnel are obtained. A list of port forwards is provided and is validated to restrict connections to system ports on the remote host to a list of pre-defined ports such as http or https. After establishing a ssh connection on the remote server, local proxy port forwards are opened. If the connection is interrupted or connection errors occur, attempts are made re-establish the tunnel.
4
+ ssh_tunnel_proxy is a wrapper to ssh2 that provides async functionality as well as an extension to the api to include methods to setup a list of proxy forwards, exec a list of commands or startup a terminal shell. To setup a ssh tunnel, parameters are suppled for host, port, authentication and a list of proxy ports or commands to invoke. If a ngrok api key is provided the host and port of the ngrok tunnel are obtained. If the connection is interrupted or connection errors occur, attempts are made re-establish the tunnel.
5
5
 
6
- Example command line to establish a list of local forwards:
6
+ In addition to the node api, a command line function called ssh-node2 is included to start ssh sessions in a manner similar to the ssh command line utility.
7
+
8
+ ### Command line examples
9
+
10
+ Connect to remote host and establish local forwards:
11
+
12
+ ```
13
+ ./ssh2-node -u=<username> -h=192.168.1.1 -k=~/.ssh/<private_key> -L=8180:192.168.1.1:80
14
+ ```
15
+
16
+ Connect to host using parameters stored in ~/.config/ssh_tunnel_proxy/config.json:
7
17
 
8
18
  ```
9
- node main.js -c
19
+ ./ssh2-node rh2
10
20
  ```
11
21
 
12
22
  default config file, located at:
13
23
  ~/.config/ssh_tunnel_proxy/config.json
14
24
  ```json
15
25
  [{
16
- "enabled": true,
26
+ "hostname":"rh2",
17
27
  "username": "<username>",
18
- "password": "",
19
- "host": "",
20
- "port": "",
21
28
  "proxy_ports": [
22
29
  "8280:127.0.0.1:80",
23
30
  "9000:127.0.0.1:9000",
24
31
  "8122:192.168.2.1:22"
25
32
  ],
26
- "whitelist": {
27
- "80": true,
28
- "443": true,
29
- "22": true
30
- },
31
- "service_name": "ssh_proxy_client",
32
- "server_name": "test",
33
+ "private_key_filename":"~/.ssh/<private key>",
33
34
  "ngrok_api": "<ngrok api key>"
34
35
  }]
35
36
  ```
36
37
 
37
- Command line to execute a series of commands on remote host:
38
- ```
39
- node main.js -c -e='uptime' -e='ls -all'
40
- ```
41
-
42
- Or execute commands defined in default config:
38
+ Execute a series of commands on remote host
43
39
  ```
44
- node main.js -c
45
- ```
46
- ```json
47
- [{
48
- "enabled": true,
49
- "username": "<username>",
50
- "service_name": "ssh_proxy_client",
51
- "server_name": "test",
52
- "exec" : [
53
- "uptime",
54
- "ls -all"
55
- ],
56
- "ngrok_api": "<ngrok api key>"
57
- }]
40
+ ./ssh-node2 rh2 -e='uptime' -e='ls -all'
58
41
  ```
59
42
 
60
- Start a terminal session on the remote host:
61
- ```
62
- node main.js -c -S
63
- ```
43
+ ### Api examples
64
44
 
65
- Example use of api to exec remote commands using async await and processing result through streams. Complete example is in test/test_remote_exec.js:
45
+ Exec remote commands using async await and processing result through streams.
66
46
 
67
47
  ```js
68
- // lsLongShellProc to json stream test
69
- async function lsTest(sshTunnelProxy) {
70
- return new Promise(async (resolve) => {
71
-
72
- // exec remote command and pipe data through tunnel until end of data
73
- const tunnel = new PassThrough();
74
- pipeline(tunnel,
75
- split(),
76
- to_lsParse(),
77
- to_JSONString(),
78
- process.stdout,
79
- () => { }
80
- );
81
-
82
- const lscmd = 'ls -all';
83
- console.log('\ninvoking ' + lscmd + ' on remote host:\n');
84
- await sshTunnelProxy.execCmd(lscmd, tunnel);
85
-
86
- // stream processing complete
87
- console.log('ls -all completed');
88
- resolve();
48
+ // send result of cmd through pipeline, generating a stream of json objects
49
+ function lsTest(cmdProxy, cmd, destination) {
50
+
51
+ return new Promise( (resolveCmd) => {
52
+
53
+ // set input of pipeline to split data into lines (npm i split)
54
+ const tunnel = split();
55
+
56
+ // when pipeline is ready exec shell cmd
57
+ const pipelineReady = (socket) => {
58
+
59
+ return new Promise((resolve) => {
60
+
61
+ // invoke command on remote host and send results to pipeline
62
+ cmdProxy.execCmd(cmd, tunnel)
63
+
64
+ // stream processing complete, cleanup pipeline and exit
65
+ .then(() => {
66
+ //self.cleanupPipeline(socket);
67
+ resolveCmd();
68
+ });
69
+ resolve();
70
+ })
71
+ }
72
+
73
+ // pipe shell cmd result through json parser pipeline to destination
74
+ pipeline(tunnel,
75
+ self.parse(),
76
+ self.toJSONString(),
77
+ destination,
78
+ pipelineReady
79
+ );
89
80
  })
90
81
  }
91
82
 
92
- async function runTests() {
83
+ async function runCmd() {
93
84
 
94
- const sshTunnelProxy = new SSHTunnelProxy();
85
+ const opts = {
86
+ "hostname":"rh2",
87
+ "username": "<username>",
88
+ "private_key_filename":"~/.ssh/<private key>",
89
+ "ngrok_api": "<ngrok api key>"
90
+ }
95
91
 
96
- // connect to remote host
97
- await sshTunnelProxy.connectSSH(opts);
92
+ const sshTunnelProxy = new SSHTunnelProxy();
98
93
 
99
- // invoke ls -all on remote host and parse result to json object string
100
- await lsTest(sshTunnelProxy);
94
+ // connect to remote host
95
+ await sshTunnelProxy.connectSSH(opts);
101
96
 
102
- process.exit();
97
+ // invoke ls -all on remote host and parse result to json object string
98
+ await lsTest(sshTunnelProxy, 'ls -all', process.stdout);
103
99
  }
104
100
 
105
- runTests();
101
+ runCmd();
106
102
  ```
107
103
 
108
- The following code is an example of use of the api with electronjs.
104
+ Example of use of the api with electronjs.
109
105
 
110
106
  main.js:
111
107
  ```js
package/lib/index.js CHANGED
@@ -17,6 +17,7 @@ License: MIT
17
17
  */
18
18
 
19
19
  const net = require('net');
20
+ const fs = require('fs');
20
21
  const process = require('process');
21
22
  const { Buffer } = require('node:buffer');
22
23
  const { Client } = require('electron-ssh2');
@@ -26,6 +27,8 @@ const NgrokApi = require('./ngrok_service');
26
27
 
27
28
  const keypairStorage = new KeypairStorage();
28
29
 
30
+ const homedir = require('os').homedir();
31
+
29
32
  class SSHTunnelProxy extends Client {
30
33
 
31
34
  constructor() {
@@ -38,15 +41,16 @@ class SSHTunnelProxy extends Client {
38
41
  }
39
42
 
40
43
  // function to close all active sockets for tunnel restart and exception handling
41
- close_sockets(server_name, proxy_ports) {
44
+ close_sockets(proxy_ports) {
42
45
 
43
- if (!this.listener[server_name] || !proxy_ports) return;
46
+ if (!proxy_ports) return;
44
47
 
45
48
  proxy_ports.forEach(proxy_port => {
46
- const server = this.listener[server_name][proxy_port];
49
+ const server = this.listener[proxy_port];
47
50
  if (server && server.listening) {
48
51
  this.debug_en && this.debug('SSH Server :: closing forward:', proxy_port)
49
52
  server.close();
53
+ delete this.listener[proxy_port];
50
54
  }
51
55
  });
52
56
  }
@@ -102,17 +106,17 @@ class SSHTunnelProxy extends Client {
102
106
  }
103
107
 
104
108
  // function to setup proxy forwards for ssh tunnel
105
- setupProxyPorts(server_name, proxy_ports) {
109
+ setupProxyPorts(proxy_ports) {
106
110
 
107
111
  return new Promise((resolve, reject) => {
108
-
112
+
109
113
  if (!proxy_ports) {
110
114
  resolve();
111
115
  return;
112
116
  }
113
117
 
114
118
  var listeners = 0;
115
- if (this.listener[server_name]) this.listener[server_name].isConnected = true;
119
+ this.listener.isConnected = true;
116
120
  const _this = this;
117
121
 
118
122
  // iterate through a list of local forward ports and create a local proxy port
@@ -121,13 +125,12 @@ class SSHTunnelProxy extends Client {
121
125
  const [local_port, remote_hostname, remote_port] = proxy_port.split(':');
122
126
 
123
127
  // create local socket server
124
- const server = this.listener[server_name][proxy_port];
128
+ const server = _this.listener[proxy_port];
125
129
  if (server && server.listening) {
126
- this.debug_en && this.debug('SSH Server :: closing forward 2:', proxy_port)
130
+ _this.debug_en && this.debug('SSH Server :: closing forward 2:', proxy_port)
127
131
  server.close();
128
132
  }
129
- this.listener[server_name][proxy_port] = null;
130
- this.listener[server_name][proxy_port] = net.createServer({ keepAlive: true, allowHalfOpen: false }, socket => {
133
+ _this.listener[proxy_port] = net.createServer({ keepAlive: true, allowHalfOpen: false }, socket => {
131
134
 
132
135
  if (this.debug_en) {
133
136
  var debug_msg = 'SSH Server :: connection on ' + local_port + ' ' + socket.remotePort;
@@ -143,7 +146,7 @@ class SSHTunnelProxy extends Client {
143
146
  try {
144
147
  var status_msg = 'SSH Server :: before listen on ' + local_port;
145
148
  _this.emit('debug', status_msg);
146
- _this.listener[server_name][proxy_port].listen(local_port, () => {
149
+ _this.listener[proxy_port].listen(local_port, () => {
147
150
 
148
151
  // emit server listening on port message
149
152
  var status_msg = 'SSH Server :: bound on ' + local_port;
@@ -154,7 +157,7 @@ class SSHTunnelProxy extends Client {
154
157
  _this.emit('ssh_tunnel_ready', {});
155
158
  if (_this._tunnelReadyTimeout) {
156
159
  clearTimeout(this._tunnelReadyTimeout);
157
- _this._tunnelReadyTimeout = undefined
160
+ _this._tunnelReadyTimeout = undefined;
158
161
  }
159
162
  resolve();
160
163
  }
@@ -178,12 +181,14 @@ class SSHTunnelProxy extends Client {
178
181
  reject(err);
179
182
  return;
180
183
  }
181
- stream.on('close', () => {
184
+
185
+ const onClose = () => {
182
186
  var buf = Buffer.concat(bufs);
183
187
  var err = Buffer.concat(errs);
184
- //stream.removeListener('exit', onExit);
188
+ stream.removeListener('close', onClose);
185
189
  resolve(buf, err);
186
- })
190
+ }
191
+ stream.on('close', onClose);
187
192
 
188
193
  stream.on('data', (data) => {
189
194
  if (dataStream) dataStream.write(data);
@@ -238,14 +243,10 @@ class SSHTunnelProxy extends Client {
238
243
  return new Promise((resolve) => {
239
244
 
240
245
  // exit if connection already established, avoid redundant connection on retry
241
- if (_this.listener[opts.server_name] && _this.listener[opts.server_name].isConnected) resolve();
246
+ if (_this.listener.isConnected) resolve();
242
247
 
243
248
  // close open sockets on server, otherwise initialize open sockets storage
244
- if (_this.listener[opts.server_name]) {
245
- _this.close_sockets(opts.server_name, opts.proxy_ports);
246
- } else {
247
- _this.listener[opts.server_name] = {};
248
- }
249
+ _this.close_sockets(opts.proxy_ports);
249
250
 
250
251
  _this.on('ready', async () => {
251
252
 
@@ -255,47 +256,48 @@ class SSHTunnelProxy extends Client {
255
256
  _this._tunnelReadyTimeout = undefined;
256
257
  }
257
258
 
259
+ // if local forwarding requested, setup local forwarding ports to remote host
260
+ if (opts.proxy_ports) {
261
+ await _this.setupProxyPorts(opts.proxy_ports);
262
+ }
263
+
258
264
  // if shell requested, enable remote terminal
259
265
  if (opts.shell) {
260
266
  _this.shell(_this.remote_shell);
261
267
 
262
- // if exec requested, exec series of cmds
268
+ // otherwise, if exec requested, exec series of cmds
263
269
  } else {
264
270
  if (opts.exec && opts.exec.length > 0) {
265
271
  for (var i = 0; i < opts.exec.length; i++) {
266
272
  var cmd = opts.exec[i];
267
- console.log(opts.username + '@' + opts.hostname + ':' + cmd);
273
+ _this.debug_en && _this.debug(opts.username + '@' + opts.hostname + ':' + cmd);
268
274
  var result = await _this.execCmd(cmd);
269
- console.log(result.toString());
275
+ _this.debug_en && _this.debug(result.toString());
270
276
  }
271
277
  process.exit();
272
278
  }
273
279
  }
274
280
 
275
- // if local forward requested, setup local forwarding ports to remote host
276
- if (opts.proxy_ports) {
277
- await _this.setupProxyPorts(opts.server_name, opts.proxy_ports);
278
- }
279
281
  resolve();
280
282
  });
281
283
 
282
284
  _this.on('end', () => {
283
285
  _this.debug_en && _this.debug('SSH Client :: end');
284
- _this.close_sockets(opts.server_name, opts.proxy_ports);
286
+ _this.close_sockets(opts.proxy_ports);
285
287
  });
286
288
 
287
289
  _this.on('close', () => {
288
290
  _this.debug_en && _this.debug('SSH Client :: close');
289
- _this.close_sockets(opts.server_name, opts.proxy_ports);
291
+ _this.close_sockets(opts.proxy_ports);
290
292
  });
291
293
 
292
294
  _this.on('error', err => {
293
295
  _this.debug_en && _this.debug('SSH Client :: error :: ' + err);
294
296
  _this.emit('debug', err);
295
- if (_this.listener[opts.server_name]) _this.listener[opts.server_name].isConnected = false;
297
+ _this.listener.isConnected = false;
296
298
  if (_this._tunnelReadyTimeout) {
297
- clearTimeout(this._tunnelReadyTimeout);
298
- _this._tunnelReadyTimeout = undefined
299
+ clearTimeout(this._tunnelReadyTimeout.elReadyTimeout);
300
+ _this._tunnelReadyTimeout = undefined;
299
301
  }
300
302
  _this.ssh_retry_connect(opts);
301
303
  resolve();
@@ -309,19 +311,19 @@ class SSHTunnelProxy extends Client {
309
311
  _this.debug_en && _this.debug('SSH Client :: greeting:', message);
310
312
  });
311
313
 
312
- // initiate ssh client connection to remote host
313
- // ssh client debug function for verbose ssh connection details
314
- const debug_client = (this.debug_ssh) ? (...args) => { console.log(...args); } : null;
315
- _this.connect({
316
- host: opts.host,
317
- port: opts.port,
318
- username: opts.username,
319
- password: opts.password,
320
- privateKey: opts.private_key,
321
- debug: debug_client,
322
- keepaliveInterval: 10000
323
- });
314
+ // create deep copy of opts as connect options
315
+ const config = JSON.parse(JSON.stringify(opts));
324
316
 
317
+ // add default ssh2 config options
318
+ config.debug = (this.debug_ssh) ? (...args) => { console.log(...args); } : null;
319
+ config.keepaliveInterval = opts.keepaliveInterval || 10000;
320
+
321
+ // remove ssh_tunnel_proxy config extensions
322
+ const ssh_tunnel_extensions = ['alias', 'disabled', 'hostname', 'ngrok_api', 'server_name', 'service_name', 'whitelist', 'private_key_filename'];
323
+ for (var i = 0; i < ssh_tunnel_extensions.length; i++) delete config[ssh_tunnel_extensions[i]];
324
+
325
+ // connect to remote host
326
+ _this.connect(config);
325
327
  });
326
328
  }
327
329
 
@@ -330,7 +332,7 @@ class SSHTunnelProxy extends Client {
330
332
  if (isNaN(port_str)) return false;
331
333
  const port = parseInt(port_str);
332
334
  if (port < 1 || port > 65535) return false;
333
- if (port < 1024 && whitelist[port] === undefined) return false;
335
+ if (port < 1024 && whitelist && whitelist[port] === undefined) return false;
334
336
  return true;
335
337
  }
336
338
 
@@ -396,13 +398,11 @@ class SSHTunnelProxy extends Client {
396
398
  // todo: if opts have changed while service is running, shutdown current service and restart with new opts
397
399
  async connectSSH(opts, whitelist) {
398
400
 
399
- this.debug_en && this.debug('connectSSH:\n', JSON.stringify(opts, null, 2));
400
-
401
401
  // make deep copy of opts for modification
402
402
  var _opts = JSON.parse(JSON.stringify(opts));
403
403
 
404
404
  // setup whitelist from param or opts in case of error retry
405
- _opts.whitelist = (whitelist !== undefined) ? whitelist : (_opts.whitelist) ? _opts.whitelist : {};
405
+ _opts.whitelist = (whitelist !== undefined) ? whitelist : (_opts.whitelist) ? _opts.whitelist : null;
406
406
 
407
407
  // validate local port forwards, emit error and quit if invalid
408
408
  try {
@@ -418,9 +418,24 @@ class SSHTunnelProxy extends Client {
418
418
  _opts.password = undefined;
419
419
  }
420
420
 
421
+ if (_opts.private_key_filename) {
422
+ try {
423
+ if (_opts.private_key_filename[0] == '~') {
424
+ _opts.private_key_filename = homedir + _opts.private_key_filename.substr(1);
425
+ }
426
+ _opts.privateKey = fs.readFileSync(_opts.private_key_filename).toString();
427
+ } catch (err) {
428
+ console.log('Error loading key:', err);
429
+ }
430
+ }
431
+
421
432
  // retrieve private key from system keychain with supplied service name and server
422
433
  // if no key found, authentication is supplied username, password
423
- _opts.private_key = await keypairStorage.get_keypair(opts.service_name, opts.server_name);
434
+ if (_opts.service_name && _opts.server_name) {
435
+ _opts.privateKey = await keypairStorage.get_keypair(opts.service_name, opts.server_name);
436
+ } else {
437
+ if (!_opts.server_name) _opts.server_name = 'sshtun';
438
+ }
424
439
 
425
440
  // if ngrok api key provided, obtain hostname and hostport from ngrok api
426
441
  // if no ngrok tunnel specified, use supplied host and port
@@ -430,7 +445,7 @@ class SSHTunnelProxy extends Client {
430
445
  var hostport = await ngrokApi.get_hostport()
431
446
  .catch(() => {
432
447
  _this.debug_en && _this.debug('ngrok get_hostport connection error');
433
- if (this.listener[opts.server_name]) this.listener[opts.server_name].isConnected = false;
448
+ _this.listener.isConnected = false;
434
449
  _this.ssh_retry_connect(_opts);
435
450
  });
436
451
  if (hostport && hostport.host) {
@@ -440,6 +455,8 @@ class SSHTunnelProxy extends Client {
440
455
  else return null;
441
456
  }
442
457
 
458
+ this.debug_en && this.debug('connectSSH:\n', JSON.stringify(_opts, null, 2));
459
+
443
460
  // initiate ssh tunnel, block until tunnel is established or error
444
461
  return await this.ssh_start_tunnel(_opts)
445
462
  .catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh_tunnel_proxy",
3
- "version": "1.2.10",
3
+ "version": "1.2.11",
4
4
  "description": "obtain ngrok host port from api and establish a ssh connection",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -1,3 +1,4 @@
1
+ #!/usr/bin/node
1
2
  const process = require('process');
2
3
  const fs = require('fs');
3
4
  const { SSHTunnelProxy, KeypairStorage } = require('./lib/index.js');
@@ -21,14 +22,6 @@ var service = null;
21
22
  var account = null;
22
23
  var shell = null;
23
24
 
24
- function get_key_from_file(filename) {
25
- try {
26
- return fs.readFileSync(filename);
27
- } catch (err) {
28
- console.log('Error loading key:', err);
29
- }
30
- }
31
-
32
25
  function get_config(filename) {
33
26
  try {
34
27
  configs = JSON.parse(fs.readFileSync(filename), 'utf8');
@@ -41,7 +34,7 @@ function get_default_config() {
41
34
  try {
42
35
  configs = JSON.parse(fs.readFileSync(homedir + '/.config/ssh_tunnel_proxy/config.json'), 'utf8');
43
36
  } catch (err) {
44
- console.log('Error loading config file:', err);
37
+ //console.log('Error loading config file:', err);
45
38
  }
46
39
  }
47
40
 
@@ -114,15 +107,21 @@ function main(args) {
114
107
  username = value;
115
108
  break;
116
109
  default:
117
- if (i > 1) console.log('Invalid argument:', arg);
110
+ if (i == 2) remote_host = cmd;
111
+ if (i > 2) console.log('Invalid argument:', arg);
118
112
  }
119
113
  }
120
114
 
121
115
  // prevent process from exiting
122
- process.stdin.resume();
116
+ //process.stdin.resume();
123
117
 
124
- // if no config file specified, build config from args
118
+ // if no config file specified attempt to load default config
125
119
  if (!configs) {
120
+ get_default_config();
121
+ }
122
+
123
+ // no default config build config from args
124
+ if (!remote_host) {
126
125
  var config = {
127
126
  enabled: true,
128
127
  username: username,
@@ -140,24 +139,28 @@ function main(args) {
140
139
  }
141
140
 
142
141
  // process each tunnel config in ssh proxy tunnel list
142
+ var remote_server = null;
143
143
  configs.forEach(async config => {
144
144
 
145
145
  // skip tunnel if not enabled
146
- if (!config.enabled) return;
146
+ if (config.disabled) return;
147
147
 
148
148
  // if remote host specified, only select tunnel for remote host
149
- if (remote_host && remote_host !== config.alias) return;
149
+ if (remote_host && remote_host !== config.hostname) return;
150
+ config.server_name = config.hostname || remote_host || 'ssh_tunnel_proxy';
151
+ remote_server = remote_host;
150
152
 
151
153
  // if system key storage name specified get key from system keychain
152
154
  if (config.server_name) {
153
155
  const service_name = config.service_name || 'ssh_tunnel_proxy';
154
156
  config.private_key = await keypairStorage.get_keypair(service_name, config.server_name);
155
157
  }
158
+
156
159
  // if private key filename specified, read private key
157
- else if (config.private_key) {
158
- config.private_key = get_key_from_file(config.private_key);
160
+ if (privateKey) {
161
+ config.private_key_filename = privateKey;
159
162
  }
160
- if (shell) {
163
+ if (shell || exec.length === 0) {
161
164
  config.shell = true;
162
165
  }
163
166
  if (exec.length > 0) {
@@ -169,7 +172,9 @@ function main(args) {
169
172
  sshTunnelProxy.debug_en = debug;
170
173
  sshTunnelProxy.connectSSH(config);
171
174
  });
172
-
175
+ if (remote_host && !remote_server) {
176
+ console.log(`Remote host ${remote_host} not found`);
177
+ }
173
178
 
174
179
  }
175
180
 
@@ -165,7 +165,6 @@ async function lsTest(sshTunnelProxy) {
165
165
  );
166
166
 
167
167
  const lscmd = 'ls -all';
168
- //const lscmd = 'ls -all | column --table --table-columns pm,links,user,group,size,month,day,time,name -J';
169
168
  console.log('\ninvoking ' + lscmd + ' on remote host:\n');
170
169
  await sshTunnelProxy.execCmd(lscmd, tunnel);
171
170
 
@@ -188,7 +187,6 @@ async function syslogTest(sshTunnelProxy) {
188
187
  process.stdout,
189
188
  () => { }
190
189
  );
191
- //const syslogcmd = 'tail /var/log/syslog | column --table --table-columns-limit 6 --table-columns month,day,time,host,proc,msg -J';
192
190
  const syslogcmd = 'tail /var/log/syslog';
193
191
  console.log('\ninvoking ' + syslogcmd + ' on remote host:\n');
194
192
  await sshTunnelProxy.execCmd(syslogcmd, tunnel);