gssh-agent 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/cmd/gssh/main.go CHANGED
@@ -21,58 +21,62 @@ const (
21
21
  version = "0.1.0"
22
22
  )
23
23
 
24
- var (
25
- socketPath = flag.String("socket", defaultSocketPath, "Unix socket path")
26
- )
27
-
28
24
  func main() {
29
- // Check for version flag before parsing
25
+ // Check for version flag before any parsing
30
26
  if len(os.Args) > 1 && (os.Args[1] == "-v" || os.Args[1] == "--version") {
31
27
  fmt.Printf("gssh version %s\n", version)
32
28
  os.Exit(0)
33
29
  }
34
30
 
35
- // Check for help flag before parsing
36
- for _, arg := range os.Args[1:] {
37
- if arg == "-h" || arg == "--help" {
38
- printUsage()
39
- os.Exit(0)
40
- }
31
+ // Check for help flag
32
+ if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
33
+ printUsage()
34
+ os.Exit(0)
41
35
  }
42
36
 
43
- flag.Parse()
37
+ // Parse global -socket flag manually
38
+ socketPath := defaultSocketPath
39
+ args := os.Args[1:]
40
+ for i := 0; i < len(args); i++ {
41
+ if args[i] == "-socket" && i+1 < len(args) {
42
+ socketPath = args[i+1]
43
+ args = append(args[:i], args[i+2:]...)
44
+ i--
45
+ }
46
+ }
44
47
 
45
- if flag.NArg() < 1 {
48
+ if len(args) < 1 {
46
49
  printUsage()
47
50
  os.Exit(1)
48
51
  }
49
52
 
50
- cmd := flag.Arg(0)
53
+ cmd := args[0]
54
+ subArgs := args[1:]
51
55
 
52
56
  var err error
53
57
  switch cmd {
54
58
  case "connect":
55
- err = handleConnect()
59
+ err = handleConnect(subArgs, socketPath)
56
60
  case "disconnect":
57
- err = handleDisconnect()
61
+ err = handleDisconnect(subArgs, socketPath)
58
62
  case "reconnect":
59
- err = handleReconnect()
63
+ err = handleReconnect(subArgs, socketPath)
60
64
  case "exec":
61
- err = handleExec()
65
+ err = handleExec(subArgs, socketPath)
62
66
  case "list", "ls":
63
- err = handleList()
67
+ err = handleList(socketPath)
64
68
  case "use":
65
- err = handleUse()
69
+ err = handleUse(subArgs, socketPath)
66
70
  case "forward":
67
- err = handleForward()
71
+ err = handleForward(subArgs, socketPath)
68
72
  case "forwards":
69
- err = handleForwards()
73
+ err = handleForwards(socketPath)
70
74
  case "forward-close":
71
- err = handleForwardClose()
72
- case "scp":
73
- err = handleSCP()
75
+ err = handleForwardClose(subArgs, socketPath)
76
+ case "scp", "sync":
77
+ err = handleSCP(subArgs, socketPath)
74
78
  case "sftp":
75
- err = handleSFTP()
79
+ err = handleSFTP(subArgs, socketPath)
76
80
  case "help":
77
81
  printUsage()
78
82
  default:
@@ -89,9 +93,7 @@ func main() {
89
93
 
90
94
  // readPassword reads password from terminal without echo
91
95
  func readPassword() (string, error) {
92
- // Check if stdin is a terminal
93
96
  if !term.IsTerminal(int(os.Stdin.Fd())) {
94
- // Try to read from stdin
95
97
  reader := bufio.NewReader(os.Stdin)
96
98
  fmt.Print("Password: ")
97
99
  password, err := reader.ReadString('\n')
@@ -131,8 +133,8 @@ func readPassphrase() (string, error) {
131
133
  return string(bytePassphrase), nil
132
134
  }
133
135
 
134
- func sendRequest(method string, params interface{}) ([]byte, error) {
135
- conn, err := net.Dial("unix", *socketPath)
136
+ func sendRequest(socketPath, method string, params interface{}) ([]byte, error) {
137
+ conn, err := net.Dial("unix", socketPath)
136
138
  if err != nil {
137
139
  return nil, fmt.Errorf("failed to connect to daemon: %w", err)
138
140
  }
@@ -155,7 +157,6 @@ func sendRequest(method string, params interface{}) ([]byte, error) {
155
157
  return nil, fmt.Errorf("failed to marshal request: %w", err)
156
158
  }
157
159
 
158
- // Add newline to signal end of request
159
160
  reqData = append(reqData, '\n')
160
161
 
161
162
  _, err = conn.Write(reqData)
@@ -163,14 +164,12 @@ func sendRequest(method string, params interface{}) ([]byte, error) {
163
164
  return nil, fmt.Errorf("failed to send request: %w", err)
164
165
  }
165
166
 
166
- // Read line by line
167
167
  reader := bufio.NewReader(conn)
168
168
  line, err := reader.ReadBytes('\n')
169
169
  if err != nil {
170
170
  return nil, fmt.Errorf("failed to read response: %w", err)
171
171
  }
172
172
 
173
- // Remove trailing newline
174
173
  line = line[:len(line)-1]
175
174
 
176
175
  var resp protocol.Response
@@ -190,22 +189,24 @@ func sendRequest(method string, params interface{}) ([]byte, error) {
190
189
  return result, nil
191
190
  }
192
191
 
193
- func handleConnect() error {
194
- user := flag.String("u", "", "Username")
195
- host := flag.String("h", "", "Host")
196
- port := flag.Int("p", 22, "Port")
197
- password := flag.String("P", "", "Password")
198
- keyPath := flag.String("i", "", "SSH key path")
199
- askPassword := flag.Bool("ask-pass", false, "Ask for password interactively")
200
- askPassphrase := flag.Bool("ask-passphrase", false, "Ask for key passphrase interactively")
192
+ func handleConnect(args []string, socketPath string) error {
193
+ fs := flag.NewFlagSet("connect", flag.ContinueOnError)
194
+ fs.SetOutput(os.Stderr)
195
+
196
+ user := fs.String("u", "", "Username")
197
+ host := fs.String("h", "", "Host")
198
+ port := fs.Int("p", 22, "Port")
199
+ password := fs.String("P", "", "Password")
200
+ keyPath := fs.String("i", "", "SSH key path")
201
+ askPassword := fs.Bool("ask-pass", false, "Ask for password interactively")
202
+ askPassphrase := fs.Bool("ask-passphrase", false, "Ask for key passphrase interactively")
201
203
 
202
- flag.CommandLine.Parse(flag.Args()[1:])
204
+ fs.Parse(args)
203
205
 
204
206
  if *user == "" || *host == "" {
205
207
  return fmt.Errorf("user and host are required")
206
208
  }
207
209
 
208
- // If no password provided via flag and -ask-pass is set, read interactively
209
210
  if *password == "" && *askPassword {
210
211
  p, err := readPassword()
211
212
  if err != nil {
@@ -214,14 +215,11 @@ func handleConnect() error {
214
215
  *password = p
215
216
  }
216
217
 
217
- // If key path provided and -ask-passphrase is set, read interactively
218
218
  if *keyPath != "" && *askPassphrase {
219
219
  passphrase, err := readPassphrase()
220
220
  if err != nil {
221
221
  return fmt.Errorf("failed to read passphrase: %w", err)
222
222
  }
223
- // Note: Passphrase support would require modifying the SSH client
224
- // For now, we'll just warn that it's not supported
225
223
  if passphrase != "" {
226
224
  fmt.Println("Note: Passphrase for keys is not yet supported, ignoring")
227
225
  }
@@ -235,7 +233,7 @@ func handleConnect() error {
235
233
  KeyPath: *keyPath,
236
234
  }
237
235
 
238
- result, err := sendRequest("connect", params)
236
+ result, err := sendRequest(socketPath, "connect", params)
239
237
  if err != nil {
240
238
  return err
241
239
  }
@@ -249,15 +247,24 @@ func handleConnect() error {
249
247
  return nil
250
248
  }
251
249
 
252
- func handleDisconnect() error {
253
- sessionID := flag.String("s", "", "Session ID")
254
- flag.CommandLine.Parse(flag.Args()[1:])
250
+ func handleDisconnect(args []string, socketPath string) error {
251
+ fs := flag.NewFlagSet("disconnect", flag.ContinueOnError)
252
+ fs.SetOutput(os.Stderr)
253
+
254
+ sessionID := fs.String("s", "", "Session ID")
255
+ fs.Parse(args)
256
+
257
+ // Support positional argument
258
+ sessionIDStr := *sessionID
259
+ if sessionIDStr == "" && fs.NArg() > 0 {
260
+ sessionIDStr = fs.Arg(0)
261
+ }
255
262
 
256
263
  params := protocol.DisconnectParams{
257
- SessionID: *sessionID,
264
+ SessionID: sessionIDStr,
258
265
  }
259
266
 
260
- _, err := sendRequest("disconnect", params)
267
+ _, err := sendRequest(socketPath, "disconnect", params)
261
268
  if err != nil {
262
269
  return err
263
270
  }
@@ -266,15 +273,24 @@ func handleDisconnect() error {
266
273
  return nil
267
274
  }
268
275
 
269
- func handleReconnect() error {
270
- sessionID := flag.String("s", "", "Session ID")
271
- flag.CommandLine.Parse(flag.Args()[1:])
276
+ func handleReconnect(args []string, socketPath string) error {
277
+ fs := flag.NewFlagSet("reconnect", flag.ContinueOnError)
278
+ fs.SetOutput(os.Stderr)
279
+
280
+ sessionID := fs.String("s", "", "Session ID")
281
+ fs.Parse(args)
282
+
283
+ // Support positional argument
284
+ sessionIDStr := *sessionID
285
+ if sessionIDStr == "" && fs.NArg() > 0 {
286
+ sessionIDStr = fs.Arg(0)
287
+ }
272
288
 
273
289
  params := protocol.ReconnectParams{
274
- SessionID: *sessionID,
290
+ SessionID: sessionIDStr,
275
291
  }
276
292
 
277
- result, err := sendRequest("reconnect", params)
293
+ result, err := sendRequest(socketPath, "reconnect", params)
278
294
  if err != nil {
279
295
  return err
280
296
  }
@@ -288,22 +304,22 @@ func handleReconnect() error {
288
304
  return nil
289
305
  }
290
306
 
291
- func handleExec() error {
292
- // Get remaining args after "exec"
293
- args := flag.Args()[1:]
307
+ func handleExec(args []string, socketPath string) error {
308
+ fs := flag.NewFlagSet("exec", flag.ContinueOnError)
309
+ fs.SetOutput(os.Stderr)
294
310
 
295
- sessionID := flag.String("s", "", "Session ID")
296
- sudoPassword := flag.String("S", "", "sudo password (will prompt if empty and -s flag used)")
297
- askSudoPassword := flag.Bool("ask-sudo-pass", false, "Interactively ask for sudo password")
311
+ sessionID := fs.String("s", "", "Session ID")
312
+ timeout := fs.Int("t", 0, "Timeout in seconds (0 = no timeout)")
313
+ sudoPassword := fs.String("S", "", "sudo password")
314
+ askSudoPassword := fs.Bool("ask-sudo-pass", false, "Interactively ask for sudo password")
298
315
 
299
- flag.CommandLine.Parse(args)
316
+ fs.Parse(args)
300
317
 
301
- if flag.NArg() < 1 {
318
+ if fs.NArg() < 1 {
302
319
  return fmt.Errorf("command required")
303
320
  }
304
- command := strings.Join(flag.Args(), " ")
321
+ command := strings.Join(fs.Args(), " ")
305
322
 
306
- // If sudo password needed but not provided, prompt for it
307
323
  if *askSudoPassword && *sudoPassword == "" {
308
324
  p, err := readPassword()
309
325
  if err != nil {
@@ -312,18 +328,17 @@ func handleExec() error {
312
328
  *sudoPassword = p
313
329
  }
314
330
 
315
- // If command contains sudo and password provided, wrap the command
316
331
  if *sudoPassword != "" && strings.Contains(command, "sudo") {
317
- // Use printf to pipe password to sudo -S
318
332
  command = fmt.Sprintf("printf '%%s\\n' '%s' | %s -S", *sudoPassword, command)
319
333
  }
320
334
 
321
335
  params := protocol.ExecParams{
322
336
  SessionID: *sessionID,
323
337
  Command: command,
338
+ Timeout: *timeout,
324
339
  }
325
340
 
326
- result, err := sendRequest("exec", params)
341
+ result, err := sendRequest(socketPath, "exec", params)
327
342
  if err != nil {
328
343
  return err
329
344
  }
@@ -345,8 +360,8 @@ func handleExec() error {
345
360
  return nil
346
361
  }
347
362
 
348
- func handleList() error {
349
- result, err := sendRequest("list", nil)
363
+ func handleList(socketPath string) error {
364
+ result, err := sendRequest(socketPath, "list", nil)
350
365
  if err != nil {
351
366
  return err
352
367
  }
@@ -369,19 +384,22 @@ func handleList() error {
369
384
  return nil
370
385
  }
371
386
 
372
- func handleUse() error {
373
- flag.CommandLine.Parse(flag.Args()[1:])
387
+ func handleUse(args []string, socketPath string) error {
388
+ fs := flag.NewFlagSet("use", flag.ContinueOnError)
389
+ fs.SetOutput(os.Stderr)
374
390
 
375
- if flag.NArg() < 1 {
391
+ fs.Parse(args)
392
+
393
+ if fs.NArg() < 1 {
376
394
  return fmt.Errorf("session ID required")
377
395
  }
378
- sessionIDStr := flag.Arg(0)
396
+ sessionIDStr := fs.Arg(0)
379
397
 
380
398
  params := protocol.UseParams{
381
399
  SessionID: sessionIDStr,
382
400
  }
383
401
 
384
- _, err := sendRequest("use", params)
402
+ _, err := sendRequest(socketPath, "use", params)
385
403
  if err != nil {
386
404
  return err
387
405
  }
@@ -390,13 +408,16 @@ func handleUse() error {
390
408
  return nil
391
409
  }
392
410
 
393
- func handleForward() error {
394
- sessionID := flag.String("s", "", "Session ID")
395
- local := flag.Int("l", 0, "Local port")
396
- remote := flag.Int("r", 0, "Remote port")
397
- isRemote := flag.Bool("R", false, "Remote port forward")
411
+ func handleForward(args []string, socketPath string) error {
412
+ fs := flag.NewFlagSet("forward", flag.ContinueOnError)
413
+ fs.SetOutput(os.Stderr)
414
+
415
+ sessionID := fs.String("s", "", "Session ID")
416
+ local := fs.Int("l", 0, "Local port")
417
+ remote := fs.Int("r", 0, "Remote port")
418
+ isRemote := fs.Bool("R", false, "Remote port forward")
398
419
 
399
- flag.CommandLine.Parse(flag.Args()[1:])
420
+ fs.Parse(args)
400
421
 
401
422
  if *local == 0 || *remote == 0 {
402
423
  return fmt.Errorf("local and remote ports are required")
@@ -414,7 +435,7 @@ func handleForward() error {
414
435
  Remote: *remote,
415
436
  }
416
437
 
417
- result, err := sendRequest("forward", params)
438
+ result, err := sendRequest(socketPath, "forward", params)
418
439
  if err != nil {
419
440
  return err
420
441
  }
@@ -428,8 +449,8 @@ func handleForward() error {
428
449
  return nil
429
450
  }
430
451
 
431
- func handleForwards() error {
432
- result, err := sendRequest("forwards", nil)
452
+ func handleForwards(socketPath string) error {
453
+ result, err := sendRequest(socketPath, "forwards", nil)
433
454
  if err != nil {
434
455
  return err
435
456
  }
@@ -452,19 +473,22 @@ func handleForwards() error {
452
473
  return nil
453
474
  }
454
475
 
455
- func handleForwardClose() error {
456
- flag.CommandLine.Parse(flag.Args()[1:])
476
+ func handleForwardClose(args []string, socketPath string) error {
477
+ fs := flag.NewFlagSet("forward-close", flag.ContinueOnError)
478
+ fs.SetOutput(os.Stderr)
479
+
480
+ fs.Parse(args)
457
481
 
458
- if flag.NArg() < 1 {
482
+ if fs.NArg() < 1 {
459
483
  return fmt.Errorf("forward ID required")
460
484
  }
461
- forwardID := flag.Arg(0)
485
+ forwardID := fs.Arg(0)
462
486
 
463
487
  params := protocol.ForwardCloseParams{
464
488
  ForwardID: forwardID,
465
489
  }
466
490
 
467
- _, err := sendRequest("forward_close", params)
491
+ _, err := sendRequest(socketPath, "forward_close", params)
468
492
  if err != nil {
469
493
  return err
470
494
  }
@@ -473,19 +497,22 @@ func handleForwardClose() error {
473
497
  return nil
474
498
  }
475
499
 
476
- func handleSCP() error {
477
- sessionID := flag.String("s", "", "Session ID")
478
- isUpload := flag.Bool("put", false, "Upload mode (local->remote)")
479
- isDownload := flag.Bool("get", false, "Download mode (remote->local)")
500
+ func handleSCP(args []string, socketPath string) error {
501
+ fs := flag.NewFlagSet("scp", flag.ContinueOnError)
502
+ fs.SetOutput(os.Stderr)
480
503
 
481
- flag.CommandLine.Parse(flag.Args()[1:])
504
+ sessionID := fs.String("s", "", "Session ID")
505
+ isUpload := fs.Bool("put", false, "Upload mode (local->remote)")
506
+ isDownload := fs.Bool("get", false, "Download mode (remote->local)")
482
507
 
483
- if flag.NArg() < 2 {
508
+ fs.Parse(args)
509
+
510
+ if fs.NArg() < 2 {
484
511
  return fmt.Errorf("source and destination paths required")
485
512
  }
486
513
 
487
- source := flag.Arg(0)
488
- dest := flag.Arg(1)
514
+ source := fs.Arg(0)
515
+ dest := fs.Arg(1)
489
516
 
490
517
  if !*isUpload && !*isDownload {
491
518
  return fmt.Errorf("must specify -put or -get")
@@ -498,7 +525,7 @@ func handleSCP() error {
498
525
  IsUpload: *isUpload,
499
526
  }
500
527
 
501
- result, err := sendRequest("scp", params)
528
+ result, err := sendRequest(socketPath, "scp", params)
502
529
  if err != nil {
503
530
  return err
504
531
  }
@@ -517,12 +544,15 @@ func handleSCP() error {
517
544
  return nil
518
545
  }
519
546
 
520
- func handleSFTP() error {
521
- sessionID := flag.String("s", "", "Session ID")
522
- command := flag.String("c", "", "SFTP command (ls, mkdir, rm)")
523
- path := flag.String("p", ".", "Path")
547
+ func handleSFTP(args []string, socketPath string) error {
548
+ fs := flag.NewFlagSet("sftp", flag.ContinueOnError)
549
+ fs.SetOutput(os.Stderr)
550
+
551
+ sessionID := fs.String("s", "", "Session ID")
552
+ command := fs.String("c", "", "SFTP command (ls, mkdir, rm)")
553
+ path := fs.String("p", ".", "Path")
524
554
 
525
- flag.CommandLine.Parse(flag.Args()[1:])
555
+ fs.Parse(args)
526
556
 
527
557
  if *command == "" {
528
558
  return fmt.Errorf("SFTP command required (-c ls|mkdir|rm)")
@@ -535,7 +565,7 @@ func handleSFTP() error {
535
565
  Command: "ls",
536
566
  Path: *path,
537
567
  }
538
- result, err := sendRequest("sftp_list", params)
568
+ result, err := sendRequest(socketPath, "sftp_list", params)
539
569
  if err != nil {
540
570
  return err
541
571
  }
@@ -555,7 +585,7 @@ func handleSFTP() error {
555
585
  Command: "mkdir",
556
586
  Path: *path,
557
587
  }
558
- _, err := sendRequest("sftp_mkdir", params)
588
+ _, err := sendRequest(socketPath, "sftp_mkdir", params)
559
589
  if err != nil {
560
590
  return err
561
591
  }
@@ -567,7 +597,7 @@ func handleSFTP() error {
567
597
  Command: "rm",
568
598
  Path: *path,
569
599
  }
570
- _, err := sendRequest("sftp_remove", params)
600
+ _, err := sendRequest(socketPath, "sftp_remove", params)
571
601
  if err != nil {
572
602
  return err
573
603
  }
@@ -585,8 +615,8 @@ func printUsage() {
585
615
 
586
616
  Usage:
587
617
  gssh connect -u user -h host [-p port] [-i key_path] [-P password] [--ask-pass]
588
- gssh disconnect [-s session_id]
589
- gssh reconnect [-s session_id]
618
+ gssh disconnect [session_id]
619
+ gssh reconnect [session_id]
590
620
  gssh exec [-s session_id] [-S password | --ask-sudo-pass] "command"
591
621
  gssh list
592
622
  gssh use <session_id>
@@ -595,6 +625,8 @@ Usage:
595
625
  gssh forward-close <forward_id>
596
626
  gssh scp [-s session_id] -put <local> <remote>
597
627
  gssh scp [-s session_id] -get <remote> <local>
628
+ gssh sync [-s session_id] -put <local> <remote>
629
+ gssh sync [-s session_id] -get <remote> <local>
598
630
  gssh sftp [-s session_id] -c <ls|mkdir|rm> -p <path>
599
631
  gssh -v, --version
600
632
 
@@ -621,16 +653,15 @@ Options:
621
653
  Examples:
622
654
  gssh connect -u admin -h 192.168.1.1 -P password
623
655
  gssh exec "ls -la"
656
+ gssh reconnect 549b6eff-f62c-4dae-a7e9-298815233cf4
624
657
  gssh forward -l 8080 -r 80
625
- gssh forward -l 9000 -r 3000 -R
626
658
  gssh scp -put local.txt /home/user/remote.txt
627
- gssh sftp -c ls -p /home/user
659
+ gssh sync -put local_dir /home/user/remote_dir
628
660
  `, version)
629
661
  }
630
662
 
631
663
  // Restore terminal on exit
632
664
  func init() {
633
- // Setup cleanup to restore terminal state on unexpected exit
634
665
  c := make(chan os.Signal, 1)
635
666
  signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
636
667
  go func() {
@@ -0,0 +1,79 @@
1
+ --- internal/session/manager.go
2
+ +++ internal/session/manager.go
3
+ @@ -100,16 +100,24 @@
4
+ // Check if session already exists
5
+ for _, s := range m.sessions {
6
+ if s.Host == host && s.User == user && s.Port == port {
7
+ - if s.Status == "connected" {
8
+ + s.mu.RLock()
9
+ + status := s.Status
10
+ + s.mu.RUnlock()
11
+ +
12
+ + if status == "connected" {
13
+ return toProtocolSession(s), fmt.Errorf("session already exists")
14
+ }
15
+ // Try to reconnect
16
+ sshClient, err := client.Connect(user, host, port, password, keyPath)
17
+ if err != nil {
18
+ return nil, err
19
+ }
20
+ +
21
+ + s.mu.Lock()
22
+ s.SSHClient = sshClient
23
+ s.Status = "connected"
24
+ + s.mu.Unlock()
25
+ +
26
+ return toProtocolSession(s), nil
27
+ }
28
+ }
29
+ @@ -159,10 +167,12 @@
30
+ return fmt.Errorf("session not found")
31
+ }
32
+
33
+ + ms.mu.Lock()
34
+ if ms.SSHClient != nil {
35
+ ms.SSHClient.Close()
36
+ }
37
+ -
38
+ ms.Status = "disconnected"
39
+ + ms.mu.Unlock()
40
+
41
+ // Clear default ID when disconnecting
42
+ if m.defaultID == sessionID {
43
+ @@ -188,10 +198,14 @@
44
+ return nil, fmt.Errorf("session not found")
45
+ }
46
+
47
+ + ms.mu.RLock()
48
+ + existingClient := ms.SSHClient
49
+ + ms.mu.RUnlock()
50
+ +
51
+ // Close existing connection
52
+ - if ms.SSHClient != nil {
53
+ - ms.SSHClient.Close()
54
+ + if existingClient != nil {
55
+ + existingClient.Close()
56
+ }
57
+
58
+ // Create new connection
59
+ sshClient, err := client.Connect(ms.User, ms.Host, ms.Port, ms.Password, ms.KeyPath)
60
+ @@ -239,15 +253,16 @@
61
+ }
62
+
63
+ ms.mu.RLock()
64
+ - if ms.SSHClient == nil {
65
+ + sshClient := ms.SSHClient
66
+ + ms.mu.RUnlock()
67
+ +
68
+ + if sshClient == nil {
69
+ - ms.mu.RUnlock()
70
+ return nil, fmt.Errorf("session not connected")
71
+ }
72
+ - ms.mu.RUnlock()
73
+
74
+ // 复用 SSH 连接,创建新的 session 执行命令
75
+ - session, err := ms.SSHClient.Client.NewSession()
76
+ + session, err := sshClient.Client.NewSession()
77
+ if err != nil {
78
+ return nil, fmt.Errorf("failed to create session: %w", err)
79
+ }