-- 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 result = os.execute(cmd) return result == 0, result ~= 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