Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Golang ssh #25

Merged
merged 32 commits into from
Nov 17, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
2ca2a34
wip golang ssh
mikew Oct 18, 2024
29d0b25
cleanup
mikew Oct 18, 2024
ba29e85
omg
mikew Oct 20, 2024
8ba25c9
disable authkey for now
mikew Oct 20, 2024
ac82ac3
cleanup files on remote
mikew Oct 20, 2024
14a91ff
wip, password, agent, and passphrase-less pubkey seem to work!
mikew Oct 21, 2024
3b00fd5
support passphrases
mikew Oct 21, 2024
bc19c09
better check for passphrase
mikew Oct 21, 2024
656f15d
extract askForPassword
mikew Oct 21, 2024
32d6136
Merge branch 'main' of https://github.com/mikew/nvrh into golang-ssh
mikew Oct 21, 2024
917a7ff
oh I've botched this merge
mikew Oct 21, 2024
afd02b2
ohno
mikew Oct 21, 2024
dc44d0e
flesh out SshEndpoint
mikew Oct 21, 2024
d53bc1c
try agents first (seems to be how SSH does it)
mikew Oct 21, 2024
62e53cc
extract a bunch of stuff to nvrh_ssh
mikew Oct 21, 2024
8fa983b
match SSH questions
mikew Oct 21, 2024
1d0696e
yup that's a mess
mikew Oct 22, 2024
74814c1
more mess
mikew Oct 22, 2024
f4c2c8b
more mess
mikew Oct 22, 2024
7b48e91
lint
mikew Oct 22, 2024
04a828d
cleanup
mikew Oct 22, 2024
12493b8
more changes
mikew Oct 22, 2024
25d5a6d
allow `--ssh-path binary`
mikew Oct 23, 2024
7ff7fff
prefer localhost
mikew Oct 23, 2024
4478a7a
cleanup
mikew Oct 23, 2024
7a72577
manage known_hosts
mikew Oct 24, 2024
591576d
fix askForPassword on Windows
mikew Nov 9, 2024
370cec3
default ssh agent pipe on windows
mikew Nov 9, 2024
220c231
fix default user name on windows
mikew Nov 9, 2024
4b68304
go mod tidy
mikew Nov 9, 2024
7cfd20f
ugly refactor for golang platform builds
mikew Nov 10, 2024
f6d09fe
try and trim down binary size
mikew Nov 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ go 1.23.1
require (
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/dusted-go/logging v1.3.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/neovim/go-client v1.2.2-0.20240514170004-863141a115a5 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/skeema/knownhosts v1.3.0 // indirect
github.com/urfave/cli/v2 v2.27.4 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/crypto v0.28.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
)
14 changes: 14 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,27 @@ github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lV
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/dusted-go/logging v1.3.0 h1:SL/EH1Rp27oJQIte+LjWvWACSnYDTqNx5gZULin0XRY=
github.com/dusted-go/logging v1.3.0/go.mod h1:s58+s64zE5fxSWWZfp+b8ZV0CHyKHjamITGyuY1wzGg=
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/neovim/go-client v1.2.1 h1:kl3PgYgbnBfvaIoGYi3ojyXH0ouY6dJY/rYUCssZKqI=
github.com/neovim/go-client v1.2.1/go.mod h1:EeqCP3z1vJd70JTaH/KXz9RMZ/nIgEFveX83hYnh/7c=
github.com/neovim/go-client v1.2.2-0.20240514170004-863141a115a5 h1:bDKPFxHFy0ApEmtUFFQzbxMGgywlKrpyNJ2opMX4hjc=
github.com/neovim/go-client v1.2.2-0.20240514170004-863141a115a5/go.mod h1:UBsOERb5epbeQT0nyPTZkmUPTffRYBcHvrXXidr1NQQ=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
95 changes: 68 additions & 27 deletions src/client/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,12 @@ import (
"nvrh/src/context"
"nvrh/src/logger"
"nvrh/src/nvim_helpers"
"nvrh/src/ssh_helpers"
"nvrh/src/nvrh_base_ssh"
"nvrh/src/nvrh_binary_ssh"
"nvrh/src/nvrh_internal_ssh"
"nvrh/src/nvrh_ssh"
"nvrh/src/ssh_endpoint"
"nvrh/src/ssh_tunnel_info"
)

func defaultSshPath() string {
Expand Down Expand Up @@ -82,10 +87,22 @@ var CliClientOpenCommand = cli.Command{
logger.PrepareLogger(isDebug)

// Prepare the context.
server := c.Args().Get(0)
if server == "" {
return fmt.Errorf("<server> is required")
}

sessionId := fmt.Sprintf("%d", time.Now().Unix())
sshPath := c.String("ssh-path")

endpoint, endpointErr := ssh_endpoint.ParseSshEndpoint(server)
if endpointErr != nil {
return endpointErr
}

nvrhContext := &context.NvrhContext{
SessionId: sessionId,
Server: c.Args().Get(0),
Endpoint: endpoint,
RemoteDirectory: c.Args().Get(1),

RemoteEnv: c.StringSlice("server-env"),
Expand All @@ -102,16 +119,30 @@ var CliClientOpenCommand = cli.Command{
Debug: isDebug,
}

if sshPath == "internal" {
sshClient, err := nvrh_ssh.GetSshClientForEndpoint(endpoint)
if err != nil {
return err
}

nvrhContext.SshClient = nvrh_base_ssh.BaseNvrhSshClient(&nvrh_internal_ssh.NvrhInternalSshClient{
Ctx: nvrhContext,
SshClient: sshClient,
})
} else {
nvrhContext.SshClient = nvrh_base_ssh.BaseNvrhSshClient(&nvrh_binary_ssh.NvrhBinarySshClient{
Ctx: nvrhContext,
})
}

defer nvrhContext.SshClient.Close()

if nvrhContext.ShouldUsePorts {
min := 1025
max := 65535
nvrhContext.PortNumber = rand.IntN(max-min) + min
}

if nvrhContext.Server == "" {
return fmt.Errorf("<server> is required")
}

var nv *nvim.Nvim

doneChan := make(chan error)
Expand All @@ -121,29 +152,32 @@ var CliClientOpenCommand = cli.Command{

// Prepare remote instance.
go func() {
remoteCmd := ssh_helpers.BuildRemoteNvimCmd(nvrhContext)
if nvrhContext.Debug {
remoteCmd.Stdout = os.Stdout
remoteCmd.Stderr = os.Stderr
// remoteCmd.Stdin = os.Stdin
}
nvrhContext.CommandsToKill = append(nvrhContext.CommandsToKill, remoteCmd)

// We don't want the ssh process ending too early, if it does we can't
// clean up the remote nvim instance.
// exec_helpers.PrepareForForking(remoteCmd)

if err := remoteCmd.Start(); err != nil {
slog.Error("Error starting ssh", "err", err)
go func() {
tunnelInfo := &ssh_tunnel_info.SshTunnelInfo{
Mode: "unix",
LocalSocket: nvrhContext.LocalSocketOrPort(),
RemoteSocket: nvrhContext.RemoteSocketOrPort(),
Public: false,
}

if nvrhContext.ShouldUsePorts {
tunnelInfo.Mode = "port"
tunnelInfo.LocalSocket = fmt.Sprintf("%d", nvrhContext.PortNumber)
tunnelInfo.RemoteSocket = fmt.Sprintf("%d", nvrhContext.PortNumber)
}

nvrhContext.SshClient.TunnelSocket(tunnelInfo)
}()

nvimCommandString := nvim_helpers.BuildRemoteCommandString(nvrhContext)
nvimCommandString = fmt.Sprintf("$SHELL -i -c '%s'", nvimCommandString)
slog.Info("Starting remote nvim", "nvimCommandString", nvimCommandString)
if err := nvrhContext.SshClient.Run(nvimCommandString); err != nil {
doneChan <- err
return
}

if err := remoteCmd.Wait(); err != nil {
slog.Error("Error running ssh", "err", err)
} else {
slog.Info("Remote nvim exited")
}
nvrhContext.SshClient.Run(fmt.Sprintf("rm -f '%s'", nvrhContext.RemoteSocketPath))
nvrhContext.SshClient.Run(fmt.Sprintf("rm -f '%s'", nvrhContext.BrowserScriptPath))
}()

// Prepare client instance.
Expand Down Expand Up @@ -231,7 +265,14 @@ func BuildClientNvimCmd(nvrhContext *context.NvrhContext) *exec.Cmd {
}

func prepareRemoteNvim(nvrhContext *context.NvrhContext, nv *nvim.Nvim) error {
nv.RegisterHandler("tunnel-port", ssh_helpers.MakeRpcTunnelHandler(nvrhContext))
nv.RegisterHandler("tunnel-port", func(v *nvim.Nvim, args []string) {
go nvrhContext.SshClient.TunnelSocket(&ssh_tunnel_info.SshTunnelInfo{
Mode: "port",
LocalSocket: fmt.Sprintf("%s", args[0]),
RemoteSocket: fmt.Sprintf("%s", args[0]),
Public: true,
})
})
nv.RegisterHandler("open-url", RpcHandleOpenUrl)

batch := nv.NewBatch()
Expand Down
7 changes: 6 additions & 1 deletion src/context/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package context
import (
"fmt"
"os/exec"

"nvrh/src/nvrh_base_ssh"
"nvrh/src/ssh_endpoint"
)

type NvrhContext struct {
SessionId string
Server string
Endpoint *ssh_endpoint.SshEndpoint
RemoteDirectory string

LocalSocketPath string
Expand All @@ -24,6 +27,8 @@ type NvrhContext struct {

SshPath string
Debug bool

SshClient nvrh_base_ssh.BaseNvrhSshClient
}

func (nc *NvrhContext) LocalSocketOrPort() string {
Expand Down
16 changes: 16 additions & 0 deletions src/nvim_helpers/main.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package nvim_helpers

import (
"fmt"
"strings"
"time"

"github.com/neovim/go-client/nvim"
Expand All @@ -27,3 +29,17 @@ func WaitForNvim(nvrhContext *context.NvrhContext) (*nvim.Nvim, error) {

// return nil, errors.New("Timed out waiting for nvim")
}

func BuildRemoteCommandString(nvrhContext *context.NvrhContext) string {
envPairsString := ""
if len(nvrhContext.RemoteEnv) > 0 {
envPairsString = strings.Join(nvrhContext.RemoteEnv, " ")
}

return fmt.Sprintf(
"%s nvim --headless --listen \"%s\" --cmd \"cd %s\"",
envPairsString,
nvrhContext.RemoteSocketOrPort(),
nvrhContext.RemoteDirectory,
)
}
11 changes: 11 additions & 0 deletions src/nvrh_base_ssh/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package nvrh_base_ssh

import (
"nvrh/src/ssh_tunnel_info"
)

type BaseNvrhSshClient interface {
Run(command string) error
TunnelSocket(tunnelInfo *ssh_tunnel_info.SshTunnelInfo)
Close() error
}
70 changes: 70 additions & 0 deletions src/nvrh_binary_ssh/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package nvrh_binary_ssh

import (
"log/slog"
"os"
"os/exec"

"nvrh/src/context"
"nvrh/src/ssh_tunnel_info"
)

type NvrhBinarySshClient struct {
Ctx *context.NvrhContext
}

func (c *NvrhBinarySshClient) Close() error {
return nil
}

func (c *NvrhBinarySshClient) Run(command string) error {
sshCommand := exec.Command(
c.Ctx.SshPath,
"-t",
c.Ctx.Endpoint.Given,
command,
)

c.Ctx.CommandsToKill = append(c.Ctx.CommandsToKill, sshCommand)
if c.Ctx.Debug {
sshCommand.Stdout = os.Stdout
sshCommand.Stderr = os.Stderr
}

if err := sshCommand.Start(); err != nil {
return err
}

if err := sshCommand.Wait(); err != nil {
return err
}

return nil
}

func (c *NvrhBinarySshClient) TunnelSocket(tunnelInfo *ssh_tunnel_info.SshTunnelInfo) {
sshCommand := exec.Command(
c.Ctx.SshPath,
"-NL",
tunnelInfo.BoundToIp(),
c.Ctx.Endpoint.Given,
)

slog.Info("Tunneling SSH socket", "tunnelInfo", tunnelInfo)

c.Ctx.CommandsToKill = append(c.Ctx.CommandsToKill, sshCommand)
if c.Ctx.Debug {
sshCommand.Stdout = os.Stdout
sshCommand.Stderr = os.Stderr
}

if err := sshCommand.Start(); err != nil {
return
}

defer sshCommand.Process.Kill()

if err := sshCommand.Wait(); err != nil {
return
}
}
Loading