SSH API
SSH 连接、远程执行和文件传输辅助能力位于 ptool.ssh 和 p.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_file,ptool会通过-i将该私钥传给ssh,并同时设置IdentitiesOnly=yes。 - 如果提供了
auth.private_key_passphrase或auth.password,ptool.ssh.connect(...)会失败,因为该 API 不会把这些密钥材料直接传给 系统ssh命令。 - 否则,认证完全交给本机 OpenSSH 配置处理,包括
IdentityFile、ProxyJump、ProxyCommand、ssh-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=no和UserKnownHostsFile=/dev/null。 - 如果
verify = "known_hosts"且提供了known_hosts_file,ptool会给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(...)->tableconn:run_capture(...)->tableconn:path(path)->RemotePathconn:exists(path)->booleanconn:is_file(path)->booleanconn:is_dir(path)->booleanconn:upload(local_path, remote_path[, options])->tableconn:download(remote_path, local_path[, options])->tableconn: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()->booleanremote:is_file()->booleanremote: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