diff --git a/README.cn.md b/README.cn.md index c64269b..31fcc59 100644 --- a/README.cn.md +++ b/README.cn.md @@ -318,6 +318,18 @@ trzsz-ssh ( tssh ) 设计为 ssh 客户端的直接替代品,提供与 openssh EnableTrzsz No ``` +- 可使用 `--upload-file` 参数在命令行中指定文件或目录直接上传,也可在服务器后面指定 `trz` 上传命令参数和保存路径,如: + + ```sh + tssh --upload-file /path/to/file1 --upload-file /path/to/dir2 xxx_server '~/.local/bin/trz -d /tmp/' + ``` + +- 可在命令行中使用 `tsz` 直接下载文件或目录到本地,可一并使用 `--download-path` 参数指定本地保存的路径,如: + + ```sh + tssh -t --client --download-path /tmp/ xxx_server 'tsz -d /path/to/file1 /path/to/dir2' + ``` + ![tssh trzsz](https://trzsz.github.io/images/tssh_trzsz.gif) ## 支持 zmodem @@ -353,6 +365,18 @@ trzsz-ssh ( tssh ) 设计为 ssh 客户端的直接替代品,提供与 openssh - 关于 `rz / sz` 进度条,己传大小和传输速度会有一点偏差,它的主要作用只是指示传输正在进行中。 +- 可使用 `--upload-file` 参数在命令行中指定文件直接上传,在服务器后面 `cd` 到保存路径再指定 `rz` 命令及参数即可,如: + + ```sh + tssh --upload-file /path/to/file1 --upload-file /path/to/file2 xxx_server 'cd /tmp/ && rz -yeb' + ``` + +- 可在命令行中使用 `sz` 直接下载文件到本地,可一并使用 `--download-path` 参数指定本地保存的路径,如: + + ```sh + tssh -t --client --zmodem --download-path /tmp/ xxx_server 'sz /path/to/file1 /path/to/file2' + ``` + ## 批量登录 - 支持在 `iTerm2`( 要开启 [Python API](https://iterm2.com/python-api-auth.html),但不需要`Allow all apps to connect` ),`tmux` 和 `Windows Terminal` 中一次选择多台服务器,批量登录,并支持批量执行预先指定的命令。 diff --git a/README.en.md b/README.en.md index 66577df..d1552a1 100644 --- a/README.en.md +++ b/README.en.md @@ -318,6 +318,18 @@ trzsz-ssh ( tssh ) is an ssh client designed as a drop-in replacement for the op EnableTrzsz No ``` +- You can use the `--upload-file` argument to specify file or directory to upload directly in the command line, and you can specify the `trz` upload command arguments and save path after the server, such as: + + ```sh + tssh --upload-file /path/to/file1 --upload-file /path/to/dir2 xxx_server '~/.local/bin/trz -d /tmp/' + ``` + +- You can use `tsz` in the command line to directly download files and directories to your local computer. You can also use the `--download-path` argument to specify the path for local saving, such as: + + ```sh + tssh -t --client --download-path /tmp/ xxx_server 'tsz -d /path/to/file1 /path/to/dir2' + ``` + ![tssh trzsz](https://trzsz.github.io/images/tssh_trzsz.gif) ## Support zmodem @@ -353,6 +365,18 @@ trzsz-ssh ( tssh ) is an ssh client designed as a drop-in replacement for the op - About the progress, the transferred and speed are not precise. It just indicating that the transfer is in progress. +- You can use the `--upload-file` argument to specify file to upload directly in the command line, and `cd` to the save path and specify the `rz` command with arguments after the server, such as: + + ```sh + tssh --upload-file /path/to/file1 --upload-file /path/to/file2 xxx_server 'cd /tmp/ && rz -yeb' + ``` + +- You can use `sz` in the command line to directly download files to your local computer. You can also use the `--download-path` argument to specify the path for local saving, such as: + + ```sh + tssh -t --client --zmodem --download-path /tmp/ xxx_server 'sz /path/to/file1 /path/to/file2' + ``` + ## Batch Login - tssh supports selecting multiple servers in `iTerm2`( Requires [Python API](https://iterm2.com/python-api-auth.html), no need to `Allow all apps to connect` ),`tmux` and `Windows Terminal`, logging in to them in batches, and executing pre-specified commands in batches. diff --git a/go.mod b/go.mod index 321ee10..5b418a6 100644 --- a/go.mod +++ b/go.mod @@ -12,19 +12,19 @@ require ( github.com/creack/pty v1.1.21 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/mattn/go-isatty v0.0.20 - github.com/mattn/go-runewidth v0.0.15 + github.com/mattn/go-runewidth v0.0.16 github.com/mitchellh/go-homedir v1.1.0 github.com/pquerna/otp v1.4.0 github.com/skeema/knownhosts v1.3.0 github.com/stretchr/testify v1.8.4 - github.com/trzsz/go-arg v1.5.3 + github.com/trzsz/go-arg v1.5.4 github.com/trzsz/go-socks5 v0.1.0 github.com/trzsz/iterm2 v0.1.2 github.com/trzsz/pageant v0.1.1 github.com/trzsz/promptui v0.10.7 github.com/trzsz/ssh_config v1.3.6 - github.com/trzsz/trzsz-go v1.1.8-0.20240720040127-f462ee534824 - github.com/trzsz/tsshd v0.1.3-0.20240720085856-c35640ee47be + github.com/trzsz/trzsz-go v1.1.8-0.20240728122806-7706c75f5426 + github.com/trzsz/tsshd v0.1.3-0.20240728123755-e3d9535a080b golang.org/x/crypto v0.25.0 golang.org/x/sys v0.22.0 golang.org/x/term v0.22.0 @@ -46,7 +46,7 @@ require ( github.com/dchest/jsmin v0.0.0-20220218165748-59f39799265f // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/go-task/slim-sprig/v3 v3.0.0 // indirect - github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da // indirect + github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/josephspurrier/goversioninfo v1.4.0 // indirect github.com/klauspost/compress v1.17.9 // indirect @@ -58,7 +58,7 @@ require ( github.com/muesli/cancelreader v0.2.2 // indirect github.com/muesli/termenv v0.15.2 // indirect github.com/ncruces/zenity v0.10.13 // indirect - github.com/onsi/ginkgo/v2 v2.19.0 // indirect + github.com/onsi/ginkgo/v2 v2.19.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/quic-go/quic-go v0.45.1 // indirect @@ -68,8 +68,8 @@ require ( github.com/templexxx/xorsimd v0.4.2 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/xtaci/kcp-go/v5 v5.6.8 // indirect - github.com/xtaci/smux v1.5.24 // indirect + github.com/xtaci/kcp-go/v5 v5.6.11 // indirect + github.com/xtaci/smux v1.5.25 // indirect go.uber.org/mock v0.4.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect golang.org/x/image v0.18.0 // indirect diff --git a/go.sum b/go.sum index 0ca72c9..6f1c07b 100644 --- a/go.sum +++ b/go.sum @@ -54,8 +54,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4= github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= -github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= -github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= @@ -75,8 +75,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da h1:xRmpO92tb8y+Z85iUOMOicpCfaYcv7o3Cg3wKrIpg8g= -github.com/google/pprof v0.0.0-20240711041743-f6c9dda6c6da/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8 h1:FKHo8hFI3A+7w0aUQuYXQ+6EN5stWmeY/AZqtM8xk9k= +github.com/google/pprof v0.0.0-20240727154555-813a5fbdbec8/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= @@ -95,8 +95,9 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI= @@ -107,10 +108,10 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/ncruces/zenity v0.10.13 h1:0Gd/EdjjEQIhrFaJ05Q5ZvyjlcjnorlZpdzgUzqQIH0= github.com/ncruces/zenity v0.10.13/go.mod h1:UyAUPSjHm1hOdeZa3Lrh/zmItyGq+iH5AP6xSCx8CFM= -github.com/onsi/ginkgo/v2 v2.19.0 h1:9Cnnf7UHo57Hy3k6/m5k3dRfGTMXGvxhHFvkDTCTpvA= -github.com/onsi/ginkgo/v2 v2.19.0/go.mod h1:rlwLi9PilAFJ8jCg9UE1QP6VBpd6/xj3SRC0d6TU0To= -github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= -github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= +github.com/onsi/ginkgo/v2 v2.19.1 h1:QXgq3Z8Crl5EL1WBAC98A5sEBHARrAJNzAmMxzLcRF0= +github.com/onsi/ginkgo/v2 v2.19.1/go.mod h1:O3DtEWQkPa/F7fBMgmZQKKsluAy8pd3rEQdrjkPb9zA= +github.com/onsi/gomega v1.34.0 h1:eSSPsPNp6ZpsG8X1OVmOTxig+CblTc4AxpPBykhe2Os= +github.com/onsi/gomega v1.34.0/go.mod h1:MIKI8c+f+QLWk+hxbePD4i0LMJSExPaZOVfkoex4cAo= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= @@ -141,8 +142,8 @@ github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUo github.com/templexxx/xorsimd v0.4.2/go.mod h1:HgwaPoDREdi6OnULpSfxhzaiiSUY4Fi3JPn1wpt28NI= github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= -github.com/trzsz/go-arg v1.5.3 h1:eIDwDEmvSahtr5HpQOLrSa+YMqWQQ0H20xx60XgXQJw= -github.com/trzsz/go-arg v1.5.3/go.mod h1:IC6Z/FiVH7uYvcbp1/gJhDYCFPS/GkL0APYakVvgY4I= +github.com/trzsz/go-arg v1.5.4 h1:8cuwV8F1UvlKRXJcdLG1cSZNGXkRped+U1rz17M7Kac= +github.com/trzsz/go-arg v1.5.4/go.mod h1:IC6Z/FiVH7uYvcbp1/gJhDYCFPS/GkL0APYakVvgY4I= github.com/trzsz/go-socks5 v0.1.0 h1:R5gbAkGf4EOuwYG3aYZF2lh72e/paFgNkBJqIohHqyE= github.com/trzsz/go-socks5 v0.1.0/go.mod h1:BN+xFP3tb8oKl4hQTFDQIoL+tdCaJ0QhJKLwpmyjVik= github.com/trzsz/iterm2 v0.1.2 h1:VwfLzr2fKeaLf+p4tS0ms+kqdiQQxVLbTJUoyuQXmK8= @@ -153,18 +154,18 @@ github.com/trzsz/promptui v0.10.7 h1:77uBrmsIPYYJS/9n+zwFRhwOz82EKXkkdjOiWSEUPpk github.com/trzsz/promptui v0.10.7/go.mod h1:9dp59ixe32qBV9GjDxQ1PDWwbzHjTzveZenQwEoVHbg= github.com/trzsz/ssh_config v1.3.6 h1:+LBCg2uzhAgw2s19yqeUdD4YwW2z4kvlsXtKB6zDjmQ= github.com/trzsz/ssh_config v1.3.6/go.mod h1:uSVHpGOTpBwE1FwyUrtnanlFuxZKt4dvdKFVKe41h58= -github.com/trzsz/trzsz-go v1.1.8-0.20240720040127-f462ee534824 h1:J+uSciazjAj4WFRySEKE0bMvz5J3RsrNj8WFrC1rxX4= -github.com/trzsz/trzsz-go v1.1.8-0.20240720040127-f462ee534824/go.mod h1:tCVwbq4ghsgyPivZDhnJIspCrvONWfuXi4Nhq+1ukwE= -github.com/trzsz/tsshd v0.1.3-0.20240720085856-c35640ee47be h1:tQQvoH7vqA364vy8P0zFSoY6icsSMydSjTkM7bq1wFw= -github.com/trzsz/tsshd v0.1.3-0.20240720085856-c35640ee47be/go.mod h1:PhAA+1QmqGYknoTQ3GJTZ02x4B6GT6sMkWoVjhCxdFA= +github.com/trzsz/trzsz-go v1.1.8-0.20240728122806-7706c75f5426 h1:WKC74ASnRo4bgwdXOZz7LKP6Cn6iC0rN9hz0AttVdt4= +github.com/trzsz/trzsz-go v1.1.8-0.20240728122806-7706c75f5426/go.mod h1:iwnXIo52Onaxd6DLcEiIv78MZJwHA0Z0u7YBTe7n6wE= +github.com/trzsz/tsshd v0.1.3-0.20240728123755-e3d9535a080b h1:N9ncbjEeYR8qjIzVGepUvOD+j0goThBpB+tsRlQAgKo= +github.com/trzsz/tsshd v0.1.3-0.20240728123755-e3d9535a080b/go.mod h1:TDwgmaVsl3pkKCQfAiaOl+oM01M4W9U2hZ1OZMeUMOA= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/xtaci/kcp-go/v5 v5.6.8 h1:jlI/0jAyjoOjT/SaGB58s4bQMJiNS41A2RKzR6TMWeI= -github.com/xtaci/kcp-go/v5 v5.6.8/go.mod h1:oE9j2NVqAkuKO5o8ByKGch3vgVX3BNf8zqP8JiGq0bM= +github.com/xtaci/kcp-go/v5 v5.6.11 h1:I2fPv8wSxV1hp21VVKUzABB8T/e3T177keaEzIicJXA= +github.com/xtaci/kcp-go/v5 v5.6.11/go.mod h1:GSs9Z62r41kTb4CxKaRKr6ED+j7I0sUvj7Koql/RN6c= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae h1:J0GxkO96kL4WF+AIT3M4mfUVinOCPgf2uUWYFUzN0sM= github.com/xtaci/lossyconn v0.0.0-20190602105132-8df528c0c9ae/go.mod h1:gXtu8J62kEgmN++bm9BVICuT/e8yiLI2KFobd/TRFsE= -github.com/xtaci/smux v1.5.24 h1:77emW9dtnOxxOQ5ltR+8BbsX1kzcOxQ5gB+aaV9hXOY= -github.com/xtaci/smux v1.5.24/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY= +github.com/xtaci/smux v1.5.25 h1:pNWlUQFcE0O4letC2+giIjxbGVQXWmjD5vAc/opm3A0= +github.com/xtaci/smux v1.5.25/go.mod h1:OMlQbT5vcgl2gb49mFkYo6SMf+zP3rcjcwQz7ZU7IGY= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU= diff --git a/tssh/args.go b/tssh/args.go index 9cedef8..2c73930 100644 --- a/tssh/args.go +++ b/tssh/args.go @@ -78,6 +78,7 @@ type sshArgs struct { DragFile bool `arg:"--dragfile" help:"enable drag files and directories to upload"` TraceLog bool `arg:"--tracelog" help:"enable trzsz detect trace logs for debugging"` Relay bool `arg:"--relay" help:"force trzsz run as a relay on the jump server"` + Client bool `arg:"--client" help:"force trzsz run as a client on the jump server"` Debug bool `arg:"--debug" help:"verbose mode for debugging, same as ssh's -vvv"` Zmodem bool `arg:"--zmodem" help:"enable zmodem lrzsz ( rz / sz ) feature"` Udp bool `arg:"--udp" help:"ssh over UDP like mosh (default mode: QUIC)"` @@ -91,6 +92,8 @@ type sshArgs struct { TrzszBinPath string `arg:"--trzsz-bin-path" placeholder:"path" help:"[tools] trzsz binary installation package path"` TsshdVersion string `arg:"--tsshd-version" placeholder:"x.x.x" help:"[tools] install the specified version of tsshd"` TsshdBinPath string `arg:"--tsshd-bin-path" placeholder:"path" help:"[tools] tsshd binary installation package path"` + UploadFile multiStr `arg:"--upload-file" placeholder:"path" help:"[tools] upload the local file to remote server"` + DownloadPath string `arg:"--download-path" placeholder:"path" help:"[tools] the local saving path for downloading"` originalDest string } diff --git a/tssh/args_test.go b/tssh/args_test.go index 48d1099..e4f6dcd 100644 --- a/tssh/args_test.go +++ b/tssh/args_test.go @@ -85,6 +85,7 @@ func TestSshArgs(t *testing.T) { assertArgsEqual("--dragfile", sshArgs{DragFile: true}) assertArgsEqual("--tracelog", sshArgs{TraceLog: true}) assertArgsEqual("--relay", sshArgs{Relay: true}) + assertArgsEqual("--client", sshArgs{Client: true}) assertArgsEqual("--debug", sshArgs{Debug: true}) assertArgsEqual("--zmodem", sshArgs{Zmodem: true}) @@ -102,6 +103,10 @@ func TestSshArgs(t *testing.T) { assertArgsEqual("--install-tsshd --tsshd-version 0.1.2", sshArgs{InstallTsshd: true, TsshdVersion: "0.1.2"}) assertArgsEqual("--install-tsshd --tsshd-bin-path b.tgz", sshArgs{InstallTsshd: true, TsshdBinPath: "b.tgz"}) + assertArgsEqual("--upload-file /tmp/1", sshArgs{UploadFile: multiStr{[]string{"/tmp/1"}}}) + assertArgsEqual("--upload-file /tmp/1 --upload-file /tmp/2", sshArgs{UploadFile: multiStr{[]string{"/tmp/1", "/tmp/2"}}}) + assertArgsEqual("--download-path ~/Downloads", sshArgs{DownloadPath: "~/Downloads"}) + assertArgsEqual("dest", sshArgs{Destination: "dest"}) assertArgsEqual("dest cmd", sshArgs{Destination: "dest", Command: "cmd"}) assertArgsEqual("dest cmd arg1", sshArgs{Destination: "dest", Command: "cmd", Argument: []string{"arg1"}}) diff --git a/tssh/main.go b/tssh/main.go index 115573b..4d2c50e 100644 --- a/tssh/main.go +++ b/tssh/main.go @@ -120,7 +120,7 @@ var isTerminal bool = isatty.IsTerminal(os.Stdin.Fd()) || isatty.IsCygwinTermina func TsshMain(argv []string) int { // parse ssh args var args sshArgs - parser, err := arg.NewParser(arg.Config{Out: os.Stderr, Exit: os.Exit}, &args) + parser, err := arg.NewParser(arg.Config{HideLongOptions: true, Out: os.Stderr, Exit: os.Exit}, &args) if err != nil { fmt.Fprintln(os.Stderr, err) return -1 @@ -194,17 +194,16 @@ func TsshMain(argv []string) int { args.originalDest = dest // start ssh program - if err = sshStart(&args); err != nil { - return 6 - } - return 0 + var code int + code, err = sshStart(&args) + return code } -func sshStart(args *sshArgs) error { +func sshStart(args *sshArgs) (int, error) { // ssh login ss, err := sshLogin(args) if err != nil { - return err + return 10, err } defer ss.Close() @@ -213,18 +212,18 @@ func sshStart(args *sshArgs) error { var wg *sync.WaitGroup wg, err = stdioForward(ss.client, args.StdioForward) if err != nil { - return err + return 11, err } cleanupAfterLogin() wg.Wait() - return nil + return 0, nil } // not executing remote command if args.NoCommand { cleanupAfterLogin() _ = ss.client.Wait() - return nil + return 0, nil } // set terminal title @@ -236,16 +235,18 @@ func sshStart(args *sshArgs) error { } // execute remote tools if necessary - execRemoteTools(args, ss.client) + if code, quit := execRemoteTools(args, ss); quit { + return code, nil + } // run command or start shell if ss.cmd != "" { if err := ss.session.Start(ss.cmd); err != nil { - return fmt.Errorf("start command [%s] failed: %v", ss.cmd, err) + return 12, fmt.Errorf("start command [%s] failed: %v", ss.cmd, err) } } else { if err := ss.session.Shell(); err != nil { - return fmt.Errorf("start shell failed: %v", err) + return 13, fmt.Errorf("start shell failed: %v", err) } } @@ -256,14 +257,14 @@ func sshStart(args *sshArgs) error { if isTerminal && ss.tty { state, err := makeStdinRaw() if err != nil { - return err + return 14, err } defer resetStdin(state) } // enable trzsz if err := enableTrzsz(args, ss); err != nil { - return err + return 15, err } // cleanup and wait for exit @@ -277,5 +278,5 @@ func sshStart(args *sshArgs) error { if !isTerminal { outputWaitGroup.Wait() } - return nil + return 0, nil } diff --git a/tssh/tools.go b/tssh/tools.go index ac03e63..35cc225 100644 --- a/tssh/tools.go +++ b/tssh/tools.go @@ -465,11 +465,16 @@ func execLocalTools(argv []string, args *sshArgs) (int, bool) { } // execRemoteTools execute remote tools if necessary -func execRemoteTools(args *sshArgs, client SshClient) { +func execRemoteTools(args *sshArgs, ss *sshClientSession) (int, bool) { if args.InstallTrzsz { - execInstallTrzsz(args, client) + execInstallTrzsz(args, ss.client) } if args.InstallTsshd { - execInstallTsshd(args, client) + execInstallTsshd(args, ss.client) } + if len(args.UploadFile.values) > 0 { + code := execTrzUpload(args, ss) + return code, true + } + return 0, false } diff --git a/tssh/tools_upload.go b/tssh/tools_upload.go new file mode 100644 index 0000000..344cf3a --- /dev/null +++ b/tssh/tools_upload.go @@ -0,0 +1,85 @@ +/* +MIT License + +Copyright (c) 2023-2024 The Trzsz SSH Authors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +package tssh + +import ( + "fmt" + "net" + "os" + "time" + + "github.com/trzsz/trzsz-go/trzsz" +) + +func execTrzUpload(args *sshArgs, ss *sshClientSession) int { + if len(args.UploadFile.values) == 0 { + return 0 + } + + wrapStdIO(nil, nil, ss.serverErr, ss.tty) + trzsz.SetAffectedByWindows(false) + width, _, err := getTerminalSize() + if err == nil { + width = 80 + } + trzszFilter := trzsz.NewTrzszFilter(os.Stdin, os.Stdout, ss.serverIn, ss.serverOut, trzsz.TrzszOptions{ + TerminalColumns: int32(width), + DetectTraceLog: args.TraceLog, + EnableZmodem: true, + }) + defer trzszFilter.ResetTerminal() + onTerminalResize(func(width, height int) { + trzszFilter.SetTerminalColumns(int32(width)) + }) + trzszFilter.SetProgressColorPair(userConfig.progressColorPair) + trzszFilter.SetTunnelConnector(func(port int) net.Conn { + conn, _ := ss.client.DialTimeout("tcp", fmt.Sprintf("127.0.0.1:%d", port), time.Second) + return conn + }) + + files := args.UploadFile.values + errCh, err := trzszFilter.OneTimeUpload(files) + if err != nil { + warning("uplaod %v failed: %v", files, err) + return 1 + } + + cmd := ss.cmd + if cmd == "" { + cmd = "trz -d" + } + if err := ss.session.Start(cmd); err != nil { + warning("start command [%s] failed: %v", cmd, err) + return 2 + } + cleanupAfterLogin() + _ = ss.session.Wait() + + if err := <-errCh; err != nil { + warning("upload %v failed: %v", files, err) + return 3 + } + return 0 +} diff --git a/tssh/trzsz.go b/tssh/trzsz.go index ee704a9..7b6af25 100644 --- a/tssh/trzsz.go +++ b/tssh/trzsz.go @@ -136,7 +136,7 @@ func enableTrzsz(args *sshArgs, ss *sshClientSession) error { trzsz.SetAffectedByWindows(false) - if args.Relay || isNoGUI() { + if args.Relay || !args.Client && isNoGUI() { // run as a relay trzszRelay := trzsz.NewTrzszRelay(os.Stdin, os.Stdout, ss.serverIn, ss.serverOut, trzsz.TrzszOptions{ DetectTraceLog: args.TraceLog, @@ -173,6 +173,11 @@ func enableTrzsz(args *sshArgs, ss *sshClientSession) error { EnableOSC52: enableOSC52, }) + // reset terminal on exit + onExitFuncs = append(onExitFuncs, func() { + trzszFilter.ResetTerminal() + }) + // reset terminal size on resize onTerminalResize(func(width, height int) { trzszFilter.SetTerminalColumns(int32(width)) @@ -181,12 +186,19 @@ func enableTrzsz(args *sshArgs, ss *sshClientSession) error { // setup trzsz config trzszFilter.SetDefaultUploadPath(userConfig.defaultUploadPath) - trzszFilter.SetDefaultDownloadPath(userConfig.defaultDownloadPath) + + downloadPath := args.DownloadPath + if downloadPath == "" { + downloadPath = userConfig.defaultDownloadPath + } + trzszFilter.SetDefaultDownloadPath(downloadPath) + dragFileUploadCommand := getExOptionConfig(args, "DragFileUploadCommand") if dragFileUploadCommand == "" { dragFileUploadCommand = userConfig.dragFileUploadCommand } trzszFilter.SetDragFileUploadCommand(dragFileUploadCommand) + trzszFilter.SetProgressColorPair(userConfig.progressColorPair) // setup tunnel connect