@jalsoedesign/dockline-ssh-client 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dockline contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,61 @@
1
+ [Documentation](https://h2ooooooo.github.io/dockline/ssh/quick-start) · [Source](https://github.com/h2ooooooo/dockline/tree/main/packages/ssh-client)
2
+
3
+ # SSH commands
4
+
5
+ `@jalsoedesign/dockline-ssh-client` provides scoped SSH command execution and SFTP transfers. It is independently installable; core, FTP and SFTP consumers do not need to install it.
6
+
7
+ ```sh
8
+ npm install @jalsoedesign/dockline-ssh-client
9
+ ```
10
+
11
+ This new workspace must be published before that registry command is available. Repository users can build it with `npm run build --workspace @jalsoedesign/dockline-ssh-client`.
12
+
13
+ ```ts
14
+ import {SshClient} from '@jalsoedesign/dockline-ssh-client';
15
+
16
+ const client = new SshClient({
17
+ host: 'server.example.com',
18
+ port: 22,
19
+ username: 'deployer',
20
+ privateKeyPath: '/home/me/.ssh/deployer',
21
+ hostFingerprint: process.env.SSH_HOST_FINGERPRINT,
22
+ });
23
+
24
+ await client.withConnection(async server => {
25
+ const result = await server.exec('uptime');
26
+
27
+ console.log(result.stdout);
28
+
29
+ await server.download({
30
+ sourceFile: '/var/backups/export.sql',
31
+ destinationFile: './backups/export.sql',
32
+ });
33
+
34
+ await server.exec('systemctl', ['restart', 'website'], {
35
+ sudo: true,
36
+ timeout: '1m',
37
+ });
38
+ });
39
+ ```
40
+
41
+ Without an explicit fingerprint or a supplied `trust(host, port, fingerprint)` callback, connections reject unknown hosts. The application owns trust persistence and any prompt UI. Supply optional `ask`, `log`, `signal` and `directory` callbacks/settings as the constructor's second argument. `ask` receives an input/password challenge and returns its answer. No CLI prompt implementation is bundled.
42
+
43
+ `withConnection` authenticates and closes its connection in `finally`. Passwords, private-key passphrases, explicit SSH agents and keyboard-interactive authentication are supported. Agent forwarding is not enabled. An SFTP account does not necessarily have command-execution permission.
44
+
45
+ ## Command API
46
+
47
+ - `exec(program, args?, options?)` quotes each argument for a POSIX remote shell.
48
+ - `shell(command, options?)` explicitly executes `sh -c` for shell expressions.
49
+ - `asUser(username, callback)` runs scoped commands through `sudo -u`.
50
+ - `download({sourceFile, destinationFile, timeout?, maxBytes?})` uses a temporary local file before replacement.
51
+ - `upload({sourceFile, destinationFile, timeout?, maxBytes?})` streams a local file using Dockline SFTP.
52
+
53
+ Exec options include `timeout`, `sudo` (boolean or target username) and `allowFailure`. Results include `code`, `stdout` and `stderr`; returned output retains the last 1 MiB per stream while `log` receives streamed output. Nonzero exit codes reject by default. Timeouts accept positive milliseconds or strings such as `30s`, `5m` and `1h`; the default command/transfer deadline is one hour. Commands are never automatically retried.
54
+
55
+ The transfer helpers open separate SFTP connections with the same resolved credentials and host verifier. They have no default byte cap. A transfer does not inherit sudo privileges: files created by privileged commands must be readable by the SFTP user.
56
+
57
+ Sudo defaults to noninteractive mode. Set `sudo.password: 'prompt'` with an `ask` callback, or `sudo.passwordEnv`, when a password is required. Passwords travel through stdin when the sudo prompt appears, not command arguments. Servers requiring an interactive TTY are outside this API's initial scope.
58
+
59
+ Cancellation requests command termination and closes channels. The remote server may ignore a signal; inspect remote state before retrying. Remote Windows shell syntax and persistent interactive `sudo su` sessions are not supported.
60
+
61
+ See the [SSH package source](https://github.com/h2ooooooo/dockline/tree/main/packages/ssh-client).
@@ -0,0 +1,58 @@
1
+ import ssh2 from 'ssh2';
2
+ import { type SshOptions, type Question } from './types.js';
3
+ export interface ResolvedSsh extends SshOptions {
4
+ host: string;
5
+ port: number;
6
+ username: string;
7
+ password?: string;
8
+ passphrase?: string;
9
+ }
10
+ export interface ExecOptions {
11
+ timeout?: string | number;
12
+ sudo?: boolean | string;
13
+ allowFailure?: boolean;
14
+ }
15
+ export interface ExecResult {
16
+ code: number;
17
+ stdout: string;
18
+ stderr: string;
19
+ }
20
+ export interface FileTransfer {
21
+ sourceFile: string;
22
+ destinationFile: string;
23
+ timeout?: string | number;
24
+ maxBytes?: number;
25
+ }
26
+ export interface SshServices {
27
+ resolve(options: SshOptions): Promise<ResolvedSsh>;
28
+ ask(question: Question): Promise<string | boolean>;
29
+ trust(host: string, port: number, fingerprint: string): Promise<boolean>;
30
+ log(message: string): void;
31
+ signal: AbortSignal;
32
+ directory: string;
33
+ }
34
+ export declare function quotePosix(value: string): string;
35
+ export declare class SshSession {
36
+ private readonly client;
37
+ private readonly config;
38
+ private readonly services;
39
+ private readonly user?;
40
+ private readonly credentials;
41
+ constructor(client: ssh2.Client, config: ResolvedSsh, services: SshServices, user?: string | undefined, credentials?: {
42
+ sudoPassword?: string;
43
+ });
44
+ asUser<T>(user: string, callback: (session: SshSession) => Promise<T>): Promise<T>;
45
+ exec(program: string, args?: string[], options?: ExecOptions): Promise<ExecResult>;
46
+ shell(command: string, options?: ExecOptions): Promise<ExecResult>;
47
+ private execute;
48
+ private transfer;
49
+ download(options: FileTransfer): Promise<void>;
50
+ upload(options: FileTransfer): Promise<void>;
51
+ }
52
+ export declare class SshClient {
53
+ private readonly services;
54
+ constructor(config: ResolvedSsh, options?: Partial<Omit<SshServices, 'resolve'>>);
55
+ withConnection<T>(callback: (server: SshSession) => Promise<T>): Promise<T>;
56
+ withConnection<T>(options: SshOptions, callback: (server: SshSession) => Promise<T>): Promise<T>;
57
+ }
58
+ //# sourceMappingURL=SshClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SshClient.d.ts","sourceRoot":"","sources":["../src/SshClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,256 @@
1
+ import ssh2 from 'ssh2';
2
+ const { Client } = ssh2;
3
+ import { createHash, randomUUID } from 'node:crypto';
4
+ import { readFile, mkdir, rename, unlink } from 'node:fs/promises';
5
+ import { createReadStream, createWriteStream } from 'node:fs';
6
+ import { Readable } from 'node:stream';
7
+ import { pipeline } from 'node:stream/promises';
8
+ import path from 'node:path';
9
+ import { SftpConnector } from '@jalsoedesign/dockline-sftp-client';
10
+ import { homedir } from 'node:os';
11
+ import { milliseconds } from './types.js';
12
+ const absolutePath = (base, value) => path.resolve(base, value.startsWith('~/') ? path.join(homedir(), value.slice(2)) : value);
13
+ export function quotePosix(value) {
14
+ if (value.includes('\0')) {
15
+ throw new Error('SSH arguments cannot contain NUL.');
16
+ }
17
+ return `'${value.replaceAll("'", "'\\''")}'`;
18
+ }
19
+ export class SshSession {
20
+ client;
21
+ config;
22
+ services;
23
+ user;
24
+ credentials;
25
+ constructor(client, config, services, user, credentials = {}) {
26
+ this.client = client;
27
+ this.config = config;
28
+ this.services = services;
29
+ this.user = user;
30
+ this.credentials = credentials;
31
+ }
32
+ async asUser(user, callback) {
33
+ if (!/^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(user)) {
34
+ throw new Error('Invalid sudo user.');
35
+ }
36
+ return callback(new SshSession(this.client, this.config, this.services, user, this.credentials));
37
+ }
38
+ exec(program, args = [], options = {}) {
39
+ return this.execute([program, ...args].map(quotePosix).join(' '), options);
40
+ }
41
+ shell(command, options = {}) {
42
+ return this.exec('sh', ['-c', command], options);
43
+ }
44
+ async execute(command, options) {
45
+ const sudoUser = typeof options.sudo === 'string' ? options.sudo : this.user;
46
+ const sudo = Boolean(options.sudo || sudoUser);
47
+ const marker = `DOCKLINE_SUDO_${randomUUID()}`;
48
+ let password;
49
+ if (sudo && (this.config.sudo?.password === 'prompt' || this.config.sudo?.passwordEnv)) {
50
+ password = this.config.sudo.passwordEnv ?
51
+ process.env[this.config.sudo.passwordEnv] : this.credentials.sudoPassword;
52
+ if (password === undefined && this.config.sudo.passwordEnv) {
53
+ throw new Error(`Missing sudo secret environment variable: ${this.config.sudo.passwordEnv}`);
54
+ }
55
+ password ??= String(await this.services.ask({
56
+ id: `sudo:${this.config.username}@${this.config.host}`,
57
+ type: 'password',
58
+ message: 'Sudo password',
59
+ required: true,
60
+ }));
61
+ this.credentials.sudoPassword = password;
62
+ }
63
+ if (sudo) {
64
+ command = `sudo ${password === undefined ? '-n' : `-S -p ${quotePosix(marker)}`} ${sudoUser ? `-u ${quotePosix(sudoUser)} ` : ''}-- ${command}`;
65
+ }
66
+ const timeout = AbortSignal.timeout(milliseconds(options.timeout ?? '1h'));
67
+ const signal = AbortSignal.any([this.services.signal, timeout]);
68
+ signal.throwIfAborted();
69
+ return new Promise((resolve, reject) => {
70
+ let channel;
71
+ let stdout = '';
72
+ let stderr = '';
73
+ let pending = '';
74
+ let sent = false;
75
+ const finish = (error, code = -1) => {
76
+ signal.removeEventListener('abort', abort);
77
+ this.client.removeListener('close', disconnected);
78
+ if (error) {
79
+ reject(error);
80
+ }
81
+ else if (code !== 0 && !options.allowFailure) {
82
+ reject(new Error(`SSH command exited with status ${code}. ${stderr.slice(-2000)}`));
83
+ }
84
+ else {
85
+ resolve({ code, stdout, stderr });
86
+ }
87
+ };
88
+ const disconnected = () => finish(new Error('SSH connection closed before command completion. Remote process state is unknown.'));
89
+ const abort = () => {
90
+ try {
91
+ channel?.signal('TERM');
92
+ }
93
+ catch { /* Server may reject signals. */ }
94
+ channel?.close();
95
+ finish(new Error('SSH command cancelled or timed out. The remote process may still be running.'));
96
+ };
97
+ signal.addEventListener('abort', abort, { once: true });
98
+ this.client.once('close', disconnected);
99
+ this.client.exec(command, (error, stream) => {
100
+ if (error) {
101
+ finish(error);
102
+ return;
103
+ }
104
+ channel = stream;
105
+ if (signal.aborted) {
106
+ abort();
107
+ return;
108
+ }
109
+ stream.on('error', finish);
110
+ stream.on('data', (chunk) => {
111
+ const text = chunk.toString();
112
+ stdout = (stdout + text).slice(-1024 * 1024);
113
+ this.services.log(text);
114
+ });
115
+ stream.stderr.on('data', (chunk) => {
116
+ pending += chunk.toString();
117
+ if (password !== undefined && !sent) {
118
+ if (!pending.includes(marker)) {
119
+ if (pending.length < 8192) {
120
+ return;
121
+ }
122
+ }
123
+ else {
124
+ pending = pending.replace(marker, '');
125
+ stream.write(password + '\n');
126
+ sent = true;
127
+ }
128
+ }
129
+ stderr = (stderr + pending).slice(-1024 * 1024);
130
+ this.services.log(pending);
131
+ pending = '';
132
+ });
133
+ stream.on('close', (code) => {
134
+ stderr = (stderr + pending.replaceAll(marker, '')).slice(-1024 * 1024);
135
+ finish(undefined, typeof code === 'number' ? code : -1);
136
+ });
137
+ });
138
+ });
139
+ }
140
+ async transfer(options, upload) {
141
+ const signal = AbortSignal.any([this.services.signal, AbortSignal.timeout(milliseconds(options.timeout ?? '1h'))]);
142
+ const connector = new SftpConnector({
143
+ ...this.config,
144
+ initialPath: '',
145
+ abortSignal: signal,
146
+ hostVerifier: (key) => this.services.trust(this.config.host, this.config.port, 'SHA256:' + createHash('sha256').update(key).digest('base64').replace(/=+$/, '')),
147
+ keyboardInteractive: this.config.keyboardInteractive ? async (challenge) => Promise.all(challenge.prompts.map((prompt, index) => this.services.ask({ id: `ssh-challenge:${index}`, message: prompt.prompt, type: prompt.echo ? 'input' : 'password' }).then(String))) : undefined,
148
+ });
149
+ try {
150
+ await connector.connect();
151
+ if (upload) {
152
+ await connector.write(options.destinationFile, () => createReadStream(absolutePath(this.services.directory, options.sourceFile)), { abortSignal: signal, maxBytes: options.maxBytes });
153
+ }
154
+ else {
155
+ const destination = absolutePath(this.services.directory, options.destinationFile);
156
+ const temporary = `${destination}.dockline-${randomUUID()}`;
157
+ await mkdir(path.dirname(destination), { recursive: true });
158
+ try {
159
+ const contents = await connector.read(options.sourceFile, {
160
+ abortSignal: signal,
161
+ maxBytes: options.maxBytes,
162
+ });
163
+ await pipeline(Buffer.isBuffer(contents) ? Readable.from([contents]) : contents, createWriteStream(temporary, { flags: 'wx', mode: 0o600 }), { signal });
164
+ await rename(temporary, destination);
165
+ }
166
+ finally {
167
+ await unlink(temporary).catch(() => { });
168
+ }
169
+ }
170
+ }
171
+ finally {
172
+ await connector.disconnect();
173
+ }
174
+ }
175
+ download(options) {
176
+ return this.transfer(options, false);
177
+ }
178
+ upload(options) {
179
+ return this.transfer(options, true);
180
+ }
181
+ }
182
+ export class SshClient {
183
+ services;
184
+ constructor(config, options = {}) {
185
+ this.services = {
186
+ directory: options.directory ?? process.cwd(),
187
+ signal: options.signal ?? new AbortController().signal,
188
+ log: options.log ?? (() => { }),
189
+ ask: options.ask ?? (async () => {
190
+ throw new Error('This operation requires a credential prompt callback.');
191
+ }),
192
+ trust: options.trust ?? (async () => false),
193
+ resolve: async (overrides) => {
194
+ const changed = (overrides.host && overrides.host !== config.host) || (overrides.port &&
195
+ overrides.port !== config.port) || (overrides.username && overrides.username !== config.username);
196
+ return {
197
+ ...config,
198
+ ...(changed ? {
199
+ password: undefined,
200
+ passphrase: undefined,
201
+ privateKeyPath: undefined,
202
+ agent: undefined,
203
+ hostFingerprint: undefined,
204
+ } : {}),
205
+ ...overrides,
206
+ };
207
+ },
208
+ };
209
+ }
210
+ async withConnection(options, callback) {
211
+ const config = await this.services.resolve(typeof options === 'function' ? {} : options);
212
+ const action = typeof options === 'function' ? options : callback;
213
+ const services = {
214
+ ...this.services,
215
+ trust: config.hostFingerprint ?
216
+ async (_host, _port, fingerprint) => fingerprint === config.hostFingerprint : this.services.trust,
217
+ };
218
+ const client = new Client();
219
+ const abort = () => client.destroy();
220
+ this.services.signal.addEventListener('abort', abort, { once: true });
221
+ try {
222
+ this.services.signal.throwIfAborted();
223
+ await new Promise((resolve, reject) => {
224
+ client.once('ready', resolve);
225
+ client.on('error', reject);
226
+ client.once('close', () => reject(new Error('SSH connection closed during authentication.')));
227
+ client.on('keyboard-interactive', (_name, _instructions, _language, prompts, finish) => {
228
+ void Promise.all(prompts.map((prompt, index) => this.services.ask({ id: `ssh-challenge:${index}`, message: prompt.prompt, type: prompt.echo ? 'input' : 'password' }))).then(answers => finish(answers.map(String)), reject);
229
+ });
230
+ void (async () => {
231
+ client.connect({
232
+ host: config.host,
233
+ port: config.port,
234
+ username: config.username,
235
+ password: config.password,
236
+ privateKey: config.privateKeyPath ? await readFile(config.privateKeyPath) : undefined,
237
+ passphrase: config.passphrase,
238
+ agent: config.agent,
239
+ tryKeyboard: config.keyboardInteractive,
240
+ readyTimeout: 60000,
241
+ hostVerifier: (key, verify) => {
242
+ const fingerprint = 'SHA256:' + createHash('sha256').update(key).digest('base64').replace(/=+$/, '');
243
+ void services.trust(config.host, config.port, fingerprint).then(verify, () => verify(false));
244
+ },
245
+ });
246
+ })().catch(reject);
247
+ });
248
+ return await action(new SshSession(client, config, services));
249
+ }
250
+ finally {
251
+ this.services.signal.removeEventListener('abort', abort);
252
+ client.destroy();
253
+ }
254
+ }
255
+ }
256
+ //# sourceMappingURL=SshClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SshClient.js","sourceRoot":"","sources":["../src/SshClient.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ export { SshClient, SshSession, quotePosix } from './SshClient.js';
2
+ export type { ResolvedSsh, SshServices, ExecOptions, ExecResult, FileTransfer } from './SshClient.js';
3
+ export type { SshOptions, Question } from './types.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAC,MAAM,gBAAgB,CAAC;AACjE,YAAY,EAAC,WAAW,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAC,MAAM,gBAAgB,CAAC;AACpG,YAAY,EAAC,UAAU,EAAE,QAAQ,EAAC,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { SshClient, SshSession, quotePosix } from './SshClient.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,UAAU,EAAE,UAAU,EAAC,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,23 @@
1
+ export interface SshOptions {
2
+ password?: string;
3
+ passphrase?: string;
4
+ host?: string;
5
+ port?: number;
6
+ username?: string;
7
+ privateKeyPath?: string;
8
+ agent?: string;
9
+ keyboardInteractive?: boolean;
10
+ hostFingerprint?: string;
11
+ sudo?: {
12
+ password?: 'prompt' | 'none';
13
+ passwordEnv?: string;
14
+ };
15
+ }
16
+ export interface Question {
17
+ id: string;
18
+ message: string;
19
+ type: 'password' | 'input';
20
+ required?: boolean;
21
+ }
22
+ export declare function milliseconds(value: string | number): number;
23
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,IAAI,CAAC,EAAE;QAAC,QAAQ,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAC,CAAC;CAC/D;AACD,MAAM,WAAW,QAAQ;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;CACtB;AACD,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAe3D"}
package/dist/types.js ADDED
@@ -0,0 +1,15 @@
1
+ export function milliseconds(value) {
2
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)$/.exec(String(value));
3
+ const scales = {
4
+ ms: 1,
5
+ s: 1000,
6
+ m: 60000,
7
+ h: 3600000,
8
+ };
9
+ const result = typeof value === 'number' ? value : match ? Number(match[1]) * scales[match[2]] : 0;
10
+ if (!Number.isFinite(result) || result <= 0 || result > 2147483647) {
11
+ throw new Error('Invalid positive timeout.');
12
+ }
13
+ return Math.ceil(result);
14
+ }
15
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,YAAY,CAAC,KAAsB;IAC/C,MAAM,KAAK,GAAG,6BAA6B,CAAC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;IAChE,MAAM,MAAM,GAA2B;QACnC,EAAE,EAAE,CAAC;QACL,CAAC,EAAE,IAAI;QACP,CAAC,EAAE,KAAK;QACR,CAAC,EAAE,OAAO;KACb,CAAC;IACF,MAAM,MAAM,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEnG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,IAAI,CAAC,IAAI,MAAM,GAAG,UAAU,EAAE,CAAC;QACjE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC7B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@jalsoedesign/dockline-ssh-client",
3
+ "version": "1.0.0",
4
+ "description": "SSH command execution, sudo scopes and file transfers for Dockline",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.js"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "src",
20
+ "LICENSE",
21
+ "README.md"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "engines": {
27
+ "node": "^22.22.2 || ^24.15.0 || >=26.0.0",
28
+ "npm": ">=12.0.2"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/h2ooooooo/dockline.git",
33
+ "directory": "packages/ssh-client"
34
+ },
35
+ "homepage": "https://h2ooooooo.github.io/dockline/",
36
+ "bugs": {
37
+ "url": "https://github.com/h2ooooooo/dockline/issues"
38
+ },
39
+ "scripts": {
40
+ "build": "node ../../scripts/build.mjs --package ssh-client",
41
+ "test": "node ../../scripts/test-package.mjs ssh-client",
42
+ "typecheck": "tsc --noEmit -p tsconfig.json",
43
+ "prepack": "npm run build"
44
+ },
45
+ "dependencies": {
46
+ "@jalsoedesign/dockline-sftp-client": "^1.0.0",
47
+ "ssh2": "^1.17.0",
48
+ "@types/ssh2": "^1.15.6"
49
+ }
50
+ }
@@ -0,0 +1,323 @@
1
+ import ssh2, {type ClientChannel} from 'ssh2';
2
+
3
+ const {Client} = ssh2;
4
+
5
+ import {createHash, randomUUID} from 'node:crypto';
6
+ import {readFile, mkdir, rename, unlink} from 'node:fs/promises';
7
+ import {createReadStream, createWriteStream} from 'node:fs';
8
+ import {Readable} from 'node:stream';
9
+ import {pipeline} from 'node:stream/promises';
10
+ import path from 'node:path';
11
+ import {SftpConnector} from '@jalsoedesign/dockline-sftp-client';
12
+ import {homedir} from 'node:os';
13
+ import {milliseconds, type SshOptions, type Question} from './types.js';
14
+
15
+ const absolutePath = (base: string, value: string) => path.resolve(base, value.startsWith('~/') ? path.join(homedir(), value.slice(2)) : value);
16
+
17
+ export interface ResolvedSsh extends SshOptions {
18
+ host: string;
19
+ port: number;
20
+ username: string;
21
+ password?: string;
22
+ passphrase?: string;
23
+ }
24
+ export interface ExecOptions {
25
+ timeout?: string | number;
26
+ sudo?: boolean | string;
27
+ allowFailure?: boolean;
28
+ }
29
+ export interface ExecResult {
30
+ code: number;
31
+ stdout: string;
32
+ stderr: string;
33
+ }
34
+ export interface FileTransfer {
35
+ sourceFile: string;
36
+ destinationFile: string;
37
+ timeout?: string | number;
38
+ maxBytes?: number;
39
+ }
40
+ export interface SshServices {
41
+ resolve(options: SshOptions): Promise<ResolvedSsh>;
42
+ ask(question: Question): Promise<string | boolean>;
43
+ trust(host: string, port: number, fingerprint: string): Promise<boolean>;
44
+ log(message: string): void;
45
+ signal: AbortSignal;
46
+ directory: string;
47
+ }
48
+
49
+ export function quotePosix(value: string): string {
50
+ if (value.includes('\0')) {
51
+ throw new Error('SSH arguments cannot contain NUL.');
52
+ }
53
+
54
+ return `'${value.replaceAll("'", "'\\''")}'`;
55
+ }
56
+
57
+ export class SshSession {
58
+ public constructor(private readonly client: ssh2.Client, private readonly config: ResolvedSsh,
59
+ private readonly services: SshServices, private readonly user?: string,
60
+ private readonly credentials: {sudoPassword?: string} = {}) {}
61
+
62
+ public async asUser<T>(user: string, callback: (session: SshSession) => Promise<T>): Promise<T> {
63
+ if (!/^[a-zA-Z0-9_][a-zA-Z0-9_.-]*$/.test(user)) {
64
+ throw new Error('Invalid sudo user.');
65
+ }
66
+
67
+ return callback(new SshSession(this.client, this.config, this.services, user, this.credentials));
68
+ }
69
+
70
+ public exec(program: string, args: string[] = [], options: ExecOptions = {}): Promise<ExecResult> {
71
+ return this.execute([program, ...args].map(quotePosix).join(' '), options);
72
+ }
73
+
74
+ public shell(command: string, options: ExecOptions = {}): Promise<ExecResult> {
75
+ return this.exec('sh', ['-c', command], options);
76
+ }
77
+
78
+ private async execute(command: string, options: ExecOptions): Promise<ExecResult> {
79
+ const sudoUser = typeof options.sudo === 'string' ? options.sudo : this.user;
80
+ const sudo = Boolean(options.sudo || sudoUser);
81
+ const marker = `DOCKLINE_SUDO_${randomUUID()}`;
82
+ let password: string | undefined;
83
+
84
+ if (sudo && (this.config.sudo?.password === 'prompt' || this.config.sudo?.passwordEnv)) {
85
+ password = this.config.sudo.passwordEnv ?
86
+ process.env[this.config.sudo.passwordEnv] : this.credentials.sudoPassword;
87
+
88
+ if (password === undefined && this.config.sudo.passwordEnv) {
89
+ throw new Error(`Missing sudo secret environment variable: ${this.config.sudo.passwordEnv}`);
90
+ }
91
+
92
+ password ??= String(await this.services.ask({
93
+ id: `sudo:${this.config.username}@${this.config.host}`,
94
+ type: 'password',
95
+ message: 'Sudo password',
96
+ required: true,
97
+ }));
98
+ this.credentials.sudoPassword = password;
99
+ }
100
+
101
+ if (sudo) {
102
+ command = `sudo ${password === undefined ? '-n' : `-S -p ${quotePosix(marker)}`} ${sudoUser ? `-u ${quotePosix(sudoUser)} ` : ''}-- ${command}`;
103
+ }
104
+
105
+ const timeout = AbortSignal.timeout(milliseconds(options.timeout ?? '1h'));
106
+ const signal = AbortSignal.any([this.services.signal, timeout]);
107
+
108
+ signal.throwIfAborted();
109
+
110
+ return new Promise<ExecResult>((resolve, reject) => {
111
+ let channel: ClientChannel | undefined;
112
+ let stdout = '';
113
+ let stderr = '';
114
+ let pending = '';
115
+ let sent = false;
116
+ const finish = (error?: Error, code = -1) => {
117
+ signal.removeEventListener('abort', abort);
118
+ this.client.removeListener('close', disconnected);
119
+
120
+ if (error) {
121
+ reject(error);
122
+ } else if (code !== 0 && !options.allowFailure) {
123
+ reject(new Error(`SSH command exited with status ${code}. ${stderr.slice(-2000)}`));
124
+ } else {
125
+ resolve({code, stdout, stderr});
126
+ }
127
+ };
128
+ const disconnected = () => finish(new Error('SSH connection closed before command completion. Remote process state is unknown.'));
129
+ const abort = () => {
130
+ try {
131
+ channel?.signal('TERM');
132
+ } catch { /* Server may reject signals. */ }
133
+
134
+ channel?.close();
135
+ finish(new Error('SSH command cancelled or timed out. The remote process may still be running.'));
136
+ };
137
+
138
+ signal.addEventListener('abort', abort, {once: true});
139
+ this.client.once('close', disconnected);
140
+ this.client.exec(command, (error, stream) => {
141
+ if (error) {
142
+ finish(error);
143
+
144
+ return;
145
+ }
146
+
147
+ channel = stream;
148
+
149
+ if (signal.aborted) {
150
+ abort();
151
+
152
+ return;
153
+ }
154
+
155
+ stream.on('error', finish);
156
+ stream.on('data', (chunk: Buffer) => {
157
+ const text = chunk.toString();
158
+
159
+ stdout = (stdout + text).slice(-1024 * 1024);
160
+ this.services.log(text);
161
+ });
162
+ stream.stderr.on('data', (chunk: Buffer) => {
163
+ pending += chunk.toString();
164
+
165
+ if (password !== undefined && !sent) {
166
+ if (!pending.includes(marker)) {
167
+ if (pending.length < 8192) {
168
+ return;
169
+ }
170
+ } else {
171
+ pending = pending.replace(marker, '');
172
+ stream.write(password + '\n');
173
+ sent = true;
174
+ }
175
+ }
176
+
177
+ stderr = (stderr + pending).slice(-1024 * 1024);
178
+ this.services.log(pending);
179
+ pending = '';
180
+ });
181
+ stream.on('close', (code: number) => {
182
+ stderr = (stderr + pending.replaceAll(marker, '')).slice(-1024 * 1024);
183
+ finish(undefined, typeof code === 'number' ? code : -1);
184
+ });
185
+ });
186
+ });
187
+ }
188
+
189
+ private async transfer(options: FileTransfer, upload: boolean): Promise<void> {
190
+ const signal = AbortSignal.any([this.services.signal, AbortSignal.timeout(milliseconds(options.timeout ?? '1h'))]);
191
+ const connector = new SftpConnector({
192
+ ...this.config,
193
+ initialPath: '',
194
+ abortSignal: signal,
195
+ hostVerifier: (key: Buffer) => this.services.trust(this.config.host, this.config.port, 'SHA256:' + createHash('sha256').update(key).digest('base64').replace(/=+$/, '')),
196
+ keyboardInteractive: this.config.keyboardInteractive ? async challenge => Promise.all(challenge.prompts.map((prompt, index) => this.services.ask({id: `ssh-challenge:${index}`, message: prompt.prompt, type: prompt.echo ? 'input' : 'password'}).then(String))) : undefined,
197
+ });
198
+
199
+ try {
200
+ await connector.connect();
201
+
202
+ if (upload) {
203
+ await connector.write(options.destinationFile,
204
+ () => createReadStream(absolutePath(this.services.directory, options.sourceFile)),
205
+ {abortSignal: signal, maxBytes: options.maxBytes});
206
+ } else {
207
+ const destination = absolutePath(this.services.directory, options.destinationFile);
208
+ const temporary = `${destination}.dockline-${randomUUID()}`;
209
+
210
+ await mkdir(path.dirname(destination), {recursive: true});
211
+
212
+ try {
213
+ const contents = await connector.read(options.sourceFile, {
214
+ abortSignal: signal,
215
+ maxBytes: options.maxBytes,
216
+ });
217
+
218
+ await pipeline(Buffer.isBuffer(contents) ? Readable.from([contents]) : contents as Readable, createWriteStream(temporary, {flags: 'wx', mode: 0o600}), {signal});
219
+ await rename(temporary, destination);
220
+ } finally {
221
+ await unlink(temporary).catch(() => {});
222
+ }
223
+ }
224
+ } finally {
225
+ await connector.disconnect();
226
+ }
227
+ }
228
+
229
+ public download(options: FileTransfer): Promise<void> {
230
+ return this.transfer(options, false);
231
+ }
232
+
233
+ public upload(options: FileTransfer): Promise<void> {
234
+ return this.transfer(options, true);
235
+ }
236
+ }
237
+
238
+ export class SshClient {
239
+ private readonly services: SshServices;
240
+
241
+ public constructor(config: ResolvedSsh, options: Partial<Omit<SshServices, 'resolve'>> = {}) {
242
+ this.services = {
243
+ directory: options.directory ?? process.cwd(),
244
+ signal: options.signal ?? new AbortController().signal,
245
+ log: options.log ?? (() => {}),
246
+ ask: options.ask ?? (async () => {
247
+ throw new Error('This operation requires a credential prompt callback.');
248
+ }),
249
+ trust: options.trust ?? (async () => false),
250
+ resolve: async overrides => {
251
+ const changed = (overrides.host && overrides.host !== config.host) || (overrides.port &&
252
+ overrides.port !== config.port) || (overrides.username && overrides.username !== config.username);
253
+
254
+ return {
255
+ ...config,
256
+ ...(changed ? {
257
+ password: undefined,
258
+ passphrase: undefined,
259
+ privateKeyPath: undefined,
260
+ agent: undefined,
261
+ hostFingerprint: undefined,
262
+ } : {}),
263
+ ...overrides,
264
+ };
265
+ },
266
+ };
267
+ }
268
+
269
+ public withConnection<T>(callback: (server: SshSession) => Promise<T>): Promise<T>;
270
+ public withConnection<T>(options: SshOptions, callback: (server: SshSession) => Promise<T>): Promise<T>;
271
+ public async withConnection<T>(
272
+ options: SshOptions | ((server: SshSession) => Promise<T>),
273
+ callback?: (server: SshSession) => Promise<T>,
274
+ ): Promise<T> {
275
+ const config = await this.services.resolve(typeof options === 'function' ? {} : options);
276
+ const action = typeof options === 'function' ? options : callback!;
277
+ const services: SshServices = {
278
+ ...this.services,
279
+ trust: config.hostFingerprint ?
280
+ async (_host, _port, fingerprint) => fingerprint === config.hostFingerprint : this.services.trust,
281
+ };
282
+ const client = new Client();
283
+ const abort = () => client.destroy();
284
+
285
+ this.services.signal.addEventListener('abort', abort, {once: true});
286
+
287
+ try {
288
+ this.services.signal.throwIfAborted();
289
+ await new Promise<void>((resolve, reject) => {
290
+ client.once('ready', resolve);
291
+ client.on('error', reject);
292
+ client.once('close', () => reject(new Error('SSH connection closed during authentication.')));
293
+ client.on('keyboard-interactive', (_name, _instructions, _language, prompts, finish) => {
294
+ void Promise.all(prompts.map((prompt, index) => this.services.ask({id: `ssh-challenge:${index}`, message: prompt.prompt, type: prompt.echo ? 'input' : 'password'}))).then(answers => finish(answers.map(String)), reject);
295
+ });
296
+ void (async () => {
297
+ client.connect({
298
+ host: config.host,
299
+ port: config.port,
300
+ username: config.username,
301
+ password: config.password,
302
+ privateKey: config.privateKeyPath ? await readFile(config.privateKeyPath) : undefined,
303
+ passphrase: config.passphrase,
304
+ agent: config.agent,
305
+ tryKeyboard: config.keyboardInteractive,
306
+ readyTimeout: 60000,
307
+ hostVerifier: (key: Buffer, verify: (accepted: boolean) => void) => {
308
+ const fingerprint = 'SHA256:' + createHash('sha256').update(key).digest('base64').replace(/=+$/, '');
309
+
310
+ void services.trust(config.host, config.port, fingerprint).then(verify,
311
+ () => verify(false));
312
+ },
313
+ });
314
+ })().catch(reject);
315
+ });
316
+
317
+ return await action(new SshSession(client, config, services));
318
+ } finally {
319
+ this.services.signal.removeEventListener('abort', abort);
320
+ client.destroy();
321
+ }
322
+ }
323
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export {SshClient, SshSession, quotePosix} from './SshClient.js';
2
+ export type {ResolvedSsh, SshServices, ExecOptions, ExecResult, FileTransfer} from './SshClient.js';
3
+ export type {SshOptions, Question} from './types.js';
package/src/types.ts ADDED
@@ -0,0 +1,34 @@
1
+ export interface SshOptions {
2
+ password?: string;
3
+ passphrase?: string;
4
+ host?: string;
5
+ port?: number;
6
+ username?: string;
7
+ privateKeyPath?: string;
8
+ agent?: string;
9
+ keyboardInteractive?: boolean;
10
+ hostFingerprint?: string;
11
+ sudo?: {password?: 'prompt' | 'none'; passwordEnv?: string};
12
+ }
13
+ export interface Question {
14
+ id: string;
15
+ message: string;
16
+ type: 'password' | 'input';
17
+ required?: boolean;
18
+ }
19
+ export function milliseconds(value: string | number): number {
20
+ const match = /^(\d+(?:\.\d+)?)(ms|s|m|h)$/.exec(String(value));
21
+ const scales: Record<string, number> = {
22
+ ms: 1,
23
+ s: 1000,
24
+ m: 60000,
25
+ h: 3600000,
26
+ };
27
+ const result = typeof value === 'number' ? value : match ? Number(match[1]) * scales[match[2]] : 0;
28
+
29
+ if (!Number.isFinite(result) || result <= 0 || result > 2147483647) {
30
+ throw new Error('Invalid positive timeout.');
31
+ }
32
+
33
+ return Math.ceil(result);
34
+ }