docs: README 改为英文默认,中文版移到 README.zh-CN.md
- README.md 英文,顶部有简体中文链接 - README.zh-CN.md 中文,顶部有 English 链接 - 补上安装/发布下载、macOS quarantine、CI 只跑 GitHub 的说明
This commit is contained in:
+227
@@ -0,0 +1,227 @@
|
||||
# sshtools
|
||||
|
||||
面向脚本的非交互 SSH 命令行工具:**golink** 对应 PuTTY 的 `plink`,**goscp** 对应 `pscp` 的上传方向。
|
||||
|
||||
纯 Go、`CGO_ENABLED=0`,每个平台一个静态二进制。SSH 协议栈和全部密码学(RSA、ECDSA、Ed25519、
|
||||
AES-GCM、ChaCha20-Poly1305、SHA-2)都是 Go 自己的实现,编译进二进制里 —— 不链接系统 OpenSSL,
|
||||
也不需要额外安装任何东西。
|
||||
|
||||
[English](README.md) | **简体中文**
|
||||
|
||||
## 安装
|
||||
|
||||
从 [最新发布](../../releases/latest) 下载对应平台的二进制,`chmod +x` 即可。
|
||||
|
||||
| 平台 | golink | goscp |
|
||||
| --- | --- | --- |
|
||||
| Windows x86-64 | `golink_windows_amd64.exe` | `goscp_windows_amd64.exe` |
|
||||
| Windows ARM64 | `golink_windows_arm64.exe` | `goscp_windows_arm64.exe` |
|
||||
| Linux x86-64 | `golink_linux_amd64` | `goscp_linux_amd64` |
|
||||
| Linux ARM64 | `golink_linux_arm64` | `goscp_linux_arm64` |
|
||||
| macOS Intel | `golink_darwin_amd64` | `goscp_darwin_amd64` |
|
||||
| macOS Apple Silicon | `golink_darwin_arm64` | `goscp_darwin_arm64` |
|
||||
|
||||
每次发布都附带 `SHA256SUMS`。
|
||||
|
||||
macOS 上从网上下载的二进制带 quarantine 属性,执行一次
|
||||
`xattr -d com.apple.quarantine ./golink` 即可。
|
||||
|
||||
## 两个工具共同遵守的约定
|
||||
|
||||
- 从不提示:不问密码、不问主机密钥、不问任何东西;
|
||||
- 从不分配终端,也不打开交互式 shell;
|
||||
- stdout 只放远端程序的标准输出,诊断信息一律走 stderr;
|
||||
- 退出码有明确含义(见下文);
|
||||
- 所有凭据都要提前给,所以可以放心放进 cron 和 CI。
|
||||
|
||||
## golink
|
||||
|
||||
连上去、跑一条命令、把远端退出码原样带回来。
|
||||
|
||||
```bash
|
||||
golink user@host -p "password" -port 22 -command "command"
|
||||
```
|
||||
|
||||
Go 标准库的 `flag` 遇到第一个位置参数就停止解析,所以 golink 在解析前把 `argv` 重排了一遍:
|
||||
目标在最前、最后、或者中间都行,flag 的顺序也无所谓。
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 最普通的一条
|
||||
golink root@10.0.0.5 -p 'secret' -port 22 -command "uptime && df -h"
|
||||
|
||||
# 密码走环境变量,不出现在 ps 和 shell history 里
|
||||
GOLINK_PASSWORD='secret' golink deploy@web1 -command "systemctl restart nginx"
|
||||
|
||||
# 远端退出码原样返回,脚本里可以直接判断
|
||||
if golink ci@build1 -p "$PASS" -command "make test"; then echo 通过; else echo "失败: $?"; fi
|
||||
|
||||
# 本地 stdin 直接喂给远端命令
|
||||
tar czf - ./src | golink root@backup -p "$PASS" -command "cat > /srv/src.tgz"
|
||||
|
||||
# 把远端输出接进本地管道
|
||||
golink db@10.0.0.9 -p "$PASS" -command "cat /var/log/app.log" | grep ERROR
|
||||
|
||||
# 密钥登录(OpenSSH 格式私钥)
|
||||
golink ops@bastion -i ~/.ssh/id_ed25519 -command hostname
|
||||
|
||||
# 写死主机指纹,避免中间人
|
||||
golink ops@10.0.0.7 -p "$PASS" -hostkey "$(ssh-keyscan -p 22 10.0.0.7 2>/dev/null | ssh-keygen -lf - | awk '{print $2}')" -command true
|
||||
|
||||
# 命令也可以当位置参数跟在后面(plink 风格)
|
||||
golink ops@web1 -p "$PASS" systemctl status nginx
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `-p` / `-password` / `-pw` | 空 | SSH 密码;也可以用环境变量 `GOLINK_PASSWORD` |
|
||||
| `-port` / `-P` | 22 | SSH 端口;也可以写在目标里 `host:2222` |
|
||||
| `-l` | 空 | 登录用户,等价于 `user@host` 里的 user |
|
||||
| `-command` | 空 | 远端执行的命令;后面的位置参数会自动拼上去 |
|
||||
| `-i` | 空 | OpenSSH 格式私钥文件(不支持带口令的私钥,见下) |
|
||||
| `-knownhosts` | 空 | known_hosts 文件;未知或变更的主机密钥直接失败,不会提示 |
|
||||
| `-hostkey` | 空 | 要求的主机指纹,`SHA256:...` 或 MD5 均可 |
|
||||
| `-timeout` | 15s | 只覆盖「建连 + 握手 + 认证」,命令跑多久都不受它限制 |
|
||||
| `-v` | 关 | 把连接、主机指纹、认证过程打到 stderr,不污染 stdout |
|
||||
| `-version` / `-h` | | 版本 / 帮助 |
|
||||
|
||||
目标写法支持 `user@host`、`user@host:2222`、`[::1]:2222`。
|
||||
|
||||
### 退出码
|
||||
|
||||
| 退出码 | 含义 |
|
||||
| --- | --- |
|
||||
| 0-255 | 远端命令的退出码原样返回(被信号杀死时按 shell 惯例返回 128+信号,如 SIGTERM → 143) |
|
||||
| 2 | 用法错误:参数写错、没写用户、没写命令、没给任何凭据 |
|
||||
| 255 | 连接、主机密钥或认证失败(和 OpenSSH 一致) |
|
||||
|
||||
stdout 只放远端命令的标准输出,远端 stderr 走本地 stderr,`-v` 的诊断信息也走 stderr,
|
||||
所以 `golink ... | ...` 和 `golink ... > out.txt` 都是安全的。
|
||||
|
||||
## goscp
|
||||
|
||||
把本地文件或目录传到远端,走 SCP 协议(远端跑 `scp -t`),不依赖 SFTP 子系统。
|
||||
|
||||
```bash
|
||||
goscp [flags] [user@]host[:port] -files "dir or file" [-files ...] [-dest /remote/dir]
|
||||
```
|
||||
|
||||
一个 `-files` 同时管文件和目录:它看路径本身,是文件就发一个条目,是目录就递归整棵树。
|
||||
没有 `-r` 开关,也没有第二种模式。
|
||||
|
||||
### 示例
|
||||
|
||||
```bash
|
||||
# 传一个文件到远端家目录
|
||||
goscp user@host -p "$PASS" -port 22 -files "build/output.bin"
|
||||
|
||||
# 传整个目录到指定位置(远端目录不存在会自动建)
|
||||
goscp user@host -p "$PASS" -files "build" -dest /home/user/app
|
||||
|
||||
# 多个文件:重复写,或者跟在后头
|
||||
goscp user@host -p "$PASS" -files a.txt -files b.txt -dest /srv/in
|
||||
goscp user@host -p "$PASS" -files a.txt b.txt -dest /srv/in
|
||||
|
||||
# 密码走环境变量
|
||||
GOSCP_PASSWORD='secret' goscp user@host -files ./dist/
|
||||
```
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `-files` | 空 | 要上传的本地文件**或目录**,自动识别;可重复,尾随位置参数也算 |
|
||||
| `-dest` | 远端家目录 | 远端目标目录,不存在会自动 `mkdir -p` |
|
||||
| `-p` / `-password` / `-pw` | 空 | SSH 密码;也可用 `GOSCP_PASSWORD` / `GOLINK_PASSWORD` |
|
||||
| `-port` / `-P` | 22 | SSH 端口 |
|
||||
| `-l` | 空 | 登录用户 |
|
||||
| `-i` | 空 | OpenSSH 格式私钥 |
|
||||
| `-knownhosts` / `-hostkey` | 空 | 主机密钥策略 |
|
||||
| `-timeout` | 15s | 建连 + 认证超时,不含传输 |
|
||||
| `-v` | 关 | 逐条列出上传的文件和大小 |
|
||||
| `-q` | 关 | 不打印最后那行汇总 |
|
||||
|
||||
### 退出码
|
||||
|
||||
| 退出码 | 含义 |
|
||||
| --- | --- |
|
||||
| 0 | 全部成功 |
|
||||
| 1 | 传输失败(本地路径不存在、远端 scp 报错、权限不足) |
|
||||
| 2 | 用法错误(参数、目标、没给文件、没给凭据) |
|
||||
| 255 | 连接、主机密钥或认证失败 |
|
||||
|
||||
### 行为说明
|
||||
|
||||
- 目录保留名字和相对结构:`-files build -dest /srv` → `/srv/build/...`
|
||||
- 权限:普通文件 0644(本地有执行位则 0755),目录 0755
|
||||
- 符号链接:命令行上直接点名的会跟随;目录内部遇到的跳过并在 stderr 提示
|
||||
- 文件名含空格没问题;含换行的不支持
|
||||
- 传输是流式的,测试里逐字节校验过
|
||||
|
||||
## 主机密钥策略(重要)
|
||||
|
||||
默认**不校验**主机密钥。脚本无法回答「是否信任这台主机」,所以行为等同 `plink -batch`:
|
||||
接受并继续,代价是理论上可被中间人攻击。
|
||||
|
||||
在意的话就钉死:
|
||||
|
||||
- `-hostkey SHA256:...` —— 指纹不符直接失败;
|
||||
- `-knownhosts ~/.ssh/known_hosts` —— 未知或变更直接失败,绝不提示。
|
||||
|
||||
## 安全提示
|
||||
|
||||
密码写在命令行会出现在 `ps` 和 shell history 里,和 `plink -pw` 一样。优先用
|
||||
`GOLINK_PASSWORD` / `GOSCP_PASSWORD` 或 `-i` 密钥。
|
||||
|
||||
带口令的私钥会被明确拒绝并给出提示:这两个工具从不交互提问,请先用 `ssh-keygen -p`
|
||||
去掉口令。
|
||||
|
||||
## 编译
|
||||
|
||||
```bash
|
||||
./build.sh # 所有程序 × 所有平台,产物在 dist/
|
||||
./build.sh local # 只编当前平台
|
||||
VERSION=1.2.3 ./build.sh
|
||||
```
|
||||
|
||||
唯一的依赖是 `golang.org/x/crypto`,只要 module cache 有货,离线也能编。
|
||||
`build.sh` 把 `GOCACHE` 指到仓库内的 `.gocache/`,因为有些沙箱里 `~/.cache/go-build` 是只读的。
|
||||
|
||||
推一个 `v*` tag 会触发 GitHub Actions:先跑两套测试,再交叉编译并把二进制挂到 GitHub Release。
|
||||
Gitea 也会识别 `.github/workflows`,所以 job 里带了
|
||||
`if: github.server_url == 'https://github.com'` 的判断,在 Gitea 上保持空转。
|
||||
|
||||
## 自测
|
||||
|
||||
```bash
|
||||
./smoketest/run.sh # golink,38 项
|
||||
./smoketest/run-goscp.sh # goscp,19 项
|
||||
```
|
||||
|
||||
两套都在本地跑:编一个迷你 sshd(`smoketest/sshd`,支持 password / keyboard-interactive /
|
||||
publickey 三种认证),通过回环驱动。每个用例都关掉 stdin 并加了超时,任何等待交互输入的行为
|
||||
都会表现为失败而不是挂起。
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
cmd/golink/ 命令行入口:参数、退出码映射
|
||||
cmd/goscp/ 命令行入口
|
||||
internal/cli/ 参数重排,让 flag 可以出现在任意位置
|
||||
internal/sshx/ 建连、认证、主机密钥策略
|
||||
internal/scp/ SCP 协议客户端
|
||||
smoketest/sshd/ 测试用迷你 SSH 服务端
|
||||
smoketest/run.sh golink 端到端测试
|
||||
smoketest/run-goscp.sh
|
||||
build.sh 六个目标的交叉编译
|
||||
```
|
||||
|
||||
## 还没做
|
||||
|
||||
- goscp 的下载方向(远端 → 本地)
|
||||
- 断点续传、并行传输、进度显示
|
||||
- SFTP 后端(会引入依赖,目前 SCP 让二进制保持自包含)
|
||||
- 跳板机(`-J`)、端口转发、交互式 shell / pty
|
||||
Reference in New Issue
Block a user