nodejs code to enable zmNinga to establish a ngrok connection - generated by codex!

Discussion topics related to mobile applications and ZoneMinder Event Server (including machine learning)
Post Reply
Autonomous
Posts: 10
Joined: Wed Dec 28, 2022 9:22 pm

nodejs code to enable zmNinga to establish a ngrok connection - generated by codex!

Post by Autonomous »

So this is really trippy - I don't know if people are familiar with openai and it's code generation capabilities. After writing a simple python script to establish a ngrok connection, I pasted the code into openai and asked it to review my code. It seemed to like my code but suggested using a more robust library for SSH connections like paramiko or fabric for prolonged sessions.

Then I took the natural language interpretation of the code generated by openai and pasted it into the codex javascript sandbox and out popped the following nodejs code. It even includes the asynchronous function to connect to ssh once the host port has been obtained from ngrok api. I could have just generated the code by typing in a few natural language lines describing the function. I forgot to ask it about establishing local forwards and asked it to add the localforwards to ssh -- see below

I have a zmNinja build environment setup and will figure out where to insert this function:

Code: Select all

/*
Can you add the following:
Obtain a list of local port forwards from the config file. The format is port:remote_hostname:remote_port.
Then when opening the ssh connection, establish the localforward ports on localhost 
*/
var fs = require('fs');
var ngrok = require('ngrok');
var config = JSON.parse(fs.readFileSync('config.json', 'utf8'));
var apiKey = config.apiKey;
var username = config.username;
var password = config.password;
var port = config.port;
var localForwards = config.localForwards;
ngrok.authtoken(apiKey, function (err, token) {
  if (err) {
    console.log(err);
  } else {
    while (true) {
      ngrok.connect({
        proto: 'tcp',
        addr: port,
        auth: username + ':' + password
      }, function (err, url) {
        if (err) {
          console.log(err);
        } else {
          var hostname = url.split(':')[0];
          var hostport = url.split(':')[1];
          console.log(hostname);
          console.log(hostport);
          var ssh = require('ssh2').Client();
          ssh.on('ready', function () {
            console.log('Client :: ready');
            ssh.exec('uptime', function (err, stream) {
              if (err) throw err;
              stream.on('close', function (code, signal) {
                console.log('Stream :: close :: code: ' + code + ', signal: ' + signal);
                ssh.end();
              }).on('data', function (data) {
                console.log('STDOUT: ' + data);
              }).stderr.on('data', function (data) {
                console.log('STDERR: ' + data);
              });
            });
          }).connect({
            host: hostname,
            port: hostport,
            username: username,
            password: password,
            localForwards: localForwards
          });
        }
      });
      sleep(2000);
    }
  }
});
Magic919
Posts: 1159
Joined: Wed Sep 18, 2013 6:56 am

Re: nodejs code to enable zmNinga to establish a ngrok connection - generated by codex!

Post by Magic919 »

We were playing with ChatGPT at work and asked it to do a Lambda function to perform a simple task, in Typescript. It was impressive. Then asked for the same in Python.

One of my guys commented that when it’s wrong it really sticks the fact it is correct and won’t budge. But we have people around that do just that.
-----------------------------------------------------------------------
I'm nothing to do with ZM, ZMNinja or ZMES, I just use them.
Contribute funding to the devs here or ZM dies - https://github.com/sponsors/connortechnology
Autonomous
Posts: 10
Joined: Wed Dec 28, 2022 9:22 pm

Re: nodejs code to enable zmNinga to establish a ngrok connection - generated by codex!

Post by Autonomous »

It took several iterations of rewriting the instructions for codex then the resulting code needed a little reworking in a few key areas. But it generated 98% of the code correctly. Here is a functional version of nodejs establishing ssh tunneling with port forwards via ngrok. I am currently watching video streams from the ZM server web interface through this tunneling code. I believe this code could be added to zmNinja to allow for a third type of authentication, via ssh tunneling and key based authentication. With the tunnel established, real-time notifications could be received by zmNinja by subscribing to mqtt notification channels on the remote server.

Code: Select all

/* codex instructions
javascript language
Obtain remote username, ngrok api and list of proxy ports from a configuration file.
Obtain a list of endpoints from ngrok-api-typescript http api endpoints.list method using
async/await. Obtain the hostport value from the first endpoint and split into hostname
and port. Establish a ssh connection using the electron-ssh2 api and async/await with the username,
hostname and port obtained from the ngrok endpoint method.
If the ssh connection ends abnormally attempt to re-establish the ssh connection
every 5 seconds until a maximum number of 10 retries is attempted.
Iterate through the list of proxy ports having the format 
local_port:remote_hostname:remote_port, split the string into three values.
Open a socket listening on the local_port with the net api and use the local_port,
localhost, remote_hostname, remote_port to open up a socks5 proxy port for each item
in the list. Then pipe the socket into the stream and pipe the result to the stream.
Keep the ssh connection open indefinitely and exit when the user logs out of the ssh connection. 
*/

const fs = require('fs');
const net = require('net');
const path = require('path');
const os = require('os');
const { Client } = require('electron-ssh2');
const { Ngrok } = require('@ngrok/ngrok-api');
const { pipeline } = require('stream');

var homedir = os.homedir();
const config = JSON.parse(fs.readFileSync('./config2.json', 'utf8'));
const private_key = require('fs').readFileSync(path.join(homedir, '.ssh', config.private_key));
const username = config.username;
const ngrok_api = config.ngrok_api;
const proxy_ports = config.proxy_ports;

const ngrok = new Ngrok({ apiToken: ngrok_api });

const ssh_connect = async () => {
    const endpoints = await ngrok.endpoints.list();
    const hostport = endpoints[0].hostport.split(':');
    const hostname = hostport[0];
    const port = hostport[1];

    const ssh_client = new Client();
    ssh_client.on('ready', () => {
        console.log('Client :: ready at ' + hostport);
        proxy_ports.forEach(proxy_port => {
            const [local_port, remote_hostname, remote_port] = proxy_port.split(':');

            const server = net.createServer({ keepAlive: true, allowHalfOpen: false }, socket => {
                //console.log('Server :: connection on ' + local_port + ' ' + socket.remotePort);
                ssh_client.forwardOut(
                    socket.remoteAddress,
                    socket.remotePort,
                    remote_hostname,
                    remote_port,
                    (err, stream) => {
                        if (err) {
                            throw err;
                        }
                        socket.pipe(stream);
                        stream.pipe(socket);
                        socket.on('close', () => {
                            stream.end();
                        });
                    }
                );
            }).listen(local_port, 'localhost');
        });
    }).connect({
        host: hostname,
        port: port,
        username: username,
        privateKey: private_key,
        keepaliveInterval: 5000
    });
    ssh_client.on('end', () => {
        console.log('Client :: end');
    });
    ssh_client.on('close', () => {
        console.log('Client :: close');
    });
    ssh_client.on('error', err => {
        console.log('Client :: error :: ' + err);
    });
};

let retries = 0;
const ssh_retry = async () => {
    try {
        await ssh_connect();
    } catch (err) {
        console.log(err);
        if (retries < 10) {
            retries++;
            setTimeout(ssh_retry, 5000);
        }
    }
};

ssh_retry();
Autonomous
Posts: 10
Joined: Wed Dec 28, 2022 9:22 pm

Re: nodejs code to enable zmNinga to establish a ngrok connection - generated by codex!

Post by Autonomous »

The create ssh/ngrok tunnel code has been added to a fork of the zmNinja app. A third zmNinja connection type option was added to options called "use ssh tunnel", where you can specify parameters to establish a ssh connection to a router or directly to a server via ngrok. The normal username/password authentication methods are available as well as no authentication.

zmNinja ssh connection example (desktop version):
Image

Image
User avatar
iconnor
Posts: 2330
Joined: Fri Oct 29, 2010 1:43 am
Location: Toronto
Contact:

Re: nodejs code to enable zmNinga to establish a ngrok connection - generated by codex!

Post by iconnor »

Hey that is really cool.Create a PR and we will look at merging it.
Autonomous
Posts: 10
Joined: Wed Dec 28, 2022 9:22 pm

Re: nodejs code to enable zmNinga to establish a ngrok connection - generated by codex!

Post by Autonomous »

Hey that is really cool.Create a PR and we will look at merging it.
Will do, thanks! Needed to add EdDSA keypair generation so that the public key can be copied from ssh settings within the app for installation on the server. Just finished testing the production build version for the desktop and everything is working correctly. I will be creating the PR shortly.
Post Reply