From 0171f03f33eabbcdfacfd7d716cdaa46e1be66bd Mon Sep 17 00:00:00 2001 From: lenbo Date: Wed, 21 Aug 2019 13:51:40 +0800 Subject: [PATCH] v1.1.0 Feature/optimize (#18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 优化窗口大小监听逻辑,降低CPU使用 cp命令显示功能优化 * cp 命令允许remote->remote * 优化代码 增加socks5代理 * bug fix update failed #15 main包没有了 #17 install,无法使用,看脚本里没有autossh可执行文件 #16 * 优化安装脚本 * Update README.md * add license info --- .gitignore | 1 - LICENSE | 21 +++ README.md | 66 ++++++- build.sh | 3 + doc/images/ezgif-5-42b5117192fc.gif | Bin 0 -> 101103 bytes go.mod | 12 +- go.sum | 24 +-- install | 5 +- src/app/app.go | 53 +++--- src/app/config.go | 32 +++- src/app/handle_add.go | 4 +- src/app/io_client.go | 92 +++++----- src/app/scan.go | 2 +- src/app/server.go | 133 ++++++++++---- src/app/server_test.go | 57 ++++++ src/app/show_cp.go | 258 ++++++++++++++-------------- src/app/show_help.go | 16 +- src/app/show_servers.go | 5 +- src/app/show_upgrade.go | 24 ++- src/main/main.go | 16 ++ src/utils/size_format.go | 17 ++ src/utils/str.go | 36 ++-- 22 files changed, 580 insertions(+), 297 deletions(-) create mode 100644 LICENSE create mode 100644 doc/images/ezgif-5-42b5117192fc.gif create mode 100644 src/app/server_test.go create mode 100644 src/main/main.go create mode 100644 src/utils/size_format.go diff --git a/.gitignore b/.gitignore index e566e8f..d648c5f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,6 @@ servers.json config.json releases/ .idea -main test.go app.log vendor diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bc0de05 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 lenbo + +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. diff --git a/README.md b/README.md index 3634929..a568f23 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # autossh -一个ssh远程客户端,可一键登录远程服务器,主要用来弥补Mac/Linux Terminal ssh无法保存密码的不足。 +一个SSH远程客户端,可一键登录远程服务器,主要用来弥补Mac/Linux Terminal SSH无法保存密码的不足。 -![演示](https://raw.githubusercontent.com/islenbo/autossh/b3e18c35ebced882ace59be7843d9a58d1ac74d7/doc/images/ezgif-1-a4ddae192f.gif) +![演示](https://raw.githubusercontent.com/islenbo/autossh/c9b52688dabbba8ef6403c2f83f8d758ae0e4dfe/doc/images/ezgif-5-42b5117192fc.gif) ## 功能说明 - 核心代码重构,使用go.mod管理依赖 @@ -13,6 +13,7 @@ - 删除功能支持ctrl+d退出 - 优化帮助菜单显示 - 修复若干Bug +- 支持SOCKS5代理 ## 下载 [https://github.com/islenbo/autossh/releases](https://github.com/islenbo/autossh/releases) @@ -22,19 +23,66 @@ - Windows用户可手动编译,参考编译章节。 ## config.json字段说明 -- TODO 字段说明 +```yaml +show_detail: bool <是否显示服务器详情(用户、IP)> +options: + ServerAliveInterval: int <是否定时发送心跳包,与SSH ServerAliveInterval属性用法相同> +servers: + - name: string <显示名称> + ip: string <服务器IP> + port: int <端口> + user: string <用户名> + password: string <密码> + method: string <鉴权方式,password-密码 key-密钥> + key: string <私钥路径> + options: + ServerAliveInterval: int <与根节点ServerAliveInterval用法相同,该值会覆盖根节点的值> + alias: string <别名> + log: + enable: bool <是否启用日志> + filename: string <日志路径, 如 /tmp/%n-%u-%dt.log 支持变量请参考下方《日志变量》章节> + mode: string <遇到同名日志的处理模式,cover-覆盖 append-追加,默认为cover> +groups: + - group_name: string <组名> + prefix: string <组前缀> + servers: array <服务器列表,配置与servers相同,配置说明略> + collapse: bool <是否折叠> + proxy: + type: string <代理方式,目前仅支持SOCKS5> + server: string <代理服务器地址> + port: int <端口号> + user: string <用户,若无留空> + password: string <密码,若无留空> +``` + +## 日志变量 +变量 | 说明 | 示例 +--- | --- | --- +%g | 组名(group_name) | MyGroup1 +%n | 显示名称(name) | vagrant +%u | 用户名(user) | root +%a | 别名(alias) | vagrant +%dt | 日期时间 | 20190821223010 +%d | 日期 | 20190821 ## Q&A -- Q: Downloads中为什么没有Windows的包? -- A: Windows下有很多ssh工具,autossh主要是面向Mac/Linux群体。 + +### Q: 为什么没有Windows的包? +A: Windows下有很多优秀的SSH工具,autossh主要是面向Mac/Linux群体,Windows用户可自行编译。 + +### Q: cp 命令出现报错: ssh: subsystem request failed +A: 修改服务器 /etc/ssh/sshd_config 将 `Subsystem sftp /usr/libexec/openssh/sftp-server` 的注释打开,重启 sshd 服务。 ## 编译 -export GO111MODULE="on" -export GOFLAGS=" -mod=vendor" -go build main.go +```bash +sh build.sh +``` ## 依赖 - 查阅 go.mod ## 注意 -v0.X版本配置文件无法与v1.X版本兼容,请勿使用! \ No newline at end of file +v0.X版本配置文件无法与v1.X版本兼容,请勿使用! + +## License +MIT \ No newline at end of file diff --git a/build.sh b/build.sh index 11477b6..7cdddf6 100644 --- a/build.sh +++ b/build.sh @@ -1,5 +1,8 @@ #!/bin/bash +export GO111MODULE="on" +go mod tidy + PROJECT="autossh" VERSION="v1.1.0" BUILD=`date +%FT%T%z` diff --git a/doc/images/ezgif-5-42b5117192fc.gif b/doc/images/ezgif-5-42b5117192fc.gif new file mode 100644 index 0000000000000000000000000000000000000000..9daa7992a974541e6e421d3c6c3ecbef59f0f2ab GIT binary patch literal 101103 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

ep}n}#P4oTA=xg&=znnhz`jVQnf$MS3 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 hDtqtpxf!(d%* github.com/golang/crypto v0.0.0-20190701094942-4def268fd1a4 + golang.org/x/net => github.com/golang/net v0.0.0-20190813141303-74dc4d7220e7 + golang.org/x/sys => github.com/golang/sys v0.0.0-20190813064441-fde4db37ae7a +) diff --git a/go.sum b/go.sum index a3bf533..bee506c 100644 --- a/go.sum +++ b/go.sum @@ -1,16 +1,20 @@ -github.com/islenbo/scp v0.0.0-20170824162307-f7b48647feef3e30991f2ecc16b2b9a977d9a7c3 h1:GZQQKQUN0Bp02sJNGN6/FoqXeKRVqftdsv6WFhwhcR4= -github.com/islenbo/scp v0.0.0-20170824162307-f7b48647feef3e30991f2ecc16b2b9a977d9a7c3/go.mod h1:h2o9ndCCKJY6lGA8rN7Da+3RVv2h5d36KKTyLGuS/T8= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= -github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang/crypto v0.0.0-20190701094942-4def268fd1a4 h1:SqpWDZAu6UkmbvUTCtyNpBZLY8110TJ7bgxIki3pZw0= +github.com/golang/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +github.com/golang/net v0.0.0-20190813141303-74dc4d7220e7 h1:0oy3VQWim3zJeCPQgw9ka5X1odfKEgRUxblrM6z/rCY= +github.com/golang/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +github.com/golang/sys v0.0.0-20190813064441-fde4db37ae7a h1:hVLU4+cxX4r89gounKarktyMqZ2cx/5Y2jeGLtWqzUE= +github.com/golang/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.10.0 h1:DGA1KlA9esU6WcicH+P8PxFZOl15O6GYtab1cIJdOlE= github.com/pkg/sftp v1.10.0/go.mod h1:NxmoDg/QLVWluQDUYG7XBZTLUpKeFa8e3aMf1BfjyHk= -github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef h1:7D6Nm4D6f0ci9yttWaKjM1TMAXrH5Su72dojqYGntFY= -github.com/tmc/scp v0.0.0-20170824174625-f7b48647feef/go.mod h1:WLFStEdnJXpjK8kd4qKLwQKX/1vrDzp5BcDyiZJBHJM= -golang.org/x/crypto v0.0.0-20181024132630-e84da0312774c21d64ee2317962ef669b27ffb41 h1:IlSGaL0SqFiWKa+4DitAJUo/USS7vA2+5VhRfkkF048= -golang.org/x/crypto v0.0.0-20181024132630-e84da0312774c21d64ee2317962ef669b27ffb41/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862 h1:rM0ROo5vb9AdYJi1110yjWGMej9ITfKddS89P3Fkhug= -golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/install b/install index bdd5daf..7b946b2 100755 --- a/install +++ b/install @@ -3,17 +3,16 @@ cd $(dirname $0) mkdir -p ~/autossh/ -cp ./autossh ~/autossh/ +cp -f ./autossh ~/autossh/ CONFIG_FILE=`cd ~/autossh/ && pwd`/config.json if [[ ! -f ${CONFIG_FILE} ]]; then - echo "not exists" cp ./config.json ~/autossh/ fi HAS_ALIAS=`cat ~/.bash_profile | grep autossh | wc -l` if [[ ${HAS_ALIAS} -eq 0 ]]; then - echo "alias autossh='~/autossh/autossh'" >> ~/.bash_profile + echo "export PATH=$PATH:~/autossh/" >> ~/.bash_profile fi source ~/.bash_profile diff --git a/src/app/app.go b/src/app/app.go index 526f523..df14587 100644 --- a/src/app/app.go +++ b/src/app/app.go @@ -1,38 +1,46 @@ package app import ( - "autossh/src/utils" "flag" + "os" + "path/filepath" ) var ( Version string Build string - varVersion bool - varHelp bool - varUpgrade bool - varCp bool - varConfig = "./config.json" + c string + v bool + h bool + upgrade bool + cp bool ) func init() { - flag.BoolVar(&varVersion, "v", varVersion, "版本信息") - flag.BoolVar(&varVersion, "version", varVersion, "版本信息") - flag.BoolVar(&varHelp, "h", varHelp, "帮助信息") - flag.BoolVar(&varHelp, "help", varHelp, "帮助信息") - flag.StringVar(&varConfig, "c", varConfig, "指定配置文件路径") - flag.StringVar(&varConfig, "config", varConfig, "指定配置文件路径") + // 取执行文件所在目录下的config.json + dir, _ := os.Executable() + c = filepath.Dir(dir) + "/config.json" + flag.StringVar(&c, "c", c, "指定配置文件路径") + flag.StringVar(&c, "config", c, "指定配置文件路径") + + flag.BoolVar(&v, "v", v, "版本信息") + flag.BoolVar(&v, "version", v, "版本信息") + + flag.BoolVar(&h, "h", h, "帮助信息") + flag.BoolVar(&h, "help", h, "帮助信息") + + flag.Usage = usage flag.Parse() if len(flag.Args()) > 0 { arg := flag.Arg(0) switch arg { case "upgrade": - varUpgrade = true + upgrade = true case "cp": - varCp = true + cp = true default: defaultServer = arg } @@ -40,20 +48,15 @@ func init() { } func Run() { - if exists, _ := utils.FileIsExists(varConfig); !exists { - utils.Errorln("Can't read config file", varConfig) - return - } - - if varVersion { + if v { showVersion() - } else if varHelp { + } else if h { showHelp() - } else if varUpgrade { + } else if upgrade { showUpgrade() - } else if varCp { - showCp(varConfig) + } else if cp { + showCp(c) } else { - showServers(varConfig) + showServers(c) } } diff --git a/src/app/config.go b/src/app/config.go index c07bc63..4a13d08 100644 --- a/src/app/config.go +++ b/src/app/config.go @@ -14,8 +14,8 @@ import ( type Config struct { ShowDetail bool `json:"show_detail"` - Servers []Server `json:"servers"` - Groups []Group `json:"groups"` + Servers []*Server `json:"servers"` + Groups []*Group `json:"groups"` Options map[string]interface{} `json:"options"` // 服务器map索引,可通过编号、别名快速定位到某一个服务器 @@ -28,6 +28,21 @@ type Group struct { Prefix string `json:"prefix"` Servers []Server `json:"servers"` Collapse bool `json:"collapse"` + Proxy *Proxy `json:"proxy"` +} + +type ProxyType string + +const ( + ProxyTypeSocks5 ProxyType = "SOCKS5" +) + +type Proxy struct { + Type ProxyType `json:"type"` + Server string `json:"server"` + Port int `json:"port"` + User string `json:"user"` + Password string `json:"password"` } type LogMode string @@ -60,7 +75,7 @@ type ServerIndex struct { func (cfg *Config) createServerIndex() { cfg.serverIndex = make(map[string]ServerIndex) for i := range cfg.Servers { - server := &cfg.Servers[i] + server := cfg.Servers[i] server.Format() index := strconv.Itoa(i + 1) @@ -81,11 +96,12 @@ func (cfg *Config) createServerIndex() { } for i := range cfg.Groups { - group := &cfg.Groups[i] + group := cfg.Groups[i] for j := range group.Servers { server := &group.Servers[j] server.Format() server.groupName = group.GroupName + server.group = group index := group.Prefix + strconv.Itoa(j+1) if _, ok := cfg.serverIndex[index]; ok { @@ -136,7 +152,9 @@ func (cfg *Config) backup() error { return err } - defer srcFile.Close() + defer func() { + _ = srcFile.Close() + }() path, _ := filepath.Abs(filepath.Dir(cfg.file)) backupFile := path + "/config-" + time.Now().Format("20060102150405") + ".json" @@ -144,7 +162,9 @@ func (cfg *Config) backup() error { if err != nil { return err } - defer desFile.Close() + defer func() { + _ = desFile.Close() + }() _, err = io.Copy(desFile, srcFile) if err != nil { diff --git a/src/app/handle_add.go b/src/app/handle_add.go index 44e2539..635d918 100644 --- a/src/app/handle_add.go +++ b/src/app/handle_add.go @@ -9,7 +9,7 @@ import ( func handleAdd(cfg *Config, _ []string) error { groups := make(map[string]*Group) for i := range cfg.Groups { - group := &cfg.Groups[i] + group := cfg.Groups[i] groups[group.Prefix] = group utils.Info("["+group.Prefix+"]"+group.GroupName, "\t") } @@ -34,7 +34,7 @@ func handleAdd(cfg *Config, _ []string) error { group.Servers = append(group.Servers, server) server.groupName = group.GroupName } else { - cfg.Servers = append(cfg.Servers, server) + cfg.Servers = append(cfg.Servers, &server) } return cfg.saveConfig(true) diff --git a/src/app/io_client.go b/src/app/io_client.go index f55f89c..88b50cd 100644 --- a/src/app/io_client.go +++ b/src/app/io_client.go @@ -2,6 +2,7 @@ package app import ( "github.com/pkg/sftp" + "io/ioutil" "os" ) @@ -15,48 +16,59 @@ type FileLike interface { Write(p []byte) (n int, err error) } -const ( - IOClientLocal IOClientType = iota - IOClientSftp -) +type IOClient interface { + Stat(file string) (os.FileInfo, error) + Mkdir(path string) error + Create(file string) (FileLike, error) + Open(file string) (FileLike, error) + ReadDir(file string) ([]os.FileInfo, error) +} + +// Local +type LocalIOClient struct { +} + +func (client *LocalIOClient) Stat(file string) (os.FileInfo, error) { + return os.Stat(file) +} + +func (client *LocalIOClient) Mkdir(path string) error { + return os.Mkdir(path, 0755) +} + +func (client *LocalIOClient) Create(file string) (FileLike, error) { + return os.Create(file) +} + +func (client *LocalIOClient) Open(file string) (FileLike, error) { + return os.Open(file) +} + +func (client *LocalIOClient) ReadDir(file string) ([]os.FileInfo, error) { + return ioutil.ReadDir(file) +} -type IOClient struct { - ClientType IOClientType +// SFTP(Remote) +type SftpIOClient struct { SftpClient *sftp.Client } -// io stat -func (client *IOClient) Stat(file string) (os.FileInfo, error) { - switch client.ClientType { - case IOClientLocal: - return os.Stat(file) - case IOClientSftp: - return client.SftpClient.Stat(file) - default: - return os.Stat(file) - } -} - -// io mkdir -func (client *IOClient) Mkdir(path string) error { - switch client.ClientType { - case IOClientLocal: - return os.Mkdir(path, 0755) - case IOClientSftp: - return client.SftpClient.Mkdir(path) - default: - return os.Mkdir(path, 0755) - } -} - -// io create -func (client *IOClient) Create(file string) (FileLike, error) { - switch client.ClientType { - case IOClientLocal: - return os.Create(file) - case IOClientSftp: - return client.SftpClient.Create(file) - default: - return os.Create(file) - } +func (client *SftpIOClient) Stat(file string) (os.FileInfo, error) { + return client.SftpClient.Stat(file) +} + +func (client *SftpIOClient) Mkdir(path string) error { + return client.SftpClient.Mkdir(path) +} + +func (client *SftpIOClient) Create(file string) (FileLike, error) { + return client.SftpClient.Create(file) +} + +func (client *SftpIOClient) Open(file string) (FileLike, error) { + return client.SftpClient.Open(file) +} + +func (client *SftpIOClient) ReadDir(file string) ([]os.FileInfo, error) { + return client.SftpClient.ReadDir(file) } diff --git a/src/app/scan.go b/src/app/scan.go index 85a6dcf..47cfcfe 100644 --- a/src/app/scan.go +++ b/src/app/scan.go @@ -42,7 +42,7 @@ func scanInput(cfg *Config) (loop bool, clear bool, reload bool) { utils.Logger.Error("server connect error ", err) utils.Errorln(err) } - return true, true, false + return false, true, false } case InputCmdGroupPrefix: { diff --git a/src/app/server.go b/src/app/server.go index 8ecf460..afc56f7 100644 --- a/src/app/server.go +++ b/src/app/server.go @@ -3,14 +3,18 @@ package app import ( "autossh/src/utils" "errors" + "fmt" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/terminal" + "golang.org/x/net/proxy" "io/ioutil" "net" "os" + "os/signal" "strconv" "strings" + "syscall" "time" ) @@ -29,6 +33,7 @@ type Server struct { termWidth int termHeight int groupName string + group *Group } // 格式化,赋予默认值 @@ -96,12 +101,44 @@ func (server *Server) GetSshClient() (*ssh.Client, error) { addr := server.Ip + ":" + strconv.Itoa(server.Port) - client, err := ssh.Dial("tcp", addr, config) + if server.group != nil && server.group.Proxy != nil { + return server.proxySshClient(server.group.Proxy, addr, config) + } else { + return ssh.Dial("tcp", addr, config) + } +} + +func (server *Server) proxySshClient(p *Proxy, sshServerAddr string, sshConfig *ssh.ClientConfig) (client *ssh.Client, err error) { + var dialer proxy.Dialer + switch p.Type { + case ProxyTypeSocks5: + var auth proxy.Auth + if p.User != "" { + auth = proxy.Auth{ + User: p.User, + Password: p.Password, + } + } + + dialer, err = proxy.SOCKS5("tcp", p.Server+":"+strconv.Itoa(p.Port), &auth, proxy.Direct) + if err != nil { + return nil, err + } + default: + return nil, errors.New(fmt.Sprintf("unknown proxy type: %s", p.Type)) + } + + conn, err := dialer.Dial("tcp", sshServerAddr) + if err != nil { + return nil, err + } + + c, chans, reqs, err := ssh.NewClientConn(conn, sshServerAddr, sshConfig) if err != nil { return nil, err } - return client, nil + return ssh.NewClient(c, chans, reqs), nil } // 生成Sftp Client @@ -138,13 +175,15 @@ func (server *Server) Connect() error { if err != nil { return errors.New("创建文件描述符出错:" + err.Error()) } + defer terminal.Restore(fd, oldState) stopKeepAliveLoop := server.startKeepAliveLoop(session) defer close(stopKeepAliveLoop) - server.stdIO(session) - - defer terminal.Restore(fd, oldState) + err = server.stdIO(session) + if err != nil { + return err + } modes := ssh.TerminalModes{ ssh.ECHO: 1, @@ -153,12 +192,15 @@ func (server *Server) Connect() error { } server.termWidth, server.termHeight, _ = terminal.GetSize(fd) - if err := session.RequestPty("xterm-256color", server.termHeight, server.termWidth, modes); err != nil { + termType := os.Getenv("TERM") + if termType == "" { + termType = "xterm-256color" + } + if err := session.RequestPty(termType, server.termHeight, server.termWidth, modes); err != nil { return errors.New("创建终端出错:" + err.Error()) } - winChange := server.listenWindowChange(session, fd) - defer close(winChange) + server.listenWindowChange(session, fd) err = session.Shell() if err != nil { @@ -174,12 +216,15 @@ func (server *Server) Connect() error { } // 重定向标准输入输出 -func (server *Server) stdIO(session *ssh.Session) { +func (server *Server) stdIO(session *ssh.Session) error { session.Stderr = os.Stderr session.Stdin = os.Stdin if server.Log.Enable { - ch, _ := session.StdoutPipe() + ch, err := session.StdoutPipe() + if err != nil { + return err + } go func() { flag := os.O_RDWR | os.O_CREATE @@ -189,7 +234,11 @@ func (server *Server) stdIO(session *ssh.Session) { case LogModeCover: } - f, _ := os.OpenFile(server.formatLogFilename(server.Log.Filename), flag, 0644) + f, err := os.OpenFile(server.formatLogFilename(server.Log.Filename), flag, 0644) + if err != nil { + utils.Logger.Error("Open file fail ", err) + return + } for { buff := [4096]byte{} @@ -208,21 +257,25 @@ func (server *Server) stdIO(session *ssh.Session) { } else { session.Stdout = os.Stdout } + + return nil } // 格式化日志文件名 func (server *Server) formatLogFilename(filename string) string { - kvs := map[string]string{ - "%g": server.groupName, - "%n": server.Name, - "%dt": time.Now().Format("2006-01-02-15-04-05"), - "%d": time.Now().Format("2006-01-02"), - "%u": server.User, - "%a": server.Alias, + kvs := []map[string]string{ + {"%g": server.groupName}, + {"%n": server.Name}, + {"%dt": time.Now().Format("20060102150405")}, + {"%d": time.Now().Format("20060102")}, + {"%u": server.User}, + {"%a": server.Alias}, } - for k, v := range kvs { - filename = strings.ReplaceAll(filename, k, v) + for _, kv := range kvs { + for k, v := range kv { + filename = strings.ReplaceAll(filename, k, v) + } } return filename @@ -306,26 +359,40 @@ func (server *Server) startKeepAliveLoop(session *ssh.Session) chan struct{} { } // 监听终端窗口变化 -func (server *Server) listenWindowChange(session *ssh.Session, fd int) chan struct{} { - terminate := make(chan struct{}) +func (server *Server) listenWindowChange(session *ssh.Session, fd int) { go func() { + sigwinchCh := make(chan os.Signal, 1) + defer close(sigwinchCh) + + signal.Notify(sigwinchCh, syscall.SIGWINCH) + termWidth, termHeight, err := terminal.GetSize(fd) + if err != nil { + utils.Logger.Error(err) + } + for { select { - case <-terminate: - return - default: - termWidth, termHeight, _ := terminal.GetSize(fd) + // 阻塞读取 + case sigwinch := <-sigwinchCh: + if sigwinch == nil { + return + } + currTermWidth, currTermHeight, err := terminal.GetSize(fd) + + // 判断一下窗口尺寸是否有改变 + if currTermHeight == termHeight && currTermWidth == termWidth { + continue + } - if server.termWidth != termWidth || server.termHeight != termHeight { - server.termHeight = termHeight - server.termWidth = termWidth - session.WindowChange(termHeight, termWidth) + // 更新远端大小 + session.WindowChange(currTermHeight, currTermWidth) + if err != nil { + utils.Logger.Error(err) + continue } - time.Sleep(time.Millisecond * 3) + termWidth, termHeight = currTermWidth, currTermHeight } } }() - - return terminate } diff --git a/src/app/server_test.go b/src/app/server_test.go new file mode 100644 index 0000000..8635aef --- /dev/null +++ b/src/app/server_test.go @@ -0,0 +1,57 @@ +package app + +import ( + "fmt" + "golang.org/x/crypto/ssh" + "golang.org/x/net/proxy" + "log" + "net" + "testing" +) + +func TestServer_Connect(t *testing.T) { + var server = Server{ + Ip: "172.18.36.217", + Method: "key", + } + auth, err := parseAuthMethods(&server) + sshConfig := &ssh.ClientConfig{ + User: "work", + Auth: auth, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + // Auth: .... fill out with keys etc as normal + } + + client, err := proxiedSSHClient("127.0.0.1:1080", "172.18.36.217:22", sshConfig) + if err != nil { + log.Fatal(err) + } + + session, _ := client.NewSession() + output, _ := session.CombinedOutput("ls") + fmt.Println(string(output)) + + // get a session etc... + +} + +func proxiedSSHClient(proxyAddress, sshServerAddress string, sshConfig *ssh.ClientConfig) (*ssh.Client, error) { + dialer, err := proxy.SOCKS5("tcp", proxyAddress, nil, proxy.Direct) + if err != nil { + return nil, err + } + + conn, err := dialer.Dial("tcp", sshServerAddress) + if err != nil { + return nil, err + } + + c, chans, reqs, err := ssh.NewClientConn(conn, sshServerAddress, sshConfig) + if err != nil { + return nil, err + } + + return ssh.NewClient(c, chans, reqs), nil +} diff --git a/src/app/show_cp.go b/src/app/show_cp.go index d638aa3..2c8f138 100644 --- a/src/app/show_cp.go +++ b/src/app/show_cp.go @@ -7,38 +7,33 @@ import ( "github.com/pkg/errors" "github.com/pkg/sftp" "io" - "io/ioutil" "os" "path" + "strconv" "strings" + "syscall" + "time" + "unsafe" ) -type TransferObjectType int +type ResType int const ( - TransferObjectTypeLocal TransferObjectType = iota - TransferObjectTypeRemote + ResTypeSrc ResType = iota + ResTypeDst ) type TransferObject struct { - raw string // 原始数据,如 vagrant:/root/example.txt - cpType TransferObjectType // 类型,TransferObjectTypeLocal-本地,TransferObjectTypeRemote-远程 - server Server // 服务器,cpType = TransferObjectTypeRemote 时为空 - path string // 从raw解析得到的文件路径,如 /root/example.txt + raw string // 原始数据,如 vagrant:/root/example.txt + resType ResType // 类型,ResTypeSrc-源,ResTypeDst-目的 + server *Server // 服务器,当raw为本地址地时,该字段为空 + path string // 从raw解析得到的文件路径,如 /root/example.txt } -type CpType int - -const ( - CpTypeUpload CpType = iota - CpTypeDownload -) - type Cp struct { isDir bool cfg *Config - cpType CpType sources []*TransferObject target *TransferObject } @@ -58,100 +53,100 @@ func showCp(configFile string) { return } - if cp.sources[0].cpType == TransferObjectTypeLocal { - cp.cpType = CpTypeUpload - err = cp.upload() + var dstIoClient IOClient + if cp.target.server == nil { + dstIoClient = new(LocalIOClient) } else { - cp.cpType = CpTypeDownload - err = cp.download() + sftpClient, err := cp.target.server.GetSftpClient() + if err != nil { + utils.Errorln(err) + return + } + + defer func() { + _ = sftpClient.Close() + }() + + c := SftpIOClient{SftpClient: sftpClient} + dstIoClient = &c } - if err != nil { - utils.Errorln(err) - return + for _, source := range cp.sources { + var srcIoClient IOClient + var sftpClient *sftp.Client + + if source.server == nil { + srcIoClient = new(LocalIOClient) + } else { + sftpClient, err := source.server.GetSftpClient() + if err != nil { + cp.printFileError(source.path, err) + continue + } + + srcIoClient = &SftpIOClient{SftpClient: sftpClient} + } + + func() { + defer func() { + if sftpClient != nil { + _ = sftpClient.Close() + } + }() + + if file, err := cp.transferNew(srcIoClient, dstIoClient, source.path, cp.target.path, ""); err != nil { + cp.printFileError(file, err) + } + }() } } // 解析参数 func (cp *Cp) parse() error { - args := flag.Args()[1:] - if len(args) == 0 { - return errors.New("请输入完整参数") - } - - if args[0] == "-r" { - cp.isDir = true - args = args[1:] - } + os.Args = flag.Args() + flag.BoolVar(&cp.isDir, "r", false, "文件夹") + flag.Parse() + var args = flag.Args() + var length = len(args) var err error - switch len(args) { - case 0: + + if len(args) < 1 { return errors.New("请输入完整参数") - case 1: - // 默认取temp目录作为target - args = []string{args[0], os.TempDir()} } - length := len(args) cp.target, err = newTransferObject(*cp.cfg, args[length-1]) if err != nil { return err } cp.sources = make([]*TransferObject, 0) - for i, arg := range args[:length-1] { + for _, arg := range args[:length-1] { s, err := newTransferObject(*cp.cfg, arg) if err != nil { return err } - if s.cpType == TransferObjectTypeLocal && s.cpType == cp.target.cpType { + if s.resType == ResTypeSrc && s.resType == cp.target.resType { return errors.New("源和目标不能同时为本地地址") } - if i > 0 && s.cpType != cp.sources[i-1].cpType { - return errors.New("source 类型不一致") - } - cp.sources = append(cp.sources, s) } return nil } -// 上传 -func (cp *Cp) upload() error { - sftpClient, err := cp.target.server.GetSftpClient() - if err != nil { - return err - } - - defer func() { - _ = sftpClient.Close() - }() - - var ioClient = IOClient{ClientType: IOClientSftp, SftpClient: sftpClient} - - for _, source := range cp.sources { - if file, err := cp.transfer(&ioClient, source.path, cp.target.path, ""); err != nil { - cp.printFileError(file, err) - } - } - - return nil -} - // IO复制 src -> dst -func (cp *Cp) ioCopy(client *IOClient, srcFile FileLike, dst string, fSize int64) (string, error) { +func (cp *Cp) ioCopy(srcIO IOClient, dstIO IOClient, srcFile FileLike, dst string) (string, error) { var err error - dst, err = cp.parseDstFilename(client, srcFile.Name(), dst) + dst, err = cp.parseDstFilename(dstIO, srcFile.Name(), dst) if err != nil { return dst, err } - dstFile, err := client.Create(dst) + dstFile, err := dstIO.Create(dst) if err != nil { return dst, err } @@ -160,10 +155,28 @@ func (cp *Cp) ioCopy(client *IOClient, srcFile FileLike, dst string, fSize int64 _ = dstFile.Close() }() - bytes := [4096]byte{} bytesCount := 0 filename := path.Base(srcFile.Name()) + startTime := time.Now() + speed := 0.0 + var process = 0.0 + + go func() { + for { + cp.printProcess(filename, process, startTime, speed) + time.Sleep(time.Second) + if process >= 100 { + break + } + } + }() + + srcFileInfo, err := srcFile.Stat() + if err != nil { + return srcFile.Name(), err + } + bytes := make([]byte, 64*1024) for { n, err := srcFile.Read(bytes[:]) eof := err == io.EOF @@ -171,16 +184,16 @@ func (cp *Cp) ioCopy(client *IOClient, srcFile FileLike, dst string, fSize int64 return srcFile.Name(), err } - bytesCount += n - process := float64(bytesCount) / float64(fSize) * 100 - cp.printProcess(filename, process) - _, err = dstFile.Write(bytes[:n]) + wn, err := dstFile.Write(bytes[:n]) if err != nil { return cp.target.path, err } + bytesCount += wn + process = float64(bytesCount) / float64(srcFileInfo.Size()) * 100 + speed = float64(bytesCount) / time.Now().Sub(startTime).Seconds() if eof { - cp.printProcess(filename, 100.0) + cp.printProcess(filename, 100.0, startTime, speed) break } } @@ -189,34 +202,11 @@ func (cp *Cp) ioCopy(client *IOClient, srcFile FileLike, dst string, fSize int64 return "", nil } -// 下载 -func (cp *Cp) download() error { - for _, source := range cp.sources { - sftpClient, err := source.server.GetSftpClient() - if err != nil { - return err - } - - func() { - defer func() { - _ = sftpClient.Close() - }() - - var ioClient = IOClient{ClientType: IOClientLocal, SftpClient: sftpClient} - if file, err := cp.transfer(&ioClient, source.path, cp.target.path, ""); err != nil { - cp.printFileError(file, err) - } - }() - } - - return nil -} - // 传输 // 上传时,src = 本地,dst = 远程 // 下载时,src = 远程,dst = 本地 -func (cp *Cp) transfer(client *IOClient, src string, dst string, vPath string) (string, error) { - srcFile, err := cp.openFile(client.SftpClient, src) +func (cp *Cp) transferNew(srcIO IOClient, dstIO IOClient, src string, dst string, vPath string) (string, error) { + srcFile, err := srcIO.Open(src) if err != nil { return src, err } @@ -235,7 +225,7 @@ func (cp *Cp) transfer(client *IOClient, src string, dst string, vPath string) ( return src, errors.New("是一个目录") } - childFiles, err := cp.readDir(client.SftpClient, srcFile.Name()) + childFiles, err := srcIO.ReadDir(srcFile.Name()) if err != nil { return srcFile.Name(), err } @@ -248,14 +238,13 @@ func (cp *Cp) transfer(client *IOClient, src string, dst string, vPath string) ( for _, childFile := range childFiles { childFilename := path.Join(src, childFile.Name()) - if str, err := cp.transfer(client, childFilename, dst, vPath); err != nil { + if str, err := cp.transferNew(srcIO, dstIO, childFilename, dst, vPath); err != nil { cp.printFileError(str, err) } } } else { newDst := path.Join(dst, vPath) - - if file, err := cp.ioCopy(client, srcFile, newDst, srcFileInfo.Size()); err != nil { + if file, err := cp.ioCopy(srcIO, dstIO, srcFile, newDst); err != nil { return file, err } } @@ -267,7 +256,7 @@ func (cp *Cp) transfer(client *IOClient, src string, dst string, vPath string) ( // src = /root/example.txt dst = /root/ => /root/example.txt // src = /root/example.txt dst = /root => /root/example.txt // src = /root/example.txt dst = /root/new-name.txt => /root/new-name.txt -func (cp *Cp) parseDstFilename(client *IOClient, src string, dst string) (string, error) { +func (cp *Cp) parseDstFilename(client IOClient, src string, dst string) (string, error) { dstFileInfo, err := client.Stat(dst) if err != nil { if !os.IsNotExist(err) { @@ -298,31 +287,40 @@ func (cp *Cp) parseDstFilename(client *IOClient, src string, dst string) (string return dst, nil } -func (cp *Cp) printProcess(name string, process float64) { - // TODO 文件大小,执行时间 - fmt.Print("\r" + name + "\t\t\t" + fmt.Sprintf("%.2f", process) + "%") -} - -func (cp *Cp) printFileError(name string, err error) { - fmt.Println(name, ": ", err) -} +func (cp *Cp) printProcess(name string, process float64, startTime time.Time, speed float64) { + // TODO 文件大小 + execTime := time.Now().Sub(startTime) -// 根据上传/下载打开相应位置的文件 -func (cp *Cp) openFile(client *sftp.Client, file string) (FileLike, error) { - if cp.cpType == CpTypeUpload { - return os.Open(file) - } else { - return client.Open(file) + type winSize struct { + Row uint16 + Col uint16 + Xpixel uint16 + Ypixel uint16 + } + ws := &winSize{} + retCode, _, _ := syscall.Syscall(syscall.SYS_IOCTL, + uintptr(syscall.Stdin), + uintptr(syscall.TIOCGWINSZ), + uintptr(unsafe.Pointer(ws))) + + padding := 0 + if int(retCode) != -1 { + padding = int(ws.Col) - utils.ZhLen(name) - 40 } + + extInfo := fmt.Sprintf("%.2f%% %10s/s %02.0f:%02.0f:%02.0f", + process, + utils.SizeFormat(speed), + execTime.Hours(), + execTime.Minutes(), + execTime.Seconds()) + + format := "\r%s%-" + strconv.Itoa(padding) + "s%40s" + fmt.Printf(format, name, "", extInfo) } -// 根据上传/下载读取相应位置的目录,返回文件列表 -func (cp *Cp) readDir(client *sftp.Client, name string) ([]os.FileInfo, error) { - if cp.cpType == CpTypeUpload { - return ioutil.ReadDir(name) - } else { - return client.ReadDir(name) - } +func (cp *Cp) printFileError(name string, err error) { + fmt.Println(name, ": ", err) } // 创建传输对象 @@ -334,7 +332,7 @@ func newTransferObject(cfg Config, raw string) (*TransferObject, error) { args := strings.Split(raw, ":") switch len(args) { case 1: - obj.cpType = TransferObjectTypeLocal + obj.resType = ResTypeSrc obj.path = args[0] case 2: obj.path = strings.TrimSpace(args[1]) @@ -342,8 +340,8 @@ func newTransferObject(cfg Config, raw string) (*TransferObject, error) { if !exists { return nil, errors.New("服务器" + args[0] + "不存在") } - obj.cpType = TransferObjectTypeRemote - obj.server = *serverIndex.server + obj.resType = ResTypeDst + obj.server = serverIndex.server default: return nil, errors.New(raw + " 格式错误") diff --git a/src/app/show_help.go b/src/app/show_help.go index 46055a4..6fa5edd 100644 --- a/src/app/show_help.go +++ b/src/app/show_help.go @@ -6,7 +6,6 @@ import ( ) func showHelp() { - flag.Usage = usage flag.Usage() } @@ -17,16 +16,15 @@ Usage: autossh [options] [commands] Options: - -c, -config 指定配置文件。 - (default: ./config.json) - -v, -version 显示版本信息。 - -h, -help 显示帮助信息。 + -c, -config string 指定配置文件(default: ./config.json)。 + -v, -version 显示版本信息。 + -h, -help 显示帮助信息。 Commands: - upgrade 检测升级。 - cp [-r] source target 复制传输。 - ${ServerNum} 使用编号登录指定服务器。 - ${ServerAlias} 使用别名登录指定服务器。 + cp [-r] source target 复制传输。 + ${ServerNum} 使用编号登录指定服务器。 + ${ServerAlias} 使用别名登录指定服务器。 + upgrade 检测并更新到最新版本。 ` utils.Logln(str) } diff --git a/src/app/show_servers.go b/src/app/show_servers.go index e93aa2f..fb48cab 100644 --- a/src/app/show_servers.go +++ b/src/app/show_servers.go @@ -19,7 +19,6 @@ func showServers(configFile string) { for { loop, clear, reload := scanInput(cfg) - // TODO 解决进入服务器之后第一次输入无效的问题(进入新增、编辑、删除没问题) if !loop { break } @@ -73,8 +72,8 @@ func show(cfg *Config) { } // 计算分隔符长度 -func separatorLength(cfg Config) float64 { - maxlength := 60.0 +func separatorLength(cfg Config) int { + maxlength := 60 for _, group := range cfg.Groups { length := utils.ZhLen(group.GroupName) if length > maxlength { diff --git a/src/app/show_upgrade.go b/src/app/show_upgrade.go index 65025a9..20129d4 100644 --- a/src/app/show_upgrade.go +++ b/src/app/show_upgrade.go @@ -108,7 +108,9 @@ func (Upgrade) unzip(zipFile string, destDir string) (string, error) { if err != nil { return fullpath, err } - defer zipReader.Close() + defer func() { + _ = zipReader.Close() + }() for _, f := range zipReader.File { fpath := filepath.Join(destDir, f.Name) @@ -127,13 +129,17 @@ func (Upgrade) unzip(zipFile string, destDir string) (string, error) { if err != nil { return fullpath, err } - defer inFile.Close() + defer func() { + _ = inFile.Close() + }() outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode()) if err != nil { return fullpath, err } - defer outFile.Close() + defer func() { + _ = outFile.Close() + }() _, err = io.Copy(outFile, inFile) if err != nil { @@ -168,11 +174,15 @@ func (Upgrade) downloadFile(url string, downloadPath string, fb func(length, dow if err != nil { return err } - defer file.Close() + defer func() { + _ = file.Close() + }() if resp.Body == nil { return errors.New("body is null") } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() //下面是 io.copyBuffer() 的简化版本 for { //读取bytes @@ -234,7 +244,9 @@ func (upgrade *Upgrade) loadLatestVersion() { panic(err) } - defer resp.Body.Close() + defer func() { + _ = resp.Body.Close() + }() body, err := ioutil.ReadAll(resp.Body) if err != nil { panic(err) diff --git a/src/main/main.go b/src/main/main.go new file mode 100644 index 0000000..ec78eba --- /dev/null +++ b/src/main/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "autossh/src/app" +) + +var ( + Version = "unknown" + Build = "unknown" +) + +func main() { + app.Version = Version + app.Build = Build + app.Run() +} diff --git a/src/utils/size_format.go b/src/utils/size_format.go new file mode 100644 index 0000000..43b1e5d --- /dev/null +++ b/src/utils/size_format.go @@ -0,0 +1,17 @@ +package utils + +import ( + "math" + "strconv" +) + +func SizeFormat(size float64) string { + var k = 1024 // or 1024 + var sizes = []string{"B", "KB", "MB", "GB", "TB"} + if size == 0 { + return "0 B" + } + i := math.Floor(math.Log(size) / math.Log(float64(k))) + r := size / math.Pow(float64(k), i) + return strconv.FormatFloat(r, 'f', 2, 64) + " " + sizes[int(i)] +} diff --git a/src/utils/str.go b/src/utils/str.go index 3982a18..10a85dc 100644 --- a/src/utils/str.go +++ b/src/utils/str.go @@ -1,10 +1,12 @@ package utils -import "unicode" +import ( + "unicode" +) // 计算字符宽度(中文) -func ZhLen(str string) float64 { - length := 0.0 +func ZhLen(str string) int { + length := 0 for _, c := range str { if unicode.Is(unicode.Scripts["Han"], c) { length += 2 @@ -21,8 +23,8 @@ func ZhLen(str string) float64 { // c 填充符号 // maxlength 总长度 // 如: title = 测试 c=* maxlength = 10 返回 ** 返回 ** -func FormatSeparator(title string, c string, maxlength float64) string { - charslen := int((maxlength - ZhLen(title)) / 2) +func FormatSeparator(title string, c string, maxlength int) string { + charslen := (maxlength - ZhLen(title)) / 2 chars := "" for i := 0; i < charslen; i++ { chars += c @@ -32,15 +34,15 @@ func FormatSeparator(title string, c string, maxlength float64) string { } // 右填充 -func AppendRight(body string, char string, maxlength int) string { - length := int(ZhLen(body)) - if length >= maxlength { - return body - } - - for i := 0; i < maxlength-length; i++ { - body = body + char - } - - return body -} +//func AppendRight(body string, char string, maxlength int) string { +// length := ZhLen(body) +// if length >= maxlength { +// return body +// } +// +// for i := 0; i < maxlength-length; i++ { +// body = body + char +// } +// +// return body +//}