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.
146 lines
4.2 KiB
146 lines
4.2 KiB
-- SSH utilities module for auto-boot-ollama-host
|
|
-- Provides SSH command execution functionality
|
|
|
|
local utils = require("utils")
|
|
local config = require("config")
|
|
|
|
local ssh_module = {}
|
|
|
|
-- 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
|
|
|
|
-- Helper function to log SSH commands with proper formatting
|
|
local function log_ssh_command(prefix, command, full_command)
|
|
if config.is_debug() then
|
|
utils.log(prefix .. full_command)
|
|
else
|
|
utils.log(prefix .. sq(command))
|
|
end
|
|
end
|
|
|
|
-- 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 "")
|
|
|
|
|
|
-- 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, "--")
|
|
-- Quote the remote command to prevent shell interpretation of && and ||
|
|
table.insert(pieces, sq(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 command
|
|
log_ssh_command("SSH exec: ", command, 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
|
|
|
|
-- Execute a remote command over SSH and return the output
|
|
-- Signature: ssh.execute_with_output(command, user, host, port, identity_file)
|
|
-- Returns: success, output, error_message
|
|
function ssh_module.execute_with_output(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 "")
|
|
|
|
-- Build base ssh command (run locally)
|
|
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=no",
|
|
}
|
|
|
|
if identity_file ~= "" then
|
|
table.insert(pieces, "-i")
|
|
table.insert(pieces, identity_file)
|
|
end
|
|
table.insert(pieces, dest)
|
|
|
|
-- Pass remote command as provided
|
|
table.insert(pieces, "--")
|
|
-- Quote the remote command to prevent shell interpretation of && and ||
|
|
table.insert(pieces, sq(command))
|
|
|
|
-- Join with spaces for io.popen
|
|
local function join(args)
|
|
return table.concat(args, " ")
|
|
end
|
|
|
|
local full = join(pieces)
|
|
|
|
-- Log SSH command
|
|
log_ssh_command("SSH exec (with output): ", command, full)
|
|
|
|
-- Use io.popen to capture output
|
|
local fh = io.popen(full, "r")
|
|
if not fh then
|
|
return false, "", "Failed to open SSH command"
|
|
end
|
|
|
|
local output = fh:read("*a")
|
|
local success, reason, code = fh:close()
|
|
|
|
if success then
|
|
utils.log("SSH command completed successfully with output")
|
|
return true, output, nil
|
|
else
|
|
local msg = string.format("SSH failed: reason=%s code=%s", tostring(reason), tostring(code))
|
|
utils.log(msg)
|
|
return false, output, msg
|
|
end
|
|
end
|
|
|
|
return ssh_module
|
|
|