gssh-agent 1.0.0
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/README.md +175 -0
- package/bin/daemon +0 -0
- package/bin/gssh +0 -0
- package/bin/gssh-daemon +0 -0
- package/cmd/daemon/main.go +45 -0
- package/cmd/gssh/main.go +394 -0
- package/go.mod +10 -0
- package/go.sum +8 -0
- package/gssh-darwin-arm64.tar.gz +0 -0
- package/homebrew/gssh.plist +22 -0
- package/homebrew/install.sh +43 -0
- package/idea.md +271 -0
- package/internal/client/ssh.go +143 -0
- package/internal/client/ssh_test.go +33 -0
- package/internal/portforward/forwarder.go +187 -0
- package/internal/protocol/types.go +90 -0
- package/internal/protocol/types_test.go +87 -0
- package/internal/session/manager.go +380 -0
- package/internal/session/manager_test.go +52 -0
- package/package.json +25 -0
- package/pkg/rpc/handler.go +206 -0
- package/skill.md +96 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
package rpc
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"encoding/json"
|
|
5
|
+
"fmt"
|
|
6
|
+
"io"
|
|
7
|
+
"log"
|
|
8
|
+
"net"
|
|
9
|
+
"net/http"
|
|
10
|
+
"os"
|
|
11
|
+
|
|
12
|
+
"gssh/internal/protocol"
|
|
13
|
+
"gssh/internal/session"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
// Handler handles JSON-RPC requests
|
|
17
|
+
type Handler struct {
|
|
18
|
+
manager *session.Manager
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// NewHandler creates a new RPC handler
|
|
22
|
+
func NewHandler(manager *session.Manager) *Handler {
|
|
23
|
+
return &Handler{manager: manager}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Handle processes a JSON-RPC request
|
|
27
|
+
func (h *Handler) Handle(data []byte) ([]byte, error) {
|
|
28
|
+
var req protocol.Request
|
|
29
|
+
if err := json.Unmarshal(data, &req); err != nil {
|
|
30
|
+
return h.errorResponse(nil, -32700, "Parse error")
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var result interface{}
|
|
34
|
+
var err error
|
|
35
|
+
|
|
36
|
+
switch req.Method {
|
|
37
|
+
case "connect":
|
|
38
|
+
var params protocol.ConnectParams
|
|
39
|
+
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
|
40
|
+
return h.errorResponse(req.ID, -32602, "Invalid params")
|
|
41
|
+
}
|
|
42
|
+
result, err = h.manager.Connect(params.User, params.Host, params.Port, params.Password, params.KeyPath)
|
|
43
|
+
|
|
44
|
+
case "disconnect":
|
|
45
|
+
var params protocol.DisconnectParams
|
|
46
|
+
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
|
47
|
+
return h.errorResponse(req.ID, -32602, "Invalid params")
|
|
48
|
+
}
|
|
49
|
+
err = h.manager.Disconnect(params.SessionID)
|
|
50
|
+
|
|
51
|
+
case "reconnect":
|
|
52
|
+
var params protocol.ReconnectParams
|
|
53
|
+
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
|
54
|
+
return h.errorResponse(req.ID, -32602, "Invalid params")
|
|
55
|
+
}
|
|
56
|
+
result, err = h.manager.Reconnect(params.SessionID)
|
|
57
|
+
|
|
58
|
+
case "exec":
|
|
59
|
+
var params protocol.ExecParams
|
|
60
|
+
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
|
61
|
+
return h.errorResponse(req.ID, -32602, "Invalid params")
|
|
62
|
+
}
|
|
63
|
+
result, err = h.manager.Exec(params.SessionID, params.Command)
|
|
64
|
+
|
|
65
|
+
case "list":
|
|
66
|
+
result = h.manager.List()
|
|
67
|
+
|
|
68
|
+
case "use":
|
|
69
|
+
var params protocol.UseParams
|
|
70
|
+
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
|
71
|
+
return h.errorResponse(req.ID, -32602, "Invalid params")
|
|
72
|
+
}
|
|
73
|
+
err = h.manager.Use(params.SessionID)
|
|
74
|
+
|
|
75
|
+
case "forward":
|
|
76
|
+
var params protocol.ForwardParams
|
|
77
|
+
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
|
78
|
+
return h.errorResponse(req.ID, -32602, "Invalid params")
|
|
79
|
+
}
|
|
80
|
+
result, err = h.manager.AddForward(params.SessionID, params.Type, params.Local, params.Remote)
|
|
81
|
+
|
|
82
|
+
case "forwards":
|
|
83
|
+
result = h.manager.ListForwards()
|
|
84
|
+
|
|
85
|
+
case "forward_close":
|
|
86
|
+
var params protocol.ForwardCloseParams
|
|
87
|
+
if err := json.Unmarshal(req.Params, ¶ms); err != nil {
|
|
88
|
+
return h.errorResponse(req.ID, -32602, "Invalid params")
|
|
89
|
+
}
|
|
90
|
+
err = h.manager.CloseForward(params.ForwardID)
|
|
91
|
+
|
|
92
|
+
default:
|
|
93
|
+
return h.errorResponse(req.ID, -32601, fmt.Sprintf("Method not found: %s", req.Method))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if err != nil {
|
|
97
|
+
return h.errorResponse(req.ID, -32000, err.Error())
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return h.successResponse(req.ID, result)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
func (h *Handler) successResponse(id interface{}, result interface{}) ([]byte, error) {
|
|
104
|
+
resp := protocol.Response{
|
|
105
|
+
JSONRPC: "2.0",
|
|
106
|
+
Result: result,
|
|
107
|
+
ID: id,
|
|
108
|
+
}
|
|
109
|
+
return json.Marshal(resp)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func (h *Handler) errorResponse(id interface{}, code int, message string) ([]byte, error) {
|
|
113
|
+
resp := protocol.Response{
|
|
114
|
+
JSONRPC: "2.0",
|
|
115
|
+
Error: &protocol.Error{
|
|
116
|
+
Code: code,
|
|
117
|
+
Message: message,
|
|
118
|
+
},
|
|
119
|
+
ID: id,
|
|
120
|
+
}
|
|
121
|
+
return json.Marshal(resp)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ServeUnixSocket starts the RPC server on a Unix socket
|
|
125
|
+
func ServeUnixSocket(manager *session.Manager, socketPath string) error {
|
|
126
|
+
// Remove existing socket file
|
|
127
|
+
os.Remove(socketPath)
|
|
128
|
+
|
|
129
|
+
listener, err := net.Listen("unix", socketPath)
|
|
130
|
+
if err != nil {
|
|
131
|
+
return fmt.Errorf("failed to listen on socket: %w", err)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Set socket permissions
|
|
135
|
+
os.Chmod(socketPath, 0700)
|
|
136
|
+
|
|
137
|
+
handler := NewHandler(manager)
|
|
138
|
+
|
|
139
|
+
log.Printf("RPC server listening on %s", socketPath)
|
|
140
|
+
|
|
141
|
+
for {
|
|
142
|
+
conn, err := listener.Accept()
|
|
143
|
+
if err != nil {
|
|
144
|
+
log.Printf("Accept error: %v", err)
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
go handleConnection(conn, handler)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
func handleConnection(conn net.Conn, handler *Handler) {
|
|
153
|
+
defer conn.Close()
|
|
154
|
+
|
|
155
|
+
buf := make([]byte, 4096)
|
|
156
|
+
for {
|
|
157
|
+
n, err := conn.Read(buf)
|
|
158
|
+
if err == io.EOF {
|
|
159
|
+
return
|
|
160
|
+
}
|
|
161
|
+
if err != nil {
|
|
162
|
+
log.Printf("Read error: %v", err)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
data := buf[:n]
|
|
167
|
+
response, err := handler.Handle(data)
|
|
168
|
+
if err != nil {
|
|
169
|
+
log.Printf("Handle error: %v", err)
|
|
170
|
+
continue
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Add newline to flush
|
|
174
|
+
response = append(response, '\n')
|
|
175
|
+
_, err = conn.Write(response)
|
|
176
|
+
if err != nil {
|
|
177
|
+
log.Printf("Write error: %v", err)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// HTTPHandler creates an HTTP handler for testing
|
|
183
|
+
func HTTPHandler(manager *session.Manager) http.Handler {
|
|
184
|
+
handler := NewHandler(manager)
|
|
185
|
+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
186
|
+
if r.Method != http.MethodPost {
|
|
187
|
+
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
data, err := io.ReadAll(r.Body)
|
|
192
|
+
if err != nil {
|
|
193
|
+
http.Error(w, "Read error", http.StatusBadRequest)
|
|
194
|
+
return
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
response, err := handler.Handle(data)
|
|
198
|
+
if err != nil {
|
|
199
|
+
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
w.Header().Set("Content-Type", "application/json")
|
|
204
|
+
w.Write(response)
|
|
205
|
+
})
|
|
206
|
+
}
|
package/skill.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# gssh 使用指南
|
|
2
|
+
|
|
3
|
+
## 快速开始
|
|
4
|
+
|
|
5
|
+
### 1. 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
git clone <repository-url>
|
|
9
|
+
cd gssh
|
|
10
|
+
go build -o bin/daemon cmd/daemon/main.go
|
|
11
|
+
go build -o bin/gssh cmd/gssh/main.go
|
|
12
|
+
./homebrew/install.sh
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### 2. 基本使用
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 连接 SSH
|
|
19
|
+
gssh connect -u admin1 -h 192.168.1.100 -P 1234
|
|
20
|
+
|
|
21
|
+
# 执行命令
|
|
22
|
+
gssh exec "ls -la"
|
|
23
|
+
|
|
24
|
+
# 列出 session
|
|
25
|
+
gssh list
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## 核心概念
|
|
29
|
+
|
|
30
|
+
- **daemon**: 后台服务,管理 SSH 连接
|
|
31
|
+
- **session**: 一个 SSH 连接会话
|
|
32
|
+
- **默认 session**: 不指定 session ID 时使用的会话
|
|
33
|
+
|
|
34
|
+
## 命令列表
|
|
35
|
+
|
|
36
|
+
| 命令 | 说明 |
|
|
37
|
+
|------|------|
|
|
38
|
+
| `connect` | 建立新 SSH 连接 |
|
|
39
|
+
| `exec` | 执行命令 |
|
|
40
|
+
| `list` | 列出所有 session |
|
|
41
|
+
| `use` | 切换默认 session |
|
|
42
|
+
| `disconnect` | 断开 session |
|
|
43
|
+
| `reconnect` | 重连 session |
|
|
44
|
+
| `forward` | 端口转发 |
|
|
45
|
+
| `forwards` | 列出转发 |
|
|
46
|
+
| `forward-close` | 关闭转发 |
|
|
47
|
+
|
|
48
|
+
## 典型工作流
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
# 1. 连接远程主机
|
|
52
|
+
gssh connect -u admin -h 192.168.1.100 -P password
|
|
53
|
+
|
|
54
|
+
# 2. 执行命令(使用默认 session)
|
|
55
|
+
gssh exec "pwd"
|
|
56
|
+
gssh exec "uname -a"
|
|
57
|
+
|
|
58
|
+
# 3. 断开连接
|
|
59
|
+
gssh disconnect
|
|
60
|
+
|
|
61
|
+
# 4. 重连
|
|
62
|
+
gssh reconnect -s <session-id>
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## SSH 密钥认证
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# 使用密钥连接
|
|
69
|
+
gssh connect -u admin -h 192.168.1.100 -i ~/.ssh/id_rsa
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## 端口转发
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# 本地转发:localhost:8080 -> remote:80
|
|
76
|
+
gssh forward -l 8080 -r 80
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## 服务管理
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 启动
|
|
83
|
+
brew services start gssh
|
|
84
|
+
|
|
85
|
+
# 停止
|
|
86
|
+
brew services stop gssh
|
|
87
|
+
|
|
88
|
+
# 状态
|
|
89
|
+
brew services list
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 注意事项
|
|
93
|
+
|
|
94
|
+
- Agent 使用场景:无状态,每次请求独立完成
|
|
95
|
+
- sudo 命令:需要配置 passwordless sudo 或使用密钥认证
|
|
96
|
+
- Session 状态由 daemon 维护,CLI 只负责发送请求
|