跳到主要内容

SSH API

SSH 连接、远程执行和文件传输辅助能力位于 ptool.sshp.ssh 下。

ptool.ssh.connect

v0.1.0 - 引入。

ptool.ssh.connect(target_or_options) 会准备一个由系统 ssh 命令驱动的 SSH 连接句柄,并返回一个 Connection 对象。

运行环境中必须能在 PATH 中找到 ssh

参数:

  • target_or_options(string|table,必填):
    • 传入字符串时,会被视为 SSH target。
    • 传入 table 时,目前支持:
      • target(string,可选):SSH target 字符串,例如 "[email protected]""[email protected]:2222"
      • host(string,可选):主机名或 IP 地址。
      • user(string,可选):SSH 用户名。默认取 $USER;如果 $USER 不可用,则默认为 "root"
      • port(integer,可选):SSH 端口。默认值为 22
      • auth(table,可选):认证设置。
      • host_key(table,可选):主机密钥校验设置。
      • connect_timeout_ms(integer,可选):超时时间(毫秒)。默认值为 10000
      • keepalive_interval_ms(integer,可选):keepalive 间隔(毫秒)。

支持的 target 字符串示例:

local a = ptool.ssh.connect("[email protected]")
local b = ptool.ssh.connect("[email protected]:2222")
local c = ptool.ssh.connect("[2001:db8::10]:2222")

auth 字段:

  • private_key_file(string,可选):私钥文件路径。
  • private_key_passphrase(string,可选):私钥口令。目前不支持。
  • password(string,可选):密码认证所用的密码。目前不支持。

认证行为:

  • 如果提供了 auth.private_key_fileptool 会通过 -i 将该私钥传给 ssh,并同时设置 IdentitiesOnly=yes
  • 如果提供了 auth.private_key_passphraseauth.passwordptool.ssh.connect(...) 会失败,因为该 API 不会把这些密钥材料直接传给 系统 ssh 命令。
  • 否则,认证完全交给本机 OpenSSH 配置处理,包括 IdentityFileProxyJumpProxyCommandssh-agent 和证书等机制。
  • 相对私钥路径会从当前 ptool 运行时目录解析,因此会受到 ptool.cd(...) 的影响。
  • 私钥路径中的 ~~/... 会被展开。

host_key 字段:

  • verify(string,可选):主机密钥校验模式。支持:
    • "known_hosts":根据 known_hosts 文件校验(默认)。
    • "ignore":跳过主机密钥校验。
  • known_hosts_file(string,可选):known_hosts 文件路径。仅在 verify = "known_hosts" 时使用。

主机密钥行为:

  • 如果 verify = "ignore"ptool 会给 ssh 传入 StrictHostKeyChecking=noUserKnownHostsFile=/dev/null
  • 如果 verify = "known_hosts" 且提供了 known_hosts_fileptool 会给 ssh 传入 StrictHostKeyChecking=yes,并设置对应的 UserKnownHostsFile
  • 如果 verify = "known_hosts" 但省略了 known_hosts_file,或者整个 host_key 都被省略,则主机密钥处理交给本机 OpenSSH 配置和默认行为。
  • 相对 known_hosts_file 路径会从当前 ptool 运行时目录解析。
  • known_hosts_file 中的 ~~/... 会被展开。
  • 当显式提供 known_hosts_file 时,它会覆盖本次连接中本机 ssh 命令默认 使用的 UserKnownHostsFile

示例:

local ssh = ptool.ssh.connect({
host = "example.com",
user = "deploy",
port = 22,
auth = {
private_key_file = "~/.ssh/id_ed25519",
},
host_key = {
verify = "known_hosts",
},
})

Connection

v0.1.0 - 引入。

Connection 表示由 ptool.ssh.connect() 返回的、基于 OpenSSH 的连接句柄。

它实现为 Lua userdata。

字段和方法:

  • 字段:
    • conn.host(string)
    • conn.user(string)
    • conn.port(integer)
    • conn.target(string)
  • 方法:
    • conn:run(...) -> table
    • conn:run_capture(...) -> table
    • conn:path(path) -> RemotePath
    • conn:exists(path) -> boolean
    • conn:is_file(path) -> boolean
    • conn:is_dir(path) -> boolean
    • conn:upload(local_path, remote_path[, options]) -> table
    • conn:download(remote_path, local_path[, options]) -> table
    • conn:close() -> nil

run

v0.1.0 - 引入。

规范 API 名称:ptool.ssh.Connection:run

conn:run(...) 通过当前 SSH 连接执行远程命令。

支持以下调用形式:

conn:run("hostname")
conn:run("echo", "hello world")
conn:run("echo", {"hello", "world"})
conn:run("hostname", { stdout = "capture" })
conn:run("echo", {"hello", "world"}, { stdout = "capture" })
conn:run({ cmd = "git", args = {"rev-parse", "HEAD"} })

参数规则:

  • conn:run(cmdline)cmdline 会作为远程命令字符串直接发送。
  • conn:run(cmd, argsline)cmd 会被视为命令,argsline 会按 shell 风格 (shlex)规则拆分。
  • conn:run(cmd, args)cmd 是字符串,args 是字符串数组。参数会在远程执行前 做 shell quoting。
  • conn:run(cmdline, options)options 会覆盖本次调用的行为。
  • conn:run(cmd, args, options)options 会覆盖本次调用的行为。
  • conn:run(options)options 是一个 table。
  • 当第二个参数是 table 时:如果它是数组(连续整数键 1..n),则视为 args; 否则视为 options

使用 conn:run(options) 时,options 目前支持:

  • cmd(string,必填):命令名或可执行文件路径。
  • args(string[],可选):参数列表。
  • cwd(string,可选):远程工作目录。会通过在生成的远程 shell 命令前追加 cd ... && 实现。
  • env(table,可选):远程环境变量,键和值都必须是字符串。会通过在生成的 远程 shell 命令前追加 export ... && 实现。
  • stdin(string,可选):发送给远程进程 stdin 的字符串。
  • echo(boolean,可选):执行前是否回显远程命令。默认值为 false
  • check(boolean,可选):退出状态不为 0 时是否立即抛错。默认值为 false
  • stdout(string,可选):stdout 处理策略。支持:
    • "inherit":继承到当前终端(默认)。
    • "capture":捕获到 res.stdout
    • "null":丢弃输出。
  • stderr(string,可选):stderr 处理策略。支持:
    • "inherit":继承到当前终端(默认)。
    • "capture":捕获到 res.stderr
    • "null":丢弃输出。

使用快捷调用形式时,options 仅支持:

  • stdin(string,可选):发送给远程进程 stdin 的字符串。
  • echo(boolean,可选):执行前是否回显远程命令。默认值为 false
  • check(boolean,可选):退出状态不为 0 时是否立即抛错。默认值为 false
  • stdout(string,可选):stdout 处理策略。支持:
    • "inherit":继承到当前终端(默认)。
    • "capture":捕获到 res.stdout
    • "null":丢弃输出。
  • stderr(string,可选):stderr 处理策略。支持:
    • "inherit":继承到当前终端(默认)。
    • "capture":捕获到 res.stderr
    • "null":丢弃输出。

返回值规则:

  • 总是返回一个 table,包含以下字段:
    • ok(boolean):远程退出状态是否为 0
    • code(integer|nil):远程退出状态。如果远程进程因信号退出,则为 nil
    • target(string):形如 user@host:port 的 SSH target 字符串。
    • stdout(string,可选):当 stdout = "capture" 时提供。
    • stderr(string,可选):当 stderr = "capture" 时提供。
    • assert_ok(self)(function):当 ok = false 时抛出错误。

示例:

local ssh = ptool.ssh.connect("[email protected]")

local res = ssh:run("uname -a", { stdout = "capture" })
print(res.target)
print(res.stdout)
local res2 = ssh:run({
cmd = "git",
args = {"rev-parse", "HEAD"},
cwd = "/srv/app",
env = {
FOO = "bar",
},
stdout = "capture",
check = true,
})

print(res2.stdout)

run_capture

Unreleased - 引入。

规范 API 名称:ptool.ssh.Connection:run_capture

conn:run_capture(...) 通过当前 SSH 连接执行远程命令。

它接受与 conn:run(...) 相同的调用形式、参数规则、返回值规则和选项。

唯一差异是默认流处理方式:

  • stdout 默认是 "capture"
  • stderr 默认是 "capture"

你仍然可以在 options 中显式覆盖任意一个字段。

示例:

local ssh = ptool.ssh.connect("[email protected]")

local res = ssh:run_capture("uname -a")
print(res.stdout)

local res2 = ssh:run_capture({
cmd = "sh",
args = {"-c", "printf 'out'; printf 'err' >&2"},
cwd = "/srv/app",
})
print(res2.stdout)
print(res2.stderr)

local res3 = ssh:run_capture("echo hello", {
stderr = "inherit",
})
print(res3.stdout)

path

v0.1.0 - 引入。

规范 API 名称:ptool.ssh.Connection:path

conn:path(path) 创建一个绑定到当前 SSH 连接的可复用 RemotePath 对象。

  • path(string,必填):远程路径。
  • 返回:RemotePath 对象,可传给 conn:upload(...)conn:download(...)ptool.fs.copy(...)

示例:

local ssh = ptool.ssh.connect("[email protected]")
local remote_release = ssh:path("/srv/app/releases/current.tar.gz")

ssh:download(remote_release, "./tmp/current.tar.gz")

exists

v0.2.0 - 引入。

规范 API 名称:ptool.ssh.Connection:exists

conn:exists(path) 检查远程路径是否存在。

  • path(string|remote path,必填):要检查的远程路径。可以是字符串,也可以是 conn:path(...) 创建的值。
  • 返回:远程路径存在时返回 true,否则返回 false

示例:

local ssh = ptool.ssh.connect("[email protected]")

print(ssh:exists("/srv/app"))
print(ssh:path("/srv/app/releases/current.tar.gz"):exists())

is_file

v0.2.0 - 引入。

规范 API 名称:ptool.ssh.Connection:is_file

conn:is_file(path) 检查远程路径是否存在且为普通文件。

  • path(string|remote path,必填):要检查的远程路径。可以是字符串,也可以是 conn:path(...) 创建的值。
  • 返回:远程路径是文件时返回 true,否则返回 false

示例:

local ssh = ptool.ssh.connect("[email protected]")
local remote_tarball = ssh:path("/srv/app/releases/current.tar.gz")

if ssh:is_file(remote_tarball) then
print("release tarball exists")
end

is_dir

v0.2.0 - 引入。

规范 API 名称:ptool.ssh.Connection:is_dir

conn:is_dir(path) 检查远程路径是否存在且为目录。

  • path(string|remote path,必填):要检查的远程路径。可以是字符串,也可以是 conn:path(...) 创建的值。
  • 返回:远程路径是目录时返回 true,否则返回 false

示例:

local ssh = ptool.ssh.connect("[email protected]")
local releases = ssh:path("/srv/app/releases")

if releases:is_dir() then
print("releases directory is ready")
end

upload

v0.1.0 - 引入。

规范 API 名称:ptool.ssh.Connection:upload

conn:upload(local_path, remote_path[, options]) 将本地文件或目录上传到远程主机。

  • local_path(string,必填):要上传的本地文件或目录。
  • remote_path(string|remote path,必填):远程目标路径。可以是字符串,也可以是 conn:path(...) 创建的值。
  • options(table,可选):传输选项。
  • 返回:包含以下字段的表:
    • bytes(integer):已上传的普通文件字节数。上传目录时,它等于目录内所有已上传 文件大小之和。
    • from(string):本地源路径。
    • to(string):远程目标路径。

支持的传输选项:

  • parents(boolean,可选):上传前是否创建 remote_path 的父目录。默认值为 false
  • overwrite(boolean,可选):是否允许覆盖已有目标文件。默认值为 true
  • echo(boolean,可选):执行前是否打印传输信息。默认值为 false

目录行为:

  • local_path 是文件时,行为保持不变。
  • local_path 是目录且 remote_path 不存在时,remote_path 会成为目标目录根。
  • local_path 是目录且 remote_path 已存在并且是目录时,会在其下按源目录的 basename 创建目标目录。
  • overwrite = false 时,如果最终目标目录已存在,则会报错。
  • 上传目录时,远程主机需要提供 tar

示例:

local ssh = ptool.ssh.connect("[email protected]")
local remote_tarball = ssh:path("/srv/app/releases/current.tar.gz")

local res = ssh:upload("./dist/app.tar.gz", remote_tarball, {
parents = true,
overwrite = true,
echo = true,
})

print(res.bytes)
print(res.to)

目录示例:

local ssh = ptool.ssh.connect("[email protected]")

local res = ssh:upload("./dist/assets", "/srv/app/releases", {
parents = true,
overwrite = true,
echo = true,
})

print(res.bytes)
print(res.to) -- [email protected]:22:/srv/app/releases

download

v0.1.0 - 引入。

规范 API 名称:ptool.ssh.Connection:download

conn:download(remote_path, local_path[, options]) 将远程文件或目录下载到本地路径。

  • remote_path(string|remote path,必填):远程源路径。可以是字符串,也可以是 conn:path(...) 创建的值。
  • local_path(string,必填):本地目标路径。
  • options(table,可选):传输选项。
  • 返回:包含以下字段的表:
    • bytes(integer):已下载的普通文件字节数。下载目录时,它等于目录内所有已下载 文件大小之和。
    • from(string):远程源路径。
    • to(string):本地目标路径。

支持的传输选项:

  • parents(boolean,可选):下载前是否创建 local_path 的父目录。默认值为 false
  • overwrite(boolean,可选):是否允许覆盖已有目标文件。默认值为 true
  • echo(boolean,可选):执行前是否打印传输信息。默认值为 false

目录行为:

  • remote_path 是文件时,行为保持不变。
  • remote_path 是目录且 local_path 不存在时,local_path 会成为目标目录根。
  • remote_path 是目录且 local_path 已存在并且是目录时,会在其下按远程源目录的 basename 创建目标目录。
  • overwrite = false 时,如果最终目标目录已存在,则会报错。
  • 下载目录时,远程主机需要提供 tar

示例:

local ssh = ptool.ssh.connect("[email protected]")

local res = ssh:download("/srv/app/logs/app.log", "./tmp/app.log", {
parents = true,
overwrite = false,
echo = true,
})

print(res.bytes)
print(res.from)

目录示例:

local ssh = ptool.ssh.connect("[email protected]")

local res = ssh:download("/srv/app/releases/assets", "./tmp/releases", {
parents = true,
overwrite = true,
echo = true,
})

print(res.bytes)
print(res.from)

close

v0.1.0 - 引入。

规范 API 名称:ptool.ssh.Connection:close

conn:close() 关闭 SSH 连接句柄。

行为说明:

  • 关闭后,连接不能再继续使用。
  • 对已经关闭的连接再次关闭是允许的,并且不会产生效果。

示例:

local ssh = ptool.ssh.connect("[email protected]")
ssh:close()

RemotePath

v0.1.0 - 引入。

RemotePath 表示一个绑定到 Connection 的远程路径,由 conn:path(path) 返回。

它实现为 Lua userdata。

方法:

  • remote:exists() -> boolean
  • remote:is_file() -> boolean
  • remote:is_dir() -> boolean

exists

remote:exists() 检查远程路径是否存在。

  • 返回:远程路径存在时返回 true,否则返回 false

示例:

local ssh = ptool.ssh.connect("[email protected]")
local remote_release = ssh:path("/srv/app/releases/current.tar.gz")

print(remote_release:exists())

is_file

remote:is_file() 检查远程路径是否存在且为普通文件。

  • 返回:远程路径是文件时返回 true,否则返回 false

示例:

local ssh = ptool.ssh.connect("[email protected]")
local remote_tarball = ssh:path("/srv/app/releases/current.tar.gz")

if remote_tarball:is_file() then
print("release tarball exists")
end

is_dir

remote:is_dir() 检查远程路径是否存在且为目录。

  • 返回:远程路径是目录时返回 true,否则返回 false

示例:

local ssh = ptool.ssh.connect("[email protected]")
local releases = ssh:path("/srv/app/releases")

if releases:is_dir() then
print("releases directory is ready")
end