@fenwave/agent 1.1.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/.claude/settings.local.json +11 -0
- package/Dockerfile +12 -0
- package/LICENSE +29 -0
- package/README.md +434 -0
- package/auth.js +276 -0
- package/cli-commands.js +1185 -0
- package/containerManager.js +385 -0
- package/convert-to-esm.sh +62 -0
- package/docker-actions/apps.js +3256 -0
- package/docker-actions/config-transformer.js +380 -0
- package/docker-actions/containers.js +346 -0
- package/docker-actions/general.js +171 -0
- package/docker-actions/images.js +1128 -0
- package/docker-actions/logs.js +188 -0
- package/docker-actions/metrics.js +270 -0
- package/docker-actions/registry.js +1100 -0
- package/docker-actions/terminal.js +247 -0
- package/docker-actions/volumes.js +696 -0
- package/helper-functions.js +193 -0
- package/index.html +60 -0
- package/index.js +988 -0
- package/package.json +49 -0
- package/setup/setupWizard.js +499 -0
- package/store/agentSessionStore.js +51 -0
- package/store/agentStore.js +113 -0
- package/store/configStore.js +174 -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/utils/deviceInfo.js +98 -0
- package/utils/ecrAuth.js +225 -0
- package/utils/encryption.js +112 -0
- package/utils/envSetup.js +54 -0
- package/utils/errorHandler.js +327 -0
- package/utils/prerequisites.js +323 -0
- package/utils/prompts.js +318 -0
- package/websocket-server.js +364 -0
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
import { docker } from './containers.js';
|
|
2
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
|
+
import { PassThrough } from 'stream';
|
|
4
|
+
|
|
5
|
+
async function handleTerminalAction(
|
|
6
|
+
ws,
|
|
7
|
+
clientId,
|
|
8
|
+
action,
|
|
9
|
+
payload,
|
|
10
|
+
terminalSessions
|
|
11
|
+
) {
|
|
12
|
+
switch (action) {
|
|
13
|
+
case 'openTerminalSession':
|
|
14
|
+
return await handleOpenTerminalSession(ws, payload, terminalSessions);
|
|
15
|
+
case 'closeTerminalSession':
|
|
16
|
+
return await handleCloseTerminalSession(ws, payload, terminalSessions);
|
|
17
|
+
case 'sendTerminalInput':
|
|
18
|
+
return await handleSendTerminalInput(ws, payload, terminalSessions);
|
|
19
|
+
case 'resizeTerminal':
|
|
20
|
+
return await handleResizeTerminal(ws, payload, terminalSessions);
|
|
21
|
+
default:
|
|
22
|
+
throw new Error(`Unknown terminal action: ${action}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function handleOpenTerminalSession(ws, payload, terminalSessions) {
|
|
27
|
+
try {
|
|
28
|
+
const { containerId, requestId } = payload;
|
|
29
|
+
const sessionId = `term-${uuidv4()}`;
|
|
30
|
+
|
|
31
|
+
const container = docker.getContainer(containerId);
|
|
32
|
+
const exec = await container.exec({
|
|
33
|
+
AttachStdin: true,
|
|
34
|
+
AttachStdout: true,
|
|
35
|
+
AttachStderr: true,
|
|
36
|
+
Tty: true,
|
|
37
|
+
Cmd: ['/bin/sh'],
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const stream = await exec.start({
|
|
41
|
+
hijack: true,
|
|
42
|
+
stdin: true,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Store the session with the exec instance
|
|
46
|
+
terminalSessions.set(sessionId, {
|
|
47
|
+
stream,
|
|
48
|
+
exec,
|
|
49
|
+
containerId,
|
|
50
|
+
lastActivity: Date.now(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// Use docker.modem.demuxStream to properly handle the stream
|
|
54
|
+
const stdout = new PassThrough();
|
|
55
|
+
const stderr = new PassThrough();
|
|
56
|
+
|
|
57
|
+
// Handle stdout data
|
|
58
|
+
stdout.on('data', (chunk) => {
|
|
59
|
+
ws.send(
|
|
60
|
+
JSON.stringify({
|
|
61
|
+
type: 'terminalOutput',
|
|
62
|
+
sessionId,
|
|
63
|
+
output: chunk.toString(),
|
|
64
|
+
})
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Handle stderr data
|
|
69
|
+
stderr.on('data', (chunk) => {
|
|
70
|
+
ws.send(
|
|
71
|
+
JSON.stringify({
|
|
72
|
+
type: 'terminalOutput',
|
|
73
|
+
sessionId,
|
|
74
|
+
output: chunk.toString(),
|
|
75
|
+
})
|
|
76
|
+
);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
// Demux the stream to remove Docker's multiplexing headers
|
|
80
|
+
docker.modem.demuxStream(stream, stdout, stderr);
|
|
81
|
+
|
|
82
|
+
// Handle stream end
|
|
83
|
+
stream.on('end', () => {
|
|
84
|
+
terminalSessions.delete(sessionId);
|
|
85
|
+
ws.send(
|
|
86
|
+
JSON.stringify({
|
|
87
|
+
type: 'terminalEnded',
|
|
88
|
+
sessionId,
|
|
89
|
+
})
|
|
90
|
+
);
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Handle stream error
|
|
94
|
+
stream.on('error', (error) => {
|
|
95
|
+
console.error('Terminal stream error:', error);
|
|
96
|
+
terminalSessions.delete(sessionId);
|
|
97
|
+
ws.send(
|
|
98
|
+
JSON.stringify({
|
|
99
|
+
type: 'error',
|
|
100
|
+
error: 'Terminal error: ' + error.message,
|
|
101
|
+
sessionId,
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
ws.send(
|
|
107
|
+
JSON.stringify({
|
|
108
|
+
type: 'terminalOpened',
|
|
109
|
+
sessionId,
|
|
110
|
+
containerId,
|
|
111
|
+
requestId,
|
|
112
|
+
})
|
|
113
|
+
);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
console.error('Error opening terminal session:', error);
|
|
116
|
+
ws.send(
|
|
117
|
+
JSON.stringify({
|
|
118
|
+
type: 'error',
|
|
119
|
+
error: 'Failed to open terminal session: ' + error.message,
|
|
120
|
+
requestId: payload.requestId,
|
|
121
|
+
})
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
async function handleCloseTerminalSession(ws, payload, terminalSessions) {
|
|
127
|
+
try {
|
|
128
|
+
const { sessionId, requestId } = payload;
|
|
129
|
+
|
|
130
|
+
if (terminalSessions.has(sessionId)) {
|
|
131
|
+
const session = terminalSessions.get(sessionId);
|
|
132
|
+
if (session.stream.destroy) {
|
|
133
|
+
session.stream.destroy();
|
|
134
|
+
}
|
|
135
|
+
terminalSessions.delete(sessionId);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
ws.send(
|
|
139
|
+
JSON.stringify({
|
|
140
|
+
type: 'terminalClosed',
|
|
141
|
+
sessionId,
|
|
142
|
+
success: true,
|
|
143
|
+
requestId,
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error('Error closing terminal session:', error);
|
|
148
|
+
ws.send(
|
|
149
|
+
JSON.stringify({
|
|
150
|
+
type: 'error',
|
|
151
|
+
error: 'Failed to close terminal session: ' + error.message,
|
|
152
|
+
requestId: payload.requestId,
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function handleSendTerminalInput(ws, payload, terminalSessions) {
|
|
159
|
+
try {
|
|
160
|
+
const { sessionId, input, requestId } = payload;
|
|
161
|
+
|
|
162
|
+
if (!terminalSessions.has(sessionId)) {
|
|
163
|
+
throw new Error('Terminal session not found');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const session = terminalSessions.get(sessionId);
|
|
167
|
+
session.lastActivity = Date.now();
|
|
168
|
+
session.stream.write(input);
|
|
169
|
+
|
|
170
|
+
ws.send(
|
|
171
|
+
JSON.stringify({
|
|
172
|
+
type: 'terminalInputSent',
|
|
173
|
+
sessionId,
|
|
174
|
+
success: true,
|
|
175
|
+
requestId,
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('Error sending terminal input:', error);
|
|
180
|
+
ws.send(
|
|
181
|
+
JSON.stringify({
|
|
182
|
+
type: 'error',
|
|
183
|
+
error: 'Failed to send terminal input: ' + error.message,
|
|
184
|
+
requestId: payload.requestId,
|
|
185
|
+
})
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function handleResizeTerminal(ws, payload, terminalSessions) {
|
|
191
|
+
try {
|
|
192
|
+
const { sessionId, cols, rows, requestId } = payload;
|
|
193
|
+
|
|
194
|
+
if (!terminalSessions.has(sessionId)) {
|
|
195
|
+
throw new Error('Terminal session not found');
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const session = terminalSessions.get(sessionId);
|
|
199
|
+
session.lastActivity = Date.now();
|
|
200
|
+
|
|
201
|
+
// Resize the terminal if the exec instance supports it
|
|
202
|
+
if (session.exec && typeof session.exec.resize === 'function') {
|
|
203
|
+
await session.exec.resize({ h: rows, w: cols });
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
ws.send(
|
|
207
|
+
JSON.stringify({
|
|
208
|
+
type: 'terminalResized',
|
|
209
|
+
sessionId,
|
|
210
|
+
success: true,
|
|
211
|
+
requestId,
|
|
212
|
+
})
|
|
213
|
+
);
|
|
214
|
+
} catch (error) {
|
|
215
|
+
console.error('Error resizing terminal:', error);
|
|
216
|
+
ws.send(
|
|
217
|
+
JSON.stringify({
|
|
218
|
+
type: 'error',
|
|
219
|
+
error: 'Failed to resize terminal: ' + error.message,
|
|
220
|
+
requestId: payload.requestId,
|
|
221
|
+
})
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Add a cleanup function for terminal sessions
|
|
227
|
+
function cleanupInactiveTerminalSessions(terminalSessions) {
|
|
228
|
+
const now = Date.now();
|
|
229
|
+
const inactivityThreshold = 15 * 60 * 1000; // 15 minutes
|
|
230
|
+
|
|
231
|
+
for (const [sessionId, session] of terminalSessions.entries()) {
|
|
232
|
+
if (now - session.lastActivity > inactivityThreshold) {
|
|
233
|
+
console.log(`Cleaning up inactive terminal session: ${sessionId}`);
|
|
234
|
+
if (session.stream.destroy) {
|
|
235
|
+
session.stream.destroy();
|
|
236
|
+
}
|
|
237
|
+
terminalSessions.delete(sessionId);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
export default { handleTerminalAction, cleanupInactiveTerminalSessions };
|
|
243
|
+
|
|
244
|
+
export {
|
|
245
|
+
handleTerminalAction,
|
|
246
|
+
cleanupInactiveTerminalSessions,
|
|
247
|
+
};
|