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.
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
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:
argv[0]
).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 the latest release
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
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
After compiling, you have the macrobean
binary. To create the final, self-contained executable, you need to append your site's ZIP archive.
mkdir -p site
echo "Hello from within!" > site/index.html
zip -r -0 site.zip site/
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.
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:
y
) or no (N
) to enable optional features like Lua scripting, SQLite database support, TLS/HTTPS, developer mode, and file watching.certbot
to issue a free Let's Encrypt certificate. This step requires sudo
privileges.site.zip
file, which is useful for development.macrobean.com
executable to /usr/local/bin/macrobean
and makes it executable. This step also requires sudo
privileges.macrobean
server with all the flags you selected during the configuration process.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. |
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:
/etc/letsencrypt/live/exampledomain.com/fullchain.pem
/etc/letsencrypt/live/exampledomain.com/privatekey.pem
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
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.
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
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";
}
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"
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);
-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
When --lua
is enabled, Macrobean exposes APIs to your Lua scripts.
request
global tableEvery 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
.
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.
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:
site/data.db
from the in-memory ZIP archive./tmp/macrobean.db
.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.
nil
on error.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.
db.exec(db_path, sql_query)
db_path
: Should always be "/tmp/macrobean.db"
true
on success, nil
on errorExample (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
--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.
--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:
--cert
.--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
)The --sandbox
flag is a critical security feature. The init_lua()
function in macrobean.c
performs these steps when sandboxing is enabled:
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.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.--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:
fork()
to create a child process.handle_http_client
or handle_tls_client
to process the request. After the request is finished, the child process exits (_exit(0)
).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.
-h --port --dev --fork --zip --lua --db --sandbox --tls --cert --watch --bundle
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.
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.
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.
macrobean is well-suited (but not limited to) for the following tasks.
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.
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.
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.
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.