Mako Server

Mako Server Logo The Mako Server is constructed utilizing the Barracuda App Server C Library, serving as a reference implementation that showcases how to craft a standalone application server with this specific library. Designed for compatibility, the Mako Server can be constructed for various high-level operating systems (HLOS) including Linux, Windows, QNX, and VxWorks. This documentation details the procedures to use and configure the Mako Server's extended features. For detailed guidance on building the Mako Server, please refer to Building the Mako Server. See Xedge if you require an RTOS based development environment.

Using Embedded Linux? Don't miss out on the valuable insights and time-saving tips in the article Embedded Linux Web Interface Design! This resource provides examples on how to control GPIOs and other helpful information for those using embedded Linux.

Pre-Compiled Mako Server Versions

Real Time Logic provides pre-compiled binary versions at the makoserver.net website including many tutorials.

The pre-compiled versions include the following additional modules:

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

The pre-compiled 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.

Mako Server's resource file mako.zip

The Mako Server's core logic is implemented in Lua and can be found in mako.zip. The server cannot operate without this ZIP file and will use an embedded version with simplified logic if it is unable to find mako.zip.

You may set the Mako Server in developer mode if you wish to change any of the Lua logic in mako.zip. The following example shows how you may set the server in developer mode by setting the environment variable MAKO_ZIP to a directory with 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

You can see from the above printouts how the server mounts the directory makozip instead of mako.zip.

Command-Line Options

The Mako Server is extremely easy to deploy (install) since only two files are required, the mako executable and the mako resource file mako.zip. The server does not need installation and can be run in a console window. 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.

List all available command line options

The Mako Server will start as a foreground process if no arguments are provided. You may 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 detatching from the console
 -s                       - Run in daemon mode without detatching 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.

An alternative to using the above command is to add the CAP_NET_BIND_SERVICE capability, which is explicitly defined as the capacity for an executable to bind to a port less than 1024, including 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 commands 1 to 8 above 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 in order to install a service. The Mako Server will request elevated permissions if the process or command window from where the service is installed does not run with elevated permissions.

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:

A recommendation is to 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 and the sub directory 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 working, install the server as a service as follows:

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

Loading Applications

An application in the context of the Barracuda App Server is comparable to the 'www' directory found in 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.

In the event of a name conflict, the server provides a priority option that can be assigned to each application. This priority setting determines which application takes precedence when a conflict occurs.

Applications are created through the Lua code found 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 by using command line options and/or by using the configuration file.

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. This means that resources are initially searched for in the non-deployed application and then fall back to the deployed application if not found in the non-deployed application. 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 handy for running initialization scripts, one-time setup tasks, or quick automated processes that require the extended Lua APIs provided by Mako Server but don't need the server to continue running afterward.

Example Usage:

mako myscript.lua

In the example above, the server will load and execute myscript.lua. After the script finishes, the server will terminate 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 by using the "-c" command line option, the Mako Server looks for a configuration file by the name 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/status information via e-mail. Log data is accumulated internally in the log module and an email is dispatched when either the accumulated data is larger than 'maxsize' or data has been accumulated for longer than 'sdelay' seconds. E-mails may also be sent immediately if the flush option is set when calling mako.log().

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 it's 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 offers seamless certificate installation and renewal automation, requiring no manual user intervention. You can enable the plugin either through the mako.conf configuration file or programmatically using the two specialized modules acme/bot and acme/dns. Programmatically using the modules is required if you intend to develop a web interface allowing users to configure ACME settings.

For a comprehensive guide on using Let's Encrypt, refer to the tutorial Using The Let's Encrypt Plugin. If you're interested in a quick overview of the "acme/dns" module and how to utilize the optional reverse connection bridge, consult the Secure-Remote-Access tutorial. Both tutorials are designed to provide you with step-by-step instructions for effortless certificate management.

See the tutorial Using The Let's Encrypt Plugin for a detailed introduction to using Let's Encrypt.

Note: The Let's Encrypt plugin creates a "cert" sub-directory in the home directory when activated. Certificates and Let's Encrypt configuration data are persistently stored in this directory. You must move this sub-directory 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 via e-mail.

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,
   rsa=true,
   email="john.doe@company.com",
   domains={"company.com"}
}

The above 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 the HTTP port 80. You must use the automatic or manual dns-01 challenge options if the server is not publicly available.

The following two example shows how to activate the automatic dns-01 challenge:

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

acme={
   acceptterms=true,
   rsa=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,
   rsa=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,
   rsa=true,
   email="john.doe@company.com",
   domains={"mydevice.company.com"},
   challenge={type="dns-01", servername="manual"}
}

The above 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/console, 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' sub-directory 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 and an application can be one of: directory, ZIP file, network path. Applications can be loaded by using the command line option -l and/or by creating an 'apps' table in the configuration file.

Table app 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

The SSL Certificate option lets you load your own certificates. This option is great if you plan on creating your own public key infrastructure (PKI) by, for example, 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.

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/. Option two above requires a user database. Option two also calls the TraceLogger's dir:configure() method with the provided table thus enbling 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

As an option to the end user, you may hide the creation and modifications of mako.conf; however, any changes require the Mako Server being restarted to take effect. As mentioned above, mako.conf uses Lua syntax and is loaded as a Lua table by using 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 applications IO interface. Note: the Barracuda io object shades Lua's standard io object. You may access the Lua io by prefixing the value 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.

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.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 arguments keys and certs must have identical sized arrays with the keys and certs in the same order. Each element in the array is a string, which must be 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 e-mail 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 dispatched when either the accumulated data is larger than 'maxsize' or data has been accumulated for longer than 'sdelay' seconds. E-mails may also be sent immediately if the flush option is set.

Parameters:

You may optionally set and 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 trace name/location. This function enables you to dynamically change the name and/or location of the trace file in a running system. The argument "name" is the full trace file name including the path, which can be a relative path or an absolute path. 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 via command line parameters and the applications loaded via the configuration file. You may also dynamically create new applications.

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

Dynamically create and load a new application.

Parameters:
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().

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)

Remove an app from the internal app DB 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 Application 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

The above 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" sub-directory in the home directory when activated. Certificates and Let's Encrypt configuration data are persistently stored in this directory. You must move this sub-directory 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 in combination with acmedns.auto() may be used as an alternative to using 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 e-mail 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 the key is the domain name and the 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 name(s) may not be the same as the name(s) set when calling acmebot.set() or domains set via the mako.conf configuration file. The domain name(s) may be changed when using dns-01 challenge in combination 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 would normally be 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' sub-directory 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. A detailed tutorial can be found on the Mako Server site.

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

When using the 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 optionally called by an LSP page when a user initially configures the system by providing his/her 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. Calling these functions when the acme options are set re-programs module acmedns and acmebot with new settings and the previous settings are discarded.

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

acmedns.active()

Returns nil 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 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 may check if 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; returns no useful information
  • acmedns.auto(op) - may optionally be called if previously registered; returns email,domain 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 amce/dns module runs.
  • String subdomain - Only include the subdomain name and not the fully qualified domain name. For example, if the domain your registered with the DNS service was company.com and you want the device domain name to be device.domain.com, use the string "device". Note that the online DNS service decides on 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. In this case, the signed certificate will be for device1.company.com. You may check if 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 immediately. No value is returned.
  • if called as: acmedns.auto(op) - the function activates a background service and returns the registered email address and the full domain name including the sub domain name if previously registered. The value nil,error is returned if the module has not been previously registered or if the certificate and/or private-key is missing.

Throws:

The function throws an error if a security module is not installed or alternatively, acmedns.configure() has not been called. An error is thrown since this is regarded 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={rsa=true,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 if a subdomain name is already in use by the zone. This function may be used by a user interface prior to calling acmedns.auto and may be used for presenting a name conflict warning.

Parameters:
  • String subdomain - The sub-domain 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.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 e-mail 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. The functions should only be used if the manual mode has been activated:

acmedns.loadcert()

Manually load the Let's Encrypt signed certificate. This function must be called at system start when using acmedns.manual(), but should not be used when automatic mode is enabled. The function returns true if the certificate is loaded and false otherwise. The function returns false if the device has no certificate or if 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 time some for the TXT record to be 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
?>

The above fully working example shows the complete process, from activating manual mode, to presenting the user with DNS TXT settings, followed by continuing the renewal process when the user clicks the submit button. However, note that the above is for illustration purposes only. A more advanced state machine would be needed for production code. For example, function acmedns.manual() should only be called at startup and/or when user changes the domain or email address. Blocking an LSP page by calling ba.sleep as done above 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 = {rsa = true, 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 be 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 pre-compiled 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, in which subdomain is the name set when calling acmedns.auto() or set via the configuration options. The integrated security module is used if you enable the automatic dns-01 challenge option without providing your own settings via the configuration file or by calling acmebot.configure().

See the Mako Server tutorial Lets-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, in which 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 and 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 pre-compiled 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 simplifies some of the tedious and repetitive operations when working with the luasql bindings. See the Lua SQLite Tutorial for examples on how to use this library. You may also study 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)

Function su.open() creates a database environment object by calling luasql.sqlite() and then opens the database file by calling env:connect().

Function su.open() returns two values, the database environment object and a database file connection object.

name
The database name without a path. The complete path and name is computed by the sqlutil library. The open function tries to open/create the following path and name in descending order:
  1. 'Configuration file'.dbdir is an optional parameter you can set in the Mako Server configuration file.
  2. The configuration directory, if the configuration file is specified with the -c option.
  3. The current directory.
  4. The Mako Server's installation directory (where the executable is stored).

The complete database name is calculated as follows: (path from 1-4 above)/data/(name).sqlite.db

options

Options can be one of the two strings 'READONLY' or 'NOCREATE'. READONLY opens the connection in read only mode, and NOCREATE prevents creation of an SQL database.

su.dir([path])

Get the database directory (if no argument) or set the database directory, thus overriding the path calculated in step 1-4 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)

Execute the provided SQL query and call the provided 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.