@hyzyn/dsh-docker 0.8.0 → 0.9.0-rc.1

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/lib/ssh-exec.d.ts CHANGED
@@ -1,4 +1,7 @@
1
- import type { ConnectConfig } from 'ssh2';
1
+ import type { ChildProcess } from 'node:child_process';
2
+ import { Duplex } from 'node:stream';
3
+ import { Client } from 'ssh2';
4
+ import type { ClientChannel, ConnectConfig } from 'ssh2';
2
5
  /**
3
6
  * TOFU 主机指纹记录(与 tty 0.19.0 同形状:同一 host:port 一组指纹)。
4
7
  *
@@ -29,7 +32,42 @@ export interface SshSpec {
29
32
  passphrase?: string;
30
33
  password?: string;
31
34
  agentForward?: boolean;
35
+ /** 经跳板机连接(ProxyJump 语义,**单跳**);缺省 = 直连。与 tty 的 `SshSpec.jump` 同形。 */
36
+ jump?: SshJumpSpec;
37
+ /**
38
+ * 代理命令(ProxyCommand 语义,与 tty 的 `SshSpec.proxyCommand` 同形):本机执行的命令,
39
+ * stdin/stdout 当 SSH 传输。本包同样**只从 tty 连接簿读**(docker 侧不做界面)。
40
+ *
41
+ * **闸门只有一处**:tty settings 的 `allowProxyCommand`(本包通过 `readTtyBooks` 的 settings
42
+ * 句柄同读)。关着时携带它的目标**明确失败**,不退回直连——理由见 tty `src/ssh.ts`。
43
+ */
44
+ proxyCommand?: string;
45
+ }
46
+ /**
47
+ * 跳板机规格(与 tty `src/ssh.ts` 的 `SshJumpSpec` **逐字同形**,两包各持一份类型)。
48
+ *
49
+ * 本包只从 tty 的连接簿读它(`readTtyBooks`)——docker 侧**不做跳板机界面**:目标是
50
+ * 「一处配置、两处生效」。`username` / `auth` / `keyPath` / `passphrase` / `password`
51
+ * 缺省时**继承目标那一跳**(见 `jumpSpecOf`)。
52
+ */
53
+ export interface SshJumpSpec {
54
+ host: string;
55
+ port?: number;
56
+ username?: string;
57
+ auth?: 'agent' | 'key' | 'password';
58
+ keyPath?: string;
59
+ passphrase?: string;
60
+ password?: string;
32
61
  }
62
+ /**
63
+ * 清洗一份跳板机输入(`readTtyBooks` 用)。返回 `undefined` = 没配跳板机——不给下游留
64
+ * `host: ''` 的半个对象(那会让拨号去连空主机名)。
65
+ */
66
+ export declare function sanitizeJumpSpec(input: unknown): SshJumpSpec | undefined;
67
+ /** 跳板机展示串(`user@host:port`);没配时返回空串。**凭据不进这里**。 */
68
+ export declare function jumpTargetLabel(spec: SshSpec): string;
69
+ /** 目标那一跳的展示串 + 跳板机 / 代理命令后缀(错误文案用;理由见 tty `src/ssh.ts` 的同名注释)。 */
70
+ export declare function targetWithJump(spec: SshSpec): string;
33
71
  /** 一条命令的执行结果。 */
34
72
  export interface ExecResult {
35
73
  /** 退出码;进程被信号杀死或 channel 异常时为 null。 */
@@ -106,6 +144,81 @@ export declare function sshTarget(spec: SshSpec): string;
106
144
  * 命令都以 argv 数组构造,禁止把用户输入拼进字符串。
107
145
  */
108
146
  export declare function shJoin(argv: readonly string[]): string;
147
+ /**
148
+ * 连接池键:同一主机同一账号复用一条 SSH 连接。
149
+ *
150
+ * host 要 trim + 小写(D111):否则 `NAS.example` 与 `nas.example` 各建一条连接,而
151
+ * `MAX_STREAMS_PER_TARGET` 与 `shouldRecycleConn` 都是**按连接**计的 → 同一台主机的
152
+ * 长流额度被悄悄翻倍(恰好掩盖 D07 想暴露的 MaxSessions 问题)。口径与 TOFU 的
153
+ * hostVerifier(D03)保持一致:那里也用 `trim().toLowerCase()` 分组指纹。
154
+ */
155
+ /**
156
+ * 池键。**跳板机 / 代理命令身份必须并进来**:不同 bastion(或不同代理命令)到同一目标
157
+ * 绝不是同一条连接——只按 `user@host:port` 记的话,第二个 bastion 会静默复用第一条连接、
158
+ * 走错跳板机。这与 tty 的 SFTP 池不同(那边键是 `JSON.stringify(spec)`,天然带上 jump)。
159
+ *
160
+ * 代理命令**只并入它的哈希**,不并入原文:原文可能含凭据(`-i /path/key`、甚至嵌 token),
161
+ * 而池键会进日志/错误文案附近的诊断路径——摘要足够区分且不泄露。
162
+ */
163
+ export declare function poolKey(spec: SshSpec): string;
164
+ /**
165
+ * 拨跳板机并借一条 forwardOut 通道(ProxyJump 单跳)。与 tty `src/ssh.ts` 的 dialJump 同序。
166
+ * **导出仅供单测**(`test/ssh-jump.test.ts` 用假 ssh2 验「先拨跳板机、再把通道当 sock」)。
167
+ */
168
+ export declare function dialJump(options: {
169
+ spec: SshSpec;
170
+ store?: HostKeyStore | undefined;
171
+ logger?: ExecLogger | undefined;
172
+ }): Promise<{
173
+ bastion: Client;
174
+ sock: ClientChannel;
175
+ }>;
176
+ /** 代理命令长度上限(与 tty 同值:它是一条命令行)。 */
177
+ export declare const PROXY_COMMAND_MAX = 2000;
178
+ /**
179
+ * 闸门关着时报什么错。**与 tty 是同一个开关**(tty settings 的 `allowProxyCommand`)——
180
+ * 连接簿只有一处,开关也只能有一处,否则「连接簿配了、docker 不认」会很难解释。
181
+ */
182
+ export declare const PROXY_COMMAND_DISABLED: string;
183
+ /**
184
+ * 清洗一份代理命令输入(`readTtyBooks` 用)。返回 `undefined` = 没配。
185
+ * 只做形状校验(非空 / 单行 / 长度);命令内容不解释——「能不能执行」由闸门决定。
186
+ */
187
+ export declare function sanitizeProxyCommand(input: unknown): string | undefined;
188
+ /**
189
+ * 展开 `%h` / `%p` / `%r` / `%n` / `%%`(与 OpenSSH 同义,与 tty 逐字同口径:
190
+ * 代入值必须过白名单,否则拒绝执行——理由见 tty `expandProxyCommand`)。
191
+ */
192
+ export declare function expandProxyCommand(command: string, spec: SshSpec): string;
193
+ /** 代理命令传输(与 tty `ProxyCommandDial` 同形)。 */
194
+ export interface ProxyCommandDial {
195
+ child: ChildProcess;
196
+ sock: Duplex;
197
+ failure(): Error | null;
198
+ /**
199
+ * 已经攒到的 stderr 摘要(`;代理命令 stderr: …` 或空串)——**错误路径的兜底**。
200
+ *
201
+ * 与 tty 同因(真机验收暴露的竞态):ssh2 一看到流断了就报错,而「子进程退出 / 传输关闭」
202
+ * 比它晚 1~2ms,那一刻 `failure()` 还是 null,最有用的那句就被丢掉。
203
+ */
204
+ stderrHint(): string;
205
+ dispose(): void;
206
+ }
207
+ /**
208
+ * 启动代理命令(ProxyCommand)并把它的 stdio 当作目标连接的传输。
209
+ *
210
+ * 与 tty `src/ssh.ts` 的 dialProxyCommand **逐句同序**(两包不互相 import,只能各写一份;
211
+ * 语义口径由这份注释与单测钉住):闸门 → 展开 → spawn → 提前退出拖垮传输 → stderr 常驻排空。
212
+ * **导出仅供单测**。
213
+ */
214
+ export declare function dialProxyCommand(options: {
215
+ spec: SshSpec;
216
+ /** 闸门求值(缺省 = 关):本包从 tty settings 读,按**每次拨号**求值——开关一关立刻生效。 */
217
+ allowed?: () => boolean;
218
+ logger?: ExecLogger | undefined;
219
+ }): Promise<ProxyCommandDial>;
220
+ /** 代理命令失败的事实 → 错误文案后缀(空串 = 没失败),与 tty 同口径。 */
221
+ export declare function proxyFailureSuffix(proxy: ProxyCommandDial | null): string;
109
222
  /**
110
223
  * 长流配额判定(纯函数,便于回归):`busy` 是连接上正在推送的长流数。
111
224
  * @param target - 目标标签,只用于文案。
@@ -122,6 +235,15 @@ export declare function streamBudgetError(target: string, busy: number, max?: nu
122
235
  * @param message - ssh2 给出的原始错误文案。
123
236
  * @returns 补了指向性说明的文案;不认识的原样返回。
124
237
  */
238
+ /**
239
+ * SSH 超时文案里的**跳板机提示**(项目级 ROADMAP 第 2 项)。
240
+ *
241
+ * 为什么值得单独一句话:本插件经数据级复用读得到 tty 的连接簿,但**不读 `~/.ssh/config`**,
242
+ * 所以「配了跳板机的目标连不上」在 docker 侧只能表现为一句通用超时。企业内网主机几乎都
243
+ * 要过 bastion,用户需要的是「可能是什么原因、以及这个版本到底支不支持」——而不是 20 秒后
244
+ * 一句放之四海皆准的「主机无响应」。
245
+ */
246
+ export declare const SSH_TIMEOUT_HINT: string;
125
247
  export declare function describeExecError(message: string): string;
126
248
  /**
127
249
  * 这条 ssh2 错误是不是**传输层 / 连接层**的(而不是命令自己失败)。
@@ -152,9 +274,27 @@ export declare function shouldRecycleConn(conn: {
152
274
  export declare class RemoteExec {
153
275
  private readonly logger;
154
276
  private readonly store;
277
+ /**
278
+ * ProxyCommand 闸门求值器(缺省 = 恒关)。
279
+ *
280
+ * 为什么是**回调**而不是构造时读一次的布尔值:开关归 tty settings,本包的 settings 句柄是
281
+ * 运行时才就绪的,而且用户随时可能关掉它——关掉之后必须**立刻**对下一次拨号生效
282
+ * (留着旧值意味着「关了还能用」,那正是这一档最不能出的错)。求值只读内存,无 IO。
283
+ */
284
+ private readonly options;
155
285
  private readonly conns;
156
286
  private sweeper;
157
- constructor(logger: ExecLogger, store: HostKeyStore);
287
+ constructor(logger: ExecLogger, store: HostKeyStore,
288
+ /**
289
+ * ProxyCommand 闸门求值器(缺省 = 恒关)。
290
+ *
291
+ * 为什么是**回调**而不是构造时读一次的布尔值:开关归 tty settings,本包的 settings 句柄是
292
+ * 运行时才就绪的,而且用户随时可能关掉它——关掉之后必须**立刻**对下一次拨号生效
293
+ * (留着旧值意味着「关了还能用」,那正是这一档最不能出的错)。求值只读内存,无 IO。
294
+ */
295
+ options?: {
296
+ proxyCommandAllowed?: () => boolean;
297
+ });
158
298
  /** 插件卸载:关定时器与全部连接(幂等)。 */
159
299
  disposeAll(): void;
160
300
  /** 在远程执行一条命令(argv 形式,内部做 shell 转义)。 */