gssh-agent 1.0.3 → 1.0.4

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/bin/daemon CHANGED
Binary file
package/bin/gssh CHANGED
Binary file
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()
75
+ err = handleForwardClose(subArgs, socketPath)
72
76
  case "scp":
73
- err = handleSCP()
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,21 @@ 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
+ sudoPassword := fs.String("S", "", "sudo password")
313
+ askSudoPassword := fs.Bool("ask-sudo-pass", false, "Interactively ask for sudo password")
298
314
 
299
- flag.CommandLine.Parse(args)
315
+ fs.Parse(args)
300
316
 
301
- if flag.NArg() < 1 {
317
+ if fs.NArg() < 1 {
302
318
  return fmt.Errorf("command required")
303
319
  }
304
- command := strings.Join(flag.Args(), " ")
320
+ command := strings.Join(fs.Args(), " ")
305
321
 
306
- // If sudo password needed but not provided, prompt for it
307
322
  if *askSudoPassword && *sudoPassword == "" {
308
323
  p, err := readPassword()
309
324
  if err != nil {
@@ -312,9 +327,7 @@ func handleExec() error {
312
327
  *sudoPassword = p
313
328
  }
314
329
 
315
- // If command contains sudo and password provided, wrap the command
316
330
  if *sudoPassword != "" && strings.Contains(command, "sudo") {
317
- // Use printf to pipe password to sudo -S
318
331
  command = fmt.Sprintf("printf '%%s\\n' '%s' | %s -S", *sudoPassword, command)
319
332
  }
320
333
 
@@ -323,7 +336,7 @@ func handleExec() error {
323
336
  Command: command,
324
337
  }
325
338
 
326
- result, err := sendRequest("exec", params)
339
+ result, err := sendRequest(socketPath, "exec", params)
327
340
  if err != nil {
328
341
  return err
329
342
  }
@@ -345,8 +358,8 @@ func handleExec() error {
345
358
  return nil
346
359
  }
347
360
 
348
- func handleList() error {
349
- result, err := sendRequest("list", nil)
361
+ func handleList(socketPath string) error {
362
+ result, err := sendRequest(socketPath, "list", nil)
350
363
  if err != nil {
351
364
  return err
352
365
  }
@@ -369,19 +382,22 @@ func handleList() error {
369
382
  return nil
370
383
  }
371
384
 
372
- func handleUse() error {
373
- flag.CommandLine.Parse(flag.Args()[1:])
385
+ func handleUse(args []string, socketPath string) error {
386
+ fs := flag.NewFlagSet("use", flag.ContinueOnError)
387
+ fs.SetOutput(os.Stderr)
374
388
 
375
- if flag.NArg() < 1 {
389
+ fs.Parse(args)
390
+
391
+ if fs.NArg() < 1 {
376
392
  return fmt.Errorf("session ID required")
377
393
  }
378
- sessionIDStr := flag.Arg(0)
394
+ sessionIDStr := fs.Arg(0)
379
395
 
380
396
  params := protocol.UseParams{
381
397
  SessionID: sessionIDStr,
382
398
  }
383
399
 
384
- _, err := sendRequest("use", params)
400
+ _, err := sendRequest(socketPath, "use", params)
385
401
  if err != nil {
386
402
  return err
387
403
  }
@@ -390,13 +406,16 @@ func handleUse() error {
390
406
  return nil
391
407
  }
392
408
 
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")
409
+ func handleForward(args []string, socketPath string) error {
410
+ fs := flag.NewFlagSet("forward", flag.ContinueOnError)
411
+ fs.SetOutput(os.Stderr)
412
+
413
+ sessionID := fs.String("s", "", "Session ID")
414
+ local := fs.Int("l", 0, "Local port")
415
+ remote := fs.Int("r", 0, "Remote port")
416
+ isRemote := fs.Bool("R", false, "Remote port forward")
398
417
 
399
- flag.CommandLine.Parse(flag.Args()[1:])
418
+ fs.Parse(args)
400
419
 
401
420
  if *local == 0 || *remote == 0 {
402
421
  return fmt.Errorf("local and remote ports are required")
@@ -414,7 +433,7 @@ func handleForward() error {
414
433
  Remote: *remote,
415
434
  }
416
435
 
417
- result, err := sendRequest("forward", params)
436
+ result, err := sendRequest(socketPath, "forward", params)
418
437
  if err != nil {
419
438
  return err
420
439
  }
@@ -428,8 +447,8 @@ func handleForward() error {
428
447
  return nil
429
448
  }
430
449
 
431
- func handleForwards() error {
432
- result, err := sendRequest("forwards", nil)
450
+ func handleForwards(socketPath string) error {
451
+ result, err := sendRequest(socketPath, "forwards", nil)
433
452
  if err != nil {
434
453
  return err
435
454
  }
@@ -452,19 +471,22 @@ func handleForwards() error {
452
471
  return nil
453
472
  }
454
473
 
455
- func handleForwardClose() error {
456
- flag.CommandLine.Parse(flag.Args()[1:])
474
+ func handleForwardClose(args []string, socketPath string) error {
475
+ fs := flag.NewFlagSet("forward-close", flag.ContinueOnError)
476
+ fs.SetOutput(os.Stderr)
477
+
478
+ fs.Parse(args)
457
479
 
458
- if flag.NArg() < 1 {
480
+ if fs.NArg() < 1 {
459
481
  return fmt.Errorf("forward ID required")
460
482
  }
461
- forwardID := flag.Arg(0)
483
+ forwardID := fs.Arg(0)
462
484
 
463
485
  params := protocol.ForwardCloseParams{
464
486
  ForwardID: forwardID,
465
487
  }
466
488
 
467
- _, err := sendRequest("forward_close", params)
489
+ _, err := sendRequest(socketPath, "forward_close", params)
468
490
  if err != nil {
469
491
  return err
470
492
  }
@@ -473,19 +495,22 @@ func handleForwardClose() error {
473
495
  return nil
474
496
  }
475
497
 
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)")
498
+ func handleSCP(args []string, socketPath string) error {
499
+ fs := flag.NewFlagSet("scp", flag.ContinueOnError)
500
+ fs.SetOutput(os.Stderr)
480
501
 
481
- flag.CommandLine.Parse(flag.Args()[1:])
502
+ sessionID := fs.String("s", "", "Session ID")
503
+ isUpload := fs.Bool("put", false, "Upload mode (local->remote)")
504
+ isDownload := fs.Bool("get", false, "Download mode (remote->local)")
482
505
 
483
- if flag.NArg() < 2 {
506
+ fs.Parse(args)
507
+
508
+ if fs.NArg() < 2 {
484
509
  return fmt.Errorf("source and destination paths required")
485
510
  }
486
511
 
487
- source := flag.Arg(0)
488
- dest := flag.Arg(1)
512
+ source := fs.Arg(0)
513
+ dest := fs.Arg(1)
489
514
 
490
515
  if !*isUpload && !*isDownload {
491
516
  return fmt.Errorf("must specify -put or -get")
@@ -498,7 +523,7 @@ func handleSCP() error {
498
523
  IsUpload: *isUpload,
499
524
  }
500
525
 
501
- result, err := sendRequest("scp", params)
526
+ result, err := sendRequest(socketPath, "scp", params)
502
527
  if err != nil {
503
528
  return err
504
529
  }
@@ -517,12 +542,15 @@ func handleSCP() error {
517
542
  return nil
518
543
  }
519
544
 
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")
545
+ func handleSFTP(args []string, socketPath string) error {
546
+ fs := flag.NewFlagSet("sftp", flag.ContinueOnError)
547
+ fs.SetOutput(os.Stderr)
548
+
549
+ sessionID := fs.String("s", "", "Session ID")
550
+ command := fs.String("c", "", "SFTP command (ls, mkdir, rm)")
551
+ path := fs.String("p", ".", "Path")
524
552
 
525
- flag.CommandLine.Parse(flag.Args()[1:])
553
+ fs.Parse(args)
526
554
 
527
555
  if *command == "" {
528
556
  return fmt.Errorf("SFTP command required (-c ls|mkdir|rm)")
@@ -535,7 +563,7 @@ func handleSFTP() error {
535
563
  Command: "ls",
536
564
  Path: *path,
537
565
  }
538
- result, err := sendRequest("sftp_list", params)
566
+ result, err := sendRequest(socketPath, "sftp_list", params)
539
567
  if err != nil {
540
568
  return err
541
569
  }
@@ -555,7 +583,7 @@ func handleSFTP() error {
555
583
  Command: "mkdir",
556
584
  Path: *path,
557
585
  }
558
- _, err := sendRequest("sftp_mkdir", params)
586
+ _, err := sendRequest(socketPath, "sftp_mkdir", params)
559
587
  if err != nil {
560
588
  return err
561
589
  }
@@ -567,7 +595,7 @@ func handleSFTP() error {
567
595
  Command: "rm",
568
596
  Path: *path,
569
597
  }
570
- _, err := sendRequest("sftp_remove", params)
598
+ _, err := sendRequest(socketPath, "sftp_remove", params)
571
599
  if err != nil {
572
600
  return err
573
601
  }
@@ -585,8 +613,8 @@ func printUsage() {
585
613
 
586
614
  Usage:
587
615
  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]
616
+ gssh disconnect [session_id]
617
+ gssh reconnect [session_id]
590
618
  gssh exec [-s session_id] [-S password | --ask-sudo-pass] "command"
591
619
  gssh list
592
620
  gssh use <session_id>
@@ -621,16 +649,14 @@ Options:
621
649
  Examples:
622
650
  gssh connect -u admin -h 192.168.1.1 -P password
623
651
  gssh exec "ls -la"
652
+ gssh reconnect 549b6eff-f62c-4dae-a7e9-298815233cf4
624
653
  gssh forward -l 8080 -r 80
625
- gssh forward -l 9000 -r 3000 -R
626
654
  gssh scp -put local.txt /home/user/remote.txt
627
- gssh sftp -c ls -p /home/user
628
655
  `, version)
629
656
  }
630
657
 
631
658
  // Restore terminal on exit
632
659
  func init() {
633
- // Setup cleanup to restore terminal state on unexpected exit
634
660
  c := make(chan os.Signal, 1)
635
661
  signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
636
662
  go func() {
@@ -65,7 +65,6 @@ func needsShell(cmd string) bool {
65
65
 
66
66
  // Check for redirection
67
67
  if strings.ContainsAny(cmd, "><") {
68
- // Make sure it's not in a string context
69
68
  if strings.Contains(cmd, ">") || strings.Contains(cmd, "<") {
70
69
  return true
71
70
  }
@@ -150,6 +149,11 @@ func (m *Manager) Connect(user, host string, port int, password, keyPath string)
150
149
 
151
150
  // Disconnect closes a session
152
151
  func (m *Manager) Disconnect(sessionID string) error {
152
+ // Use default session if not specified
153
+ if sessionID == "" {
154
+ sessionID = m.defaultID
155
+ }
156
+
153
157
  m.mu.Lock()
154
158
  defer m.mu.Unlock()
155
159
 
@@ -174,6 +178,11 @@ func (m *Manager) Disconnect(sessionID string) error {
174
178
 
175
179
  // Reconnect reconnects a session
176
180
  func (m *Manager) Reconnect(sessionID string) (*protocol.Session, error) {
181
+ // Use default session if not specified
182
+ if sessionID == "" {
183
+ sessionID = m.defaultID
184
+ }
185
+
177
186
  m.mu.Lock()
178
187
  ms, ok := m.sessions[sessionID]
179
188
  m.mu.Unlock()
@@ -246,7 +255,8 @@ func (m *Manager) Exec(sessionID, command string) (*protocol.ExecResult, error)
246
255
  // 检测是否需要通过 shell 执行
247
256
  fullCmd := command
248
257
  if needsShell(command) {
249
- fullCmd = fmt.Sprintf("/bin/zsh -c %q", command)
258
+ // 使用 /bin/sh 更通用
259
+ fullCmd = fmt.Sprintf("/bin/sh -c %q", command)
250
260
  }
251
261
 
252
262
  output, err := session.CombinedOutput(fullCmd)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gssh-agent",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "SSH Session Manager for Agents - Stateless SSH client with SFTP support",
5
5
  "bin": {
6
6
  "gssh": "./bin/gssh",