Refactor auto-boot-ollama-host script into modular structure with separate configuration, network, SSH, and service management modules for improved maintainability and readability
This commit is contained in:
56
scripts/README.md
Normal file
56
scripts/README.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
# Auto-Boot Ollama Host - Refactored Structure
|
||||||
|
|
||||||
|
This directory contains the refactored version of the auto-boot-ollama-host script, split into multiple modules for better maintainability and structure.
|
||||||
|
|
||||||
|
## File Structure
|
||||||
|
|
||||||
|
### Main Script
|
||||||
|
- `auto-boot-ollama-host.lua` - Original monolithic script
|
||||||
|
- `auto-boot-ollama-host-refactored.lua` - New modular main script
|
||||||
|
|
||||||
|
### Modules
|
||||||
|
|
||||||
|
#### `config.lua`
|
||||||
|
Handles all environment variable configuration with sensible defaults:
|
||||||
|
- Docker configuration (container name, since time)
|
||||||
|
- Ollama service configuration (host, port)
|
||||||
|
- SSH configuration (port, user, identity file)
|
||||||
|
- Pattern configuration (error and finish patterns)
|
||||||
|
- Wake-on-LAN configuration
|
||||||
|
|
||||||
|
#### `utils.lua`
|
||||||
|
Provides utility functions:
|
||||||
|
- `log(msg)` - Timestamped logging
|
||||||
|
- `getenv(name, def)` - Environment variable with default
|
||||||
|
|
||||||
|
#### `network.lua`
|
||||||
|
Network-related functionality:
|
||||||
|
- `port_is_up(host, port, timeout)` - Check if TCP port is accessible
|
||||||
|
- `send_wol(mac, bcast_ip, port)` - Send Wake-on-LAN magic packet
|
||||||
|
|
||||||
|
#### `ssh.lua`
|
||||||
|
SSH command execution:
|
||||||
|
- `execute(command, user, host, port, identity_file)` - Execute remote SSH command
|
||||||
|
|
||||||
|
#### `ollama_manager.lua`
|
||||||
|
Ollama service management:
|
||||||
|
- `start_service(config)` - Start Ollama service via SSH
|
||||||
|
- `stop_service_and_shutdown(config)` - Stop service and shutdown host
|
||||||
|
|
||||||
|
## Benefits of Refactoring
|
||||||
|
|
||||||
|
1. **Separation of Concerns**: Each module has a single responsibility
|
||||||
|
2. **Reusability**: Modules can be reused in other projects
|
||||||
|
3. **Testability**: Individual modules can be unit tested
|
||||||
|
4. **Maintainability**: Easier to locate and modify specific functionality
|
||||||
|
5. **Readability**: Clear structure and organization
|
||||||
|
6. **Configuration**: Centralized configuration management
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To use the refactored version, simply run:
|
||||||
|
```bash
|
||||||
|
lua auto-boot-ollama-host-refactored.lua
|
||||||
|
```
|
||||||
|
|
||||||
|
The refactored version maintains full compatibility with the original script while providing better structure and maintainability.
|
||||||
@@ -7,204 +7,72 @@
|
|||||||
|
|
||||||
local socket = require("socket")
|
local socket = require("socket")
|
||||||
|
|
||||||
local function getenv(name, def)
|
-- Import modules
|
||||||
local v = os.getenv(name)
|
local config = require("config")
|
||||||
return (v ~= nil and v ~= "") and v or def
|
local utils = require("utils")
|
||||||
end
|
local network = require("network")
|
||||||
|
local ssh = require("ssh")
|
||||||
|
local ollama_manager = require("ollama_manager")
|
||||||
|
|
||||||
-- Check if a TCP port is accepting connections within a timeout (seconds)
|
-- Handle error pattern detection and recovery
|
||||||
local function port_is_up(host, port, timeout_sec)
|
local function handle_error_pattern(config, powered_on)
|
||||||
host = tostring(host or "127.0.0.1")
|
utils.log(("Detected EHOSTUNREACH for Ollama (%s:%d)."):format(config.OLLAMA_HOST, config.OLLAMA_PORT))
|
||||||
port = tonumber(port or 0) or 0
|
|
||||||
local timeout = tonumber(timeout_sec or 1) or 1
|
|
||||||
if port <= 0 then return false end
|
|
||||||
|
|
||||||
local deadline = socket.gettime() + timeout
|
-- Send Wake-on-LAN if configured
|
||||||
while socket.gettime() < deadline do
|
if config.WOL_MAC ~= "" then
|
||||||
local tcp = socket.tcp()
|
utils.log(("Sending WOL to %s via %s:%d"):format(config.WOL_MAC, config.WOL_BCAST, config.WOL_PORT))
|
||||||
if not tcp then return false end
|
local ok, err = network.send_wol(config.WOL_MAC, config.WOL_BCAST, config.WOL_PORT)
|
||||||
tcp:settimeout(1)
|
if ok then
|
||||||
local ok = tcp:connect(host, port)
|
powered_on = true
|
||||||
tcp:close()
|
utils.log(("Successfully sent WOL to %s via %s:%d"):format(config.WOL_MAC, config.WOL_BCAST, config.WOL_PORT))
|
||||||
if ok then return true end
|
|
||||||
socket.sleep(0.5)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
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_USER = getenv("SSH_USER", "user")
|
|
||||||
local SSH_IDENTITY_FILE = getenv("SSH_IDENTITY_FILE", "/root/.ssh/id_rsa") -- e.g. "/path/to/id_rsa"
|
|
||||||
local ERROR_PATTERN = getenv(
|
|
||||||
"ERROR_PATTERN",
|
|
||||||
("[ERROR] Document analysis failed: connect EHOSTUNREACH %s:%d"):format(OLLAMA_HOST, OLLAMA_PORT)
|
|
||||||
)
|
|
||||||
local FINISH_PATTERN = getenv("FINISH_PATTERN", "[DEBUG] Finished fetching. Found 0 documents.") -- e.g. "Server started"
|
|
||||||
|
|
||||||
-- 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
|
|
||||||
|
|
||||||
-- Execute a remote command over SSH.
|
|
||||||
-- Signature must remain: ssh(command, user, host, port, identity_file)
|
|
||||||
local function ssh(command, user, host, port, identity_file)
|
|
||||||
-- Basic validation and defaults
|
|
||||||
user = tostring(user or "")
|
|
||||||
host = tostring(host or "")
|
|
||||||
port = tonumber(port or 22) or 22
|
|
||||||
identity_file = tostring(identity_file or "")
|
|
||||||
|
|
||||||
-- Quote a string for safe single-quoted POSIX shell context
|
|
||||||
local function sq(s)
|
|
||||||
-- Replace ' with: '\'' (close, escape quote, reopen)
|
|
||||||
return "'" .. tostring(s):gsub("'", "'\\''") .. "'"
|
|
||||||
end
|
|
||||||
|
|
||||||
-- Build base ssh command (run locally)
|
|
||||||
-- -oBatchMode to avoid interactive prompts
|
|
||||||
-- -oConnectTimeout for faster failure
|
|
||||||
-- -oStrictHostKeyChecking uses known_hosts; adjust if needed
|
|
||||||
local dest = (user ~= "" and (user .. "@" .. host) or host)
|
|
||||||
local pieces = {
|
|
||||||
"ssh",
|
|
||||||
"-p", tostring(port),
|
|
||||||
"-o", "BatchMode=yes",
|
|
||||||
"-o", "ConnectTimeout=30",
|
|
||||||
"-o", "ServerAliveInterval=5",
|
|
||||||
"-o", "ServerAliveCountMax=1",
|
|
||||||
"-o", "UserKnownHostsFile=/root/.ssh/known_hosts",
|
|
||||||
"-o", "StrictHostKeyChecking=yes",
|
|
||||||
}
|
|
||||||
if identity_file ~= "" then
|
|
||||||
table.insert(pieces, "-i"); table.insert(pieces, identity_file)
|
|
||||||
end
|
|
||||||
table.insert(pieces, dest)
|
|
||||||
|
|
||||||
-- Pass remote command as provided; caller is responsible for proper quoting.
|
|
||||||
table.insert(pieces, "--")
|
|
||||||
table.insert(pieces, command)
|
|
||||||
|
|
||||||
-- Join with spaces for os.execute
|
|
||||||
local function join(args)
|
|
||||||
-- We only quote the remote command explicitly. Other args are simple tokens.
|
|
||||||
return table.concat(args, " ")
|
|
||||||
end
|
|
||||||
|
|
||||||
local full = join(pieces)
|
|
||||||
log("SSH exec: " .. full)
|
|
||||||
local ok, reason, code = os.execute(full)
|
|
||||||
if ok == true or ok == 0 then
|
|
||||||
log("SSH command completed successfully")
|
|
||||||
return true
|
|
||||||
else
|
else
|
||||||
local msg = string.format("SSH failed: reason=%s code=%s", tostring(reason), tostring(code))
|
utils.log("WOL failed: " .. tostring(err))
|
||||||
log(msg)
|
|
||||||
return false, msg
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Wait for SSH and start service
|
||||||
|
utils.log("Waiting for SSH to become reachable...")
|
||||||
|
if network.port_is_up(config.OLLAMA_HOST, config.SSH_PORT, 60) then
|
||||||
|
ollama_manager.start_service(config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Handle finish pattern detection and shutdown
|
||||||
|
local function handle_finish_pattern(config)
|
||||||
|
utils.log(("Detected finish pattern: %q"):format(config.FINISH_PATTERN))
|
||||||
|
ollama_manager.stop_service_and_shutdown(config)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Main application logic
|
||||||
local function main()
|
local function main()
|
||||||
log(("Watching container='%s' since='%s'"):format(CONTAINER_NAME, SINCE))
|
utils.log(("Watching container='%s' since='%s'"):format(config.CONTAINER_NAME, config.SINCE))
|
||||||
log(("Looking for pattern: %q"):format(ERROR_PATTERN))
|
utils.log(("Looking for pattern: %q"):format(config.ERROR_PATTERN))
|
||||||
|
|
||||||
local cmd = ("docker logs -f --since %q %q 2>&1"):format(SINCE, CONTAINER_NAME)
|
local cmd = ("docker logs -f --since %q %q 2>&1"):format(config.SINCE, config.CONTAINER_NAME)
|
||||||
local powered_on = false
|
local powered_on = false
|
||||||
|
|
||||||
while true do
|
while true do
|
||||||
|
|
||||||
local fh = assert(io.popen(cmd, "r"))
|
local fh = assert(io.popen(cmd, "r"))
|
||||||
|
|
||||||
for line in fh:lines() do
|
for line in fh:lines() do
|
||||||
-- Plain substring match (no regex)
|
-- Handle error pattern detection
|
||||||
if line:find(ERROR_PATTERN, 1, true) ~= nil then
|
if line:find(config.ERROR_PATTERN, 1, true) ~= nil then
|
||||||
log(("Detected EHOSTUNREACH for Ollama (%s:%d)."):format(OLLAMA_HOST, OLLAMA_PORT))
|
handle_error_pattern(config, powered_on)
|
||||||
|
|
||||||
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
|
|
||||||
powered_on = true
|
powered_on = true
|
||||||
log(("Sucessfully sent WOL to %s via %s:%d"):format(WOL_MAC, WOL_BCAST, WOL_PORT))
|
|
||||||
else
|
|
||||||
log("WOL failed: " .. tostring(err))
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
log("Waiting for SSH to become reachable...")
|
-- Handle finish pattern detection
|
||||||
|
if line:find(config.FINISH_PATTERN, 1, true) ~= nil and powered_on == true then
|
||||||
if port_is_up(OLLAMA_HOST, SSH_PORT, 60) then
|
handle_finish_pattern(config)
|
||||||
log("SSH is reachable. Starting ollama service...")
|
|
||||||
socket.sleep(5)
|
|
||||||
ssh("nssm start ollama", SSH_USER, OLLAMA_HOST, SSH_PORT, SSH_IDENTITY_FILE)
|
|
||||||
--ssh('wsl.exe -d Debian -- sudo systemctl enable ollama', SSH_USER, OLLAMA_HOST, SSH_PORT, SSH_IDENTITY_FILE)
|
|
||||||
--ssh('wsl.exe -d Debian -- sudo systemctl start ollama', SSH_USER, OLLAMA_HOST, SSH_PORT, SSH_IDENTITY_FILE)
|
|
||||||
if (port_is_up(OLLAMA_HOST, OLLAMA_PORT, 90)) then
|
|
||||||
log("Ollama service is reachable again.")
|
|
||||||
socket.sleep(30)
|
|
||||||
break
|
|
||||||
else
|
|
||||||
log("Timeout waiting for Ollama service to come up after SSH command.")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
if line:find(FINISH_PATTERN, 1, true) ~= nil and powered_on == true then
|
|
||||||
log(("Detected finish pattern: %q"):format(FINISH_PATTERN))
|
|
||||||
log("Shutting down Ollama host to save power...")
|
|
||||||
ssh("nssm stop ollama", SSH_USER, OLLAMA_HOST, SSH_PORT, SSH_IDENTITY_FILE)
|
|
||||||
--ssh('wsl.exe -d Debian -- sudo systemctl disable ollama', SSH_USER, OLLAMA_HOST, SSH_PORT, SSH_IDENTITY_FILE)
|
|
||||||
--ssh('wsl.exe -d Debian -- sudo systemctl stop ollama', SSH_USER, OLLAMA_HOST, SSH_PORT, SSH_IDENTITY_FILE)
|
|
||||||
ssh("shutdown.exe /s /t 0", SSH_USER, OLLAMA_HOST, SSH_PORT, SSH_IDENTITY_FILE)
|
|
||||||
socket.sleep(5)
|
|
||||||
powered_on = false
|
powered_on = false
|
||||||
break
|
break
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
fh:close()
|
fh:close()
|
||||||
log("Restarting log watch loop...")
|
utils.log("Restarting log watch loop...")
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- Run the application
|
||||||
main()
|
main()
|
||||||
|
|||||||
42
scripts/config.lua
Normal file
42
scripts/config.lua
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
-- Configuration module for auto-boot-ollama-host
|
||||||
|
-- Handles all environment variable configuration with defaults
|
||||||
|
|
||||||
|
local function getenv(name, def)
|
||||||
|
local v = os.getenv(name)
|
||||||
|
return (v ~= nil and v ~= "") and v or def
|
||||||
|
end
|
||||||
|
|
||||||
|
local config = {}
|
||||||
|
|
||||||
|
-- Docker configuration
|
||||||
|
config.CONTAINER_NAME = getenv("CONTAINER_NAME", "paperless-ai")
|
||||||
|
config.SINCE = getenv("SINCE", "0s")
|
||||||
|
|
||||||
|
-- Ollama service configuration
|
||||||
|
config.OLLAMA_HOST = getenv("OLLAMA_HOST", "192.168.222.12")
|
||||||
|
config.OLLAMA_PORT = tonumber(getenv("OLLAMA_PORT", "11434"))
|
||||||
|
|
||||||
|
-- SSH configuration
|
||||||
|
config.SSH_PORT = tonumber(getenv("SSH_PORT", "22"))
|
||||||
|
config.SSH_USER = getenv("SSH_USER", "user")
|
||||||
|
config.SSH_IDENTITY_FILE = getenv("SSH_IDENTITY_FILE", "/root/.ssh/id_rsa")
|
||||||
|
|
||||||
|
-- Pattern configuration
|
||||||
|
config.ERROR_PATTERN = getenv(
|
||||||
|
"ERROR_PATTERN",
|
||||||
|
("[ERROR] Document analysis failed: connect EHOSTUNREACH %s:%d"):format(
|
||||||
|
config.OLLAMA_HOST,
|
||||||
|
config.OLLAMA_PORT
|
||||||
|
)
|
||||||
|
)
|
||||||
|
config.FINISH_PATTERN = getenv("FINISH_PATTERN", "[DEBUG] Finished fetching. Found 0 documents.")
|
||||||
|
|
||||||
|
-- Wake-on-LAN configuration
|
||||||
|
config.WOL_MAC = getenv("WOL_MAC", "")
|
||||||
|
config.WOL_BCAST = getenv("WOL_BCAST", "192.168.222.255")
|
||||||
|
config.WOL_PORT = tonumber(getenv("WOL_PORT", "9"))
|
||||||
|
|
||||||
|
-- Optional: wait for service to come up (kept commented to stay minimal)
|
||||||
|
-- config.UP_WAIT_TIMEOUT = tonumber(getenv("UP_WAIT_TIMEOUT", "90"))
|
||||||
|
|
||||||
|
return config
|
||||||
63
scripts/network.lua
Normal file
63
scripts/network.lua
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
-- Network utilities module for auto-boot-ollama-host
|
||||||
|
-- Provides port checking and Wake-on-LAN functionality
|
||||||
|
|
||||||
|
local socket = require("socket")
|
||||||
|
local utils = require("utils")
|
||||||
|
|
||||||
|
local network = {}
|
||||||
|
|
||||||
|
-- Check if a TCP port is accepting connections within a timeout (seconds)
|
||||||
|
function network.port_is_up(host, port, timeout_sec)
|
||||||
|
host = tostring(host or "127.0.0.1")
|
||||||
|
port = tonumber(port or 0) or 0
|
||||||
|
local timeout = tonumber(timeout_sec or 1) or 1
|
||||||
|
if port <= 0 then return false end
|
||||||
|
|
||||||
|
local deadline = socket.gettime() + timeout
|
||||||
|
while socket.gettime() < deadline do
|
||||||
|
local tcp = socket.tcp()
|
||||||
|
if not tcp then return false end
|
||||||
|
tcp:settimeout(1)
|
||||||
|
local ok = tcp:connect(host, port)
|
||||||
|
tcp:close()
|
||||||
|
if ok then return true end
|
||||||
|
socket.sleep(0.5)
|
||||||
|
end
|
||||||
|
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)
|
||||||
|
|
||||||
|
-- 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
|
||||||
50
scripts/ollama_manager.lua
Normal file
50
scripts/ollama_manager.lua
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
-- Ollama service management module for auto-boot-ollama-host
|
||||||
|
-- Handles starting, stopping, and monitoring Ollama service
|
||||||
|
|
||||||
|
local socket = require("socket")
|
||||||
|
local utils = require("utils")
|
||||||
|
local network = require("network")
|
||||||
|
local ssh = require("ssh")
|
||||||
|
|
||||||
|
local ollama_manager = {}
|
||||||
|
|
||||||
|
-- Start Ollama service via SSH
|
||||||
|
function ollama_manager.start_service(config)
|
||||||
|
utils.log("SSH is reachable. Starting ollama service...")
|
||||||
|
socket.sleep(5)
|
||||||
|
|
||||||
|
-- Start ollama service using nssm
|
||||||
|
ssh.execute("nssm start ollama", config.SSH_USER, config.OLLAMA_HOST, config.SSH_PORT, config.SSH_IDENTITY_FILE)
|
||||||
|
|
||||||
|
-- Alternative systemctl commands (commented out)
|
||||||
|
-- ssh.execute('wsl.exe -d Debian -- sudo systemctl enable ollama', config.SSH_USER, config.OLLAMA_HOST, config.SSH_PORT, config.SSH_IDENTITY_FILE)
|
||||||
|
-- ssh.execute('wsl.exe -d Debian -- sudo systemctl start ollama', config.SSH_USER, config.OLLAMA_HOST, config.SSH_PORT, config.SSH_IDENTITY_FILE)
|
||||||
|
|
||||||
|
-- Wait for service to become available
|
||||||
|
if network.port_is_up(config.OLLAMA_HOST, config.OLLAMA_PORT, 90) then
|
||||||
|
utils.log("Ollama service is reachable again.")
|
||||||
|
socket.sleep(30)
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
utils.log("Timeout waiting for Ollama service to come up after SSH command.")
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Stop Ollama service and shutdown host
|
||||||
|
function ollama_manager.stop_service_and_shutdown(config)
|
||||||
|
utils.log("Shutting down Ollama host to save power...")
|
||||||
|
|
||||||
|
-- Stop ollama service using nssm
|
||||||
|
ssh.execute("nssm stop ollama", config.SSH_USER, config.OLLAMA_HOST, config.SSH_PORT, config.SSH_IDENTITY_FILE)
|
||||||
|
|
||||||
|
-- Alternative systemctl commands (commented out)
|
||||||
|
-- ssh.execute('wsl.exe -d Debian -- sudo systemctl disable ollama', config.SSH_USER, config.OLLAMA_HOST, config.SSH_PORT, config.SSH_IDENTITY_FILE)
|
||||||
|
-- ssh.execute('wsl.exe -d Debian -- sudo systemctl stop ollama', config.SSH_USER, config.OLLAMA_HOST, config.SSH_PORT, config.SSH_IDENTITY_FILE)
|
||||||
|
|
||||||
|
-- Shutdown the host
|
||||||
|
ssh.execute("shutdown.exe /s /t 0", config.SSH_USER, config.OLLAMA_HOST, config.SSH_PORT, config.SSH_IDENTITY_FILE)
|
||||||
|
socket.sleep(5)
|
||||||
|
end
|
||||||
|
|
||||||
|
return ollama_manager
|
||||||
69
scripts/ssh.lua
Normal file
69
scripts/ssh.lua
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
-- SSH utilities module for auto-boot-ollama-host
|
||||||
|
-- Provides SSH command execution functionality
|
||||||
|
|
||||||
|
local utils = require("utils")
|
||||||
|
|
||||||
|
local ssh_module = {}
|
||||||
|
|
||||||
|
-- Execute a remote command over SSH
|
||||||
|
-- Signature: ssh(command, user, host, port, identity_file)
|
||||||
|
function ssh_module.execute(command, user, host, port, identity_file)
|
||||||
|
-- Basic validation and defaults
|
||||||
|
user = tostring(user or "")
|
||||||
|
host = tostring(host or "")
|
||||||
|
port = tonumber(port or 22) or 22
|
||||||
|
identity_file = tostring(identity_file or "")
|
||||||
|
|
||||||
|
-- Quote a string for safe single-quoted POSIX shell context
|
||||||
|
local function sq(s)
|
||||||
|
-- Replace ' with: '\'' (close, escape quote, reopen)
|
||||||
|
return "'" .. tostring(s):gsub("'", "'\\''") .. "'"
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Build base ssh command (run locally)
|
||||||
|
-- -oBatchMode to avoid interactive prompts
|
||||||
|
-- -oConnectTimeout for faster failure
|
||||||
|
-- -oStrictHostKeyChecking uses known_hosts; adjust if needed
|
||||||
|
local dest = (user ~= "" and (user .. "@" .. host) or host)
|
||||||
|
local pieces = {
|
||||||
|
"ssh",
|
||||||
|
"-p", tostring(port),
|
||||||
|
"-o", "BatchMode=yes",
|
||||||
|
"-o", "ConnectTimeout=30",
|
||||||
|
"-o", "ServerAliveInterval=5",
|
||||||
|
"-o", "ServerAliveCountMax=1",
|
||||||
|
"-o", "UserKnownHostsFile=/root/.ssh/known_hosts",
|
||||||
|
"-o", "StrictHostKeyChecking=yes",
|
||||||
|
}
|
||||||
|
|
||||||
|
if identity_file ~= "" then
|
||||||
|
table.insert(pieces, "-i")
|
||||||
|
table.insert(pieces, identity_file)
|
||||||
|
end
|
||||||
|
table.insert(pieces, dest)
|
||||||
|
|
||||||
|
-- Pass remote command as provided; caller is responsible for proper quoting
|
||||||
|
table.insert(pieces, "--")
|
||||||
|
table.insert(pieces, command)
|
||||||
|
|
||||||
|
-- Join with spaces for os.execute
|
||||||
|
local function join(args)
|
||||||
|
-- We only quote the remote command explicitly. Other args are simple tokens.
|
||||||
|
return table.concat(args, " ")
|
||||||
|
end
|
||||||
|
|
||||||
|
local full = join(pieces)
|
||||||
|
utils.log("SSH exec: " .. full)
|
||||||
|
|
||||||
|
local ok, reason, code = os.execute(full)
|
||||||
|
if ok == true or ok == 0 then
|
||||||
|
utils.log("SSH command completed successfully")
|
||||||
|
return true
|
||||||
|
else
|
||||||
|
local msg = string.format("SSH failed: reason=%s code=%s", tostring(reason), tostring(code))
|
||||||
|
utils.log(msg)
|
||||||
|
return false, msg
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return ssh_module
|
||||||
18
scripts/utils.lua
Normal file
18
scripts/utils.lua
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
-- Utility functions module for auto-boot-ollama-host
|
||||||
|
-- Provides logging and other utility functions
|
||||||
|
|
||||||
|
local utils = {}
|
||||||
|
|
||||||
|
-- Logging function with timestamp
|
||||||
|
function utils.log(msg)
|
||||||
|
io.stdout:write(os.date("[%F %T] "), msg, "\n")
|
||||||
|
io.stdout:flush()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Get environment variable with default value
|
||||||
|
function utils.getenv(name, def)
|
||||||
|
local v = os.getenv(name)
|
||||||
|
return (v ~= nil and v ~= "") and v or def
|
||||||
|
end
|
||||||
|
|
||||||
|
return utils
|
||||||
Reference in New Issue
Block a user