5

I have this "sort of" working, but here is what is missing:

  1. control keys do not work. "ctrl-c" kills the entire go script, "ctrl-r" does not work as expected, etc.
  2. up and down arrows print ^[[A^[[D^[[C

The code I have below I got from this package: https://github.com/nanobox-io/golang-ssh I tested using that package separately and it works perfectly. I can't see what that package is doing that isn't in my script. I've also included the code I use to create a tunnel to the host, in case that is relevant.

Here is the example code. Please disregard the use of panic, it makes it easier for me to debug this new code while I am working on it. I would not intend this code in its current state to be used in a production environment.

func Start(hostName string) {
    var (
        termWidth, termHeight = 80, 24
    )

    jumpAddr := "..."
    hostAddr := "..."

    jumpConfig := ssh.ClientConfig{
        // ...
    }

    hostConfig := ssh.ClientConfig{
        // ...
    }

    jumpConn, err := ssh.Dial("tcp", jumpAddr+":22", &jumpConfig)
    ifpanic(err)

    hostConn, err := jumpConn.Dial("tcp", hostAddr+":22")
    ifpanic(err)

    hostSSHConn, chans, reqs, err := ssh.NewClientConn(hostConn, hostName+":22", &hostConfig)
    ifpanic(err)

    hostClient := ssh.NewClient(hostSSHConn, chans, reqs)
    session, err := hostClient.NewSession()
    ifpanic(err)

    session.Stdout = os.Stdout
    session.Stderr = os.Stderr
    session.Stdin = os.Stdin

    modes := ssh.TerminalModes{
        ssh.ECHO: 1,
    }

    fd := os.Stdin.Fd()

    oldState, err := term.MakeRaw(fd)
    ifpanic(err)

    defer ifpanic(term.RestoreTerminal(fd, oldState))

    winsize, err := term.GetWinsize(fd)
    if err == nil {
        termWidth = int(winsize.Width)
        termHeight = int(winsize.Height)
    }

    if err := session.RequestPty("xterm", termHeight, termWidth, modes); err != nil {
        panic(err)
    }

    if err := session.Shell(); err != nil {
        panic(err)
    }

    go monWinCh(session, os.Stdout.Fd())

    if err := session.Wait(); err != nil {
        panic(err)
    }
}

func ifpanic(err error) {
    if err != nil {
        panic(err)
    }
}

func monWinCh(session *ssh.Session, fd uintptr) {
    sigs := make(chan os.Signal, 1)

    signal.Notify(sigs, syscall.SIGWINCH)
    defer signal.Stop(sigs)

    for range sigs {
        if _, err := session.SendRequest("window-change", false, termSize(fd)); err != nil {
            panic(err)
        }
    }
}

func termSize(fd uintptr) []byte {
    size := make([]byte, 16)

    winsize, err := term.GetWinsize(fd)
    if err != nil {
        binary.BigEndian.PutUint32(size, uint32(80))
        binary.BigEndian.PutUint32(size[4:], uint32(24))
        return size
    }

    binary.BigEndian.PutUint32(size, uint32(winsize.Width))
    binary.BigEndian.PutUint32(size[4:], uint32(winsize.Height))

    return size
}

2
  • 3
    Please don't try to use panics as exceptions: github.com/golang/go/wiki/CodeReviewComments#dont-panic Commented May 19, 2020 at 14:29
  • @JimB thank you, i know. this is test code, not intended yet for production use Commented May 20, 2020 at 15:41

1 Answer 1

1

You can capture OS signals using a channel, then redirect them to the SSH session:

import (
    "os"
    "os/signal"
    "syscall"
)
//...

sigChannel := make(chan os.Signal)
signal.Notify(sigChannel, syscall.SIGINT, syscall.SIGTERM)
go func() {
    for {
        sig, ok := <-sigChannel
        if !ok {
            break
        }

        switch sig {
        case syscall.SIGINT:
            session.Signal(ssh.SIGINT)
        case syscall.SIGTERM:
            session.Signal(ssh.SIGTERM)
        }
    }
}()

Note: don't forget to close the signal channel properly so your goroutine can exit gracefully.

This code doesn't capture every signals, but you can find a list of available signals here, and pick the ones you want to handle.

Sign up to request clarification or add additional context in comments.

1 Comment

I don't think this is the right approach. First, this won't fix the up, down, left, and right arrows. Second, in the example repo i linked to, github.com/nanobox-io/golang-ssh, the author does not capture signals for their implementation, and yet it works perfectly

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.