Skip to content

Commit 4fd6d36

Browse files
authored
conn updates 2 (#1660)
* use pwsh over powershell if installed (on windows) for default shell * refactor blockcontroller.DoRunShellCommand into a "setup" and "manage" phase * fix wshcmd-conn to also disconnect wsl connections * new genconn interfaces to make a standardized environment to run SSH/WSL commands via `sh -c`. also create better quoting functions that are composable * replace html/template with text/template for shell command templating (avoids special chars getting turned into HTML entities, breaking the commands) * do not reinstall wsh if the installed version has a higher version (prevents flip-flopping on shared systems) * simplify clientOs/clientArch detection. use `uname -sm`. also validate the os/arch combo as compatible with our builds. * replace CpHostToRemote with CpWshToRemote. hard codes wsh paths inside of the function instead of having them passed in (quoting restrictions) * new SyncBuffer class to use with commands that properly synchronizes Writes/String output * fix setTermSize to actually update DB with terminal size
1 parent 6c53eb8 commit 4fd6d36

File tree

18 files changed

+869
-196
lines changed

18 files changed

+869
-196
lines changed

ROADMAP.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Legend: ✅ Done | 🔧 In Progress | 🔷 Planned | 🤞 Stretch Goal
2929
- 🔷 Monaco Theming
3030
- 🤞 Blockcontroller fixes for terminal escape sequences
3131
- 🤞 Explore VSCode Extension Compatibility with standalone Monaco Editor (language servers)
32-
- 🔷 Various Connection Bugs + Improvements
32+
- 🔧 Various Connection Bugs + Improvements
3333
- 🔧 More Connection Config Options
3434

3535
## Future Releases

cmd/wsh/cmd/wshcmd-conn.go

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,26 @@ func validateConnectionName(name string) error {
8787
return nil
8888
}
8989

90-
func connStatusRun(cmd *cobra.Command, args []string) error {
90+
func getAllConnStatus() ([]wshrpc.ConnStatus, error) {
9191
var allResp []wshrpc.ConnStatus
9292
sshResp, err := wshclient.ConnStatusCommand(RpcClient, nil)
9393
if err != nil {
94-
return fmt.Errorf("getting ssh connection status: %w", err)
94+
return nil, fmt.Errorf("getting ssh connection status: %w", err)
9595
}
9696
allResp = append(allResp, sshResp...)
9797
wslResp, err := wshclient.WslStatusCommand(RpcClient, nil)
9898
if err != nil {
99-
return fmt.Errorf("getting wsl connection status: %w", err)
99+
return nil, fmt.Errorf("getting wsl connection status: %w", err)
100100
}
101101
allResp = append(allResp, wslResp...)
102+
return allResp, nil
103+
}
104+
105+
func connStatusRun(cmd *cobra.Command, args []string) error {
106+
allResp, err := getAllConnStatus()
107+
if err != nil {
108+
return err
109+
}
102110
if len(allResp) == 0 {
103111
WriteStdout("no connections\n")
104112
return nil
@@ -142,21 +150,19 @@ func connDisconnectRun(cmd *cobra.Command, args []string) error {
142150
}
143151

144152
func connDisconnectAllRun(cmd *cobra.Command, args []string) error {
145-
resp, err := wshclient.ConnStatusCommand(RpcClient, nil)
153+
allConns, err := getAllConnStatus()
146154
if err != nil {
147-
return fmt.Errorf("getting connection status: %w", err)
148-
}
149-
if len(resp) == 0 {
150-
return nil
155+
return err
151156
}
152-
for _, conn := range resp {
153-
if conn.Status == "connected" {
154-
err := wshclient.ConnDisconnectCommand(RpcClient, conn.Connection, &wshrpc.RpcOpts{Timeout: 10000})
155-
if err != nil {
156-
WriteStdout("error disconnecting %q: %v\n", conn.Connection, err)
157-
} else {
158-
WriteStdout("disconnected %q\n", conn.Connection)
159-
}
157+
for _, conn := range allConns {
158+
if conn.Status != "connected" {
159+
continue
160+
}
161+
err := wshclient.ConnDisconnectCommand(RpcClient, conn.Connection, &wshrpc.RpcOpts{Timeout: 10000})
162+
if err != nil {
163+
WriteStdout("error disconnecting %q: %v\n", conn.Connection, err)
164+
} else {
165+
WriteStdout("disconnected %q\n", conn.Connection)
160166
}
161167
}
162168
return nil

frontend/app/store/keymodel.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,6 @@ function registerGlobalKeys() {
372372
return false;
373373
}
374374
globalKeyMap.set("Cmd:f", activateSearch);
375-
globalKeyMap.set("Ctrl:f", activateSearch);
376375
globalKeyMap.set("Escape", deactivateSearch);
377376
const allKeys = Array.from(globalKeyMap.keys());
378377
// special case keys, handled by web view

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ require (
6363
go.opentelemetry.io/otel/metric v1.29.0 // indirect
6464
go.opentelemetry.io/otel/trace v1.29.0 // indirect
6565
go.uber.org/atomic v1.7.0 // indirect
66+
golang.org/x/mod v0.22.0 // indirect
6667
golang.org/x/net v0.33.0 // indirect
6768
golang.org/x/oauth2 v0.24.0 // indirect
6869
golang.org/x/sync v0.10.0 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,8 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
133133
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
134134
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
135135
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
136+
golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
137+
golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
136138
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
137139
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
138140
golang.org/x/oauth2 v0.24.0 h1:KTBBxWqUa0ykRPLtV69rRto9TLXcqYkeswu48x/gvNE=

pkg/blockcontroller/blockcontroller.go

Lines changed: 52 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -273,27 +273,34 @@ func createCmdStrAndOpts(blockId string, blockMeta waveobj.MetaMapType) (string,
273273
}
274274

275275
func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj.MetaMapType) error {
276+
shellProc, err := bc.setupAndStartShellProcess(rc, blockMeta)
277+
if err != nil {
278+
return err
279+
}
280+
return bc.manageRunningShellProcess(shellProc, rc, blockMeta)
281+
}
282+
283+
func (bc *BlockController) setupAndStartShellProcess(rc *RunShellOpts, blockMeta waveobj.MetaMapType) (*shellexec.ShellProc, error) {
276284
// create a circular blockfile for the output
277285
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
278286
defer cancelFn()
279-
err := filestore.WFS.MakeFile(ctx, bc.BlockId, BlockFile_Term, nil, filestore.FileOptsType{MaxSize: DefaultTermMaxFileSize, Circular: true})
280-
if err != nil && err != fs.ErrExist {
281-
err = fs.ErrExist
282-
return fmt.Errorf("error creating blockfile: %w", err)
287+
fsErr := filestore.WFS.MakeFile(ctx, bc.BlockId, BlockFile_Term, nil, filestore.FileOptsType{MaxSize: DefaultTermMaxFileSize, Circular: true})
288+
if fsErr != nil && fsErr != fs.ErrExist {
289+
return nil, fmt.Errorf("error creating blockfile: %w", fsErr)
283290
}
284-
if err == fs.ErrExist {
291+
if fsErr == fs.ErrExist {
285292
// reset the terminal state
286293
bc.resetTerminalState()
287294
}
288-
err = nil
289295
bcInitStatus := bc.GetRuntimeStatus()
290296
if bcInitStatus.ShellProcStatus == Status_Running {
291-
return nil
297+
return nil, nil
292298
}
293299
// TODO better sync here (don't let two starts happen at the same times)
294300
remoteName := blockMeta.GetString(waveobj.MetaKey_Connection, "")
295301
var cmdStr string
296302
var cmdOpts shellexec.CommandOptsType
303+
var err error
297304
if bc.ControllerType == BlockController_Shell {
298305
cmdOpts.Env = make(map[string]string)
299306
cmdOpts.Interactive = true
@@ -302,19 +309,19 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
302309
if cmdOpts.Cwd != "" {
303310
cwdPath, err := wavebase.ExpandHomeDir(cmdOpts.Cwd)
304311
if err != nil {
305-
return err
312+
return nil, err
306313
}
307314
cmdOpts.Cwd = cwdPath
308315
}
309316
} else if bc.ControllerType == BlockController_Cmd {
310317
var cmdOptsPtr *shellexec.CommandOptsType
311318
cmdStr, cmdOptsPtr, err = createCmdStrAndOpts(bc.BlockId, blockMeta)
312319
if err != nil {
313-
return err
320+
return nil, err
314321
}
315322
cmdOpts = *cmdOptsPtr
316323
} else {
317-
return fmt.Errorf("unknown controller type %q", bc.ControllerType)
324+
return nil, fmt.Errorf("unknown controller type %q", bc.ControllerType)
318325
}
319326
var shellProc *shellexec.ShellProc
320327
if strings.HasPrefix(remoteName, "wsl://") {
@@ -325,45 +332,45 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
325332
wslConn := wsl.GetWslConn(credentialCtx, wslName, false)
326333
connStatus := wslConn.DeriveConnStatus()
327334
if connStatus.Status != conncontroller.Status_Connected {
328-
return fmt.Errorf("not connected, cannot start shellproc")
335+
return nil, fmt.Errorf("not connected, cannot start shellproc")
329336
}
330337

331338
// create jwt
332339
if !blockMeta.GetBool(waveobj.MetaKey_CmdNoWsh, false) {
333340
jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId, Conn: wslConn.GetName()}, wslConn.GetDomainSocketName())
334341
if err != nil {
335-
return fmt.Errorf("error making jwt token: %w", err)
342+
return nil, fmt.Errorf("error making jwt token: %w", err)
336343
}
337344
cmdOpts.Env[wshutil.WaveJwtTokenVarName] = jwtStr
338345
}
339346
shellProc, err = shellexec.StartWslShellProc(ctx, rc.TermSize, cmdStr, cmdOpts, wslConn)
340347
if err != nil {
341-
return err
348+
return nil, err
342349
}
343350
} else if remoteName != "" {
344351
credentialCtx, cancelFunc := context.WithTimeout(context.Background(), 60*time.Second)
345352
defer cancelFunc()
346353

347354
opts, err := remote.ParseOpts(remoteName)
348355
if err != nil {
349-
return err
356+
return nil, err
350357
}
351358
conn := conncontroller.GetConn(credentialCtx, opts, false, &wshrpc.ConnKeywords{})
352359
connStatus := conn.DeriveConnStatus()
353360
if connStatus.Status != conncontroller.Status_Connected {
354-
return fmt.Errorf("not connected, cannot start shellproc")
361+
return nil, fmt.Errorf("not connected, cannot start shellproc")
355362
}
356363
if !blockMeta.GetBool(waveobj.MetaKey_CmdNoWsh, false) {
357364
jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId, Conn: conn.Opts.String()}, conn.GetDomainSocketName())
358365
if err != nil {
359-
return fmt.Errorf("error making jwt token: %w", err)
366+
return nil, fmt.Errorf("error making jwt token: %w", err)
360367
}
361368
cmdOpts.Env[wshutil.WaveJwtTokenVarName] = jwtStr
362369
}
363370
if !conn.WshEnabled.Load() {
364371
shellProc, err = shellexec.StartRemoteShellProcNoWsh(rc.TermSize, cmdStr, cmdOpts, conn)
365372
if err != nil {
366-
return err
373+
return nil, err
367374
}
368375
} else {
369376
shellProc, err = shellexec.StartRemoteShellProc(rc.TermSize, cmdStr, cmdOpts, conn)
@@ -376,19 +383,16 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
376383
log.Print("attempting install without wsh")
377384
shellProc, err = shellexec.StartRemoteShellProcNoWsh(rc.TermSize, cmdStr, cmdOpts, conn)
378385
if err != nil {
379-
return err
386+
return nil, err
380387
}
381388
}
382389
}
383-
if err != nil {
384-
return err
385-
}
386390
} else {
387391
// local terminal
388392
if !blockMeta.GetBool(waveobj.MetaKey_CmdNoWsh, false) {
389393
jwtStr, err := wshutil.MakeClientJWTToken(wshrpc.RpcContext{TabId: bc.TabId, BlockId: bc.BlockId}, wavebase.GetDomainSocketName())
390394
if err != nil {
391-
return fmt.Errorf("error making jwt token: %w", err)
395+
return nil, fmt.Errorf("error making jwt token: %w", err)
392396
}
393397
cmdOpts.Env[wshutil.WaveJwtTokenVarName] = jwtStr
394398
}
@@ -407,14 +411,18 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
407411
}
408412
shellProc, err = shellexec.StartShellProc(rc.TermSize, cmdStr, cmdOpts)
409413
if err != nil {
410-
return err
414+
return nil, err
411415
}
412416
}
413417
bc.UpdateControllerAndSendUpdate(func() bool {
414418
bc.ShellProc = shellProc
415419
bc.ShellProcStatus = Status_Running
416420
return true
417421
})
422+
return shellProc, nil
423+
}
424+
425+
func (bc *BlockController) manageRunningShellProcess(shellProc *shellexec.ShellProc, rc *RunShellOpts, blockMeta waveobj.MetaMapType) error {
418426
shellInputCh := make(chan *BlockInputUnion, 32)
419427
bc.ShellInputCh = shellInputCh
420428

@@ -473,14 +481,7 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
473481
shellProc.Cmd.Write(ic.InputData)
474482
}
475483
if ic.TermSize != nil {
476-
err = setTermSize(ctx, bc.BlockId, *ic.TermSize)
477-
if err != nil {
478-
log.Printf("error setting pty size: %v\n", err)
479-
}
480-
err = shellProc.Cmd.SetSize(ic.TermSize.Rows, ic.TermSize.Cols)
481-
if err != nil {
482-
log.Printf("error setting pty size: %v\n", err)
483-
}
484+
updateTermSize(shellProc, bc.BlockId, *ic.TermSize)
484485
}
485486
}
486487
}()
@@ -522,6 +523,17 @@ func (bc *BlockController) DoRunShellCommand(rc *RunShellOpts, blockMeta waveobj
522523
return nil
523524
}
524525

526+
func updateTermSize(shellProc *shellexec.ShellProc, blockId string, termSize waveobj.TermSize) {
527+
err := setTermSizeInDB(blockId, termSize)
528+
if err != nil {
529+
log.Printf("error setting pty size: %v\n", err)
530+
}
531+
err = shellProc.Cmd.SetSize(termSize.Rows, termSize.Cols)
532+
if err != nil {
533+
log.Printf("error setting pty size: %v\n", err)
534+
}
535+
}
536+
525537
func checkCloseOnExit(blockId string, exitCode int) {
526538
ctx, cancelFn := context.WithTimeout(context.Background(), DefaultTimeout)
527539
defer cancelFn()
@@ -569,16 +581,22 @@ func getTermSize(bdata *waveobj.Block) waveobj.TermSize {
569581
}
570582
}
571583

572-
func setTermSize(ctx context.Context, blockId string, termSize waveobj.TermSize) error {
584+
func setTermSizeInDB(blockId string, termSize waveobj.TermSize) error {
585+
ctx, cancelFn := context.WithTimeout(context.Background(), 2*time.Second)
586+
defer cancelFn()
573587
ctx = waveobj.ContextWithUpdates(ctx)
574-
bdata, err := wstore.DBMustGet[*waveobj.Block](context.Background(), blockId)
588+
bdata, err := wstore.DBMustGet[*waveobj.Block](ctx, blockId)
575589
if err != nil {
576590
return fmt.Errorf("error getting block data: %v", err)
577591
}
578592
if bdata.RuntimeOpts == nil {
579-
return fmt.Errorf("error from nil RuntimeOpts: %v", err)
593+
bdata.RuntimeOpts = &waveobj.RuntimeOpts{}
580594
}
581595
bdata.RuntimeOpts.TermSize = termSize
596+
err = wstore.DBUpdate(ctx, bdata)
597+
if err != nil {
598+
return fmt.Errorf("error updating block data: %v", err)
599+
}
582600
updates := waveobj.ContextGetUpdatesRtn(ctx)
583601
wps.Broker.SendUpdateEvents(updates)
584602
return nil

0 commit comments

Comments
 (0)