fluxy-bot 0.5.32 → 0.5.34
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/bin/cli.js +236 -3
- package/package.json +1 -1
- package/supervisor/fluxy-agent.ts +1 -0
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { spawn, execSync } from 'child_process';
|
|
3
|
+
import { spawn, execSync, spawnSync } from 'child_process';
|
|
4
|
+
import readline from 'readline';
|
|
4
5
|
import fs from 'fs';
|
|
5
6
|
import path from 'path';
|
|
6
7
|
import os from 'os';
|
|
@@ -17,7 +18,78 @@ const BIN_DIR = path.join(DATA_DIR, 'bin');
|
|
|
17
18
|
const CF_PATH = path.join(BIN_DIR, 'cloudflared');
|
|
18
19
|
|
|
19
20
|
const pkg = JSON.parse(fs.readFileSync(path.join(ROOT, 'package.json'), 'utf-8'));
|
|
20
|
-
const [, , command] = process.argv;
|
|
21
|
+
const [, , command, subcommand] = process.argv;
|
|
22
|
+
|
|
23
|
+
// ── Daemon constants & helpers ──
|
|
24
|
+
|
|
25
|
+
const SERVICE_NAME = 'fluxy';
|
|
26
|
+
const SERVICE_PATH = `/etc/systemd/system/${SERVICE_NAME}.service`;
|
|
27
|
+
|
|
28
|
+
function needsSudo() {
|
|
29
|
+
return process.getuid() !== 0;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getRealUser() {
|
|
33
|
+
return process.env.SUDO_USER || os.userInfo().username;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getRealHome() {
|
|
37
|
+
if (process.env.FLUXY_REAL_HOME) return process.env.FLUXY_REAL_HOME;
|
|
38
|
+
try {
|
|
39
|
+
return execSync(`getent passwd ${getRealUser()}`, { encoding: 'utf-8' }).split(':')[5];
|
|
40
|
+
} catch {
|
|
41
|
+
return os.homedir();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isServiceInstalled() {
|
|
46
|
+
return fs.existsSync(SERVICE_PATH);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function isServiceActive() {
|
|
50
|
+
try {
|
|
51
|
+
execSync(`systemctl is-active ${SERVICE_NAME}`, { stdio: 'ignore' });
|
|
52
|
+
return true;
|
|
53
|
+
} catch {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function generateUnitFile({ user, home, nodePath, dataDir }) {
|
|
59
|
+
const nodeBinDir = path.dirname(nodePath);
|
|
60
|
+
return `[Unit]
|
|
61
|
+
Description=Fluxy Bot
|
|
62
|
+
After=network-online.target
|
|
63
|
+
Wants=network-online.target
|
|
64
|
+
|
|
65
|
+
[Service]
|
|
66
|
+
Type=simple
|
|
67
|
+
User=${user}
|
|
68
|
+
WorkingDirectory=${dataDir}
|
|
69
|
+
ExecStart=${nodePath} --import tsx/esm ${dataDir}/supervisor/index.ts
|
|
70
|
+
Restart=on-failure
|
|
71
|
+
RestartSec=5
|
|
72
|
+
Environment=HOME=${home}
|
|
73
|
+
Environment=NODE_ENV=production
|
|
74
|
+
Environment=PATH=${nodeBinDir}:/usr/local/bin:/usr/bin:/bin
|
|
75
|
+
StandardOutput=journal
|
|
76
|
+
StandardError=journal
|
|
77
|
+
SyslogIdentifier=fluxy
|
|
78
|
+
|
|
79
|
+
[Install]
|
|
80
|
+
WantedBy=multi-user.target
|
|
81
|
+
`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function askQuestion(query) {
|
|
85
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
86
|
+
return new Promise((resolve) => {
|
|
87
|
+
rl.question(query, (answer) => {
|
|
88
|
+
rl.close();
|
|
89
|
+
resolve(answer);
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
}
|
|
21
93
|
|
|
22
94
|
// ── UI helpers ──
|
|
23
95
|
|
|
@@ -350,6 +422,25 @@ async function init() {
|
|
|
350
422
|
stepper.finish();
|
|
351
423
|
finalMessage(tunnelUrl, relayUrl);
|
|
352
424
|
|
|
425
|
+
// Offer daemon install on Linux
|
|
426
|
+
if (os.platform() === 'linux' && process.stdin.isTTY) {
|
|
427
|
+
try {
|
|
428
|
+
const answer = await askQuestion(` ${c.bold}Install as a system daemon (auto-start on boot)?${c.reset} ${c.dim}[Y/n]${c.reset} `);
|
|
429
|
+
if (!answer || answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
430
|
+
console.log(`\n ${c.dim}Stopping foreground server...${c.reset}`);
|
|
431
|
+
child.kill('SIGTERM');
|
|
432
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
433
|
+
const nodePath = process.execPath;
|
|
434
|
+
const realHome = os.homedir();
|
|
435
|
+
const result = spawnSync(process.execPath, [process.argv[1], 'daemon', 'install'], {
|
|
436
|
+
stdio: 'inherit',
|
|
437
|
+
env: { ...process.env, FLUXY_NODE_PATH: nodePath, FLUXY_REAL_HOME: realHome },
|
|
438
|
+
});
|
|
439
|
+
process.exit(result.status ?? 0);
|
|
440
|
+
}
|
|
441
|
+
} catch {}
|
|
442
|
+
}
|
|
443
|
+
|
|
353
444
|
child.stdout.on('data', (d) => {
|
|
354
445
|
process.stdout.write(` ${c.dim}${d.toString().trim()}${c.reset}\n`);
|
|
355
446
|
});
|
|
@@ -532,7 +623,148 @@ async function update() {
|
|
|
532
623
|
stepper.finish();
|
|
533
624
|
|
|
534
625
|
console.log(`\n ${c.blue}${c.bold}✔ Updated to v${latest.version}${c.reset}\n`);
|
|
535
|
-
|
|
626
|
+
|
|
627
|
+
// Auto-restart daemon if installed
|
|
628
|
+
if (os.platform() === 'linux' && isServiceInstalled()) {
|
|
629
|
+
try {
|
|
630
|
+
execSync(`systemctl restart ${SERVICE_NAME}`, { stdio: 'ignore' });
|
|
631
|
+
console.log(` ${c.blue}✔${c.reset} Daemon restarted with new version.\n`);
|
|
632
|
+
} catch {
|
|
633
|
+
console.log(` ${c.yellow}⚠${c.reset} Could not restart daemon. Run ${c.pink}fluxy daemon restart${c.reset} manually.\n`);
|
|
634
|
+
}
|
|
635
|
+
} else {
|
|
636
|
+
console.log(` ${c.dim}Run ${c.reset}${c.pink}fluxy start${c.reset}${c.dim} to launch.${c.reset}\n`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// ── Daemon ──
|
|
641
|
+
|
|
642
|
+
async function daemon(sub) {
|
|
643
|
+
// Platform guard
|
|
644
|
+
if (os.platform() !== 'linux') {
|
|
645
|
+
const hint = os.platform() === 'darwin'
|
|
646
|
+
? 'Use tmux or nohup to keep Fluxy running in the background.'
|
|
647
|
+
: 'Use Task Scheduler to keep Fluxy running in the background.';
|
|
648
|
+
console.log(`\n ${c.yellow}⚠${c.reset} Daemon mode is only supported on Linux with systemd.`);
|
|
649
|
+
console.log(` ${c.dim}${hint}${c.reset}\n`);
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Check systemd is available
|
|
654
|
+
try {
|
|
655
|
+
execSync('systemctl --version', { stdio: 'ignore' });
|
|
656
|
+
} catch {
|
|
657
|
+
console.log(`\n ${c.red}✗${c.reset} systemd not found. Daemon mode requires systemd.\n`);
|
|
658
|
+
process.exit(1);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const action = sub || 'install';
|
|
662
|
+
|
|
663
|
+
switch (action) {
|
|
664
|
+
case 'install': {
|
|
665
|
+
if (!fs.existsSync(path.join(getRealHome(), '.fluxy', 'supervisor', 'index.ts'))) {
|
|
666
|
+
console.log(`\n ${c.red}✗${c.reset} Run ${c.pink}fluxy init${c.reset} first.\n`);
|
|
667
|
+
process.exit(1);
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
// Re-exec with sudo if needed
|
|
671
|
+
if (needsSudo()) {
|
|
672
|
+
const nodePath = process.env.FLUXY_NODE_PATH || process.execPath;
|
|
673
|
+
const realHome = getRealHome();
|
|
674
|
+
const args = process.argv.slice(1);
|
|
675
|
+
const result = spawnSync('sudo', [
|
|
676
|
+
`FLUXY_NODE_PATH=${nodePath}`,
|
|
677
|
+
`FLUXY_REAL_HOME=${realHome}`,
|
|
678
|
+
nodePath, ...args,
|
|
679
|
+
], { stdio: 'inherit' });
|
|
680
|
+
process.exit(result.status ?? 1);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
const user = getRealUser();
|
|
684
|
+
const home = getRealHome();
|
|
685
|
+
const nodePath = process.env.FLUXY_NODE_PATH || process.execPath;
|
|
686
|
+
const dataDir = path.join(home, '.fluxy');
|
|
687
|
+
|
|
688
|
+
const unit = generateUnitFile({ user, home, nodePath, dataDir });
|
|
689
|
+
fs.writeFileSync(SERVICE_PATH, unit);
|
|
690
|
+
|
|
691
|
+
execSync('systemctl daemon-reload', { stdio: 'ignore' });
|
|
692
|
+
execSync(`systemctl enable ${SERVICE_NAME}`, { stdio: 'ignore' });
|
|
693
|
+
execSync(`systemctl start ${SERVICE_NAME}`, { stdio: 'ignore' });
|
|
694
|
+
|
|
695
|
+
// Verify it started
|
|
696
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
697
|
+
if (isServiceActive()) {
|
|
698
|
+
console.log(`\n ${c.blue}✔${c.reset} Fluxy daemon installed and running.`);
|
|
699
|
+
console.log(` ${c.dim}It will auto-start on boot.${c.reset}`);
|
|
700
|
+
console.log(`\n ${c.dim}View logs:${c.reset} ${c.pink}fluxy daemon logs${c.reset}`);
|
|
701
|
+
console.log(` ${c.dim}Stop:${c.reset} ${c.pink}fluxy daemon stop${c.reset}`);
|
|
702
|
+
console.log(` ${c.dim}Uninstall:${c.reset} ${c.pink}fluxy daemon uninstall${c.reset}\n`);
|
|
703
|
+
} else {
|
|
704
|
+
console.log(`\n ${c.yellow}⚠${c.reset} Service installed but may not be running.`);
|
|
705
|
+
console.log(` ${c.dim}Check with: ${c.reset}${c.pink}fluxy daemon status${c.reset}\n`);
|
|
706
|
+
}
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
case 'stop': {
|
|
711
|
+
if (needsSudo()) {
|
|
712
|
+
const result = spawnSync('sudo', [process.execPath, ...process.argv.slice(1)], { stdio: 'inherit' });
|
|
713
|
+
process.exit(result.status ?? 1);
|
|
714
|
+
}
|
|
715
|
+
execSync(`systemctl stop ${SERVICE_NAME}`, { stdio: 'inherit' });
|
|
716
|
+
console.log(`\n ${c.blue}✔${c.reset} Fluxy daemon stopped.\n`);
|
|
717
|
+
break;
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
case 'start': {
|
|
721
|
+
if (needsSudo()) {
|
|
722
|
+
const result = spawnSync('sudo', [process.execPath, ...process.argv.slice(1)], { stdio: 'inherit' });
|
|
723
|
+
process.exit(result.status ?? 1);
|
|
724
|
+
}
|
|
725
|
+
execSync(`systemctl start ${SERVICE_NAME}`, { stdio: 'inherit' });
|
|
726
|
+
console.log(`\n ${c.blue}✔${c.reset} Fluxy daemon started.\n`);
|
|
727
|
+
break;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
case 'restart': {
|
|
731
|
+
if (needsSudo()) {
|
|
732
|
+
const result = spawnSync('sudo', [process.execPath, ...process.argv.slice(1)], { stdio: 'inherit' });
|
|
733
|
+
process.exit(result.status ?? 1);
|
|
734
|
+
}
|
|
735
|
+
execSync(`systemctl restart ${SERVICE_NAME}`, { stdio: 'inherit' });
|
|
736
|
+
console.log(`\n ${c.blue}✔${c.reset} Fluxy daemon restarted.\n`);
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
case 'status': {
|
|
741
|
+
spawnSync('systemctl', ['status', SERVICE_NAME], { stdio: 'inherit' });
|
|
742
|
+
break;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
case 'logs': {
|
|
746
|
+
spawnSync('journalctl', ['-u', SERVICE_NAME, '-f', '-n', '50'], { stdio: 'inherit' });
|
|
747
|
+
break;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
case 'uninstall': {
|
|
751
|
+
if (needsSudo()) {
|
|
752
|
+
const result = spawnSync('sudo', [process.execPath, ...process.argv.slice(1)], { stdio: 'inherit' });
|
|
753
|
+
process.exit(result.status ?? 1);
|
|
754
|
+
}
|
|
755
|
+
try { execSync(`systemctl stop ${SERVICE_NAME}`, { stdio: 'ignore' }); } catch {}
|
|
756
|
+
try { execSync(`systemctl disable ${SERVICE_NAME}`, { stdio: 'ignore' }); } catch {}
|
|
757
|
+
if (fs.existsSync(SERVICE_PATH)) fs.unlinkSync(SERVICE_PATH);
|
|
758
|
+
execSync('systemctl daemon-reload', { stdio: 'ignore' });
|
|
759
|
+
console.log(`\n ${c.blue}✔${c.reset} Fluxy daemon uninstalled.\n`);
|
|
760
|
+
break;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
default:
|
|
764
|
+
console.log(`\n ${c.red}✗${c.reset} Unknown daemon command: ${action}`);
|
|
765
|
+
console.log(` ${c.dim}Available: install, start, stop, restart, status, logs, uninstall${c.reset}\n`);
|
|
766
|
+
process.exit(1);
|
|
767
|
+
}
|
|
536
768
|
}
|
|
537
769
|
|
|
538
770
|
// ── Route ──
|
|
@@ -542,6 +774,7 @@ switch (command) {
|
|
|
542
774
|
case 'start': start(); break;
|
|
543
775
|
case 'status': status(); break;
|
|
544
776
|
case 'update': update(); break;
|
|
777
|
+
case 'daemon': daemon(subcommand); break;
|
|
545
778
|
default:
|
|
546
779
|
fs.existsSync(CONFIG_PATH) ? start() : init();
|
|
547
780
|
}
|
package/package.json
CHANGED