macrobean logo

macrobean

Macrobean is a self-contained, single-binary web server designed for simplicity, security, and portability. It can serve static files, execute dynamic Lua scripts, query SQLite databases, and handle TLS (HTTPS) traffic without requiring any external runtimes, libraries, or configuration files. It's built for developers who need to deploy simple web applications quickly, hobbyists hosting a personal site, or anyone who values a minimal, dependency-free toolchain.


table of contents


introduction

philosophy: a retreat to sanity

Macrobean is a deliberate rejection of the accidental complexity that plagues modern software deployment. It champions the idea of a self-sufficient artifact: a single, executable file that is the entire application. There are no external runtimes to install, no package managers to appease, and no dependency trees to audit. Deployment is scp. Rollbacks are mv.

This is achieved by combining a minimal C web server with a Lua interpreter, a SQLite engine, and the application's assets, all within a single file.

Macrobean owes a significant intellectual and spiritual debt to the redbean project. redbean demonstrated that a single-file, cross-platform, high-performance web server was not just possible, but that it could be elegant and powerful. More background here: kill the bloat

zip-appended executable

The macrobean binary is a standard compiled executable. The magic lies in what comes after the executable code. A ZIP archive containing all the site assets (HTML, CSS, Lua scripts, etc.) is appended to the end of the binary.

When Macrobean starts, it performs the following steps:

  1. Finds Itself: It opens its own executable file (argv[0]).
  2. Locates the ZIP: It scans backwards from the end of the file to find the ZIP archive's "End of Central Directory" (EOCD) record. This allows it to precisely locate where the appended ZIP data begins.
  3. Maps the ZIP: It reads the entire ZIP archive into memory.
  4. Parses the Central Directory: It reads the ZIP's central directory to create an in-memory index of all the files, including their names, sizes, and offsets within the archive.
  5. Serves Requests: When a request comes in, Macrobean looks up the requested file in its in-memory index and serves the data directly from the memory-mapped ZIP content. No files are ever written to disk (with one specific exception for the database, explained later).

Crucially, the appended ZIP archive must be created with store-only (-0) compression. Macrobean does not decompress files on the fly; it reads them directly. This is a key design decision that keeps the C code simple and the server's footprint tiny.


download

download the latest release

using pre-compiled binaries

The easiest way to use macrobean is to download the pre-compiled binary for your OS. After downloading, unzip the binary and make macrobean.com executable.

chmod +x macrobean.com

Note: On macOS, you may need to remove the quarantine attribute:

xattr -d com.apple.quarantine macrobean.com

build from source

To build macrobean, you need a C compiler (gcc, clang or GNU). Any good C compiler would work.

dependencies (Debian/Ubuntu):

sudo apt-get install build-essential liblua5.4-dev libsqlite3-dev libmbedtls-dev

compile command (no TLS):

gcc -O2 -o macrobean macrobean.c -llua5.4 -lsqlite3

compile command (with TLS):

gcc -O2 -DUSE_TLS -o macrobean macrobean.c -llua5.4 -lsqlite3 -lmbedtls -lmbedcrypto -lmbedx509

creating the final Executable

After compiling, you have the macrobean binary. To create the final, self-contained executable, you need to append your site's ZIP archive.

  1. Prepare your site:
    mkdir -p site
    echo "Hello from within!" > site/index.html
    
  2. Create the store-only ZIP:
    zip -r -0 site.zip site/
    
  3. Append the ZIP to the binary:
    cat site.zip >> macrobean
    

Your macrobean file is now a self-contained web server. You can rename it to macrobean.com and run it directly.


installation (installer.sh)

The installer.sh script provides an interactive way to configure and install Macrobean on your system. It is designed for both macOS and Linux.

To run it, make it executable and then execute it:

chmod +x installer.sh
./installer.sh

The script will guide you through the following steps:

  1. Platform Detection: It automatically detects your operating system.
  2. Configuration Prompts: It will ask you a series of questions to configure the server:
    • Custom Port: Choose the port the server will run on (defaults to 8080).
    • Enable Features: Answer yes (y) or no (N) to enable optional features like Lua scripting, SQLite database support, TLS/HTTPS, developer mode, and file watching.
    • TLS Domain: If you enable TLS, it will ask for your domain name. It then attempts to verify that the domain points to your public IP and uses certbot to issue a free Let's Encrypt certificate. This step requires sudo privileges.
    • External ZIP: You can provide a path to an external site.zip file, which is useful for development.
  3. Installation: It copies the macrobean.com executable to /usr/local/bin/macrobean and makes it executable. This step also requires sudo privileges.
  4. Launch: Finally, it launches the macrobean server with all the flags you selected during the configuration process.

cli flags

Macrobean is configured entirely via command-line flags. There are no config files.

Flag Description
--help, -h Shows the help message and exits.
--port <n> Lets you set the TCP port to listen on. Defaults to 8080. Any custom port setting requires that port to be free.
--zip <file> Use an external, uncompressed site.zip file instead of the one embedded in the binary. This is essential for development, as it allows you to change your site without recompiling or re-bundling.
--dev Enables developer mode, which provides verbose logging, detailed error pages (including stack traces for Lua errors), and enables the /admin.html panel.
--watch Enables hot-reloading. When used with --zip, it monitors the external site.zip file for changes and automatically reloads it. It also re-extracts the database file. Implies --dev.
--lua Enables the Lua scripting engine. Requests for .lua files will execute them. Also enables the init.lua routing file.
--db Enables the SQLite3 engine, making the global db object available in Lua.
--fork Enables a process-per-request concurrency model. For each incoming connection, the main server process will fork() a child process to handle the request. This provides excellent isolation (a crash in one request won't affect the server), but has higher overhead than the default single-threaded model.
--sandbox Enables a strict Lua sandbox. This is highly recommended for production. It removes the io, os, package, and debug libraries to prevent filesystem access and command execution. It also installs a Lua hook that acts as a watchdog, terminating any script that runs for too long to prevent denial-of-service attacks from infinite loops.
--tls Enables HTTPS. Requires --cert and --key to be provided.
--cert <file> Path to your TLS certificate file in PEM format.
--key <file> Path to your TLS private key file in PEM format.

keys

microbean.com --tls --cert cert.pem --key key.pem loads the certs to run the web server in TLS mode. Create a certificate using Let's Encrypt or generate a self-signed certificate with new RSA private key.

 openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes 
./macrobean --tls --cert cert.pem --key key.pem --lua --db --dev
# Test with:
curl -k https://localhost:8080/		

Self-signed certificate is not suitable for production purposes. Use Let's Encrypt for custom domains. certbot needs root access.

$ sudo apt install certbot      							# installs TLS for your domain
$ sudo certbot certonly --standalone -d exampledomain.com

After setup, cert files will be:

magic numbers

In case ./macrobean.com returns zsh: permission denied for appending zip file at the end of macrobean, there is a workaround which does not require a polyglot header file. First confirm the nature of the binary.

$ file macrobean.com 
# Mach-O 64-bit executable arm64
$ hexdump -C /tmp/macrobean.com | head
# This should return magic numbers cf fa ed fe ..  
$ stat -f %z macrobean.com. # A
$ stat -f %z macrobean      # B
$ unzip -l site.zip         # C

A = B + C; if this is not the case, the zip file is either corrupted or not appended properly.


watch

watch mode can only be used with dev mode and polls file modification timestamps at runtime and hot-reloads if changes happen. This works similar to inotify(Linux) or kqueue(macOS), using stat() every 2 seconds. Polling might incure slight CPU load and around ~1s delay.

$ ./macrobean --lua --db --dev --watch --zip site.zip

This auto-reloads the changes made to site/ or data.db on disk without the need to run the command everytime user writes to the zip.

control panel

The endpoint /admin.html works only with dev mode to allow uplifted access to other files in the directory. So, admin has all the access of developer mode besides a UI dashboard for dynamic CMS over .lua and .db. That's it – you can change routes or content directly from the browser like a form editor. If no site/admin.html then server returns not found in ZIP. Add it to site.zip. Server then gracefully falls back to 200 HTML.

$ curl http://localhost:8080/admin
$ site/admin.html not found in ZIP. Add it to site.zip

mime types

We use simple extension-based mapping that covers most web-asset types with predictive behaviour for zip-hosted files. This is different from libmagic used in Apache, Nginx and examines files headers (magic bytes) to identify MIME types. There is also Magika–an ML based MIME detector developed by Google, but that would need a python env. The in-built MIME type matching works well for most cases. Anyways, you can always add an extension according to the directory structure. For example, for typescript:

const char *guess_content_type(const char *filename) {
if (ends_with(filename, ".ts")) return "text/typescript"; 
}

compression

As of now, macrobean supports "stored" (uncompressed) zip structure (files zipped using 0 method). Verify the compression method using $ xxd test.zip | less .

const unsigned char *extract_file_data(const zip_entry_t *entry, size_t *out_size) {
    if ((entry->cmpr_method != 0) && (dev_mode)) {
        printf("DEBUG: File '%s' is compressed (method %d), skipping
",   // skips if not "uncompressed"
               entry->filename, entry->cmpr_method);
        return NULL;
    }

Look for compression method bytes (offset 8 in each local file handler). Some other compression methods (list is not exhaustive):

`1`  --- "Shrunk";
`2`  --- "Reduced (factor 1)"
`6`  --- "Imploded"
`8`  --- "Deflated"
`12` --- "BZIP2"
`14` --- "LZMA"
`18` --- "IBM TERESE"
`20` --- "zstd"
`95` --- "WavPack"
`97` --- "AE-x encryption marker"		

select()

The standard concurrency model is select() with fork() as an optional add-on to the server when use_fork is enabled.

#include 
int use_fork = 0;
// wait for activity on any socket
int ready = select(fd + 1, &readfds, NULL, NULL, NULL);

troubleshoot

Fixing compilation errors for -llua

If you are getting

$ gcc -o macrobean macrobean.c -llua
# error: ... undefined symbols ...

That likely means either lua development headers are not installed or they are not in CPATH or LIBRARY_PATH. Do brew install lua then compile with:

$ gcc -o macrobean macrobean.c -I/opt/homebrew/include -L/opt/homebrew/lib -llua
$ gcc -o macrobean macrobean.c $(pkg-config --cflags --libs lua) 
$ ./macrobean --lua --zip site.zip --dev
# Hit http://localhost:8080/test.lua 
Memory Duplication: Avoid modifying global state inside forked child unless you know it's private. In other words, don't write global variables (or heap structures) in the child after fork, unless you are sure the memory is not shared, or you intend to keep the child around and accept the memory cost. Keep track of high memory usage in child process post-fork:
$ ps aux | grep macrobean.com
$ top
$ htop

lua

When --lua is enabled, Macrobean exposes APIs to your Lua scripts.

request global table

Every Lua script has access to a global request table containing all the information about the incoming HTTP request.

-- Example structure of the global 'request' table
request = {
  -- The full request path, including query string
  path = "/api/users?id=123"
  -- Other request properties would be documented here
}

  -- The HTTP method (e.g., "GET", "POST")
  method = "POST",

  -- A table of query string parameters
  query = {
    id = "123"
  },

  -- A table of request headers
  headers = {
    Host = "localhost:8080",
    ["User-Agent"] = "curl/7.79.1",
    ["Content-Type"] = "application/json"
  },

  -- The raw request body as a string
  body = "{"name": "Alice"}",

  -- A table of parameters from pattern-based routes
  -- This is only populated if the route was matched with a pattern
  params = {
    userId = "456" -- from a route like /users/:userId
  }
}

Your script should return a single string, which will be sent as the HTTP response body with a 200 OK status and a Content-Type of text/plain.

routing with init.lua

If a file named site/init.lua exists, it is executed once when the server starts. This is the ideal place to define your application's logic and routes.

simple routing:

You can define simple, direct routes by adding functions to the global routes table.

routes = {}
routes["/"] = function() return "Home" end
routes["/about"] = function() return "About Us" end

pattern-based routing:

For more complex routes, you can use the route() helper function, which supports named parameters.

-- site/init.lua

-- This function is built-in for you
-- route(pattern, handler)

route("/users/:id", function(params)
  -- The captured 'id' is available in request.params
  local userId = request.params.id
  return "User ID: " .. userId
end)

route("/files/:category/:filename", function(params)
  return string.format("Category: %s, File: %s", 
    request.params.category, request.params.filename)
end)

middleware with routes.before:

You can define a routes.before function that will be executed before every dynamic route handler. If this function returns a string, that string will be sent as the response, and the actual route handler will not be called. This is useful for authentication, logging, or other pre-request checks.

-- site/init.lua
routes.before = function()
  local token = request.headers["X-Auth-Token"]
  if not token or token ~= "secret-password" then
    -- Block the request
    return "403 Forbidden: Invalid auth token"
  end
  -- If it returns nothing (nil), the request continues
end

server pages:

Any request for a file ending in .lua will execute that file. The script should return a single string, which will be sent as the HTTP response body with a 200 OK status.

example site/hello.lua:

local name = request.query.name or "World"
return "Hello, " .. name .. "!"

A request to /hello.lua?name=Macrobean would return Hello, Macrobean!.

json() helper API

macrobean provides a simple json() function to serialize a Lua table into a JSON string. Returns the string back to lua, which serve_path() writes.

route("/api/user", function()
  local user = { id = 1, name = "Alice", active = true }
  -- Set the content type header manually if needed
  -- (Note: macrobean doesn't have a response header API yet)
  return json(user) -- returns '{"id":1,"name":"Alice","active":true}'
end)

If instead of raw output, you get something like:

Pattern route error: attempt to call a table value

Then the stack fit for the corresponding block is not correctly applied.


sqlite

When --db is enabled, Macrobean provides a global db table for interacting with a SQLite database.

How it Works:

Your database file must be named data.db and placed in your site directory. When a Lua script calls a db function, Macrobean:

  1. Extracts site/data.db from the in-memory ZIP archive.
  2. Writes it to a temporary file at /tmp/macrobean.db.
  3. Opens this temporary file with SQLite and executes the query.
  4. If the query was a write operation (db.exec), the temporary file is updated.

This means your database is essentially read-only unless you are using the --watch flag, which will periodically re-extract the database from the site.zip file.

db.query(db_path, sql_query)

Executes a SELECT query. It always takes the path to the database as the first argument.

Example (query.lua):

-- URL: /query.lua?key=some_key

local key = request.query.key
if not key then return "Missing key parameter" end

-- Note the use of string.format to prevent SQL injection
local sql = string.format("SELECT value FROM kv WHERE key = '%s';", key)

local rows = db.query("/tmp/macrobean.db", sql)

if rows and #rows > 0 then
  return rows[1].value
else
  return "Not found"
end

db.exec(db_path, sql_query)

Executes CRUD statements.

Example (submit.lua):

-- URL: /submit.lua?key=foo&value=bar (via POST)

if request.method ~= "POST" then return "Invalid method" end

local k = request.query.k
local v = request.query.v
if not k or not v then return "Missing key or value" end

local sql = string.format("INSERT OR REPLACE INTO kv (key, value) VALUES ('%s', '%s');", k, v)
db.exec("/tmp/macrobean.db", sql)

return "Saved " .. k

sandboxing

--sandbox lets you run code in sandbox mode with lua_sethook() to ensure that Lua code cannot hang or abuse the server - even if it is malicious or buggy.

#define SANDBOX_LIMIT 1000000   	

We will disable os (no file or process access), io (no disk writes or stdin reads), debug (no introspection) Suppose the script includes:

route("/burn", function() 
while true do end 
end) 
$ ./macrobean.com --lua --sandbox --dev
$ curl "http://localhost:8080/burn?secret=opensesame"
HTTP/1.1 500 Internal Server Error
...
Lua Error: Execution timed out (sandbox limit reached)

Server survives. Memory intact. Thread continues.


security

mbedTLS (--tls)

When compiled with -DUSE_TLS, Macrobean uses the mbedTLS library to provide HTTPS. The init_tls_server() function in macrobean.c performs the following steps:

  1. Initializes the mbedTLS configuration, entropy source, and random number generator.
  2. Parses the server certificate provided via --cert.
  3. Parses the private key provided via --key.
  4. Sets up the SSL configuration with the loaded certificate and key.

When a new connection arrives, handle_tls_client() is called instead of handle_http_client(). It performs the TLS handshake before reading the HTTP request from the encrypted stream.

sandbox (--sandbox)

The --sandbox flag is a critical security feature. The init_lua() function in macrobean.c performs these steps when sandboxing is enabled:

  1. Removes Dangerous Libraries: It explicitly sets the global variables for io, os, package, and debug to nil, effectively removing them from the Lua environment. This prevents scripts from accessing the filesystem, executing shell commands, or loading arbitrary code.
  2. Installs a Timeout Hook: It uses lua_sethook to register a timeout_hook function. This function is called by the Lua interpreter every SANDBOX_LIMIT (1,000,000) instructions. If the hook is called, it means the script has been running for too long, and it immediately terminates the script with an error. This prevents denial-of-service attacks caused by infinite loops.

process isolation (--fork)

The --fork flag provides OS-level isolation between requests. In the main while(1) loop, after accept()-ing a new connection, the server does the following:

  1. Calls fork() to create a child process.
  2. In the child process: The child closes the main listening socket and calls either handle_http_client or handle_tls_client to process the request. After the request is finished, the child process exits (_exit(0)).
  3. In the parent process: The parent closes the client connection socket and immediately goes back to the select() loop to wait for new connections. It does not wait for the child to finish.

The signal(SIGCHLD, SIG_IGN) call in main() tells the kernel that the parent process is not interested in the exit status of its children, so the kernel will automatically reap the zombie processes, preventing resource leaks.


flags

  -h
  --port
  --dev
  --fork
  --zip
  --lua
  --db
  --sandbox
  --tls
  --cert
  --watch
  --bundle

functions

user-defined functions

discover_zip_structure()

scans the ZIP file structure and populates the zip_contents array. No return value.

extract_file_data(const zip_entry_t *entry, size_t *out_size)

extracts file data from the ZIP archive for the given entry. Returns a pointer to the extracted data, or NULL on error.

find_best_match(const char *requested_path)

finds the best matching file in the ZIP archive for the given path. Returns a pointer to the matching zip_entry_t, or NULL if no 	match is found.

find_zip_entry(const char *path)

finds a file in the ZIP archive by its exact path. Returns a pointer to the zip_entry_t, or NULL if not found.

guess_content_type(const char *path)

determines the MIME type of a file based on its extension. Returns a string containing the MIME type.

handle_http_client(int client_fd)

handles an HTTP client connection. No return value.

handle_tls_client(int client_fd)

handles a TLS-secured client connection. No return value.

init_lua(void)

initializes the Lua interpreter and loads necessary libraries. Returns a pointer to the initialized Lua state.

init_tls_server()

initializes the TLS server context. No return value.

lua_json(lua_State *L)

a Lua function that converts a Lua table to a JSON string. Returns 1 (number of return values).

main(int argc, char **argv)

the entry point of the program. Returns 0 on success, non-zero on error.

process_client(int client_fd)

processes a client connection. No return value.

run_server(int server_fd)

the main server loop. No return value.

serialize_json(lua_State *L, int index, luaL_Buffer *bj)

serializes a Lua value to JSON. No return value.

serve_path(int client_fd, const char *url_path, const char *method, const char *body)

serves a file or directory at the given URL path. No return value.

serve_static(int client_fd, const char *url_path, const char *method, const char *body)

serves a static file from the ZIP archive. Returns true if the file was served, false otherwise.

sqlite_exec(lua_State *L)

a Lua function that executes an SQL statement. Returns 1 (number of return values).

sqlite_query(lua_State *L)

a Lua function that executes an SQL query and returns the results as a Lua table. Returns 1 (number of return values).

timeout_hook(lua_State *L, lua_Debug *ar)

a Lua hook function that implements a timeout for Lua scripts. No return value.

standard library functions

accept(int socked, struct sockaddr *addr, socklen_t *addrlen)

Accepts a connection on a socket and returns a new socket file descriptor for the accepted connection.

bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

Binds a name to a socket. Used to associate a socket with a specific port and IP address.

chdir(const char *path)

Changes the current working directory. Returns 0 on success, -1 on error.

close(int fd)

Closes a file descriptor. Returns 0 on success, -1 on error.

execvp(const char *file, char *const argv[])

Replaces the current process image with a new process image. Returns only on error.

exit(int status)

Causes normal process termination. The status is returned to the parent process.

fclose(FILE *stream)

Closes a stream. Retruns 0 on success, EOF on error.

fopen(const char *pathname, const char *mode)

Opens a file. Returns a pointer on success, NULL on error.

fork()

Creates a new process by duplicating the calling process. Returns 0 to the child process and the process ID of the child to the 	parent.

fprintf(FILE *stream, const char *format, ...)

Writes formatted output to a stream. Returns the number of characters written.

fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

Reads data from a stream and returns the number of items read.

free(void *ptr)

Frees the memory space pointed to by the ptr. No return value.

fseek(FILE *stream, long offset, int whence)

Repositions the file position indicator. Returns 0 on success, -1 on error.

fstat(int fd, struct stat *statbuf)

Gets file status. Returns 0 on success. -1 on error.

ftell(FILE *stream)

Returns the current file position. Returns the current position on success, -1 on error.

fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream)

Write data to a stream. Returns the number of items written.

getaddrinfo(const char *node, const char *service, const struct addrinfo *hints, struct addrinfo **res)

Network address and service translation. Returns 0 on success, non-zero on error.

getcwd(char *buf, size_t size)

gets the current working directory. Returns a pointer to the buffer on success, NULL on error.

getenv(const char *name)

gets an environment variable. Returns the value of the environment variable, or NULL if not found.

gettimeofday(struct timeval *tv, struct timezone *tz)

Gets the time. Returns 0 on success, -1 on error.

htonl(uint32_t hostlong)

Converts a 32-bit integer from host to network byte order. Returns the value in network byte order.

listen(int sockfd, int backlog)

Listens for connections on a socket. Returns 0 on success, -1 on error.

luaL_checkstring(lua_State *L, int narg)

Checks the function argument narg is a string. Returns the string.

luaL_newstate()

creates a new lua state. returns the new state, or NULL on error.

luaL_openlibs(lua_State *L)

opens all standard lua libraries.

lua_call(lua_State *L, int nargs, int nresults)

calls a function in protected mode.

lua_close(lua_State *L)

destroys a lua state.

lua_getfield(lua_State *L, int idx, const char *k)

pushes onto the stack the value `t[k]`, where `t` is the value at the given index. No return value.

lua_gettop(lua_State *L)

returns the index of the top element in the stack. Returns the index.

lua_isstring(lua_State *L, int index)

returns 1 if the value at the given index is a string, and 0 otherwise.

lua_istable(lua_State *L, int index)

returns 1 if the value at the given index is a table, and 0 otherwise.

lua_newtable(lua_State *L)

creates a new empty table and pushes it onto the stack. No return value.

lua_next(lua_State *L, int index)

pops a key from the stack, and pushes a key-value pair from the table at the given index. Returns 0 when there are no more 		elements.

lua_pcall(lua_State *L, int nargs, int nresults, int msgh)

calls a function in protected mode. Returns 0 on success, or an error code.

lua_pop(lua_State *L, int n)

pops `n` elements from the stack. No return value.

lua_pushboolean(lua_State *L, int b)

pushes a boolean value onto the stack. 

lua_pushinteger(lua_State *L, lua_Integer n)

pushes an integer value onto the stack.

lua_pushlightuserdata(lua_State *L, void *p)

pushes a light user data onto the stack.

lua_pushlstring(lua_State *L, const char *s, size_t len)

pushes a string with known length onto the stack. No return value.

lua_pushnil(lua_State *L)

pushes a null value onto the stack.

lua_pushnumber(lua_State *L, lua_Number n)

pushes a number onto the stack.

lua_pushstring(lua_State *L, const char *s)

pushes a string onto the stack.

lua_pushvalue(lua_State *L, int index)

pushes a copy of the known element onto the stack.

lua_setfield(lua_State *L, int index, const char *k)

does the equivalent to `t[k] = v`, where `t` is the value of the given index and `v` is the value at the top of stack.

lua_settop(lua_State *L, int index)

sets the stack top to the given index.

lua_toboolean(lua_State *L, int index)

converts the lua value at the given index to a `C` boolean value and returns the converted value.

lua_tolstring(lua_State *L, int index, size_t *len)

converts the lua value at the given index to a `C` string and returns the string.

lua_tonumber(lua_State *L, int index)

converts the lua value at the given index to a `C` number and returns the converted value.

lua_touserdata(lua_State *L, int index)

converts the lua value at the given index to a user data and returns the userdata.

lua_type(lua_State *L, int index)

returns the type of the value at the given index as an integer.

lua_typename(lua_State *L, int tp)

returns the name of the type encoded by the value `tp`. Returns the type name.

luaL_Buffer

Lua buffer for string concatenation.

luaL_buffinit(lua_State *L, luaL_Buffer *B)

initialises a buffer.

luaL_buffinitsize(lua_State *L, luaL_Buffer *B, size_t sz)

initialises a buffer with a preallocated size and returns a pointer to the buffer. 

luaL_prepbuffsize(luaL_Buffer *B, size_t sz)

returns an address to a space of size `sz` where you can copy a string to be added to buffer `B` and returns the address.

luaL_pushresult(luaL_Buffer *B)

finishes the use of buffer `B` leaving the final string on the top of the stack. 

luaL_pushresultsize(luaL_Buffer *B, size_t sz)

equivalent to the sequence `luaL_addsize` + `luaL_pushresult`. No return value.

luaL_tolstring(lua_State *L, int idx, size_t *len)

converts any lua value at the given index to a `C` string in a reasonable format. Returns the string.

malloc(size_t size)

allocates size bytes of memory. Returns a pointer to the allocated memory, or NULL on error.

memchr(const void *s, int c, size_t n)

scans the initial `n` bytes of the memory area pointed to by `s` for the first instance of `c`. Returns a pointer to the matching 	byte, or NULL if not found. 

memcmp(const void *s1, const void *s2, size_t n)

compares the first `n` bytes of the memory areas `s1` and `s2`. Returns an integer less than, equal to, or greater than zero if 	`s1` is found to be less than, equal to, or greater than `s2`.

memcpy(void *dest, const void *src, size_t n)

copies `n` bytes from memory area `src` to `dest`. Returns a pointer to `dest`.

memset(void *s, int c, size_t n)

fills the first `n` bytes of the memory area pointed to by `s` with the constant byte `c`. Returns a pointer to the memory area 	`s`.

opendir(const char *name)

opens a directory stream. Returns a pointer to the directory stream, or NULL on error.

perror(const char *s)

prints a description of the error code currently stored in the system variable errno.

printf(const char *format, ...)

writes formatted output to stdout. Returns the number of characters written.

read(int fd, void *buf, size_t count)

reads up to count bytes from file descriptor `fd` into the buffer starting at `buf`. Returns the number of bytes read, 0 on EOF,		
or -1 on error.

readdir(DIR *dirp)

returns a pointer to a dirent structure representing the next directory entry in the directory stream. Returns a pointer to the 	structure, or NULL on end-of-directory or error.

realloc(void *ptr, size_t size)

changes the size of the memory block pointed to by ptr to size bytes. Returns a pointer to the newly allocated memory, or NULL on 	error.

recv(int sockfd, void *buf, size_t len, int flags)

receives a message from a socket. Returns the number of bytes received, or -1 on error.

select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

monitors multiple file descriptors, waiting until one or more of the file descriptors become "ready" for some class of I/O 		operation. Returns the number of file descriptors in the descriptor sets, or -1 on error.

send(int sockfd, const void *buf, size_t len, int flags)

sends a message on a socket. Returns the number of bytes send, or -1 on error.

setenv(const char *name, const char *value, int overwrite)

Adds the variable name to the environment with the value. Returns 0 on success, -1 on error.

signal(int signum, void (*handler)(int))

Sets the disposition of the signal signum to handler. Returns the previous value of the signal handler, or SIG_ERR on error.

snprintf(char *str, size_t size, const char *format, ...)

Write formatted output to a string and returns the number of characters that would have been written, or a negative value on 		error.

socket(int domain, int type, int protocol)

creates an endpoint for communication. Returns a file descriptor for the new socket, or -1 on error.

sprintf(char *str, const char *format, ...)

writes formatted output to a string. Returns the number of character written, or a negative value on error.

sqlite3_bind_int(sqlite3_stmt*, int, int)

binds an integer value to a parameter in a prepared statement. Returns SQLITE_OK on success, or an error code on failure.

sqlite3_close(sqlite3*)

closes a database connection. Returns SQLITE_OK on success, or an error code on failure.

sqlite3_column_count(sqlite3_stmt*)

returns the number of columns in the result set of a prepared statement. Returns the number of columns.

sqlite3_errmsg(sqlite3*)

returns the English-language text that describes the most recent error. Returns the error message.

sqlite3_exec(sqlite3*, const char *sql, int (*callback)(void*,int,char**,char**), void *, char **errmsg)

executes one or more SQL statements. Returns SQLITE_OK on success, or an error code on failure

sqlite3_finalize(sqlite3_stmt *pStmt)

destroys a prepared statement object. Returns SQLITE_OK on success, or an error code on failure.

sqlite3_free(void*)

frees the memory allocated by SQLite and no return value.

sqlite3_open_v2(const char *filename, sqlite3 **ppDb, int flags, const char *zVfs)

opens a database connection with additional options. Returns SQLITE_OK on success, or an error code on failure.

sqlite3_reset(sqlite3_stmt *pStmt)

resets a prepared statement object back to its initial state. 

sqlite3_step(sqlite3_stmt*)

evaluates a prepared statement. Returns SQLITE_ROW if a row of data is available, SQLITE_DONE if the statement has finished 		executing, or an error code on failure.

sqlite3_stmt

a pointer to a prepared statement object. 

srand(unsigned int seed)

sets the seed for the random number generator. No return value.

stat(const char *pathname, struct stat *statbuf)

gets file status. Returns 0 on success, -1 on error. 

strcasecmp(const char *s1, const char *s2)

compares two strings, ignoring case. Returns an integer less than, equal to, or greater than zero if `s1` is found to be less 		than, equal to, or greater than `s2`.

strcasestr(const char *haystack, const char *needle)

locates a substring in a string, ignoring case. Returns a pointer to the beginning of the substring, or NULL if the substring is 	not found.

strchr(const char *s, int c)

locates the first occurrence of `c` in the string `s`. Returns a pointer to the located character, or NULL if the character does 	not appear in the string.

strcmp(const char *s1, const char *s2)

compares the two strings `s1` and `s2`. Returns an integer less than, equal to, or greater than zero if `s1` is found to be less than, 	equal to, or greater than `s2`.

strcpy(char *dest, const char *src)

copies the string pointed to by src to the buffer pointed to by dest. Returns a pointer to the duplicated string, or NULL on 		error.

strcspn(const char *s, const char *reject)

calculates the length of the initial segment of s which consists entirely of bytes not in reject. Returns the length of the 		segment.

strdup(const char *s)

returns a pointer to a new string which is a duplicate of the string `s`. Returns a pointer to the duplicated string, or `NULL` 	on error.	

strerror(int errnum)

returns a string describing the error code errnum. Returns the error string.

strlen(const char *s)

calculates the length of the string `s`, excluding the terminating null byte. Returns the number of characters in the string.

strncasecmp(const char *s1, const char *s2, size_t n)

compares the first `n` bytes of the string `s1` and `s2`, ignoring case. Returns an integer less than, equal to, or greater than 	zero if `s1` is found to be less than, equal to, or greater than `s2`. 

strncmp(const char *s1, const char *s2, size_t n)

compares the first `n` bytes of the strings `s1` and `s2`, ignoring case. Returns an integer less than, equal to, or greater than 	zero if `s1` is found to be less than, equal to, or greater than `s2`.

strncpy(char *dest, const char *src, size_t n)

copies at most `n` bytes from the string pointed to by `src` to the buffer pointed to by dest. Returns a pointer to the 			destination 	string.

strndup(const char *s, size_t n)

returns a pointer to a new string which is a duplicate of the string `s`, but only copies at most `n` bytes. Returns a pointer to 	the duplicated string, or NULL on error.

strrchr(const char *s, int c)

locates the last occurrence of `c` in the string `s`. Returns a pointer to the located character, or NULL if the character does 	not appear in the string.

strstr(const char *haystack, const char *needle)

locates the first occurrence of the substring needle in the string haystack. Returns a pointer to the beginning of the substring, 	or NULL if the substring is not found.

strtod(const char *nptr, char **endptr)

converts the initial portion of the string pointed to by `nptr` to double. Returns the converted value.

strtok(char *str, const char *delim)

extracts the tokens from the string `s`, which are sequences of contiguous characters separated by any of the characters the 		string `delim`. Returns a pointer to the next token, or NULL if there are no more tokens.

system(const char *command)

executes a shell command. Returns the exit status of the command. 

time(time_t *tloc)

returns the time as the number of seconds since the Epoch and returns the current time.

unlink(const char *pathname)

deletes a name from the filesystem. Returns 0 on success, -1 on error.

vsnprintf(char *str, size_t size, const char *format, va_list ap)

writes formatted output to a string using a variable argument list. Returns the number of characters written, or a negative value 	on error.

waitpid(pid_t pid, int *wstatus, int options)

waits for a child process to change state. Returns the process ID of the child whose state has changed, or -1 on error.

write(int fd, const void *buf, size_t count)

writes up to count bytes from the buffer starting at `buf` to the file referred to by the file descriptor `fd`.
Returns the number of bytes written, or -1 on error.

macros

MAX_FILES: The maximum number of files that can be stored in the ZIP archive.

MAX_PATH: The maximum length of a file path.

MAX_REQ: The maximum size of an HTTP request.

PORT: The default port number for the server.

SANDBOX_LIMIT: The maximum size of a file that can be served in sandbox mode.

USE_TLS: Defined if TLS support is enabled.


use cases

macrobean is well-suited (but not limited to) for the following tasks.

simple static site hosting

the most basic use case is serving a static website. simply package your html, css, and javascript files into a site.zip, append it to the macrobean binary, and you have a single-file web server that you can deploy anywhere.

prototyping and demos

macrobean is an excellent tool for quickly prototyping web applications or creating self-contained demos. you can build a fully functional application with lua and sqlite, and then distribute it as a single file that anyone can run without any setup.

personal wiki or blog

With a few Lua scripts, you can create a simple but powerful personal wiki or blog. Use Lua to render Markdown files, and SQLite to store your posts and metadata. The entire site can be a single file that you can easily back up or move to a different server.

API server

Macrobean can be used to create a lightweight API server. Use Lua to handle API requests, and SQLite to store your data. The result is a single-file, dependency-free API server that is easy to deploy and maintain.