-
Notifications
You must be signed in to change notification settings - Fork 0
Module Development Guide
Create a new module in 5 minutes:
mkdir modules/mod_mymod// modules/mod_mymod/mod_mymod.c
#include "portal/portal.h"
static portal_core_t *g_core = NULL;
static portal_module_info_t info = {
.name = "mod_mymod",
.version = "1.0.0",
.description = "My custom module",
.author = "Your Name",
.depends = "" // comma-separated soft deps
};
// 1. INFO — return module descriptor
portal_module_info_t *portal_module_info(void) {
return &info;
}
// 2. LOAD — initialize, register paths
int portal_module_load(portal_core_t *core) {
g_core = core;
// Register paths (with optional ACL labels)
portal_labels_t labels = { .count = 0 }; // public
core->path_register(core, "/mymod/hello", "mod_mymod", &labels);
core->path_register(core, "/mymod/data", "mod_mymod", &labels);
// Declare events
core->event_declare(core, "/events/mymod/action", "mod_mymod", &labels);
core->log(core, LOG_INFO, "mod_mymod loaded");
return 0; // 0 = success, non-zero = failure
}
// 3. UNLOAD — cleanup, unregister
int portal_module_unload(portal_core_t *core) {
core->path_unregister(core, "/mymod/hello");
core->path_unregister(core, "/mymod/data");
core->log(core, LOG_INFO, "mod_mymod unloaded");
return 0;
}
// 4. HANDLE — process messages
int portal_module_handle(portal_core_t *core,
const portal_msg_t *msg,
portal_resp_t *resp) {
// Route by path
if (strcmp(msg->path, "/mymod/hello") == 0) {
resp->status = PORTAL_OK;
snprintf(resp->body, sizeof(resp->body),
"Hello, %s!", msg->context.user);
return 0;
}
if (strcmp(msg->path, "/mymod/data") == 0) {
if (msg->method == PORTAL_METHOD_GET) {
// Read from storage
char buf[4096];
if (core->store_get(core, "mymod.data", buf, sizeof(buf)) == 0) {
resp->status = PORTAL_OK;
snprintf(resp->body, sizeof(resp->body), "%s", buf);
} else {
resp->status = PORTAL_NOT_FOUND;
snprintf(resp->body, sizeof(resp->body), "No data stored");
}
} else if (msg->method == PORTAL_METHOD_SET) {
// Write to storage
core->store_set(core, "mymod.data", msg->body);
core->event_emit(core, "/events/mymod/action", "data updated");
resp->status = PORTAL_OK;
snprintf(resp->body, sizeof(resp->body), "Stored");
}
return 0;
}
resp->status = PORTAL_NOT_FOUND;
return 0;
}The Makefile auto-discovers modules in modules/*/:
make
# or compile manually:
gcc -shared -fPIC -o modules/mod_mymod/mod_mymod.so \
modules/mod_mymod/mod_mymod.c \
-Iinclude# portal.conf
[modules]
load = mod_mymod./portal -c portal.conf &
# CLI
./portal -r
portal:/> mymod hello
Hello, admin!
# HTTP
curl http://localhost:8080/api/mymod/helloEvery module must export exactly these 4 symbols:
| Export | Signature | Called When |
|---|---|---|
portal_module_info |
portal_module_info_t *(void) |
Module discovery |
portal_module_load |
int (portal_core_t *core) |
Module loaded |
portal_module_unload |
int (portal_core_t *core) |
Module unloaded |
portal_module_handle |
int (portal_core_t *core, const portal_msg_t *msg, portal_resp_t *resp) |
Message received |
Route by path and method:
int portal_module_handle(portal_core_t *core,
const portal_msg_t *msg,
portal_resp_t *resp) {
// Check path
if (strncmp(msg->path, "/mymod/", 7) != 0) {
resp->status = PORTAL_NOT_FOUND;
return 0;
}
const char *subpath = msg->path + 7;
// Check method
switch (msg->method) {
case PORTAL_METHOD_GET:
// read operation
break;
case PORTAL_METHOD_SET:
// write operation
break;
case PORTAL_METHOD_ACTION:
// execute operation
break;
case PORTAL_METHOD_DELETE:
// delete operation
break;
default:
resp->status = PORTAL_BAD_REQUEST;
break;
}
return 0;
}Declare dependencies in the info struct. They are soft — your module loads even if deps are missing, but you should check at runtime:
static portal_module_info_t info = {
.name = "mod_mymod",
.depends = "mod_cache,mod_email" // soft deps
};
int portal_module_load(portal_core_t *core) {
if (!core->module_loaded(core, "mod_cache")) {
core->log(core, LOG_WARN, "mod_cache not loaded, caching disabled");
}
return 0;
}Protect paths with labels:
portal_labels_t admin_only = {
.labels = { "admin" },
.count = 1
};
core->path_register(core, "/mymod/admin", "mod_mymod", &admin_only);
portal_labels_t ops_or_admin = {
.labels = { "admin", "ops" },
.count = 2
};
core->path_register(core, "/mymod/manage", "mod_mymod", &ops_or_admin);
// Public path (no labels = anyone can access)
portal_labels_t public = { .count = 0 };
core->path_register(core, "/mymod/status", "mod_mymod", &public);Call other modules through the core dispatch:
portal_msg_t req = {0};
portal_resp_t res = {0};
strncpy(req.path, "/cache/mykey", sizeof(req.path));
req.method = PORTAL_METHOD_GET;
req.context = msg->context; // propagate auth + trace
core->dispatch(core, &req, &res);
if (res.status == 200) {
// use res.body
}Declare, emit, and subscribe:
// In load:
core->event_declare(core, "/events/mymod/changed", "mod_mymod", &labels);
// In handle:
core->event_emit(core, "/events/mymod/changed", "something changed");
// Subscribe to other module events:
void my_callback(const char *path, const char *body, void *userdata) {
// handle event
}
core->event_subscribe(core, "/events/iot/*", "mod_mymod", my_callback);- All paths registered in
load, unregistered inunload - All events declared in
load - Handle returns 0 (non-zero = critical error)
- No global state leaked between unload/load cycles
- Thread-safe if using shared data
- Labels set on sensitive paths
- Soft deps checked at runtime
- Version string updated
Top Level System — GPL-2.0 | Website | Repository
mod_cli · mod_web · mod_node · mod_ssh · mod_config_sqlite · mod_config_psql
mod_cache · mod_kv · mod_shm · mod_queue · mod_websocket · mod_mqtt · mod_email · mod_file
mod_logic · mod_logic_lua · mod_logic_python · mod_logic_c · mod_logic_pascal
mod_metrics · mod_health · mod_sysinfo · mod_process · mod_log · mod_audit · mod_cron · mod_scheduler · mod_worker · mod_backup
mod_proxy · mod_dns · mod_http_client · mod_webhook · mod_api_gateway · mod_tunnel · mod_acme
mod_firewall · mod_crypto · mod_ldap · mod_validator
mod_iot · mod_gpio · mod_serial
mod_xz · mod_gzip · mod_template · mod_admin