@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 CHANGED
@@ -417,13 +417,11 @@ function setupCLICommands(program, startServerFunction) {
417
417
  serviceCommand
418
418
  .command("stop")
419
419
  .description("stop the running daemon")
420
- .option("-f, --force", "force kill if graceful shutdown fails")
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
 
@@ -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 server = http.createServer();
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 () => {
@@ -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 { force = false, timeout = 10000 } = options;
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, try SIGKILL if force option
155
- if (force) {
156
- console.log(chalk.yellow('Graceful shutdown timed out, forcing...'));
157
- try {
158
- process.kill(pid, 'SIGKILL');
159
- await new Promise((resolve) => setTimeout(resolve, 1000));
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
- // Create proper backup directory (relative to the parent directory)
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 (relative to parent directory)
436
- const backupDir = path.join(process.cwd(), '..', 'volume-backups');
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
- // Security check: only allow deletion from the backup directory (relative to parent)
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
- // Security check: only allow downloads from the backup directory (relative to parent)
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
- const downloadUrl = `/api/download?path=${encodeURIComponent(backupPath)}`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oussema_mili/test-pkg-123",
3
- "version": "1.1.25",
3
+ "version": "1.1.26",
4
4
  "description": "Fenwave Docker Agent and CLI",
5
5
  "keywords": [
6
6
  "fenwave",
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