// Package easyssh provides a simple implementation of some SSH protocol // features in Go. You can simply run a command on a remote server or get a file // even simpler than native console SSH client. You don't need to think about // Dials, sessions, defers, or public keys... Let easyssh think about it! package easyssh import ( "bufio" "io" "io/ioutil" "net" "time" "golang.org/x/crypto/ssh" ) // MakeConfig Contains main authority information. // User field should be a name of user on remote server (ex. john in ssh john@example.com). // Server field should be a remote machine address (ex. example.com in ssh john@example.com) // Key is a path to private key on your local machine. // Port is SSH server port on remote machine. // Note: easyssh looking for private key in user's home directory (ex. /home/john + Key). // Then ensure your Key begins from '/' (ex. /.ssh/id_rsa) type MakeConfig struct { User string Server string Key string KeyPath string Port string Password string Timeout time.Duration } // returns ssh.Signer from user you running app home path + cutted key path. // (ex. pubkey,err := getKeyFile("/.ssh/id_rsa") ) func getKeyFile(keypath string) (ssh.Signer, error) { buf, err := ioutil.ReadFile(keypath) if err != nil { return nil, err } pubkey, err := ssh.ParsePrivateKey(buf) if err != nil { return nil, err } return pubkey, nil } // connects to remote server using MakeConfig struct and returns *ssh.Session func (ssh_conf *MakeConfig) connect() (*ssh.Session, error) { // auths holds the detected ssh auth methods auths := []ssh.AuthMethod{} // figure out what auths are requested, what is supported if ssh_conf.Password != "" { auths = append(auths, ssh.Password(ssh_conf.Password)) } if ssh_conf.KeyPath != "" { if pubkey, err := getKeyFile(ssh_conf.KeyPath); err == nil { auths = append(auths, ssh.PublicKeys(pubkey)) } } if ssh_conf.Key != "" { signer, _ := ssh.ParsePrivateKey([]byte(ssh_conf.Key)) auths = append(auths, ssh.PublicKeys(signer)) } config := &ssh.ClientConfig{ Timeout: ssh_conf.Timeout, User: ssh_conf.User, Auth: auths, } client, err := ssh.Dial("tcp", net.JoinHostPort(ssh_conf.Server, ssh_conf.Port), config) if err != nil { return nil, err } session, err := client.NewSession() if err != nil { return nil, err } return session, nil } // Stream returns one channel that combines the stdout and stderr of the command // as it is run on the remote machine, and another that sends true when the // command is done. The sessions and channels will then be closed. func (ssh_conf *MakeConfig) Stream(command string) (output chan string, done chan bool, err error) { // connect to remote host session, err := ssh_conf.connect() if err != nil { return output, done, err } // connect to both outputs (they are of type io.Reader) outReader, err := session.StdoutPipe() if err != nil { return output, done, err } errReader, err := session.StderrPipe() if err != nil { return output, done, err } // combine outputs, create a line-by-line scanner outputReader := io.MultiReader(outReader, errReader) err = session.Start(command) scanner := bufio.NewScanner(outputReader) // continuously send the command's output over the channel outputChan := make(chan string) done = make(chan bool) go func(scanner *bufio.Scanner, out chan string, done chan bool) { defer close(outputChan) defer close(done) for scanner.Scan() { outputChan <- scanner.Text() } // close all of our open resources done <- true session.Close() }(scanner, outputChan, done) return outputChan, done, err } // Run command on remote machine and returns its stdout as a string func (ssh_conf *MakeConfig) Run(command string) (outStr string, err error) { outChan, doneChan, err := ssh_conf.Stream(command) if err != nil { return outStr, err } // read from the output channel until the done signal is passed stillGoing := true for stillGoing { select { case <-doneChan: stillGoing = false case line := <-outChan: outStr += line + "\n" } } // return the concatenation of all signals from the output channel return outStr, err }