libf před 5 měsíci
rodič
revize
491a6590b0
12 změnil soubory, kde provedl 390 přidání a 1318 odebrání
  1. 0 546
      _/main.go
  2. 0 612
      _/mainx.go
  3. 15 12
      go.mod
  4. 66 105
      go.sum
  5. 10 11
      msh/main.go
  6. 1 1
      msh/winsize_others.go
  7. 18 18
      mssh/client.go
  8. 3 3
      mssh/config.go
  9. 9 8
      mssh/main.go
  10. 132 0
      ssh/sftp.go
  11. 134 0
      ssh/ssh.go
  12. 2 2
      sync/sync.go

+ 0 - 546
_/main.go

@@ -1,546 +0,0 @@
-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
-	}
-}

+ 0 - 612
_/mainx.go

@@ -1,612 +0,0 @@
-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
-		}
-	}
-}

+ 15 - 12
go.mod

@@ -1,9 +1,8 @@
 module zerone
 
-go 1.19
+go 1.22
 
 require (
-	git.wecise.com/wecise/common v0.0.0-20241210084937-d93a95618502
 	github.com/atrox/homedir v1.0.0
 	github.com/creack/pty v1.1.18
 	github.com/kevinburke/ssh_config v1.2.0
@@ -20,32 +19,36 @@ require (
 	github.com/gobwas/pool v0.2.1 // indirect
 	github.com/gobwas/ws v1.3.2 // indirect
 	github.com/gogo/protobuf v1.3.2 // indirect
-	github.com/golang/protobuf v1.5.2 // indirect
+	github.com/golang/protobuf v1.5.4 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
+	github.com/kr/fs v0.1.0 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
-	github.com/webview/webview v0.0.0-20241127090340-83a4b4a5bbcb // indirect
-	go.etcd.io/etcd/api/v3 v3.5.8 // indirect
-	go.etcd.io/etcd/client/pkg/v3 v3.5.8 // indirect
-	go.etcd.io/etcd/client/v3 v3.5.8 // indirect
+	github.com/spacemonkeygo/errors v0.0.0-20201030155909-2f5f890dbc62 // indirect
+	go.etcd.io/etcd/api/v3 v3.5.17 // indirect
+	go.etcd.io/etcd/client/pkg/v3 v3.5.17 // indirect
+	go.etcd.io/etcd/client/v3 v3.5.17 // indirect
 	go.uber.org/atomic v1.7.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
 	go.uber.org/zap v1.17.0 // indirect
-	golang.org/x/net v0.22.0 // indirect
+	golang.org/x/net v0.32.0 // indirect
 	golang.org/x/text v0.21.0 // indirect
-	google.golang.org/genproto v0.0.0-20210630183607-d20f26d13c79 // indirect
-	google.golang.org/grpc v1.50.1 // indirect
-	google.golang.org/protobuf v1.28.1 // indirect
+	google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect
+	google.golang.org/grpc v1.59.0 // indirect
+	google.golang.org/protobuf v1.33.0 // indirect
 	gopkg.in/ini.v1 v1.67.0 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect
 )
 
 require (
-	git.wecise.com/wecise/util v0.0.0-20241212012021-7dae097404ec
+	git.wecise.com/wecise/util v0.0.0-20241213033438-5064ad8df1f5
 	github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e
 	github.com/chromedp/chromedp v0.9.5
 	github.com/fatih/color v1.9.0 // indirect
 	github.com/mattn/go-colorable v0.1.7 // indirect
 	github.com/mattn/go-isatty v0.0.20 // indirect
+	github.com/pkg/sftp v1.13.7
 	github.com/spf13/cast v1.6.0 // indirect
 	golang.org/x/sys v0.28.0 // indirect
 )

+ 66 - 105
go.sum

@@ -1,12 +1,7 @@
-cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
-git.wecise.com/wecise/common v0.0.0-20241210084937-d93a95618502 h1:d/qsPfdulXqobMmuyq3FhmLzPUccKKK+wWaEmXLeD/g=
-git.wecise.com/wecise/common v0.0.0-20241210084937-d93a95618502/go.mod h1:4osIHxc9vhckvXYFejuELnQ6ncA8U5Xx5WWRmQ1qY2s=
-git.wecise.com/wecise/util v0.0.0-20241212012021-7dae097404ec h1:snSFFMn+ZvDm+E4oxEyKwppKSuvQVeey21Soc6bkpwo=
-git.wecise.com/wecise/util v0.0.0-20241212012021-7dae097404ec/go.mod h1:jvQwP46pybS4l6VpsEr1PJRAB9/OiPEuSVuXIUUCqsk=
-github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+git.wecise.com/wecise/util v0.0.0-20241213033438-5064ad8df1f5 h1:iwgK+2gBbRjyk2LB7JgEKpjGxUF/o2oQJwAEdVfFNFo=
+git.wecise.com/wecise/util v0.0.0-20241213033438-5064ad8df1f5/go.mod h1:3T0eAIwD7TnDT87ShyoV6HHGFoWcK/SHFUKhNe2ok+o=
 github.com/atrox/homedir v1.0.0 h1:99Vwk+XECZTDLaAPeMj7vF9JMNcVarWddqPeyDzJT5E=
 github.com/atrox/homedir v1.0.0/go.mod h1:ZKVEIDNKscX8qV1TyrwLP+ayjv3XQO7wbVmc5EW00A8=
-github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
 github.com/chromedp/cdproto v0.0.0-20240202021202-6d0b6a386732/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
 github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e h1:kXEolCWQZzuEFcuaTzfqXToX+e29OcvK87BcBiBBJ1c=
 github.com/chromedp/cdproto v0.0.0-20240214232516-ad4608604e9e/go.mod h1:GKljq0VrfU4D5yc+2qA6OVr8pmO/MBbPEWqWQ/oqGEs=
@@ -14,8 +9,6 @@ github.com/chromedp/chromedp v0.9.5 h1:viASzruPJOiThk7c5bueOUY91jGLJVximoEMGoH93
 github.com/chromedp/chromedp v0.9.5/go.mod h1:D4I2qONslauw/C7INoCir1BJkSwBYMyZgx8X276z3+Y=
 github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
 github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
-github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
-github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 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=
@@ -25,13 +18,10 @@ github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr
 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/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.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-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/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
+github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
 github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
 github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
@@ -41,36 +31,22 @@ github.com/gobwas/ws v1.3.2/go.mod h1:hRKAFb8wOxFROYNsT1bqfWnhX+b5MFeJM9r2ZSwg/K
 github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
 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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
-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.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/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.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
+github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
-github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 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/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
+github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
 github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
@@ -87,28 +63,34 @@ github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhA
 github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
+github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/sftp v1.13.7 h1:uv+I3nNJvlKZIQGSr8JVQLNHFU9YhhNpvC14Y6KgmSM=
+github.com/pkg/sftp v1.13.7/go.mod h1:KMKI0t3T6hfA+lTR/ssZdunHo+uwq7ghoN09/FSu3DY=
 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_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
+github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
+github.com/spacemonkeygo/errors v0.0.0-20201030155909-2f5f890dbc62 h1:X5+jSi+pL+sc2/Sp9mtrmRqDDnKkm9YVy1ik/jJDRD0=
+github.com/spacemonkeygo/errors v0.0.0-20201030155909-2f5f890dbc62/go.mod h1:7NL9UAYQnRM5iKHUCld3tf02fKb5Dft+41+VckASUy0=
 github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
 github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
-github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
-github.com/webview/webview v0.0.0-20241127090340-83a4b4a5bbcb h1:dKHZEHxtVTqnp29vsFicRxuYr/heaUN8rFgY8OZVep4=
-github.com/webview/webview v0.0.0-20241127090340-83a4b4a5bbcb/go.mod h1:Zk81X+8/mp/MNoeJXXK4Noydn8pcea09e00dFfAwrxg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
+github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 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=
-go.etcd.io/etcd/api/v3 v3.5.8 h1:Zf44zJszoU7zRV0X/nStPenegNXoFDWcB/MwrJbA+L4=
-go.etcd.io/etcd/api/v3 v3.5.8/go.mod h1:uyAal843mC8uUVSLWz6eHa/d971iDGnCRpmKd2Z+X8k=
-go.etcd.io/etcd/client/pkg/v3 v3.5.8 h1:tPp9YRn/UBFAHdhOQUII9eUs7aOK35eulpMhX4YBd+M=
-go.etcd.io/etcd/client/pkg/v3 v3.5.8/go.mod h1:y+CzeSmkMpWN2Jyu1npecjB9BBnABxGM4pN8cGuJeL4=
-go.etcd.io/etcd/client/v3 v3.5.8 h1:B6ngTKZSWWowHEoaucOKHQR/AtZKaoHLiUpWxOLG4l4=
-go.etcd.io/etcd/client/v3 v3.5.8/go.mod h1:idZYIPVkttBJBiRigkB5EM0MmEyx8jcl18zCV3F5noc=
+github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+go.etcd.io/etcd/api/v3 v3.5.17 h1:cQB8eb8bxwuxOilBpMJAEo8fAONyrdXTHUNcMd8yT1w=
+go.etcd.io/etcd/api/v3 v3.5.17/go.mod h1:d1hvkRuXkts6PmaYk2Vrgqbv7H4ADfAKhyJqHNLJCB4=
+go.etcd.io/etcd/client/pkg/v3 v3.5.17 h1:XxnDXAWq2pnxqx76ljWwiQ9jylbpC4rvkAeRVOUKKVw=
+go.etcd.io/etcd/client/pkg/v3 v3.5.17/go.mod h1:4DqK1TKacp/86nJk4FLQqo6Mn2vvQFBmruW3pP14H/w=
+go.etcd.io/etcd/client/v3 v3.5.17 h1:o48sINNeWz5+pjy/Z0+HKpj/xSnBkuVhVvXkjEXbqZY=
+go.etcd.io/etcd/client/v3 v3.5.17/go.mod h1:j2d4eXTHWkT2ClBgnnEPm/Wuu7jsqku41v9DZ3OtjQo=
 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=
@@ -118,36 +100,29 @@ go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
 golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY=
 golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
-golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
-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-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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
-golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/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-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
-golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
-golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
-golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
-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/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
+golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
+golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI=
+golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs=
 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-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/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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=
@@ -156,67 +131,55 @@ golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/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-20210330210617-4fbd30eecc44/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-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
 golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
+golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
+golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
+golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
 golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
 golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 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.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
+golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
 golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
 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-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-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
+golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
 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-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
-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.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.50.1 h1:DS/BukOZWp8s6p4Dt/tOaJaTQyPyOoCcrjroHuCeLzY=
-google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI=
-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=
+google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d h1:VBu5YqKPv6XiJ199exd8Br+Aetz+o08F+PLMnwJQHAY=
+google.golang.org/genproto v0.0.0-20230822172742-b8732ec3820d/go.mod h1:yZTlhN0tQnXo3h00fuXNCxJdLdIdnVFVBaRJ5LWBbw4=
+google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d h1:DoPTO70H+bcDXcd39vOqb2viZxgqeBeSGtZ55yZU4/Q=
+google.golang.org/genproto/googleapis/api v0.0.0-20230822172742-b8732ec3820d/go.mod h1:KjSP20unUpOx5kyQUFa7k4OJg0qeJ7DEZflGDu2p6Bk=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M=
+google.golang.org/grpc v1.59.0 h1:Z5Iec2pjwb+LEOqzpB2MR12/eKFhDPhuqW91O+4bwUk=
+google.golang.org/grpc v1.59.0/go.mod h1:aUPDwccQo6OTjy7Hct4AfBPD1GptF4fyUjIkQ9YtF98=
+google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
+google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.8/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=
@@ -224,5 +187,3 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C
 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=

+ 10 - 11
msh/main.go

@@ -11,13 +11,12 @@ import (
 	"os/exec"
 	"os/signal"
 	"os/user"
-	"reflect"
 	"regexp"
 	"strings"
 	"time"
 
-	"git.wecise.com/wecise/common/matrix/logger"
-	"git.wecise.com/wecise/common/matrix/util"
+	"git.wecise.com/wecise/util/logger"
+	"git.wecise.com/wecise/util/merrs"
 	"github.com/creack/pty"
 	"golang.org/x/term"
 	"gopkg.in/yaml.v2"
@@ -71,7 +70,7 @@ func main() {
 	u := func() string {
 		user, err := user.Current()
 		if err != nil {
-			logger.Error(util.ErrorWithSourceLine(err))
+			logger.Error(merrs.NewError(err))
 			return ""
 		}
 		return user.Username
@@ -119,7 +118,7 @@ func main() {
 		case "ry", "regex_yes_no":
 			re, err := regexp.Compile(val)
 			if err != nil {
-				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				logger.Error("arg", i, merrs.NewError(err))
 				return
 			} else {
 				regxyesno = &Regexp{re}
@@ -127,7 +126,7 @@ func main() {
 		case "rc", "regex_passcode", "regex_password":
 			re, err := regexp.Compile(val)
 			if err != nil {
-				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				logger.Error("arg", i, merrs.NewError(err))
 				return
 			} else {
 				regxpassword = &Regexp{re}
@@ -135,7 +134,7 @@ func main() {
 		case "rp", "regex_prompt":
 			re, err := regexp.Compile(val)
 			if err != nil {
-				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				logger.Error("arg", i, merrs.NewError(err))
 				return
 			} else {
 				regxprompt = &Regexp{re}
@@ -150,7 +149,7 @@ func main() {
 			} else {
 				re, err := regexp.Compile(val)
 				if err != nil {
-					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					logger.Error("arg", i, merrs.NewError(err))
 					return
 				} else {
 					cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: &Regexp{re}})
@@ -162,7 +161,7 @@ func main() {
 			} else {
 				re, err := regexp.Compile(val)
 				if err != nil {
-					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					logger.Error("arg", i, merrs.NewError(err))
 					return
 				} else {
 					cmds[len(cmds)-1].Endregx = &Regexp{re}
@@ -239,7 +238,7 @@ func Pipe(reader io.Reader, writer io.Writer, pfs ...func(lastbs []byte, sin str
 			if err == io.EOF {
 				return
 			}
-			logger.Error(util.ErrorWithSourceLine(err))
+			logger.Error(merrs.NewError(err))
 			return
 		}
 		if len(bs) > 0 {
@@ -312,7 +311,7 @@ func (c *Client) IOProc(cmdout io.Reader, stdout io.Writer, cmdin io.Writer) err
 			if _, y := err.(*fs.PathError); y {
 				return nil
 			}
-			return util.ErrorWithSourceLine(err, reflect.TypeOf(err))
+			return merrs.NewError(err)
 		}
 		if len(bs) > 0 {
 			change = true

+ 1 - 1
msh/winsize_others.go

@@ -7,7 +7,7 @@ import (
 	"os/signal"
 	"syscall"
 
-	"git.wecise.com/wecise/common/matrix/logger"
+	"git.wecise.com/wecise/util/logger"
 	"github.com/creack/pty"
 )
 

+ 18 - 18
mssh/client.go

@@ -15,8 +15,8 @@ import (
 	"syscall"
 	"time"
 
-	"git.wecise.com/wecise/common/matrix/logger"
-	"git.wecise.com/wecise/common/matrix/util"
+	"git.wecise.com/wecise/util/logger"
+	"git.wecise.com/wecise/util/merrs"
 	"golang.org/x/crypto/ssh"
 	"golang.org/x/term"
 )
@@ -56,7 +56,7 @@ type defaultClient struct {
 func genSSHConfig(node *Node) *defaultClient {
 	u, err := user.Current()
 	if err != nil {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 		return nil
 	}
 
@@ -69,7 +69,7 @@ func genSSHConfig(node *Node) *defaultClient {
 		pemBytes, err = os.ReadFile(node.KeyPath)
 	}
 	if err != nil && !os.IsNotExist(err) {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 	} else if len(pemBytes) > 0 {
 		var signer ssh.Signer
 		if node.Passphrase != "" {
@@ -78,7 +78,7 @@ func genSSHConfig(node *Node) *defaultClient {
 			signer, err = ssh.ParsePrivateKey(pemBytes)
 		}
 		if err != nil {
-			logger.Error(util.ErrorWithSourceLine(err))
+			logger.Error(merrs.NewError(err))
 		} else {
 			authMethods = append(authMethods, ssh.PublicKeys(signer))
 		}
@@ -147,17 +147,17 @@ func (c *defaultClient) Login() {
 		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))
+			logger.Error(merrs.NewError(err))
 			return
 		}
 		conn, err := proxyClient.Dial("tcp", net.JoinHostPort(host, port))
 		if err != nil {
-			logger.Error(util.ErrorWithSourceLine(err))
+			logger.Error(merrs.NewError(err))
 			return
 		}
 		ncc, chans, reqs, err := ssh.NewClientConn(conn, net.JoinHostPort(host, port), c.clientConfig)
 		if err != nil {
-			logger.Error(util.ErrorWithSourceLine(err))
+			logger.Error(merrs.NewError(err))
 			return
 		}
 		client = ssh.NewClient(ncc, chans, reqs)
@@ -182,7 +182,7 @@ func (c *defaultClient) Login() {
 			}
 		}
 		if err != nil {
-			logger.Error(util.ErrorWithSourceLine(err))
+			logger.Error(merrs.NewError(err))
 			return
 		}
 	}
@@ -194,7 +194,7 @@ func (c *defaultClient) Login() {
 
 	session, err := client.NewSession()
 	if err != nil {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 		return
 	}
 	defer session.Close()
@@ -202,7 +202,7 @@ func (c *defaultClient) Login() {
 	fd := int(os.Stdin.Fd())
 	w, h, err := term.GetSize(fd)
 	if err != nil {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 		w = 300
 		h = 50
 	}
@@ -214,29 +214,29 @@ func (c *defaultClient) Login() {
 	}
 	err = session.RequestPty("xterm", h, w, modes)
 	if err != nil {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 		return
 	}
 
 	stdinPipe, err := session.StdinPipe()
 	if err != nil {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 		session.Stdin = os.Stdin
 	}
 	stdoutPipe, err := session.StdoutPipe()
 	if err != nil {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 		session.Stdout = os.Stdout
 	}
 	stderrPipe, err := session.StderrPipe()
 	if err != nil {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 		session.Stderr = os.Stderr
 	}
 
 	err = session.Shell()
 	if err != nil {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 		return
 	}
 
@@ -250,7 +250,7 @@ func (c *defaultClient) Login() {
 				if err == io.EOF {
 					return
 				}
-				logger.Error(util.ErrorWithSourceLine(err))
+				logger.Error(merrs.NewError(err))
 				return
 			}
 			if len(bs) > 0 {
@@ -371,7 +371,7 @@ func (c *defaultClient) Login() {
 				if err == io.EOF {
 					return
 				}
-				logger.Error(util.ErrorWithSourceLine(err))
+				logger.Error(merrs.NewError(err))
 				return
 			}
 			s := string(bs[:n])

+ 3 - 3
mssh/config.go

@@ -9,8 +9,8 @@ import (
 	"regexp"
 	"strconv"
 
-	"git.wecise.com/wecise/common/matrix/logger"
-	"git.wecise.com/wecise/common/matrix/util"
+	"git.wecise.com/wecise/util/logger"
+	"git.wecise.com/wecise/util/merrs"
 	"github.com/atrox/homedir"
 	"github.com/kevinburke/ssh_config"
 	"golang.org/x/crypto/ssh"
@@ -118,7 +118,7 @@ func LoadConfig() error {
 func LoadSshConfig() error {
 	u, err := user.Current()
 	if err != nil {
-		logger.Error(util.ErrorWithSourceLine(err))
+		logger.Error(merrs.NewError(err))
 		return nil
 	}
 	f, _ := os.Open(path.Join(u.HomeDir, ".ssh/config"))

+ 9 - 8
mssh/main.go

@@ -8,8 +8,8 @@ import (
 	"regexp"
 	"strings"
 
-	"git.wecise.com/wecise/common/matrix/logger"
-	"git.wecise.com/wecise/common/matrix/util"
+	"git.wecise.com/wecise/util/logger"
+	"git.wecise.com/wecise/util/merrs"
 	"gopkg.in/yaml.v2"
 )
 
@@ -19,6 +19,7 @@ func init() {
 	logger.SetFormat("yyyy-MM-dd HH:mm:ss.SSS [level] msg", "\n")
 }
 
+// 通过命令行参数获取 user:password@host
 func uchi() (u, c, h string, i int) {
 	if len(os.Args) < 2 {
 		return
@@ -43,7 +44,7 @@ func uchi() (u, c, h string, i int) {
 	if u == "" {
 		user, err := user.Current()
 		if err != nil {
-			logger.Error(util.ErrorWithSourceLine(err))
+			logger.Error(merrs.NewError(err))
 			return "", "", "", 0
 		}
 		u = user.Username
@@ -129,7 +130,7 @@ func main() {
 		case "ry":
 			re, err := regexp.Compile(val)
 			if err != nil {
-				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				logger.Error("arg", i, merrs.NewError(err))
 				return
 			} else {
 				regxyesno = &Regexp{re}
@@ -137,7 +138,7 @@ func main() {
 		case "rc":
 			re, err := regexp.Compile(val)
 			if err != nil {
-				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				logger.Error("arg", i, merrs.NewError(err))
 				return
 			} else {
 				regxpassword = &Regexp{re}
@@ -145,7 +146,7 @@ func main() {
 		case "rp":
 			re, err := regexp.Compile(val)
 			if err != nil {
-				logger.Error("arg", i, util.ErrorWithSourceLine(err))
+				logger.Error("arg", i, merrs.NewError(err))
 				return
 			} else {
 				regxprompt = &Regexp{re}
@@ -160,7 +161,7 @@ func main() {
 			} else {
 				re, err := regexp.Compile(val)
 				if err != nil {
-					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					logger.Error("arg", i, merrs.NewError(err))
 					return
 				} else {
 					cmds[len(cmds)-1].Regexps = append(cmds[len(cmds)-1].Regexps, &Matcher{Regexp: &Regexp{re}})
@@ -172,7 +173,7 @@ func main() {
 			} else {
 				re, err := regexp.Compile(val)
 				if err != nil {
-					logger.Error("arg", i, util.ErrorWithSourceLine(err))
+					logger.Error("arg", i, merrs.NewError(err))
 					return
 				} else {
 					cmds[len(cmds)-1].Endregx = &Regexp{re}

+ 132 - 0
ssh/sftp.go

@@ -0,0 +1,132 @@
+package ssh
+
+import (
+	"fmt"
+	"os"
+	"time"
+
+	"github.com/pkg/sftp"
+)
+
+func (me *Client) List(dir string, proc func(path string, err error) error) error {
+	// open an SFTP session over an existing ssh connection.
+	client, err := sftp.NewClient(me.Client)
+	if err != nil {
+		return err
+	}
+	defer client.Close()
+
+	// walk a directory
+	w := client.Walk(dir)
+	for w.Step() {
+		err = proc(w.Path(), w.Err())
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (me *Client) Stat(fpath string) (os.FileInfo, error) {
+	// open an SFTP session over an existing ssh connection.
+	client, err := sftp.NewClient(me.Client)
+	if err != nil {
+		return nil, err
+	}
+	defer client.Close()
+	return client.Lstat(fpath)
+}
+
+func (me *Client) ReadFile(fpath string) (data []byte, err error) {
+	// open an SFTP session over an existing ssh connection.
+	client, err := sftp.NewClient(me.Client)
+	if err != nil {
+		return nil, err
+	}
+	defer client.Close()
+	f, err := client.Open(fpath)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+
+	var size int
+	if info, err := f.Stat(); err == nil {
+		size64 := info.Size()
+		if int64(int(size64)) == size64 {
+			size = int(size64)
+		}
+	}
+	size++ // one byte for final read at EOF
+
+	// If a file claims a small size, read at least 512 bytes.
+	// In particular, files in Linux's /proc claim size 0 but
+	// then do not work right if read in small pieces,
+	// so an initial read of 1 byte would not work correctly.
+	offset := len(data)
+	if size > cap(data) {
+		ndata := make([]byte, len(data), size)
+		copy(ndata, data)
+		data = ndata
+	}
+
+	for {
+		n, err := f.ReadAt(data[offset:cap(data)], int64(offset))
+		offset += n
+		if err != nil {
+			return data[:offset], err
+		}
+		if offset >= cap(data) {
+			d := append(data[:cap(data)], 0)
+			data = d[:offset]
+		}
+	}
+}
+
+func (me *Client) WriteFile(fpath string, bs []byte, mode os.FileMode, mtime time.Time) error {
+	// open an SFTP session over an existing ssh connection.
+	client, err := sftp.NewClient(me.Client)
+	if err != nil {
+		return err
+	}
+	defer client.Close()
+
+	// leave your mark
+	f, err := client.Create(fpath)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	w := 0
+	for w < len(bs) {
+		n, err := f.Write(bs[w:])
+		w += n
+		if err != nil {
+			return err
+		}
+	}
+
+	err = client.Chmod(fpath, mode)
+	if err != nil {
+		return err
+	}
+	err = client.Chtimes(fpath, mtime, mtime)
+	if err != nil {
+		return err
+	}
+
+	// check it's there
+	fi, err := client.Lstat(fpath)
+	if err != nil {
+		return err
+	}
+
+	if fi.Size() != int64(len(bs)) {
+		return fmt.Errorf("write file size incorrect")
+	}
+	if !fi.ModTime().Equal(mtime) {
+		return fmt.Errorf("write file time incorrect")
+	}
+
+	return nil
+}

+ 134 - 0
ssh/ssh.go

@@ -0,0 +1,134 @@
+package ssh
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"os/user"
+	"path"
+	"syscall"
+	"time"
+
+	"git.wecise.com/wecise/util/merrs"
+	"golang.org/x/crypto/ssh"
+	"golang.org/x/term"
+)
+
+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",
+	}
+)
+
+type Node struct {
+	IPPort     string
+	User       string
+	Password   string
+	Passphrase string
+	Keypath    string
+	Timeout    time.Duration
+}
+
+type Client struct {
+	*ssh.Client
+}
+
+func (me *Node) Connect() (*Client, error) {
+	authMethods, err := authMethods(me.Password, me.Passphrase, me.Keypath)
+	if err != nil {
+		return nil, err
+	}
+	config := &ssh.ClientConfig{
+		User:            me.User,
+		Auth:            authMethods,
+		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
+		Timeout:         me.Timeout,
+	}
+	config.SetDefaults()
+	config.Ciphers = append(config.Ciphers, DefaultCiphers...)
+
+	client, err := ssh.Dial("tcp", me.IPPort, config)
+	if err != nil {
+		return nil, err
+	}
+
+	return &Client{
+		Client: client,
+	}, nil
+}
+
+func authMethods(password string, passphrase string, keypath string) ([]ssh.AuthMethod, error) {
+	var authMethods []ssh.AuthMethod
+
+	osuser, err := user.Current()
+	if err != nil {
+		return nil, merrs.NewError(err)
+	}
+
+	var pemBytes []byte
+	if keypath == "" {
+		pemBytes, err = os.ReadFile(path.Join(osuser.HomeDir, ".ssh/id_rsa"))
+	} else {
+		pemBytes, err = os.ReadFile(keypath)
+	}
+	if err != nil && !os.IsNotExist(err) {
+		return nil, merrs.NewError(err)
+	}
+
+	if len(pemBytes) > 0 {
+		var signer ssh.Signer
+		if passphrase != "" {
+			signer, err = ssh.ParsePrivateKeyWithPassphrase(pemBytes, []byte(passphrase))
+		} else {
+			signer, err = ssh.ParsePrivateKey(pemBytes)
+		}
+		if err != nil {
+			return nil, merrs.NewError(err)
+		}
+		authMethods = append(authMethods, ssh.PublicKeys(signer))
+	}
+
+	if password != "" {
+		authMethods = append(authMethods, ssh.Password(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 := term.ReadPassword(int(syscall.Stdin))
+				if err != nil {
+					return nil, err
+				}
+				fmt.Println()
+				answers = append(answers, string(b))
+			}
+		}
+		return answers, nil
+	}))
+
+	return authMethods, nil
+}

+ 2 - 2
sync/sync.go

@@ -9,9 +9,9 @@ import (
 	"strings"
 	"time"
 
-	ccfg "git.wecise.com/wecise/common/matrix/cfg"
-	clog "git.wecise.com/wecise/common/matrix/logger"
+	ccfg "git.wecise.com/wecise/util/cfg"
 	"git.wecise.com/wecise/util/filewalker"
+	clog "git.wecise.com/wecise/util/logger"
 )
 
 var config = ccfg.MConfig()