Reticulum-Go/bindings/lua/examples/pageserver/main.lua

186 lines
5.2 KiB
Lua

-- SPDX-License-Identifier: Apache-2.0
-- Copyright (c) 2024-2026 Quad4.io
--
-- NomadNet-style pageserver over LuaJIT librns bindings.
-- Usage: luajit main.lua -c config [-i identity] [-a announce_sec] [-p page_file]
local rns = require("rns")
local DEFAULT_ANNOUNCE_SEC = 900
local DEFAULT_PAGE_PATH = "/page/index.mu"
local DEFAULT_FILE_PATH = "/file/test.txt"
local DEFAULT_PAGE_FILE = "pages/index.mu"
local DEFAULT_FILE_FILE = "files/test.txt"
local DEFAULT_IDENTITY_PATH = "identity"
local REQ_DATA_CAP = 64 * 1024
local FALLBACK_PAGE = table.concat({
"> Lua pageserver\n\n",
"librns via Reticulum-Go\n\n",
"Fallback page (file not found).\n\n",
})
local FALLBACK_FILE = "Test file from Reticulum-Go node!\n"
local function die(msg)
io.stderr:write(msg .. "\n")
os.exit(1)
end
local function print_last_error(what)
local msg = rns.last_error()
if msg ~= "" then
io.stderr:write(what .. ": " .. msg .. "\n")
else
io.stderr:write(what .. "\n")
end
end
local function load_bytes(path, fallback)
local f = io.open(path, "rb")
if not f then
io.stderr:write("warning: could not read " .. path .. ", using built-in content\n")
return fallback
end
local data = f:read("*a")
f:close()
return data
end
local function load_or_create_identity(path)
local ok, identity = pcall(rns.Identity.load, path)
if ok then
io.stderr:write("loaded identity from " .. path .. "\n")
return identity
end
identity = rns.Identity.generate()
identity:save(path)
io.stderr:write("created and saved identity to " .. path .. "\n")
return identity
end
local function parse_args(argv)
local config_path
local identity_path = DEFAULT_IDENTITY_PATH
local announce_sec = DEFAULT_ANNOUNCE_SEC
local page_file = DEFAULT_PAGE_FILE
local file_file = DEFAULT_FILE_FILE
local request_path = DEFAULT_PAGE_PATH
local i = 1
while i <= #argv do
local a = argv[i]
if a == "-c" then
i = i + 1
config_path = argv[i]
elseif a == "-i" then
i = i + 1
identity_path = argv[i]
elseif a == "-a" then
i = i + 1
announce_sec = tonumber(argv[i]) or -1
elseif a == "-p" then
i = i + 1
page_file = argv[i]
elseif a == "-f" then
i = i + 1
file_file = argv[i]
elseif a == "-P" then
i = i + 1
request_path = argv[i]
else
die("unexpected argument: " .. a)
end
i = i + 1
end
if not config_path then
die("usage: luajit main.lua -c config [options]")
end
if announce_sec < 0 then
die("announce interval must be >= 0")
end
return config_path, identity_path, announce_sec, page_file, file_file, request_path
end
local config_path, identity_path, announce_sec, page_file, file_file, request_path = parse_args(arg)
local ver = rns.version()
if ver ~= rns.API_VERSION then
die("librns version mismatch: got " .. ver)
end
local page_body = load_bytes(page_file, FALLBACK_PAGE)
local file_body = load_bytes(file_file, FALLBACK_FILE)
local node = rns.Node.create(config_path)
local identity = load_or_create_identity(identity_path)
node:set_identity(identity)
node:start()
local dest = rns.Destination.create(node, nil, "nomadnetwork", { "node" }, true)
dest:register_request_handler(request_path)
dest:register_request_handler(DEFAULT_FILE_PATH)
local dest_hex = rns.hash_to_hex(dest:hash())
print("DEST_HASH=" .. dest_hex)
print("REQUEST_PATH=" .. request_path)
print("FILE_PATH=" .. DEFAULT_FILE_PATH)
io.stderr:write("librns " .. ver .. " pageserver listening as nomadnetwork.node\n")
io.stderr:write("announce name=librns-lua-pageserver interval=" .. announce_sec .. "s\n")
local app_data = "librns-lua-pageserver"
if not pcall(function()
dest:announce(app_data)
end) then
print_last_error("destination.announce failed")
else
io.stderr:write("announce sent\n")
end
local last_announce = os.time()
while true do
if announce_sec > 0 and os.time() - last_announce >= announce_sec then
pcall(function()
dest:announce(app_data)
end)
io.stderr:write("announce refreshed\n")
last_announce = os.time()
end
local ok_ev, ev_or_err = pcall(rns.Event.poll, node, 200, REQ_DATA_CAP)
if not ok_ev then
if type(ev_or_err) ~= "table" or ev_or_err.code ~= rns.Error.TIMEOUT then
print_last_error("Event.poll failed")
dest:close()
identity:close()
node:close()
os.exit(1)
end
else
local ev = ev_or_err
local kind = ev:kind()
if kind == rns.EventKind.LINK_ESTABLISHED then
io.stderr:write("inbound link established\n")
elseif kind == rns.EventKind.LINK_CLOSED then
io.stderr:write("link closed\n")
elseif kind == rns.EventKind.REQUEST_INCOMING then
local path = ev:path()
io.stderr:write("request incoming path=" .. path .. "\n")
local req_id = ev:request_id()
if path == request_path then
if not pcall(rns.request_respond, node, req_id, page_body) then
print_last_error("request_respond failed")
else
io.stderr:write("served " .. request_path .. " (" .. #page_body .. " bytes)\n")
end
elseif path == DEFAULT_FILE_PATH then
if not pcall(rns.request_respond_file, node, req_id, "test.txt", file_body) then
print_last_error("request_respond_file failed")
else
io.stderr:write("served " .. DEFAULT_FILE_PATH .. " (" .. #file_body .. " bytes)\n")
end
else
pcall(rns.request_respond, node, req_id, "page not found\n")
end
end
end
end