-- Purpose: Minimal log watcher using `docker logs -f` as input. -- Requirements: lua 5.4 + luasocket + docker CLI inside the container. -- Notes: -- - Pattern match is plain substring (fast & simple). -- - Optional Wake-on-LAN is native (no external tools). -- - Optional port-wait is provided but commented out (mirrors original bash idea). local socket = require("socket") local function getenv(name, def) local v = os.getenv(name) return (v ~= nil and v ~= "") and v or def end -- ---- Config via env ---- local CONTAINER_NAME = getenv("CONTAINER_NAME", "paperless-ai") local SINCE = getenv("SINCE", "0s") local OLLAMA_HOST = getenv("OLLAMA_HOST", "192.168.222.12") local OLLAMA_PORT = tonumber(getenv("OLLAMA_PORT", "11434")) local ERROR_PATTERN = getenv( "ERROR_PATTERN", ("connect EHOSTUNREACH %s:%d"):format(OLLAMA_HOST, OLLAMA_PORT) ) -- Optional Wake-on-LAN local WOL_MAC = getenv("WOL_MAC", "") -- e.g. "AA:BB:CC:DD:EE:FF" local WOL_BCAST = getenv("WOL_BCAST", "192.168.222.255") local WOL_PORT = tonumber(getenv("WOL_PORT", "9")) -- Optional: wait for service to come up (kept commented to stay minimal) -- local UP_WAIT_TIMEOUT = tonumber(getenv("UP_WAIT_TIMEOUT", "90")) local function log(msg) io.stdout:write(os.date("[%F %T] "), msg, "\n"); io.stdout:flush() end -- "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 local function 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) -- 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 -- Kept for reference; not used to keep parity with your minimal bash -- local function port_is_up(host, port, timeout_sec) -- local deadline = socket.gettime() + (timeout_sec or 1) -- repeat -- local tcp = socket.tcp(); tcp:settimeout(1) -- if tcp:connect(host, port) then tcp:close(); return true end -- tcp:close(); socket.sleep(0.5) -- until socket.gettime() >= deadline -- return false -- end local function main() log(("Watching container='%s' since='%s'"):format(CONTAINER_NAME, SINCE)) log(("Looking for pattern: %q"):format(ERROR_PATTERN)) local cmd = ("docker logs -f --since %q %q 2>&1"):format(SINCE, CONTAINER_NAME) local fh = assert(io.popen(cmd, "r")) for line in fh:lines() do -- Plain substring match (no regex) if line:find(ERROR_PATTERN, 1, true) ~= nil then log(("Detected EHOSTUNREACH for Ollama (%s:%d)."):format(OLLAMA_HOST, OLLAMA_PORT)) if WOL_MAC ~= "" then log(("Sending WOL to %s via %s:%d"):format(WOL_MAC, WOL_BCAST, WOL_PORT)) local ok, err = send_wol(WOL_MAC, WOL_BCAST, WOL_PORT) if ok then log(("Sucessfully sent WOL to %s via %s:%d"):format(WOL_MAC, WOL_BCAST, WOL_PORT)) else log("WOL failed: " .. tostring(err)) end end -- Optional wait (kept commented for minimal parity) -- if port_is_up(OLLAMA_HOST, OLLAMA_PORT, UP_WAIT_TIMEOUT) then -- log("Ollama reachable again.") -- else -- log("Timeout waiting for Ollama.") -- end end end fh:close() log("Log stream ended.") end main()