From 112780a7eda6fca95acc1d973b34e7a5b99e20ef Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Sun, 12 Jan 2025 00:05:03 -0800 Subject: [PATCH 1/6] feat: add RemoteGetInfoCommand This adds an rpc command to get info on the remote system. --- cmd/wsh/cmd/wshcmd-connserver.go | 2 +- pkg/remote/conncontroller/conncontroller.go | 2 +- pkg/wshrpc/wshremote/wshremote.go | 5 +++++ pkg/wshrpc/wshrpctypes.go | 3 ++- pkg/wshrpc/wshserver/wshserver.go | 6 +++++- pkg/wshutil/wshutil.go | 3 +-- 6 files changed, 15 insertions(+), 6 deletions(-) diff --git a/cmd/wsh/cmd/wshcmd-connserver.go b/cmd/wsh/cmd/wshcmd-connserver.go index 8d1b7bea88..844735d7da 100644 --- a/cmd/wsh/cmd/wshcmd-connserver.go +++ b/cmd/wsh/cmd/wshcmd-connserver.go @@ -190,7 +190,7 @@ func serverRunRouter() error { } func checkForUpdate() error { - remoteInfo := wshutil.GetInfo(RpcContext) + remoteInfo := wshutil.GetInfo() needsRestartRaw, err := RpcClient.SendRpcRequest(wshrpc.Command_ConnUpdateWsh, remoteInfo, &wshrpc.RpcOpts{Timeout: 60000}) if err != nil { return fmt.Errorf("could not update: %w", err) diff --git a/pkg/remote/conncontroller/conncontroller.go b/pkg/remote/conncontroller/conncontroller.go index c341f583df..72b0a7789c 100644 --- a/pkg/remote/conncontroller/conncontroller.go +++ b/pkg/remote/conncontroller/conncontroller.go @@ -379,7 +379,7 @@ Would you like to install them? func (conn *SSHConn) UpdateWsh(ctx context.Context, clientDisplayName string, remoteInfo *wshrpc.RemoteInfo) error { conn.Infof(ctx, "attempting to update wsh for connection %s (os:%s arch:%s version:%s)\n", - remoteInfo.ConnName, remoteInfo.ClientOs, remoteInfo.ClientArch, remoteInfo.ClientVersion) + conn.GetName(), remoteInfo.ClientOs, remoteInfo.ClientArch, remoteInfo.ClientVersion) client := conn.GetClient() if client == nil { return fmt.Errorf("cannot update wsh: ssh client is not connected") diff --git a/pkg/wshrpc/wshremote/wshremote.go b/pkg/wshrpc/wshremote/wshremote.go index cd3cd5d394..9c317cbde6 100644 --- a/pkg/wshrpc/wshremote/wshremote.go +++ b/pkg/wshrpc/wshremote/wshremote.go @@ -18,6 +18,7 @@ import ( "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/wshrpc" + "github.com/wavetermdev/waveterm/pkg/wshutil" ) const MaxFileSize = 50 * 1024 * 1024 // 10M @@ -387,3 +388,7 @@ func (*ServerImpl) RemoteFileDeleteCommand(ctx context.Context, path string) err } return nil } + +func (*ServerImpl) RemoteGetInfoCommand(ctx context.Context) wshrpc.RemoteInfo { + return wshutil.GetInfo() +} diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 5c42ebd4d0..0d84cb102c 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -73,6 +73,7 @@ const ( Command_GetVar = "getvar" Command_SetVar = "setvar" Command_RemoteMkdir = "remotemkdir" + Command_RemoteGetInfo = "remotegetinfo" Command_ConnStatus = "connstatus" Command_WslStatus = "wslstatus" @@ -179,6 +180,7 @@ type WshRpcInterface interface { RemoteFileJoinCommand(ctx context.Context, paths []string) (*FileInfo, error) RemoteMkdirCommand(ctx context.Context, path string) error RemoteStreamCpuDataCommand(ctx context.Context) chan RespOrErrorUnion[TimeSeriesData] + RemoteGetInfoCommand(ctx context.Context) RemoteInfo // emain WebSelectorCommand(ctx context.Context, data CommandWebSelectorData) ([]string, error) @@ -503,7 +505,6 @@ type ConnRequest struct { } type RemoteInfo struct { - ConnName string `json:"host"` ClientArch string `json:"clientarch"` ClientOs string `json:"clientos"` ClientVersion string `json:"clientversion"` diff --git a/pkg/wshrpc/wshserver/wshserver.go b/pkg/wshrpc/wshserver/wshserver.go index 4077913739..cb7d9ff646 100644 --- a/pkg/wshrpc/wshserver/wshserver.go +++ b/pkg/wshrpc/wshserver/wshserver.go @@ -700,7 +700,11 @@ func (ws *WshServer) ConnReinstallWshCommand(ctx context.Context, data wshrpc.Co } func (ws *WshServer) ConnUpdateWshCommand(ctx context.Context, remoteInfo wshrpc.RemoteInfo) (bool, error) { - connName := remoteInfo.ConnName + handler := wshutil.GetRpcResponseHandlerFromContext(ctx) + if handler == nil { + return false, fmt.Errorf("could not determine handler from context") + } + connName := handler.GetRpcContext().Conn if connName == "" { return false, fmt.Errorf("invalid remote info: missing connection name") } diff --git a/pkg/wshutil/wshutil.go b/pkg/wshutil/wshutil.go index 88b259aff2..3bcbff7351 100644 --- a/pkg/wshutil/wshutil.go +++ b/pkg/wshutil/wshutil.go @@ -538,9 +538,8 @@ func ExtractUnverifiedSocketName(tokenStr string) (string, error) { return sockName, nil } -func GetInfo(rpcContext wshrpc.RpcContext) wshrpc.RemoteInfo { +func GetInfo() wshrpc.RemoteInfo { return wshrpc.RemoteInfo{ - ConnName: rpcContext.Conn, ClientArch: runtime.GOARCH, ClientOs: runtime.GOOS, ClientVersion: wavebase.WaveVersion, From 7a6730952cb44e18c0e9b33b2e6e29cbaebbc62f Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Sun, 12 Jan 2025 00:33:35 -0800 Subject: [PATCH 2/6] fix: update generated files --- frontend/app/store/wshclientapi.ts | 5 +++++ frontend/types/gotypes.d.ts | 1 - pkg/wshrpc/wshclient/wshclient.go | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/frontend/app/store/wshclientapi.ts b/frontend/app/store/wshclientapi.ts index 8d93b2cc41..d69b789b5a 100644 --- a/frontend/app/store/wshclientapi.ts +++ b/frontend/app/store/wshclientapi.ts @@ -242,6 +242,11 @@ class RpcApiType { return client.wshRpcCall("remotefiletouch", data, opts); } + // command "remotegetinfo" [call] + RemoteGetInfoCommand(client: WshClient, opts?: RpcOpts): Promise { + return client.wshRpcCall("remotegetinfo", null, opts); + } + // command "remotemkdir" [call] RemoteMkdirCommand(client: WshClient, data: string, opts?: RpcOpts): Promise { return client.wshRpcCall("remotemkdir", data, opts); diff --git a/frontend/types/gotypes.d.ts b/frontend/types/gotypes.d.ts index 604eb464d1..370d63137c 100644 --- a/frontend/types/gotypes.d.ts +++ b/frontend/types/gotypes.d.ts @@ -555,7 +555,6 @@ declare global { // wshrpc.RemoteInfo type RemoteInfo = { - host: string; clientarch: string; clientos: string; clientversion: string; diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go index 6e8e8ece0d..d58bb87819 100644 --- a/pkg/wshrpc/wshclient/wshclient.go +++ b/pkg/wshrpc/wshclient/wshclient.go @@ -295,6 +295,12 @@ func RemoteFileTouchCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts return err } +// command "remotegetinfo", wshserver.RemoteGetInfoCommand +func RemoteGetInfoCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) error { + _, err := sendRpcRequestCallHelper[any](w, "remotegetinfo", nil, opts) + return err +} + // command "remotemkdir", wshserver.RemoteMkdirCommand func RemoteMkdirCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts) error { _, err := sendRpcRequestCallHelper[any](w, "remotemkdir", data, opts) From 3078c2aab090e3d3f5f3db8c3fb8d9a3fbb8d034 Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Sun, 12 Jan 2025 01:47:40 -0800 Subject: [PATCH 3/6] fix: remove home dir check The SoftQuote function makes it unnecessary to fully expand the home directory path. This replaces those cases with a ~ instead. --- pkg/remote/connutil.go | 12 ------------ pkg/shellexec/shellexec.go | 15 +++++++++------ 2 files changed, 9 insertions(+), 18 deletions(-) diff --git a/pkg/remote/connutil.go b/pkg/remote/connutil.go index 0795936069..6b1c0d85b5 100644 --- a/pkg/remote/connutil.go +++ b/pkg/remote/connutil.go @@ -241,18 +241,6 @@ func InstallClientRcFiles(client *ssh.Client) error { return err } -func GetHomeDir(client *ssh.Client) string { - session, err := client.NewSession() - if err != nil { - return "~" - } - out, err := session.Output(`echo "$HOME"`) - if err == nil { - return strings.TrimSpace(string(out)) - } - return "~" -} - func IsPowershell(shellPath string) bool { // get the base path, and then check contains shellBase := filepath.Base(shellPath) diff --git a/pkg/shellexec/shellexec.go b/pkg/shellexec/shellexec.go index 1529332461..23f2f79e71 100644 --- a/pkg/shellexec/shellexec.go +++ b/pkg/shellexec/shellexec.go @@ -19,6 +19,7 @@ import ( "time" "github.com/creack/pty" + "github.com/wavetermdev/waveterm/pkg/genconn" "github.com/wavetermdev/waveterm/pkg/panichandler" "github.com/wavetermdev/waveterm/pkg/remote" "github.com/wavetermdev/waveterm/pkg/remote/conncontroller" @@ -305,22 +306,23 @@ func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts Comm } shellOpts = append(shellOpts, cmdOpts.ShellOpts...) - homeDir := remote.GetHomeDir(client) - if cmdStr == "" { /* transform command in order to inject environment vars */ if isBashShell(shellPath) { log.Printf("recognized as bash shell") // add --rcfile // cant set -l or -i with --rcfile - shellOpts = append(shellOpts, "--rcfile", fmt.Sprintf(`"%s"/.waveterm/%s/.bashrc`, homeDir, shellutil.BashIntegrationDir)) + bashPath := genconn.SoftQuote(fmt.Sprintf("~/.waveterm/%s/.bashrc", shellutil.BashIntegrationDir)) + shellOpts = append(shellOpts, "--rcfile", bashPath) } else if isFishShell(shellPath) { - carg := fmt.Sprintf(`"set -x PATH \"%s\"/.waveterm/%s $PATH"`, homeDir, shellutil.WaveHomeBinDir) + fishDir := genconn.SoftQuote(fmt.Sprintf("~/.waveterm/%s", shellutil.WaveHomeBinDir)) + carg := fmt.Sprintf(`"set -x PATH %s $PATH"`, fishDir) shellOpts = append(shellOpts, "-C", carg) } else if remote.IsPowershell(shellPath) { + pwshPath := genconn.SoftQuote(fmt.Sprintf("~/.waveterm/%s/wavepwsh.ps1", shellutil.PwshIntegrationDir)) // powershell is weird about quoted path executables and requires an ampersand first shellPath = "& " + shellPath - shellOpts = append(shellOpts, "-ExecutionPolicy", "Bypass", "-NoExit", "-File", fmt.Sprintf("%s/.waveterm/%s/wavepwsh.ps1", homeDir, shellutil.PwshIntegrationDir)) + shellOpts = append(shellOpts, "-ExecutionPolicy", "Bypass", "-NoExit", "-File", pwshPath) } else { if cmdOpts.Login { shellOpts = append(shellOpts, "-l") @@ -374,7 +376,8 @@ func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts Comm } if isZshShell(shellPath) { - cmdCombined = fmt.Sprintf(`ZDOTDIR="%s/.waveterm/%s" %s`, homeDir, shellutil.ZshIntegrationDir, cmdCombined) + zshDir := genconn.SoftQuote(fmt.Sprintf("~/.waveterm/%s", shellutil.ZshIntegrationDir)) + cmdCombined = fmt.Sprintf(`ZDOTDIR=%s %s`, zshDir, cmdCombined) } jwtToken, ok := cmdOpts.Env[wshutil.WaveJwtTokenVarName] From ec1d2b07289bff181c880b21c6474ceac768973a Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Sun, 12 Jan 2025 01:56:08 -0800 Subject: [PATCH 4/6] fix: use correct mac shell in GetInfo --- pkg/wshutil/wshutil.go | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pkg/wshutil/wshutil.go b/pkg/wshutil/wshutil.go index 3bcbff7351..4381853df7 100644 --- a/pkg/wshutil/wshutil.go +++ b/pkg/wshutil/wshutil.go @@ -13,6 +13,7 @@ import ( "os" "os/signal" "runtime" + "strings" "sync" "sync/atomic" "syscall" @@ -22,6 +23,7 @@ import ( "github.com/google/uuid" "github.com/wavetermdev/waveterm/pkg/panichandler" "github.com/wavetermdev/waveterm/pkg/util/packetparser" + "github.com/wavetermdev/waveterm/pkg/util/shellutil" "github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/wshrpc" "golang.org/x/term" @@ -538,12 +540,24 @@ func ExtractUnverifiedSocketName(tokenStr string) (string, error) { return sockName, nil } +func getShell() string { + if runtime.GOOS == "darwin" { + return shellutil.GetMacUserShell() + "\n" + } + + shell := os.Getenv("SHELL") + if shell == "" { + return "/bin/bash\n" + } + return strings.TrimSpace(shell) + "\n" +} + func GetInfo() wshrpc.RemoteInfo { return wshrpc.RemoteInfo{ ClientArch: runtime.GOARCH, ClientOs: runtime.GOOS, ClientVersion: wavebase.WaveVersion, - Shell: os.Getenv("SHELL"), + Shell: getShell(), } } From c964a60126fe681bda761e9f28e6c75262c055ac Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Sun, 12 Jan 2025 14:45:31 -0800 Subject: [PATCH 5/6] feat: use rpc to detect shell This removes the need to create a new session for detecting the shell --- frontend/app/store/wshclientapi.ts | 2 +- pkg/remote/connutil.go | 19 ------------------- pkg/shellexec/shellexec.go | 20 +++++++++++++------- pkg/wshrpc/wshclient/wshclient.go | 6 +++--- pkg/wshrpc/wshremote/wshremote.go | 4 ++-- pkg/wshrpc/wshrpctypes.go | 2 +- 6 files changed, 20 insertions(+), 33 deletions(-) diff --git a/frontend/app/store/wshclientapi.ts b/frontend/app/store/wshclientapi.ts index d69b789b5a..e1d88d60fc 100644 --- a/frontend/app/store/wshclientapi.ts +++ b/frontend/app/store/wshclientapi.ts @@ -243,7 +243,7 @@ class RpcApiType { } // command "remotegetinfo" [call] - RemoteGetInfoCommand(client: WshClient, opts?: RpcOpts): Promise { + RemoteGetInfoCommand(client: WshClient, opts?: RpcOpts): Promise { return client.wshRpcCall("remotegetinfo", null, opts); } diff --git a/pkg/remote/connutil.go b/pkg/remote/connutil.go index 6b1c0d85b5..e5b7ff3688 100644 --- a/pkg/remote/connutil.go +++ b/pkg/remote/connutil.go @@ -35,25 +35,6 @@ func ParseOpts(input string) (*SSHOpts, error) { return &SSHOpts{SSHHost: remoteHost, SSHUser: remoteUser, SSHPort: remotePort}, nil } -func DetectShell(client *ssh.Client) (string, error) { - wshPath := GetWshPath(client) - - session, err := client.NewSession() - if err != nil { - return "", err - } - - log.Printf("shell detecting using command: %s shell", wshPath) - out, err := session.Output(wshPath + " shell") - if err != nil { - log.Printf("unable to determine shell. defaulting to /bin/bash: %s", err) - return "/bin/bash", nil - } - log.Printf("detecting shell: %s", out) - - return fmt.Sprintf(`"%s"`, strings.TrimSpace(string(out))), nil -} - func GetWshPath(client *ssh.Client) string { defaultPath := wavebase.RemoteFullWshBinPath session, err := client.NewSession() diff --git a/pkg/shellexec/shellexec.go b/pkg/shellexec/shellexec.go index 23f2f79e71..ced6443ee8 100644 --- a/pkg/shellexec/shellexec.go +++ b/pkg/shellexec/shellexec.go @@ -28,6 +28,8 @@ import ( "github.com/wavetermdev/waveterm/pkg/util/utilfn" "github.com/wavetermdev/waveterm/pkg/wavebase" "github.com/wavetermdev/waveterm/pkg/waveobj" + "github.com/wavetermdev/waveterm/pkg/wshrpc" + "github.com/wavetermdev/waveterm/pkg/wshrpc/wshclient" "github.com/wavetermdev/waveterm/pkg/wshutil" "github.com/wavetermdev/waveterm/pkg/wsl" ) @@ -287,19 +289,23 @@ func StartRemoteShellProcNoWsh(termSize waveobj.TermSize, cmdStr string, cmdOpts func StartRemoteShellProc(termSize waveobj.TermSize, cmdStr string, cmdOpts CommandOptsType, conn *conncontroller.SSHConn) (*ShellProc, error) { client := conn.GetClient() + connRoute := wshutil.MakeConnectionRouteId(conn.GetName()) + rpcClient := wshclient.GetBareRpcClient() + remoteInfo, err := wshclient.RemoteGetInfoCommand(rpcClient, &wshrpc.RpcOpts{Route: connRoute, Timeout: 2000}) + if err != nil { + return nil, fmt.Errorf("unable to obtain client info: %w", err) + } + log.Printf("client info collected: %+#v", remoteInfo) + shellPath := cmdOpts.ShellPath if shellPath == "" { - remoteShellPath, err := remote.DetectShell(client) - if err != nil { - return nil, err - } - shellPath = remoteShellPath + shellPath = remoteInfo.Shell } var shellOpts []string var cmdCombined string - log.Printf("detected shell: %s", shellPath) + log.Printf("using shell: %s", shellPath) - err := remote.InstallClientRcFiles(client) + err = remote.InstallClientRcFiles(client) if err != nil { log.Printf("error installing rc files: %v", err) return nil, err diff --git a/pkg/wshrpc/wshclient/wshclient.go b/pkg/wshrpc/wshclient/wshclient.go index d58bb87819..b21eb3d9a0 100644 --- a/pkg/wshrpc/wshclient/wshclient.go +++ b/pkg/wshrpc/wshclient/wshclient.go @@ -296,9 +296,9 @@ func RemoteFileTouchCommand(w *wshutil.WshRpc, data string, opts *wshrpc.RpcOpts } // command "remotegetinfo", wshserver.RemoteGetInfoCommand -func RemoteGetInfoCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) error { - _, err := sendRpcRequestCallHelper[any](w, "remotegetinfo", nil, opts) - return err +func RemoteGetInfoCommand(w *wshutil.WshRpc, opts *wshrpc.RpcOpts) (wshrpc.RemoteInfo, error) { + resp, err := sendRpcRequestCallHelper[wshrpc.RemoteInfo](w, "remotegetinfo", nil, opts) + return resp, err } // command "remotemkdir", wshserver.RemoteMkdirCommand diff --git a/pkg/wshrpc/wshremote/wshremote.go b/pkg/wshrpc/wshremote/wshremote.go index 9c317cbde6..09e5c1944d 100644 --- a/pkg/wshrpc/wshremote/wshremote.go +++ b/pkg/wshrpc/wshremote/wshremote.go @@ -389,6 +389,6 @@ func (*ServerImpl) RemoteFileDeleteCommand(ctx context.Context, path string) err return nil } -func (*ServerImpl) RemoteGetInfoCommand(ctx context.Context) wshrpc.RemoteInfo { - return wshutil.GetInfo() +func (*ServerImpl) RemoteGetInfoCommand(ctx context.Context) (wshrpc.RemoteInfo, error) { + return wshutil.GetInfo(), nil } diff --git a/pkg/wshrpc/wshrpctypes.go b/pkg/wshrpc/wshrpctypes.go index 0d84cb102c..ce71c92abb 100644 --- a/pkg/wshrpc/wshrpctypes.go +++ b/pkg/wshrpc/wshrpctypes.go @@ -180,7 +180,7 @@ type WshRpcInterface interface { RemoteFileJoinCommand(ctx context.Context, paths []string) (*FileInfo, error) RemoteMkdirCommand(ctx context.Context, path string) error RemoteStreamCpuDataCommand(ctx context.Context) chan RespOrErrorUnion[TimeSeriesData] - RemoteGetInfoCommand(ctx context.Context) RemoteInfo + RemoteGetInfoCommand(ctx context.Context) (RemoteInfo, error) // emain WebSelectorCommand(ctx context.Context, data CommandWebSelectorData) ([]string, error) From a36b5d46e90c8536a453ac5f75a7602f4cd2da4e Mon Sep 17 00:00:00 2001 From: Sylvia Crowe Date: Sun, 12 Jan 2025 14:57:07 -0800 Subject: [PATCH 6/6] fix: remove shell line endings for remoteinfo --- pkg/wshutil/wshutil.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/wshutil/wshutil.go b/pkg/wshutil/wshutil.go index 4381853df7..44eedf9f87 100644 --- a/pkg/wshutil/wshutil.go +++ b/pkg/wshutil/wshutil.go @@ -542,14 +542,14 @@ func ExtractUnverifiedSocketName(tokenStr string) (string, error) { func getShell() string { if runtime.GOOS == "darwin" { - return shellutil.GetMacUserShell() + "\n" + return shellutil.GetMacUserShell() } shell := os.Getenv("SHELL") if shell == "" { - return "/bin/bash\n" + return "/bin/bash" } - return strings.TrimSpace(shell) + "\n" + return strings.TrimSpace(shell) } func GetInfo() wshrpc.RemoteInfo {