Skip to content

Commit 03fd851

Browse files
committed
route shells exec/send/screen through server API instead of SSH
1 parent 2919762 commit 03fd851

2 files changed

Lines changed: 47 additions & 70 deletions

File tree

cmd/latch/main.go

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -279,35 +279,31 @@ func main() {
279279
fatal("%v", err)
280280
}
281281
case "ssh":
282-
args, nhc := shellArgs(4)
283-
if len(args) < 1 {
284-
fatal("usage: latch shells ssh [--no-host-check] <shell-id>")
282+
if len(os.Args) < 4 {
283+
fatal("usage: latch shells ssh <shell-id>")
285284
}
286-
if err := client.ShellsSSH(config.Path(), args[0], nhc); err != nil {
285+
if err := client.ShellsSSH(config.Path(), os.Args[3]); err != nil {
287286
fatal("%v", err)
288287
}
289288
case "exec":
290-
args, nhc := shellArgs(4)
291-
if len(args) < 2 {
292-
fatal("usage: latch shells exec [--no-host-check] <shell-id> <command>")
289+
if len(os.Args) < 5 {
290+
fatal("usage: latch shells exec <shell-id> <command>")
293291
}
294-
if err := client.ShellsExec(config.Path(), args[0], args[1], nhc); err != nil {
292+
if err := client.ShellsExec(config.Path(), os.Args[3], os.Args[4]); err != nil {
295293
fatal("%v", err)
296294
}
297295
case "send":
298-
args, nhc := shellArgs(4)
299-
if len(args) < 2 {
300-
fatal("usage: latch shells send [--no-host-check] <shell-id> <text>")
296+
if len(os.Args) < 5 {
297+
fatal("usage: latch shells send <shell-id> <text>")
301298
}
302-
if err := client.ShellsSend(config.Path(), args[0], args[1], nhc); err != nil {
299+
if err := client.ShellsSend(config.Path(), os.Args[3], os.Args[4]); err != nil {
303300
fatal("%v", err)
304301
}
305302
case "screen":
306-
args, nhc := shellArgs(4)
307-
if len(args) < 1 {
308-
fatal("usage: latch shells screen [--no-host-check] <shell-id>")
303+
if len(os.Args) < 4 {
304+
fatal("usage: latch shells screen <shell-id>")
309305
}
310-
if err := client.ShellsScreen(config.Path(), args[0], nhc); err != nil {
306+
if err := client.ShellsScreen(config.Path(), os.Args[3]); err != nil {
311307
fatal("%v", err)
312308
}
313309
case "key":
@@ -346,20 +342,6 @@ func main() {
346342
}
347343
}
348344

349-
// shellArgs extracts positional args and --no-host-check flag from os.Args[start:].
350-
func shellArgs(start int) ([]string, bool) {
351-
var args []string
352-
var noHostCheck bool
353-
for _, a := range os.Args[start-1:] {
354-
if a == "--no-host-check" {
355-
noHostCheck = true
356-
} else {
357-
args = append(args, a)
358-
}
359-
}
360-
return args, noHostCheck
361-
}
362-
363345
func shellsUsage() {
364346
fmt.Fprintln(os.Stderr, "usage: latch shells <command>")
365347
fmt.Fprintln(os.Stderr, "")

internal/client/shells.go

Lines changed: 35 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ func apiRequest(method, path string, body interface{}, result interface{}) error
5151
}
5252
}
5353

54-
resp, err := (&http.Client{Timeout: 30 * time.Second}).Do(req)
54+
resp, err := (&http.Client{Timeout: 120 * time.Second}).Do(req)
5555
if err != nil {
5656
return fmt.Errorf("server unreachable: %w", err)
5757
}
@@ -204,19 +204,8 @@ func ShellsRestart(cfgPath, nameOrID string) error {
204204
return nil
205205
}
206206

207-
// shellSSH builds an SSH command for connecting to a shell through the relay.
208-
func shellSSH(host string, noHostCheck bool, args ...string) *exec.Cmd {
209-
sshArgs := []string{}
210-
if noHostCheck {
211-
sshArgs = append(sshArgs, "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null")
212-
}
213-
sshArgs = append(sshArgs, "-J", "relay.unixshells.com", "default@"+host)
214-
sshArgs = append(sshArgs, args...)
215-
return exec.Command("ssh", sshArgs...)
216-
}
217-
218207
// ShellsSSH connects to a shell via SSH through the relay.
219-
func ShellsSSH(cfgPath, nameOrID string, noHostCheck bool) error {
208+
func ShellsSSH(cfgPath, nameOrID string) error {
220209
shellID, cfg, err := resolveShellID(cfgPath, nameOrID)
221210
if err != nil {
222211
return err
@@ -225,60 +214,66 @@ func ShellsSSH(cfgPath, nameOrID string, noHostCheck bool) error {
225214
device := "shell-" + shellID
226215
host := device + "." + cfg.RelayUser + ".unixshells.com"
227216

228-
cmd := shellSSH(host, noHostCheck)
217+
cmd := exec.Command("ssh", "-J", "relay.unixshells.com", "default@"+host)
229218
cmd.Stdin = os.Stdin
230219
cmd.Stdout = os.Stdout
231220
cmd.Stderr = os.Stderr
232221
return cmd.Run()
233222
}
234223

235-
// ShellsExec runs a command on a shell via SSH through the relay.
236-
func ShellsExec(cfgPath, nameOrID, command string, noHostCheck bool) error {
224+
// ShellsExec runs a command on a shell via the server API.
225+
func ShellsExec(cfgPath, nameOrID, command string) error {
237226
shellID, cfg, err := resolveShellID(cfgPath, nameOrID)
238227
if err != nil {
239228
return err
240229
}
241230

242-
device := "shell-" + shellID
243-
host := device + "." + cfg.RelayUser + ".unixshells.com"
231+
var result struct {
232+
Output string `json:"output"`
233+
Error string `json:"error"`
234+
}
235+
if err := apiRequest("POST", "/api/shells/"+shellID+"/exec?username="+cfg.RelayUser, map[string]string{
236+
"command": command,
237+
}, &result); err != nil {
238+
return err
239+
}
244240

245-
cmd := shellSSH(host, noHostCheck, command)
246-
cmd.Stdin = os.Stdin
247-
cmd.Stdout = os.Stdout
248-
cmd.Stderr = os.Stderr
249-
return cmd.Run()
241+
fmt.Print(result.Output)
242+
if result.Error != "" {
243+
return fmt.Errorf("%s", result.Error)
244+
}
245+
return nil
250246
}
251247

252-
// ShellsSend injects text into a session on a shell via SSH through the relay.
253-
func ShellsSend(cfgPath, nameOrID, text string, noHostCheck bool) error {
248+
// ShellsSend injects text into a session on a shell via the server API.
249+
func ShellsSend(cfgPath, nameOrID, text string) error {
254250
shellID, cfg, err := resolveShellID(cfgPath, nameOrID)
255251
if err != nil {
256252
return err
257253
}
258254

259-
device := "shell-" + shellID
260-
host := device + "." + cfg.RelayUser + ".unixshells.com"
261-
262-
cmd := shellSSH(host, noHostCheck, "latch", "send", "default", text)
263-
cmd.Stdout = os.Stdout
264-
cmd.Stderr = os.Stderr
265-
return cmd.Run()
255+
return apiRequest("POST", "/api/shells/"+shellID+"/send?username="+cfg.RelayUser, map[string]string{
256+
"session": "default",
257+
"text": text,
258+
}, nil)
266259
}
267260

268-
// ShellsScreen reads the terminal screen from a shell via SSH through the relay.
269-
func ShellsScreen(cfgPath, nameOrID string, noHostCheck bool) error {
261+
// ShellsScreen reads the terminal screen from a shell via the server API.
262+
func ShellsScreen(cfgPath, nameOrID string) error {
270263
shellID, cfg, err := resolveShellID(cfgPath, nameOrID)
271264
if err != nil {
272265
return err
273266
}
274267

275-
device := "shell-" + shellID
276-
host := device + "." + cfg.RelayUser + ".unixshells.com"
268+
var result struct {
269+
Screen string `json:"screen"`
270+
}
271+
if err := apiRequest("GET", "/api/shells/"+shellID+"/screen?username="+cfg.RelayUser, nil, &result); err != nil {
272+
return err
273+
}
277274

278-
cmd := shellSSH(host, noHostCheck, "latch", "screen")
279-
cmd.Stdout = os.Stdout
280-
cmd.Stderr = os.Stderr
281-
return cmd.Run()
275+
fmt.Print(result.Screen)
276+
return nil
282277
}
283278

284279
// ShellsKeyAdd adds an SSH key to a shell (sends verification email).

0 commit comments

Comments
 (0)