libf 1 năm trước cách đây
commit
4c3e73d977
18 tập tin đã thay đổi với 3060 bổ sung0 xóa
  1. 546 0
      _/main.go
  2. 612 0
      _/mainx.go
  3. BIN
      _/osh
  4. BIN
      bin/msh
  5. BIN
      bin/msh_mac
  6. BIN
      bin/mssh
  7. BIN
      bin/mssh.exe
  8. BIN
      bin/mssh_mac
  9. 22 0
      build.sh
  10. 21 0
      go.mod
  11. 461 0
      go.sum
  12. 504 0
      msh/main.go
  13. BIN
      msh/msh
  14. 457 0
      mssh/client.go
  15. 175 0
      mssh/config.go
  16. 209 0
      mssh/main.go
  17. 53 0
      xsh/main.go
  18. BIN
      xsh/xsh

+ 546 - 0
_/main.go

@@ -0,0 +1,546 @@
+package main
+
+import (
+	"bufio"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"os/user"
+	"regexp"
+	"strings"
+	"time"
+
+	"git.wecise.com/wecise/common/matrix/logger"
+	"git.wecise.com/wecise/common/matrix/util"
+	"golang.org/x/term"
+	"gopkg.in/yaml.v2"
+)
+
+func init() {
+	logger.SetConsole(true)
+	logger.SetLevel(logger.TRACE)
+	logger.SetFormat("yyyy-MM-dd HH:mm:ss.SSS [level] msg", "\n")
+}
+
+type KV struct{ Key, Val string }
+
+func parseArgs(args []string) (kvs []*KV) {
+	argk := ""
+	argv := ""
+	for _, arg := range args {
+		if argk != "" {
+			argv = arg
+		} else if regexp.MustCompile(`^\-\w+$`).MatchString(arg) {
+			argk = arg[1:]
+			continue
+		} else {
+			kv := strings.SplitN(arg, "=", 2)
+			if len(kv) == 2 {
+				argk = kv[0]
+				argv = kv[1]
+			} else {
+				argk = ""
+				argv = arg
+			}
+		}
+		kvs = append(kvs, &KV{argk, argv})
+		argk, argv = "", ""
+	}
+	if argk != "" {
+		kvs = append(kvs, &KV{argk, argv})
+	}
+	return
+}
+
+func usage() {
+	fmt.Println("usage:")
+	fmt.Println("  msh p=password|a=passcode [[c=]command [p=password|a=passcode] [x=cmd-end-regexp] [[r=regexp] [o=output|n=outputline]]...]...")
+	fmt.Println("  a=passcode should be base64 encoded, or use p=password")
+	fmt.Println("  debug info include: p(progress) a(argments) m(match) 1(all)")
+}
+
+func main() {
+	// get user
+	u := func() string {
+		user, err := user.Current()
+		if err != nil {
+			logger.Error(util.ErrorWithSourceLine(err))
+			return ""
+		}
+		return user.Username
+	}()
+	// get passcode
+	c := ""
+	kvs := parseArgs(os.Args)
+	for _, kv := range kvs {
+		if kv.Key == "a" {
+			c = kv.Val
+			if c == "" {
+				c = "="
+			}
+			break
+		}
+		if kv.Key == "p" {
+			c = "=" + kv.Val
+			break
+		}
+	}
+	// get password
+	p := c
+	if p == "" {
+		usage()
+		return
+	} else if p[0:1] == "=" {
+		p = p[1:]
+	} else {
+		x, e := base64.RawStdEncoding.DecodeString(p)
+		if e == nil {
+			p = string(x)
+		}
+		// else 不是Base64编码,保持原值
+	}
+	// explainArgs key, val
+	cmds := []*Command{}
+	i := 0
+	for _, kv := range kvs {
+		key, val := kv.Key, kv.Val
+		switch key {
+		case "", "cmd", "command", "c":
+			cmds = append(cmds, &Command{Cmd: val, Password: c, Regexps: []*Matcher{{Regexp: nil, Output: ""}}, Endregx: regxprompt})
+		case "ry", "regex_yes_no":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxyesno = &Regexp{re}
+			}
+		case "rc", "regex_passcode", "regex_password":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxpassword = &Regexp{re}
+			}
+		case "rp", "regex_prompt":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxprompt = &Regexp{re}
+			}
+		case "password", "code", "pass", "p":
+			cmds[len(cmds)-1].Password = "=" + val
+		case "passcode", "b64code", "a":
+			cmds[len(cmds)-1].Password = val
+		case "re", "r", "regex":
+			if val == "" {
+				cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: nil})
+			} else {
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					return
+				} else {
+					cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: &Regexp{re}})
+				}
+			}
+		case "x", "end":
+			if val == "" {
+				cmds[len(cmds)-1].Endregx = regxprompt
+			} else {
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					return
+				} else {
+					cmds[len(cmds)-1].Endregx = &Regexp{re}
+				}
+			}
+		case "out", "o", "output":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val
+		case "outln", "n", "outputline":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val + "\n"
+		case "debug", "d":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Debug += val
+		}
+	}
+	if strings.Index(cmds[0].Regexps[0].Debug, "a") >= 0 || strings.Index(cmds[0].Regexps[0].Debug, "1") >= 0 {
+		//bs, _ := json.MarshalIndent(cmds, "", "  ")
+		bs, _ := yaml.Marshal(cmds)
+		logger.Debug("arguments:\n" + string(bs))
+	}
+	client := &Client{
+		User:     u,
+		Password: p,
+		Commands: cmds,
+	}
+	client.Run()
+}
+
+type Regexp struct {
+	*regexp.Regexp
+}
+
+func (r *Regexp) MarshalJSON() ([]byte, error) {
+	if r == nil || r.Regexp == nil {
+		return json.Marshal(nil)
+	}
+	return json.Marshal(r.String())
+}
+
+func (r *Regexp) MarshalYAML() (interface{}, error) {
+	if r == nil || r.Regexp == nil {
+		return nil, nil
+	}
+	return r.String(), nil
+}
+
+type Matcher struct {
+	Regexp *Regexp
+	Output string `yaml:"output"`
+	Debug  string `yaml:"debug"`
+}
+
+type Command struct {
+	Cmd      string `yaml:"cmd"`
+	Password string `yaml:"password"`
+	Regexps  []*Matcher
+	Endregx  *Regexp
+}
+
+var regxyesno = &Regexp{regexp.MustCompile(`.*(\(yes\/no\)\?)\s*$`)}
+var regxpassword = &Regexp{regexp.MustCompile(`.*([Pp]assword:)\s*$`)}
+var regxprompt = &Regexp{regexp.MustCompile(`.*([%#\$\>])\s*$`)}
+
+type Client struct {
+	User     string
+	Password string
+	Commands []*Command
+}
+
+func Pipe(reader io.Reader, writer io.Writer, pfs ...func(lastbs []byte, sin string) (sout string)) {
+	lastbs := []byte{'\n'}
+	for {
+		bs := make([]byte, 1024)
+		n, err := reader.Read(bs)
+		if err != nil {
+			if err == io.EOF {
+				return
+			}
+			logger.Error(util.ErrorWithSourceLine(err))
+			return
+		}
+		if n > 0 {
+			xbs := bs[:n]
+			for _, pf := range pfs {
+				if pf != nil {
+					s := pf(lastbs, string(xbs))
+					xbs = []byte(s)
+				}
+			}
+			if writer != nil {
+				writer.Write(xbs)
+			}
+			lastbs = bs[:n]
+		}
+	}
+}
+
+type TerminalIO struct {
+	termin  io.Reader
+	termout io.Writer
+}
+
+func (tio *TerminalIO) Read(bs []byte) (n int, e error) {
+	return tio.termin.Read(bs)
+}
+
+func (tio *TerminalIO) Write(bs []byte) (n int, e error) {
+	return tio.termout.Write(bs)
+}
+
+func (c *Client) Run() {
+	logger.Info("msh ready")
+
+	fdstdin := int(os.Stdin.Fd())
+
+	var err error
+	oldTermState, err := term.MakeRaw(fdstdin)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	// cmdinreader, cmdinwriter := io.Pipe()
+
+	terminreader, terminwriter := io.Pipe()
+	go Pipe(os.Stdin, terminwriter)
+
+	termoutreader, termoutwriter := io.Pipe()
+	go Pipe(termoutreader, os.Stdout)
+
+	tio := &TerminalIO{termin: terminreader, termout: termoutwriter}
+	tm := term.NewTerminal(tio, util.Hostname()+":> ")
+	rawState, err := term.GetState(fdstdin)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+	exit := -1
+	// term.Restore(fdstdin, oldTermState)
+	// eoc, eos := c.RunShell(cmdinreader, termoutwriter, termoutwriter)
+	// select {
+	// case <-eoc:
+	// case exit = <-eos:
+	// }
+	// term.Restore(fdstdin, rawState)
+
+	for exit < 0 {
+		cmdline, err := tm.ReadLine()
+		if err != nil {
+			break
+		}
+		cmdline = strings.TrimSpace(cmdline)
+		if cmdline == "" {
+			continue
+		}
+		term.Restore(fdstdin, oldTermState)
+		if cmdline == "exit" {
+			break
+		}
+		// cmdinwriter.Write([]byte(cmdline + "\n"))
+		// select {
+		// case <-eoc:
+		// case exit = <-eos:
+		// }
+		cmd := exec.Command("sh", "-c", cmdline)
+		cmd.Stdin = os.Stdin
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		cmd.Run()
+		term.Restore(fdstdin, rawState)
+	}
+
+	term.Restore(fdstdin, oldTermState)
+
+	logger.Info("msh exit")
+}
+
+func (c *Client) RunShell(stdin io.Reader, cmdout, cmderr io.Writer) (eoc <-chan int, exit <-chan int) {
+	endofcmd := make(chan int)
+	endofshell := make(chan int)
+	prompt := ":> "
+	cmd := exec.Command("sh", "-c", "export LANG=en_US.utf8; export LC_ALL=en_US.utf8; export PS1='\\h"+prompt+"'; sh -i")
+	// cmd := exec.Command("sh")
+	// cmd.Stdin = stdin
+	cmdinwriter, err := cmd.StdinPipe()
+	if err != nil {
+		logger.Error(err)
+	}
+	go Pipe(stdin, cmdinwriter)
+	cmdoutreader, err := cmd.StdoutPipe()
+	if err != nil {
+		logger.Error(err)
+	}
+	go Pipe(cmdoutreader, cmdout, func(lastbs []byte, sin string) (sout string) {
+		sout = strings.ReplaceAll(strings.ReplaceAll(sin, "\r", "\rS:"), "\n", "\nO:")
+		if strings.HasSuffix(sout, "\nO:") {
+			sout = sout[:len(sout)-3]
+		}
+		if lastbs[len(lastbs)-1] == '\n' {
+			sout = "\nO:" + sout
+		}
+		return
+	})
+	cmderrreader, err := cmd.StderrPipe()
+	go Pipe(cmderrreader, cmderr, func(lastbs []byte, sin string) (sout string) {
+		sout = strings.ReplaceAll(strings.ReplaceAll(sin, "\r", "\rQ:"), "\n", "\nE:")
+		if strings.HasSuffix(sout, "\nE:") {
+			sout = sout[:len(sout)-3]
+		}
+		if lastbs[len(lastbs)-1] == '\n' {
+			sout = "\nE:" + sout
+		}
+		if strings.HasSuffix(sout, prompt) {
+			endofcmd <- 0
+		}
+		return
+	})
+	go func() {
+		err := cmd.Run()
+		if err != nil {
+			logger.Error(err)
+		}
+		endofshell <- cmd.ProcessState.ExitCode()
+	}()
+	return endofcmd, endofshell
+}
+
+func (c *Client) IOProc(cmdout_pipe_reader, cmderr_pipe_reader io.Reader, inputEcho io.Writer, inputPipe io.Writer) {
+	cmdidx := 0
+	outputproc := func(stdoutbtr *BTReader, inputEcho io.Writer, inputPipe io.Writer) {
+		as := ""
+		change := false
+		for {
+			bs, err := stdoutbtr.ReadTimeout(10 * time.Millisecond)
+			if err != nil {
+				if err == io.EOF {
+					return
+				}
+				logger.Error(util.ErrorWithSourceLine(err))
+				return
+			}
+			if len(bs) > 0 {
+				change = true
+				s := string(bs)
+				inputEcho.Write([]byte(s))
+				as += s
+			} else if change {
+				change = false
+				if cmdidx < len(c.Commands) {
+					if msi := c.Commands[cmdidx].Endregx.FindStringSubmatchIndex(as); msi != nil {
+						match := as[msi[0]:msi[1]]
+						if strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+							logger.Trace("match end:", "'"+match+"'")
+						}
+						as = "" // 全清,开始新的命令
+						cmdidx++
+						if cmdidx >= len(c.Commands) {
+							continue
+						}
+						if strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "p") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+							logger.Trace("command:", c.Commands[cmdidx].Cmd)
+						}
+						inputEcho.Write([]byte(c.Commands[cmdidx].Cmd + "\n"))
+						inputPipe.Write([]byte(c.Commands[cmdidx].Cmd + "\n"))
+						continue
+					}
+					for _, regex := range c.Commands[cmdidx].Regexps {
+						if regex == nil {
+							continue
+						}
+						if regex.Regexp == nil {
+							// like match all
+							if regex.Output != "" {
+								inputEcho.Write([]byte(regex.Output))
+								inputPipe.Write([]byte(regex.Output))
+							}
+						} else if msi := regex.Regexp.FindStringSubmatchIndex(as); msi != nil {
+							match := as[msi[0]:msi[1]]
+							if len(msi) >= 4 {
+								match = as[msi[2]:msi[3]]
+								as = as[msi[3]:] // 清除已处理完的内容
+							} else {
+								as = as[msi[1]:] // 清除已处理完的内容
+							}
+							if strings.Index(regex.Debug, "m") >= 0 || strings.Index(regex.Debug, "1") >= 0 {
+								logger.Trace("match regex:", "'"+match+"'")
+							}
+							if regex.Output != "" {
+								inputEcho.Write([]byte(regex.Output))
+								inputPipe.Write([]byte(regex.Output))
+							}
+						}
+					}
+				}
+				if msi := regxyesno.FindStringSubmatchIndex(as); msi != nil {
+					match := as[msi[0]:msi[1]]
+					if len(msi) >= 4 {
+						as = as[msi[3]:] // 清除已处理完的内容
+					} else {
+						as = as[msi[1]:] // 清除已处理完的内容
+					}
+					if strings.Index(c.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+						cmdidx < len(c.Commands) &&
+							(strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+						logger.Trace("match yesno:", "'"+match+"'")
+					}
+					os.Stdout.Write([]byte("yes\n"))
+					inputPipe.Write([]byte("yes\n"))
+				}
+				if msi := regxpassword.FindStringSubmatchIndex(as); msi != nil {
+					match := as[msi[0]:msi[1]]
+					if len(msi) >= 4 {
+						as = as[msi[3]:] // 清除已处理完的内容
+					} else {
+						as = as[msi[1]:] // 清除已处理完的内容
+					}
+					p := c.Commands[0].Password
+					if cmdidx < len(c.Commands) {
+						p = c.Commands[cmdidx].Password
+					}
+					if strings.Index(c.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+						cmdidx < len(c.Commands) &&
+							(strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+						logger.Trace("match password:", "'"+match+"'")
+					}
+					if p != "" {
+						if p[0:1] == "=" {
+							p = p[1:]
+						} else {
+							x, e := base64.RawStdEncoding.DecodeString(p)
+							if e == nil {
+								p = string(x)
+							}
+							// else 不是Base64编码,保持原值
+						}
+						// don't echo password
+						if c.Commands[0].Regexps[0].Debug != "" || cmdidx < len(c.Commands) && c.Commands[cmdidx].Regexps[0].Debug != "" {
+							os.Stdout.Write([]byte(p + "\n"))
+						}
+						inputPipe.Write([]byte(p + "\n"))
+					}
+				}
+				if len(as) > 1024 {
+					as = as[len(as)-1024:]
+				}
+			}
+		}
+	}
+	go outputproc(&BTReader{Reader: bufio.NewReader(cmdout_pipe_reader)}, os.Stdout, inputPipe)
+	go outputproc(&BTReader{Reader: bufio.NewReader(cmderr_pipe_reader)}, os.Stderr, inputPipe)
+}
+
+type BTReader struct {
+	*bufio.Reader
+	bufop int32
+	chbs  chan []byte
+}
+
+func (me *BTReader) ReadTimeout(d time.Duration) (rbs []byte, err error) {
+	if me.chbs == nil {
+		me.chbs = make(chan []byte)
+		go func() {
+			n := 0
+			bs := make([]byte, me.Size())
+			for {
+				_, err = me.ReadByte()
+				if err != nil {
+					return
+				}
+				err = me.UnreadByte()
+				if err != nil {
+					return
+				}
+				n, err = me.Read(bs[0:me.Buffered()])
+				if err != nil {
+					return
+				}
+				me.chbs <- bs[0:n]
+			}
+		}()
+	}
+	t := time.NewTimer(d)
+	select {
+	case rbs = <-me.chbs:
+		return
+	case <-t.C:
+		return
+	}
+}

+ 612 - 0
_/mainx.go

@@ -0,0 +1,612 @@
+package main
+
+import (
+	"bufio"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"os/signal"
+	"os/user"
+	"regexp"
+	"strings"
+	"syscall"
+	"time"
+
+	"git.wecise.com/wecise/common/matrix/logger"
+	"git.wecise.com/wecise/common/matrix/util"
+	"github.com/creack/pty"
+	"golang.org/x/term"
+	"gopkg.in/yaml.v2"
+)
+
+func init() {
+	logger.SetConsole(true)
+	logger.SetLevel(logger.TRACE)
+	logger.SetFormat("yyyy-MM-dd HH:mm:ss.SSS [level] msg", "\n")
+}
+
+type KV struct{ Key, Val string }
+
+func parseArgs(args []string) (kvs []*KV) {
+	argk := ""
+	argv := ""
+	for _, arg := range args {
+		if argk != "" {
+			argv = arg
+		} else if regexp.MustCompile(`^\-\w+$`).MatchString(arg) {
+			argk = arg[1:]
+			continue
+		} else {
+			kv := strings.SplitN(arg, "=", 2)
+			if len(kv) == 2 {
+				argk = kv[0]
+				argv = kv[1]
+			} else {
+				argk = ""
+				argv = arg
+			}
+		}
+		kvs = append(kvs, &KV{argk, argv})
+		argk, argv = "", ""
+	}
+	if argk != "" {
+		kvs = append(kvs, &KV{argk, argv})
+	}
+	return
+}
+
+func usage() {
+	fmt.Println("usage:")
+	fmt.Println("  msh p=password|a=passcode [[c=]command [p=password|a=passcode] [x=cmd-end-regexp] [[r=regexp] [o=output|n=outputline]]...]...")
+	fmt.Println("  a=passcode should be base64 encoded, or use p=password")
+	fmt.Println("  debug info include: p(progress) a(argments) m(match) 1(all)")
+}
+
+func main() {
+	// get user
+	u := func() string {
+		user, err := user.Current()
+		if err != nil {
+			logger.Error(util.ErrorWithSourceLine(err))
+			return ""
+		}
+		return user.Username
+	}()
+	// get passcode,取默认密码设置
+	c := ""
+	kvs := parseArgs(os.Args)
+	for _, kv := range kvs {
+		if kv.Key == "a" {
+			c = kv.Val
+			if c == "" {
+				c = "="
+			}
+			break
+		}
+		if kv.Key == "p" {
+			c = "=" + kv.Val
+			break
+		}
+	}
+	// get password
+	p := c
+	if p == "" {
+		usage()
+		return
+	} else if p[0:1] == "=" {
+		p = p[1:]
+	} else {
+		x, e := base64.RawStdEncoding.DecodeString(p)
+		if e == nil {
+			p = string(x)
+		}
+		// else 不是Base64编码,保持原值
+	}
+	// explainArgs key, val
+	cmds := []*Command{{Cmd: "sh", Password: c, Regexps: []*Matcher{{Regexp: nil, Output: ""}}, Endregx: regxprompt}}
+	i := 0
+	// 掠过第一个参数,当前执行程序
+	kvs = kvs[1:]
+	for _, kv := range kvs {
+		key, val := kv.Key, kv.Val
+		switch key {
+		case "", "cmd", "command", "c":
+			cmds = append(cmds, &Command{Cmd: val, Password: c, Regexps: []*Matcher{{Regexp: nil, Output: ""}}, Endregx: regxprompt})
+		case "ry", "regex_yes_no":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxyesno = &Regexp{re}
+			}
+		case "rc", "regex_passcode", "regex_password":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxpassword = &Regexp{re}
+			}
+		case "rp", "regex_prompt":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxprompt = &Regexp{re}
+			}
+		case "password", "code", "pass", "p":
+			cmds[len(cmds)-1].Password = "=" + val
+		case "passcode", "b64code", "a":
+			cmds[len(cmds)-1].Password = val
+		case "re", "r", "regex":
+			if val == "" {
+				cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: nil})
+			} else {
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					return
+				} else {
+					cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: &Regexp{re}})
+				}
+			}
+		case "x", "end":
+			if val == "" {
+				cmds[len(cmds)-1].Endregx = regxprompt
+			} else {
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					return
+				} else {
+					cmds[len(cmds)-1].Endregx = &Regexp{re}
+				}
+			}
+		case "out", "o", "output":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val
+		case "outln", "n", "outputline":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val + "\n"
+		case "debug", "d":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Debug += val
+		}
+	}
+	if strings.Index(cmds[0].Regexps[0].Debug, "a") >= 0 || strings.Index(cmds[0].Regexps[0].Debug, "1") >= 0 {
+		//bs, _ := json.MarshalIndent(cmds, "", "  ")
+		bs, _ := yaml.Marshal(cmds)
+		logger.Debug("arguments:\n" + string(bs))
+	}
+	client := &Client{
+		User:     u,
+		Password: p,
+		Commands: cmds,
+	}
+	client.Run()
+}
+
+type Regexp struct {
+	*regexp.Regexp
+}
+
+func (r *Regexp) MarshalJSON() ([]byte, error) {
+	if r == nil || r.Regexp == nil {
+		return json.Marshal(nil)
+	}
+	return json.Marshal(r.String())
+}
+
+func (r *Regexp) MarshalYAML() (interface{}, error) {
+	if r == nil || r.Regexp == nil {
+		return nil, nil
+	}
+	return r.String(), nil
+}
+
+type Matcher struct {
+	Regexp *Regexp
+	Output string `yaml:"output"`
+	Debug  string `yaml:"debug"`
+}
+
+type Command struct {
+	Cmd      string `yaml:"cmd"`
+	Password string `yaml:"password"`
+	Regexps  []*Matcher
+	Endregx  *Regexp
+}
+
+var regxyesno = &Regexp{regexp.MustCompile(`.*(\(yes\/no\)\?)\s*$`)}
+var regxpassword = &Regexp{regexp.MustCompile(`.*([Pp]assword:)\s*$`)}
+var regxprompt = &Regexp{regexp.MustCompile(`.*([%#\$\>])\s*$`)}
+
+type Client struct {
+	User     string
+	Password string
+	Commands []*Command
+}
+
+func Pipe(reader io.Reader, writer io.Writer, pfs ...func(lastbs []byte, sin string) (sout string)) {
+	btr := NewBTReader("P:", reader, 30*time.Millisecond, 1024)
+	lastbs := []byte{'\n'}
+	for {
+		bs, err := btr.Read()
+		if err != nil {
+			if err == io.EOF {
+				return
+			}
+			logger.Error(util.ErrorWithSourceLine(err))
+			return
+		}
+		if len(bs) > 0 {
+			xbs := bs
+			for _, pf := range pfs {
+				if pf != nil {
+					s := pf(lastbs, string(xbs))
+					xbs = []byte(s)
+				}
+			}
+			if writer != nil {
+				writer.Write(xbs)
+			}
+			lastbs = bs
+		}
+	}
+}
+
+type TerminalIO struct {
+	termin  io.Reader
+	termout io.Writer
+}
+
+func (tio *TerminalIO) Read(bs []byte) (n int, e error) {
+	return tio.termin.Read(bs)
+}
+
+func (tio *TerminalIO) Write(bs []byte) (n int, e error) {
+	return tio.termout.Write(bs)
+}
+
+func (c *Client) Run() {
+	// Create arbitrary command.
+	cmd := exec.Command("sh")
+
+	// Start the command with a pty.
+	ptmx, err := pty.Start(cmd)
+	if err != nil {
+		logger.Error(err)
+		return
+	}
+	// Make sure to close the pty at the end.
+	defer func() { _ = ptmx.Close() }() // Best effort.
+
+	// Handle pty size.
+	ch := make(chan os.Signal, 1)
+	signal.Notify(ch, syscall.SIGWINCH)
+	go func() {
+		for range ch {
+			if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
+				logger.Error("error resizing pty:", err)
+			}
+		}
+	}()
+	ch <- syscall.SIGWINCH                        // Initial resize.
+	defer func() { signal.Stop(ch); close(ch) }() // Cleanup signals when done.
+
+	// Set stdin in raw mode.
+	oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
+	if err != nil {
+		logger.Error(err)
+	}
+	defer func() { _ = term.Restore(int(os.Stdin.Fd()), oldState) }() // Best effort.
+
+	go func() { _, _ = io.Copy(ptmx, os.Stdin) }()
+	//_, _ = io.Copy(os.Stdout, ptmx)
+	Pipe(ptmx, os.Stdout)
+
+	return
+}
+
+func (c *Client) RunX() {
+	logger.Info("tsh ready")
+	exit := -1
+	cmdsctrlreader, cmdsctrlwriter := io.Pipe()
+	terminreader, terminwriter := io.Pipe()
+	termoutreader, termoutwriter := io.Pipe()
+	termerrreader, termerrwriter := io.Pipe()
+	go Pipe(os.Stdin, terminwriter)
+	go Pipe(cmdsctrlreader, terminwriter, func(lastbs []byte, sin string) (sout string) {
+		sout = sin
+		termoutwriter.Write([]byte(sout))
+		return
+	})
+	// autocmds := []string{"pwd", "echo `ls`", "sh -i", "exit"}
+	// go Pipe(termoutreader, os.Stdout, func(lastbs []byte, sin string) (sout string) {
+	// 	sout = sin
+	// 	return
+	// })
+	// go Pipe(termerrreader, os.Stderr)
+	eoc, eos := c.RunShell(terminreader, termoutwriter, termerrwriter)
+	c.IOProc(termoutreader, termerrreader, os.Stdout, os.Stderr, cmdsctrlwriter)
+	for exit < 0 {
+		select {
+		case <-eoc:
+			// if len(autocmds) > 0 {
+			// 	cmdsctrlwriter.Write([]byte(autocmds[0] + "\n"))
+			// 	autocmds = autocmds[1:]
+			// }
+		case exit = <-eos:
+		}
+	}
+	logger.Info("tsh exit")
+}
+
+func (c *Client) RunShell(stdin io.Reader, cmdout, cmderr io.Writer) (eoc <-chan int, exit <-chan int) {
+	endofcmd := make(chan int)
+	endofshell := make(chan int)
+	prompt := util.Hostname() + ":> "
+	// cmd := exec.Command("bash", "-c", "export LANG=en_US.utf8; export LC_ALL=en_US.utf8; export PS1='\\h"+prompt+"'; bash")
+	cmd := exec.Command("bash")
+	// cmd.Stdin = stdin
+	cmdinwriter, err := cmd.StdinPipe()
+	if err != nil {
+		logger.Error(err)
+	}
+	newcmd := true
+	eocflag := "<end-of-command-in-zerone-t-shell>"
+	cmdeoc := "echo '" + eocflag + "'\n"
+	go Pipe(stdin, cmdinwriter, func(lastbs []byte, sin string) (sout string) {
+		sout = sin
+		if newcmd {
+			newcmd = false
+			sout = sout[:len(sout)-1] + ";" + cmdeoc
+		}
+		return
+	})
+	cmdoutreader, err := cmd.StdoutPipe()
+	if err != nil {
+		logger.Error(err)
+	}
+	go Pipe(cmdoutreader, cmdout, func(lastbs []byte, sin string) (sout string) {
+		sout = sin
+		// sout = strings.ReplaceAll(strings.ReplaceAll(sin, "\r", "\rS:"), "\n", "\nO:")
+		// if strings.HasSuffix(sout, "\nO:") {
+		// 	sout = sout[:len(sout)-3]
+		// }
+		// if lastbs[len(lastbs)-1] == '\n' {
+		// 	sout = "\nO:" + sout
+		// }
+		if strings.HasSuffix(sout, eocflag+"\n") {
+			sout = sout[:len(sout)-len(eocflag)-1]
+			sout += prompt
+			newcmd = true
+			endofcmd <- 0
+		}
+		return
+	})
+	cmderrreader, err := cmd.StderrPipe()
+	go Pipe(cmderrreader, cmderr, func(lastbs []byte, sin string) (sout string) {
+		sout = sin
+		// sout = strings.ReplaceAll(strings.ReplaceAll(sin, "\r", "\rQ:"), "\n", "\nE:")
+		// if strings.HasSuffix(sout, "\nE:") {
+		// 	sout = sout[:len(sout)-3]
+		// }
+		// if lastbs[len(lastbs)-1] == '\n' {
+		// 	sout = "\nE:" + sout
+		// }
+		// sout = strings.ReplaceAll(sou, "no job control in this shell\n", "ready\n")
+		return
+	})
+	go func() {
+		err := cmd.Run()
+		if err != nil {
+			logger.Error(err)
+		}
+		endofshell <- cmd.ProcessState.ExitCode()
+	}()
+	cmdinwriter.Write([]byte(cmdeoc))
+	return endofcmd, endofshell
+}
+
+func (c *Client) IOProc(cmdout_pipe_reader, cmderr_pipe_reader io.Reader, stdout io.Writer, stderr io.Writer, cmdinwriter io.Writer) {
+	cmdidx := 0
+	outputproc := func(flag string, cmdout io.Reader, stdout io.Writer, cmdinwriter io.Writer) {
+		as := ""
+		change := false
+		stdoutbtr := NewBTReader(flag, cmdout, 50*time.Millisecond, 1024)
+		for {
+			bs, err := stdoutbtr.Read()
+			if err != nil {
+				if err == io.EOF {
+					return
+				}
+				logger.Error(util.ErrorWithSourceLine(err))
+				return
+			}
+			if len(bs) > 0 {
+				change = true
+				s := string(bs)
+				stdout.Write([]byte(s))
+				as += s
+				logger.Warn(s)
+			} else if change {
+				change = false
+				if cmdidx < len(c.Commands) {
+					if msi := c.Commands[cmdidx].Endregx.FindStringSubmatchIndex(as); msi != nil {
+						logger.Error("EndregexMatch", as)
+						match := as[msi[0]:msi[1]]
+						if strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+							logger.Trace("match end:", "'"+match+"'")
+						}
+						as = "" // 全清,开始新的命令
+						cmdidx++
+						if cmdidx >= len(c.Commands) {
+							return
+						}
+						if strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "p") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+							logger.Trace("command:", c.Commands[cmdidx].Cmd)
+						}
+						// stdout.Write([]byte(c.Commands[cmdidx].Cmd + "\n"))
+						cmdinwriter.Write([]byte(c.Commands[cmdidx].Cmd + "\n"))
+						continue
+					}
+					for _, regex := range c.Commands[cmdidx].Regexps {
+						if regex == nil {
+							continue
+						}
+						if regex.Regexp == nil {
+							// like match all
+							if regex.Output != "" {
+								// stdout.Write([]byte(regex.Output))
+								cmdinwriter.Write([]byte(regex.Output))
+							}
+						} else if msi := regex.Regexp.FindStringSubmatchIndex(as); msi != nil {
+							match := as[msi[0]:msi[1]]
+							if len(msi) >= 4 {
+								match = as[msi[2]:msi[3]]
+								as = as[msi[3]:] // 清除已处理完的内容
+							} else {
+								as = as[msi[1]:] // 清除已处理完的内容
+							}
+							if strings.Index(regex.Debug, "m") >= 0 || strings.Index(regex.Debug, "1") >= 0 {
+								logger.Trace("match regex:", "'"+match+"'")
+							}
+							if regex.Output != "" {
+								// stdout.Write([]byte(regex.Output))
+								cmdinwriter.Write([]byte(regex.Output))
+							}
+						}
+					}
+				}
+				if msi := regxyesno.FindStringSubmatchIndex(as); msi != nil {
+					match := as[msi[0]:msi[1]]
+					if len(msi) >= 4 {
+						as = as[msi[3]:] // 清除已处理完的内容
+					} else {
+						as = as[msi[1]:] // 清除已处理完的内容
+					}
+					if strings.Index(c.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+						cmdidx < len(c.Commands) &&
+							(strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+						logger.Trace("match yesno:", "'"+match+"'")
+					}
+					// stdout.Write([]byte("yes\n"))
+					cmdinwriter.Write([]byte("yes\n"))
+				}
+				if msi := regxpassword.FindStringSubmatchIndex(as); msi != nil {
+					logger.Error(as)
+					match := as[msi[0]:msi[1]]
+					if len(msi) >= 4 {
+						as = as[msi[3]:] // 清除已处理完的内容
+					} else {
+						as = as[msi[1]:] // 清除已处理完的内容
+					}
+					p := c.Commands[0].Password
+					if cmdidx < len(c.Commands) {
+						p = c.Commands[cmdidx].Password
+					}
+					if strings.Index(c.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+						cmdidx < len(c.Commands) &&
+							(strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+						logger.Trace("match password:", "'"+match+"'")
+					}
+					if p != "" {
+						if p[0:1] == "=" {
+							p = p[1:]
+						} else {
+							x, e := base64.RawStdEncoding.DecodeString(p)
+							if e == nil {
+								p = string(x)
+							}
+							// else 不是Base64编码,保持原值
+						}
+						// don't echo password
+						if c.Commands[0].Regexps[0].Debug != "" || cmdidx < len(c.Commands) && c.Commands[cmdidx].Regexps[0].Debug != "" {
+							stdout.Write([]byte(p + "\n"))
+						}
+						cmdinwriter.Write([]byte(p + "\n"))
+					}
+				}
+				if len(as) > 1024 {
+					as = as[len(as)-1024:]
+				}
+			}
+		}
+	}
+	go outputproc("O:", cmdout_pipe_reader, stdout, cmdinwriter)
+	go outputproc("E:", cmderr_pipe_reader, stderr, cmdinwriter)
+}
+
+type BTReader struct {
+	*bufio.Reader
+	flag    string
+	chbs    chan []byte
+	cher    chan error
+	timeout time.Duration
+	sizeout int
+	err     error
+}
+
+func NewBTReader(flag string, reader io.Reader, timeout time.Duration, sizeout int) (me *BTReader) {
+	me = &BTReader{Reader: bufio.NewReader(reader),
+		flag:    flag,
+		chbs:    make(chan []byte),
+		cher:    make(chan error),
+		timeout: timeout,
+		sizeout: sizeout,
+	}
+	go func() {
+		bs := make([]byte, me.Size())
+		for {
+			n, err := me.Reader.Read(bs[:1])
+			if err != nil {
+				me.cher <- err
+				return
+			}
+			x, err := me.Reader.Read(bs[1 : me.Reader.Buffered()+1])
+			if err != nil {
+				me.cher <- err
+				return
+			}
+			n += x
+			abs := make([]byte, n)
+			copy(abs, bs[:n])
+			me.chbs <- abs
+		}
+	}()
+	return
+}
+
+// 指定时间内没有新数据进入,且有积累数据,或积累数据超过指定数量,即返回
+func (me *BTReader) Read() (rbs []byte, err error) {
+	if me.err != nil {
+		return nil, me.err
+	}
+	for {
+		t := time.NewTimer(me.timeout)
+		select {
+		case me.err = <-me.cher:
+			if len(rbs) > 0 {
+				// 返回最后的数据,下次读时才返回错误
+				return rbs, nil
+			}
+			return nil, me.err
+		case abs := <-me.chbs:
+			rbs = append(rbs, abs...)
+			if len(rbs) > me.sizeout {
+				return
+			}
+			t.Stop()
+			t.Reset(me.timeout)
+		case <-t.C:
+			if len(rbs) == 0 {
+				t.Stop()
+				t.Reset(me.timeout)
+			}
+			return
+		}
+	}
+}

BIN
_/osh


BIN
bin/msh


BIN
bin/msh_mac


BIN
bin/mssh


BIN
bin/mssh.exe


BIN
bin/mssh_mac


+ 22 - 0
build.sh

@@ -0,0 +1,22 @@
+
+export LANG=zh_CN.utf8
+export LC_ALL=zh_CN.utf8
+
+installpath=`go env GOPATH | awk -F ':' '{print $1}'`/bin
+
+#改变工作目录到当前脚本所在路径
+if [[ "$0" =~ / ]]; then cd "${0%/*}"; fi
+
+cd ./mssh
+CGO_ENABLED=0 GOOS=windows go build -o ../bin/mssh.exe
+CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ../bin/mssh
+CGO_ENABLED=0 go build -o ../bin/mssh_mac
+cd ..
+
+cd ./msh
+CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o ../bin/msh
+CGO_ENABLED=0 go build -o ../bin/msh_mac
+cd ..
+
+cp ./bin/* ${installpath}/matrix/zerone/
+echo copied to ${installpath}/matrix/zerone/

+ 21 - 0
go.mod

@@ -0,0 +1,21 @@
+module zerone
+
+go 1.19
+
+require (
+	git.wecise.com/wecise/common v0.0.0-20221129092339-3223d60ef015
+	github.com/atrox/homedir v1.0.0
+	github.com/creack/pty v1.1.18
+	github.com/kevinburke/ssh_config v1.2.0
+	golang.org/x/crypto v0.3.0
+	golang.org/x/term v0.2.0
+	gopkg.in/yaml.v2 v2.4.0
+)
+
+require (
+	github.com/fatih/color v1.9.0 // indirect
+	github.com/mattn/go-colorable v0.1.7 // indirect
+	github.com/mattn/go-isatty v0.0.12 // indirect
+	github.com/spf13/cast v1.3.1 // indirect
+	golang.org/x/sys v0.2.0 // indirect
+)

+ 461 - 0
go.sum

@@ -0,0 +1,461 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+code.sajari.com/docconv v1.2.1-0.20210820021724-c8f472b62ad0/go.mod h1:r8yfCP6OKbZ9Xkd87aBa4nfpk6ud/PoyLwex3n6cXSc=
+git.wecise.com/wecise/common v0.0.0-20221129092339-3223d60ef015 h1:z+bgqgO4TF6qgp8OdF1lwuROrKJVOUPjGReATOAxMWE=
+git.wecise.com/wecise/common v0.0.0-20221129092339-3223d60ef015/go.mod h1:KhkuF5pRD2nwL9WpIU57nhmiXwlQuyoFuwScqiMDr4g=
+git.wecise.com/wecise/odb-go v0.0.0-20220825070427-e443cba6de6d/go.mod h1:RUaugeb49Q7CKD1szlFvoVmw657wrVGnt0QMMZrhMTg=
+github.com/360EntSecGroup-Skylar/excelize/v2 v2.1.0/go.mod h1:NRW1nxuHjsv3AUisgUneVjItiDPsxKSBBypDwX46bf4=
+github.com/AlekSi/pointer v1.0.0/go.mod h1:1kjywbfcPFCmncIxtk6fIEub6LKrfMz3gc5QKVOSOA8=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/EndFirstCorp/peekingReader v0.0.0-20171012052444-257fb6f1a1a6/go.mod h1:zpqkXxDsVfEIUZEWvT9yAo8OmRvSlRrcYQ3Zs8sSubA=
+github.com/JalfResi/justext v0.0.0-20170829062021-c0282dea7198/go.mod h1:0SURuH1rsE8aVWvutuMZghRNrNrYEUzibzJfhEYR8L0=
+github.com/PuerkitoBio/goquery v1.4.1/go.mod h1:T9ezsOHcCrDCgA8aF1Cqr3sSYbO/xgdy8/R/XiIMAhA=
+github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
+github.com/advancedlogic/GoOse v0.0.0-20191112112754-e742535969c1/go.mod h1:f3HCSN1fBWjcpGtXyM119MJgeQl838v6so/PQOqvE1w=
+github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/andybalholm/cascadia v1.0.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
+github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
+github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
+github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
+github.com/araddon/dateparse v0.0.0-20180729174819-cfd92a431d0e/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
+github.com/araddon/dateparse v0.0.0-20200409225146-d820a6159ab1/go.mod h1:SLqhdZcd+dF3TEVL2RMoob5bBP5R1P1qkox+HtCBgGI=
+github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
+github.com/atrox/homedir v1.0.0 h1:99Vwk+XECZTDLaAPeMj7vF9JMNcVarWddqPeyDzJT5E=
+github.com/atrox/homedir v1.0.0/go.mod h1:ZKVEIDNKscX8qV1TyrwLP+ayjv3XQO7wbVmc5EW00A8=
+github.com/axgle/mahonia v0.0.0-20180208002826-3358181d7394/go.mod h1:Q8n74mJTIgjX4RBBcHnJ05h//6/k6foqmgE45jTQtxg=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bitly/go-hostpool v0.1.0/go.mod h1:4gOCgp6+NZnVqlKyZ/iBZFTAJKembaVENUpMkpg42fw=
+github.com/bluele/gcache v0.0.0-20190518031135-bc40bd653833/go.mod h1:8c4/i2VlovMO2gBnHGQPN5EJw+H0lx1u/5p+cgsXtCk=
+github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
+github.com/cdipaolo/goml v0.0.0-20210723214924-bf439dd662aa/go.mod h1:sduMkaHcXDIWurl/Bd/z0rNEUHw5tr6LUA9IO8E9o0o=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
+github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
+github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd/v22 v22.3.2 h1:D9/bQk5vlXQFZ6Kwuu6zaiXJ9oTPe68++AzAJc1DzSI=
+github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
+github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
+github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/ddliu/go-httpclient v0.6.6/go.mod h1:zM9P0OxV4OGGz1pt/ibuj0ooX2SWH9a6MvXZLbT0JMc=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
+github.com/elgs/gojq v0.0.0-20201120033525-b5293fef2759/go.mod h1:rQELVIqRXpraeUryHOBadz99ePvEVQmTVpGr8M9QQ4Q=
+github.com/elgs/gosplitargs v0.0.0-20161028071935-a491c5eeb3c8/go.mod h1:o4DgpccPNAQAlPSxo7I4L/LWNh2oyr/BBGSynrLTmZM=
+github.com/elgs/jsonql v0.0.0-20200329014701-4e420b8aa13a/go.mod h1:8DZMFjT1ud2nlIkei0loC0VwZFOzJqF9xwxPWtHnEok=
+github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o=
+github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
+github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s=
+github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
+github.com/fatih/set v0.2.1/go.mod h1:+RKtMCH+favT2+3YecHGxcc0b4KyVWA1QWWJUs4E0CI=
+github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/gabriel-vasile/mimetype v1.1.1/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/gigawattio/window v0.0.0-20180317192513-0f5467e35573/go.mod h1:eBvb3i++NHDH4Ugo9qCvMw8t0mTSctaEa5blJbWcNxs=
+github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0=
+github.com/go-ego/cedar v0.10.2/go.mod h1:OlEbpcRpzwp69CoCXPJTmrOzELoGAmFDgW3hdWrHHc0=
+github.com/go-ego/gse v0.67.0/go.mod h1:AbOXXmI3rgUG/Rb3e9xtk6xDjSLj6KpBHoctONT58ns=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
+github.com/go-redis/redis/v8 v8.11.2/go.mod h1:DLomh7y2e3ggQXQLd1YgmvIfecPJoFl7WU5SOQ/r06M=
+github.com/go-resty/resty/v2 v2.0.0/go.mod h1:dZGr0i9PLlaaTD4H/hoZIDjQ+r6xq8mgbRzHZf7f2J8=
+github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
+github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
+github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
+github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gomodule/redigo v1.8.2/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
+github.com/gomodule/redigo v1.8.5/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
+github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
+github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
+github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
+github.com/jaytaylor/html2text v0.0.0-20200412013138-3577fbdbcff7/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk=
+github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
+github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
+github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
+github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
+github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
+github.com/kevinburke/ssh_config v0.0.0-20190725054713-01f96b0aa0cd/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
+github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
+github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/klauspost/compress v1.11.12/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
+github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
+github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/levigross/exp-html v0.0.0-20120902181939-8df60c69a8f5/go.mod h1:QMe2wuKJ0o7zIVE8AqiT8rd8epmm6WDIZ2wyuBqYPzM=
+github.com/lu4p/cat v0.1.5/go.mod h1:G3YRyjSvBipqMBRZ2uLf1oRL3/eGGmuZf96m95Y4jRQ=
+github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
+github.com/mattn/go-colorable v0.1.7 h1:bQGKb3vps/j0E9GfJQ03JyhRuxsvdAanXlT9BTw3mdw=
+github.com/mattn/go-colorable v0.1.7/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
+github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
+github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
+github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
+github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
+github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
+github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q=
+github.com/nats-io/jwt/v2 v2.0.2/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY=
+github.com/nats-io/jwt/v2 v2.1.0/go.mod h1:0tqz9Hlu6bCBFLWAASKhE5vUA4c24L9KPUUgvwumE/k=
+github.com/nats-io/nats-server/v2 v2.3.0/go.mod h1:7v4HvHI2Zu4n1775982gHbvBNXywHeaTj1WGo0S+uFI=
+github.com/nats-io/nats.go v1.11.0/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w=
+github.com/nats-io/nats.go v1.20.0/go.mod h1:tLqubohF7t4z3du1QDPYJIQQyhb4wl6DhjxEajSI7UA=
+github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s=
+github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4=
+github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
+github.com/nleeper/goment v1.4.2/go.mod h1:zDl5bAyDhqxwQKAvkSXMRLOdCowrdZz53ofRJc4VhTo=
+github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
+github.com/olekukonko/tablewriter v0.0.0-20180506121414-d4647c9c7a84/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
+github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
+github.com/onsi/ginkgo v1.15.0/go.mod h1:hF8qUzuuC8DJGygJH3726JnCZX4MYbRB8yFfISqnKUg=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
+github.com/onsi/gomega v1.10.5/go.mod h1:gza4q3jKQJijlu05nKWRCW/GavJumGt8aNRxWg7mt48=
+github.com/orfjackal/nanospec.go v0.0.0-20120727230329-de4694c1d701/go.mod h1:VtBIF1XX0c1nKkeAPk8i4aXkYopqQgfDqolHUIHPwNI=
+github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE=
+github.com/otiai10/gosseract/v2 v2.2.4/go.mod h1:ahOp/kHojnOMGv1RaUnR0jwY5JVa6BYKhYAS8nbMLSo=
+github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo=
+github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
+github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+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/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
+github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
+github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
+github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
+github.com/rafrombrc/gospec v0.0.0-20121213232506-2e46585948f4/go.mod h1:P3c81VRt80v5x5NRUsozgHnA2GUejoHPh52zyd7otjI=
+github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
+github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
+github.com/simplereach/timeutils v1.2.0/go.mod h1:VVbQDfN/FHRZa1LSqcwo4kNZ62OOyqLLGQKYB3pB0Q8=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
+github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
+github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/src-d/gcfg v1.4.0/go.mod h1:p/UMsR43ujA89BJY9duynAwIpvqEujIH/jFlfL7jWoI=
+github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf/go.mod h1:RJID2RhlZKId02nZ62WenDCkgHFerpIOmW0iT7GKmXM=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
+github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
+github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/tidwall/uhatools v0.4.1/go.mod h1:A0rmLPzOam2WhWA02UFtC3WRWcQJygCH/TwXR11Jd3w=
+github.com/tkuchiki/go-timezone v0.2.0/go.mod h1:b1Ean9v2UXtxSq4TZF0i/TU9NuoWa9hOzOKoGCV2zqY=
+github.com/vcaesar/tt v0.11.0/go.mod h1:GHPxQYhn+7OgKakRusH7KJ0M5MhywoeLb8Fcffs/Gtg=
+github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
+github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
+github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0Bx9h2kr4=
+github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
+github.com/yuin/gopher-lua v0.0.0-20210529063254-f4c35e4016d9/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
+go.etcd.io/etcd/api/v3 v3.5.6 h1:Cy2qx3npLcYqTKqGJzMypnMv2tiRyifZJ17BlWIWA7A=
+go.etcd.io/etcd/api/v3 v3.5.6/go.mod h1:KFtNaxGDw4Yx/BA4iPPwevUTAuqcsPxzyX8PHydchN8=
+go.etcd.io/etcd/client/pkg/v3 v3.5.6 h1:TXQWYceBKqLp4sa87rcPs11SXxUA/mHwH975v+BDvLU=
+go.etcd.io/etcd/client/pkg/v3 v3.5.6/go.mod h1:ggrwbk069qxpKPq8/FKkQ3Xq9y39kbFR4LnKszpRXeQ=
+go.etcd.io/etcd/client/v3 v3.5.6 h1:coLs69PWCXE9G4FKquzNaSHrRyMCAXwF+IX1tAPVO8E=
+go.etcd.io/etcd/client/v3 v3.5.6/go.mod h1:f6GRinRMCsFVv9Ht42EyY7nfsVGwrNO0WEoS2pRKzQk=
+go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
+go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
+go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
+go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
+go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
+golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
+golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.3.0 h1:a06MkbcxBrEFc0w0QIZWXrH/9cCX6KJyWbBOIwAn+7A=
+golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/image v0.0.0-20210216034530-4410531fe030/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
+golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
+golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
+golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
+golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
+golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.2.0 h1:sZfSu1wtKLGlWI4ZZayP0ck9Y73K1ynO6gqzTdBVdPU=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190221075227-b4e8571b14e0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
+golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.2.0 h1:z85xZCsEl7bi/KwbNADeBYoOP0++7W1ipu+aGnpwzRM=
+golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.4.0 h1:BrVqGRd7+k1DiOgtnFvAkoQEWQvBc25ouMJM6429SFg=
+golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190729092621-ff9f1409240a/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
+golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
+golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
+golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
+google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79 h1:s1jFTXJryg4a1mew7xv03VZD8N9XjxFhk1o4Js4WvPQ=
+google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79/go.mod h1:yiaVoXHpRzHGyxV3o4DktVWY4mSUErTKaeEOq6C3t3U=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
+google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
+google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
+google.golang.org/grpc v1.41.0 h1:f+PlOh7QV4iIJkPrx5NQ7qaNGFQ3OTse67yaDHfju4E=
+google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
+google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
+google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
+google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
+gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
+gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbRxng+XOj5ITVgY24WcbNnQopyfKoYQ=
+gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
+gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98=
+gopkg.in/src-d/go-git-fixtures.v3 v3.5.0/go.mod h1:dLBcvytrw/TYZsNTWCnkNF2DSIlzWYqTe3rJR56Ac7g=
+gopkg.in/src-d/go-git.v4 v4.13.1/go.mod h1:nx5NYcxdKxq5fpltdHnPa2Exj4Sx0EclMWZQbYDu2z8=
+gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=

+ 504 - 0
msh/main.go

@@ -0,0 +1,504 @@
+package main
+
+import (
+	"bufio"
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"io"
+	"io/fs"
+	"os"
+	"os/exec"
+	"os/signal"
+	"os/user"
+	"reflect"
+	"regexp"
+	"strings"
+	"syscall"
+	"time"
+
+	"git.wecise.com/wecise/common/matrix/logger"
+	"git.wecise.com/wecise/common/matrix/util"
+	"github.com/creack/pty"
+	"golang.org/x/term"
+	"gopkg.in/yaml.v2"
+)
+
+func init() {
+	logger.SetConsole(true)
+	logger.SetLevel(logger.TRACE)
+	logger.SetFormat("yyyy-MM-dd HH:mm:ss.SSS [level] msg", "\n")
+}
+
+type KV struct{ Key, Val string }
+
+func parseArgs(args []string) (kvs []*KV) {
+	argk := ""
+	argv := ""
+	for _, arg := range args {
+		if argk != "" {
+			argv = arg
+		} else if regexp.MustCompile(`^\-\w+$`).MatchString(arg) {
+			argk = arg[1:]
+			continue
+		} else {
+			kv := strings.SplitN(arg, "=", 2)
+			if len(kv) == 2 {
+				argk = kv[0]
+				argv = kv[1]
+			} else {
+				argk = ""
+				argv = arg
+			}
+		}
+		kvs = append(kvs, &KV{argk, argv})
+		argk, argv = "", ""
+	}
+	if argk != "" {
+		kvs = append(kvs, &KV{argk, argv})
+	}
+	return
+}
+
+func usage() {
+	fmt.Println("usage:")
+	fmt.Println("  msh p=password|a=passcode [[c=]command [p=password|a=passcode] [x=cmd-end-regexp] [[r=regexp] [o=output|n=outputline]]...]...")
+	fmt.Println("  a=passcode should be base64 encoded, or use p=password")
+	fmt.Println("  debug info include: p(progress) a(argments) m(match) 1(all)")
+}
+
+func main() {
+	// get user
+	u := func() string {
+		user, err := user.Current()
+		if err != nil {
+			logger.Error(util.ErrorWithSourceLine(err))
+			return ""
+		}
+		return user.Username
+	}()
+	// get passcode,取默认密码设置
+	c := ""
+	kvs := parseArgs(os.Args)
+	for _, kv := range kvs {
+		if kv.Key == "a" {
+			c = kv.Val
+			if c == "" {
+				c = "="
+			}
+			break
+		}
+		if kv.Key == "p" {
+			c = "=" + kv.Val
+			break
+		}
+	}
+	// get password
+	p := c
+	if p == "" {
+		usage()
+		return
+	} else if p[0:1] == "=" {
+		p = p[1:]
+	} else {
+		x, e := base64.RawStdEncoding.DecodeString(p)
+		if e == nil {
+			p = string(x)
+		}
+		// else 不是Base64编码,保持原值
+	}
+	// explainArgs key, val
+	cmds := []*Command{{Cmd: "sh", Password: c, Regexps: []*Matcher{{Regexp: nil, Output: ""}}, Endregx: regxprompt}}
+	i := 0
+	// 掠过第一个参数,当前执行程序
+	kvs = kvs[1:]
+	for _, kv := range kvs {
+		key, val := kv.Key, kv.Val
+		switch key {
+		case "", "cmd", "command", "c":
+			cmds = append(cmds, &Command{Cmd: val, Password: c, Regexps: []*Matcher{{Regexp: nil, Output: ""}}, Endregx: regxprompt})
+		case "ry", "regex_yes_no":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxyesno = &Regexp{re}
+			}
+		case "rc", "regex_passcode", "regex_password":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxpassword = &Regexp{re}
+			}
+		case "rp", "regex_prompt":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxprompt = &Regexp{re}
+			}
+		case "password", "code", "pass", "p":
+			cmds[len(cmds)-1].Password = "=" + val
+		case "passcode", "b64code", "a":
+			cmds[len(cmds)-1].Password = val
+		case "re", "r", "regex":
+			if val == "" {
+				cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: nil})
+			} else {
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					return
+				} else {
+					cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: &Regexp{re}})
+				}
+			}
+		case "x", "end":
+			if val == "" {
+				cmds[len(cmds)-1].Endregx = regxprompt
+			} else {
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					return
+				} else {
+					cmds[len(cmds)-1].Endregx = &Regexp{re}
+				}
+			}
+		case "out", "o", "output":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val
+		case "outln", "n", "outputline":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val + "\n"
+		case "debug", "d":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Debug += val
+		}
+	}
+	if strings.Index(cmds[0].Regexps[0].Debug, "a") >= 0 || strings.Index(cmds[0].Regexps[0].Debug, "1") >= 0 {
+		//bs, _ := json.MarshalIndent(cmds, "", "  ")
+		bs, _ := yaml.Marshal(cmds)
+		logger.Debug("arguments:\n" + string(bs))
+	}
+	client := &Client{
+		User:     u,
+		Password: p,
+		Commands: cmds,
+	}
+	client.Run()
+}
+
+type Regexp struct {
+	*regexp.Regexp
+}
+
+func (r *Regexp) MarshalJSON() ([]byte, error) {
+	if r == nil || r.Regexp == nil {
+		return json.Marshal(nil)
+	}
+	return json.Marshal(r.String())
+}
+
+func (r *Regexp) MarshalYAML() (interface{}, error) {
+	if r == nil || r.Regexp == nil {
+		return nil, nil
+	}
+	return r.String(), nil
+}
+
+type Matcher struct {
+	Regexp *Regexp
+	Output string `yaml:"output"`
+	Debug  string `yaml:"debug"`
+}
+
+type Command struct {
+	Cmd      string `yaml:"cmd"`
+	Password string `yaml:"password"`
+	Regexps  []*Matcher
+	Endregx  *Regexp
+}
+
+var regxyesno = &Regexp{regexp.MustCompile(`.*(\(yes\/no\)\?)\s*$`)}
+var regxpassword = &Regexp{regexp.MustCompile(`.*([Pp]assword:)\s*$`)}
+var regxprompt = &Regexp{regexp.MustCompile(`.*([%#\$\>])\s*$`)}
+
+type Client struct {
+	User     string
+	Password string
+	Commands []*Command
+}
+
+func Pipe(reader io.Reader, writer io.Writer, pfs ...func(lastbs []byte, sin string) (sout string)) {
+	btr := NewBTReader("", reader, 30*time.Millisecond, 1024)
+	lastbs := []byte{'\n'}
+	for {
+		bs, err := btr.Read()
+		if err != nil {
+			if err == io.EOF {
+				return
+			}
+			logger.Error(util.ErrorWithSourceLine(err))
+			return
+		}
+		if len(bs) > 0 {
+			xbs := bs
+			for _, pf := range pfs {
+				if pf != nil {
+					s := pf(lastbs, string(xbs))
+					xbs = []byte(s)
+				}
+			}
+			if writer != nil {
+				writer.Write(xbs)
+			}
+			lastbs = bs
+		}
+	}
+}
+
+func (c *Client) Run() {
+	logger.Info("msh ready")
+	// Create arbitrary command.
+	cmd := exec.Command("sh", "-c", "export PS1='\\h:> ';sh -i")
+
+	// Start the command with a pty.
+	ptmx, err := pty.Start(cmd)
+	if err != nil {
+		logger.Error(err)
+		return
+	}
+	// Make sure to close the pty at the end.
+	defer func() { _ = ptmx.Close() }() // Best effort.
+
+	// Handle pty size.
+	ch := make(chan os.Signal, 1)
+	signal.Notify(ch, syscall.SIGWINCH)
+	go func() {
+		for range ch {
+			if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
+				logger.Error("error resizing pty:", err)
+			}
+		}
+	}()
+	ch <- syscall.SIGWINCH                        // Initial resize.
+	defer func() { signal.Stop(ch); close(ch) }() // Cleanup signals when done.
+
+	// Set stdin in raw mode.
+	oldState, err := term.MakeRaw(int(os.Stdin.Fd()))
+	if err != nil {
+		logger.Error(err)
+	}
+
+	go Pipe(os.Stdin, ptmx)
+	e := c.IOProc(ptmx, os.Stdout, ptmx)
+	if oldState != nil {
+		term.Restore(int(os.Stdin.Fd()), oldState)
+		fmt.Println()
+	}
+	if cmd.ProcessState.ExitCode() == -1 && e != nil {
+		logger.Error(e)
+		return
+	}
+	logger.Info("msh exit")
+	return
+}
+
+func (c *Client) IOProc(cmdout io.Reader, stdout io.Writer, cmdin io.Writer) error {
+	cmdidx := 0
+	as := ""
+	change := false
+	stdoutbtr := NewBTReader("", cmdout, 50*time.Millisecond, 1024)
+	for {
+		bs, err := stdoutbtr.Read()
+		if err != nil {
+			if err == io.EOF {
+				return nil
+			}
+			if _, y := err.(*fs.PathError); y {
+				return nil
+			}
+			return util.ErrorWithSourceLine(err, reflect.TypeOf(err))
+		}
+		if len(bs) > 0 {
+			change = true
+			s := string(bs)
+			stdout.Write([]byte(s))
+			as += s
+		} else if change {
+			change = false
+			if cmdidx < len(c.Commands) {
+				if msi := c.Commands[cmdidx].Endregx.FindStringSubmatchIndex(as); msi != nil {
+					// logger.Error("EndregexMatch", as)
+					match := as[msi[0]:msi[1]]
+					if strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+						logger.Trace("match end:", "'"+match+"'")
+					}
+					as = "" // 全清,开始新的命令
+					cmdidx++
+					if cmdidx >= len(c.Commands) {
+						return nil
+					}
+					if strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "p") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+						logger.Trace("command:", c.Commands[cmdidx].Cmd)
+					}
+					// stdout.Write([]byte(c.Commands[cmdidx].Cmd + "\n"))
+					cmdin.Write([]byte(c.Commands[cmdidx].Cmd + "\n"))
+					continue
+				}
+				for _, regex := range c.Commands[cmdidx].Regexps {
+					if regex == nil {
+						continue
+					}
+					if regex.Regexp == nil {
+						// like match all
+						if regex.Output != "" {
+							// stdout.Write([]byte(regex.Output))
+							cmdin.Write([]byte(regex.Output))
+						}
+					} else if msi := regex.Regexp.FindStringSubmatchIndex(as); msi != nil {
+						match := as[msi[0]:msi[1]]
+						if len(msi) >= 4 {
+							match = as[msi[2]:msi[3]]
+							as = as[msi[3]:] // 清除已处理完的内容
+						} else {
+							as = as[msi[1]:] // 清除已处理完的内容
+						}
+						if strings.Index(regex.Debug, "m") >= 0 || strings.Index(regex.Debug, "1") >= 0 {
+							logger.Trace("match regex:", "'"+match+"'")
+						}
+						if regex.Output != "" {
+							// stdout.Write([]byte(regex.Output))
+							cmdin.Write([]byte(regex.Output))
+						}
+					}
+				}
+			}
+			if msi := regxyesno.FindStringSubmatchIndex(as); msi != nil {
+				match := as[msi[0]:msi[1]]
+				if len(msi) >= 4 {
+					as = as[msi[3]:] // 清除已处理完的内容
+				} else {
+					as = as[msi[1]:] // 清除已处理完的内容
+				}
+				if strings.Index(c.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+					cmdidx < len(c.Commands) &&
+						(strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+					logger.Trace("match yesno:", "'"+match+"'")
+				}
+				// stdout.Write([]byte("yes\n"))
+				cmdin.Write([]byte("yes\n"))
+			}
+			if msi := regxpassword.FindStringSubmatchIndex(as); msi != nil {
+				// logger.Error(as)
+				match := as[msi[0]:msi[1]]
+				if len(msi) >= 4 {
+					as = as[msi[3]:] // 清除已处理完的内容
+				} else {
+					as = as[msi[1]:] // 清除已处理完的内容
+				}
+				p := c.Commands[0].Password
+				if cmdidx < len(c.Commands) {
+					p = c.Commands[cmdidx].Password
+				}
+				if strings.Index(c.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+					cmdidx < len(c.Commands) &&
+						(strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+					logger.Trace("match password:", "'"+match+"'")
+				}
+				if p != "" {
+					if p[0:1] == "=" {
+						p = p[1:]
+					} else {
+						x, e := base64.RawStdEncoding.DecodeString(p)
+						if e == nil {
+							p = string(x)
+						}
+						// else 不是Base64编码,保持原值
+					}
+					// don't echo password
+					if c.Commands[0].Regexps[0].Debug != "" || cmdidx < len(c.Commands) && c.Commands[cmdidx].Regexps[0].Debug != "" {
+						stdout.Write([]byte(p + "\n"))
+					}
+					cmdin.Write([]byte(p + "\n"))
+				}
+			}
+			if len(as) > 1024 {
+				as = as[len(as)-1024:]
+			}
+		}
+	}
+}
+
+type BTReader struct {
+	*bufio.Reader
+	flag    string
+	chbs    chan []byte
+	cher    chan error
+	timeout time.Duration
+	sizeout int
+	err     error
+}
+
+func NewBTReader(flag string, reader io.Reader, timeout time.Duration, sizeout int) (me *BTReader) {
+	me = &BTReader{Reader: bufio.NewReader(reader),
+		flag:    flag,
+		chbs:    make(chan []byte),
+		cher:    make(chan error),
+		timeout: timeout,
+		sizeout: sizeout,
+	}
+	go func() {
+		bs := make([]byte, me.Size())
+		for {
+			n, err := me.Reader.Read(bs[:1])
+			if err != nil {
+				me.cher <- err
+				return
+			}
+			x, err := me.Reader.Read(bs[1 : me.Reader.Buffered()+1])
+			if err != nil {
+				me.cher <- err
+				return
+			}
+			n += x
+			abs := make([]byte, n)
+			copy(abs, bs[:n])
+			me.chbs <- abs
+		}
+	}()
+	return
+}
+
+// 指定时间内没有新数据进入,且有积累数据,或积累数据超过指定数量,即返回
+func (me *BTReader) Read() (rbs []byte, err error) {
+	if me.err != nil {
+		return nil, me.err
+	}
+	for {
+		t := time.NewTimer(me.timeout)
+		select {
+		case me.err = <-me.cher:
+			if len(rbs) > 0 {
+				// 返回最后的数据,下次读时才返回错误
+				return rbs, nil
+			}
+			return nil, me.err
+		case abs := <-me.chbs:
+			rbs = append(rbs, abs...)
+			if len(rbs) > me.sizeout {
+				return
+			}
+			t.Stop()
+			t.Reset(me.timeout)
+		case <-t.C:
+			if len(rbs) == 0 {
+				t.Stop()
+				t.Reset(me.timeout)
+			}
+			return
+		}
+	}
+}

BIN
msh/msh


+ 457 - 0
mssh/client.go

@@ -0,0 +1,457 @@
+package main
+
+import (
+	"bufio"
+	"encoding/base64"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net"
+	"os"
+	"os/user"
+	"path"
+	"regexp"
+	"strconv"
+	"strings"
+	"syscall"
+	"time"
+
+	"git.wecise.com/wecise/common/matrix/logger"
+	"git.wecise.com/wecise/common/matrix/util"
+	"golang.org/x/crypto/ssh"
+	"golang.org/x/crypto/ssh/terminal"
+)
+
+var (
+	DefaultCiphers = []string{
+		"aes128-ctr",
+		"aes192-ctr",
+		"aes256-ctr",
+		"aes128-gcm@openssh.com",
+		"chacha20-poly1305@openssh.com",
+		"arcfour256",
+		"arcfour128",
+		"arcfour",
+		"aes128-cbc",
+		"3des-cbc",
+		"blowfish-cbc",
+		"cast128-cbc",
+		"aes192-cbc",
+		"aes256-cbc",
+	}
+)
+
+var regxyesno = &Regexp{regexp.MustCompile(`.*(\(yes\/no\)\?)\s*$`)}
+var regxpassword = &Regexp{regexp.MustCompile(`.*([Pp]assword:)\s*$`)}
+var regxprompt = &Regexp{regexp.MustCompile(`.*([%#\$\>])\s*$`)}
+
+type Client interface {
+	Login()
+}
+
+type defaultClient struct {
+	clientConfig *ssh.ClientConfig
+	node         *Node
+}
+
+func genSSHConfig(node *Node) *defaultClient {
+	u, err := user.Current()
+	if err != nil {
+		logger.Error(util.ErrorWithSourceLine(err))
+		return nil
+	}
+
+	var authMethods []ssh.AuthMethod
+
+	var pemBytes []byte
+	if node.KeyPath == "" {
+		pemBytes, err = ioutil.ReadFile(path.Join(u.HomeDir, ".ssh/id_rsa"))
+	} else {
+		pemBytes, err = ioutil.ReadFile(node.KeyPath)
+	}
+	if err != nil && !os.IsNotExist(err) {
+		logger.Error(util.ErrorWithSourceLine(err))
+	} else if len(pemBytes) > 0 {
+		var signer ssh.Signer
+		if node.Passphrase != "" {
+			signer, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(node.Passphrase))
+		} else {
+			signer, err = ssh.ParsePrivateKey(pemBytes)
+		}
+		if err != nil {
+			logger.Error(util.ErrorWithSourceLine(err))
+		} else {
+			authMethods = append(authMethods, ssh.PublicKeys(signer))
+		}
+	}
+
+	password := node.password()
+
+	if password != nil {
+		authMethods = append(authMethods, password)
+	}
+
+	authMethods = append(authMethods, ssh.KeyboardInteractive(func(user, instruction string, questions []string, echos []bool) ([]string, error) {
+		answers := make([]string, 0, len(questions))
+		for i, q := range questions {
+			fmt.Print(q)
+			if echos[i] {
+				scan := bufio.NewScanner(os.Stdin)
+				if scan.Scan() {
+					answers = append(answers, scan.Text())
+				}
+				err := scan.Err()
+				if err != nil {
+					return nil, err
+				}
+			} else {
+				b, err := terminal.ReadPassword(int(syscall.Stdin))
+				if err != nil {
+					return nil, err
+				}
+				fmt.Println()
+				answers = append(answers, string(b))
+			}
+		}
+		return answers, nil
+	}))
+
+	config := &ssh.ClientConfig{
+		User:            node.user(),
+		Auth:            authMethods,
+		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+		Timeout:         time.Second * 10,
+	}
+
+	config.SetDefaults()
+	config.Ciphers = append(config.Ciphers, DefaultCiphers...)
+
+	return &defaultClient{
+		clientConfig: config,
+		node:         node,
+	}
+}
+
+func NewClient(node *Node) Client {
+	return genSSHConfig(node)
+}
+
+func (c *defaultClient) Login() {
+	host := c.node.Host
+	port := strconv.Itoa(c.node.port())
+	jNodes := c.node.Jump
+
+	var client *ssh.Client
+
+	if len(jNodes) > 0 {
+		jNode := jNodes[0]
+		jc := genSSHConfig(jNode)
+		proxyClient, err := ssh.Dial("tcp", net.JoinHostPort(jNode.Host, strconv.Itoa(jNode.port())), jc.clientConfig)
+		if err != nil {
+			logger.Error(util.ErrorWithSourceLine(err))
+			return
+		}
+		conn, err := proxyClient.Dial("tcp", net.JoinHostPort(host, port))
+		if err != nil {
+			logger.Error(util.ErrorWithSourceLine(err))
+			return
+		}
+		ncc, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(host, port), c.clientConfig)
+		if err != nil {
+			logger.Error(util.ErrorWithSourceLine(err))
+			return
+		}
+		client = ssh.NewClient(ncc, chans, reqs)
+	} else {
+		client1, err := ssh.Dial("tcp", net.JoinHostPort(host, port), c.clientConfig)
+		client = client1
+		if err != nil {
+			msg := err.Error()
+			// use terminal password retry
+			if strings.Contains(msg, "no supported methods remain") && !strings.Contains(msg, "password") {
+				fmt.Printf("%s@%s's password:", c.clientConfig.User, host)
+				var b []byte
+				b, err = terminal.ReadPassword(int(syscall.Stdin))
+				if err == nil {
+					p := string(b)
+					if p != "" {
+						c.clientConfig.Auth = append(c.clientConfig.Auth, ssh.Password(p))
+					}
+					fmt.Println()
+					client, err = ssh.Dial("tcp", net.JoinHostPort(host, port), c.clientConfig)
+				}
+			}
+		}
+		if err != nil {
+			logger.Error(util.ErrorWithSourceLine(err))
+			return
+		}
+	}
+	defer client.Close()
+
+	if strings.Index(c.node.Commands[0].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 {
+		logger.Trace("connect server ssh", fmt.Sprintf("-p %d %s@%s version: %s", c.node.port(), c.node.user(), host, string(client.ServerVersion())))
+	}
+
+	session, err := client.NewSession()
+	if err != nil {
+		logger.Error(util.ErrorWithSourceLine(err))
+		return
+	}
+	defer session.Close()
+
+	fd := int(os.Stdin.Fd())
+	w, h, err := terminal.GetSize(fd)
+	if err != nil {
+		logger.Error(util.ErrorWithSourceLine(err))
+		return
+	}
+
+	modes := ssh.TerminalModes{
+		ssh.ECHO:          0,
+		ssh.TTY_OP_ISPEED: 14400,
+		ssh.TTY_OP_OSPEED: 14400,
+	}
+	err = session.RequestPty("xterm", h, w, modes)
+	if err != nil {
+		logger.Error(util.ErrorWithSourceLine(err))
+		return
+	}
+
+	stdinPipe, err := session.StdinPipe()
+	if err != nil {
+		logger.Error(util.ErrorWithSourceLine(err))
+		session.Stdin = os.Stdin
+	}
+	stdoutPipe, err := session.StdoutPipe()
+	if err != nil {
+		logger.Error(util.ErrorWithSourceLine(err))
+		session.Stdout = os.Stdout
+	}
+	stderrPipe, err := session.StderrPipe()
+	if err != nil {
+		logger.Error(util.ErrorWithSourceLine(err))
+		session.Stderr = os.Stderr
+	}
+
+	err = session.Shell()
+	if err != nil {
+		logger.Error(util.ErrorWithSourceLine(err))
+		return
+	}
+
+	cmdidx := 0
+	outputproc := func(stdoutbtr *BTReader, localstdout io.Writer, stdinPipe io.Writer) {
+		as := ""
+		change := false
+		for {
+			bs, err := stdoutbtr.ReadTimeout(10 * time.Millisecond)
+			if err != nil {
+				if err == io.EOF {
+					return
+				}
+				logger.Error(util.ErrorWithSourceLine(err))
+				return
+			}
+			if len(bs) > 0 {
+				change = true
+				s := string(bs)
+				localstdout.Write([]byte(s))
+				as += s
+			} else if change {
+				change = false
+				if cmdidx < len(c.node.Commands) {
+					if msi := c.node.Commands[cmdidx].Endregx.FindStringSubmatchIndex(as); msi != nil {
+						match := as[msi[0]:msi[1]]
+						if strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+							logger.Trace("match end:", "'"+match+"'")
+						}
+						as = "" // 全清,开始新的命令
+						cmdidx++
+						if cmdidx >= len(c.node.Commands) {
+							continue
+						}
+						if strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0 {
+							logger.Trace("command:", c.node.Commands[cmdidx].Cmd)
+						}
+						localstdout.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
+						stdinPipe.Write([]byte(c.node.Commands[cmdidx].Cmd + "\n"))
+						continue
+					}
+					for _, regex := range c.node.Commands[cmdidx].Regexps {
+						if regex == nil {
+							continue
+						}
+						if regex.Regexp == nil {
+							// like match all
+							if regex.Output != "" {
+								localstdout.Write([]byte(regex.Output))
+								stdinPipe.Write([]byte(regex.Output))
+							}
+						} else if msi := regex.Regexp.FindStringSubmatchIndex(as); msi != nil {
+							match := as[msi[0]:msi[1]]
+							if len(msi) >= 4 {
+								match = as[msi[2]:msi[3]]
+								as = as[msi[3]:] // 清除已处理完的内容
+							} else {
+								as = as[msi[1]:] // 清除已处理完的内容
+							}
+							if strings.Index(regex.Debug, "m") >= 0 || strings.Index(regex.Debug, "1") >= 0 {
+								logger.Trace("match regex:", "'"+match+"'")
+							}
+							if regex.Output != "" {
+								localstdout.Write([]byte(regex.Output))
+								stdinPipe.Write([]byte(regex.Output))
+							}
+						}
+					}
+				}
+				if msi := regxyesno.FindStringSubmatchIndex(as); msi != nil {
+					match := as[msi[0]:msi[1]]
+					if len(msi) >= 4 {
+						as = as[msi[3]:] // 清除已处理完的内容
+					} else {
+						as = as[msi[1]:] // 清除已处理完的内容
+					}
+					if strings.Index(c.node.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+						cmdidx < len(c.node.Commands) &&
+							(strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+						logger.Trace("match yesno:", "'"+match+"'")
+					}
+					os.Stdout.Write([]byte("yes\n"))
+					stdinPipe.Write([]byte("yes\n"))
+				}
+				if msi := regxpassword.FindStringSubmatchIndex(as); msi != nil {
+					match := as[msi[0]:msi[1]]
+					if len(msi) >= 4 {
+						as = as[msi[3]:] // 清除已处理完的内容
+					} else {
+						as = as[msi[1]:] // 清除已处理完的内容
+					}
+					p := c.node.Commands[0].Password
+					if cmdidx < len(c.node.Commands) {
+						p = c.node.Commands[cmdidx].Password
+					}
+					if strings.Index(c.node.Commands[0].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 ||
+						cmdidx < len(c.node.Commands) &&
+							(strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "m") >= 0 || strings.Index(c.node.Commands[cmdidx].Regexps[0].Debug, "1") >= 0) {
+						logger.Trace("match password:", "'"+match+"'")
+					}
+					if p != "" {
+						if p[0:1] == "=" {
+							p = p[1:]
+						} else {
+							x, e := base64.RawStdEncoding.DecodeString(p)
+							if e == nil {
+								p = string(x)
+							}
+							// else 不是Base64编码,保持原值
+						}
+						// don't echo password
+						if c.node.Commands[0].Regexps[0].Debug != "" || cmdidx < len(c.node.Commands) && c.node.Commands[cmdidx].Regexps[0].Debug != "" {
+							os.Stdout.Write([]byte(p + "\n"))
+						}
+						stdinPipe.Write([]byte(p + "\n"))
+					}
+				}
+				if len(as) > 1024 {
+					as = as[len(as)-1024:]
+				}
+			}
+		}
+	}
+	go outputproc(&BTReader{Reader: bufio.NewReader(stdoutPipe)}, os.Stdout, stdinPipe)
+	go outputproc(&BTReader{Reader: bufio.NewReader(stderrPipe)}, os.Stderr, stdinPipe)
+
+	go func() {
+		for {
+			bs := make([]byte, 1024)
+			n, err := os.Stdin.Read(bs)
+			if err != nil {
+				if err == io.EOF {
+					return
+				}
+				logger.Error(util.ErrorWithSourceLine(err))
+				return
+			}
+			s := string(bs[:n])
+			stdinPipe.Write([]byte(s))
+		}
+	}()
+
+	// interval get terminal size
+	// fix resize issue
+	go func() {
+		var (
+			ow = w
+			oh = h
+		)
+		for {
+			cw, ch, err := terminal.GetSize(fd)
+			if err != nil {
+				break
+			}
+
+			if cw != ow || ch != oh {
+				err = session.WindowChange(ch, cw)
+				if err != nil {
+					break
+				}
+				ow = cw
+				oh = ch
+			}
+			time.Sleep(time.Second)
+		}
+	}()
+
+	// send keepalive
+	go func() {
+		for {
+			time.Sleep(time.Second * 10)
+			client.SendRequest("nop", false, nil)
+		}
+	}()
+
+	session.Wait()
+	if strings.Index(c.node.Commands[0].Regexps[0].Debug, "p") >= 0 || strings.Index(c.node.Commands[0].Regexps[0].Debug, "1") >= 0 {
+		logger.Trace("disconnected")
+	}
+}
+
+type BTReader struct {
+	*bufio.Reader
+	bufop int32
+	chbs  chan []byte
+}
+
+func (me *BTReader) ReadTimeout(d time.Duration) (rbs []byte, err error) {
+	if me.chbs == nil {
+		me.chbs = make(chan []byte)
+		go func() {
+			n := 0
+			bs := make([]byte, me.Size())
+			for {
+				_, err = me.ReadByte()
+				if err != nil {
+					return
+				}
+				err = me.UnreadByte()
+				if err != nil {
+					return
+				}
+				n, err = me.Read(bs[0:me.Buffered()])
+				if err != nil {
+					return
+				}
+				me.chbs <- bs[0:n]
+			}
+		}()
+	}
+	t := time.NewTimer(d)
+	select {
+	case rbs = <-me.chbs:
+		return
+	case <-t.C:
+		return
+	}
+}

+ 175 - 0
mssh/config.go

@@ -0,0 +1,175 @@
+package main
+
+import (
+	"encoding/json"
+	"fmt"
+	"io/ioutil"
+	"os"
+	"os/user"
+	"path"
+	"regexp"
+	"strconv"
+
+	"git.wecise.com/wecise/common/matrix/logger"
+	"git.wecise.com/wecise/common/matrix/util"
+	"github.com/atrox/homedir"
+	"github.com/kevinburke/ssh_config"
+	"golang.org/x/crypto/ssh"
+	"gopkg.in/yaml.v2"
+)
+
+type Node struct {
+	Name       string     `yaml:"name"`
+	Alias      string     `yaml:"alias"`
+	Host       string     `yaml:"host"`
+	User       string     `yaml:"user"`
+	Port       int        `yaml:"port"`
+	KeyPath    string     `yaml:"keypath"`
+	Passphrase string     `yaml:"passphrase"`
+	Password   string     `yaml:"password"`
+	Commands   []*Command `yaml:"commands"`
+	Children   []*Node    `yaml:"children"`
+	Jump       []*Node    `yaml:"jump"`
+}
+
+type Regexp struct {
+	*regexp.Regexp
+}
+
+func (r *Regexp) MarshalJSON() ([]byte, error) {
+	if r == nil || r.Regexp == nil {
+		return json.Marshal(nil)
+	}
+	return json.Marshal(r.String())
+}
+
+func (r *Regexp) MarshalYAML() (interface{}, error) {
+	if r == nil || r.Regexp == nil {
+		return nil, nil
+	}
+	return r.String(), nil
+}
+
+type Matcher struct {
+	Regexp *Regexp
+	Output string `yaml:"output"`
+	Debug  string `yaml:"debug"`
+}
+
+type Command struct {
+	Cmd      string `yaml:"cmd"`
+	Password string `yaml:"password"`
+	Regexps  []*Matcher
+	Endregx  *Regexp
+}
+
+func (n *Node) String() string {
+	return n.Name
+}
+
+func (n *Node) user() string {
+	if n.User == "" {
+		return "root"
+	}
+	return n.User
+}
+
+func (n *Node) port() int {
+	if n.Port <= 0 {
+		return 22
+	}
+	return n.Port
+}
+
+func (n *Node) password() ssh.AuthMethod {
+	if n.Password == "" {
+		return nil
+	}
+	return ssh.Password(n.Password)
+}
+
+func (n *Node) alias() string {
+	return n.Alias
+}
+
+var (
+	config []*Node
+)
+
+func GetConfig() []*Node {
+	return config
+}
+
+func LoadConfig() error {
+	b, err := LoadConfigBytes(".sshw", ".sshw.yml", ".sshw.yaml")
+	if err != nil {
+		return err
+	}
+	var c []*Node
+	err = yaml.Unmarshal(b, &c)
+	if err != nil {
+		return err
+	}
+
+	config = c
+
+	return nil
+}
+
+func LoadSshConfig() error {
+	u, err := user.Current()
+	if err != nil {
+		logger.Error(util.ErrorWithSourceLine(err))
+		return nil
+	}
+	f, _ := os.Open(path.Join(u.HomeDir, ".ssh/config"))
+	cfg, _ := ssh_config.Decode(f)
+	var nc []*Node
+	for _, host := range cfg.Hosts {
+		alias := fmt.Sprintf("%s", host.Patterns[0])
+		hostName, err := cfg.Get(alias, "HostName")
+		if err != nil {
+			return err
+		}
+		if hostName != "" {
+			port, _ := cfg.Get(alias, "Port")
+			if port == "" {
+				port = "22"
+			}
+			var c = new(Node)
+			c.Name = alias
+			c.Alias = alias
+			c.Host = hostName
+			c.User, _ = cfg.Get(alias, "User")
+			c.Port, _ = strconv.Atoi(port)
+			keyPath, _ := cfg.Get(alias, "IdentityFile")
+			c.KeyPath, _ = homedir.Expand(keyPath)
+			nc = append(nc, c)
+			// fmt.Println(c.Alias, c.Host, c.User, c.Port, c.KeyPath)
+		}
+	}
+	config = nc
+	return nil
+}
+
+func LoadConfigBytes(names ...string) ([]byte, error) {
+	u, err := user.Current()
+	if err != nil {
+		return nil, err
+	}
+	// homedir
+	for i := range names {
+		sshw, err := ioutil.ReadFile(path.Join(u.HomeDir, names[i]))
+		if err == nil {
+			return sshw, nil
+		}
+	}
+	// relative
+	for i := range names {
+		sshw, err := ioutil.ReadFile(names[i])
+		if err == nil {
+			return sshw, nil
+		}
+	}
+	return nil, err
+}

+ 209 - 0
mssh/main.go

@@ -0,0 +1,209 @@
+package main
+
+import (
+	"encoding/base64"
+	"fmt"
+	"os"
+	"os/user"
+	"regexp"
+	"strings"
+
+	"git.wecise.com/wecise/common/matrix/logger"
+	"git.wecise.com/wecise/common/matrix/util"
+	"gopkg.in/yaml.v2"
+)
+
+func init() {
+	logger.SetConsole(true)
+	logger.SetLevel(logger.TRACE)
+	logger.SetFormat("yyyy-MM-dd HH:mm:ss.SSS [level] msg", "\n")
+}
+
+func uchi() (u, c, h string, i int) {
+	if len(os.Args) < 2 {
+		return
+	}
+	if msi := regexp.MustCompile(`^([^:]+):(.*)@([^@]*)$`).FindStringSubmatchIndex(os.Args[1]); msi != nil {
+		u = os.Args[1][msi[2]:msi[3]]
+		c = "=" + os.Args[1][msi[4]:msi[5]]
+		h = os.Args[1][msi[6]:msi[7]]
+		i = 2
+		return
+	}
+	if msi := regexp.MustCompile(`^([^:]+)@([^@]*)$`).FindStringSubmatchIndex(os.Args[1]); msi != nil {
+		u = os.Args[1][msi[2]:msi[3]]
+		h = os.Args[1][msi[6]:msi[7]]
+		i = 2
+	} else if os.Args[1][0] != '-' && strings.Index(os.Args[1], "=") < 0 {
+		h = os.Args[1]
+		i = 2
+	} else {
+		i = 1
+	}
+	if u == "" {
+		user, err := user.Current()
+		if err != nil {
+			logger.Error(util.ErrorWithSourceLine(err))
+			return "", "", "", 0
+		}
+		u = user.Username
+	}
+	if h == "" {
+		h = "127.0.0.1"
+	}
+	return
+}
+
+type KV struct{ Key, Val string }
+
+func parseArgs(args []string) (kvs []*KV) {
+	argk := ""
+	argv := ""
+	for _, arg := range args {
+		if argk != "" {
+			argv = arg
+		} else if regexp.MustCompile(`^\-\w+$`).MatchString(arg) {
+			argk = arg[1:]
+			continue
+		} else {
+			kv := strings.SplitN(arg, "=", 2)
+			if len(kv) == 2 {
+				argk = kv[0]
+				argv = kv[1]
+			} else {
+				argk = ""
+				argv = arg
+			}
+		}
+		kvs = append(kvs, &KV{argk, argv})
+		argk, argv = "", ""
+	}
+	if argk != "" {
+		kvs = append(kvs, &KV{argk, argv})
+	}
+	return
+}
+
+func main() {
+	u, c, h, i := uchi()
+	if i == 0 {
+		fmt.Println("usage:")
+		fmt.Println("  msh user:password@host|p=password|a=passcode [[c=]command [p=password|a=passcode] [x=cmd-end-regexp] [[r=regexp] [o=output|n=outputline]]...]...")
+		fmt.Println("  a=passcode should be base64 encoded, or use p=password")
+		fmt.Println("  debug info include: p(progress) a(argments) m(match) 1(all)")
+		return
+	}
+	kvs := parseArgs(os.Args[i:])
+	if c == "" {
+		for _, kv := range kvs {
+			if kv.Key == "a" {
+				c = kv.Val
+				if c == "" {
+					c = "="
+				}
+				break
+			}
+			if kv.Key == "p" {
+				c = "=" + kv.Val
+				break
+			}
+		}
+	}
+	p := c
+	if p[0:1] == "=" {
+		p = p[1:]
+	} else {
+		x, e := base64.RawStdEncoding.DecodeString(p)
+		if e == nil {
+			p = string(x)
+		}
+		// else 不是Base64编码,保持原值
+	}
+	// explainArgs key, val
+	cmds := []*Command{{Cmd: "ssh " + u + ":" + p + "@" + h, Password: c, Regexps: []*Matcher{{Regexp: nil, Output: ""}}, Endregx: regxprompt}}
+	for _, kv := range kvs {
+		key, val := kv.Key, kv.Val
+		switch key {
+		case "", "cmd", "command", "c":
+			cmds = append(cmds, &Command{Cmd: val, Password: c, Regexps: []*Matcher{{Regexp: nil, Output: ""}}, Endregx: regxprompt})
+		case "ry":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxyesno = &Regexp{re}
+			}
+		case "rc":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxpassword = &Regexp{re}
+			}
+		case "rp":
+			re, err := regexp.Compile(val)
+			if err != nil {
+				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				return
+			} else {
+				regxprompt = &Regexp{re}
+			}
+		case "password", "code", "pass", "p":
+			cmds[len(cmds)-1].Password = "=" + val
+		case "passcode", "b64code", "a":
+			cmds[len(cmds)-1].Password = val
+		case "re", "r", "regex":
+			if val == "" {
+				cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: nil})
+			} else {
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					return
+				} else {
+					cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: &Regexp{re}})
+				}
+			}
+		case "x", "end":
+			if val == "" {
+				cmds[len(cmds)-1].Endregx = regxprompt
+			} else {
+				re, err := regexp.Compile(val)
+				if err != nil {
+					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					return
+				} else {
+					cmds[len(cmds)-1].Endregx = &Regexp{re}
+				}
+			}
+		case "out", "o", "output":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val
+		case "outln", "n", "outputline":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Output += val + "\n"
+		case "debug", "d":
+			cmds[len(cmds)-1].Regexps[len(cmds[len(cmds)-1].Regexps)-1].Debug += val
+		}
+	}
+	if strings.Index(cmds[0].Regexps[0].Debug, "a") >= 0 || strings.Index(cmds[0].Regexps[0].Debug, "1") >= 0 {
+		//bs, _ := json.MarshalIndent(cmds, "", "  ")
+		bs, _ := yaml.Marshal(cmds)
+		logger.Debug("arguments:\n" + string(bs))
+	}
+	node := &Node{
+		Name:       "x",
+		Alias:      "x",
+		Host:       h,
+		User:       u,
+		Port:       22,
+		KeyPath:    "",
+		Passphrase: "",
+		Password:   p,
+		Commands:   cmds,
+		Children:   []*Node{},
+		Jump:       []*Node{},
+	}
+	sshc := NewClient(node)
+	sshc.Login()
+}

+ 53 - 0
xsh/main.go

@@ -0,0 +1,53 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"strings"
+	"syscall"
+
+	"golang.org/x/crypto/ssh/terminal"
+)
+
+var (
+	oldTermState *terminal.State
+)
+
+const prompt = "LBS> "
+
+func main() {
+
+	fmt.Printf("\nLBS CLI - version 1.0\n\n")
+
+	var err error
+	oldTermState, err = terminal.MakeRaw(syscall.Stdin)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	term := terminal.NewTerminal(os.Stdin, prompt)
+	rawState, err := terminal.GetState(syscall.Stdin)
+	if err != nil {
+		fmt.Println(err)
+		return
+	}
+
+	for {
+		cmdline, err := term.ReadLine()
+		if err != nil {
+			break
+		}
+		cmdline = strings.TrimSpace(cmdline)
+		if cmdline == "" {
+			continue
+		}
+		terminal.Restore(syscall.Stdin, oldTermState)
+		fmt.Println("oooooo:", cmdline)
+		terminal.Restore(syscall.Stdin, rawState)
+	}
+
+	terminal.Restore(syscall.Stdin, oldTermState)
+	fmt.Println("")
+	//fmt.Println("Terminal Over !!!!")
+}

BIN
xsh/xsh