@@ -67,7 +67,7 @@ func (r *RootCmd) ssh() *serpent.Command {
67
67
stdio bool
68
68
hostPrefix string
69
69
hostnameSuffix string
70
- forceTunnel bool
70
+ forceNewTunnel bool
71
71
forwardAgent bool
72
72
forwardGPG bool
73
73
identityAgent string
@@ -278,27 +278,38 @@ func (r *RootCmd) ssh() *serpent.Command {
278
278
return err
279
279
}
280
280
281
- // See if we can use the Coder Connect tunnel
282
- if ! forceTunnel {
281
+ // If we're in stdio mode, check to see if we can use Coder Connect.
282
+ // We don't support Coder Connect over non-stdio coder ssh yet.
283
+ if stdio && ! forceNewTunnel {
283
284
connInfo ,err := wsClient .AgentConnectionInfoGeneric (ctx )
284
285
if err != nil {
285
286
return xerrors .Errorf ("get agent connection info: %w" ,err )
286
287
}
287
-
288
288
coderConnectHost := fmt .Sprintf ("%s.%s.%s.%s" ,
289
289
workspaceAgent .Name ,workspace .Name ,workspace .OwnerName ,connInfo .HostnameSuffix )
290
290
exists ,_ := workspacesdk .ExistsViaCoderConnect (ctx ,coderConnectHost )
291
291
if exists {
292
292
_ ,_ = fmt .Fprintln (inv .Stderr ,"Connecting to workspace via Coder Connect..." )
293
293
defer cancel ()
294
- addr := fmt . Sprintf ( "%s:22" , coderConnectHost )
295
- if stdio {
294
+
295
+ if networkInfoDir != "" {
296
296
if err := writeCoderConnectNetInfo (ctx ,networkInfoDir );err != nil {
297
297
logger .Error (ctx ,"failed to write coder connect net info file" ,slog .Error (err ))
298
298
}
299
- return runCoderConnectStdio (ctx ,addr ,stdioReader ,stdioWriter ,stack )
300
299
}
301
- return runCoderConnectPTY (ctx ,addr ,inv .Stdin ,inv .Stdout ,inv .Stderr ,stack )
300
+
301
+ stopPolling := tryPollWorkspaceAutostop (ctx ,client ,workspace )
302
+ defer stopPolling ()
303
+
304
+ usageAppName := getUsageAppName (usageApp )
305
+ if usageAppName != "" {
306
+ closeUsage := client .UpdateWorkspaceUsageWithBodyContext (ctx ,workspace .ID , codersdk.PostWorkspaceUsageRequest {
307
+ AgentID :workspaceAgent .ID ,
308
+ AppName :usageAppName ,
309
+ })
310
+ defer closeUsage ()
311
+ }
312
+ return runCoderConnectStdio (ctx ,fmt .Sprintf ("%s:22" ,coderConnectHost ),stdioReader ,stdioWriter ,stack )
302
313
}
303
314
}
304
315
@@ -481,11 +492,36 @@ func (r *RootCmd) ssh() *serpent.Command {
481
492
stdinFile ,validIn := inv .Stdin .(* os.File )
482
493
stdoutFile ,validOut := inv .Stdout .(* os.File )
483
494
if validIn && validOut && isatty .IsTerminal (stdinFile .Fd ())&& isatty .IsTerminal (stdoutFile .Fd ()) {
484
- restorePtyFn ,err := configurePTY (ctx ,stdinFile ,stdoutFile ,sshSession )
485
- defer restorePtyFn ()
495
+ inState ,err := pty .MakeInputRaw (stdinFile .Fd ())
496
+ if err != nil {
497
+ return err
498
+ }
499
+ defer func () {
500
+ _ = pty .RestoreTerminal (stdinFile .Fd (),inState )
501
+ }()
502
+ outState ,err := pty .MakeOutputRaw (stdoutFile .Fd ())
486
503
if err != nil {
487
- return xerrors . Errorf ( "configure pty: %w" , err )
504
+ return err
488
505
}
506
+ defer func () {
507
+ _ = pty .RestoreTerminal (stdoutFile .Fd (),outState )
508
+ }()
509
+
510
+ windowChange := listenWindowSize (ctx )
511
+ go func () {
512
+ for {
513
+ select {
514
+ case <- ctx .Done ():
515
+ return
516
+ case <- windowChange :
517
+ }
518
+ width ,height ,err := term .GetSize (int (stdoutFile .Fd ()))
519
+ if err != nil {
520
+ continue
521
+ }
522
+ _ = sshSession .WindowChange (height ,width )
523
+ }
524
+ }()
489
525
}
490
526
491
527
for _ ,kv := range parsedEnv {
@@ -667,48 +703,14 @@ func (r *RootCmd) ssh() *serpent.Command {
667
703
{
668
704
Flag :"force-new-tunnel" ,
669
705
Description :"Force the creation of a new tunnel to the workspace, even if the Coder Connect tunnel is available." ,
670
- Value :serpent .BoolOf (& forceTunnel ),
706
+ Value :serpent .BoolOf (& forceNewTunnel ),
707
+ Hidden :true ,
671
708
},
672
709
sshDisableAutostartOption (serpent .BoolOf (& disableAutostart )),
673
710
}
674
711
return cmd
675
712
}
676
713
677
- func configurePTY (ctx context.Context ,stdinFile * os.File ,stdoutFile * os.File ,sshSession * gossh.Session ) (restoreFn func (),err error ) {
678
- inState ,err := pty .MakeInputRaw (stdinFile .Fd ())
679
- if err != nil {
680
- return restoreFn ,err
681
- }
682
- restoreFn = func () {
683
- _ = pty .RestoreTerminal (stdinFile .Fd (),inState )
684
- }
685
- outState ,err := pty .MakeOutputRaw (stdoutFile .Fd ())
686
- if err != nil {
687
- return restoreFn ,err
688
- }
689
- restoreFn = func () {
690
- _ = pty .RestoreTerminal (stdinFile .Fd (),inState )
691
- _ = pty .RestoreTerminal (stdoutFile .Fd (),outState )
692
- }
693
-
694
- windowChange := listenWindowSize (ctx )
695
- go func () {
696
- for {
697
- select {
698
- case <- ctx .Done ():
699
- return
700
- case <- windowChange :
701
- }
702
- width ,height ,err := term .GetSize (int (stdoutFile .Fd ()))
703
- if err != nil {
704
- continue
705
- }
706
- _ = sshSession .WindowChange (height ,width )
707
- }
708
- }()
709
- return restoreFn ,nil
710
- }
711
-
712
714
// findWorkspaceAndAgentByHostname parses the hostname from the commandline and finds the workspace and agent it
713
715
// corresponds to, taking into account any name prefixes or suffixes configured (e.g. myworkspace.coder, or
714
716
// vscode-coder--myusername--myworkspace).
@@ -1502,87 +1504,14 @@ func runCoderConnectStdio(ctx context.Context, addr string, stdin io.Reader, std
1502
1504
return err
1503
1505
}
1504
1506
1505
- agentssh .Bicopy (ctx ,conn ,& cliutil.StdioConn {
1507
+ agentssh .Bicopy (ctx ,conn ,& cliutil.ReaderWriterConn {
1506
1508
Reader :stdin ,
1507
1509
Writer :stdout ,
1508
1510
})
1509
1511
1510
1512
return nil
1511
1513
}
1512
1514
1513
- func runCoderConnectPTY (ctx context.Context ,addr string ,stdin io.Reader ,stdout io.Writer ,stderr io.Writer ,stack * closerStack )error {
1514
- client ,err := gossh .Dial ("tcp" ,addr ,& gossh.ClientConfig {
1515
- // We've already checked the agent's address
1516
- // is within the Coder service prefix.
1517
- // #nosec
1518
- HostKeyCallback :gossh .InsecureIgnoreHostKey (),
1519
- })
1520
- if err != nil {
1521
- return xerrors .Errorf ("dial coder connect host: %w" ,err )
1522
- }
1523
- if err := stack .push ("ssh client" ,client );err != nil {
1524
- return err
1525
- }
1526
-
1527
- session ,err := client .NewSession ()
1528
- if err != nil {
1529
- return xerrors .Errorf ("create ssh session: %w" ,err )
1530
- }
1531
- if err := stack .push ("ssh session" ,session );err != nil {
1532
- return err
1533
- }
1534
-
1535
- stdinFile ,validIn := stdin .(* os.File )
1536
- stdoutFile ,validOut := stdout .(* os.File )
1537
- if validIn && validOut && isatty .IsTerminal (stdinFile .Fd ())&& isatty .IsTerminal (stdoutFile .Fd ()) {
1538
- restorePtyFn ,err := configurePTY (ctx ,stdinFile ,stdoutFile ,session )
1539
- defer restorePtyFn ()
1540
- if err != nil {
1541
- return xerrors .Errorf ("configure pty: %w" ,err )
1542
- }
1543
- }
1544
-
1545
- session .Stdin = stdin
1546
- session .Stdout = stdout
1547
- session .Stderr = stderr
1548
-
1549
- err = session .RequestPty ("xterm-256color" ,80 ,24 , gossh.TerminalModes {})
1550
- if err != nil {
1551
- return xerrors .Errorf ("request pty: %w" ,err )
1552
- }
1553
-
1554
- err = session .Shell ()
1555
- if err != nil {
1556
- return xerrors .Errorf ("start shell: %w" ,err )
1557
- }
1558
-
1559
- if validOut {
1560
- // Set initial window size.
1561
- width ,height ,err := term .GetSize (int (stdoutFile .Fd ()))
1562
- if err == nil {
1563
- _ = session .WindowChange (height ,width )
1564
- }
1565
- }
1566
-
1567
- err = session .Wait ()
1568
- if err != nil {
1569
- if exitErr := (& gossh.ExitError {});errors .As (err ,& exitErr ) {
1570
- // Clear the error since it's not useful beyond
1571
- // reporting status.
1572
- return ExitError (exitErr .ExitStatus (),nil )
1573
- }
1574
- // If the connection drops unexpectedly, we get an
1575
- // ExitMissingError but no other error details, so try to at
1576
- // least give the user a better message
1577
- if errors .Is (err ,& gossh.ExitMissingError {}) {
1578
- return ExitError (255 ,xerrors .New ("SSH connection ended unexpectedly" ))
1579
- }
1580
- return xerrors .Errorf ("session ended: %w" ,err )
1581
- }
1582
-
1583
- return nil
1584
- }
1585
-
1586
1515
func writeCoderConnectNetInfo (ctx context.Context ,networkInfoDir string )error {
1587
1516
fs ,ok := ctx .Value ("fs" ).(afero.Fs )
1588
1517
if ! ok {