termbeam 1.17.2 → 1.17.3
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/package.json +1 -1
- package/src/cli/service.js +139 -54
package/package.json
CHANGED
package/src/cli/service.js
CHANGED
|
@@ -46,6 +46,7 @@ function installPm2Global() {
|
|
|
46
46
|
execFileSync('npm', ['install', '-g', 'pm2'], {
|
|
47
47
|
stdio: 'inherit',
|
|
48
48
|
timeout: 120000,
|
|
49
|
+
shell: os.platform() === 'win32',
|
|
49
50
|
});
|
|
50
51
|
console.log(green('✓ PM2 installed successfully.\n'));
|
|
51
52
|
return true;
|
|
@@ -122,6 +123,20 @@ function writeEcosystem(content) {
|
|
|
122
123
|
fs.writeFileSync(ECOSYSTEM_FILE, content, 'utf8');
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
function readEcosystemName() {
|
|
127
|
+
try {
|
|
128
|
+
const content = fs.readFileSync(ECOSYSTEM_FILE, 'utf8');
|
|
129
|
+
const json = content.replace(/^module\.exports\s*=\s*/, '').replace(/;\s*$/, '');
|
|
130
|
+
const eco = JSON.parse(json);
|
|
131
|
+
if (eco.apps && eco.apps[0] && eco.apps[0].name) {
|
|
132
|
+
return eco.apps[0].name;
|
|
133
|
+
}
|
|
134
|
+
} catch {
|
|
135
|
+
// ecosystem file missing or malformed
|
|
136
|
+
}
|
|
137
|
+
return DEFAULT_SERVICE_NAME;
|
|
138
|
+
}
|
|
139
|
+
|
|
125
140
|
// ── PM2 Commands ─────────────────────────────────────────────────────────────
|
|
126
141
|
|
|
127
142
|
function pm2Exec(args, opts = {}) {
|
|
@@ -131,6 +146,8 @@ function pm2Exec(args, opts = {}) {
|
|
|
131
146
|
encoding: 'utf8',
|
|
132
147
|
stdio: opts.inherit ? 'inherit' : ['pipe', 'pipe', 'pipe'],
|
|
133
148
|
timeout: 30000,
|
|
149
|
+
// Windows npm globals are .cmd wrappers; execFileSync needs shell to resolve them
|
|
150
|
+
shell: os.platform() === 'win32',
|
|
134
151
|
...opts,
|
|
135
152
|
});
|
|
136
153
|
} catch (err) {
|
|
@@ -225,6 +242,7 @@ async function actionInstall() {
|
|
|
225
242
|
|
|
226
243
|
// Service name
|
|
227
244
|
showProgress(0);
|
|
245
|
+
console.log(dim(' The PM2 process name for this service.\n'));
|
|
228
246
|
config.name = await ask(rl, 'Service name:', DEFAULT_SERVICE_NAME);
|
|
229
247
|
decisions.push({ label: 'Service name', value: config.name });
|
|
230
248
|
|
|
@@ -424,62 +442,108 @@ async function actionInstall() {
|
|
|
424
442
|
// Run pm2 startup if chosen during wizard
|
|
425
443
|
if (config.startup) {
|
|
426
444
|
console.log('');
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
// pm2 startup exits 1 by design — the sudo command is in stdout
|
|
438
|
-
startupOutput = (err.stdout || '') + (err.stderr || '');
|
|
439
|
-
}
|
|
440
|
-
const sudoMatch = startupOutput.match(/^(sudo .+)$/m);
|
|
441
|
-
if (sudoMatch) {
|
|
442
|
-
console.log(dim('Setting up boot persistence (may ask for your password)...\n'));
|
|
443
|
-
const { spawnSync } = require('child_process');
|
|
444
|
-
// pm2 outputs: sudo env PATH=$PATH:/extra /path/to/pm2 startup <init> -u <user> --hp <home>
|
|
445
|
-
// We can't use sh -c because $PATH may contain spaces (e.g. "Visual Studio Code.app").
|
|
446
|
-
// Instead, parse the structured command and pass PATH via env to avoid shell expansion.
|
|
447
|
-
const envMatch = sudoMatch[1].match(
|
|
448
|
-
/^sudo\s+env\s+PATH=\$PATH:([\S]+)\s+(\S+)\s+startup\s+(.+)$/,
|
|
445
|
+
if (os.platform() === 'win32') {
|
|
446
|
+
// Windows: pm2 startup doesn't support Windows init systems.
|
|
447
|
+
// Instead, create a script in the Windows Startup folder that runs pm2 resurrect.
|
|
448
|
+
const startupDir = path.join(
|
|
449
|
+
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
450
|
+
'Microsoft',
|
|
451
|
+
'Windows',
|
|
452
|
+
'Start Menu',
|
|
453
|
+
'Programs',
|
|
454
|
+
'Startup',
|
|
449
455
|
);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
const pm2Bin = envMatch[2]; // e.g. /opt/homebrew/.../pm2
|
|
454
|
-
const restArgs = envMatch[3].split(/\s+/); // e.g. ['launchd', '-u', 'user', '--hp', '/home']
|
|
455
|
-
const fullPath = (process.env.PATH || '') + ':' + extraPath;
|
|
456
|
-
result = spawnSync('sudo', ['env', `PATH=${fullPath}`, pm2Bin, 'startup', ...restArgs], {
|
|
457
|
-
stdio: 'inherit',
|
|
458
|
-
});
|
|
459
|
-
} else {
|
|
460
|
-
// Fallback: try running via sh with quoted PATH
|
|
461
|
-
const resolved = sudoMatch[1].replace(/\$PATH/g, `'${process.env.PATH || ''}'`);
|
|
462
|
-
result = spawnSync('sh', ['-c', resolved], { stdio: 'inherit' });
|
|
463
|
-
}
|
|
464
|
-
const code = result.status;
|
|
465
|
-
if (code === 0) {
|
|
456
|
+
const startupScript = path.join(startupDir, 'termbeam-pm2.cmd');
|
|
457
|
+
try {
|
|
458
|
+
fs.writeFileSync(startupScript, '@echo off\r\npm2 resurrect\r\n', 'utf8');
|
|
466
459
|
pm2Exec(['save'], { inherit: true });
|
|
467
460
|
console.log(green('✓ TermBeam will start automatically on boot.'));
|
|
468
|
-
|
|
469
|
-
|
|
461
|
+
console.log(dim(` Startup script: ${startupScript}`));
|
|
462
|
+
} catch (err) {
|
|
463
|
+
console.error(red('✗ Failed to create startup script.'));
|
|
470
464
|
console.log(yellow(" TermBeam is running, but won't auto-start after a reboot."));
|
|
471
|
-
console.log(yellow(' To fix this,
|
|
472
|
-
console.log(` ${cyan(
|
|
465
|
+
console.log(yellow(' To fix this manually, create a file at:\n'));
|
|
466
|
+
console.log(` ${cyan(startupScript)}`);
|
|
467
|
+
console.log(yellow('\n With contents:'));
|
|
468
|
+
console.log(` ${cyan('@echo off & pm2 resurrect')}`);
|
|
473
469
|
console.log(yellow('\n Then run:'));
|
|
474
470
|
console.log(` ${cyan('pm2 save')}\n`);
|
|
475
471
|
}
|
|
476
472
|
} else {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
473
|
+
// Unix/macOS/WSL: try pm2 startup to set up boot persistence
|
|
474
|
+
let startupOutput = '';
|
|
475
|
+
let succeeded = false;
|
|
476
|
+
let initSystemError = false;
|
|
477
|
+
try {
|
|
478
|
+
startupOutput = execFileSync('pm2', ['startup'], {
|
|
479
|
+
encoding: 'utf8',
|
|
480
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
481
|
+
timeout: 15000,
|
|
482
|
+
});
|
|
483
|
+
// Exit 0 means pm2 configured startup directly (e.g. running as root)
|
|
484
|
+
succeeded = true;
|
|
485
|
+
} catch (err) {
|
|
486
|
+
// pm2 startup exits 1 by design — the sudo command is in stdout
|
|
487
|
+
startupOutput = (err.stdout || '') + (err.stderr || '');
|
|
488
|
+
if (startupOutput.includes('Init system not found')) {
|
|
489
|
+
initSystemError = true;
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (succeeded) {
|
|
494
|
+
// pm2 startup succeeded (typically running as root) — just save
|
|
495
|
+
pm2Exec(['save'], { inherit: true });
|
|
496
|
+
console.log(green('✓ TermBeam will start automatically on boot.'));
|
|
497
|
+
} else if (initSystemError) {
|
|
498
|
+
// WSL without systemd, or other environments without an init system
|
|
499
|
+
console.log(yellow('⚠ No init system detected (common in WSL without systemd).'));
|
|
500
|
+
console.log(yellow(" TermBeam is running, but won't auto-start after a reboot."));
|
|
501
|
+
console.log(dim(' To enable boot persistence, either:'));
|
|
502
|
+
console.log(dim(' • Enable systemd in WSL: add [boot] systemd=true to /etc/wsl.conf'));
|
|
503
|
+
console.log(dim(' • Or add "pm2 resurrect" to your shell profile (~/.bashrc)\n'));
|
|
504
|
+
} else {
|
|
505
|
+
const sudoMatch = startupOutput.match(/^(sudo .+)$/m);
|
|
506
|
+
if (sudoMatch) {
|
|
507
|
+
console.log(dim('Setting up boot persistence (may ask for your password)...\n'));
|
|
508
|
+
const { spawnSync } = require('child_process');
|
|
509
|
+
const envMatch = sudoMatch[1].match(
|
|
510
|
+
/^sudo\s+env\s+PATH=\$PATH:([\S]+)\s+(\S+)\s+startup\s+(.+)$/,
|
|
511
|
+
);
|
|
512
|
+
let result;
|
|
513
|
+
if (envMatch) {
|
|
514
|
+
const extraPath = envMatch[1];
|
|
515
|
+
const pm2Bin = envMatch[2];
|
|
516
|
+
const restArgs = envMatch[3].split(/\s+/);
|
|
517
|
+
const fullPath = (process.env.PATH || '') + ':' + extraPath;
|
|
518
|
+
result = spawnSync(
|
|
519
|
+
'sudo',
|
|
520
|
+
['env', `PATH=${fullPath}`, pm2Bin, 'startup', ...restArgs],
|
|
521
|
+
{ stdio: 'inherit' },
|
|
522
|
+
);
|
|
523
|
+
} else {
|
|
524
|
+
const resolved = sudoMatch[1].replace(/\$PATH/g, `'${process.env.PATH || ''}'`);
|
|
525
|
+
result = spawnSync('sh', ['-c', resolved], { stdio: 'inherit' });
|
|
526
|
+
}
|
|
527
|
+
if (result.status === 0) {
|
|
528
|
+
pm2Exec(['save'], { inherit: true });
|
|
529
|
+
console.log(green('✓ TermBeam will start automatically on boot.'));
|
|
530
|
+
} else {
|
|
531
|
+
console.error(red('\n✗ Failed to set up boot persistence.'));
|
|
532
|
+
console.log(yellow(" TermBeam is running, but won't auto-start after a reboot."));
|
|
533
|
+
console.log(yellow(' To fix this, run the following command manually:\n'));
|
|
534
|
+
console.log(` ${cyan(sudoMatch[1])}`);
|
|
535
|
+
console.log(yellow('\n Then run:'));
|
|
536
|
+
console.log(` ${cyan('pm2 save')}\n`);
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
console.error(red('✗ Could not determine boot persistence command.'));
|
|
540
|
+
console.log(yellow(" TermBeam is running, but won't auto-start after a reboot."));
|
|
541
|
+
console.log(yellow(' To fix this, run:\n'));
|
|
542
|
+
console.log(` ${cyan('pm2 startup')}`);
|
|
543
|
+
console.log(dim(' …then run the sudo command it outputs, followed by:'));
|
|
544
|
+
console.log(` ${cyan('pm2 save')}\n`);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
483
547
|
}
|
|
484
548
|
}
|
|
485
549
|
|
|
@@ -528,20 +592,22 @@ async function actionUninstall() {
|
|
|
528
592
|
process.exit(1);
|
|
529
593
|
}
|
|
530
594
|
|
|
531
|
-
//
|
|
595
|
+
// Determine service name: prefer ecosystem config, then PM2 process list, then default
|
|
596
|
+
const ecoName = readEcosystemName();
|
|
532
597
|
const list = pm2Exec(['jlist'], { silent: true });
|
|
533
598
|
let services = [];
|
|
534
599
|
if (list) {
|
|
535
600
|
try {
|
|
536
601
|
services = JSON.parse(list).filter(
|
|
537
|
-
(p) =>
|
|
602
|
+
(p) =>
|
|
603
|
+
p.name === ecoName || p.name === DEFAULT_SERVICE_NAME || p.name.startsWith('termbeam'),
|
|
538
604
|
);
|
|
539
605
|
} catch {
|
|
540
606
|
// ignore parse errors
|
|
541
607
|
}
|
|
542
608
|
}
|
|
543
609
|
|
|
544
|
-
const name = services.length > 0 ? services[0].name :
|
|
610
|
+
const name = services.length > 0 ? services[0].name : ecoName;
|
|
545
611
|
|
|
546
612
|
const rl = createRL();
|
|
547
613
|
const sure = await confirm(rl, `Remove TermBeam service "${name}" from PM2?`, true);
|
|
@@ -562,6 +628,23 @@ async function actionUninstall() {
|
|
|
562
628
|
console.log(dim(`Removed ${ECOSYSTEM_FILE}`));
|
|
563
629
|
}
|
|
564
630
|
|
|
631
|
+
// Clean up Windows startup script if present
|
|
632
|
+
if (os.platform() === 'win32') {
|
|
633
|
+
const startupScript = path.join(
|
|
634
|
+
process.env.APPDATA || path.join(os.homedir(), 'AppData', 'Roaming'),
|
|
635
|
+
'Microsoft',
|
|
636
|
+
'Windows',
|
|
637
|
+
'Start Menu',
|
|
638
|
+
'Programs',
|
|
639
|
+
'Startup',
|
|
640
|
+
'termbeam-pm2.cmd',
|
|
641
|
+
);
|
|
642
|
+
if (fs.existsSync(startupScript)) {
|
|
643
|
+
fs.unlinkSync(startupScript);
|
|
644
|
+
console.log(dim(`Removed ${startupScript}`));
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
565
648
|
log.info('Service stopped');
|
|
566
649
|
console.log(green(`\n✓ TermBeam service "${name}" removed.\n`));
|
|
567
650
|
}
|
|
@@ -572,7 +655,7 @@ function actionStatus() {
|
|
|
572
655
|
console.error(red('✗ PM2 is not installed. Run: npm install -g pm2'));
|
|
573
656
|
process.exit(1);
|
|
574
657
|
}
|
|
575
|
-
pm2Exec(['describe',
|
|
658
|
+
pm2Exec(['describe', readEcosystemName()], { inherit: true });
|
|
576
659
|
}
|
|
577
660
|
|
|
578
661
|
function actionLogs() {
|
|
@@ -582,8 +665,9 @@ function actionLogs() {
|
|
|
582
665
|
process.exit(1);
|
|
583
666
|
}
|
|
584
667
|
const { spawn } = require('child_process');
|
|
585
|
-
const child = spawn('pm2', ['logs',
|
|
668
|
+
const child = spawn('pm2', ['logs', readEcosystemName(), '--lines', '200'], {
|
|
586
669
|
stdio: 'inherit',
|
|
670
|
+
shell: os.platform() === 'win32',
|
|
587
671
|
});
|
|
588
672
|
child.on('error', (err) => {
|
|
589
673
|
console.error(red(`✗ Failed to stream logs: ${err.message}`));
|
|
@@ -596,7 +680,7 @@ function actionRestart() {
|
|
|
596
680
|
console.error(red('✗ PM2 is not installed. Run: npm install -g pm2'));
|
|
597
681
|
process.exit(1);
|
|
598
682
|
}
|
|
599
|
-
pm2Exec(['restart',
|
|
683
|
+
pm2Exec(['restart', readEcosystemName()], { inherit: true });
|
|
600
684
|
log.info('Service restarted');
|
|
601
685
|
console.log(green('\n✓ TermBeam service restarted.\n'));
|
|
602
686
|
}
|
|
@@ -651,6 +735,7 @@ module.exports = {
|
|
|
651
735
|
buildArgs,
|
|
652
736
|
generateEcosystem,
|
|
653
737
|
writeEcosystem,
|
|
738
|
+
readEcosystemName,
|
|
654
739
|
pm2Exec,
|
|
655
740
|
actionStatus,
|
|
656
741
|
actionRestart,
|