You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
167 lines
5.4 KiB
167 lines
5.4 KiB
-- Network utilities module for auto-boot-ollama-host
|
|
-- Provides port checking, Wake-on-LAN functionality, and socket fallback support
|
|
|
|
local utils = require("utils")
|
|
|
|
-- Try to load socket module, fallback to basic implementation for LuaJIT
|
|
local socket
|
|
local socket_available = true
|
|
-- Try to load socket module, ignore Lua's error message
|
|
local ok, err = pcall(function() socket = require("socket") end)
|
|
if not ok then
|
|
print("Warning: socket module not available, using fallback implementation")
|
|
socket_available = false
|
|
-- Create a minimal socket fallback for LuaJIT
|
|
socket = {
|
|
gettime = function() return os.time() end,
|
|
sleep = function(sec) os.execute("sleep " .. tostring(sec)) end,
|
|
tcp = function() return nil end, -- Will cause network functions to fail gracefully
|
|
udp = function() return nil end,
|
|
udp4 = function() return nil end
|
|
}
|
|
end
|
|
|
|
local network = {}
|
|
|
|
-- Check if socket module is fully functional
|
|
function network.is_socket_available()
|
|
return socket_available and socket and socket.tcp and socket.udp
|
|
end
|
|
|
|
-- Check if socket module is available for sleep operations
|
|
function network.is_sleep_available()
|
|
return socket_available and socket and socket.sleep
|
|
end
|
|
|
|
-- Get the socket module (either real or fallback)
|
|
function network.get_socket()
|
|
return socket
|
|
end
|
|
|
|
-- Check if a TCP port is accepting connections within a timeout (seconds)
|
|
function network.port_is_up(host, port, timeout_sec, check_interval_sec)
|
|
-- Set defaults with clear intent
|
|
host = host or "127.0.0.1"
|
|
port = port or 0
|
|
timeout_sec = timeout_sec or 30
|
|
check_interval_sec = check_interval_sec or 2
|
|
|
|
-- Convert to proper types
|
|
host = tostring(host)
|
|
port = tonumber(port) or 0
|
|
local timeout = tonumber(timeout_sec) or 30
|
|
|
|
if port <= 0 then return false end
|
|
|
|
-- Fallback to basic check if socket is not available
|
|
if not network.is_socket_available() then
|
|
utils.log("Socket module not available, using basic port check with " .. timeout .. "s timeout")
|
|
|
|
-- Implement timeout loop with short intervals
|
|
local start_time = os.time()
|
|
|
|
local retry_message_sent = false
|
|
|
|
while (os.time() - start_time) < timeout do
|
|
local cmd = string.format("nc -z -w1 %s %d 2>/dev/null", host, port)
|
|
local success, reason, code = os.execute(cmd)
|
|
if success and reason == "exit" and code == 0 then
|
|
utils.log("Port " .. port .. " is now available on " .. host)
|
|
return true
|
|
end
|
|
|
|
-- Wait before next check
|
|
if (os.time() - start_time) < timeout then
|
|
if not retry_message_sent then
|
|
utils.log("Port " ..
|
|
port .. " not yet available on " .. host .. ", retrying every " .. check_interval_sec .. "s...")
|
|
retry_message_sent = true
|
|
end
|
|
os.execute("sleep " .. check_interval_sec)
|
|
end
|
|
end
|
|
|
|
utils.log("Port " .. port .. " not available on " .. host .. " after " .. timeout .. "s timeout")
|
|
return false
|
|
end
|
|
|
|
local deadline = socket.gettime() + timeout
|
|
local check_count = 0
|
|
|
|
-- Create TCP socket once outside the loop for better performance
|
|
local tcp = socket.tcp()
|
|
if not tcp then
|
|
utils.log("Failed to create TCP socket")
|
|
return false
|
|
end
|
|
tcp:settimeout(1)
|
|
|
|
while socket.gettime() < deadline do
|
|
check_count = check_count + 1
|
|
local ok = tcp:connect(host, port)
|
|
tcp:close() -- Close connection after each attempt
|
|
if ok then
|
|
utils.log("Port " .. port .. " is now available on " .. host .. " (attempt " .. check_count .. ")")
|
|
return true
|
|
end
|
|
|
|
-- Log progress every 10 attempts
|
|
if check_count % 10 == 0 then
|
|
local elapsed = socket.gettime() - (deadline - timeout)
|
|
utils.log("Port " ..
|
|
port ..
|
|
" not yet available on " .. host .. " (attempt " .. check_count .. ", " .. math.floor(elapsed) .. "s elapsed)")
|
|
end
|
|
|
|
socket.sleep(0.5)
|
|
end
|
|
|
|
utils.log("Port " ..
|
|
port .. " not available on " .. host .. " after " .. timeout .. "s timeout (" .. check_count .. " attempts)")
|
|
return false
|
|
end
|
|
|
|
-- Convert MAC address string to bytes
|
|
-- "AA:BB:CC:DD:EE:FF" -> 6 bytes
|
|
-- local function mac_to_bytes(mac)
|
|
-- local bytes = {}
|
|
-- for byte in mac:gmatch("(%x%x)") do
|
|
-- table.insert(bytes, tonumber(byte, 16))
|
|
-- end
|
|
-- if #bytes ~= 6 then return nil end
|
|
-- return string.char(table.unpack(bytes))
|
|
-- end
|
|
|
|
-- Send Wake-on-LAN magic packet
|
|
function network.send_wol(mac_str, bcast_ip, port)
|
|
-- Build magic packet
|
|
local bytes = {}
|
|
for byte in mac_str:gmatch("(%x%x)") do
|
|
table.insert(bytes, tonumber(byte, 16))
|
|
end
|
|
if #bytes ~= 6 then return false, "invalid MAC" end
|
|
|
|
local mac = string.char(table.unpack(bytes))
|
|
local packet = string.rep(string.char(0xFF), 6) .. mac:rep(16)
|
|
|
|
-- Fallback to external tool if socket is not available
|
|
if not network.is_socket_available() then
|
|
utils.log("Socket module not available, using external wol tool")
|
|
local cmd = string.format("wol -i %s -p %d %s", bcast_ip, port, mac_str)
|
|
local ok, reason, code = os.execute(cmd)
|
|
return ok and reason == "exit" and code == 0,
|
|
(not ok or reason ~= "exit" or code ~= 0) and "wol command failed" or nil
|
|
end
|
|
|
|
-- Create IPv4 UDP socket (udp4 if available), bind to IPv4 wildcard to lock AF_INET
|
|
local udp = assert((socket.udp4 or socket.udp)())
|
|
udp:settimeout(2)
|
|
assert(udp:setsockname("0.0.0.0", 0)) -- force IPv4 family
|
|
assert(udp:setoption("broadcast", true)) -- allow broadcast
|
|
|
|
local ok, err = udp:sendto(packet, bcast_ip, port)
|
|
udp:close()
|
|
return ok ~= nil, err
|
|
end
|
|
|
|
return network
|
|
|