Skip to content

Lua Scripts

NSClient++ can host Lua scripts in-process via the LUAScript module. Hosted scripts can call NSClient++'s APIs directly — run checks, listen on channels, register new check commands, read and write settings — and they keep state between invocations.

To add a Lua script as an internal script you need to load the LUAScript module and register the script:

nscp lua add --script my_script.lua

This produces:

[/modules]
LUAScript = enabled

[/settings/lua/scripts]
my_script = my_script.lua

Scripts are resolved against ${scripts} (typically scripts/lua/) and may be specified with or without the .lua extension. A lib/ folder under scripts/lua/ is added to package.path automatically, so shared helpers can live in scripts/lua/lib/.

Lifecycle

A Lua script can hook into three lifecycle moments:

  • Top-level code — runs once when the script is loaded. Use this to register check commands, channel subscriptions, and event handlers.
  • on_start — optional global function, invoked after every script has loaded. Use it for work that needs other modules to be ready.
  • main — optional global function, invoked when the script is run from the command line.

Top-level code

The body of the script runs exactly once, when the LUAScript module loads it. This is where you register everything the script wants to expose:

local function check_hello(command, args)
    return "ok", "Hello from Lua!", ""
end

Registry():simple_query("check_hello", check_hello, "A Lua greeting")

After the script is loaded, NSClient++ will route check_hello to your function.

on_start

Define a global on_start() function if you need a hook that fires after all scripts have been loaded — e.g. to talk to other modules or schedule background work.

function on_start()
    nscp.info("Lua script ready")
end

on_start takes no arguments and does not need to return anything.

main

main is invoked when the script is run from the command line:

nscp lua execute --script my_script.lua install --root /tmp

The function receives the command-line arguments as an array (Lua table) and must return a 2-tuple (code, message):

function main(args)
    for i, v in ipairs(args) do
        nscp.info(string.format("arg %d = %s", i, v))
    end
    return "ok", "Ran with " .. #args .. " arguments"
end

Status codes

Throughout the API, status codes are represented as strings:

String Meaning
"ok" Nagios OK
"warning" Nagios warning
"critical" Nagios critical
"unknown" Nagios unknown

The wrapper also accepts the corresponding integer codes (0/1/2/3) on input, but always produces strings when handing values to your callbacks. Prefer the string form in your own code.

API

The Lua environment exposes:

  • A nscp global with utility functions (info, print, error, sleep, getSetting)
  • Three constructors that return wrapper objects: Core(), Registry(), Settings()
  • Standard Lua library functions (string, table, io, os, etc.)

Wrapper objects use method-call syntax (obj:method(args)). The : is required — calling with . will not pass the object instance correctly.

local core = Core()
local code, msg, perf = core:simple_query("check_cpu", {})

Most operations have a simple variant that uses plain Lua values, and a raw variant that takes serialized protobuf messages. Use the simple form unless you need fields it doesn't expose.

The nscp global

nscp.info / nscp.print / nscp.error

nscp.info(message)
nscp.print(message)   -- alias for nscp.info
nscp.error(message)

Write a message to the NSClient++ log. info and print log at the info level; error logs at the error level.

nscp.info("Script starting up")
nscp.error("Something is wrong: " .. err)

nscp.sleep

nscp.sleep(milliseconds)

Sleep for the given number of milliseconds. Use this rather than busy-looping or shelling out to os.execute("sleep ...").

nscp.info("Waiting 500ms")
nscp.sleep(500)

nscp.getSetting

value = nscp.getSetting(path, key, default)

One-shot helper for reading a single string setting without instantiating Settings().

local port = nscp.getSetting("/settings/NRPE/server", "port", "5666")

For anything more involved, instantiate Settings() directly (see below).

Core

Core() returns a wrapper around the running NSClient++ instance — use it to run check commands, submit passive results, and reload modules.

local core = Core()

Core:simple_query

code, message, perf = core:simple_query(command, args)

Run a check command. args can be a Lua table of argument strings or a single argument string. Returns the Nagios status string, the message, and the performance data.

local code, msg, perf = core:simple_query("check_cpu", {"warn=load > 80", "crit=load > 90"})
nscp.info(string.format("%s: %s (%s)", code, msg, perf))

Core:query

ok, response_bytes = core:query(request_bytes)

Raw protobuf variant of simple_query. request_bytes is a serialized QueryRequestMessage; response_bytes is a serialized QueryResponseMessage. Use Core:create_pb_query to build the request bytes from a command and argument list.

Core:create_pb_query

request_bytes = core:create_pb_query(command, args)

Build a serialized QueryRequestMessage for Core:query. args can be a Lua table or a single string.

local req = core:create_pb_query("check_cpu", {"warn=load > 80"})
local ok, resp = core:query(req)

Core:simple_exec

code, results = core:simple_exec(target, command, args)

Execute a command-line command against target. target is a remote NSClient++ instance name, or "" for in-process. results is a Lua array of result strings.

local code, lines = core:simple_exec("", "list-modules", {})
for _, line in ipairs(lines) do nscp.info(line) end

Core:simple_submit

ok, response = core:simple_submit(channel, command, code, message, perf)

Submit a passive check result on a channel (e.g. "NSCA", "NRDP").

Argument Description
channel Channel to submit to
command Check command name being reported
code Status string ("ok", "warning", ...)
message Message text
perf Performance data string
core:simple_submit("NSCA", "check_battery", "warning", "Battery low (15%)", "")

Core:reload

core:reload(module)

Reload the given module by name. Pass "service" to reload the entire service.

Core:log

core:log(level, message)

Log a message at the specified level. level is a string: "info", "error", "debug", etc. nscp.info() / nscp.error() are usually more convenient.

Registry

Registry() lets a script publish itself into NSClient++ — registering check commands, command-line commands, and channel subscriptions.

local reg = Registry()

Registration happens at the top level of the script; once registered, your callbacks fire whenever the corresponding command is invoked.

Registry:simple_query / Registry:simple_function

reg:simple_query(name, function, description)
reg:simple_function(name, function, description)   -- alias

Register a check command with a simple callback. Both names refer to the same registration helper — simple_query reads more naturally for queries.

Argument Description
name The check command name (e.g. check_hello)
function Callback invoked when the command runs
description Help text shown by <command> help and the WEB UI

The callback signature is (command, args) -> (code, message, perf):

local function check_hello(command, args)
    return "ok", "Hello!", "'count'=1;5;10"
end

Registry():simple_query("check_hello", check_hello, "Returns a greeting")

Registry:query

reg:query(name, function, description)

Register a check command with a raw, protobuf-based callback. Use simple_query instead unless you need fields it doesn't expose.

The callback signature is (command, request_bytes, request_message_bytes) -> response_bytes, where response_bytes is a serialized QueryResponseMessage.

Registry:simple_cmdline

reg:simple_cmdline(name, function, description)

Register a command-line command invoked via nscp ext --command <name>. Callback signature is (command, args) -> (code, message):

local function do_something(command, args)
    return "ok", "Did " .. (args[1] or "nothing")
end

Registry():simple_cmdline("do_something", do_something, "Sample cmdline command")

Registry:simple_subscription

reg:simple_subscription(channel, function, description)

Subscribe to a channel (think passive check submissions). Per submission, the callback receives the channel, the originating command name, the status code, and a Lua table of {message = perf} lines:

local function on_submit(channel, command, code, lines)
    for msg, perf in pairs(lines) do
        nscp.info(string.format("%s on %s [%s]: %s", command, channel, code, msg))
    end
    return true, "ok"
end

Registry():simple_subscription("MY-CHANNEL", on_submit, "Custom submission handler")

Return (success_bool, message).

To route real-time submissions through your handler, point a filter or client at the channel name you registered:

[/modules]
LUAScript=enabled
CheckEventLog=enabled

[/settings/eventlog/real-time/filters/login]
log=Security
filter=id=4624
target=MY-CHANNEL

Settings

Settings() wraps the configuration store. Reads return current values; writes are in-memory only until :save() is called.

local config = Settings()

Settings:get_section

keys = config:get_section(path)

Return the keys under a given section as a Lua array of strings.

for _, key in ipairs(Settings():get_section("/modules")) do
    nscp.info("Module: " .. key)
end

Settings:get_string / Settings:set_string

value = config:get_string(path, key, default)
config:set_string(path, key, value)

Read or write a string. Writes are not persisted until :save() is called.

local config = Settings()
local existing = config:get_string("/modules", "LUAScript", "disabled")
config:set_string("/modules", "LUAScript", "enabled")
config:save()

Settings:get_bool / Settings:set_bool

value = config:get_bool(path, key, default)
config:set_bool(path, key, value)

Read or write a boolean.

Settings:get_int / Settings:set_int

value = config:get_int(path, key, default)
config:set_int(path, key, value)

Read or write an integer.

local port = Settings():get_int("/settings/NRPE/server", "port", 5666)
nscp.info("NRPE port is: " .. port)

Settings:save

config:save()

Persist any in-memory changes back to the settings store.

Settings:register_path

config:register_path(path, title, description)

Register a settings section for documentation and WEB UI purposes.

Settings:register_key

config:register_key(path, key, type, title, description, default)

Register an individual settings key. type is a type hint string ("string", "int", "bool").

local config = Settings()
config:register_path("/settings/my_script", "My Lua script", "Configuration for my Lua script")
config:register_key("/settings/my_script", "interval", "int",
                    "Sampling interval",
                    "How often, in seconds, to sample",
                    "60")

A complete example

A script that registers two checks and a passive submission handler:

-- scripts/lua/example.lua

-- A simple active check
local function check_random(command, args)
    local n = math.random(0, 100)
    if n > 90 then
        return "critical", "Random value " .. n .. " is too high", "'random'=" .. n .. ";80;90"
    elseif n > 80 then
        return "warning", "Random value " .. n .. " is high", "'random'=" .. n .. ";80;90"
    else
        return "ok", "Random value " .. n .. " is fine", "'random'=" .. n .. ";80;90"
    end
end

-- A passive submission handler that logs every submission
local function on_submit(channel, command, code, lines)
    for msg, _ in pairs(lines) do
        nscp.info(string.format("[%s] %s %s: %s", channel, command, code, msg))
    end
    return true, "ok"
end

-- A cmdline command for ad-hoc invocation
local function say_hello(command, args)
    return "ok", "Hello, " .. (args[1] or "world")
end

local reg = Registry()
reg:simple_query("check_random", check_random, "Random-number sanity check")
reg:simple_subscription("LOG-SUBMIT", on_submit, "Log every submitted result")
reg:simple_cmdline("say_hello", say_hello, "Greet someone")

function on_start()
    nscp.info("example.lua ready")
end

function main(args)
    nscp.info("Invoked from the command line with " .. #args .. " arguments")
    return "ok", "done"
end

Enable it via:

[/modules]
LUAScript = enabled

[/settings/lua/scripts]
example = example.lua