@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,385 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import { spawn, exec } from 'child_process';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { loadConfig } from './store/configStore.js';
|
|
6
|
+
|
|
7
|
+
// Load configuration
|
|
8
|
+
const config = loadConfig();
|
|
9
|
+
const CONTAINER_NAME = config.containerName;
|
|
10
|
+
const AGENT_ROOT_DIR = config.agentRootDir;
|
|
11
|
+
const REGISTRIES_DIR = config.registriesDir;
|
|
12
|
+
const HOST_DATA_DIR = path.join(os.homedir(), AGENT_ROOT_DIR, REGISTRIES_DIR);
|
|
13
|
+
const HOST_FW_DIR = path.join(os.homedir(), AGENT_ROOT_DIR);
|
|
14
|
+
const CONTAINER_DATA_DIR = config.containerDataDir;
|
|
15
|
+
const CONTAINER_FW_DIR = '/app/.fenwave';
|
|
16
|
+
const APP_PORT = config.containerPort;
|
|
17
|
+
const WS_PORT = config.wsPort;
|
|
18
|
+
const DOCKER_IMAGE = config.dockerImage;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Container Manager for Fenwave DevApp
|
|
22
|
+
*/
|
|
23
|
+
class ContainerManager {
|
|
24
|
+
constructor() {
|
|
25
|
+
this.isRunning = false;
|
|
26
|
+
this.containerProcess = null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Check if Docker is available
|
|
31
|
+
*/
|
|
32
|
+
async checkDockerAvailable() {
|
|
33
|
+
return new Promise((resolve) => {
|
|
34
|
+
exec('docker --version', (error) => {
|
|
35
|
+
resolve(!error);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if container is already running
|
|
42
|
+
*/
|
|
43
|
+
async isContainerRunning() {
|
|
44
|
+
return new Promise((resolve) => {
|
|
45
|
+
exec(
|
|
46
|
+
`docker ps --filter name=${CONTAINER_NAME} --format "{{.Names}}"`,
|
|
47
|
+
(error, stdout) => {
|
|
48
|
+
if (error) {
|
|
49
|
+
resolve(false);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
resolve(stdout.trim() === CONTAINER_NAME);
|
|
53
|
+
}
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Stop and remove existing container
|
|
60
|
+
*/
|
|
61
|
+
async stopContainer() {
|
|
62
|
+
return new Promise((resolve) => {
|
|
63
|
+
exec(
|
|
64
|
+
`docker stop ${CONTAINER_NAME} && docker rm ${CONTAINER_NAME}`,
|
|
65
|
+
(error) => {
|
|
66
|
+
// Don't worry if container doesn't exist
|
|
67
|
+
resolve();
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Authenticate with AWS ECR
|
|
75
|
+
*/
|
|
76
|
+
async authenticateECR() {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const awsRegion = config.awsRegion;
|
|
79
|
+
const awsAccountId = config.awsAccountId;
|
|
80
|
+
|
|
81
|
+
if (!awsRegion || !awsAccountId) {
|
|
82
|
+
console.log(chalk.yellow('⚠️ AWS ECR configuration not set, skipping ECR authentication'));
|
|
83
|
+
console.log(chalk.gray(' Run "fenwave init" to configure ECR settings'));
|
|
84
|
+
resolve();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
console.log(chalk.blue('🔐 Authenticating with AWS ECR...'));
|
|
89
|
+
|
|
90
|
+
const authProcess = spawn('aws', [
|
|
91
|
+
'ecr',
|
|
92
|
+
'get-login-password',
|
|
93
|
+
'--region',
|
|
94
|
+
awsRegion,
|
|
95
|
+
]);
|
|
96
|
+
|
|
97
|
+
let authToken = '';
|
|
98
|
+
let errorOutput = '';
|
|
99
|
+
|
|
100
|
+
authProcess.stdout.on('data', (data) => {
|
|
101
|
+
authToken += data.toString();
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
authProcess.stderr.on('data', (data) => {
|
|
105
|
+
errorOutput += data.toString();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
authProcess.on('close', (code) => {
|
|
109
|
+
if (code === 0) {
|
|
110
|
+
// Now login to Docker with the auth token
|
|
111
|
+
const loginProcess = spawn('docker', [
|
|
112
|
+
'login',
|
|
113
|
+
'--username',
|
|
114
|
+
'AWS',
|
|
115
|
+
'--password-stdin',
|
|
116
|
+
`${awsAccountId}.dkr.ecr.${awsRegion}.amazonaws.com`,
|
|
117
|
+
]);
|
|
118
|
+
|
|
119
|
+
loginProcess.stdin.write(authToken.trim());
|
|
120
|
+
loginProcess.stdin.end();
|
|
121
|
+
|
|
122
|
+
let loginError = '';
|
|
123
|
+
loginProcess.stderr.on('data', (data) => {
|
|
124
|
+
loginError += data.toString();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
loginProcess.on('close', (loginCode) => {
|
|
128
|
+
if (loginCode === 0) {
|
|
129
|
+
console.log(chalk.green('✅ Authenticated with ECR'));
|
|
130
|
+
resolve();
|
|
131
|
+
} else {
|
|
132
|
+
console.error(chalk.red('❌ Failed to login to Docker registry:'));
|
|
133
|
+
console.error(loginError);
|
|
134
|
+
reject(new Error('ECR Docker login failed'));
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
console.error(chalk.red('❌ Failed to get ECR auth token:'));
|
|
139
|
+
console.error(errorOutput);
|
|
140
|
+
reject(new Error('ECR authentication failed'));
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Check if image exists locally
|
|
148
|
+
*/
|
|
149
|
+
async imageExistsLocally() {
|
|
150
|
+
return new Promise((resolve) => {
|
|
151
|
+
exec(
|
|
152
|
+
`docker images -q ${DOCKER_IMAGE}`,
|
|
153
|
+
(error, stdout) => {
|
|
154
|
+
resolve(stdout.trim().length > 0);
|
|
155
|
+
}
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Pull the Fenwave DevApp image from ECR
|
|
162
|
+
*/
|
|
163
|
+
async pullImage() {
|
|
164
|
+
return new Promise(async (resolve, reject) => {
|
|
165
|
+
try {
|
|
166
|
+
// Check if image exists locally
|
|
167
|
+
const imageExists = await this.imageExistsLocally();
|
|
168
|
+
if (imageExists) {
|
|
169
|
+
console.log(chalk.green('✅ Image already exists locally, checking for updates...'));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
console.log(chalk.blue('📥 Pulling Fenwave DevApp image from ECR...'));
|
|
173
|
+
console.log(chalk.gray(` Image: ${DOCKER_IMAGE}`));
|
|
174
|
+
|
|
175
|
+
const pullProcess = spawn('docker', ['pull', DOCKER_IMAGE], {
|
|
176
|
+
stdio: 'pipe',
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
let output = '';
|
|
180
|
+
let errorOutput = '';
|
|
181
|
+
|
|
182
|
+
pullProcess.stdout.on('data', (data) => {
|
|
183
|
+
output += data.toString();
|
|
184
|
+
// Show pull progress for important lines
|
|
185
|
+
const lines = data
|
|
186
|
+
.toString()
|
|
187
|
+
.split('\n')
|
|
188
|
+
.filter((line) => line.trim());
|
|
189
|
+
lines.forEach((line) => {
|
|
190
|
+
if (
|
|
191
|
+
line.includes('Pulling from') ||
|
|
192
|
+
line.includes('Digest:') ||
|
|
193
|
+
line.includes('Status:') ||
|
|
194
|
+
line.includes('Downloaded')
|
|
195
|
+
) {
|
|
196
|
+
console.log(chalk.gray(` ${line.trim()}`));
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
pullProcess.stderr.on('data', (data) => {
|
|
202
|
+
errorOutput += data.toString();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
pullProcess.on('close', (code) => {
|
|
206
|
+
if (code === 0) {
|
|
207
|
+
console.log(chalk.green('✅ Image pulled successfully'));
|
|
208
|
+
resolve();
|
|
209
|
+
} else {
|
|
210
|
+
console.error(chalk.red('❌ Failed to pull image:'));
|
|
211
|
+
console.error(errorOutput);
|
|
212
|
+
reject(new Error(`Pull failed with code ${code}`));
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
} catch (error) {
|
|
216
|
+
reject(error);
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Start the Fenwave DevApp container
|
|
223
|
+
*/
|
|
224
|
+
async startContainer() {
|
|
225
|
+
try {
|
|
226
|
+
// Check Docker availability
|
|
227
|
+
const dockerAvailable = await this.checkDockerAvailable();
|
|
228
|
+
if (!dockerAvailable) {
|
|
229
|
+
throw new Error(
|
|
230
|
+
'Docker is not available. Please install Docker and try again.'
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Stop existing container if running
|
|
235
|
+
await this.stopContainer();
|
|
236
|
+
|
|
237
|
+
// Authenticate with ECR
|
|
238
|
+
try {
|
|
239
|
+
await this.authenticateECR();
|
|
240
|
+
} catch (ecrError) {
|
|
241
|
+
console.log(chalk.yellow('⚠️ ECR authentication failed, attempting to use cached image...'));
|
|
242
|
+
// Continue with cached image if authentication fails
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Pull image from ECR
|
|
246
|
+
try {
|
|
247
|
+
await this.pullImage();
|
|
248
|
+
} catch (pullError) {
|
|
249
|
+
console.log(chalk.yellow('⚠️ Image pull failed, checking for local image...'));
|
|
250
|
+
const imageExists = await this.imageExistsLocally();
|
|
251
|
+
if (!imageExists) {
|
|
252
|
+
throw new Error('No local image available and pull failed. Please check your AWS credentials and network connection.');
|
|
253
|
+
}
|
|
254
|
+
console.log(chalk.green('✅ Using cached local image'));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Determine WebSocket URL based on platform
|
|
258
|
+
let wsUrl = `ws://host.docker.internal:${WS_PORT}`;
|
|
259
|
+
if (os.platform() === 'linux') {
|
|
260
|
+
// On Linux, use host network or get host IP
|
|
261
|
+
wsUrl = `ws://172.17.0.1:${WS_PORT}`; // Docker default bridge IP
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
console.log(chalk.blue('🚀 Starting Fenwave DevApp container...'));
|
|
265
|
+
|
|
266
|
+
const runArgs = [
|
|
267
|
+
'run',
|
|
268
|
+
'-d', // Detached mode
|
|
269
|
+
'--name',
|
|
270
|
+
CONTAINER_NAME,
|
|
271
|
+
'-p',
|
|
272
|
+
`${APP_PORT}:${APP_PORT}`,
|
|
273
|
+
// Mount registry data directory
|
|
274
|
+
'-v',
|
|
275
|
+
`${HOST_DATA_DIR}:${CONTAINER_DATA_DIR}:rw`,
|
|
276
|
+
// Mount .fenwave directory for WebSocket token access
|
|
277
|
+
'-v',
|
|
278
|
+
`${HOST_FW_DIR}:${CONTAINER_FW_DIR}:ro`,
|
|
279
|
+
'-e',
|
|
280
|
+
`PORT=${APP_PORT}`,
|
|
281
|
+
'-e',
|
|
282
|
+
`NEXT_PUBLIC_WS_URL=${wsUrl}`,
|
|
283
|
+
'--restart',
|
|
284
|
+
'unless-stopped',
|
|
285
|
+
DOCKER_IMAGE,
|
|
286
|
+
];
|
|
287
|
+
|
|
288
|
+
return new Promise((resolve, reject) => {
|
|
289
|
+
const runProcess = spawn('docker', runArgs, {
|
|
290
|
+
stdio: 'pipe',
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
let output = '';
|
|
294
|
+
let errorOutput = '';
|
|
295
|
+
|
|
296
|
+
runProcess.stdout.on('data', (data) => {
|
|
297
|
+
output += data.toString();
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
runProcess.stderr.on('data', (data) => {
|
|
301
|
+
errorOutput += data.toString();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
runProcess.on('close', (code) => {
|
|
305
|
+
if (code === 0) {
|
|
306
|
+
this.isRunning = true;
|
|
307
|
+
console.log(chalk.green('✅ Container started successfully'));
|
|
308
|
+
console.log(chalk.gray(` Image: ${DOCKER_IMAGE}`));
|
|
309
|
+
console.log(chalk.gray(` Container: ${CONTAINER_NAME}`));
|
|
310
|
+
console.log(chalk.gray(` Port: ${APP_PORT}`));
|
|
311
|
+
console.log(chalk.gray(` Data volume: ${HOST_DATA_DIR} -> ${CONTAINER_DATA_DIR}`));
|
|
312
|
+
console.log(chalk.gray(` Token volume: ${HOST_FW_DIR} -> ${CONTAINER_FW_DIR}`));
|
|
313
|
+
resolve();
|
|
314
|
+
} else {
|
|
315
|
+
console.error(chalk.red('❌ Failed to start container:'));
|
|
316
|
+
if (errorOutput) {
|
|
317
|
+
console.error(errorOutput);
|
|
318
|
+
}
|
|
319
|
+
reject(new Error(`Container start failed with code ${code}`));
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
} catch (error) {
|
|
324
|
+
console.error(
|
|
325
|
+
chalk.red('❌ Failed to start Fenwave DevApp:'),
|
|
326
|
+
error.message
|
|
327
|
+
);
|
|
328
|
+
throw error;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Stop the Fenwave DevApp container
|
|
334
|
+
*/
|
|
335
|
+
async stopContainerGracefully() {
|
|
336
|
+
try {
|
|
337
|
+
const running = await this.isContainerRunning();
|
|
338
|
+
if (!running) {
|
|
339
|
+
console.log(chalk.yellow('ℹ️ Container is not running'));
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
await this.stopContainer();
|
|
344
|
+
this.isRunning = false;
|
|
345
|
+
} catch (error) {
|
|
346
|
+
console.error(chalk.red('❌ Failed to stop container:'), error.message);
|
|
347
|
+
throw error;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Get container status
|
|
353
|
+
*/
|
|
354
|
+
async getStatus() {
|
|
355
|
+
const running = await this.isContainerRunning();
|
|
356
|
+
return {
|
|
357
|
+
isRunning: running,
|
|
358
|
+
containerName: CONTAINER_NAME,
|
|
359
|
+
port: APP_PORT,
|
|
360
|
+
dataDirectory: HOST_DATA_DIR,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Show container logs
|
|
366
|
+
*/
|
|
367
|
+
showLogs(follow = false) {
|
|
368
|
+
const args = ['logs'];
|
|
369
|
+
if (follow) {
|
|
370
|
+
args.push('-f');
|
|
371
|
+
}
|
|
372
|
+
args.push(CONTAINER_NAME);
|
|
373
|
+
|
|
374
|
+
const logsProcess = spawn('docker', args, {
|
|
375
|
+
stdio: 'inherit',
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
return logsProcess;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Singleton instance
|
|
383
|
+
const containerManager = new ContainerManager();
|
|
384
|
+
|
|
385
|
+
export default containerManager;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# ES Module Conversion Script
|
|
4
|
+
# Converts CommonJS files to ES modules
|
|
5
|
+
|
|
6
|
+
echo "Converting CommonJS files to ES modules..."
|
|
7
|
+
|
|
8
|
+
convert_file() {
|
|
9
|
+
local file="$1"
|
|
10
|
+
|
|
11
|
+
if [ ! -f "$file" ]; then
|
|
12
|
+
echo "Skipping $file (not found)"
|
|
13
|
+
return
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
echo "Converting: $file"
|
|
17
|
+
|
|
18
|
+
# Backup original
|
|
19
|
+
cp "$file" "${file}.bak"
|
|
20
|
+
|
|
21
|
+
# Convert require to import
|
|
22
|
+
sed -i '' \
|
|
23
|
+
-e "s/const \(.*\) = require('\(.*\)');/import \1 from '\2';/g" \
|
|
24
|
+
-e "s/const { \([^}]*\) } = require('\(.*\)');/import { \1 } from '\2';/g" \
|
|
25
|
+
-e "s/require('dotenv').config();/import dotenv from 'dotenv';\ndotenv.config();/g" \
|
|
26
|
+
"$file"
|
|
27
|
+
|
|
28
|
+
# Convert module.exports
|
|
29
|
+
sed -i '' \
|
|
30
|
+
-e 's/module\.exports = {/export {/g' \
|
|
31
|
+
-e 's/module\.exports =/export default/g' \
|
|
32
|
+
"$file"
|
|
33
|
+
|
|
34
|
+
# Add export before function declarations that were in module.exports
|
|
35
|
+
# This is a simple heuristic - may need manual adjustment
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# Convert store files
|
|
39
|
+
for file in store/agentStore.js store/registryStore.js store/agentSessionStore.js; do
|
|
40
|
+
convert_file "$file"
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
# Convert docker-actions files
|
|
44
|
+
for file in docker-actions/*.js; do
|
|
45
|
+
convert_file "$file"
|
|
46
|
+
done
|
|
47
|
+
|
|
48
|
+
# Convert root files
|
|
49
|
+
for file in websocket-server.js auth.js containerManager.js cli-commands.js helper-functions.js; do
|
|
50
|
+
convert_file "$file"
|
|
51
|
+
done
|
|
52
|
+
|
|
53
|
+
echo ""
|
|
54
|
+
echo "Conversion complete!"
|
|
55
|
+
echo "Backup files created with .bak extension"
|
|
56
|
+
echo ""
|
|
57
|
+
echo "NOTE: Some manual fixes may be needed:"
|
|
58
|
+
echo " - Circular dependencies"
|
|
59
|
+
echo " - Complex module.exports patterns"
|
|
60
|
+
echo " - Dynamic requires"
|
|
61
|
+
echo ""
|
|
62
|
+
echo "Test with: npm run agent"
|