@oussema_mili/test-pkg-123 1.1.25 → 1.1.26
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.
Potentially problematic release.
This version of @oussema_mili/test-pkg-123 might be problematic. Click here for more details.
- package/cli-commands.js +1 -3
- package/daemon/agentRunner.js +64 -2
- package/daemon/daemonManager.js +13 -18
- package/docker-actions/volumes.js +29 -12
- package/package.json +1 -1
- package/utils/envSetup.js +0 -3
package/cli-commands.js
CHANGED
|
@@ -417,13 +417,11 @@ function setupCLICommands(program, startServerFunction) {
|
|
|
417
417
|
serviceCommand
|
|
418
418
|
.command("stop")
|
|
419
419
|
.description("stop the running daemon")
|
|
420
|
-
.
|
|
421
|
-
.action(async (options) => {
|
|
420
|
+
.action(async () => {
|
|
422
421
|
const spinner = ora("Stopping Fenwave Agent daemon...").start();
|
|
423
422
|
|
|
424
423
|
try {
|
|
425
424
|
const stopped = await stopDaemon({
|
|
426
|
-
force: options.force,
|
|
427
425
|
timeout: 10000,
|
|
428
426
|
});
|
|
429
427
|
|
package/daemon/agentRunner.js
CHANGED
|
@@ -315,10 +315,72 @@ export async function runAgent(options = {}) {
|
|
|
315
315
|
log('Starting agent without container...');
|
|
316
316
|
}
|
|
317
317
|
|
|
318
|
-
// Create HTTP server
|
|
318
|
+
// Create HTTP server with download endpoint handler
|
|
319
319
|
const agentId = uuidv4();
|
|
320
320
|
const existingWsToken = readWsToken();
|
|
321
|
-
const
|
|
321
|
+
const backupDir = path.join(os.homedir(), 'volume-backups');
|
|
322
|
+
|
|
323
|
+
const server = http.createServer((req, res) => {
|
|
324
|
+
// Handle backup download requests
|
|
325
|
+
if (req.method === 'GET' && req.url?.startsWith('/api/download')) {
|
|
326
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
327
|
+
const backupPath = url.searchParams.get('path');
|
|
328
|
+
|
|
329
|
+
if (!backupPath) {
|
|
330
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
331
|
+
res.end(JSON.stringify({ error: 'Missing backup path' }));
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// Security check: only allow downloads from the backup directory
|
|
336
|
+
const normalizedBackupPath = path.resolve(backupPath);
|
|
337
|
+
const normalizedBackupDir = path.resolve(backupDir);
|
|
338
|
+
|
|
339
|
+
if (
|
|
340
|
+
!normalizedBackupPath.startsWith(normalizedBackupDir) ||
|
|
341
|
+
!path.basename(backupPath).includes('-backup-') ||
|
|
342
|
+
!backupPath.endsWith('.tar')
|
|
343
|
+
) {
|
|
344
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
345
|
+
res.end(JSON.stringify({ error: 'Invalid backup path' }));
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Check if file exists
|
|
350
|
+
if (!fs.existsSync(normalizedBackupPath)) {
|
|
351
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
352
|
+
res.end(JSON.stringify({ error: 'Backup file not found' }));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Stream the file to the client
|
|
357
|
+
const stat = fs.statSync(normalizedBackupPath);
|
|
358
|
+
const filename = path.basename(normalizedBackupPath);
|
|
359
|
+
|
|
360
|
+
res.writeHead(200, {
|
|
361
|
+
'Content-Type': 'application/x-tar',
|
|
362
|
+
'Content-Length': stat.size,
|
|
363
|
+
'Content-Disposition': `attachment; filename="${filename}"`,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
const readStream = fs.createReadStream(normalizedBackupPath);
|
|
367
|
+
readStream.pipe(res);
|
|
368
|
+
|
|
369
|
+
readStream.on('error', (err) => {
|
|
370
|
+
log(`Error streaming backup file: ${err.message}`, 'error');
|
|
371
|
+
if (!res.headersSent) {
|
|
372
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
373
|
+
res.end(JSON.stringify({ error: 'Failed to download backup' }));
|
|
374
|
+
}
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// For non-download requests, return 404 (WebSocket handles the rest)
|
|
381
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
382
|
+
res.end(JSON.stringify({ error: 'Not found' }));
|
|
383
|
+
});
|
|
322
384
|
|
|
323
385
|
return new Promise((resolve, reject) => {
|
|
324
386
|
server.listen(actualPort, async () => {
|
package/daemon/daemonManager.js
CHANGED
|
@@ -99,12 +99,11 @@ export async function startDaemon(options = {}) {
|
|
|
99
99
|
/**
|
|
100
100
|
* Stop the running daemon
|
|
101
101
|
* @param {Object} options - Stop options
|
|
102
|
-
* @param {boolean} options.force - Force kill if graceful fails
|
|
103
102
|
* @param {number} options.timeout - Timeout in ms to wait for graceful shutdown
|
|
104
103
|
* @returns {Promise<boolean>} True if stopped
|
|
105
104
|
*/
|
|
106
105
|
export async function stopDaemon(options = {}) {
|
|
107
|
-
const {
|
|
106
|
+
const { timeout = 10000 } = options;
|
|
108
107
|
|
|
109
108
|
const pid = getDaemonPid();
|
|
110
109
|
if (!pid) {
|
|
@@ -151,27 +150,23 @@ export async function stopDaemon(options = {}) {
|
|
|
151
150
|
}
|
|
152
151
|
}
|
|
153
152
|
|
|
154
|
-
// Timeout reached,
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
153
|
+
// Timeout reached, force kill (always happens automatically now)
|
|
154
|
+
console.log(chalk.yellow('Graceful shutdown timed out, forcing stop...'));
|
|
155
|
+
try {
|
|
156
|
+
process.kill(pid, 'SIGKILL');
|
|
157
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
158
|
+
clearDaemonPid();
|
|
159
|
+
clearDaemonState();
|
|
160
|
+
console.log(chalk.green('Daemon stopped'));
|
|
161
|
+
return true;
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (error.code === 'ESRCH') {
|
|
160
164
|
clearDaemonPid();
|
|
161
165
|
clearDaemonState();
|
|
162
|
-
console.log(chalk.green('Daemon force killed'));
|
|
163
166
|
return true;
|
|
164
|
-
} catch (error) {
|
|
165
|
-
if (error.code === 'ESRCH') {
|
|
166
|
-
clearDaemonPid();
|
|
167
|
-
clearDaemonState();
|
|
168
|
-
return true;
|
|
169
|
-
}
|
|
170
|
-
throw error;
|
|
171
167
|
}
|
|
168
|
+
throw error;
|
|
172
169
|
}
|
|
173
|
-
|
|
174
|
-
throw new Error('Daemon did not stop within timeout. Use --force to kill it.');
|
|
175
170
|
}
|
|
176
171
|
|
|
177
172
|
/**
|
|
@@ -4,10 +4,28 @@ import { exec } from 'child_process';
|
|
|
4
4
|
import util from 'util';
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import path from 'path';
|
|
7
|
+
import os from 'os';
|
|
8
|
+
import { loadConfig } from '../store/configStore.js';
|
|
9
|
+
import { getDaemonState } from '../store/daemonStore.js';
|
|
7
10
|
|
|
8
11
|
const execPromise = util.promisify(exec);
|
|
9
12
|
const fsPromises = fs.promises;
|
|
10
13
|
|
|
14
|
+
// Get the backup directory in the user's home directory
|
|
15
|
+
function getBackupDir() {
|
|
16
|
+
return path.join(os.homedir(), 'volume-backups');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Get the agent's HTTP port (from daemon state or config)
|
|
20
|
+
function getAgentPort() {
|
|
21
|
+
const state = getDaemonState();
|
|
22
|
+
if (state?.port) {
|
|
23
|
+
return state.port;
|
|
24
|
+
}
|
|
25
|
+
const config = loadConfig();
|
|
26
|
+
return config.wsPort || 3001;
|
|
27
|
+
}
|
|
28
|
+
|
|
11
29
|
// Cache for docker system df result to avoid concurrent operations
|
|
12
30
|
let dfCache = null;
|
|
13
31
|
let dfCacheTime = 0;
|
|
@@ -331,9 +349,8 @@ async function handleBackupVolume(ws, payload = {}) {
|
|
|
331
349
|
throw new Error('Missing volume name.');
|
|
332
350
|
}
|
|
333
351
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const backupDir = path.join(process.cwd(), '..', 'volume-backups');
|
|
352
|
+
// Create backup directory in user's home directory
|
|
353
|
+
const backupDir = getBackupDir();
|
|
337
354
|
|
|
338
355
|
try {
|
|
339
356
|
await fsPromises.mkdir(backupDir, { recursive: true });
|
|
@@ -432,8 +449,8 @@ async function handleListVolumeBackups(ws, payload = {}) {
|
|
|
432
449
|
try {
|
|
433
450
|
const { requestId } = payload;
|
|
434
451
|
|
|
435
|
-
// Use the same backup directory as backup creation
|
|
436
|
-
const backupDir =
|
|
452
|
+
// Use the same backup directory as backup creation
|
|
453
|
+
const backupDir = getBackupDir();
|
|
437
454
|
const backups = [];
|
|
438
455
|
|
|
439
456
|
try {
|
|
@@ -600,9 +617,8 @@ async function handleDeleteVolumeBackup(ws, payload = {}) {
|
|
|
600
617
|
throw new Error('Missing backup path');
|
|
601
618
|
}
|
|
602
619
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
const backupDir = path.join(process.cwd(), '..', 'volume-backups');
|
|
620
|
+
// Security check: only allow deletion from the backup directory
|
|
621
|
+
const backupDir = getBackupDir();
|
|
606
622
|
const normalizedBackupPath = path.resolve(backupPath);
|
|
607
623
|
const normalizedBackupDir = path.resolve(backupDir);
|
|
608
624
|
|
|
@@ -647,9 +663,8 @@ async function handleGetBackupDownloadUrl(ws, payload = {}) {
|
|
|
647
663
|
throw new Error('Missing backup path');
|
|
648
664
|
}
|
|
649
665
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
const backupDir = path.join(process.cwd(), '..', 'volume-backups');
|
|
666
|
+
// Get backup directory from home directory
|
|
667
|
+
const backupDir = getBackupDir();
|
|
653
668
|
const normalizedBackupPath = path.resolve(backupPath);
|
|
654
669
|
const normalizedBackupDir = path.resolve(backupDir);
|
|
655
670
|
|
|
@@ -668,7 +683,9 @@ async function handleGetBackupDownloadUrl(ws, payload = {}) {
|
|
|
668
683
|
throw new Error('Backup file not found');
|
|
669
684
|
}
|
|
670
685
|
|
|
671
|
-
|
|
686
|
+
// Return the download URL pointing to the agent's HTTP server
|
|
687
|
+
const agentPort = getAgentPort();
|
|
688
|
+
const downloadUrl = `http://localhost:${agentPort}/api/download?path=${encodeURIComponent(backupPath)}`;
|
|
672
689
|
|
|
673
690
|
ws.send(
|
|
674
691
|
JSON.stringify({
|
package/package.json
CHANGED
package/utils/envSetup.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import os from "os";
|
|
4
|
-
import chalk from "chalk";
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* Ensures environment files exist with default values
|
|
@@ -32,9 +31,7 @@ REGISTRIES_DIR=registries
|
|
|
32
31
|
AUTH_TIMEOUT_MS=60000
|
|
33
32
|
`;
|
|
34
33
|
fs.writeFileSync(agentEnvPath, defaultAgentEnv, { mode: 0o600 });
|
|
35
|
-
console.log(chalk.green("📄 Created .env.agent with default values"));
|
|
36
34
|
} else if (showFoundMessages) {
|
|
37
|
-
console.log(chalk.green("📄 Found existing .env.agent file"));
|
|
38
35
|
}
|
|
39
36
|
|
|
40
37
|
// Note: DevApp runs as a Docker container from GHCR with environment variables
|