vibelet 1.0.11 → 1.0.12
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/vibelet.mjs +99 -19
- package/dist/index.cjs +52 -50
- package/package.json +1 -1
package/bin/vibelet.mjs
CHANGED
|
@@ -8,6 +8,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
8
8
|
import QRCode from 'qrcode';
|
|
9
9
|
import { extractQuickTunnelUrl } from './cloudflared-quick-tunnel.mjs';
|
|
10
10
|
import { formatCloudflaredFailureMessage, resolveCloudflaredLaunchSpec } from './cloudflared-resolver.mjs';
|
|
11
|
+
import { canUseSystemdUserManager, isSystemdUserManagerUnavailable } from './linux-systemd.mjs';
|
|
11
12
|
import { doesHealthMatchRequestedConnectionConfig, shouldReuseHealthyDaemon } from './vibelet-runtime-policy.mjs';
|
|
12
13
|
|
|
13
14
|
// ─── Paths & constants ─────────────────────────────────────────────────────────
|
|
@@ -16,7 +17,6 @@ const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
|
16
17
|
const packageJson = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8'));
|
|
17
18
|
const daemonDistDir = resolve(rootDir, 'dist');
|
|
18
19
|
const daemonEntryPath = resolve(daemonDistDir, 'index.cjs');
|
|
19
|
-
const port = Number(process.env.VIBE_PORT) || 9876;
|
|
20
20
|
const vibeletDir = join(homedir(), '.vibelet');
|
|
21
21
|
const pairingQrPngPath = join(vibeletDir, 'pairing-qr.png');
|
|
22
22
|
const logDir = join(vibeletDir, 'logs');
|
|
@@ -35,6 +35,22 @@ const UPDATE_CHECK_INTERVAL_MS = 4 * 60 * 60 * 1000; // 4 hours
|
|
|
35
35
|
|
|
36
36
|
let officialSitePrinted = false;
|
|
37
37
|
let updateMessage = '';
|
|
38
|
+
let port = normalizePortValue(process.env.VIBE_PORT) ?? 9876;
|
|
39
|
+
|
|
40
|
+
function normalizePortValue(rawValue) {
|
|
41
|
+
if (typeof rawValue !== 'string' && typeof rawValue !== 'number') {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
const normalized = String(rawValue).trim();
|
|
45
|
+
if (!/^\d+$/.test(normalized)) {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
const parsed = Number(normalized);
|
|
49
|
+
if (!Number.isInteger(parsed) || parsed <= 0 || parsed > 65535) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
return parsed;
|
|
53
|
+
}
|
|
38
54
|
|
|
39
55
|
function printOfficialSite() {
|
|
40
56
|
if (officialSitePrinted) return;
|
|
@@ -147,7 +163,7 @@ function readRuntimeMetadata() {
|
|
|
147
163
|
|
|
148
164
|
function ensureRuntimeInstalled() {
|
|
149
165
|
if (!existsSync(daemonEntryPath)) {
|
|
150
|
-
fail('The compiled daemon runtime is missing.', 'Run `pnpm build` before invoking `npx @vibelet/cli` from a source checkout.');
|
|
166
|
+
fail('The compiled daemon runtime is missing.', 'Run `pnpm build:release` before invoking `npx @vibelet/cli` from a source checkout.');
|
|
151
167
|
}
|
|
152
168
|
|
|
153
169
|
const sourceDaemonStat = statSync(daemonEntryPath);
|
|
@@ -419,8 +435,18 @@ function createLinuxBackend() {
|
|
|
419
435
|
return spawnSync('systemctl', ['--user', ...args], { encoding: 'utf8' });
|
|
420
436
|
}
|
|
421
437
|
|
|
422
|
-
function
|
|
423
|
-
return
|
|
438
|
+
function resultOutput(result) {
|
|
439
|
+
return [result?.stderr, result?.stdout].filter(Boolean).join('\n').trim();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
let useSystemd = canUseSystemdUserManager();
|
|
443
|
+
|
|
444
|
+
function demoteToDetachedIfSystemdUnavailable(result) {
|
|
445
|
+
if (!isSystemdUserManagerUnavailable(resultOutput(result))) {
|
|
446
|
+
return false;
|
|
447
|
+
}
|
|
448
|
+
useSystemd = false;
|
|
449
|
+
return true;
|
|
424
450
|
}
|
|
425
451
|
|
|
426
452
|
function unitContents() {
|
|
@@ -448,18 +474,26 @@ WantedBy=default.target
|
|
|
448
474
|
// Fallback: detached process with PID file (no systemd)
|
|
449
475
|
const fallback = createDetachedBackend();
|
|
450
476
|
|
|
451
|
-
const useSystemd = hasSystemd();
|
|
452
477
|
return {
|
|
453
|
-
name
|
|
454
|
-
|
|
478
|
+
get name() {
|
|
479
|
+
return useSystemd ? 'systemd' : 'detached';
|
|
480
|
+
},
|
|
481
|
+
|
|
482
|
+
get handlesProcessLifecycle() {
|
|
483
|
+
return useSystemd;
|
|
484
|
+
},
|
|
455
485
|
|
|
456
486
|
isServiceInstalled() {
|
|
457
|
-
if (!
|
|
458
|
-
|
|
487
|
+
if (!useSystemd) return fallback.isServiceInstalled();
|
|
488
|
+
const result = systemctl(['is-enabled', unitName]);
|
|
489
|
+
if (result.status !== 0 && demoteToDetachedIfSystemdUnavailable(result)) {
|
|
490
|
+
return fallback.isServiceInstalled();
|
|
491
|
+
}
|
|
492
|
+
return result.status === 0;
|
|
459
493
|
},
|
|
460
494
|
|
|
461
495
|
install() {
|
|
462
|
-
if (!
|
|
496
|
+
if (!useSystemd) {
|
|
463
497
|
fallback.install();
|
|
464
498
|
return;
|
|
465
499
|
}
|
|
@@ -469,33 +503,58 @@ WantedBy=default.target
|
|
|
469
503
|
const currentContents = existsSync(unitPath) ? readFileSync(unitPath, 'utf8') : null;
|
|
470
504
|
if (currentContents !== nextContents) {
|
|
471
505
|
writeFileSync(unitPath, nextContents, 'utf8');
|
|
472
|
-
systemctl(['daemon-reload']);
|
|
506
|
+
const reloadResult = systemctl(['daemon-reload']);
|
|
507
|
+
if (reloadResult.status !== 0) {
|
|
508
|
+
if (demoteToDetachedIfSystemdUnavailable(reloadResult)) {
|
|
509
|
+
fallback.install();
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
fail('Failed to reload vibelet systemd user units.', resultOutput(reloadResult));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
const enableResult = systemctl(['enable', unitName]);
|
|
516
|
+
if (enableResult.status !== 0) {
|
|
517
|
+
if (demoteToDetachedIfSystemdUnavailable(enableResult)) {
|
|
518
|
+
fallback.install();
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
fail('Failed to enable vibelet daemon.', resultOutput(enableResult));
|
|
473
522
|
}
|
|
474
|
-
systemctl(['enable', unitName]);
|
|
475
523
|
},
|
|
476
524
|
|
|
477
525
|
start() {
|
|
478
|
-
if (!
|
|
526
|
+
if (!useSystemd) {
|
|
479
527
|
fallback.start();
|
|
480
528
|
return;
|
|
481
529
|
}
|
|
482
530
|
const result = systemctl(['start', unitName]);
|
|
483
531
|
if (result.status !== 0) {
|
|
532
|
+
if (demoteToDetachedIfSystemdUnavailable(result)) {
|
|
533
|
+
fallback.install();
|
|
534
|
+
fallback.start();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
484
537
|
fail('Failed to start vibelet daemon.', result.stderr || result.stdout);
|
|
485
538
|
}
|
|
486
539
|
},
|
|
487
540
|
|
|
488
541
|
stop() {
|
|
489
|
-
if (!
|
|
542
|
+
if (!useSystemd) {
|
|
490
543
|
fallback.stop();
|
|
491
544
|
return;
|
|
492
545
|
}
|
|
493
|
-
systemctl(['stop', unitName]);
|
|
546
|
+
const result = systemctl(['stop', unitName]);
|
|
547
|
+
if (result.status !== 0 && demoteToDetachedIfSystemdUnavailable(result)) {
|
|
548
|
+
fallback.stop();
|
|
549
|
+
}
|
|
494
550
|
},
|
|
495
551
|
|
|
496
552
|
statusLabel() {
|
|
497
|
-
if (!
|
|
553
|
+
if (!useSystemd) return fallback.statusLabel();
|
|
498
554
|
const result = systemctl(['is-active', unitName]);
|
|
555
|
+
if (result.status !== 0 && demoteToDetachedIfSystemdUnavailable(result)) {
|
|
556
|
+
return fallback.statusLabel();
|
|
557
|
+
}
|
|
499
558
|
return result.stdout?.trim() || 'unknown';
|
|
500
559
|
},
|
|
501
560
|
};
|
|
@@ -703,6 +762,7 @@ function createCompactPairingPayload(pairingPayload) {
|
|
|
703
762
|
target.port,
|
|
704
763
|
target.source,
|
|
705
764
|
target.stability,
|
|
765
|
+
target.secure === true,
|
|
706
766
|
]);
|
|
707
767
|
}
|
|
708
768
|
return compactPayload;
|
|
@@ -799,6 +859,7 @@ function normalizePairingConnections(pairingPayload) {
|
|
|
799
859
|
kind: target.kind === 'relay' ? 'relay' : 'direct',
|
|
800
860
|
host: normalizeHostValue(target.host),
|
|
801
861
|
port: Math.floor(Number(target.port)),
|
|
862
|
+
...(target.secure === true ? { secure: true } : {}),
|
|
802
863
|
source: target.source,
|
|
803
864
|
stability: target.stability,
|
|
804
865
|
}))
|
|
@@ -808,7 +869,7 @@ function normalizePairingConnections(pairingPayload) {
|
|
|
808
869
|
const dedupe = (targets) => {
|
|
809
870
|
const seen = new Set();
|
|
810
871
|
return targets.filter((target) => {
|
|
811
|
-
const key = `${target.host}:${target.port}`;
|
|
872
|
+
const key = `${target.host}:${target.port}:${target.secure === true ? 'secure' : 'plain'}`;
|
|
812
873
|
if (seen.has(key)) {
|
|
813
874
|
return false;
|
|
814
875
|
}
|
|
@@ -916,6 +977,7 @@ function printHelp() {
|
|
|
916
977
|
process.stdout.write(` npx ${packageJson.name} --force Force a new Cloudflare Tunnel URL\n`);
|
|
917
978
|
process.stdout.write(` npx ${packageJson.name} --relay <url> Use a custom tunnel URL for remote access\n`);
|
|
918
979
|
process.stdout.write(` npx ${packageJson.name} --host <ip> Set the primary host/IP address\n`);
|
|
980
|
+
process.stdout.write(` npx ${packageJson.name} --port <port> Start or query the daemon on a custom port\n`);
|
|
919
981
|
process.stdout.write(` npx ${packageJson.name} --fallback-hosts <ips> Comma-separated fallback IPs\n`);
|
|
920
982
|
process.stdout.write(` npx ${packageJson.name} stop Stop the daemon\n`);
|
|
921
983
|
process.stdout.write(` npx ${packageJson.name} restart Restart the daemon\n`);
|
|
@@ -978,6 +1040,16 @@ function parseRelayArg() {
|
|
|
978
1040
|
return parseNamedArg('relay', 'https://abc.trycloudflare.com');
|
|
979
1041
|
}
|
|
980
1042
|
|
|
1043
|
+
function parsePortArg() {
|
|
1044
|
+
const rawValue = parseNamedArg('port', '9876');
|
|
1045
|
+
if (rawValue === null) return null;
|
|
1046
|
+
const parsed = normalizePortValue(rawValue);
|
|
1047
|
+
if (parsed === null) {
|
|
1048
|
+
fail('--port must be an integer between 1 and 65535');
|
|
1049
|
+
}
|
|
1050
|
+
return parsed;
|
|
1051
|
+
}
|
|
1052
|
+
|
|
981
1053
|
function loadRelayConfig() {
|
|
982
1054
|
try {
|
|
983
1055
|
const data = JSON.parse(readFileSync(relayConfigPath, 'utf8'));
|
|
@@ -1125,6 +1197,12 @@ async function main() {
|
|
|
1125
1197
|
checkForUpdateFromCache();
|
|
1126
1198
|
fetchLatestVersionInBackground();
|
|
1127
1199
|
|
|
1200
|
+
const portArg = parsePortArg();
|
|
1201
|
+
if (portArg !== null) {
|
|
1202
|
+
port = portArg;
|
|
1203
|
+
process.env.VIBE_PORT = String(portArg);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1128
1206
|
consumeFlag('remote') || consumeFlag('tunnel') || readNpmConfigFlag('remote') || readNpmConfigFlag('tunnel');
|
|
1129
1207
|
const localFlag = consumeFlag('local') || readNpmConfigFlag('local');
|
|
1130
1208
|
const forceFlag = consumeFlag('force') || readNpmConfigFlag('force');
|
|
@@ -1275,7 +1353,8 @@ async function main() {
|
|
|
1275
1353
|
fallbackHosts: fallbackHostsArg || '',
|
|
1276
1354
|
localMode: localFlag,
|
|
1277
1355
|
}) && (localFlag || Boolean(relayUrl) || Boolean(hostArg) || Boolean(fallbackHostsArg));
|
|
1278
|
-
|
|
1356
|
+
const shouldReplaceDetachedDaemon = !healthyDaemon && !backend.handlesProcessLifecycle && backend.isServiceInstalled();
|
|
1357
|
+
if (shouldReplaceDetachedDaemon || (healthyDaemon && hasExplicitConfigOverrides)) {
|
|
1279
1358
|
await stopRunningDaemon(backend);
|
|
1280
1359
|
}
|
|
1281
1360
|
ensureRuntimeInstalled();
|
|
@@ -1314,7 +1393,8 @@ async function main() {
|
|
|
1314
1393
|
return;
|
|
1315
1394
|
}
|
|
1316
1395
|
|
|
1317
|
-
|
|
1396
|
+
const shouldReplaceDetachedDaemon = !healthyDaemon && !backend.handlesProcessLifecycle && backend.isServiceInstalled();
|
|
1397
|
+
if (shouldReplaceDetachedDaemon || (healthyDaemon && hasExplicitConfigOverrides)) {
|
|
1318
1398
|
await stopRunningDaemon(backend);
|
|
1319
1399
|
}
|
|
1320
1400
|
|