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 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 hasSystemd() {
423
- return spawnSync('systemctl', ['--user', '--version'], { encoding: 'utf8' }).status === 0;
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: useSystemd ? 'systemd' : 'detached',
454
- handlesProcessLifecycle: useSystemd,
478
+ get name() {
479
+ return useSystemd ? 'systemd' : 'detached';
480
+ },
481
+
482
+ get handlesProcessLifecycle() {
483
+ return useSystemd;
484
+ },
455
485
 
456
486
  isServiceInstalled() {
457
- if (!hasSystemd()) return fallback.isServiceInstalled();
458
- return systemctl(['is-enabled', unitName]).status === 0;
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 (!hasSystemd()) {
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 (!hasSystemd()) {
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 (!hasSystemd()) {
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 (!hasSystemd()) return fallback.statusLabel();
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
- if (healthyDaemon && hasExplicitConfigOverrides) {
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
- if (healthyDaemon && hasExplicitConfigOverrides) {
1396
+ const shouldReplaceDetachedDaemon = !healthyDaemon && !backend.handlesProcessLifecycle && backend.isServiceInstalled();
1397
+ if (shouldReplaceDetachedDaemon || (healthyDaemon && hasExplicitConfigOverrides)) {
1318
1398
  await stopRunningDaemon(backend);
1319
1399
  }
1320
1400