diff --git a/cli/server/add.go b/cli/server/add.go index 3a290967..eede197d 100644 --- a/cli/server/add.go +++ b/cli/server/add.go @@ -225,14 +225,12 @@ func installDocker(c *cli.Context, cl *dockerClient.Client, sshCl *simplessh.Cli return err } logrus.Debugf("running '%s' on %s now with sudo password", cmd, domainName) - _, err := sshCl.ExecSudo(cmd, sudoPass) - if err != nil { + if err := ssh.RunSudoCmd(cmd, sudoPass, sshCl); err != nil { return err } } else { logrus.Debugf("running '%s' on %s now without sudo password", cmd, domainName) - _, err := sshCl.Exec(cmd) - if err != nil { + if err := ssh.Exec(cmd, sshCl); err != nil { return err } } diff --git a/pkg/ssh/ssh.go b/pkg/ssh/ssh.go index c403cc35..e5aefa7e 100644 --- a/pkg/ssh/ssh.go +++ b/pkg/ssh/ssh.go @@ -1,7 +1,12 @@ package ssh import ( + "bufio" + "bytes" + "fmt" + "io" "os/user" + "sync" "time" "github.com/AlecAivazis/survey/v2" @@ -94,3 +99,128 @@ func New(domainName, sshAuth, username, port string) (*simplessh.Client, error) return client, nil } + +// sudoWriter supports sudo command handling. +// https://github.com/sfreiberg/simplessh/blob/master/simplessh.go +type sudoWriter struct { + b bytes.Buffer + pw string + stdin io.Writer + m sync.Mutex +} + +// Write satisfies the write interface for sudoWriter. +// https://github.com/sfreiberg/simplessh/blob/master/simplessh.go +func (w *sudoWriter) Write(p []byte) (int, error) { + if string(p) == "sudo_password" { + w.stdin.Write([]byte(w.pw + "\n")) + w.pw = "" + return len(p), nil + } + + w.m.Lock() + defer w.m.Unlock() + + return w.b.Write(p) +} + +// RunSudoCmd runs SSH commands and streams output. +// https://github.com/sfreiberg/simplessh/blob/master/simplessh.go +func RunSudoCmd(cmd, passwd string, cl *simplessh.Client) error { + session, err := cl.SSHClient.NewSession() + if err != nil { + return err + } + defer session.Close() + + cmd = "sudo -p " + "sudo_password" + " -S " + cmd + + w := &sudoWriter{ + pw: passwd, + } + w.stdin, err = session.StdinPipe() + if err != nil { + return err + } + + session.Stdout = w + session.Stderr = w + + done := make(chan struct{}) + scanner := bufio.NewScanner(session.Stdin) + + go func() { + for scanner.Scan() { + line := scanner.Text() + fmt.Println(line) + } + done <- struct{}{} + }() + + if err := session.Start(cmd); err != nil { + return err + } + + <-done + + if err := session.Wait(); err != nil { + return err + } + + return err +} + +// Exec runs a command on a remote and streams output. +// https://github.com/sfreiberg/simplessh/blob/master/simplessh.go +func Exec(cmd string, cl *simplessh.Client) error { + session, err := cl.SSHClient.NewSession() + if err != nil { + return err + } + defer session.Close() + + stdout, err := session.StdoutPipe() + if err != nil { + return err + } + + stderr, err := session.StdoutPipe() + if err != nil { + return err + } + + stdoutDone := make(chan struct{}) + stdoutScanner := bufio.NewScanner(stdout) + + go func() { + for stdoutScanner.Scan() { + line := stdoutScanner.Text() + fmt.Println(line) + } + stdoutDone <- struct{}{} + }() + + stderrDone := make(chan struct{}) + stderrScanner := bufio.NewScanner(stderr) + + go func() { + for stderrScanner.Scan() { + line := stderrScanner.Text() + fmt.Println(line) + } + stderrDone <- struct{}{} + }() + + if err := session.Start(cmd); err != nil { + return err + } + + <-stdoutDone + <-stderrDone + + if err := session.Wait(); err != nil { + return err + } + + return nil +}