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.deployVerticle("maven:{maven-groupId}:{maven-artifactId}:{maven-version}", [
  config:[
    telnetOptions:[
      host:"localhost",
      port:4000
    ]
  ]
])

or

vertx.deployVerticle("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:

import io.vertx.ext.auth.shiro.ShiroAuthRealmType
import io.vertx.groovy.ext.shell.ShellService
def service = ShellService.create(vertx, [
  sSHOptions:[
    host:"localhost",
    port:5000,
    keyPairOptions:[
      path:"server-keystore.jks",
      password:"wibble"
    ],
    shiroAuthOptions:[
      type:ShiroAuthRealmType.PROPERTIES,
      config:[
        properties_path:"file:/path/to/my/auth.properties"
      ]
    ]
  ]
])
service.start()

Starting a shell service available via Telnet:

import io.vertx.groovy.ext.shell.ShellService
def service = 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:

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:

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

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:

import io.vertx.groovy.ext.shell.command.CommandBuilder
import io.vertx.groovy.ext.shell.registry.CommandRegistry

def builder = CommandBuilder.command("my-command")
builder.processHandler({ process ->

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

  // End the process
  process.end()
})

// Register the command
def registry = CommandRegistry.get(vertx)
registry.registerCommand(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.processHandler({ process ->

  process.args().each { arg ->
    // Print each argument on the console
    process.write("Argument ${arg}")
  }

  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

import io.vertx.groovy.core.cli.CLI
import io.vertx.groovy.ext.shell.command.CommandBuilder
def cli = CLI.create("my-command").addArgument([
  argName:"my-arg"
]).addOption([
  shortName:"m",
  longName:"my-option"
])
def command = CommandBuilder.command(cli)
command.processHandler({ process ->

  def commandLine = process.commandLine()

  def argValue = commandLine.getArgumentValue(0)
  def optValue = commandLine.getOptionValue("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:

import io.vertx.groovy.core.cli.CLI
import io.vertx.groovy.ext.shell.command.CommandBuilder
def cli = CLI.create("my-command").addArgument([
  argName:"my-arg"
]).addOption([
  argName:"help",
  shortName:"h",
  longName:"help"
])
def command = CommandBuilder.command(cli)
command.processHandler({ process ->
  // ...
})

Terminal size

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

command.processHandler({ 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.processHandler({ process ->

  def session = process.session()

  if (session.get("my_key") == null) {
    session.put("my key", "my value")
  }

  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.processHandler({ process ->
  process.setStdin({ data ->
    println("Received ${data}")
  })
})

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

command.processHandler({ process ->
  process.stdout().write("Hello World")
  process.end()
})

Or it can use the write method:

command.processHandler({ 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.processHandler({ process ->
  def vertx = process.vertx()

  // Set a timer
  vertx.setTimer(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:

import io.vertx.ext.shell.io.EventType
command.processHandler({ process ->
  def vertx = process.vertx()

  // Every second print a message on the console
  def periodicId = vertx.setPeriodic(1000, { id ->
    process.write("tick\n")
  })

  // When user press Ctrl+C: cancel the timer and end the process
  process.eventHandler(EventType.SIGINT, { event ->
    vertx.cancelTimer(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

import io.vertx.ext.shell.io.EventType
command.processHandler({ process ->

  // Command is suspended
  process.eventHandler(EventType.SIGTSTP, { event ->
    println("Suspended")
  })

  // Command is resumed
  process.eventHandler(EventType.SIGCONT, { event ->
    println("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.