@oussema_mili/test-pkg-123 1.1.32
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 +29 -0
- package/README.md +220 -0
- package/auth-callback.html +97 -0
- package/auth.js +276 -0
- package/cli-commands.js +1921 -0
- package/containerManager.js +304 -0
- package/daemon/agentRunner.js +491 -0
- package/daemon/daemonEntry.js +64 -0
- package/daemon/daemonManager.js +266 -0
- package/daemon/logManager.js +227 -0
- package/dist/styles.css +504 -0
- package/docker-actions/apps.js +3913 -0
- package/docker-actions/config-transformer.js +380 -0
- package/docker-actions/containers.js +355 -0
- package/docker-actions/general.js +171 -0
- package/docker-actions/images.js +1128 -0
- package/docker-actions/logs.js +224 -0
- package/docker-actions/metrics.js +270 -0
- package/docker-actions/registry.js +1100 -0
- package/docker-actions/setup-tasks.js +859 -0
- package/docker-actions/terminal.js +247 -0
- package/docker-actions/volumes.js +713 -0
- package/helper-functions.js +193 -0
- package/index.html +83 -0
- package/index.js +341 -0
- package/package.json +82 -0
- package/postcss.config.mjs +5 -0
- package/scripts/release.sh +212 -0
- package/setup/setupWizard.js +403 -0
- package/store/agentSessionStore.js +51 -0
- package/store/agentStore.js +113 -0
- package/store/configStore.js +171 -0
- package/store/daemonStore.js +217 -0
- package/store/deviceCredentialStore.js +107 -0
- package/store/npmTokenStore.js +65 -0
- package/store/registryStore.js +329 -0
- package/store/setupState.js +147 -0
- package/styles.css +1 -0
- package/utils/appLogger.js +223 -0
- package/utils/deviceInfo.js +98 -0
- package/utils/ecrAuth.js +225 -0
- package/utils/encryption.js +112 -0
- package/utils/envSetup.js +41 -0
- package/utils/errorHandler.js +327 -0
- package/utils/portUtils.js +59 -0
- package/utils/prerequisites.js +323 -0
- package/utils/prompts.js +318 -0
- package/utils/ssl-certificates.js +256 -0
- package/websocket-server.js +415 -0
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
import { WebSocketServer } from "ws";
|
|
2
|
+
import { v4 as uuidv4 } from "uuid";
|
|
3
|
+
import crypto from "crypto";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import path from "path";
|
|
6
|
+
import chalk from "chalk";
|
|
7
|
+
import os from "os";
|
|
8
|
+
import { loadConfig } from "./store/configStore.js";
|
|
9
|
+
|
|
10
|
+
// Import handlers
|
|
11
|
+
import containerHandlers from "./docker-actions/containers.js";
|
|
12
|
+
import imageHandlers from "./docker-actions/images.js";
|
|
13
|
+
import volumeHandlers from "./docker-actions/volumes.js";
|
|
14
|
+
import appHandlers from "./docker-actions/apps.js";
|
|
15
|
+
import registryHandlers from "./docker-actions/registry.js";
|
|
16
|
+
import logHandlers from "./docker-actions/logs.js";
|
|
17
|
+
import metricsHandlers from "./docker-actions/metrics.js";
|
|
18
|
+
import terminalHandlers from "./docker-actions/terminal.js";
|
|
19
|
+
import generalHandlers from "./docker-actions/general.js";
|
|
20
|
+
import setupTaskHandlers from "./docker-actions/setup-tasks.js";
|
|
21
|
+
import agentSessionStore from "./store/agentSessionStore.js";
|
|
22
|
+
|
|
23
|
+
// Store active streams and sessions
|
|
24
|
+
const activeStreams = new Map();
|
|
25
|
+
const terminalSessions = new Map();
|
|
26
|
+
|
|
27
|
+
// Load configuration
|
|
28
|
+
const config = loadConfig();
|
|
29
|
+
const AGENT_ROOT_DIR = path.join(os.homedir(), config.agentRootDir);
|
|
30
|
+
const WS_TOKEN_FILE = path.join(AGENT_ROOT_DIR, "ws-token");
|
|
31
|
+
const AUTH_TIMEOUT_MS = 30000; // 30 seconds to authenticate
|
|
32
|
+
|
|
33
|
+
// Heartbeat settings
|
|
34
|
+
const HEARTBEAT_INTERVAL_MS = 30000; // 30 seconds
|
|
35
|
+
const HEARTBEAT_TIMEOUT_MS = 35000; // 35 seconds to respond
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Generate a secure random WebSocket authentication token
|
|
39
|
+
* @returns {string} 64-character hex token
|
|
40
|
+
*/
|
|
41
|
+
function generateWsToken() {
|
|
42
|
+
return crypto.randomBytes(32).toString("hex");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Save WebSocket token to file for local-env app to read
|
|
47
|
+
* @param {string} token - The WebSocket authentication token
|
|
48
|
+
*/
|
|
49
|
+
function saveWsToken(token) {
|
|
50
|
+
// Ensure directory exists
|
|
51
|
+
if (!fs.existsSync(AGENT_ROOT_DIR)) {
|
|
52
|
+
fs.mkdirSync(AGENT_ROOT_DIR, { recursive: true, mode: 0o700 });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Write token to file with restricted permissions (owner only)
|
|
56
|
+
fs.writeFileSync(WS_TOKEN_FILE, token, { mode: 0o600 });
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Read WebSocket token from file
|
|
61
|
+
* @returns {string|null} The WebSocket token or null if not found
|
|
62
|
+
*/
|
|
63
|
+
function readWsToken() {
|
|
64
|
+
try {
|
|
65
|
+
if (fs.existsSync(WS_TOKEN_FILE)) {
|
|
66
|
+
return fs.readFileSync(WS_TOKEN_FILE, "utf-8").trim();
|
|
67
|
+
}
|
|
68
|
+
} catch (error) {
|
|
69
|
+
console.error("Error reading WS token:", error.message);
|
|
70
|
+
}
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function setupWebSocketServer(
|
|
75
|
+
server,
|
|
76
|
+
clients,
|
|
77
|
+
agentId,
|
|
78
|
+
sessionInfo = {},
|
|
79
|
+
existingToken = null
|
|
80
|
+
) {
|
|
81
|
+
// Store agent session info for use in handlers
|
|
82
|
+
agentSessionStore.setAgentSessionInfo({
|
|
83
|
+
agentId: agentId,
|
|
84
|
+
userEntityRef: sessionInfo.userEntityRef || null,
|
|
85
|
+
sessionExpiresAt: sessionInfo.expiresAt || null,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// Use existing token or generate a new one
|
|
89
|
+
const wsToken = existingToken || generateWsToken();
|
|
90
|
+
if (!existingToken) {
|
|
91
|
+
// Only save if we generated a new token
|
|
92
|
+
saveWsToken(wsToken);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const wss = new WebSocketServer({ server });
|
|
96
|
+
|
|
97
|
+
// Set up cleanup timer for inactive terminal sessions
|
|
98
|
+
setInterval(() => {
|
|
99
|
+
terminalHandlers.cleanupInactiveTerminalSessions(terminalSessions);
|
|
100
|
+
}, 15 * 60 * 1000); // Run every 15 minutes
|
|
101
|
+
|
|
102
|
+
// Setup heartbeat interval to check client health
|
|
103
|
+
const heartbeatInterval = setInterval(() => {
|
|
104
|
+
wss.clients.forEach((ws) => {
|
|
105
|
+
if (ws.isAlive === false) {
|
|
106
|
+
// Client didn't respond to ping, terminate
|
|
107
|
+
console.log(chalk.yellow(`Terminating unresponsive client`));
|
|
108
|
+
return ws.terminate();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
ws.isAlive = false;
|
|
112
|
+
ws.ping();
|
|
113
|
+
});
|
|
114
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
115
|
+
|
|
116
|
+
// Clean up heartbeat interval when server closes
|
|
117
|
+
wss.on("close", () => {
|
|
118
|
+
clearInterval(heartbeatInterval);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
wss.on("connection", (ws) => {
|
|
122
|
+
// Generate a unique client ID for each WebSocket connection
|
|
123
|
+
const clientId = uuidv4();
|
|
124
|
+
const connectionTime = Date.now();
|
|
125
|
+
let authenticated = false;
|
|
126
|
+
|
|
127
|
+
// Initialize heartbeat tracking
|
|
128
|
+
ws.isAlive = true;
|
|
129
|
+
|
|
130
|
+
// Handle pong responses (heartbeat acknowledgment)
|
|
131
|
+
ws.on("pong", () => {
|
|
132
|
+
ws.isAlive = true;
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
// Set authentication timeout
|
|
136
|
+
const authTimeout = setTimeout(() => {
|
|
137
|
+
if (!authenticated) {
|
|
138
|
+
ws.send(
|
|
139
|
+
JSON.stringify({
|
|
140
|
+
type: "auth_error",
|
|
141
|
+
error: "Authentication timeout. Connection closed.",
|
|
142
|
+
})
|
|
143
|
+
);
|
|
144
|
+
ws.close(4001, "Authentication timeout");
|
|
145
|
+
}
|
|
146
|
+
}, AUTH_TIMEOUT_MS);
|
|
147
|
+
|
|
148
|
+
// Send authentication challenge
|
|
149
|
+
ws.send(
|
|
150
|
+
JSON.stringify({
|
|
151
|
+
type: "auth_required",
|
|
152
|
+
message:
|
|
153
|
+
"Authentication required. Please send auth message with token.",
|
|
154
|
+
clientId,
|
|
155
|
+
})
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// Handle messages from client
|
|
159
|
+
ws.on("message", async (message) => {
|
|
160
|
+
let data;
|
|
161
|
+
try {
|
|
162
|
+
data = JSON.parse(message);
|
|
163
|
+
} catch (parseError) {
|
|
164
|
+
console.error("Error parsing message: ", parseError);
|
|
165
|
+
ws.send(
|
|
166
|
+
JSON.stringify({
|
|
167
|
+
type: "error",
|
|
168
|
+
error: "Failed to parse message: " + parseError.message,
|
|
169
|
+
})
|
|
170
|
+
);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Handle authentication
|
|
175
|
+
if (!authenticated) {
|
|
176
|
+
if (data.type === "auth" && data.token) {
|
|
177
|
+
if (data.token === wsToken) {
|
|
178
|
+
// Authentication successful
|
|
179
|
+
authenticated = true;
|
|
180
|
+
clearTimeout(authTimeout);
|
|
181
|
+
|
|
182
|
+
// Store client info
|
|
183
|
+
clients.set(clientId, {
|
|
184
|
+
ws,
|
|
185
|
+
connectionTime,
|
|
186
|
+
lastActivity: Date.now(),
|
|
187
|
+
isActive: true,
|
|
188
|
+
authenticated: true,
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
console.log(`✅ Client authenticated: ${clientId}`);
|
|
192
|
+
|
|
193
|
+
// Send success response
|
|
194
|
+
ws.send(
|
|
195
|
+
JSON.stringify({
|
|
196
|
+
type: "auth_success",
|
|
197
|
+
clientId,
|
|
198
|
+
agentId: agentId,
|
|
199
|
+
message: "Successfully authenticated with Fenwave Agent",
|
|
200
|
+
})
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
// Invalid token
|
|
204
|
+
console.log(
|
|
205
|
+
chalk.red(
|
|
206
|
+
`❌ Invalid authentication token from client: ${clientId}`
|
|
207
|
+
)
|
|
208
|
+
);
|
|
209
|
+
ws.send(
|
|
210
|
+
JSON.stringify({
|
|
211
|
+
type: "auth_error",
|
|
212
|
+
error: "Invalid authentication token",
|
|
213
|
+
})
|
|
214
|
+
);
|
|
215
|
+
ws.close(4003, "Invalid authentication token");
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
// Not an auth message
|
|
219
|
+
ws.send(
|
|
220
|
+
JSON.stringify({
|
|
221
|
+
type: "auth_error",
|
|
222
|
+
error: "Authentication required before sending messages",
|
|
223
|
+
})
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Update last activity time for authenticated clients
|
|
230
|
+
const client = clients.get(clientId);
|
|
231
|
+
if (client) {
|
|
232
|
+
client.lastActivity = Date.now();
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Only log received messages in debug mode
|
|
236
|
+
if (process.env.DEBUG === 'true' || process.env.FW_VERBOSE === 'true') {
|
|
237
|
+
console.log(`📨 Received message from client ${clientId}:`, data.action);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Route messages to appropriate handlers
|
|
241
|
+
await routeMessage(ws, clientId, data);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Handle client disconnect
|
|
245
|
+
ws.on("close", () => {
|
|
246
|
+
console.log(`🔴 Client disconnected: ${clientId}`);
|
|
247
|
+
clients.delete(clientId);
|
|
248
|
+
|
|
249
|
+
// Clean up any active streams for this client
|
|
250
|
+
for (const [streamId, stream] of activeStreams.entries()) {
|
|
251
|
+
if (streamId.startsWith(clientId)) {
|
|
252
|
+
if (stream.destroy) stream.destroy();
|
|
253
|
+
activeStreams.delete(streamId);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
return wss;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
async function routeMessage(ws, clientId, data) {
|
|
263
|
+
const { action, payload = {} } = data;
|
|
264
|
+
|
|
265
|
+
// Container actions
|
|
266
|
+
if (
|
|
267
|
+
action === "fetchContainers" ||
|
|
268
|
+
action === "startContainer" ||
|
|
269
|
+
action === "stopContainer" ||
|
|
270
|
+
action === "restartContainer" ||
|
|
271
|
+
action === "deleteContainer" ||
|
|
272
|
+
action === "createContainer" ||
|
|
273
|
+
action === "inspectContainer"
|
|
274
|
+
) {
|
|
275
|
+
return await containerHandlers.handleContainerAction(ws, action, payload);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Image actions
|
|
279
|
+
if (
|
|
280
|
+
action === "fetchImages" ||
|
|
281
|
+
action === "pullImage" ||
|
|
282
|
+
action === "pushImage" ||
|
|
283
|
+
action === "deleteImage" ||
|
|
284
|
+
action === "tagImage" ||
|
|
285
|
+
action === "inspectImage"
|
|
286
|
+
) {
|
|
287
|
+
return await imageHandlers.handleImageAction(ws, action, payload);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Volume actions
|
|
291
|
+
if (
|
|
292
|
+
action === "fetchVolumes" ||
|
|
293
|
+
action === "createVolume" ||
|
|
294
|
+
action === "deleteVolume" ||
|
|
295
|
+
action === "inspectVolume" ||
|
|
296
|
+
action === "getContainersUsingVolume" ||
|
|
297
|
+
action === "fetchImages" ||
|
|
298
|
+
action === "pullImage" ||
|
|
299
|
+
action === "backupVolume" ||
|
|
300
|
+
action === "listVolumeBackups" ||
|
|
301
|
+
action === "fetchImages" ||
|
|
302
|
+
action === "pullImage" ||
|
|
303
|
+
action === "restoreVolumeFromBackup" ||
|
|
304
|
+
action === "deleteVolumeBackup" ||
|
|
305
|
+
action === "getBackupDownloadUrl"
|
|
306
|
+
) {
|
|
307
|
+
return await volumeHandlers.handleVolumeAction(ws, action, payload);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// App actions
|
|
311
|
+
if (
|
|
312
|
+
action === "fetchApps" ||
|
|
313
|
+
action === "fetchAppVersions" ||
|
|
314
|
+
action === "startApp" ||
|
|
315
|
+
action === "stopApp" ||
|
|
316
|
+
action === "restartApp" ||
|
|
317
|
+
action === "deleteApp" ||
|
|
318
|
+
action === "deleteAppVersions" ||
|
|
319
|
+
action === "createApp" ||
|
|
320
|
+
action === "validateCompose" ||
|
|
321
|
+
action === "syncApp" ||
|
|
322
|
+
action === "changeVersion" ||
|
|
323
|
+
action === "cleanApp" ||
|
|
324
|
+
action === "fetchAppLogs" ||
|
|
325
|
+
action === "fetchLogContent"
|
|
326
|
+
) {
|
|
327
|
+
return await appHandlers.handleAppAction(ws, action, payload);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// Registry actions
|
|
331
|
+
if (
|
|
332
|
+
action === "fetchRegistries" ||
|
|
333
|
+
action === "connectRegistry" ||
|
|
334
|
+
action === "disconnectRegistry" ||
|
|
335
|
+
action === "renameRegistry" ||
|
|
336
|
+
action === "fetchRegistryImages" ||
|
|
337
|
+
action === "setActiveRegistry"
|
|
338
|
+
) {
|
|
339
|
+
return await registryHandlers.handleRegistryAction(ws, action, payload);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// Log actions
|
|
343
|
+
if (
|
|
344
|
+
action === "fetchContainerLogs" ||
|
|
345
|
+
action === "streamContainerLogs" ||
|
|
346
|
+
action === "stopStreamLogs"
|
|
347
|
+
) {
|
|
348
|
+
return await logHandlers.handleLogAction(
|
|
349
|
+
ws,
|
|
350
|
+
clientId,
|
|
351
|
+
action,
|
|
352
|
+
payload,
|
|
353
|
+
activeStreams
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Metrics actions
|
|
358
|
+
if (
|
|
359
|
+
action === "fetchSystemMetrics" ||
|
|
360
|
+
action === "fetchContainerMetrics" ||
|
|
361
|
+
action === "systemMetrics"
|
|
362
|
+
) {
|
|
363
|
+
return await metricsHandlers.handleMetricsAction(ws, action, payload);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Terminal actions
|
|
367
|
+
if (
|
|
368
|
+
action === "openTerminalSession" ||
|
|
369
|
+
action === "closeTerminalSession" ||
|
|
370
|
+
action === "sendTerminalInput" ||
|
|
371
|
+
action === "resizeTerminal"
|
|
372
|
+
) {
|
|
373
|
+
return await terminalHandlers.handleTerminalAction(
|
|
374
|
+
ws,
|
|
375
|
+
clientId,
|
|
376
|
+
action,
|
|
377
|
+
payload,
|
|
378
|
+
terminalSessions
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// General actions
|
|
383
|
+
if (["getAgentInfo", "getDockerInfo", "execInContainer"].includes(action)) {
|
|
384
|
+
return await generalHandlers.handleGeneralAction(
|
|
385
|
+
ws,
|
|
386
|
+
clientId,
|
|
387
|
+
action,
|
|
388
|
+
payload
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Setup task actions
|
|
393
|
+
if (
|
|
394
|
+
action === "getSetupTasks" ||
|
|
395
|
+
action === "getSetupStatus" ||
|
|
396
|
+
action === "verifySetupTask" ||
|
|
397
|
+
action === "executeSetupTask" ||
|
|
398
|
+
action === "saveSetupProgress" ||
|
|
399
|
+
action === "clearSetupProgress" ||
|
|
400
|
+
action === "browseDirectory"
|
|
401
|
+
) {
|
|
402
|
+
return await setupTaskHandlers.handleSetupAction(ws, action, payload);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Unknown action
|
|
406
|
+
ws.send(
|
|
407
|
+
JSON.stringify({
|
|
408
|
+
type: "error",
|
|
409
|
+
requestId: payload.requestId,
|
|
410
|
+
error: `Unknown action: ${action}`,
|
|
411
|
+
})
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
export { setupWebSocketServer, generateWsToken, saveWsToken, readWsToken };
|