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.
154 lines
5.5 KiB
154 lines
5.5 KiB
-- 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 SSH_PORT = tonumber(getenv("SSH_PORT", "22"))
|
|
local SSH_IDENTITY_FILE = getenv("SSH_IDENTITY_FILE", "/root/.ssh/id") -- e.g. "/path/to/id_rsa"
|
|
local SSH_PRIVATE_KEY = getenv("SSH_PRIVATE_KEY", "")
|
|
local SSH_PUBLIC_KEY = getenv("SSH_PUBLIC_KEY", "")
|
|
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 createSSHKeyFilesFromEnv()
|
|
if SSH_PRIVATE_KEY == "" or SSH_PUBLIC_KEY == "" then
|
|
log("SSH_PRIVATE_KEY or SSH_PUBLIC_KEY env var is empty, skipping SSH key file creation.")
|
|
return
|
|
end
|
|
|
|
-- Ensure .ssh directory exists
|
|
local ssh_dir = SSH_IDENTITY_FILE:match("^(.*)/[^/]+$
|
|
if ssh_dir then
|
|
os.execute(("mkdir -p %q && chmod 700 %q"):format(ssh_dir, ssh_dir))
|
|
end
|
|
|
|
local priv_fh = io.open(SSH_IDENTITY_FILE, "w")
|
|
if not priv_fh then
|
|
log("Failed to open SSH identity file for writing: " .. SSH_IDENTITY_FILE)
|
|
return
|
|
end
|
|
priv_fh:write(SSH_PRIVATE_KEY)
|
|
priv_fh:close()
|
|
os.execute(("chmod 600 %q"):format(SSH_IDENTITY_FILE))
|
|
log("Wrote SSH private key to " .. SSH_IDENTITY_FILE)
|
|
|
|
local pub_fh = io.open(SSH_IDENTITY_FILE .. ".pub", "w")
|
|
if not pub_fh then
|
|
log("Failed to open SSH public key file for writing: " .. SSH_IDENTITY_FILE .. ".pub")
|
|
return
|
|
end
|
|
pub_fh:write(SSH_PUBLIC_KEY)
|
|
pub_fh:close()
|
|
os.execute(("chmod 644 %q"):format(SSH_IDENTITY_FILE .. ".pub"))
|
|
log("Wrote SSH public key to " .. SSH_IDENTITY_FILE .. ".pub")
|
|
end
|
|
|
|
local function main()
|
|
createSSHKeyFilesFromEnv()
|
|
|
|
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
|
|
|
|
if port_is_up(OLLAMA_HOST, SSH_PORT, 60) then
|
|
log("SSH is reachable. Starting ollama service...")
|
|
--ssh("wsl.exe -d Debian -- sudo systemctl enable --now ollama && sudo systemctl start ollama", "micro", OLLAMA_HOST, SSH_PORT, SSH_IDENTITY_FILE)
|
|
end
|
|
end
|
|
end
|
|
|
|
fh:close()
|
|
log("Log stream ended.")
|
|
end
|
|
|
|
main()
|
|
|