network-infra-utility 0.3.0 → 0.6.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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -0
  3. data/GUIDE.md +212 -0
  4. data/Gemfile +2 -0
  5. data/Gemfile.lock +70 -0
  6. data/Rakefile +1 -1
  7. data/bin/dns-query +836 -0
  8. data/bin/geo-doc +135 -0
  9. data/bin/geo-get +1 -1
  10. data/bin/geo-update +429 -0
  11. data/document/ASNum/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +242 -0
  12. data/document/DNSQuery/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +248 -0
  13. data/document/Geo/345/221/275/344/273/244/345/267/245/345/205/267/344/275/277/347/224/250/346/226/271/346/263/225.md +441 -0
  14. data/document/IP/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +297 -0
  15. data/document/MAC/346/250/241/345/235/227/345/212/237/350/203/275/350/257/264/346/230/216.md +296 -0
  16. data/document/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/344/275/277/347/224/250/346/226/271/346/263/225.md +764 -0
  17. data/network-infra-utility.gemspec +4 -2
  18. data/network.rb +3 -1
  19. data/service/geodb/geodb.rb +278 -1
  20. data/service/ssh/README.md +955 -0
  21. data/service/ssh/bin/ssh-client +198 -0
  22. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/345/212/237/350/203/275/351/234/200/346/261/202/346/226/207/346/241/243.md +292 -0
  23. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/257/246/347/273/206/350/256/276/350/256/241/346/226/207/346/241/243.md +1521 -0
  24. data/service/ssh/design/SSH/350/277/236/346/216/245/345/256/242/346/210/267/347/253/257/350/275/257/344/273/266/350/256/276/350/256/241/346/226/207/346/241/243.md +2493 -0
  25. data/service/ssh/ext/ssh_core/bin/ssh_core.cmd +28 -0
  26. data/service/ssh/ext/ssh_core/config/sys.config +0 -0
  27. data/service/ssh/ext/ssh_core/config/vm.args +0 -0
  28. data/service/ssh/ext/ssh_core/local_deps/jsx/CHECKSUM +1 -0
  29. data/service/ssh/ext/ssh_core/local_deps/jsx/LICENSE +21 -0
  30. data/service/ssh/ext/ssh_core/local_deps/jsx/README.md +696 -0
  31. data/service/ssh/ext/ssh_core/local_deps/jsx/VERSION +1 -0
  32. data/service/ssh/ext/ssh_core/local_deps/jsx/contents.tar.gz +0 -0
  33. data/service/ssh/ext/ssh_core/local_deps/jsx/metadata.config +15 -0
  34. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.config +17 -0
  35. data/service/ssh/ext/ssh_core/local_deps/jsx/rebar.lock +1 -0
  36. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.app.src +10 -0
  37. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx.erl +506 -0
  38. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.erl +393 -0
  39. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_config.hrl +18 -0
  40. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_consult.erl +81 -0
  41. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_decoder.erl +1909 -0
  42. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_encoder.erl +116 -0
  43. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_parser.erl +1214 -0
  44. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_json.erl +408 -0
  45. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_to_term.erl +389 -0
  46. data/service/ssh/ext/ssh_core/local_deps/jsx/src/jsx_verify.erl +121 -0
  47. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx.erl +506 -0
  48. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.erl +393 -0
  49. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_config.hrl +18 -0
  50. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_consult.erl +81 -0
  51. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_decoder.erl +1909 -0
  52. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_encoder.erl +116 -0
  53. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_parser.erl +1214 -0
  54. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_json.erl +408 -0
  55. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_to_term.erl +389 -0
  56. data/service/ssh/ext/ssh_core/local_deps/jsx/src/src/jsx_verify.erl +121 -0
  57. data/service/ssh/ext/ssh_core/rebar.config +24 -0
  58. data/service/ssh/ext/ssh_core/rebar.lock +1 -0
  59. data/service/ssh/ext/ssh_core/src/ssh_auth_engine.erl +156 -0
  60. data/service/ssh/ext/ssh_core/src/ssh_channel_stm.erl +232 -0
  61. data/service/ssh/ext/ssh_core/src/ssh_codec.erl +83 -0
  62. data/service/ssh/ext/ssh_core/src/ssh_conn_sup.erl +48 -0
  63. data/service/ssh/ext/ssh_core/src/ssh_conn_worker.erl +535 -0
  64. data/service/ssh/ext/ssh_core/src/ssh_core.app.src +36 -0
  65. data/service/ssh/ext/ssh_core/src/ssh_core_app.erl +11 -0
  66. data/service/ssh/ext/ssh_core/src/ssh_core_sup.erl +46 -0
  67. data/service/ssh/ext/ssh_core/src/ssh_infra_sup.erl +117 -0
  68. data/service/ssh/ext/ssh_core/src/ssh_ipc.hrl +80 -0
  69. data/service/ssh/ext/ssh_core/src/ssh_ipc_coalesce.erl +94 -0
  70. data/service/ssh/ext/ssh_core/src/ssh_ipc_gateway.erl +467 -0
  71. data/service/ssh/ext/ssh_core/src/ssh_ipc_proto.erl +95 -0
  72. data/service/ssh/ext/ssh_core/src/ssh_jump_chain.erl +101 -0
  73. data/service/ssh/ext/ssh_core/src/ssh_keepalive_mgr.erl +222 -0
  74. data/service/ssh/ext/ssh_core/src/ssh_known_hosts_proxy.erl +67 -0
  75. data/service/ssh/ext/ssh_core/src/ssh_port_fwd.erl +225 -0
  76. data/service/ssh/ext/ssh_core/src/ssh_sftp_session.erl +250 -0
  77. data/service/ssh/ext/ssh_core/src/ssh_sftp_sup.erl +62 -0
  78. data/service/ssh/ext/ssh_core_rs/Cargo.lock +2345 -0
  79. data/service/ssh/ext/ssh_core_rs/Cargo.toml +30 -0
  80. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs +34 -0
  81. data/service/ssh/ext/ssh_core_rs/bin/ssh_core_rs.cmd +40 -0
  82. data/service/ssh/ext/ssh_core_rs/src/channel.rs +296 -0
  83. data/service/ssh/ext/ssh_core_rs/src/coalesce.rs +143 -0
  84. data/service/ssh/ext/ssh_core_rs/src/codec.rs +71 -0
  85. data/service/ssh/ext/ssh_core_rs/src/conn.rs +628 -0
  86. data/service/ssh/ext/ssh_core_rs/src/gateway.rs +389 -0
  87. data/service/ssh/ext/ssh_core_rs/src/handler.rs +293 -0
  88. data/service/ssh/ext/ssh_core_rs/src/keepalive.rs +194 -0
  89. data/service/ssh/ext/ssh_core_rs/src/main.rs +351 -0
  90. data/service/ssh/ext/ssh_core_rs/src/portfwd.rs +378 -0
  91. data/service/ssh/ext/ssh_core_rs/src/proto.rs +198 -0
  92. data/service/ssh/ext/ssh_core_rs/src/sftp.rs +294 -0
  93. data/service/ssh/lib/network_infra_utility/ssh/automation/macro_engine.rb +213 -0
  94. data/service/ssh/lib/network_infra_utility/ssh/client.rb +257 -0
  95. data/service/ssh/lib/network_infra_utility/ssh/config/schema.rb +90 -0
  96. data/service/ssh/lib/network_infra_utility/ssh/config/settings.rb +103 -0
  97. data/service/ssh/lib/network_infra_utility/ssh/config/store.rb +90 -0
  98. data/service/ssh/lib/network_infra_utility/ssh/ipc/coalesce.rb +83 -0
  99. data/service/ssh/lib/network_infra_utility/ssh/ipc/errors.rb +36 -0
  100. data/service/ssh/lib/network_infra_utility/ssh/ipc/router.rb +212 -0
  101. data/service/ssh/lib/network_infra_utility/ssh/ipc/transport.rb +81 -0
  102. data/service/ssh/lib/network_infra_utility/ssh/security/host_key.rb +211 -0
  103. data/service/ssh/lib/network_infra_utility/ssh/security/vault.rb +211 -0
  104. data/service/ssh/lib/network_infra_utility/ssh/session/history.rb +56 -0
  105. data/service/ssh/lib/network_infra_utility/ssh/session/manager.rb +92 -0
  106. data/service/ssh/lib/network_infra_utility/ssh/session/session.rb +109 -0
  107. data/service/ssh/lib/network_infra_utility/ssh/session/tree.rb +95 -0
  108. data/service/ssh/lib/network_infra_utility/ssh/terminal/ansi_parser.rb +435 -0
  109. data/service/ssh/lib/network_infra_utility/ssh/terminal/buffer.rb +78 -0
  110. data/service/ssh/lib/network_infra_utility/ssh/terminal/emulator.rb +159 -0
  111. data/service/ssh/lib/network_infra_utility/ssh/terminal/logger.rb +195 -0
  112. data/service/ssh/lib/network_infra_utility/ssh/terminal/screen.rb +212 -0
  113. data/service/ssh/lib/network_infra_utility/ssh/terminal/theme.rb +127 -0
  114. data/service/ssh/lib/network_infra_utility/ssh/version.rb +7 -0
  115. data/service/ssh/lib/network_infra_utility/ssh.rb +44 -0
  116. data/support/basic/as_num.rb +221 -0
  117. data/support/basic/mac_address.rb +281 -0
  118. data/version.rb +1 -1
  119. metadata +142 -1
@@ -0,0 +1,764 @@
1
+
2
+ # SSH 连接客户端使用方法
3
+
4
+ > 源码位置:`service/ssh/`(含 `bin/` CLI、`lib/` Ruby 源码、`ext/` 双引擎)
5
+ > CLI 入口:`service/ssh/bin/ssh-client`
6
+ > 加载方式:`require_relative 'service/ssh/lib/network_infra_utility/ssh'`
7
+
8
+ Ruby + 双引擎架构的 SSH 连接客户端。SSH 协议核心由**可插拔双引擎**提供:默认 **Rust 引擎**(`ssh_core_rs`,基于 russh),可选 **Erlang 引擎**(`ssh_core`,基于 OTP ssh);Ruby 负责调度、终端渲染、配置与自动化,与引擎通过本地 socket 上的 JSON-RPC 2.0 通信,两端 IPC 协议完全一致,切换引擎无需改动 Ruby 代码。
9
+
10
+ ---
11
+
12
+ ## 一、环境要求与编译
13
+
14
+ | 组件 | 版本要求 | 说明 |
15
+ |------|---------|------|
16
+ | Ruby | >= 3.0 | 运行 CLI 和 Ruby 端逻辑 |
17
+ | Rust | rustc/cargo(稳定版) | 编译默认 SSH 核心引擎(`ssh_core_rs`) |
18
+ | Erlang/OTP | >= 26 | 编译可选 Erlang 引擎(`ssh_core`) |
19
+ | rebar3 | 任意版本 | 编译 Erlang 引擎 |
20
+ | OpenSSL | 系统自带 | Vault 加密 |
21
+
22
+ > **说明**:默认引擎为 Rust,仅使用 Rust 即可运行全部功能;Erlang 引擎作为可插拔备选,需要时再安装 Erlang/OTP 与 rebar3。
23
+
24
+ ### 编译
25
+
26
+ ```bash
27
+ # 1. 编译 Rust 核心引擎(默认)
28
+ cd service/ssh/ext/ssh_core_rs
29
+ cargo build --release
30
+
31
+ # 2. 编译 Erlang 核心引擎(可选备选)
32
+ cd service/ssh/ext/ssh_core
33
+ rebar3 compile
34
+
35
+ # 3. 验证 Ruby 端
36
+ cd service/ssh
37
+ ruby bin/ssh-client version # 输出 1.0.0
38
+ ruby bin/ssh-client themes # 列出全部主题
39
+ ```
40
+
41
+ > 启动脚本 `bin/ssh_core_rs`(Linux/macOS)与 `bin/ssh_core_rs.cmd`(Windows)会在首次启动时自动编译,也可按上述命令预先编译。
42
+
43
+ ---
44
+
45
+ ## 二、命令行用法(CLI)
46
+
47
+ CLI 入口为 `bin/ssh-client`,基于 Thor 框架,提供 4 个子命令。
48
+
49
+ ### 1. `connect` — 连接服务器
50
+
51
+ ```
52
+ ruby bin/ssh-client connect HOST [options]
53
+ ```
54
+
55
+ 连接到指定 SSH 服务器并进入交互终端。连接成功后输入命令回车发送,输入 `exit` 或 `quit` 断开,`Ctrl+C` 退出。
56
+
57
+ | 选项 | 简写 | 类型 | 默认值 | 说明 |
58
+ |------|------|------|--------|------|
59
+ | `--user` | `-u` | String | **必填** | SSH 用户名 |
60
+ | `--port` | `-p` | Integer | 22 | SSH 端口 |
61
+ | `--key` | `-i` | String | — | 私钥文件路径 |
62
+ | `--jump` | `-J` | String | — | 跳板机,格式 `user@host:port`,多个用逗号分隔(**仅 Erlang 引擎支持**,CLI 默认 Rust 引擎下不生效) |
63
+ | `--name` | — | String | — | 会话名称 |
64
+ | `--password` | — | Boolean | false | 使用密码认证(交互式输入,不回显) |
65
+ | `--theme` | — | String | default | 终端配色主题 |
66
+ | `--connect-timeout` | — | Integer | 60 | 连接超时(秒) |
67
+ | `--keepalive-interval` | — | Integer | 30 | 保活心跳间隔(秒);注意:引擎 `set_interval` 不接受 0 作为禁用值,设 0 时 CLI 会跳过设置并保持默认 30s |
68
+ | `--algorithms` | — | String | — | 自定义算法,格式 `kex=algo1,algo2;cipher=aes256-ctr` |
69
+
70
+ **示例:**
71
+
72
+ ```bash
73
+ # 基本连接
74
+ ruby bin/ssh-client connect 10.0.0.1 -u admin
75
+
76
+ # 密钥认证
77
+ ruby bin/ssh-client connect 10.0.0.1 -u admin -i ~/.ssh/id_ed25519
78
+
79
+ # 密码认证(交互输入,不回显)
80
+ ruby bin/ssh-client connect 10.0.0.1 -u admin --password
81
+
82
+ # 指定端口、主题、连接超时
83
+ ruby bin/ssh-client connect 10.0.0.1 -u admin -p 2222 --theme dracula --connect-timeout 30
84
+ ```
85
+
86
+ ### 2. `batch` — 批量执行命令
87
+
88
+ ```
89
+ ruby bin/ssh-client batch FILE [options]
90
+ ```
91
+
92
+ 从 YAML 文件加载多个会话定义,并行(默认)或串行在所有会话上执行同一条命令。
93
+
94
+ | 选项 | 简写 | 类型 | 默认值 | 说明 |
95
+ |------|------|------|--------|------|
96
+ | `--command` | `-c` | String | **必填** | 要执行的命令 |
97
+ | `--serial` | — | Boolean | false | 串行执行(默认并行) |
98
+
99
+ **会话文件格式(YAML):**
100
+
101
+ ```yaml
102
+ # 单条会话(Hash)
103
+ host: 10.0.0.1
104
+ user: admin
105
+ port: 22
106
+ key_path: /home/user/.ssh/id_ed25519
107
+
108
+ # 多条会话(Array)
109
+ - host: 10.0.0.1
110
+ user: admin
111
+ key_path: /home/user/.ssh/id_ed25519
112
+ - host: 10.0.0.2
113
+ user: root
114
+ password: secret
115
+ - host: 10.0.0.3
116
+ user: deploy
117
+ port: 2222
118
+ key_path: /home/user/.ssh/deploy_key
119
+ ```
120
+
121
+ **示例:**
122
+
123
+ ```bash
124
+ # 并行执行(默认)
125
+ ruby bin/ssh-client batch servers.yml -c "uptime"
126
+
127
+ # 串行执行
128
+ ruby bin/ssh-client batch servers.yml -c "show version" --serial
129
+ ```
130
+
131
+ ### 3. `themes` — 列出配色主题
132
+
133
+ ```
134
+ ruby bin/ssh-client themes
135
+ ```
136
+
137
+ ### 4. `version` — 显示版本
138
+
139
+ ```
140
+ ruby bin/ssh-client version
141
+ ```
142
+
143
+ ---
144
+
145
+ ## 三、连接参数详解
146
+
147
+ 连接参数(`spec`)是一个 Hash,以下字段被引擎识别:
148
+
149
+ | 字段 | 类型 | 必填 | 说明 |
150
+ |------|------|------|------|
151
+ | `host` | String | 是 | 目标主机 IP 或域名 |
152
+ | `user` | String | 是 | SSH 用户名 |
153
+ | `port` | Integer | 否 | SSH 端口,默认 22 |
154
+ | `name` | String | 否 | 会话显示名称 |
155
+ | `key_path` | String | 否 | 私钥文件路径 |
156
+ | `key_dir` | String | 否 | 密钥搜索目录(默认 `.`,即引擎工作目录) |
157
+ | `auth` | Hash | 否 | 认证配置,见下表 |
158
+ | `jumps` | Array | 否 | 跳板机链(仅 Erlang 引擎) |
159
+ | `terminal` | Hash | 否 | 终端配置,如 `{ theme: "dracula" }` |
160
+ | `proxy` | Hash | 否 | 代理配置(仅 Erlang 引擎,SOCKS5 / HTTP CONNECT) |
161
+ | `connect_timeout_ms` | Integer | 否 | 连接超时(毫秒),默认 60000 |
162
+ | `inactivity_timeout_s` | Integer | 否 | 空闲断开时间(秒) |
163
+ | `algorithms` | Hash | 否 | 自定义算法列表,如 `{ kex: [...], cipher: [...] }` |
164
+
165
+ > **引擎支持差异**:`jumps`(跳板链)与 `proxy`(代理)目前仅 **Erlang 引擎**实现(OTP ssh 原生支持 + 自研 SOCKS5/HTTP CONNECT 握手);**Rust 引擎暂未实现**,传入会被忽略。若需使用跳板机/代理,请用 `Client.new(backend: :erlang)` 切换后端。
166
+
167
+ ### auth 字段
168
+
169
+ | 认证类型 | auth 值 | 说明 |
170
+ |---------|---------|------|
171
+ | 密钥认证 | `{ type: "publickey" }` | 私钥路径通过 `key_path` 指定 |
172
+ | 密码认证 | `{ type: "password", password: "..." }` | 密码明文或 Vault 引用 |
173
+ | 键盘交互 | `{ type: "keyboard_interactive", responses: ["..."] }` | 交互式认证 |
174
+
175
+ 密码字段支持 [Vault 引用](#凭据加密vault),格式为 `~vault:<key>`。连接前由 Ruby 端自动解密替换。
176
+
177
+ ### jumps 字段(仅 Erlang 引擎)
178
+
179
+ 跳板机链,数组形式,连接顺序从前到后:
180
+
181
+ ```ruby
182
+ jumps: [
183
+ { user: "jump", host: "10.0.0.1", port: 22 },
184
+ { user: "bastion", host: "192.168.1.1", port: 2222 }
185
+ ]
186
+ ```
187
+
188
+ ### proxy 字段(仅 Erlang 引擎)
189
+
190
+ SOCKS5(默认)或 HTTP CONNECT 代理:
191
+
192
+ ```ruby
193
+ proxy: {
194
+ type: "socks5", # 或 "http",默认 "socks5"
195
+ host: "127.0.0.1",
196
+ port: 1080,
197
+ username: "proxy_user", # 可选
198
+ password: "proxy_pass" # 可选
199
+ }
200
+ ```
201
+
202
+ ---
203
+
204
+ ## 四、配置文件
205
+
206
+ 所有配置文件默认存放在 `~/.network-infra-utility/` 目录下。
207
+
208
+ ### settings.yml — 全局设置
209
+
210
+ ```yaml
211
+ # 主密码(用于 Vault 加解密,请妥善保管)
212
+ master_password: "your-master-password"
213
+
214
+ # 日志目录(不设则默认 ~/.network-infra-utility/logs/)
215
+ log_dir: ~/.network-infra-utility/logs
216
+
217
+ # 配置目录
218
+ config_dir: ~/.network-infra-utility
219
+
220
+ # 默认终端类型
221
+ default_terminal: xterm-256color
222
+
223
+ # 默认回滚行数
224
+ default_scrollback: 10000
225
+
226
+ # 默认心跳间隔(秒)
227
+ default_keepalive_interval: 30
228
+
229
+ # 默认配色主题
230
+ default_theme: dark
231
+ ```
232
+
233
+ ### sessions.yml | 会话列表
234
+
235
+ 会话列表持久化文件,由 `Config::Store` 管理,支持原子写入(先写 `.tmp` 再 rename)。
236
+
237
+ ```yaml
238
+ version: 1
239
+ groups:
240
+ - id: prod
241
+ name: 生产环境
242
+ parent: ~
243
+ collapsed: false
244
+ sessions:
245
+ - id: sess_001
246
+ name: web-server-1
247
+ group: prod
248
+ host: 10.0.0.1
249
+ port: 22
250
+ user: admin
251
+ tags: [web, production]
252
+ auth:
253
+ type: publickey
254
+ terminal:
255
+ theme: dracula
256
+ macro:
257
+ enabled: true
258
+ steps:
259
+ - action: "enable\n"
260
+ wait_pattern: "#"
261
+ delay: 0.5
262
+ on_fail: continue
263
+ - action: "terminal monitor\n"
264
+ wait_pattern: "#"
265
+ delay: 0.3
266
+ on_fail: abort
267
+ ```
268
+
269
+ **会话字段定义(Schema v1):**
270
+
271
+ | 字段 | 类型 | 必填 | 说明 |
272
+ |------|------|------|------|
273
+ | `host` | String | 是 | 主机地址 |
274
+ | `user` | String | 是 | 用户名 |
275
+ | `port` | Integer | 否 | 端口 |
276
+ | `name` | String | 否 | 会话名称 |
277
+ | `group` | String | 否 | 所属分组 ID |
278
+ | `tags` | Array | 否 | 标签列表 |
279
+ | `auth` | Hash | 否 | 认证配置 |
280
+ | `terminal` | Hash | 否 | 终端配置 |
281
+ | `jumps` | Array | 否 | 跳板机链 |
282
+ | `port_forwards` | Array | 否 | 端口转发规则 |
283
+ | `macro` | Hash | 否 | 登录宏配置 |
284
+ | `keepalive` | Hash | 否 | 心跳配置 |
285
+ | `auto_reconnect` | Hash | 否 | 自动重连配置 |
286
+
287
+ ### known_hosts.yml | 主机密钥
288
+
289
+ 主机密钥指纹存储。首次连接时提示用户确认,确认后自动保存。
290
+
291
+ ```yaml
292
+ "10.0.0.1:22":
293
+ :fingerprint: "SHA256:abc123..."
294
+ :added_at: "2026-01-15T10:30:00+08:00"
295
+ ```
296
+
297
+ **裁决逻辑:**
298
+
299
+ | 场景 | 行为 |
300
+ |------|------|
301
+ | 首次连接 | 提示用户确认(`y/N`),确认后保存指纹 |
302
+ | 指纹匹配 | 自动通过,无交互 |
303
+ | 指纹变更 | 拒绝连接并告警,需手动删除条目后重连 |
304
+
305
+ ---
306
+
307
+ ## 五、凭据加密(Vault)
308
+
309
+ Vault 使用 **AES-256-GCM** 加密算法保护敏感凭据(如 SSH 密码):
310
+
311
+ | 参数 | 值 |
312
+ |------|-----|
313
+ | 算法 | AES-256-GCM |
314
+ | KDF | PBKDF2-HMAC-SHA256 |
315
+ | 迭代次数 | 100,000 |
316
+ | Salt 长度 | 32 字节(每条独立随机) |
317
+ | Key 长度 | 32 字节 |
318
+ | Nonce 长度 | 12 字节 |
319
+
320
+ ### 加密存储与引用
321
+
322
+ ```ruby
323
+ client = NetworkInfraUtility::SSH::Client.new
324
+ # client.vault 是 Vault 实例
325
+
326
+ # 存储密码
327
+ client.vault.store("web_server_pass", "MySecret123!")
328
+
329
+ # 在连接参数中引用
330
+ session = client.connect(
331
+ host: "10.0.0.1",
332
+ user: "admin",
333
+ auth: { type: "password", password: "~vault:web_server_pass" }
334
+ )
335
+ # Vault 会自动将 ~vault:web_server_pass 解密为明文密码
336
+ ```
337
+
338
+ ### vault.yml 格式
339
+
340
+ ```yaml
341
+ web_server_pass:
342
+ :salt: <Base64>
343
+ :nonce: <Base64>
344
+ :tag: <Base64>
345
+ :data: <Base64>
346
+ ```
347
+
348
+ 文件权限:Linux 下 `chmod 600`,Windows 下通过 `icacls` 限制为当前用户。
349
+
350
+ ---
351
+
352
+ ## 六、编程 API 使用
353
+
354
+ ### 1. Client — 生命周期管理
355
+
356
+ `SSH::Client` 是主入口,管理引擎生命周期和子系统协调。默认后端为 **Rust**(`ssh_core_rs`),可指定 `backend: :erlang` 切换到 Erlang 引擎。
357
+
358
+ ```ruby
359
+ client = NetworkInfraUtility::SSH::Client.new # 默认 Rust 引擎
360
+ client_erl = NetworkInfraUtility::SSH::Client.new(backend: :erlang) # 切换 Erlang 引擎
361
+ ```
362
+
363
+ | 方法 | 说明 |
364
+ |------|------|
365
+ | `start_engine` | 启动引擎子进程(按 `backend` 选择 Rust/Erlang),建立 IPC 连接,注册反向 RPC |
366
+ | `connect(spec)` | 发起 SSH 连接,返回 `Session::Session` 对象 |
367
+ | `stop` | 停止引擎,断开所有连接,清理资源 |
368
+ | `started?` | 引擎是否已启动 |
369
+ | `ipc` | `IPC::Router` 实例,用于底层 RPC 调用 |
370
+ | `sessions` | `Session::Manager` 实例,管理所有会话 |
371
+ | `vault` | `Security::Vault` 实例,凭据加密 |
372
+ | `host_key` | `Security::HostKey` 实例,主机密钥管理 |
373
+ | `settings` | `Config::Settings` 实例,全局配置 |
374
+ | `backend` | 当前引擎后端(`:rust` 或 `:erlang`) |
375
+ | `on_engine_exit(&block)` | 设置引擎异常退出回调(`:crashed` / `:disconnected`) |
376
+
377
+ **基本用法:**
378
+
379
+ ```ruby
380
+ require_relative "service/ssh/lib/network_infra_utility/ssh"
381
+
382
+ client = NetworkInfraUtility::SSH::Client.new
383
+ client.start_engine
384
+
385
+ begin
386
+ # 终端主题通过 spec[:terminal][:theme] 传递
387
+ session = client.connect(
388
+ host: "10.0.0.1", user: "admin",
389
+ key_path: "~/.ssh/id_ed25519",
390
+ terminal: { theme: "dracula" }
391
+ )
392
+ terminal = session.open_terminal
393
+
394
+ terminal.puts("show version")
395
+ # ... 读取终端输出 ...
396
+
397
+ session.disconnect
398
+ ensure
399
+ client.stop
400
+ end
401
+ ```
402
+
403
+ ### 2. Session — 会话操作
404
+
405
+ `SSH::Session::Session` 封装单个 SSH 会话。
406
+
407
+ | 方法 | 说明 |
408
+ |------|------|
409
+ | `open_terminal(term_type:, cols:, rows:)` | 打开终端通道,返回 `Terminal::Emulator` |
410
+ | `close_terminal` | 关闭终端通道 |
411
+ | `add_port_forward(type, local_port, remote_host, remote_port)` | 添加端口转发,返回 `rule_id` |
412
+ | `remove_port_forward(rule_id)` | 移除端口转发规则 |
413
+ | `disconnect` | 断开 SSH 连接 |
414
+ | `connected?` | 是否已连接 |
415
+ | `on_closed(reason)` | 连接被远端关闭时的回调 |
416
+
417
+ **属性:** `session_id`, `conn_id`, `spec`, `name`, `host`, `port`, `user`, `fingerprint`, `terminal`, `file_manager`, `port_forwards`, `tags`, `group`, `status`
418
+
419
+ **端口转发示例:**
420
+
421
+ ```ruby
422
+ session = client.connect(host: "10.0.0.1", user: "admin", key_path: "~/.ssh/id_ed25519")
423
+
424
+ # 本地端口转发:将本地 8080 转发到远程 80
425
+ rule_id = session.add_port_forward(:local, 8080, "127.0.0.1", 80)
426
+
427
+ # ... 使用本地 8080 端口 ...
428
+
429
+ # 移除转发
430
+ session.remove_port_forward(rule_id)
431
+ ```
432
+
433
+ ### 3. Terminal::Emulator — 终端交互
434
+
435
+ `SSH::Terminal::Emulator` 提供终端模拟和数据收发。
436
+
437
+ | 方法 | 说明 |
438
+ |------|------|
439
+ | `send(data)` | 发送原始数据到 SSH 通道 |
440
+ | `puts(text)` | 发送一行(自动追加 `\r`) |
441
+ | `feed(data)` | 输入从 SSH 收到的原始字节(内部解析 ANSI 转义) |
442
+ | `resize(cols, rows)` | 变更窗口大小 |
443
+ | `theme=` | 设置配色主题(名称) |
444
+ | `search(pattern)` | 搜索回滚缓冲区内容 |
445
+ | `export(path)` | 导出回滚缓冲区为文本 |
446
+ | `start_logging(path, max_size:, rotate:)` | 启动会话日志轮转 |
447
+ | `stop_logging` | 停止日志记录 |
448
+
449
+ **属性:** `conn_id`, `channel_id`, `screen`, `buffer`, `theme`, `logger`
450
+
451
+ **会话日志示例:**
452
+
453
+ ```ruby
454
+ terminal = session.open_terminal
455
+ terminal.start_logging("session_20260115.log", max_size: 100 * 1024 * 1024, rotate: 10)
456
+
457
+ # ... 交互过程中自动记录 ...
458
+ # 文件达到 max_size 时自动轮转,保留最近 rotate 个文件
459
+
460
+ terminal.stop_logging
461
+ ```
462
+
463
+ ### 4. Automation::MacroEngine — 登录宏
464
+
465
+ 连接后自动执行预设命令序列,支持等待匹配和失败处理。最多 50 步。
466
+
467
+ ```ruby
468
+ session = client.connect(host: "10.0.0.1", user: "admin", key_path: "~/.ssh/id_ed25519")
469
+ terminal = session.open_terminal
470
+
471
+ macro = NetworkInfraUtility::SSH::Automation::MacroEngine.new(session)
472
+
473
+ # 添加步骤
474
+ macro.add_step(action: "enable\n", wait_pattern: "Password:", delay: 0.5)
475
+ macro.add_step(action: "admin123\n", wait_pattern: "#", on_fail: :abort)
476
+ macro.add_step(action: "terminal monitor\n", wait_pattern: "#", delay: 0.3)
477
+
478
+ # 设置失败时的交互回调(on_fail: :ask 时触发)
479
+ macro.on_ask do |step, index|
480
+ puts "Step #{index} timeout: #{step.action}"
481
+ puts "Continue? (y/n)"
482
+ STDIN.gets.chomp =~ /^y/i ? :continue : :abort
483
+ end
484
+
485
+ # 执行宏
486
+ result = macro.run do |step, index, total|
487
+ puts "[#{index}/#{total}] #{step.action.strip}"
488
+ end
489
+ # result => :completed 或 :aborted
490
+ ```
491
+
492
+ **Step 参数:**
493
+
494
+ | 参数 | 类型 | 默认值 | 说明 |
495
+ |------|------|--------|------|
496
+ | `action` | String | — | 要发送的命令 |
497
+ | `wait_pattern` | String/Regexp/nil | nil | 等待匹配的输出,nil 不等待 |
498
+ | `delay` | Float | 0 | 发送前延迟(秒) |
499
+ | `on_fail` | Symbol | :continue | 超时行为:`:continue` / `:abort` / `:ask` |
500
+
501
+ ### 5. Security::HostKey — 主机密钥管理
502
+
503
+ ```ruby
504
+ host_key = client.host_key
505
+
506
+ # 手动添加信任条目
507
+ host_key.add("10.0.0.1", 22, "SHA256:abc123...")
508
+
509
+ # 查询
510
+ entry = host_key.get("10.0.0.1", 22)
511
+ # => { fingerprint: "SHA256:abc123...", added_at: "2026-01-15T..." }
512
+
513
+ # 删除
514
+ host_key.remove("10.0.0.1", 22)
515
+
516
+ # 列出全部
517
+ host_key.list.each do |entry|
518
+ puts "#{entry[:host]}:#{entry[:port]} - #{entry[:fingerprint]}"
519
+ end
520
+ ```
521
+
522
+ **自定义交互回调:**
523
+
524
+ ```ruby
525
+ host_key.on_prompt do |host, port, fingerprint|
526
+ puts "首次连接 #{host}:#{port}"
527
+ puts "指纹: #{fingerprint}"
528
+ print "信任? (y/N) "
529
+ STDIN.gets.chomp =~ /^y/i ? :accept : :reject
530
+ end
531
+
532
+ host_key.on_key_changed do |host, port, old_fp, new_fp|
533
+ warn "警告: #{host}:#{port} 主机密钥变更!"
534
+ warn " 旧: #{old_fp}"
535
+ warn " 新: #{new_fp}"
536
+ end
537
+ ```
538
+
539
+ ---
540
+
541
+ ## 七、终端配色主题
542
+
543
+ 内置 11 套配色方案:
544
+
545
+ | 主题名 | 风格 | 背景色 |
546
+ |--------|------|--------|
547
+ | `default` | VS Code Dark | #1e1e1e |
548
+ | `solarized-dark` | Solarized Dark | #002b36 |
549
+ | `solarized-light` | Solarized Light | #fdf6e3 |
550
+ | `dracula` | Dracula | #282a36 |
551
+ | `monokai` | Monokai | #272822 |
552
+ | `nord` | Nord | #2e3440 |
553
+ | `gruvbox-dark` | Gruvbox Dark | #282828 |
554
+ | `one-dark` | One Dark | #282c34 |
555
+ | `tokyo-night` | Tokyo Night | #1a1b26 |
556
+ | `catppuccin-mocha` | Catppuccin Mocha | #1e1e2e |
557
+ | `github-dark` | GitHub Dark | #0d1117 |
558
+
559
+ 每个主题包含:背景色 `bg`、前景色 `fg`、光标色 `cursor`、16 色调色板 `palette`。
560
+
561
+ ### 自定义主题
562
+
563
+ 在 `~/.network-infra-utility/themes/<name>.yml` 创建自定义主题:
564
+
565
+ ```yaml
566
+ bg: "#1a1b26"
567
+ fg: "#a9b1d6"
568
+ cursor: "#c0caf5"
569
+ palette:
570
+ - "#15161e" # 0 黑
571
+ - "#f7768e" # 1 红
572
+ - "#9ece6a" # 2 绿
573
+ - "#e0af68" # 3 黄
574
+ - "#7aa2f7" # 4 蓝
575
+ - "#bb9af7" # 5 洋红
576
+ - "#7dcfff" # 6 青
577
+ - "#a9b1d6" # 7 白
578
+ - "#414868" # 8 亮黑
579
+ - "#f7768e" # 9 亮红
580
+ - "#9ece6a" # 10 亮绿
581
+ - "#e0af68" # 11 亮黄
582
+ - "#7aa2f7" # 12 亮蓝
583
+ - "#bb9af7" # 13 亮洋红
584
+ - "#7dcfff" # 14 亮青
585
+ - "#c0caf5" # 15 亮白
586
+ ```
587
+
588
+ ---
589
+
590
+ ## 八、RPC 方法列表
591
+
592
+ Ruby 端通过 `IPC::Router` 向引擎发起 JSON-RPC 2.0 请求。Rust 与 Erlang 引擎实现同一套协议,方法列表完全一致。以下是全部已注册的 RPC 方法:
593
+
594
+ ### 连接管理
595
+
596
+ | 方法 | 参数 | 返回 | 说明 |
597
+ |------|------|------|------|
598
+ | `conn.connect` | 连接 spec(host, user, port, auth, jumps 等) | `{ conn_id, fingerprint }` | 发起 SSH 连接 |
599
+ | `conn.disconnect` | `{ id }` | `{ ok: true }` | 断开指定连接 |
600
+ | `conn.list` | `{}` | 连接列表 | 列出所有活动连接 |
601
+ | `conn.reconnect` | `{ id }` | — | 重连指定连接 |
602
+
603
+ ### 通道管理
604
+
605
+ | 方法 | 参数 | 返回 | 说明 |
606
+ |------|------|------|------|
607
+ | `channel.open` | `{ conn_id, type, term, cols, rows }` | `{ channel_id }` | 打开通道(shell/exec) |
608
+ | `channel.send` | `{ id, data }` | — | 发送数据(Base64 编码) |
609
+ | `channel.close` | `{ id }` | — | 关闭通道 |
610
+ | `channel.window_change` | `{ id, cols, rows }` | — | 变更窗口大小 |
611
+
612
+ ### SFTP 文件传输
613
+
614
+ | 方法 | 参数 | 返回 | 说明 |
615
+ |------|------|------|------|
616
+ | `sftp.open` | `{ conn_id }` | `{ sftp_id }` | 打开 SFTP 会话 |
617
+ | `sftp.list_dir` | `{ sftp_id, path }` | 目录列表 | 列出远程目录 |
618
+ | `sftp.download` | `{ sftp_id, remote, local }` | — | 下载文件 |
619
+ | `sftp.upload` | `{ sftp_id, local, remote }` | — | 上传文件 |
620
+ | `sftp.mkdir` | `{ sftp_id, path }` | — | 创建远程目录 |
621
+ | `sftp.remove` | `{ sftp_id, path }` | — | 删除远程文件 |
622
+ | `sftp.stat` | `{ sftp_id, path }` | 文件信息 | 查询文件属性 |
623
+
624
+ ### 端口转发
625
+
626
+ | 方法 | 参数 | 返回 | 说明 |
627
+ |------|------|------|------|
628
+ | `portfwd.add` | `{ conn_id, type, local_port, remote_host, remote_port }` | `{ rule_id }` | 添加转发规则 |
629
+ | `portfwd.remove` | `{ conn_id, rule_id }` | — | 移除转发规则 |
630
+ | `portfwd.list` | `{ conn_id }` | 规则列表 | 列出转发规则 |
631
+
632
+ ### 引擎管理
633
+
634
+ | 方法 | 参数 | 返回 | 说明 |
635
+ |------|------|------|------|
636
+ | `engine.ping` | `{}` | `{ ok: true }` | 心跳检测 |
637
+ | `engine.stats` | `{}` | 运行时统计 | 引擎统计信息 |
638
+ | `engine.shutdown` | `{}` | — | 关闭引擎 |
639
+ | `bye` | `{}` | — | 断开 IPC 连接 |
640
+
641
+ ### 保活管理
642
+
643
+ | 方法 | 参数 | 返回 | 说明 |
644
+ |------|------|------|------|
645
+ | `keepalive.set_interval` | `{ interval_ms }` | — | 设置心跳间隔(毫秒)。引擎无 0=禁用语义,设 0 会导致忙循环,不建议 |
646
+ | `keepalive.get_interval` | `{}` | 当前间隔 | 查询心跳间隔 |
647
+ | `keepalive.get_status` | `{}` | 连接保活状态 | 查询各连接保活状态 |
648
+
649
+ ### 反向 RPC(引擎 → Ruby)
650
+
651
+ | 方法 | 触发时机 | 说明 |
652
+ |------|---------|------|
653
+ | `hostkey.resolve` | 引擎连接前校验主机密钥 | Ruby 返回 `accept` / `reject` / `once` |
654
+
655
+ ### 推送事件(引擎 → Ruby,notification)
656
+
657
+ | 方法 | 说明 |
658
+ |------|------|
659
+ | `channel.data` | 通道收到远端数据 |
660
+ | `channel.eof` | 通道收到 EOF |
661
+ | `channel.extended_data` | 通道收到扩展数据(如 stderr) |
662
+ | `conn.ready` | 连接已就绪 |
663
+ | `conn.failed` | 连接失败 |
664
+ | `conn.closed` | 连接被远端关闭 |
665
+
666
+ ---
667
+
668
+ ## 九、架构说明
669
+
670
+ ### 启动流程
671
+
672
+ ```
673
+ 用户执行 Ruby 进程 引擎进程(Rust / Erlang)
674
+ ─────── ────────── ──────────
675
+ ssh-client connect 10.0.0.1
676
+ -u admin
677
+ │ │ │
678
+ ▼ ▼ │
679
+ ┌──────────┐ ┌──────────────┐ │
680
+ │ Thor CLI │──调用──▶ │ SSH::Client │ │
681
+ │ 参数解析 │ │ .new │ │
682
+ └──────────┘ └──────┬───────┘ │
683
+ │ │
684
+ start_engine() │
685
+ │ │
686
+ ┌────────┴────────┐ │
687
+ │ Process.spawn │────拉起子进程───────▶│ ssh_core_rs(Rust)
688
+ │ 按 backend 选择 │ │ 或 ssh_core(Erlang)
689
+ │ 引擎二进制 │ │ 写入端点文件
690
+ └────────┬────────┘ │
691
+ │ │
692
+ 读取端点文件,建立 IPC 连接(JSON-RPC) │
693
+ │◀════════socket════════════════▶│
694
+ │ │
695
+ vault.resolve_credentials(密码解密) │
696
+ │ │
697
+ IPC call "conn.connect" ───────────────▶│ ssh:connect() / russh
698
+ │ │ 建立SSH连接
699
+ │◀────返回 conn_id ──────────────│
700
+ │ │
701
+ session.open_terminal │
702
+ IPC call "channel.open" ───────────────▶│ 打开shell通道
703
+ │◀────返回 channel_id ───────────│
704
+ │ │
705
+ 进入交互循环 │
706
+ ┌──────────────┐ │
707
+ │ 用户输入命令 │──IPC──▶ channel.send ──▶│ 发往远程服务器
708
+ │ 终端输出显示 │◀─IPC─── channel.data ───│ 收到服务器响应
709
+ └──────────────┘ │
710
+ │ │
711
+ 输入 exit → 关闭连接 → 停止引擎进程 │
712
+ ```
713
+
714
+ ### 分工
715
+
716
+ | | Ruby | Rust / Erlang 引擎 |
717
+ |---|---|---|
718
+ | **职责** | 调度、配置、终端渲染、加密 | SSH 协议、连接管理、通道复用 |
719
+ | **进程** | 主进程 | 子进程(引擎与 Ruby 隔离,崩溃不影响 Ruby) |
720
+ | **通信** | 发 JSON-RPC 请求,收推送事件 | 响应请求,推送 channel.data 等事件 |
721
+
722
+ ### 关键模块在运行时的角色
723
+
724
+ - **`bin/ssh-client`** — 入口,Thor 解析参数后调 `Client`
725
+ - **`SSH::Client`** — 总指挥,按 `backend` 拉起对应引擎、建立 IPC、组合各子系统
726
+ - **`IPC::Router`** — 管所有 JSON-RPC 往来,含订阅/反向 RPC
727
+ - **`Security::Vault`** — 连接前把 `~vault:xxx` 凭据引用解密成明文
728
+ - **`Security::HostKey`** — 引擎连接时反问 Ruby"这主机密钥信不信"(双向 RPC)
729
+ - **`Terminal::Emulator`** — 收到远端字节后输入给 `AnsiParser`,解析 ANSI 转义、驱动 `Screen` 渲染
730
+
731
+ ---
732
+
733
+ ## 十、常见问题(FAQ)
734
+
735
+ ### 1. CLI 的 `-J` 跳板机为什么无效?
736
+
737
+ CLI 默认使用 Rust 引擎(`DEFAULT_BACKEND = :rust`),而 `jumps` 目前仅 Erlang 引擎实现,且 CLI 层暂未提供 `--backend` 选项。如需跳板机:
738
+
739
+ ```ruby
740
+ client = NetworkInfraUtility::SSH::Client.new(backend: :erlang)
741
+ client.start_engine
742
+ session = client.connect(host: "192.168.1.100", user: "deploy",
743
+ jumps: [{ user: "jump", host: "10.0.0.1", port: 22 }])
744
+ ```
745
+
746
+ ### 2. 引擎无法启动怎么办?
747
+
748
+ 检查三处:编译产物是否存在(`ext/ssh_core_rs/target/release/`)、启动脚本是否有执行权限、日志目录(`~/.network-infra-utility/logs/engine.err`)报错内容。
749
+
750
+ ### 3. 首次连接提示主机密钥?
751
+
752
+ 这是 `Security::HostKey` 的正常行为,确认指纹后自动保存到 `known_hosts.yml`。若密钥变更会拒绝连接,需手动删除条目。
753
+
754
+ ### 4. 命令行窗口中文乱码?
755
+
756
+ Windows 下请使用 `chcp 65001` 切换到 UTF-8 代码页后再运行 CLI。
757
+
758
+ ---
759
+
760
+ ## 十一、相关文档
761
+
762
+ - 设计文档:`service/ssh/design/`(功能需求文档、HLD、LLD)
763
+ - 主 README:`service/ssh/README.md`
764
+ - 引擎源码:`service/ssh/ext/ssh_core_rs/`(Rust)、`service/ssh/ext/ssh_core/`(Erlang)