From 90b4fa9e7e84c6f228810f8f0f05995c3c5f798a Mon Sep 17 00:00:00 2001 From: "Bastian (BaM)" Date: Sun, 14 Sep 2025 10:49:11 +0200 Subject: [PATCH] Add lua log watcher script and update Dockerfile to include lua dependencies --- Dockerfile | 2 +- compose.yaml | 1 + scripts/auto-boot-ollama-host.lua | 102 ++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 scripts/auto-boot-ollama-host.lua diff --git a/Dockerfile b/Dockerfile index 911ba01..6d919f6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,7 +4,7 @@ FROM alpine:3.20 # Install minimal tooling RUN apk add --no-cache \ --repository=https://dl-cdn.alpinelinux.org/alpine/edge/testing wol \ - && apk add --no-cache bash curl ca-certificates docker-cli + && apk add --no-cache bash curl ca-certificates docker-cli lua5.4 lua5.4-socket # Copy script WORKDIR /app diff --git a/compose.yaml b/compose.yaml index 5b00be1..414a51c 100644 --- a/compose.yaml +++ b/compose.yaml @@ -13,4 +13,5 @@ services: WOL_PORT: "${WOL_PORT:-9}" # optional restart: unless-stopped volumes: + - /etc/localtime:/etc/localtime:ro - /var/run/docker.sock:/var/run/docker.sock:ro \ No newline at end of file diff --git a/scripts/auto-boot-ollama-host.lua b/scripts/auto-boot-ollama-host.lua new file mode 100644 index 0000000..765b3f7 --- /dev/null +++ b/scripts/auto-boot-ollama-host.lua @@ -0,0 +1,102 @@ +-- 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", + ("Error: [ERROR] Document analysis failed: 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", "255.255.255.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) + local mb = mac_to_bytes(mac_str) + if not mb then return false, "invalid MAC" end + local packet = string.rep(string.char(0xFF), 6) .. mb:rep(16) + local udp = assert(socket.udp()) + udp:settimeout(2) + pcall(function() assert(udp:setoption("broadcast", true)) end) + 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 + local ok, err = send_wol(WOL_MAC, WOL_BCAST, WOL_PORT) + if ok then + log(("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()