@ebowwa/terminal 0.2.1 → 0.3.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/dist/index.js +6855 -28130
- package/dist/mcp/index.js +7244 -27317
- package/package.json +1 -1
- package/src/api.js +0 -861
- package/src/client.js +0 -92
- package/src/config.js +0 -490
- package/src/error.js +0 -32
- package/src/exec.js +0 -183
- package/src/files.js +0 -521
- package/src/fingerprint.js +0 -336
- package/src/index.js +0 -127
- package/src/manager.js +0 -358
- package/src/mcp/index.js +0 -555
- package/src/mcp/stdio.js +0 -840
- package/src/network-error-detector.js +0 -101
- package/src/pool.js +0 -840
- package/src/pty.js +0 -344
- package/src/resources.js +0 -64
- package/src/scp.js +0 -166
- package/src/sessions.js +0 -895
- package/src/tmux-exec.js +0 -169
- package/src/tmux-local.js +0 -937
- package/src/tmux-manager.js +0 -1026
- package/src/tmux.js +0 -826
- package/src/types.js +0 -5
package/src/pool.js
DELETED
|
@@ -1,840 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* SSH Connection Pool Manager
|
|
4
|
-
* Maintains persistent SSH connections for reuse across commands
|
|
5
|
-
*/
|
|
6
|
-
var __assign = (this && this.__assign) || function () {
|
|
7
|
-
__assign = Object.assign || function(t) {
|
|
8
|
-
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
|
9
|
-
s = arguments[i];
|
|
10
|
-
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
|
|
11
|
-
t[p] = s[p];
|
|
12
|
-
}
|
|
13
|
-
return t;
|
|
14
|
-
};
|
|
15
|
-
return __assign.apply(this, arguments);
|
|
16
|
-
};
|
|
17
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
18
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
19
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
20
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
21
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
22
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
23
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
24
|
-
});
|
|
25
|
-
};
|
|
26
|
-
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
27
|
-
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
|
|
28
|
-
return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
29
|
-
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
30
|
-
function step(op) {
|
|
31
|
-
if (f) throw new TypeError("Generator is already executing.");
|
|
32
|
-
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
33
|
-
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
34
|
-
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
35
|
-
switch (op[0]) {
|
|
36
|
-
case 0: case 1: t = op; break;
|
|
37
|
-
case 4: _.label++; return { value: op[1], done: false };
|
|
38
|
-
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
39
|
-
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
40
|
-
default:
|
|
41
|
-
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
42
|
-
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
43
|
-
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
44
|
-
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
45
|
-
if (t[2]) _.ops.pop();
|
|
46
|
-
_.trys.pop(); continue;
|
|
47
|
-
}
|
|
48
|
-
op = body.call(thisArg, _);
|
|
49
|
-
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
50
|
-
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
54
|
-
exports.SSHConnectionPool = void 0;
|
|
55
|
-
exports.getSSHPool = getSSHPool;
|
|
56
|
-
exports.closeGlobalSSHPool = closeGlobalSSHPool;
|
|
57
|
-
exports.getActiveSSHConnections = getActiveSSHConnections;
|
|
58
|
-
var node_ssh_1 = require("node-ssh");
|
|
59
|
-
var node_path_1 = require("node:path");
|
|
60
|
-
var DEFAULT_CONFIG = {
|
|
61
|
-
maxConnections: 50,
|
|
62
|
-
maxConnectionsPerHost: 5, // Allow up to 5 connections per host for parallel ops
|
|
63
|
-
idleTimeout: 5 * 60 * 1000, // 5 minutes
|
|
64
|
-
connectionTimeout: 10 * 1000, // 10 seconds
|
|
65
|
-
keepAliveInterval: 30 * 1000, // 30 seconds
|
|
66
|
-
};
|
|
67
|
-
/**
|
|
68
|
-
* SSH Connection Pool Class
|
|
69
|
-
*/
|
|
70
|
-
var SSHConnectionPool = /** @class */ (function () {
|
|
71
|
-
function SSHConnectionPool(config) {
|
|
72
|
-
if (config === void 0) { config = {}; }
|
|
73
|
-
this.connections = new Map();
|
|
74
|
-
this.cleanupInterval = null;
|
|
75
|
-
this.nextId = 0; // Counter for generating unique connection IDs
|
|
76
|
-
this.config = __assign(__assign({}, DEFAULT_CONFIG), config);
|
|
77
|
-
this.startCleanup();
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
|
-
* Generate a unique key for the connection (host-based)
|
|
81
|
-
*/
|
|
82
|
-
SSHConnectionPool.prototype.getKey = function (host, port, user) {
|
|
83
|
-
return "".concat(user, "@").concat(host, ":").concat(port);
|
|
84
|
-
};
|
|
85
|
-
/**
|
|
86
|
-
* Get all connections for a given host
|
|
87
|
-
*/
|
|
88
|
-
SSHConnectionPool.prototype.getConnectionsList = function (key) {
|
|
89
|
-
var list = this.connections.get(key);
|
|
90
|
-
if (!list) {
|
|
91
|
-
list = [];
|
|
92
|
-
this.connections.set(key, list);
|
|
93
|
-
}
|
|
94
|
-
return list;
|
|
95
|
-
};
|
|
96
|
-
/**
|
|
97
|
-
* Get or create a connection (returns least recently used connection)
|
|
98
|
-
*/
|
|
99
|
-
SSHConnectionPool.prototype.getConnection = function (options) {
|
|
100
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
101
|
-
var connections;
|
|
102
|
-
return __generator(this, function (_a) {
|
|
103
|
-
switch (_a.label) {
|
|
104
|
-
case 0: return [4 /*yield*/, this.getConnections(options, 1)];
|
|
105
|
-
case 1:
|
|
106
|
-
connections = _a.sent();
|
|
107
|
-
return [2 /*return*/, connections[0]];
|
|
108
|
-
}
|
|
109
|
-
});
|
|
110
|
-
});
|
|
111
|
-
};
|
|
112
|
-
/**
|
|
113
|
-
* Get or create a connection using password authentication
|
|
114
|
-
*/
|
|
115
|
-
SSHConnectionPool.prototype.getConnectionWithPassword = function (host_1, user_1, password_1) {
|
|
116
|
-
return __awaiter(this, arguments, void 0, function (host, user, password, port) {
|
|
117
|
-
if (port === void 0) { port = 22; }
|
|
118
|
-
return __generator(this, function (_a) {
|
|
119
|
-
return [2 /*return*/, this.getConnection({ host: host, user: user, password: password, port: port })];
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
/**
|
|
124
|
-
* Get or create multiple connections for parallel execution
|
|
125
|
-
* @param options - SSH connection options
|
|
126
|
-
* @param count - Number of connections to retrieve
|
|
127
|
-
* @returns Array of SSH connections
|
|
128
|
-
*/
|
|
129
|
-
SSHConnectionPool.prototype.getConnections = function (options, count) {
|
|
130
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
131
|
-
var host, _a, user, _b, port, keyPath, password, key, list, now, result, i, existing, _c, _d, _loop_1, _i, result_1, conn, currentHostCount, ssh;
|
|
132
|
-
return __generator(this, function (_e) {
|
|
133
|
-
switch (_e.label) {
|
|
134
|
-
case 0:
|
|
135
|
-
host = options.host, _a = options.user, user = _a === void 0 ? 'root' : _a, _b = options.port, port = _b === void 0 ? 22 : _b, keyPath = options.keyPath, password = options.password;
|
|
136
|
-
key = this.getKey(host, port, user);
|
|
137
|
-
list = this.getConnectionsList(key);
|
|
138
|
-
now = Date.now();
|
|
139
|
-
result = [];
|
|
140
|
-
i = list.length - 1;
|
|
141
|
-
_e.label = 1;
|
|
142
|
-
case 1:
|
|
143
|
-
if (!(i >= 0 && result.length < count)) return [3 /*break*/, 10];
|
|
144
|
-
existing = list[i];
|
|
145
|
-
// If used recently (within 30 seconds), reuse without verification
|
|
146
|
-
if (now - existing.lastUsed < 30000) {
|
|
147
|
-
existing.lastUsed = now;
|
|
148
|
-
result.push(existing.ssh);
|
|
149
|
-
list.splice(i, 1);
|
|
150
|
-
return [3 /*break*/, 9];
|
|
151
|
-
}
|
|
152
|
-
_e.label = 2;
|
|
153
|
-
case 2:
|
|
154
|
-
_e.trys.push([2, 4, , 9]);
|
|
155
|
-
return [4 /*yield*/, existing.ssh.exec('echo', ['ok'])];
|
|
156
|
-
case 3:
|
|
157
|
-
_e.sent();
|
|
158
|
-
existing.lastUsed = now;
|
|
159
|
-
result.push(existing.ssh);
|
|
160
|
-
list.splice(i, 1);
|
|
161
|
-
return [3 /*break*/, 9];
|
|
162
|
-
case 4:
|
|
163
|
-
_c = _e.sent();
|
|
164
|
-
// Connection is dead, remove and dispose
|
|
165
|
-
list.splice(i, 1);
|
|
166
|
-
_e.label = 5;
|
|
167
|
-
case 5:
|
|
168
|
-
_e.trys.push([5, 7, , 8]);
|
|
169
|
-
return [4 /*yield*/, existing.ssh.dispose()];
|
|
170
|
-
case 6:
|
|
171
|
-
_e.sent();
|
|
172
|
-
return [3 /*break*/, 8];
|
|
173
|
-
case 7:
|
|
174
|
-
_d = _e.sent();
|
|
175
|
-
return [3 /*break*/, 8];
|
|
176
|
-
case 8: return [3 /*break*/, 9];
|
|
177
|
-
case 9:
|
|
178
|
-
i--;
|
|
179
|
-
return [3 /*break*/, 1];
|
|
180
|
-
case 10:
|
|
181
|
-
_loop_1 = function (conn) {
|
|
182
|
-
var pooled = list.find(function (c) { return c.ssh === conn; });
|
|
183
|
-
if (pooled) {
|
|
184
|
-
// Move to end of list (most recently used)
|
|
185
|
-
list.splice(list.indexOf(pooled), 1);
|
|
186
|
-
list.push(pooled);
|
|
187
|
-
}
|
|
188
|
-
};
|
|
189
|
-
// Put back the connections we're reusing at the end (most recently used)
|
|
190
|
-
for (_i = 0, result_1 = result; _i < result_1.length; _i++) {
|
|
191
|
-
conn = result_1[_i];
|
|
192
|
-
_loop_1(conn);
|
|
193
|
-
}
|
|
194
|
-
_e.label = 11;
|
|
195
|
-
case 11:
|
|
196
|
-
if (!(result.length < count)) return [3 /*break*/, 15];
|
|
197
|
-
if (!(this.getTotalConnectionCount() >= this.config.maxConnections)) return [3 /*break*/, 13];
|
|
198
|
-
return [4 /*yield*/, this.evictOldest()];
|
|
199
|
-
case 12:
|
|
200
|
-
_e.sent();
|
|
201
|
-
_e.label = 13;
|
|
202
|
-
case 13:
|
|
203
|
-
currentHostCount = list.length + result.length;
|
|
204
|
-
if (currentHostCount >= this.config.maxConnectionsPerHost) {
|
|
205
|
-
// Can't create more connections for this host
|
|
206
|
-
return [3 /*break*/, 15];
|
|
207
|
-
}
|
|
208
|
-
return [4 /*yield*/, this.createConnection(host, port, user, keyPath, password)
|
|
209
|
-
// Add to result and pool
|
|
210
|
-
];
|
|
211
|
-
case 14:
|
|
212
|
-
ssh = _e.sent();
|
|
213
|
-
// Add to result and pool
|
|
214
|
-
result.push(ssh);
|
|
215
|
-
list.push({
|
|
216
|
-
ssh: ssh,
|
|
217
|
-
lastUsed: now,
|
|
218
|
-
host: host,
|
|
219
|
-
port: port,
|
|
220
|
-
user: user,
|
|
221
|
-
id: "".concat(key, "-").concat(this.nextId++),
|
|
222
|
-
});
|
|
223
|
-
return [3 /*break*/, 11];
|
|
224
|
-
case 15: return [2 /*return*/, result];
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
};
|
|
229
|
-
/**
|
|
230
|
-
* Create a new SSH connection
|
|
231
|
-
* Tries key-based auth first, then password auth, then SSH agent
|
|
232
|
-
*/
|
|
233
|
-
SSHConnectionPool.prototype.createConnection = function (host, port, user, keyPath, password) {
|
|
234
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
235
|
-
var ssh, baseConfig, authMethods, resolvedKeyPath, projectRoot, keyName, errors, _loop_2, this_1, _i, authMethods_1, method, state_1;
|
|
236
|
-
var _this = this;
|
|
237
|
-
return __generator(this, function (_a) {
|
|
238
|
-
switch (_a.label) {
|
|
239
|
-
case 0:
|
|
240
|
-
ssh = new node_ssh_1.NodeSSH();
|
|
241
|
-
baseConfig = {
|
|
242
|
-
host: host,
|
|
243
|
-
port: port,
|
|
244
|
-
username: user,
|
|
245
|
-
readyTimeout: this.config.connectionTimeout,
|
|
246
|
-
keepaliveInterval: this.config.keepAliveInterval,
|
|
247
|
-
};
|
|
248
|
-
authMethods = [];
|
|
249
|
-
// 1. Key-based authentication (if keyPath provided)
|
|
250
|
-
if (keyPath) {
|
|
251
|
-
resolvedKeyPath = keyPath;
|
|
252
|
-
if (!node_path_1.default.isAbsolute(keyPath)) {
|
|
253
|
-
projectRoot = node_path_1.default.resolve(import.meta.dir, '../../com.hetzner.codespaces');
|
|
254
|
-
if (keyPath.includes('.ssh-keys')) {
|
|
255
|
-
keyName = node_path_1.default.basename(keyPath);
|
|
256
|
-
resolvedKeyPath = node_path_1.default.join(projectRoot, '.ssh-keys', keyName);
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
resolvedKeyPath = node_path_1.default.resolve(keyPath);
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
authMethods.push({
|
|
263
|
-
name: 'key',
|
|
264
|
-
config: __assign(__assign({}, baseConfig), { privateKeyPath: resolvedKeyPath }),
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
// 2. Password authentication (if password provided)
|
|
268
|
-
if (password) {
|
|
269
|
-
authMethods.push({
|
|
270
|
-
name: 'password',
|
|
271
|
-
config: __assign(__assign({}, baseConfig), { password: password }),
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
// 3. SSH agent (fallback if available)
|
|
275
|
-
if (process.env.SSH_AUTH_SOCK) {
|
|
276
|
-
authMethods.push({
|
|
277
|
-
name: 'agent',
|
|
278
|
-
config: __assign(__assign({}, baseConfig), { agent: process.env.SSH_AUTH_SOCK }),
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
// If no auth methods available, try with empty config (will fail)
|
|
282
|
-
if (authMethods.length === 0) {
|
|
283
|
-
authMethods.push({
|
|
284
|
-
name: 'default',
|
|
285
|
-
config: baseConfig,
|
|
286
|
-
});
|
|
287
|
-
}
|
|
288
|
-
errors = [];
|
|
289
|
-
_loop_2 = function (method) {
|
|
290
|
-
var underlyingClient, connectionKey_1, error_1, errMsg;
|
|
291
|
-
return __generator(this, function (_b) {
|
|
292
|
-
switch (_b.label) {
|
|
293
|
-
case 0:
|
|
294
|
-
_b.trys.push([0, 2, , 3]);
|
|
295
|
-
return [4 /*yield*/, ssh.connect(method.config)
|
|
296
|
-
// Attach error handler to underlying ssh2 client to catch keepalive and other errors
|
|
297
|
-
// The NodeSSH instance exposes the underlying ssh2 Client via the 'ssh' property
|
|
298
|
-
];
|
|
299
|
-
case 1:
|
|
300
|
-
_b.sent();
|
|
301
|
-
// Attach error handler to underlying ssh2 client to catch keepalive and other errors
|
|
302
|
-
// The NodeSSH instance exposes the underlying ssh2 Client via the 'ssh' property
|
|
303
|
-
if (ssh.ssh) {
|
|
304
|
-
underlyingClient = ssh.ssh;
|
|
305
|
-
connectionKey_1 = this_1.getKey(host, port, user);
|
|
306
|
-
underlyingClient.on('error', function (err) {
|
|
307
|
-
// Handle keepalive timeouts gracefully - these are network issues, not fatal errors
|
|
308
|
-
var isKeepalive = err.message.includes('Keepalive') || err.level === 'client-timeout';
|
|
309
|
-
if (isKeepalive) {
|
|
310
|
-
console.warn("[SSH Pool] Keepalive timeout for ".concat(user, "@").concat(host, ":").concat(port, " - removing stale connection"));
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
console.error("[SSH Pool] Connection error for ".concat(user, "@").concat(host, ":").concat(port, ":"), err.message);
|
|
314
|
-
}
|
|
315
|
-
// Remove failed connection from pool
|
|
316
|
-
var list = _this.connections.get(connectionKey_1);
|
|
317
|
-
if (list) {
|
|
318
|
-
var index = list.findIndex(function (c) { return c.ssh === ssh; });
|
|
319
|
-
if (index !== -1) {
|
|
320
|
-
var removed = list.splice(index, 1)[0];
|
|
321
|
-
// Dispose connection
|
|
322
|
-
try {
|
|
323
|
-
ssh.dispose();
|
|
324
|
-
}
|
|
325
|
-
catch (_a) {
|
|
326
|
-
// Ignore dispose errors
|
|
327
|
-
}
|
|
328
|
-
console.log("[SSH Pool] Removed errored connection ".concat(removed.id, " from pool (will reconnect on next use)"));
|
|
329
|
-
// Clean up empty lists
|
|
330
|
-
if (list.length === 0) {
|
|
331
|
-
_this.connections.delete(connectionKey_1);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
// Also handle 'close' event for unexpected disconnections
|
|
337
|
-
underlyingClient.on('close', function () {
|
|
338
|
-
var list = _this.connections.get(connectionKey_1);
|
|
339
|
-
if (list) {
|
|
340
|
-
var index = list.findIndex(function (c) { return c.ssh === ssh; });
|
|
341
|
-
if (index !== -1) {
|
|
342
|
-
var removed = list.splice(index, 1)[0];
|
|
343
|
-
console.log("[SSH Pool] Connection ".concat(removed.id, " closed unexpectedly (will reconnect on next use)"));
|
|
344
|
-
if (list.length === 0) {
|
|
345
|
-
_this.connections.delete(connectionKey_1);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
});
|
|
350
|
-
}
|
|
351
|
-
return [2 /*return*/, { value: ssh }];
|
|
352
|
-
case 2:
|
|
353
|
-
error_1 = _b.sent();
|
|
354
|
-
errMsg = error_1 instanceof Error ? error_1.message : String(error_1);
|
|
355
|
-
errors.push("".concat(method.name, ": ").concat(errMsg));
|
|
356
|
-
return [3 /*break*/, 3];
|
|
357
|
-
case 3: return [2 /*return*/];
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
};
|
|
361
|
-
this_1 = this;
|
|
362
|
-
_i = 0, authMethods_1 = authMethods;
|
|
363
|
-
_a.label = 1;
|
|
364
|
-
case 1:
|
|
365
|
-
if (!(_i < authMethods_1.length)) return [3 /*break*/, 4];
|
|
366
|
-
method = authMethods_1[_i];
|
|
367
|
-
return [5 /*yield**/, _loop_2(method)];
|
|
368
|
-
case 2:
|
|
369
|
-
state_1 = _a.sent();
|
|
370
|
-
if (typeof state_1 === "object")
|
|
371
|
-
return [2 /*return*/, state_1.value];
|
|
372
|
-
_a.label = 3;
|
|
373
|
-
case 3:
|
|
374
|
-
_i++;
|
|
375
|
-
return [3 /*break*/, 1];
|
|
376
|
-
case 4:
|
|
377
|
-
// All methods failed
|
|
378
|
-
throw new Error("SSH connection failed to ".concat(host, " (tried: ").concat(authMethods.map(function (m) { return m.name; }).join(', '), "): ").concat(errors.join('; ')));
|
|
379
|
-
}
|
|
380
|
-
});
|
|
381
|
-
});
|
|
382
|
-
};
|
|
383
|
-
/**
|
|
384
|
-
* Get total number of connections across all hosts
|
|
385
|
-
*/
|
|
386
|
-
SSHConnectionPool.prototype.getTotalConnectionCount = function () {
|
|
387
|
-
var count = 0;
|
|
388
|
-
for (var _i = 0, _a = this.connections.values(); _i < _a.length; _i++) {
|
|
389
|
-
var list = _a[_i];
|
|
390
|
-
count += list.length;
|
|
391
|
-
}
|
|
392
|
-
return count;
|
|
393
|
-
};
|
|
394
|
-
/**
|
|
395
|
-
* Execute a command using a pooled connection
|
|
396
|
-
*
|
|
397
|
-
* ERROR HANDLING BEHAVIOR:
|
|
398
|
-
* =========================
|
|
399
|
-
* If result.stderr exists AND result.stdout is empty, we throw an error.
|
|
400
|
-
* This is intentional - commands that fail should return fallback values
|
|
401
|
-
* via shell redirection (e.g., `|| echo "0"` or `2>/dev/null`).
|
|
402
|
-
*
|
|
403
|
-
* Example of proper fallback handling:
|
|
404
|
-
* `type nvidia-smi 2>/dev/null && nvidia-smi ... || echo NOGPU`
|
|
405
|
-
*
|
|
406
|
-
* This ensures commands don't silently fail - they must handle their own
|
|
407
|
-
* error cases and return sensible defaults.
|
|
408
|
-
*/
|
|
409
|
-
SSHConnectionPool.prototype.exec = function (command, options) {
|
|
410
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
411
|
-
var ssh, result, output, error_2, host, _a, user, _b, port, key, list, index, removed, _c;
|
|
412
|
-
return __generator(this, function (_d) {
|
|
413
|
-
switch (_d.label) {
|
|
414
|
-
case 0: return [4 /*yield*/, this.getConnection(options)];
|
|
415
|
-
case 1:
|
|
416
|
-
ssh = _d.sent();
|
|
417
|
-
_d.label = 2;
|
|
418
|
-
case 2:
|
|
419
|
-
_d.trys.push([2, 4, , 10]);
|
|
420
|
-
return [4 /*yield*/, ssh.execCommand(command, {
|
|
421
|
-
execOptions: {
|
|
422
|
-
timeout: (options.timeout || 5) * 1000,
|
|
423
|
-
},
|
|
424
|
-
})
|
|
425
|
-
// If we have stderr but no stdout, the command failed
|
|
426
|
-
// Commands should handle their own fallbacks (|| echo fallback, 2>/dev/null, etc)
|
|
427
|
-
];
|
|
428
|
-
case 3:
|
|
429
|
-
result = _d.sent();
|
|
430
|
-
// If we have stderr but no stdout, the command failed
|
|
431
|
-
// Commands should handle their own fallbacks (|| echo fallback, 2>/dev/null, etc)
|
|
432
|
-
if (result.stderr && !result.stdout) {
|
|
433
|
-
output = result.stdout ? "".concat(result.stderr, "\n").concat(result.stdout) : result.stderr;
|
|
434
|
-
throw new Error(output);
|
|
435
|
-
}
|
|
436
|
-
return [2 /*return*/, result.stdout.trim()];
|
|
437
|
-
case 4:
|
|
438
|
-
error_2 = _d.sent();
|
|
439
|
-
host = options.host, _a = options.user, user = _a === void 0 ? 'root' : _a, _b = options.port, port = _b === void 0 ? 22 : _b;
|
|
440
|
-
key = this.getKey(host, port, user);
|
|
441
|
-
list = this.connections.get(key);
|
|
442
|
-
if (!list) return [3 /*break*/, 9];
|
|
443
|
-
index = list.findIndex(function (c) { return c.ssh === ssh; });
|
|
444
|
-
if (!(index !== -1)) return [3 /*break*/, 8];
|
|
445
|
-
removed = list.splice(index, 1)[0];
|
|
446
|
-
_d.label = 5;
|
|
447
|
-
case 5:
|
|
448
|
-
_d.trys.push([5, 7, , 8]);
|
|
449
|
-
return [4 /*yield*/, removed.ssh.dispose()];
|
|
450
|
-
case 6:
|
|
451
|
-
_d.sent();
|
|
452
|
-
return [3 /*break*/, 8];
|
|
453
|
-
case 7:
|
|
454
|
-
_c = _d.sent();
|
|
455
|
-
return [3 /*break*/, 8];
|
|
456
|
-
case 8:
|
|
457
|
-
// Clean up empty lists
|
|
458
|
-
if (list.length === 0) {
|
|
459
|
-
this.connections.delete(key);
|
|
460
|
-
}
|
|
461
|
-
_d.label = 9;
|
|
462
|
-
case 9: throw error_2;
|
|
463
|
-
case 10: return [2 /*return*/];
|
|
464
|
-
}
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
};
|
|
468
|
-
/**
|
|
469
|
-
* Check if a connection exists and is alive for a given host
|
|
470
|
-
*/
|
|
471
|
-
SSHConnectionPool.prototype.hasConnection = function (options) {
|
|
472
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
473
|
-
var host, _a, user, _b, port, key, list, i, conn, _c, _d;
|
|
474
|
-
return __generator(this, function (_e) {
|
|
475
|
-
switch (_e.label) {
|
|
476
|
-
case 0:
|
|
477
|
-
host = options.host, _a = options.user, user = _a === void 0 ? 'root' : _a, _b = options.port, port = _b === void 0 ? 22 : _b;
|
|
478
|
-
key = this.getKey(host, port, user);
|
|
479
|
-
list = this.connections.get(key);
|
|
480
|
-
if (!list || list.length === 0) {
|
|
481
|
-
return [2 /*return*/, false];
|
|
482
|
-
}
|
|
483
|
-
i = list.length - 1;
|
|
484
|
-
_e.label = 1;
|
|
485
|
-
case 1:
|
|
486
|
-
if (!(i >= 0)) return [3 /*break*/, 10];
|
|
487
|
-
conn = list[i];
|
|
488
|
-
_e.label = 2;
|
|
489
|
-
case 2:
|
|
490
|
-
_e.trys.push([2, 4, , 9]);
|
|
491
|
-
return [4 /*yield*/, conn.ssh.exec('echo', ['ok'])];
|
|
492
|
-
case 3:
|
|
493
|
-
_e.sent();
|
|
494
|
-
return [2 /*return*/, true];
|
|
495
|
-
case 4:
|
|
496
|
-
_c = _e.sent();
|
|
497
|
-
// Remove dead connection
|
|
498
|
-
list.splice(i, 1);
|
|
499
|
-
_e.label = 5;
|
|
500
|
-
case 5:
|
|
501
|
-
_e.trys.push([5, 7, , 8]);
|
|
502
|
-
return [4 /*yield*/, conn.ssh.dispose()];
|
|
503
|
-
case 6:
|
|
504
|
-
_e.sent();
|
|
505
|
-
return [3 /*break*/, 8];
|
|
506
|
-
case 7:
|
|
507
|
-
_d = _e.sent();
|
|
508
|
-
return [3 /*break*/, 8];
|
|
509
|
-
case 8: return [3 /*break*/, 9];
|
|
510
|
-
case 9:
|
|
511
|
-
i--;
|
|
512
|
-
return [3 /*break*/, 1];
|
|
513
|
-
case 10:
|
|
514
|
-
// Clean up empty list
|
|
515
|
-
if (list.length === 0) {
|
|
516
|
-
this.connections.delete(key);
|
|
517
|
-
}
|
|
518
|
-
return [2 /*return*/, false];
|
|
519
|
-
}
|
|
520
|
-
});
|
|
521
|
-
});
|
|
522
|
-
};
|
|
523
|
-
/**
|
|
524
|
-
* Close a specific connection by SSH instance
|
|
525
|
-
*/
|
|
526
|
-
SSHConnectionPool.prototype.closeConnectionInstance = function (ssh) {
|
|
527
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
528
|
-
var _i, _a, _b, key, list, index, removed, _c;
|
|
529
|
-
return __generator(this, function (_d) {
|
|
530
|
-
switch (_d.label) {
|
|
531
|
-
case 0:
|
|
532
|
-
_i = 0, _a = this.connections.entries();
|
|
533
|
-
_d.label = 1;
|
|
534
|
-
case 1:
|
|
535
|
-
if (!(_i < _a.length)) return [3 /*break*/, 7];
|
|
536
|
-
_b = _a[_i], key = _b[0], list = _b[1];
|
|
537
|
-
index = list.findIndex(function (c) { return c.ssh === ssh; });
|
|
538
|
-
if (!(index !== -1)) return [3 /*break*/, 6];
|
|
539
|
-
removed = list.splice(index, 1)[0];
|
|
540
|
-
_d.label = 2;
|
|
541
|
-
case 2:
|
|
542
|
-
_d.trys.push([2, 4, , 5]);
|
|
543
|
-
return [4 /*yield*/, removed.ssh.dispose()];
|
|
544
|
-
case 3:
|
|
545
|
-
_d.sent();
|
|
546
|
-
return [3 /*break*/, 5];
|
|
547
|
-
case 4:
|
|
548
|
-
_c = _d.sent();
|
|
549
|
-
return [3 /*break*/, 5];
|
|
550
|
-
case 5:
|
|
551
|
-
// Clean up empty lists
|
|
552
|
-
if (list.length === 0) {
|
|
553
|
-
this.connections.delete(key);
|
|
554
|
-
}
|
|
555
|
-
return [2 /*return*/];
|
|
556
|
-
case 6:
|
|
557
|
-
_i++;
|
|
558
|
-
return [3 /*break*/, 1];
|
|
559
|
-
case 7: return [2 /*return*/];
|
|
560
|
-
}
|
|
561
|
-
});
|
|
562
|
-
});
|
|
563
|
-
};
|
|
564
|
-
/**
|
|
565
|
-
* Close all connections for a specific host
|
|
566
|
-
*/
|
|
567
|
-
SSHConnectionPool.prototype.closeConnection = function (options) {
|
|
568
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
569
|
-
var host, _a, user, _b, port, key, list, _i, list_1, conn, _c;
|
|
570
|
-
return __generator(this, function (_d) {
|
|
571
|
-
switch (_d.label) {
|
|
572
|
-
case 0:
|
|
573
|
-
host = options.host, _a = options.user, user = _a === void 0 ? 'root' : _a, _b = options.port, port = _b === void 0 ? 22 : _b;
|
|
574
|
-
key = this.getKey(host, port, user);
|
|
575
|
-
list = this.connections.get(key);
|
|
576
|
-
if (!list) return [3 /*break*/, 6];
|
|
577
|
-
this.connections.delete(key);
|
|
578
|
-
_i = 0, list_1 = list;
|
|
579
|
-
_d.label = 1;
|
|
580
|
-
case 1:
|
|
581
|
-
if (!(_i < list_1.length)) return [3 /*break*/, 6];
|
|
582
|
-
conn = list_1[_i];
|
|
583
|
-
_d.label = 2;
|
|
584
|
-
case 2:
|
|
585
|
-
_d.trys.push([2, 4, , 5]);
|
|
586
|
-
return [4 /*yield*/, conn.ssh.dispose()];
|
|
587
|
-
case 3:
|
|
588
|
-
_d.sent();
|
|
589
|
-
return [3 /*break*/, 5];
|
|
590
|
-
case 4:
|
|
591
|
-
_c = _d.sent();
|
|
592
|
-
return [3 /*break*/, 5];
|
|
593
|
-
case 5:
|
|
594
|
-
_i++;
|
|
595
|
-
return [3 /*break*/, 1];
|
|
596
|
-
case 6: return [2 /*return*/];
|
|
597
|
-
}
|
|
598
|
-
});
|
|
599
|
-
});
|
|
600
|
-
};
|
|
601
|
-
/**
|
|
602
|
-
* Evict the oldest connection from the pool
|
|
603
|
-
*/
|
|
604
|
-
SSHConnectionPool.prototype.evictOldest = function () {
|
|
605
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
606
|
-
var oldest, _i, _a, _b, key, list, i, conn, list, _c;
|
|
607
|
-
return __generator(this, function (_d) {
|
|
608
|
-
switch (_d.label) {
|
|
609
|
-
case 0:
|
|
610
|
-
oldest = null;
|
|
611
|
-
for (_i = 0, _a = this.connections.entries(); _i < _a.length; _i++) {
|
|
612
|
-
_b = _a[_i], key = _b[0], list = _b[1];
|
|
613
|
-
for (i = 0; i < list.length; i++) {
|
|
614
|
-
conn = list[i];
|
|
615
|
-
if (!oldest || conn.lastUsed < oldest.conn.lastUsed) {
|
|
616
|
-
oldest = { key: key, conn: conn, listIndex: i };
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
if (!oldest) return [3 /*break*/, 4];
|
|
621
|
-
list = this.connections.get(oldest.key);
|
|
622
|
-
if (list) {
|
|
623
|
-
list.splice(oldest.listIndex, 1);
|
|
624
|
-
// Clean up empty lists
|
|
625
|
-
if (list.length === 0) {
|
|
626
|
-
this.connections.delete(oldest.key);
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
_d.label = 1;
|
|
630
|
-
case 1:
|
|
631
|
-
_d.trys.push([1, 3, , 4]);
|
|
632
|
-
return [4 /*yield*/, oldest.conn.ssh.dispose()];
|
|
633
|
-
case 2:
|
|
634
|
-
_d.sent();
|
|
635
|
-
return [3 /*break*/, 4];
|
|
636
|
-
case 3:
|
|
637
|
-
_c = _d.sent();
|
|
638
|
-
return [3 /*break*/, 4];
|
|
639
|
-
case 4: return [2 /*return*/];
|
|
640
|
-
}
|
|
641
|
-
});
|
|
642
|
-
});
|
|
643
|
-
};
|
|
644
|
-
/**
|
|
645
|
-
* Clean up idle connections
|
|
646
|
-
*/
|
|
647
|
-
SSHConnectionPool.prototype.cleanupIdle = function () {
|
|
648
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
649
|
-
var now, _i, _a, _b, key, list, toRemove, i, _c, _d, index, conn, _e;
|
|
650
|
-
return __generator(this, function (_f) {
|
|
651
|
-
switch (_f.label) {
|
|
652
|
-
case 0:
|
|
653
|
-
now = Date.now();
|
|
654
|
-
_i = 0, _a = this.connections.entries();
|
|
655
|
-
_f.label = 1;
|
|
656
|
-
case 1:
|
|
657
|
-
if (!(_i < _a.length)) return [3 /*break*/, 9];
|
|
658
|
-
_b = _a[_i], key = _b[0], list = _b[1];
|
|
659
|
-
toRemove = [];
|
|
660
|
-
for (i = 0; i < list.length; i++) {
|
|
661
|
-
if (now - list[i].lastUsed > this.config.idleTimeout) {
|
|
662
|
-
toRemove.push(i);
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
_c = 0, _d = toRemove.reverse();
|
|
666
|
-
_f.label = 2;
|
|
667
|
-
case 2:
|
|
668
|
-
if (!(_c < _d.length)) return [3 /*break*/, 7];
|
|
669
|
-
index = _d[_c];
|
|
670
|
-
conn = list.splice(index, 1)[0];
|
|
671
|
-
_f.label = 3;
|
|
672
|
-
case 3:
|
|
673
|
-
_f.trys.push([3, 5, , 6]);
|
|
674
|
-
return [4 /*yield*/, conn.ssh.dispose()];
|
|
675
|
-
case 4:
|
|
676
|
-
_f.sent();
|
|
677
|
-
return [3 /*break*/, 6];
|
|
678
|
-
case 5:
|
|
679
|
-
_e = _f.sent();
|
|
680
|
-
return [3 /*break*/, 6];
|
|
681
|
-
case 6:
|
|
682
|
-
_c++;
|
|
683
|
-
return [3 /*break*/, 2];
|
|
684
|
-
case 7:
|
|
685
|
-
// Clean up empty lists
|
|
686
|
-
if (list.length === 0) {
|
|
687
|
-
this.connections.delete(key);
|
|
688
|
-
}
|
|
689
|
-
_f.label = 8;
|
|
690
|
-
case 8:
|
|
691
|
-
_i++;
|
|
692
|
-
return [3 /*break*/, 1];
|
|
693
|
-
case 9: return [2 /*return*/];
|
|
694
|
-
}
|
|
695
|
-
});
|
|
696
|
-
});
|
|
697
|
-
};
|
|
698
|
-
/**
|
|
699
|
-
* Start periodic cleanup
|
|
700
|
-
*/
|
|
701
|
-
SSHConnectionPool.prototype.startCleanup = function () {
|
|
702
|
-
var _this = this;
|
|
703
|
-
// Run cleanup every minute
|
|
704
|
-
this.cleanupInterval = setInterval(function () {
|
|
705
|
-
_this.cleanupIdle().catch(console.error);
|
|
706
|
-
}, 60 * 1000);
|
|
707
|
-
};
|
|
708
|
-
/**
|
|
709
|
-
* Stop cleanup interval
|
|
710
|
-
*/
|
|
711
|
-
SSHConnectionPool.prototype.stopCleanup = function () {
|
|
712
|
-
if (this.cleanupInterval) {
|
|
713
|
-
clearInterval(this.cleanupInterval);
|
|
714
|
-
this.cleanupInterval = null;
|
|
715
|
-
}
|
|
716
|
-
};
|
|
717
|
-
/**
|
|
718
|
-
* Close all connections and stop cleanup
|
|
719
|
-
*/
|
|
720
|
-
SSHConnectionPool.prototype.closeAll = function () {
|
|
721
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
722
|
-
var closePromises, _i, _a, list, _loop_3, _b, list_2, conn;
|
|
723
|
-
var _this = this;
|
|
724
|
-
return __generator(this, function (_c) {
|
|
725
|
-
switch (_c.label) {
|
|
726
|
-
case 0:
|
|
727
|
-
this.stopCleanup();
|
|
728
|
-
closePromises = [];
|
|
729
|
-
for (_i = 0, _a = this.connections.values(); _i < _a.length; _i++) {
|
|
730
|
-
list = _a[_i];
|
|
731
|
-
_loop_3 = function (conn) {
|
|
732
|
-
closePromises.push((function () { return __awaiter(_this, void 0, void 0, function () {
|
|
733
|
-
var _a;
|
|
734
|
-
return __generator(this, function (_b) {
|
|
735
|
-
switch (_b.label) {
|
|
736
|
-
case 0:
|
|
737
|
-
_b.trys.push([0, 2, , 3]);
|
|
738
|
-
return [4 /*yield*/, conn.ssh.dispose()];
|
|
739
|
-
case 1:
|
|
740
|
-
_b.sent();
|
|
741
|
-
return [3 /*break*/, 3];
|
|
742
|
-
case 2:
|
|
743
|
-
_a = _b.sent();
|
|
744
|
-
return [3 /*break*/, 3];
|
|
745
|
-
case 3: return [2 /*return*/];
|
|
746
|
-
}
|
|
747
|
-
});
|
|
748
|
-
}); })());
|
|
749
|
-
};
|
|
750
|
-
for (_b = 0, list_2 = list; _b < list_2.length; _b++) {
|
|
751
|
-
conn = list_2[_b];
|
|
752
|
-
_loop_3(conn);
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
return [4 /*yield*/, Promise.all(closePromises)];
|
|
756
|
-
case 1:
|
|
757
|
-
_c.sent();
|
|
758
|
-
this.connections.clear();
|
|
759
|
-
return [2 /*return*/];
|
|
760
|
-
}
|
|
761
|
-
});
|
|
762
|
-
});
|
|
763
|
-
};
|
|
764
|
-
/**
|
|
765
|
-
* Get pool statistics
|
|
766
|
-
*/
|
|
767
|
-
SSHConnectionPool.prototype.getStats = function () {
|
|
768
|
-
var now = Date.now();
|
|
769
|
-
var allConnections = [];
|
|
770
|
-
for (var _i = 0, _a = this.connections.values(); _i < _a.length; _i++) {
|
|
771
|
-
var list = _a[_i];
|
|
772
|
-
for (var _b = 0, list_3 = list; _b < list_3.length; _b++) {
|
|
773
|
-
var conn = list_3[_b];
|
|
774
|
-
allConnections.push({
|
|
775
|
-
host: conn.host,
|
|
776
|
-
port: conn.port,
|
|
777
|
-
user: conn.user,
|
|
778
|
-
lastUsed: new Date(conn.lastUsed),
|
|
779
|
-
idleMs: now - conn.lastUsed,
|
|
780
|
-
id: conn.id,
|
|
781
|
-
});
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
return {
|
|
785
|
-
totalConnections: allConnections.length,
|
|
786
|
-
connections: allConnections,
|
|
787
|
-
};
|
|
788
|
-
};
|
|
789
|
-
/**
|
|
790
|
-
* Check if a host has an active connection
|
|
791
|
-
*/
|
|
792
|
-
SSHConnectionPool.prototype.isConnected = function (host, user, port) {
|
|
793
|
-
if (user === void 0) { user = 'root'; }
|
|
794
|
-
if (port === void 0) { port = 22; }
|
|
795
|
-
var key = this.getKey(host, port, user);
|
|
796
|
-
var list = this.connections.get(key);
|
|
797
|
-
return list !== undefined && list.length > 0;
|
|
798
|
-
};
|
|
799
|
-
return SSHConnectionPool;
|
|
800
|
-
}());
|
|
801
|
-
exports.SSHConnectionPool = SSHConnectionPool;
|
|
802
|
-
// Global singleton instance
|
|
803
|
-
var globalPool = null;
|
|
804
|
-
/**
|
|
805
|
-
* Get the global connection pool instance
|
|
806
|
-
*/
|
|
807
|
-
function getSSHPool(config) {
|
|
808
|
-
if (!globalPool) {
|
|
809
|
-
globalPool = new SSHConnectionPool(config);
|
|
810
|
-
}
|
|
811
|
-
return globalPool;
|
|
812
|
-
}
|
|
813
|
-
/**
|
|
814
|
-
* Close the global pool (for cleanup/shutdown)
|
|
815
|
-
*/
|
|
816
|
-
function closeGlobalSSHPool() {
|
|
817
|
-
return __awaiter(this, void 0, void 0, function () {
|
|
818
|
-
return __generator(this, function (_a) {
|
|
819
|
-
switch (_a.label) {
|
|
820
|
-
case 0:
|
|
821
|
-
if (!globalPool) return [3 /*break*/, 2];
|
|
822
|
-
return [4 /*yield*/, globalPool.closeAll()];
|
|
823
|
-
case 1:
|
|
824
|
-
_a.sent();
|
|
825
|
-
globalPool = null;
|
|
826
|
-
_a.label = 2;
|
|
827
|
-
case 2: return [2 /*return*/];
|
|
828
|
-
}
|
|
829
|
-
});
|
|
830
|
-
});
|
|
831
|
-
}
|
|
832
|
-
/**
|
|
833
|
-
* Get active SSH connections (for monitoring)
|
|
834
|
-
*/
|
|
835
|
-
function getActiveSSHConnections() {
|
|
836
|
-
if (!globalPool) {
|
|
837
|
-
return { totalConnections: 0, connections: [] };
|
|
838
|
-
}
|
|
839
|
-
return globalPool.getStats();
|
|
840
|
-
}
|