From 08411d78e53bb36abee78fd853ba93cfce7c0254 Mon Sep 17 00:00:00 2001 From: rinetd Date: Wed, 19 Dec 2018 14:33:31 +0800 Subject: [PATCH] add ssh functions: Run Exec Output --- README.md | 6 +- client.go | 10 +- example/bash/Makefile | 124 ++++++++++++++++++++++ example/{get => download}/main.go | 0 example/getdata/Makefile | 52 ++++++++++ example/getdata/main.go | 105 +++++++++++++++++++ example/getdata/rc/icon.ico | Bin 0 -> 370070 bytes example/getdata/rc/manifest.exe.manifest | 18 ++++ example/getdata/rc/versioninfo.json | 43 ++++++++ example/runetl/Makefile | 126 +++++++++++++++++++++++ example/runetl/main.go | 31 ++++++ example/runetl/rc/icon.ico | Bin 0 -> 67646 bytes example/runetl/rc/manifest.exe.manifest | 18 ++++ example/runetl/rc/versioninfo.json | 37 +++++++ {cmd => example/update}/main.go | 0 example/{put => upload}/main.go | 0 sftp.go | 7 +- ssh.go | 70 +++++++++++-- 18 files changed, 634 insertions(+), 13 deletions(-) create mode 100644 example/bash/Makefile rename example/{get => download}/main.go (100%) create mode 100644 example/getdata/Makefile create mode 100644 example/getdata/main.go create mode 100644 example/getdata/rc/icon.ico create mode 100644 example/getdata/rc/manifest.exe.manifest create mode 100644 example/getdata/rc/versioninfo.json create mode 100644 example/runetl/Makefile create mode 100644 example/runetl/main.go create mode 100644 example/runetl/rc/icon.ico create mode 100644 example/runetl/rc/manifest.exe.manifest create mode 100644 example/runetl/rc/versioninfo.json rename {cmd => example/update}/main.go (100%) rename example/{put => upload}/main.go (100%) diff --git a/README.md b/README.md index 4e5845a..6739c75 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,10 @@ ## Example ### 在远程执行ssh命令 +提供3个方法: Run() Exec() Output() +1. Run() : 程序执行后,不再受执行者控制. 适用于启动服务端进程. +2. Exec() : 在控制台同步实时输出程序的执行结果. +3. Output() : 会等待程序执行完成后,输出执行结果,在需要对执行的结果进行操作时使用. ```go package main import ( @@ -32,7 +36,7 @@ func main() { } defer c.Close() - output, err := c.Exec("uptime") + output, err := c.Output("uptime") if err != nil { panic(err) } diff --git a/client.go b/client.go index bb26658..e63b1c5 100644 --- a/client.go +++ b/client.go @@ -17,6 +17,7 @@ const DefaultTimeout = 30 * time.Second type Client struct { *Config SSHClient *ssh.Client + SSHSession *ssh.Session SFTPClient *sftp.Client } @@ -65,7 +66,13 @@ func New(cnf *Config) (client *Client, err error) { return client, errors.New("Failed to conn sftp: " + err.Error()) } - return &Client{SSHClient: sshClient, SFTPClient: sftpClient}, nil + session, err := sshClient.NewSession() + if err != nil { + return nil, err + } + // defer session.Close() + + return &Client{SSHClient: sshClient, SFTPClient: sftpClient, SSHSession: session}, nil } // NewClient 根据配置 @@ -149,4 +156,5 @@ func NewWithPrivateKey(Host, Port, User, Passphrase string) (client *Client, err func (c *Client) Close() { c.SFTPClient.Close() c.SSHClient.Close() + c.SSHSession.Close() } diff --git a/example/bash/Makefile b/example/bash/Makefile new file mode 100644 index 0000000..b150f08 --- /dev/null +++ b/example/bash/Makefile @@ -0,0 +1,124 @@ +PROJECT = transfer +VERSION = $(shell git describe) +BRANCH = $(shell git name-rev --name-only HEAD) +VERSION ="$(echo $TRAVIS_TAG)" +ARCNAME = $(PROJECT)-$(VERSION)-$(GOOS)-$(GOARCH) +ARTIFACTS_DIR=artifact + + +# ZIP="zip -9 -r" +# TAR="tar cvf" +# GZIP="gzip -9" +# GZIP_7Z="7za a -tgzip -mx9" +# ZIP_7Z="7za a -tzip -mx9" +ifndef ZIP + ifneq ($(shell which zip 2>/dev/null),) + ZIP := zip -9 + endif + ifneq ($(shell which 7z 2>/dev/null),) + ZIP := 7z a -tzip -mx=9 + endif + ifneq ($(shell which 7za 2>/dev/null),) + ZIP := 7za a -tzip -mx=9 + endif + ifndef ZIP + $(warning "No zip / 7z / 7za in ($(PATH))") + ZIP := : zip_not_found + endif +endif + +.PHONY: all release release-all +all: release-all + + +.PHONY: windows-dependencies +windows-dependencies: + @ go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo + +.PHONY: embed-assets +embed-assets: + @ GO111MODULE=off go get -u github.com/jteeuwen/go-bindata/... + @# go-bindata ./logos/microBadger_headert.png ./webpage.html + + + +release: + env CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build $(FLAGS) -ldflags='-s -w -X github.com/rinetd/transfer/version.Version=$(VERSION)' -o build/$(GOOS)-$(GOARCH)/$(PROJECT)$(EXT) + - tar czf build/$(ARCNAME).tar.gz -C build/$(GOOS)-$(GOARCH)/ $(PROJECT)$(EXT) + - tar czf build/$(ARCNAME).tar.gz -C build/$(GOOS)-$(GOARCH)/ $(PROJECT)$(EXT) +ifeq ($(OS),Windows_NT) + cd dist; \ + rm -f $(ARCNAME)-*-Win.zip; \ + $(ZIP) $(ARCNAME)-$(VERSTR)-Win.zip build/$(GOOS)-$(GOARCH)/ $(PROJECT)$(EXT); + $(ZIP) dist/$(ARCNAME)-$(VERSTR)-Win.zip -r0 images/*.png; +endif + +release-all: + # -@$(MAKE) release GOOS=darwin GOARCH=amd64 + # -@$(MAKE) release GOOS=linux GOARCH=386 + # -@$(MAKE) release GOOS=linux GOARCH=amd64 + -@$(MAKE) release GOOS=windows GOARCH=386 EXT=.exe + # -@$(MAKE) release GOOS=windows GOARCH=amd64 EXT=.exe + +.PHONY: windows +windows: windows-dependencies embed-assets + goversioninfo -icon=rc/icon.ico -manifest=rc/manifest.exe.manifest rc/versioninfo.json + @- rm binaries/*.exe + CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -x -ldflags="-s -w " -o binaries/deploy`date +%m%d`.exe + @# - rm binaries/deploy_windows_64bit.exe + @# - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w -H=windowsgui -linkmode internal" -o binaries/deploy_windows_64bit.exe + rm resource.syso + +platform: + # @$(MAKE) releaseGOOS=js GOARCH=wasm + # @$(MAKE) release GOOS=windows GOARCH=386 FLAGS='-ldflags="-H=windowsgui"' EXE=.exe + # @$(MAKE) release GOOS=windows GOARCH=amd64 FLAGS='-ldflags="-H=windowsgui"' EXE=.exe + # @$(MAKE) release GOOS=linux GOARCH=arm + # @$(MAKE) release GOOS=linux GOARCH=arm64 + # @$(MAKE) release GOOS=linux GOARCH=mips + # @$(MAKE) release GOOS=linux GOARCH=mips64 + # @$(MAKE) release GOOS=linux GOARCH=mips64le + # @$(MAKE) release GOOS=linux GOARCH=mipsle + # @$(MAKE) release GOOS=linux GOARCH=ppc64 + # @$(MAKE) release GOOS=linux GOARCH=ppc64le + # @$(MAKE) release GOOS=linux GOARCH=s390x + # @$(MAKE) release GOOS=android GOARCH=386 + # @$(MAKE) release GOOS=android GOARCH=amd64 + # @$(MAKE) release GOOS=android GOARCH=arm + # @$(MAKE) release GOOS=android GOARCH=arm64 + # @$(MAKE) release GOOS=darwin GOARCH=386 + # @$(MAKE) release GOOS=darwin GOARCH=arm + # @$(MAKE) release GOOS=darwin GOARCH=arm64 + # @$(MAKE) release GOOS=dragonfly GOARCH=amd64 + # @$(MAKE) release GOOS=freebsd GOARCH=386 + # @$(MAKE) release GOOS=freebsd GOARCH=amd64 + # @$(MAKE) release GOOS=freebsd GOARCH=arm + # @$(MAKE) release GOOS=nacl GOARCH=386 + # @$(MAKE) release GOOS=nacl GOARCH=amd64p32 + # @$(MAKE) release GOOS=nacl GOARCH=arm + # @$(MAKE) release GOOS=netbsd GOARCH=386 + # @$(MAKE) release GOOS=netbsd GOARCH=amd64 + # @$(MAKE) release GOOS=netbsd GOARCH=arm + # @$(MAKE) release GOOS=openbsd GOARCH=386 + # @$(MAKE) release GOOS=openbsd GOARCH=amd64 + # @$(MAKE) release GOOS=openbsd GOARCH=arm + # @$(MAKE) release GOOS=plan9 GOARCH=386 + # @$(MAKE) release GOOS=plan9 GOARCH=amd64 + # @$(MAKE) release GOOS=plan9 GOARCH=arm + # @$(MAKE) release GOOS=solaris GOARCH=amd64 + + +.PHONY: build +build: + go get ./... + +.PHONY: test +test: + go get -t ./... + go test -v ... + +build-image: + docker build -t rientd/transfer . + +clean: + rm -rf build dist diff --git a/example/get/main.go b/example/download/main.go similarity index 100% rename from example/get/main.go rename to example/download/main.go diff --git a/example/getdata/Makefile b/example/getdata/Makefile new file mode 100644 index 0000000..396bac3 --- /dev/null +++ b/example/getdata/Makefile @@ -0,0 +1,52 @@ +COMPILE_TIME = $(shell date +"%Y-%M-%d %H:%M:%S") +BUILD=`date +%FT%T%z` + +# Setup the -ldflags option for go build here, interpolate the variable values +LDFLAGS_f1=-ldflags "-w -s -X main.Version=${VERSION} -X main.Build=${BUILD} -X main.Entry=f1" +LDFLAGS_f2=-ldflags "-w -s -X main.Version=${VERSION} -X main.Build=${BUILD} -X main.Entry=f2" + + +name:=getdata +# make ver=release +ifeq ($(ver), debug) + CXXFLAGS = -c -g -Ddebug +else + CXXFLAGS = -c -O3 +endif + +.PHONY: all +all: windows + +.PHONY: windows-dependencies +windows-dependencies: + go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo + +.PHONY: embed-assets +embed-assets: + @# go get github.com/jteeuwen/go-bindata/... + @# go-bindata ./logos/$(name)_headert.png ./webpage.html + +.PHONY: linux +linux: *.go embed-assets + GOOS=linux GOARCH=amd64 go build -o output/$(name)_linux_64bit + GOOS=linux GOARCH=386 go build -o output/$(name)_linux_32bit + strip output/$(name)_linux_* + +.PHONY: windows +windows: *.go windows-dependencies embed-assets + goversioninfo -icon=rc/icon.ico -manifest=rc/manifest.exe.manifest rc/versioninfo.json + @- rm output/*.exe + CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -ldflags="-s -w " -o output/$(name)_`date +%m-%d`.exe + @# - rm output/$(name)_windows_64bit.exe + @# - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w -H=windowsgui -linkmode internal" -o output/deploy_windows_64bit.exe + rm resource.syso + +.PHONY: osx +osx: *.go embed-assets + GOOS=darwin GOARCH=amd64 go build -o output/$(name)_osx_64bit + GOOS=darwin GOARCH=386 go build -o output/$(name)_osx_32bit + + +.PHONY: clean +clean: + rm -rf output/* \ No newline at end of file diff --git a/example/getdata/main.go b/example/getdata/main.go new file mode 100644 index 0000000..917f937 --- /dev/null +++ b/example/getdata/main.go @@ -0,0 +1,105 @@ +package getdata + +import ( + "fmt" + "log" + "os" + "path" + "time" + + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" +) + +func connect(user, password, host string, port int) (*sftp.Client, error) { + var ( + auth []ssh.AuthMethod + addr string + clientConfig *ssh.ClientConfig + sshClient *ssh.Client + sftpClient *sftp.Client + err error + ) + // get auth method + auth = make([]ssh.AuthMethod, 0) + auth = append(auth, ssh.Password(password)) + + clientConfig = &ssh.ClientConfig{ + User: user, + Auth: auth, + Timeout: 30 * time.Second, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), + } + + // connet to ssh + addr = fmt.Sprintf("%s:%d", host, port) + + if sshClient, err = ssh.Dial("tcp", addr, clientConfig); err != nil { + return nil, err + } + + // create sftp client + if sftpClient, err = sftp.NewClient(sshClient); err != nil { + return nil, err + } + + return sftpClient, nil +} + +var FORMAT = "2006-01-02" +var ( + err error + sftpClient *sftp.Client +) +var dbnames = []string{"tower", "mengyin", "pingyi", "shizhi", "tancheng", "yinan", "yishui", "feixian", "gaoxinqu", "hedong", "jingkaiqu", "junan", "luozhuang", "lanling", "lanshan", "lingang", "linshu"} + +func main() { + + // 这里换成实际的 SSH 连接的 用户名,密码,主机名或IP,SSH端口 + // sftpClient, err = connect("root", "sdlylshl871016", "111.235.181.127", 443) + sftpClient, err = connect("root", "HR2018!!", "15.14.12.150", 22) + if err != nil { + log.Println(err) + } + defer sftpClient.Close() + // 用来测试的远程文件路径 和 本地文件夹 + // fmt.Println(shizhi) + // var localDir = "." + date_dir := "db_" + time.Now().Format(FORMAT) + os.Mkdir(date_dir, 0755) + var lzkpbi = "/docker/backup/" + time.Now().Format(FORMAT) + "_lzkp_bi_inner.zip" + Down(lzkpbi, date_dir) + for _, n := range dbnames { + p := "/docker/backup/" + time.Now().Format(FORMAT) + "_" + n + "_inner.zip" + // fmt.Println(p) + + Down(p, date_dir) + } + // fmt.Scanln() +} + +func Down(src, dst string) { + fmt.Println(src, "数据正在复制中,请耐心等待...") + srcFile, err := sftpClient.Open(src) + if err != nil { + log.Println(err) + return + } + defer srcFile.Close() + + var localFileName = path.Base(src) + dstFile, err := os.Create(path.Join(dst, localFileName)) + if err != nil { + log.Println(err) + return + } + defer dstFile.Close() + + if _, err = srcFile.WriteTo(dstFile); err != nil { + log.Println(err) + return + } + + fmt.Println(src, "数据复制完成!") + +} diff --git a/example/getdata/rc/icon.ico b/example/getdata/rc/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..94e966358fd00ebc2674a148daccfdffe0b1eb47 GIT binary patch literal 370070 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 + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/getdata/rc/versioninfo.json b/example/getdata/rc/versioninfo.json new file mode 100644 index 0000000..77423aa --- /dev/null +++ b/example/getdata/rc/versioninfo.json @@ -0,0 +1,43 @@ +{ + "_FixedFileInfo": { + "FileVersion": { + "Major": 18, + "Minor": 7, + "Patch": 16, + "Build": 0 + }, + "ProductVersion": { + "Major": 2018, + "Minor": 7, + "Patch": 16, + "Build": 0 + }, + "FileFlagsMask": "3f", + "FileFlags ": "00", + "FileOS": "040004", + "FileType": "01", + "FileSubType": "00" + }, + "StringFileInfo": { + "FileDescription": "履职考评系统升级", + "FileVersion": "2.1.0", + "ProductName": "履职考评系统", + "ProductVersion": "2018.7.1", + "LegalCopyright": "Copyright@pytool", + "OriginalFilename": "update.exe", + "Comments": " ", + "CompanyName": "Company", + "InternalName": "", + "LegalTrademarks": "", + "PrivateBuild": "", + "SpecialBuild": "" + }, + "VarFileInfo": { + "Translation": { + "LangID": "0409", + "CharsetID": "04B0" + } + }, + "IconPath": "deploy.ico", + "ManifestPath": "" +} \ No newline at end of file diff --git a/example/runetl/Makefile b/example/runetl/Makefile new file mode 100644 index 0000000..bc56960 --- /dev/null +++ b/example/runetl/Makefile @@ -0,0 +1,126 @@ +PROJECT = 数据同步 +VERSION = $(shell git describe) +BRANCH = $(shell git name-rev --name-only HEAD) +VERSION ="$(echo $TRAVIS_TAG)" +ARCNAME = $(PROJECT)-$(VERSION)-$(GOOS)-$(GOARCH) +ARTIFACTS_DIR=artifact + + +# ZIP="zip -9 -r" +# TAR="tar cvf" +# GZIP="gzip -9" +# GZIP_7Z="7za a -tgzip -mx9" +# ZIP_7Z="7za a -tzip -mx9" +ifndef ZIP + ifneq ($(shell which zip 2>/dev/null),) + ZIP := zip -9 + endif + ifneq ($(shell which 7z 2>/dev/null),) + ZIP := 7z a -tzip -mx=9 + endif + ifneq ($(shell which 7za 2>/dev/null),) + ZIP := 7za a -tzip -mx=9 + endif + ifndef ZIP + $(warning "No zip / 7z / 7za in ($(PATH))") + ZIP := : zip_not_found + endif +endif + +.PHONY: all release release-all +all: release-all + + +.PHONY: windows-dependencies +windows-dependencies: + @ go get github.com/josephspurrier/goversioninfo/cmd/goversioninfo + +.PHONY: embed-assets +embed-assets: + @ GO111MODULE=off go get -u github.com/jteeuwen/go-bindata/... + @# go-bindata ./logos/microBadger_headert.png ./webpage.html + + + +release: + goversioninfo -icon=rc/icon.ico -manifest=rc/manifest.exe.manifest rc/versioninfo.json + env CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) go build $(FLAGS) -ldflags='-s -w -X github.com/rinetd/transfer/version.Version=$(VERSION)' -o build/$(GOOS)-$(GOARCH)/$(PROJECT)$(EXT) + rm resource.syso + - tar czf build/$(ARCNAME).tar.gz -C build/$(GOOS)-$(GOARCH)/ $(PROJECT)$(EXT) + - tar czf build/$(ARCNAME).tar.gz -C build/$(GOOS)-$(GOARCH)/ $(PROJECT)$(EXT) +ifeq ($(OS),Windows_NT) + cd dist; \ + rm -f $(ARCNAME)-*-Win.zip; \ + $(ZIP) $(ARCNAME)-$(VERSTR)-Win.zip build/$(GOOS)-$(GOARCH)/ $(PROJECT)$(EXT); + $(ZIP) dist/$(ARCNAME)-$(VERSTR)-Win.zip -r0 images/*.png; +endif + +release-all: + # -@$(MAKE) release GOOS=darwin GOARCH=amd64 + # -@$(MAKE) release GOOS=linux GOARCH=386 + # -@$(MAKE) release GOOS=linux GOARCH=amd64 + -@$(MAKE) release GOOS=windows GOARCH=386 EXT=.exe + # -@$(MAKE) release GOOS=windows GOARCH=amd64 EXT=.exe + +.PHONY: windows +windows: windows-dependencies embed-assets + goversioninfo -icon=rc/icon.ico -manifest=rc/manifest.exe.manifest rc/versioninfo.json + @- rm binaries/*.exe + CGO_ENABLED=0 GOOS=windows GOARCH=386 go build -x -ldflags="-s -w " -o binaries/deploy`date +%m%d`.exe + @# - rm binaries/deploy_windows_64bit.exe + @# - CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="-s -w -H=windowsgui -linkmode internal" -o binaries/deploy_windows_64bit.exe + rm resource.syso + +platform: + # @$(MAKE) releaseGOOS=js GOARCH=wasm + # @$(MAKE) release GOOS=windows GOARCH=386 FLAGS='-ldflags="-H=windowsgui"' EXE=.exe + # @$(MAKE) release GOOS=windows GOARCH=amd64 FLAGS='-ldflags="-H=windowsgui"' EXE=.exe + # @$(MAKE) release GOOS=linux GOARCH=arm + # @$(MAKE) release GOOS=linux GOARCH=arm64 + # @$(MAKE) release GOOS=linux GOARCH=mips + # @$(MAKE) release GOOS=linux GOARCH=mips64 + # @$(MAKE) release GOOS=linux GOARCH=mips64le + # @$(MAKE) release GOOS=linux GOARCH=mipsle + # @$(MAKE) release GOOS=linux GOARCH=ppc64 + # @$(MAKE) release GOOS=linux GOARCH=ppc64le + # @$(MAKE) release GOOS=linux GOARCH=s390x + # @$(MAKE) release GOOS=android GOARCH=386 + # @$(MAKE) release GOOS=android GOARCH=amd64 + # @$(MAKE) release GOOS=android GOARCH=arm + # @$(MAKE) release GOOS=android GOARCH=arm64 + # @$(MAKE) release GOOS=darwin GOARCH=386 + # @$(MAKE) release GOOS=darwin GOARCH=arm + # @$(MAKE) release GOOS=darwin GOARCH=arm64 + # @$(MAKE) release GOOS=dragonfly GOARCH=amd64 + # @$(MAKE) release GOOS=freebsd GOARCH=386 + # @$(MAKE) release GOOS=freebsd GOARCH=amd64 + # @$(MAKE) release GOOS=freebsd GOARCH=arm + # @$(MAKE) release GOOS=nacl GOARCH=386 + # @$(MAKE) release GOOS=nacl GOARCH=amd64p32 + # @$(MAKE) release GOOS=nacl GOARCH=arm + # @$(MAKE) release GOOS=netbsd GOARCH=386 + # @$(MAKE) release GOOS=netbsd GOARCH=amd64 + # @$(MAKE) release GOOS=netbsd GOARCH=arm + # @$(MAKE) release GOOS=openbsd GOARCH=386 + # @$(MAKE) release GOOS=openbsd GOARCH=amd64 + # @$(MAKE) release GOOS=openbsd GOARCH=arm + # @$(MAKE) release GOOS=plan9 GOARCH=386 + # @$(MAKE) release GOOS=plan9 GOARCH=amd64 + # @$(MAKE) release GOOS=plan9 GOARCH=arm + # @$(MAKE) release GOOS=solaris GOARCH=amd64 + + +.PHONY: build +build: + go get ./... + +.PHONY: test +test: + go get -t ./... + go test -v ... + +build-image: + docker build -t rientd/transfer . + +clean: + rm -rf build dist diff --git a/example/runetl/main.go b/example/runetl/main.go new file mode 100644 index 0000000..37fb721 --- /dev/null +++ b/example/runetl/main.go @@ -0,0 +1,31 @@ +package main + +import ( + "fmt" + + "github.com/pytool/ssh" +) + +func main() { + config := ssh.Default.WithHost("192.168.5.157").WithPassword("HR2018!!") + // config.Host = "15.14.12.153" + client, err := ssh.New(config) + // client, err := ssh.NewClient("localhost", "22", "root", "ubuntu") + if err != nil { + // panic(err) + fmt.Println("连接失败,按Enter键退出!") + fmt.Scanln() + } + defer client.Close() + + err = client.Exec("sh /root/shetl/etl.sh") + if err != nil { + fmt.Println(err) + // panic(err) + fmt.Println("执行失败,按Enter键退出!") + fmt.Scanln() + } + + // fmt.Printf("Uptime: %s\n", output) + +} diff --git a/example/runetl/rc/icon.ico b/example/runetl/rc/icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..3ba71ab6afdc8e8b8586c7c32d7265875f66c194 GIT binary patch literal 67646 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 hDmuRk{m)Y+p?15b676;+fl8fc + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/example/runetl/rc/versioninfo.json b/example/runetl/rc/versioninfo.json new file mode 100644 index 0000000..93b3b9a --- /dev/null +++ b/example/runetl/rc/versioninfo.json @@ -0,0 +1,37 @@ +{ + "FixedFileInfo": { + "FileVersion": { + "Major": 1, + "Minor": 2, + "Patch": 0, + "Build": 1 + }, + "FileFlagsMask": "3f", + "FileFlags ": "00", + "FileOS": "040004", + "FileType": "01", + "FileSubType": "00" + }, + "StringFileInfo": { + "Comments": "执行etl", + "CompanyName": "pytool", + "FileVersion": "", + "FileDescription": "执行数据同步etl", + "ProductName": "履职考评数据同步", + "ProductVersion": "2018.12.11", + "LegalCopyright": "Copyright@pytool", + "OriginalFilename": "etl.exe", + "InternalName": "", + "LegalTrademarks": "", + "PrivateBuild": "", + "SpecialBuild": "" + }, + "VarFileInfo": { + "Translation": { + "LangID": "0409", + "CharsetID": "04B0" + } + }, + "IconPath": "icon.ico", + "ManifestPath": "" +} \ No newline at end of file diff --git a/cmd/main.go b/example/update/main.go similarity index 100% rename from cmd/main.go rename to example/update/main.go diff --git a/example/put/main.go b/example/upload/main.go similarity index 100% rename from example/put/main.go rename to example/upload/main.go diff --git a/sftp.go b/sftp.go index 80bd23a..ebb477f 100644 --- a/sftp.go +++ b/sftp.go @@ -61,13 +61,13 @@ func (c *Client) downloadFile(remoteFile, local string) error { rsum := c.Md5File(remoteFile) ioutil.WriteFile(localFile+".md5", []byte(rsum), 755) if FileExist(localFile) { - // 1. 检测远程是否存在 if rsum != "" { lsum, _ := Md5File(localFile) if lsum == rsum { - log.Println("sftp: 文件与本地一致,跳过上传!", localFile) + log.Println("sftp: 文件与本地一致,跳过下载!", localFile) return nil } + log.Println("sftp: 正在下载 ", localFile) } } } @@ -196,6 +196,7 @@ func (c *Client) UploadFile(localFile, remote string) error { log.Println("sftp: 文件与本地一致,跳过上传!", localFile) return nil } + log.Println("sftp: 正在上传 ", localFile) } } @@ -437,7 +438,7 @@ func (c *Client) Md5File(path string) string { if c.IsNotExist(path) { return "" } - b, err := c.Run("md5sum " + path) + b, err := c.Output("md5sum " + path) if err != nil { return "" } diff --git a/ssh.go b/ssh.go index 78d5644..f6770a2 100644 --- a/ssh.go +++ b/ssh.go @@ -1,30 +1,84 @@ package ssh import ( + "bufio" + "bytes" + "errors" "fmt" + "io" "path/filepath" ) -// Run Execute cmd on the remote host and return stderr and stdout -func (c *Client) Run(cmd string) ([]byte, error) { +// Run Execute cmd on the remote host for daemon service +func (c *Client) Run(cmd string) { session, err := c.SSHClient.NewSession() if err != nil { - return nil, err + return } defer session.Close() - return session.CombinedOutput(cmd) + session.Start(cmd) + if err != nil { + fmt.Printf("exec command:%v error:%v\n", cmd, err) + } + fmt.Printf("Waiting for command:%v to finish...\n", cmd) + //阻塞等待fork出的子进程执行的结果,和cmd.Start()配合使用[不等待回收资源,会导致fork出执行shell命令的子进程变为僵尸进程] + err = session.Wait() + if err != nil { + fmt.Printf(":Command finished with error: %v\n", err) + } + return } -//Exec Execute cmd on the remote host and return stderr and stdout -func (c *Client) Exec(cmd string) ([]byte, error) { +//Exec Execute cmd on the remote host and bind stderr and stdout +func (c *Client) Exec(cmd string) error { + session, err := c.SSHClient.NewSession() + if err != nil { + return err + } + defer session.Close() + // session.Run(cmd) + // return session.CombinedOutput(cmd) + stdout, err := session.StdoutPipe() + // stderr, err = session.StderrPipe() + if err != nil { + fmt.Println(err) + return err + } + + var b bytes.Buffer + session.Stderr = &b + session.Start(cmd) + //创建一个流来读取管道内内容,这里逻辑是通过一行一行的读取的 + reader := bufio.NewReader(stdout) + + //实时循环读取输出流中的一行内容 + for { + line, err2 := reader.ReadString('\n') + if err2 != nil || io.EOF == err2 { + break + } + print(line) + } + + //阻塞直到该命令执行完成,该命令必须是被Start方法开始执行的 + session.Wait() + if b.Len() > 0 { + return errors.New(b.String()) + } + return nil +} + +// Output Execute cmd on the remote host and return stderr and stdout +func (c *Client) Output(cmd string) ([]byte, error) { session, err := c.SSHClient.NewSession() if err != nil { return nil, err } defer session.Close() - - return session.CombinedOutput(cmd) + // session.Run(cmd) + // return session.CombinedOutput(cmd) + return session.Output(cmd) } // RunScript Executes a shell script file on the remote machine.