# Vert.x Shell

Vert.x Shell is a command line interface for the Vert.x runtime available from regular terminals using different protocols.

Vert.x Shell provides a variety of commands for interacting live with Vert.x services.

Vert.x Shell can be extended with custom commands in any language supported by Vert.x

## Using Vert.x Shell

Vert.x Shell is a Vert.x Service and can be started programmatically via the `ShellService` or deployed as a service.

### Deployed service

The shell can be started as a service directly either from the command line or as a the Vert.x deployment:

Starting a shell service available via Telnet

    vertx run -conf '{"telnetOptions":{"port":5000}}' maven:io.vertx:vertx-shell:3.1.0

or

Starting a shell service available via SSH

    # create a key pair for the SSH server
    keytool -genkey -keyalg RSA -keystore ssh.jks -keysize 2048 -validity 1095 -dname CN=localhost -keypass secret -storepass secret
    # create the auth config
    echo user.admin=password > auth.properties
    # start the shell
    vertx run -conf '{"sshOptions":{"port":4000,"keyPairOptions":{"path":"ssh.jks","password":"secret"},"shiroAuthOptions":{"config":{"properties_path":"file:auth.properties"}}}}' maven:io.vertx:vertx-shell:3.1.0

You can also deploy this service inside your own verticle:

    vertx.deploy_verticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", {
      'config' => {
        'telnetOptions' => {
          'host' => "localhost",
          'port' => 4000
        }
      }
    })

or

    vertx.deploy_verticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", {
      'config' => {
        'sshOptions' => {
          'host' => "localhost",
          'port' => 5000,
          'keyPairOptions' => {
            'path' => "server-keystore.jks",
            'password' => "wibble"
          },
          'config' => {
            'properties_path' => "file:/path/to/my/auth.properties"
          }
        }
      }
    })

Note | when Vert.x Shell is already on your classpath you can use `service:io.vertx.ext.shell` instead or `maven:io.vertx:vertx-shell:3.1.0`

### Programmatic service

The `ShellService` takes care of starting an instance of Vert.x Shell.

Starting a shell service available via SSH:

    require 'vertx-shell/shell_service'
    service = VertxShell::ShellService.create(vertx, {
      'sSHOptions' => {
        'host' => "localhost",
        'port' => 5000,
        'keyPairOptions' => {
          'path' => "server-keystore.jks",
          'password' => "wibble"
        },
        'shiroAuthOptions' => {
          'type' => :PROPERTIES,
          'config' => {
            'properties_path' => "file:/path/to/my/auth.properties"
          }
        }
      }
    })
    service.start()

Starting a shell service available via Telnet:

    require 'vertx-shell/shell_service'
    service = VertxShell::ShellService.create(vertx, {
      'telnetOptions' => {
        'host' => "localhost",
        'port' => 4000
      }
    })
    service.start()

The `TelnetOptions` extends the Vert.x Core `NetServerOptions` as the Telnet server implementation is based on a `NetServer`.

Caution | Telnet does not provide any authentication nor encryption at all.

## Telnet configuration

The telnet connector is configured by `telnetOptions`, the `TelnetOptions` extends the `NetServerOptions` so they have the exact same configuration.

## SSH configuration

The SSH connector is configured by `SSHOptions`:

- `port`: port

- `host`: host

Only username/password authentication is supported at the moment, it can be configured with property file or LDAP, see Vert.x Auth for more info:

- `shiroAuthOptions`: configures user authentication

The server key configuration reuses the key pair store configuration scheme provided by Vert.x Core:

- `keyPairOptions`: set `.jks` key pair store

- `pfxKeyPairOptions`: set `.pfx` key pair store

- `pemKeyPairOptions`: set `.pem` key pair store

## Base commands

To find out the available commands you can use the help builtin command:

1. Verticle commands

   1. verticle-ls: list all deployed verticles

   2. verticle-undeploy: undeploy a verticle

   3. verticle-deploy: deployes a verticle

   4. verticle-factories: list all known verticle factories

2. File system commands

   1. ls

   2. cd

   3. pwd

3. Bus commands

   1. bus-tail: display all incoming messages on an event bus address

   2. bus-send: send a message on the event bus

4. Net commands

   1. net-ls: list all available net servers, including HTTP servers

5. Shared data commands

   1. local-map-put

   2. local-map-get

   3. local-map-rm

6. Metrics commands (requires Dropwizard metrics setup)

   1. metrics-ls: show all available metrics

   2. metrics-info: show particular metrics

7. Various commands

   1. echo

   2. sleep

   3. help

   4. exit

   5. logout

8. Job control

   1. fg

   2. bg

   3. jobs

Note | this command list should evolve in next releases of Vert.x Shell

## Extending Vert.x Shell

Vert.x Shell can be extended with custom commands in any of the languages supporting code generation.

A command is created by the `CommandBuilder.command` method: the command process handler is called by the shell when the command is executed, this handler can be set with the `processHandler` method:

    require 'vertx-shell/command_builder'
    require 'vertx-shell/command_registry'

    builder = VertxShell::CommandBuilder.command("my-command")
    builder.process_handler() { |process|

      # Write a message to the console
      process.write("Hello World")

      # End the process
      process.end()
    }

    # Register the command
    registry = VertxShell::CommandRegistry.get(vertx)
    registry.register_command(builder.build())

After a command is created, it needs to be registed to a `CommandRegistry`. The command registry holds all the commands for a Vert.x instance.

Note | Command callbacks are invoked in the `io.vertx.core.Context` when the command is registered in the registry. Keep this in mind if you maintain state in a command.

The `CommandProcess` object can be used for interacting with the shell.

### Command arguments

The `args` returns the command arguments:

    command.process_handler() { |process|

      process.args().each do |arg|
        # Print each argument on the console
        process.write("Argument #{arg}")
      end

      process.end()
    }

Besides it is also possible to create commands using `Vert.x CLI`: it makes easier to write command line argument parsing:

- option and argument parsing

- argument validation

- generation of the command usage

    require 'vertx/cli'
    require 'vertx-shell/command_builder'
    cli = Vertx::CLI.create("my-command").add_argument({
      'argName' => "my-arg"
    }).add_option({
      'shortName' => "m",
      'longName' => "my-option"
    })
    command = VertxShell::CommandBuilder.command(cli)
    command.process_handler() { |process|

      commandLine = process.command_line()

      argValue = commandLine.get_argument_value(0)
      optValue = commandLine.get_option_value("my-option")
      process.write("The argument is #{argValue} and the option is #{optValue}")

      process.end()
    }

When an option named help is added to the CLI object, the shell will take care of generating the command usage when the option is activated:

    require 'vertx/cli'
    require 'vertx-shell/command_builder'
    cli = Vertx::CLI.create("my-command").add_argument({
      'argName' => "my-arg"
    }).add_option({
      'argName' => "help",
      'shortName' => "h",
      'longName' => "help"
    })
    command = VertxShell::CommandBuilder.command(cli)
    command.process_handler() { |process|
      # ...
    }

### Terminal size

The current terminal size can be obtained using `width` and `height`.

    command.process_handler() { |process|
      process.write("Current terminal size: (#{process.width()}, #{process.height()})").end()
    }

### Shell session

The shell is a connected service that naturally maintains a session with the client, this session can be used in commands to scope data. A command can get the session with `session`:

    command.process_handler() { |process|

      session = process.session()

      if (session.get("my_key") == nil)
        session.put("my key", "my value")
      end

      process.end()
    }

### Process I/O

A command can set a `setStdin` handler to be notified when the shell receives data, e.g the user uses his keyboard:

    command.process_handler() { |process|
      process.set_stdin() { |data|
        puts "Received #{data}"
      }
    }

A command can use the `stdout` to write to the standard output.

    command.process_handler() { |process|
      process.stdout().write("Hello World")
      process.end()
    }

Or it can use the `write` method:

    command.process_handler() { |process|
      process.write("Hello World")
      process.end()
    }

### Process termination

Calling `end` ends the current process. It can be called directly in the invocation of the command handler or any time later:

    command.process_handler() { |process|
      vertx = process.vertx()

      # Set a timer
      vertx.set_timer(1000) { |id|

        # End the command when the timer is fired
        process.end()
      }
    }

### Process events

A command can subscribe to a few process events, named after the posix signals.

#### `SIGINT` event

The `SIGINT` event is fired when the process is interrupted, this event is fired when the user press Ctrl+C during the execution of a command. This handler can be used for interrupting commands blocking the CLI and gracefully ending the command process:

    command.process_handler() { |process|
      vertx = process.vertx()

      # Every second print a message on the console
      periodicId = vertx.set_periodic(1000) { |id|
        process.write("tick\n")
      }

      # When user press Ctrl+C: cancel the timer and end the process
      process.event_handler(:SIGINT) { |event|
        vertx.cancel_timer?(periodicId)
        process.end()
      }
    }

When no `SIGINT` handler is registered, pressing Ctrl+C will have no effect on the current process and the event will be delayed and will likely be handled by the shell, like printing a new line on the console.

#### `SIGTSTP`/`SIGCONT` events

The `SIGTSTP` event is fired when the process is running and the user press Ctrl+Z: the command is suspended:

- the command can receive the `SIGTSTP` event when it has registered an handler for this event

- the command will not receive anymore data from the standard input

- the shell prompt the user for input

The `SIGCONT` event is fired when the process is resumed, usually when the user types fg:

- the command can receive the `SIGCONT` event when it has registered an handler for this event

- the command will receive anymore data from the standard input when it has registered an stdin handler

    command.process_handler() { |process|

      # Command is suspended
      process.event_handler(:SIGTSTP) { |event|
        puts "Suspended"
      }

      # Command is resumed
      process.event_handler(:SIGCONT) { |event|
        puts "Resumed"
      }
    }

#### `SIGWINCH` event

The `SIGWINCH` event is fired when the size of the terminal changes, the new terminal size can be obtained with `width` and `height`.

### Command completion

A command can provide a completion handler when it wants to provide contextual command line interface completion.

Like the process handler, the `completion handler` is non blocking because the implementation may use Vert.x services, e.g the file system.

The `lineTokens` returns a list of `tokens` from the beginning of the line to the cursor position. The list can be empty if the cursor when the cursor is at the beginning of the line.

The `rawLine` returns the current completed from the beginning of the line to the cursor position, in raw format, i.e without any char escape performed.

Completion ends with a call to `complete`.