Using the Mako Server

Mako Server Logo

The Mako Server is a ready-to-run Lua application server built on the Barracuda App Server C Library. It packages BAS, Lua, Lua Server Pages, SQLite support, TLS, WebSockets, and server-side APIs into a single executable for high-level operating systems such as Linux, Windows, macOS, QNX, and VxWorks.

This page explains how to use and configure the Mako Server runtime. For downloads, getting-started material, tutorials, and the broader product overview, see makoserver.net. For source-level build instructions, see Building the Mako Server with BAS. See Xedge if you need an RTOS-based Lua application environment.

If you are working with Embedded Linux, check out the Embedded Linux Web Interface Design Tutorial. It covers practical examples for controlling GPIOs and provides useful insights to streamline web interface development in embedded systems.

Quick Map

GoalSection
Use a precompiled serverPrecompiled Mako Server Versions
Understand mako.zipMako Server's resource file mako.zip
Start the server and load applicationsCommand-Line Options
Configure the serverConfiguration File (mako.conf)
Use the browser-based IDEThe Mako Server's Integrated Development Environment
Use Mako runtime APIsFunctions, IO interfaces, and Modules

Precompiled Mako Server Versions

Most users should start with a precompiled Mako Server from makoserver.net. The site also provides tutorials, getting-started material, and ready-to-use examples. Build from BAS only when you need to customize the executable, add C modules, change compile-time options, or port Mako to a specific platform.

The precompiled versions include the following additional modules:

Pre-integrated DNS zone key for domain local.makoserver.net.

The precompiled Mako Server includes an embedded zone key for Real Time Logic's DNS testing service. See the Let's Encrypt configuration options and module acme/dns for more information.

🚫 Warning - Do Not Use the Included Key in Production

The key embedded in the tokengen.c file is intended only for easy testing of SharkTrustX features with the local.makoserver.net domain. For deployment, you must run your own SharkTrustX service with a securely generated key.

Mako Server's resource file mako.zip

The Mako Server's core runtime logic is implemented in Lua and stored in mako.zip. A normal deployment consists of two files: the Mako executable and mako.zip. If the executable cannot find the external mako.zip, it falls back to the embedded ZIP compiled into the executable. The embedded ZIP contains simplified fallback logic and should not be treated as a replacement for the full external resource file.

If you are new to the Mako Server or using it for development, we recommend using the mako.zip Developer Edition.

Embedding applications in mako.zip

The Mako Server can load applications from command-line arguments or from instructions in mako.conf. When distributing a finished product, however, it is often more convenient to package the applications directly inside mako.zip so the deployment remains self-contained.

To embed applications in mako.zip, create the following directory:

mako.zip/apps/

Each application must be placed in its own subdirectory. For example, an application named myapp should be installed as follows:

mako.zip/apps/myapp/

By default, the application's base URL path is derived from the directory name. In this example, the application is available under the URL path /myapp/.

You can override the default base path by creating a configuration file named .config in the application's root directory. This Lua-parsed configuration file supports the following settings:

{
   name = "basepath", -- Set to "" to install as the root application.
   prio = 0           -- Application directory priority.
}

All embedded applications are installed as LSP applications. If an application should be loaded for internal use only and should not be accessible through a URL, create a .preload script in the application's root directory that executes the following statement:

dir:unlink()

This removes the application's URL mapping while still allowing the application to be loaded and used internally.

To modify the Lua logic in mako.zip, run the Mako Server in developer mode. The following example sets the environment variable MAKO_ZIP to a directory containing a copy of the mako.zip content:

$ mkdir makozip
$ cd makozip/
$ unzip ../mako.zip
$ cd ..
$ export MAKO_ZIP=makozip
$ mako

Mako Server. Version 3.0
BAS lib 4396. Build date: Jun 18 2019
Copyright (c) Real Time Logic.

Mounting makozip
Server listening on IPv6 port 9357
Server listening on IPv4 port 9357
Loading certificate MakoServer
SharkSSL server listening on IPv6 port 9443
SharkSSL server listening on IPv4 port 9443

The output shows that the server mounts the directory makozip instead of mako.zip.

Command-Line Options

The Mako Server is easy to deploy because only two files are normally required: the mako executable and the mako.zip resource file. The server does not need to be installed and can run directly in a console window.

For a complete beginner's tutorial, see Getting Started with the Mako Server.

On Windows, the server can optionally be installed as a Windows service. On all other platforms, the server can optionally be installed as a daemon (background) process. See the following tutorials for details:

List all available command line options

The Mako Server starts as a foreground process if no arguments are provided. You can request the server to list all commands and then exit as follows:

mako -?

Configuration File

You may specify the location of an optional configuration file.

Example:
mako -c mytestdir/mako.conf

See the Configuration File documentation for more information on how to use the configuration file.

Linux and Mac Options:

 -d                       - Run in daemon mode by detaching from the console
 -s                       - Run in daemon mode without detaching from the console
 -u username              - Username to run as
Example:
sudo mako -l::myapp.zip -u `whoami`

Note: The server will open alternate ports above 1024 if it is unable to open the default ports.

Instead of using this command, you can add the CAP_NET_BIND_SERVICE capability, which allows an executable to bind to ports below 1024 even when run as a non-root Linux user.

sudo setcap cap_net_bind_service=+ep /path/2/mako

Microsoft Windows options:

 -install               - Installs the service
 -installauto           - Installs the service for autostart
 -remove                - Removes the service
 -start                 - Starts the service
 -stop                  - Stops the service
 -restart               - Stops and starts the service
 -autostart             - Changes the service to automatic start
 -manual                - Changes the service to manual start

 -minimize              - Minimize the console window
 -elevate               - Request elevated privileges

The first eight commands are for installing and managing the server as a Windows service. For example, the following command installs the server as a service, enables auto-start, and starts the service:

mako -l::myapp.zip -installauto

On Windows, administrative privileges are required to install a service. The Mako Server will request elevated permissions if the process or command window from which the service is installed is not already elevated.

The two command line options -elevate and -minimize are designed for creating web-based dashboard-like applications for Windows when not running as a service. When using both options, use the command line sequence -elevate -minimize to leave the current command line window (if any) "as is" and to only minimize the new command window that opens when running in elevated mode.

Windows Service Installation Example:

This section provides a quick introduction to running the Mako Server as a Windows service. For a more detailed explanation, see the tutorial Installing the Mako Server as a Windows Service.

Use a Mako Server Configuration File when starting the Mako Server as a Windows service. The following example uses the directory c:\wwwapps as the base directory and the subdirectory mywww for storing the web application (HTML, LSP, etc.).

The configuration file "mymako.conf" must use absolute paths:

apps={
   { name="", path="/c/wwwapps/mywww"}
}
homeio="/c/wwwapps"

Initially, test the configuration file without installing the server as a service:

mako -c c:\wwwapps\mymako.conf

Make sure you can navigate to your 'mywww' app. When it works, install the server as a service as follows:

mako -c c:\wwwapps\mymako.conf -installauto

Loading Applications

In the Barracuda App Server, an application is comparable to the 'www' directory used by other servers. Multiple web applications can be loaded simultaneously, and each application can be assigned a base URL. This URL serves as a unique identifier, helping to distinguish it from other loaded applications and prevent URL conflicts.

If two applications use the same name, the server uses each application's priority setting to determine which application takes precedence.

Applications are created by the Lua code in the Mako Server's .config script, using LSP-enabled Resource Reader instances. For more detailed information on this process, refer to the introduction to the virtual file system.

Applications are loaded with command-line options, the configuration file, or both.

Loading apps via the command line:

-l[name]:[priority]:path  - Load one or multiple applications

The following example loads three applications:

mako -lmyapp:1:path/2/myapp -lmyapp:0:myapp.zip -l::/path/2/my-root-app

The two myapp applications have the same base URI, but the non-deployed application has a higher priority than the deployed application myapp.zip. Resources are first searched for in the non-deployed application and then in the deployed application if not found. The last application loaded is a non-deployed root application.

Execute a Lua script and exit

script - Execute the specified script and exit.

Description:

This option allows you to run the Mako Server in a special mode that closely resembles the behavior of the standard Lua interpreter. When you provide a script name as the only argument and without a preceding hyphen, the server will execute the specified script immediately upon starting.

During the execution of the script, the server operates as usual: it opens any configured ports and runs any necessary services. However, once the script completes its execution, the server shuts down completely, closing all ports and stopping all services.

Use Case:

This mode is useful for running initialization scripts, one-time setup tasks, or quick automated processes that require the extended Lua APIs provided by the Mako Server but do not need the server to continue running afterward.

Example Usage:

mako myscript.lua

In this example, the server loads and executes myscript.lua. After the script finishes, the server terminates automatically.

See also Using The Mako Server Instead of Bash For Scripting and XLua.

Configuration File (mako.conf)

The configuration file lets you customize the server's initialization of the Barracuda App Server and perform other initialization tasks. You can also use the configuration file for loading applications and setting your own configuration parameters.

If you do not specify a configuration file with the "-c" command line option, the Mako Server looks for a file named mako.conf in the following directories:

  1. The directory where the Mako Server executable is stored
  2. In the current directory
  3. In directories found in the following environment variables:
    1. HOME (all platforms)
    2. USERPROFILE and HOMEPATH (Windows)

The configuration file uses Lua syntax. The Mako Server loads the configuration file as a Lua table; thus all configuration options must be provided as key/value pairs. The following key/value pairs are the available configuration options:

Certificate Store

When the Mako Server creates a SharkSSL object using ba.create.sharkssl() for its secure server connection object, the associated certificate store is set to nil unless 'certstore' is set. In most typical web server operations, a certificate store is not required. However, if there is a requirement to authenticate HTTP clients, including web browsers, through client-side certificates, then it becomes necessary to establish a certificate store. To request a client certificate from server-side code, use the request:clientcert() method. The client's certificate can be verified using request:certificate(). For this verification to succeed and return true, the certificate store must contain the certificate of the authority that signed the client's certificate. To facilitate the creation of a certificate store for the SharkSSL object, you can specify the necessary configuration in mako.conf. This configuration can be a single string (for one certificate) or a table (for multiple certificates).

certstore = string | table

Examples:

certstore="my-CA-cert.pem"
certstore={"my-CA-cert1.pem", "my-CA-cert2.pem"}

Custom Parameters

Set your own parameters in the configuration file and then access the configuration parameters from the loaded LSP applications. The configuration parameters are made available as a Lua module.

Example: Configuration File:
myparam="This is my custom parameter"
In your Lua code, load the configuration table as follows:
local conf=require"loadconf"
print(conf.myparam) -- prints "This is my custom parameter"

Data Logging

Enable function mako.log(), which sends log and status information by email. Log data is accumulated internally in the log module, and an email is sent when the accumulated data is larger than 'maxsize' or data has been accumulated for longer than 'sdelay' seconds. Emails may also be sent immediately if the flush option is set when calling mako.log().

Download: ready-to-run example code from GitHub.

Table log: Example:
log = {
   smtp = {
      from = "john.doe@gmail.com",
      to = "john.doe@gmail.com",
      subject="My Server's Log",
      server = "smtp.googlemail.com",
      port = 465,
      useauth = true,
      consec = "tls",
      user = "john.doe@gmail.com",
      password = "qwerty"
   },
   logerr = true, -- Send Lua exceptions by email
   signature = "Sent from my Mako Server"
}

Set Home Directory

The Mako Server creates several Barracuda IO instances, and one of them is the home IO, the IO object returned when calling ba.openio("home"). Many applications and modules rely on a working home directory. The home directory is by default set to the execution directory. This works when the server runs in console mode and when the server runs in daemon mode on Linux since a startup script can change to a suitable home directory prior to starting the server in daemon mode. However, this does not work when run as a service on Windows.

The Mako Server provides a platform-neutral way of setting the home directory by using the "homeio" option.

The following mako.conf example sets the home directory to c:\mako-home on a Windows computer:
homeio="/c/mako-home" -- Must use POSIX path

If you are compiling the server's C code for an operating system that lacks the "executable directory" concept, such as VxWorks, you may want to provide a hard-coded default value in the C code. The hard-coded value is used if "homeio" is not set. See the C macro MAKO_HOME_DIR for details.

Let's Encrypt

Let's Encrypt is a free Certificate Authority (CA), and its root certificate is installed in most browsers and secure client devices. Mako Server includes support for the Automated Certificate Management Environment (ACME) protocol used by the Let's Encrypt service.

The Mako Server's ACME plugin enables fully automated certificate installation and renewal with no manual user intervention. You can activate the plugin through the mako.conf configuration file or programmatically by using the specialized modules acme/bot and acme/dns. Use the programmatic option if users must be able to enable or disable the feature, or if you want to fully automate the process without user interaction.

Tutorials:

Note: The Let's Encrypt plugin creates a "cert" subdirectory in the home directory when activated. Certificates and Let's Encrypt configuration data are persistently stored in this directory. You must move this subdirectory if you move the home directory.

Note: Any error is printed to the console when the Mako Server runs in console mode. Errors are also sent to the trace. We recommend enabling the log option when the server runs in daemon mode to receive errors and status information by email.

Proxy: All HTTP requests are routed via the configured proxy if the proxy configuration options are set.

Mako Server automatically loads module acme/bot when the following options are set. The server also loads module acme/dns if the DNS-01 challenge option is set.

Table acme:

Note: When using the automatic dns-01 challenge option, servername, key, and secret are required; however, these settings can be set automatically by a compiled-in security module. See acmebot.configure() for details.

Examples:

The following example shows how to activate the automatic http-01 challenge.

acme={
   acceptterms=true,
   email="john.doe@company.com",
   domains={"company.com"}
}

This example requires that the server has a publicly available IP address or that you have set up port forwarding to port 80. The HTTP challenge option cannot be used if the server cannot be publicly accessed on HTTP port 80. Use the automatic or manual dns-01 challenge options if the server is not publicly available.

The following two examples show how to activate the automatic dns-01 challenge:

1) Server name, key, and secret in cleartext (not recommended):

acme={
   acceptterms=true,
   email="john.doe@company.com",
   domains={"mydevice"}, -- Include only subdomain name
   challenge={
     type="dns-01",
     servername="my-domain-name",
     key="64-byte-key",
     secret="64-byte-secret"
     }
}

2) Server name, key, and secret provided by integrated security module:

acme={
   acceptterms=true,
   email="john.doe@company.com",
   domains={"mydevice"}, -- Include only subdomain name
   challenge={type="dns-01"}
}

See function acmedns.auto() for more information on how to use the automatic dns-01 challenge option.

The following example shows how to activate the manual dns-01 challenge.

acme={
   acceptterms=true,
   email="john.doe@company.com",
   domains={"mydevice.company.com"},
   challenge={type="dns-01", servername="manual"}
}

These configuration options activate module acme/dns in automatic renewal mode, but with manual DNS configuration. Module acme/dns prints a message in the console and sends you an email (if the log options are enabled) when it is time to renew the certificate. After setting the DNS TXT record as instructed in the email or console message, run the following LSP script:

<?lsp
local ok,err = require("acme/dns").recordset()
if ok then
   print"Resuming renewal process..."
else
   -- Renewal process most likely not active
   print("Resuming renewal process failed:", err)
end
?>

See the acmedns example for additional information on using function acmedns.recordset()

Certificates and JSON Files

The Let's Encrypt plugin creates a 'cert' subdirectory in the home directory for certificate storage and JSON configuration files. See the Module acme/bot for details on these files. Module acme/bot is used when enabling the Let's Encrypt options in mako.conf.

Loading Applications

The Mako Server can load multiple applications. An application can be a directory, ZIP file, or network path. Applications can be loaded with the command line option -l, by creating an 'apps' table in the configuration file, or both.

Table 'apps' is a list of one or several of the following:

The following example loads three applications: the first application is provided as a string, and the other two applications are provided as a Lua table. Providing a Lua table allows for more configuration options. The last application is loaded as a root application since we set the name to an empty string. The first application's name is set to the name of the directory 'app1'.

apps={
   "/path/2/app1", -- path to 'www' directory. Base URL is: http://server/app1
   {
      name="myapp", -- Base URL is: http://server/myapp
      prio=1, -- Higher priority (for directory/resource name conflict resolution).
      path="/path/2/app2" -- path to 'www' directory
      dav=true, -- WebDAV at /myapp/dav/
   },
   {
      name="", -- Root app. -- Base URL is: http://server/
      path="/path/2/app3"
      dav="fs", -- WebDAV at /fs/
      auth=true, -- Enable WebDAV authentication
   }
}

Port and Interface

Proxy Options

The proxy configuration options are required if the server runs in an environment that requires a proxy for connecting to the Internet.

Table proxy:

SSL Certificate

If you cannot use our recommended Let’s Encrypt option, you can still load your own SSL certificates. For instance, you could create your own public key infrastructure using our free Certificate Management Tool. Note that the SSL Certificate option cannot be used in combination with the Let's Encrypt option.

SSL RSA v.s. ECC Certificate

Mako Server can load multiple certificates for various domains, but Mako Server also lets you load multiple certificates for the same domain name. SharkSSL then selects the certificate with the strongest cipher selection accepted by the client. Elliptic Curve Cryptography (ECC) certificates typically have a higher ranking than RSA certificates. The favorRSA option enables you to still favor RSA for clients that support both EC and RSA certificates. This option makes it possible to have a self signed ECC Certificate Authority (CA) and ECC chain for devices and serve RSA certificates for browsers. See the Certificate Management for IoT tutorial for details.

The /rtl/ Directory Priority

  1. Number rtlprio - set the RTL directory priority; the default is 127

TraceLogger

Enable the TraceLogger by setting one of the following two options:

  1. Boolean tracelogger - Enable unprotected TraceLogger by setting tracelogger=true.
  1. Table tracelogger - Enable protected TraceLogger by setting tracelogger={auth=true}.

When the TraceLogger is enabled, navigate to http://your-server/rtl/tracelogger/. The second option requires a user database. The second option also calls the TraceLogger's dir:configure() method with the provided table, enabling additional TraceLogger configuration.

Example:
tracelogger={
   auth=true,
   priority= 1 -- Filter out lower priority messages
}
users={
   root="qwerty",
}

User Database

You can setup a static user database that can be used by the WebDAV server(s), the TraceLogger, and custom authenticators. The user database is a key-value pair, where the key is the username and the value is the password. The password can be cleartext or HA1.

Example:
users={
   joe="querty", -- Joe's password is 'querty'
   alice={'ef8fef1da0258aea0d0d234ab05debaf'} -- Alice's password is a HA1 hash
}

Function mako.udb() returns the static user database as an Authenticator User Database Object.

Realm name and HA1

The realm is used by the Mako Server authenticators and the default realm name is "Barracuda Server". You may change the realm name as follows:

realm="My realm name"

Web File Server

Create a system wide Web File Server (WFS) at startup.

Table fileserver:

Note: Unless noauth is set, the "fileserver" requires a user database and the fileserver parameter is ignored if no user database is configured.

MS Windows Example:
fileserver={
   fsname="fs",
   ioname="disk",
   path="/C",
   lockdir="temp/.LOCK"
}

Advanced Options

The Barracuda App Server powering the Mako Server is designed for deep embedded systems where one may have requirements on resource allocation. The server is therefore designed such that one must pre-allocate the number of resources required. The following allows you to modify these options. See the C reference manual for detailed information.

Programmatically changing mako.conf

You may hide the creation and modification of mako.conf from the end user; however, any changes require the Mako Server to be restarted before they take effect. As mentioned earlier, mako.conf uses Lua syntax and is loaded as a Lua table by module loadconf.

Converting the loaded Lua table back to a configuration file can be done by using a library such as inspect.lua.

Mako Server Functions and Environment

The Mako Server includes all Barracuda App Server APIs and features. The Mako Server also includes the following extended features:

Application environment and the .preload script

The Mako Server has the concept of an application environment. The environment for an application is a location where you can keep commonly used Lua code, i.e., library functions. This environment is available to LSP pages in the application, but not to LSP pages in other applications. The application environment is referred to as the application table in our documentation. The Mako Server provides a number of environments that can be used by LSP programmers.

The Mako Server creates an application table/environment per loaded application. This table is known as the "app" table and is explained in section Command (Request/Response) Environment. See the Mako Server tutorial Lua Environments and the .preload Script for a more in-depth explanation on how to use the .preload script.

The Mako Server looks for the .preload file in the application's root directory when starting an application. This file is loaded and executed as a Lua script, if found.

The preload script runs in the 'app' table environment (_ENV) and comes with the following pre-set values:

  1. Userdata io - the application's I/O interface. Note: the Barracuda io object shadows Lua's standard io object. You may access Lua's io by prefixing it with _G, e.g., _G.io.
  2. Userdata dir - the application's resource reader directory.
  3. Table app - _ENV.app == _ENV and app == _ENV
.preload example:
trace("Starting my application")
trace("Path = "..io:realpath(""))
trace("Base URL = "..dir:baseuri())

function onunload()
   trace("Stopping my application")
end

The optional function 'onunload' is run when the application stops either when the server terminates or if the application is stopped by calling mako.stopapp or mako.reloadapp.

Functions and data in the .preload script are added to the application table. You can access this table from an LSP page as "app". For example, a function such as myfunc() can be accessed from an LSP page as app.myfunc().

For more information, see the tutorial Lua Environments and the .preload Script.

The Mako Server's Integrated Development Environment

You can use any source code editor to develop Lua applications for the Mako Server. For a fully integrated browser-based development workflow, load the Xedge IDE as a Mako Server application. This is the high-level operating system version of Xedge described in The Two Xedge Versions.

Download a pre-assembled Xedge.zip
or
Build the Xedge IDE:

  1. Clone the BAS Resources repository: https://github.com/RealTimeLogic/BAS-Resources/
  2. Navigate to the build directory.
  3. Run the appropriate build script:
  4. When prompted to minify the resources, select n (no).

The build process generates a file named Xedge.zip.

Starting the IDE

  1. Copy Xedge.zip to a convenient location, for example the same directory where the Mako Server executable is located.
  2. Start the Mako Server and load the IDE: mako -l::Xedge.zip
  3. Open a browser and navigate to the server's address.

The Xedge IDE will now be available in your browser, allowing you to edit, manage, and deploy Lua applications directly from the Mako Server runtime. For detailed information about using the IDE and its additional APIs, see Using Xedge.

Functions

mako.dos2unix(path)

A function on the Windows version that translates a Windows path to a path that can be used by the IO interface.

mako.createloader(io)

Mako Server's require() function searches for Lua files in the same locations where the standalone Lua interpreter searches. Mako Server may be set to extend require()'s search path and enable loading of Lua scripts from within the application. To allow the loading of Lua script modules from your application, add the following line to the beginning of the application's .preload script.

mako.createloader(io) -- io is the application's pre-defined IO interface

The loader enables "require" to search for files in ".lua," a subdirectory in your Lua application. The loader works for both deployed and non-deployed Lua applications. In other words, the loader is effective for both deployed (ZIP) and non-deployed (directory) application formats, ensuring flexibility across different deployment scenarios.

The example application require-test (GitHub) shows how to use function "require" with Lua modules in a Mako application.

mako.exit([code])

Immediately terminates the Mako Server process with an optional exit code.

This function is only available when the server is running in the foreground. Internally, it calls the standard C exit() function and does not perform a graceful shutdown - open socket connections and other resources are not explicitly closed. Use this function for development or controlled shutdown scenarios where cleanup is not required.

Parameters:
mako.loadcerts([keys, certs])

The function re-loads the certificates specified in mako.conf if no arguments are provided. The function enables an application to install new SSL certificates and then load the new certificates without having to restart the server. The Mako function loadcerts() is internally using the BAS function ba.create.servcon in the mode that enables it to recycle an existing server listening connection.

You may also dynamically load new certificates by providing the two arguments. The keys and certs arguments must be arrays of identical size, with the keys and certificates in the same order. Each array element is a string containing the pathname.

Note: Do not use this function if the Let's Encrypt plugin is enabled since use of this function would conflict with the Let's Encrypt plugin's use of this function.

mako.log(message [, op])

Sends the message by email if data logging has been enabled in the configuration file. The function returns true if data logging is enabled and false otherwise.

Log data is accumulated internally in the log module, and an email is sent when the accumulated data is larger than 'maxsize' or data has been accumulated for longer than 'sdelay' seconds. Emails may also be sent immediately if the flush option is set.

Parameters:

You may optionally set or override the following configuration options when 'flush' is set to true: 'from', 'to', and 'subject'. You may also add 'cc' and 'bcc'. See the SMTP library for details.

mako.sharkclient()

Deprecated. Use function ba.sharkclient().

mako.tracefile(name)

Change the trace file name or location. This function enables you to dynamically change the trace file in a running system. The argument "name" is the full trace file name, including the path, which can be relative or absolute. The function returns true on success. The trace file is used by function trace(), Lua exceptions, and any traces enabled by the TraceLogger.

mako.udb()

This function is created by mako.zip:/.config if a static user database has been set up in mako.conf.

The function returns the static user database as an Authenticator User Database Object.

Example usage:
local auth = ba.create.authenticator(mako.udb())

Dynamic Application Management

You may control the applications loaded through command line parameters and the applications loaded through the configuration file. You may also dynamically create new applications.

mako.createapp(name, priority, path [,conf])

Dynamically create and load a new application.

Parameters:

Returns true on success and nil, err if the .preload script fails.

mako.getapps()

Returns a table with all apps loaded by the Mako Server. The following example restarts all applications:

for ix,path in ipairs(mako.getapps()) do
   print("Reloading:",path)
   mako.reloadapp(ix)
end
mako.reloadapp(x)

Reloads the .preload script for an app. Parameter x is either the application's environment or an index position returned by function mako.getapps(). The following code snippet can be used by an LSP page to reload the .preload script for the app:

mako.reloadapp(app) -- reload 'self'
mako.stopapp(x)

Stops a registered application. Parameter x is either the application's environment (_ENV: app tab) or an index position returned by function mako.getapps().

Returns true on success and false, err if the .preload script has an unload function that failed. The function throws an error if parameter x is invalid.

mako.startapp(x)

Starts a registered application. Parameter x is either the application's environment or an index position returned by function mako.getapps().

mako.removeapp(x)

Removes an app from the internal app database stored in mako.zip:/.config. Note: this function does not change static options such as mako.conf. Parameter x is either the application's environment or an index position returned by function mako.getapps().

Mako Server IO interfaces

The Barracuda App Server provides an IO interface in addition to the standard Lua input/output operations. These IO interfaces must be created by C code, and the Mako Server's C startup code creates the following IO interfaces:

The following example shows how to list all available IO interfaces:

for name,io in pairs(ba.io()) do
   print(name, ":", io:realpath"", " : ", io:resourcetype())
end

This example produces the following output when run on Linux:

disk    :       /        :      disk    POSIX
home    :       ./       :      disk    POSIX
net     :       nil      :      net
vm      :       nil      :      zip     zip

See io:realpath() and io:resourcetype() for details on the printouts.

Global Variables

The server makes the following global variables accessible to the loaded applications:

Modules

Mako Server includes all Barracuda App Server modules and protocols in addition to the following Mako Server specific modules:

acme/bot.lua

acmebot=require"acme/bot"

Module acmebot is automatically loaded by mako.zip/.config if mako.conf includes the Let's Encrypt settings.

Note: The module creates a "cert" subdirectory in the home directory when activated. Certificates and Let's Encrypt configuration data are persistently stored in this directory. You must move this subdirectory if you move the home directory.

When activated, module acmebot runs in the background and uses module acme engine for the Let's Encrypt communication. The module returns a table with functions, where the following functions may be used by your programs:

acmebot.configure(email,domains,op)

The 'configure' function, combined with acmedns.auto(), can be used as an alternative to the Let's Encrypt configuration option in mako.conf. The function configures acmebot and prepares the module for automatic certificate renewal.

This function is automatically called by Mako Server's .config script if the Let's Encrypt configuration options are set in mako.conf. Calling the function when the acme options are set re-programs acmebot with new settings and the previous settings are discarded.

Parameters:
  • String email - The email address used when signing up for the Let's Encrypt service.
  • Table domains - A list of domains and for each domain you want Mako Server to: create a private key, create a CSR, and request Let's Encrypt to sign and create a certificate.
  • Table op - Options:
    • Required: boolean acceptterms - Must be set to true to indicate acceptance of the Let's Encrypt terms.
    • Optional: boolean cleanup - When using http-01 challenge, any certificate previously set and that is not in the new list of domains will be deleted from the "certs" directory.
    • Optional: boolean production - Set to true if you want to use the Let's Encrypt production environment. The default is to use the staging environment (test server).
    • Optional: boolean rsa - Set to true if you want to use RSA certificates. The default is to use ECC certificates.
    • Optional: number bits - The key size for RSA certificates. Default is 2048.
    • Optional: function renewed - Callback function called when a certificate has been renewed. Parameters: renewed(domain, x509key,x509cert).
    • Optional: table ch - (advanced option) The ACME challenge option is a table (interface) with callback functions. Acmebot uses the default http-01 challenge if not set.
acmebot.start()
Start (activate) the engine.
acmebot.getdomains()

Returns a table with the domains saved in cert/domains.json, where each key is a domain name and each value is the certificate expiration time in the format 'YYMMDDHHMMSSZ'. The certificate has not been issued if the expiration time string is empty. The domain names may differ from the names set when calling acmebot.set() or from domains set through the mako.conf configuration file. Domain names may change when using the dns-01 challenge with the DNS service.

Example:
for domain,exp in pairs(acmebot.getdomains()) do
   if #exp > 0 then
      local y,m,d=exp:match"(%d%d)(%d%d)(%d%d)"
      print(string.format("%s expires year %s, month %s, day %s",domain,y,m,d))
   else
      print(string.format("Certificate not issued for %s",domain))
   end
end
acmebot.status()

This function returns jobs [, domain [,error]

  • Number jobs - The number of active certificate renewal jobs. This number is normally zero.
  • String domain - The last domain renewed since server start. This value is nil if no domain has been renewed.
  • String error - Set if the last renewal process failed. Note, renewal failures are also sent to mako.log (trace lib) and sent by email if the log option is enabled.

Certificates and JSON files managed by acmebot

Module acme/bot creates a 'cert' subdirectory in the home directory for certificate storage and JSON configuration files.

Files created and maintained:

acme/dns.lua

acmedns=require"acme/dns"

The purpose of this module is to make it possible to install Let's Encrypt signed certificates for servers running on private networks.

See the list of ACME tutorials for an introduction to this module.

Module acme/dns extends module acme/bot and implements the dns-01 challenge option. This module can be used in automatic mode together with a DNS service, or in manual DNS mode where the server maintainer must manually set a DNS TXT record when the renewal process starts.

When using automatic mode, the only function your program must call is acmedns.auto(), which should typically be called at system startup. The function may also be called by an LSP page when a user initially configures the system by providing an email address and accepting the Let's Encrypt terms.

Note: the functions acmedns.auto() or acmedns.manual() are automatically called by Mako Server's .config script if the Let's Encrypt configuration options are set in mako.conf.

When not using mako.conf, the module is typically activated and reactivated programmatically as follows:

local options={revcon=true,acceptterms=true}
local ad=require"acme/dns"
ad.loadcert() -- Optional; expedite loading
ad.isreg(function(name)
   if name then
      ad.auto(options) -- reactivate
   else -- first time
      -- email and subdomain name provided by user or hardcoded:
      local email,subdom="info@realtimelogic.com", "xyz"
      -- If hardcoded, subdomain is typically derived from MAC address
      ad.auto(email,subdom,options)
   end
end)

acmedns=require"acme/dns"

The module returns a table with functions, where the following functions may be used by your programs:

acmedns.active()

Returns false if the module is inactive, the string "auto" if acme.auto() has been called, or the string "manual" if acme.manual() has been called.

acmedns.configure(op)

Prepare acmedns for automatic DNS mode by setting the zone data. Function acmedns.configure() must be called prior to calling the functions: acmedns.auto(), acmedns.available(), and acmedns.isreg() if a security module is not installed.

Table op:
  • string servername - the domain name used.
  • string key - The 64 byte long SharkTrustX registration key.
  • string secret - The 64 byte long SharkTrustX secret.
acmedns.auto(email, subdomain, op)
acmedns.auto(op)

Activate automatic certificate and DNS management by using an online instance of the SharkTrustX DNS service. Note that the certificate renewal process takes more than two minutes to complete.

The function must be called each time the server starts; however, arguments email and subdomain are ignored if the server is already registered with the online DNS service. You can check whether a certificate is installed by calling acmedns.auto{}. The return value is nil, error if the function must be called with the email and subdomain.

  • acmedns.auto(email, subdomain, op) - must be called initially
  • acmedns.auto(op) - Use this option if previously registered; returns email if previously registered

Parameters:

  • String email - The Let's Encrypt CA service requires a valid email address. The Let's Encrypt service may send a certificate expiration notice to this email address if the certificate is about to expire. This email will not be sent as long as the machine/device running the Barracuda App Server is operational and as long as the acme/dns module runs.
  • String subdomain - Only include the subdomain name, not the fully qualified domain name. For example, if the domain registered with the DNS service is company.com and you want the device domain name to be device.company.com, use the string "device". The online DNS service decides the final name. The subdomain is renamed to "subdomain#" if another device is using the name. For example, the fully qualified domain name will be device1.company.com if "device" is already in use. You can check whether a subdomain is in use by calling acmedns.available(). Function acmebot.getdomains() returns the fully qualified domain name assigned to your device.
  • Table op - Options:
    The options passed on to module acmebot. See function acmebot.configure for required and optional options. You may set the following additional options:
    • string info - An informative string such as product type. The informative string will be shown in the online web interface together with the DNS name and IP address.
    • boolean revcon - Activate the Reverse Connection (revcon) module. The revcon module enables remote server access via the Remote Access Bridge, an integrated SharkTrustX plugin.

Returns:

  • if called as: acmedns.auto(email, subdomain, op) - the function activates a background service and returns the email address if successful; otherwise, nil, error is returned. This mode requires that the device has access to the Internet when the function is called.
  • if called as: acmedns.auto(op) - the function activates a background service and returns the registered email address if previously registered. The value nil, error is returned if the module has not been previously registered or if the certificate or private key is missing. This mode does not require an active Internet connection when the function is called.

Throws:

The function throws an error if a security module is not installed and acmedns.configure() has not been called. This is treated as a program design error.

Example:

The following example shows how the module can be initialized at system startup.

local acmedns=require"acme/dns"
-- Call acmedns.configure(secrets) if a security module is not installed
local op={acceptterms=true}
local email,domain=acmedns.auto(op)
if email then
   print("Your fully qualified domain name is:", domain)
else
   print"Must register"
   acmedns.auto("john.doe@company.com","device",op)
end
acmedns.available(subdomain [,cb])

Check whether a subdomain name is already in use by the zone. A user interface can call this function before calling acmedns.auto to present a name conflict warning.

Parameters:
  • String subdomain - The subdomain name to check.
  • Function cb - Optional callback function that may be used if a non-blocking asynchronous response is required. The callback receives the same arguments as the return values described below:
Returns:
  • Available: - boolean true, string wan-IP, string local-IP
  • Not available: - boolean false, string wan-IP, string local-IP
  • Error: - nil, number http-status, string reason
acmedns.isreg([cb])

Check if the server is registered with the online DNS service. The arguments to acmedns.auto() are ignored when this function returns state "Registered".

Parameters:
  • Function cb - Optional callback function that may be used if a non-blocking asynchronous response is required. The callback receives the same arguments as the return values described below:
Returns:
  • Registered: - string full-domain-name, string wan-IP, string local-IP, string email, string device-key
  • Not registered: - boolean false, string wan-IP, string local-IP
  • Error: - nil, number http-status, string reason
acmedns.setip(ipaddress)

Updates the IP address associated with this device's DNS A record. This function is automatically invoked by acmedns.auto() when automatic certificate management is enabled. However, if the device's IP address changes during operation (e.g., due to a DHCP lease renewal or expiration), you must manually call this function at runtime to update the DNS record.

acmedns.manual(email, domain, op)

Enable the manual dns-01 challenge option.

Important: function acmedns.recordset() must be called after acmebot.manual() has been called and after the server maintainer has set the DNS record for the domain. The renewal process is paused until this function is called, and no new activation may be initiated as long as the current renewal job is paused. When using manual mode, function acmedns.loadcert() must be called when the device starts.

Parameters:
  • String email - The email address used when signing up for the Let's Encrypt service.
  • String domain name - A fully qualified domain name, e.g., company.com or device.company.com.
  • Table op - The options passed on to module acme/bot. See function acmebot.configure for required and optional options.

The manual DNS challenge option must be used with the following support functions:

acmedns.loadcert()

This function must be called at system startup when using acmedns.manual() but can also be used in automatic mode to expedite certificate loading for already registered devices (without waiting for the acmedns.auto() function to complete). If a certificate is successfully loaded, the function returns {key}, {certificate}. Otherwise, it returns false, indicating that the device has either no certificate or that the certificate has already been loaded.

acmedns.recordset()

Call this function if the dns-01 challenge option mode is set to "manual" and after setting the DNS TXT record for your domain name.

Note:

  • The function only exists if the manual mode has been activated.
  • This function should not be called immediately after setting the DNS TXT record since it takes some time for the TXT record to become publicly available. Give it at least two minutes.
acmedns.status()

The function returns a table with the following {record=string, data=string, msg=string} if the certificate renewal job is active and if the job is waiting for the server maintainer to set the DNS TXT record. The 'record' is the DNS record value to set and 'data' is the DNS data value to set. The string 'msg' is a preformatted version of 'record' and 'data'.

local t=require"acme/dns".status()
if t then
   print("record:",t.record)
   print("data:",t.data)
else
   print"Not active. Activate by calling acmedns.manual()"
end
Manual Mode Example:
<?lsp
local acmedns=require"acme/dns"
local acmebot=require"acme/bot"
if request:method() == "GET" then
   local sT,jobs,domain,err
   local op={production=false,acceptterms=true}
   local ok,err=acmedns.manual("john.doe@company.com","device.company.com",op)
   if ok then
      repeat
         ba.sleep(200) -- wait
         sT=acmedns.status()
         jobs,domain,err=acmebot.status()
      until sT or err
      if not sT then ok=false end
   end
   if ok then
      print'<p>Set the following DNS TXT record:</p>'
      print('<pre>record:',sT.record)
      print('data:',sT.data,'</pre>')
      print'<p>Click button two minutes after setting TXT record</p>'
      print'<form method="post"><input type="submit"></form>'
   else
      print('<p>Renewing',domain,'failed, err:',err,'</p>')
   end
else -- POST
   acmedns.recordset() -- Continue with renewal (resume background job)
   local jobs,domain,err
   repeat
      ba.sleep(200) -- wait
      jobs,domain,err=acmebot.status()
   until jobs == 0
   if err then
      print('<p>Renewing',domain,'failed, err:',err,'</p>')
   else
      print('<p>Domain',domain,'renewed</p>')
   end
end
?>

This complete example shows the full process: activating manual mode, presenting the DNS TXT settings to the user, and continuing the renewal process when the user clicks the submit button. However, the example is for illustration only. Production code should use a more advanced state machine. For example, function acmedns.manual() should only be called at startup or when the user changes the domain or email address. Blocking an LSP page by calling ba.sleep, as done here, is not recommended. Instead, use JavaScript and AJAX to poll for server status updates.

When the server restarts, the certificate must be loaded. To do so, include code similar to the following when the server starts. Notice the 'auto' attribute.

local op = {acceptterms = true, auto = true}
acmedns.manual("john.doe@company.com", "device.company.com", op)
Security Module

When using the automatic dns-01 challenge option, servername, key, and secret can be provided in cleartext. You may use the cleartext option for testing purposes, but these parameters should be protected in a production environment. An installed SharkTrustX server can automatically create a security module (C code) that may be compiled into the firmware or dynamically loaded as a DLL/shared library.

A security module is associated with a zone registered with the online SharkTrustX server. The online server generates the C code automatically when you navigate to https://registered-zone-name/cgen. You must first login as an administrator and then navigate to this URL.

The precompiled Mako Server delivered by Real Time Logic includes an embedded security module for the zone (domain name) https://local.makoserver.net/. This zone is registered with Real Time Logic's DNS testing service. The fully qualified domain name will be subdomain.local.makoserver.net, where subdomain is the name set when calling acmedns.auto() or set through the configuration options. The integrated security module is used if you enable the automatic dns-01 challenge option without providing your own settings through the configuration file or by calling acmebot.configure().

See the Mako Server tutorial Let's Encrypt for more information on using the security module.

loadconf.lua

require"loadconf"

Returns all options in mako.conf, including custom options, as a Lua table.

Example:
for key,val in pairs(require"loadconf") do
   print("Key=",key, ", val=", val)
end

log.lua

log=require"log"

Module log is automatically loaded by mako.zip/.config if mako.conf includes the data logging option. Module log activates function mako.log(). The module returns a table with functions, where the following function may be used by your programs:

log.sendmail(op)

Send an email using the log module's pre-configured SMTP object.

Argument op is a Lua table. You may optionally set or override the following log configuration options: ('body' or htmlbody'), 'from', 'to', and 'subject'. You may also add 'cc' and 'bcc'. See the SMTP library for details.

Note that the sendmail function may also route outgoing SMTP requests via a proxy if the proxy options are set in mako.conf.

LPeg

The precompiled Mako Server includes LPeg (Lua Parsing Expression Grammars). LPeg brings PEG to Lua. See the following for details:

Example:

-- Using the LPeg module
local lpeg=require"lpeg"
print(lpeg.match(lpeg.P'a','aaa')) -- prints: 2

-- Using the re module
local re = require"re"
-- find the position of the first numeral in a string
print(re.find("the number 423 is odd", "[0-9]+"))  -- prints: 12    14

sqlutil.lua

su=require"sqlutil"

The sqlutil library streamlines common and repetitive tasks when working with the LuaSQL SQLite module. It provides a structured, opinionated layer on top of LuaSQL to simplify database setup, connection management, and query execution.

To understand how this module is intended to be used, first read the Lua SQLite Tutorial. The tutorial demonstrates the recommended usage patterns and serves as the primary guide for working with sqlutil.

For a real-world reference, you can also examine the SharkTrustX database module, which uses sqlutil for all database read operations.

The library is loaded as follows:

local su=require"sqlutil"

The sqlutil functions:

su.open([env,] name, options)

Opens (or creates) an SQLite database connection using LuaSQL, with a consistent, opinionated database file layout. sqlutil.open resolves the database filename from the configured database base directory and then opens the database using env:connect() from LuaSQL's SQLite driver.

Parameters

  • env (optional, userdata)
    A LuaSQL environment previously created with luasql.sqlite().
    If omitted, sqlutil.open will create a new environment internally by calling luasql.sqlite().
  • name (string)
    Logical database name without an extension. The module maps this name to the filename dbBaseDir .. name .. ".sqlite.db". For example, if name is "users", the file becomes users.sqlite.db in the database base directory.
  • options (optional)
    Passed as-is to env:connect(dbFilename, options). Supported values depend on the LuaSQL SQLite driver build you're using.

Returns

  • env (userdata)
    The LuaSQL SQLite environment in use. If you didn't pass one in, this is the newly created environment.
  • conn (userdata)
    The active database connection object returned by env:connect().

Errors

Raises a Lua error if the database cannot be opened or created, with a message like:

Cannot open <resolved path>: <driver error>

If env was created internally and connect fails, the environment is closed before raising the error.

Database location

When the module is loaded, it determines a database base directory and automatically appends a data/ subdirectory to it. All SQLite database files are created or opened inside this data/ directory.

The base directory is selected using the following priority order:

  1. dbdir set in mako.conf
    If dbdir is explicitly defined in mako.conf, that directory is used as the base.
  2. Mako configuration directory
    If a mako.conf file is found, the module uses this file's directory as the base.
  3. Default "home" IO directory
    If neither option is available, the module falls back to the default Mako Server home IO directory.

After selecting the base directory, the module appends:

<dataBaseDir>/data/

You can override the base directory at runtime with:

sqlutil.dir("/path/to/dbroot")

This sets the database base directory to "/path/to/dbroot/", and all database files will be created or opened inside it.

Usage examples

Simplest form (let open create the environment):

local sqlutil = require "sqlutil"
local env, conn = sqlutil.open("users")
-- ...
sqlutil.close(env, conn)

Reuse a shared environment:

local sqlutil = require "sqlutil"
local luasql = require "luasql.sqlite3" -- or your luasql sqlite module
local env = luasql.sqlite()
local env2, conn = sqlutil.open(env, "users")
assert(env2 == env)
-- ...
sqlutil.close(env, conn)
su.dir([path])

Get the database directory (if no argument) or set the database directory, thus overriding the path as explained in su.open().

su.exist(name)

Returns true if the database file exists, otherwise false is returned.

su.find(conn, query)
su.findt(conn, query, table)

Search the database using the provided SQL query and return the first entry found. The first argument is either a connection object or a function that returns env, conn. The database is also automatically closed if the argument is a function.

Example:
local a,b,c = su.find(conn,"a,b,c FROM table WHERE a='10' LIMIT 1")

Function su.findt is similar to su.find, except that the return values are saved in the provided table instead of being returned.

su.iter(conn,sql,tab)

Simplifies iterating the results returned from an SQL SELECT query. When argument 'tab' is set, the response is a table with key/value pairs matching the query.

local function sqlIter(sql)
   local env,conn=su.open"mydb"
   local next = su.iter(conn, sql, true)
   return function()
      local t, err = next()
      if t then return t end
      if err then trace("Err:", err, sql) end
      conn:close()
      env:close()
   end
end

for tab in sqlIter("* FROM MyTable") do -- "SELECT is automatically prepended
  trace(ba.json.encode(tab))
end
su.select(conn,sql,func)

Executes the provided SQL query and calls the callback function func. The first argument is either a connection object or a function that returns env, conn. The database is also automatically closed if the argument is a function.