@qpjoy/tunnel-cli 0.1.5 → 0.1.6

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/README.md CHANGED
@@ -45,7 +45,9 @@ The command:
45
45
  - downloads the HDO manifest from `electron-server`
46
46
  - writes a local WireGuard config
47
47
  - stores local HDO state and refresh credentials
48
- - starts a system-level tunnel at boot
48
+ - starts a system-level tunnel at boot. On Linux, if `wg-quick@.service` is not
49
+ installed by the OS, the CLI writes a compatible systemd unit that uses the
50
+ bundled WireGuard tools from the npm package.
49
51
 
50
52
  Useful follow-up commands:
51
53
 
package/README.setup.md CHANGED
@@ -13,4 +13,7 @@ sudo qp-tunnel-cli server-on
13
13
  sudo qp-tunnel-cli status
14
14
 
15
15
  qp-tunnel-cli curl google.com
16
+
17
+ # 删除mac的HDO进程
18
+ qp-tunnel-cli hdo down --interface hdo-client
16
19
  ```
package/dist/hdo.js CHANGED
@@ -78,6 +78,8 @@ Examples:
78
78
 
79
79
  Notes:
80
80
  Linux writes /etc/wireguard and enables wg-quick@<interface>.
81
+ If Linux does not provide wg-quick@.service, this CLI installs a compatible
82
+ systemd unit that uses the bundled WireGuard tools from npm.
81
83
  macOS installs a LaunchDaemon and may prompt for an administrator password.
82
84
  Windows installs a WireGuard tunnel service and may show a UAC prompt.
83
85
 
@@ -182,21 +184,26 @@ function statusCommand(input) {
182
184
  const interfaceName = sanitizeInterfaceName(input.interfaceName || state.interfaceName || defaultInterfaceName);
183
185
  const configPath = resolveConfigPath(input.configPath || state.configPath, interfaceName);
184
186
  const installDir = resolveInstallDir(input.installDir || state.installDir);
187
+ const runtime = (0, electron_core_wireguard_1.resolveWireGuardConnectionRuntime)({
188
+ installDir,
189
+ allowSystemFallback: true,
190
+ });
185
191
  process.stdout.write(`State file: ${stateFile}\n`);
186
192
  process.stdout.write(`Server URL: ${state.serverUrl || 'unset'}\n`);
187
193
  process.stdout.write(`Device: ${state.deviceId || 'unset'}\n`);
188
194
  process.stdout.write(`Overlay IP: ${state.overlayIp || 'unset'}\n`);
189
195
  process.stdout.write(`WireGuard config: ${configPath}\n\n`);
190
196
  if (process.platform === 'linux') {
191
- inherit('systemctl', ['status', `wg-quick@${interfaceName}`, '--no-pager']);
192
- process.stdout.write('\n');
193
- inherit('wg', ['show', interfaceName]);
197
+ if (commandAvailable('systemctl')) {
198
+ inherit('systemctl', ['status', `wg-quick@${interfaceName}`, '--no-pager']);
199
+ process.stdout.write('\n');
200
+ }
201
+ else {
202
+ process.stdout.write('systemctl unavailable; showing WireGuard runtime status only.\n\n');
203
+ }
204
+ printWireGuardRuntimeStatus(runtime, configPath);
194
205
  return;
195
206
  }
196
- const runtime = (0, electron_core_wireguard_1.resolveWireGuardConnectionRuntime)({
197
- installDir,
198
- allowSystemFallback: true,
199
- });
200
207
  if (process.platform === 'darwin') {
201
208
  const daemon = (0, electron_core_wireguard_1.getDarwinWireGuardLaunchDaemonStatus)({ runtime, configPath });
202
209
  process.stdout.write(`LaunchDaemon: ${daemon.loaded ? 'loaded' : 'not loaded'}; running=${daemon.running}\n`);
@@ -225,7 +232,18 @@ async function downCommand(input) {
225
232
  const configPath = resolveConfigPath(input.configPath || state.configPath, interfaceName);
226
233
  const installDir = resolveInstallDir(input.installDir || state.installDir);
227
234
  if (process.platform === 'linux') {
228
- inheritRequired('systemctl', ['disable', '--now', `wg-quick@${interfaceName}`]);
235
+ const runtime = (0, electron_core_wireguard_1.resolveWireGuardConnectionRuntime)({
236
+ installDir,
237
+ allowSystemFallback: true,
238
+ });
239
+ if (commandAvailable('systemctl') && systemdUnitExists('wg-quick@.service')) {
240
+ inheritRequired('systemctl', ['disable', '--now', `wg-quick@${interfaceName}`]);
241
+ return;
242
+ }
243
+ const result = await (0, electron_core_wireguard_1.setWireGuardTunnelState)({ runtime, configPath, action: 'down' });
244
+ if (!result.ok)
245
+ throw new Error(result.message);
246
+ process.stdout.write(`${result.message}\n`);
229
247
  return;
230
248
  }
231
249
  const runtime = (0, electron_core_wireguard_1.resolveWireGuardConnectionRuntime)({
@@ -451,6 +469,15 @@ function resolveKeypair(options, previous, installDir) {
451
469
  }
452
470
  async function startSystemTunnel(interfaceName, configPath, installDir) {
453
471
  if (process.platform === 'linux') {
472
+ const runtime = await ensureLinuxWireGuardRuntime(installDir);
473
+ if (!commandAvailable('systemctl')) {
474
+ const result = await (0, electron_core_wireguard_1.setWireGuardTunnelState)({ runtime, configPath, action: 'restart' });
475
+ if (!result.ok)
476
+ throw new Error(result.message);
477
+ return `${result.message} systemctl is unavailable, so this tunnel is not installed as a boot service.`;
478
+ }
479
+ ensureLinuxWgQuickSystemdUnit(runtime);
480
+ inheritRequired('systemctl', ['daemon-reload']);
454
481
  inheritRequired('systemctl', ['enable', `wg-quick@${interfaceName}`]);
455
482
  inheritRequired('systemctl', ['restart', `wg-quick@${interfaceName}`]);
456
483
  return `Enabled and restarted wg-quick@${interfaceName}.`;
@@ -596,6 +623,138 @@ function inheritRequired(command, args) {
596
623
  const result = (0, node_child_process_1.spawnSync)(command, args, { stdio: 'inherit' });
597
624
  assertSpawnOk(command, args, result);
598
625
  }
626
+ function printWireGuardRuntimeStatus(runtime, configPath) {
627
+ process.stdout.write(`Runtime: ${runtime.method}\n`);
628
+ if (runtime.warnings.length) {
629
+ process.stdout.write(`Runtime warnings: ${runtime.warnings.join('; ')}\n`);
630
+ }
631
+ try {
632
+ const tunnel = (0, electron_core_wireguard_1.getWireGuardTunnelStatus)({ runtime, configPath });
633
+ process.stdout.write(`WireGuard active: ${tunnel.active}\n`);
634
+ if (tunnel.realInterfaceName)
635
+ process.stdout.write(`Interface: ${tunnel.realInterfaceName}\n`);
636
+ if (tunnel.peers.length)
637
+ process.stdout.write(`Peers: ${tunnel.peers.length}\n`);
638
+ if (tunnel.error)
639
+ process.stdout.write(`Status detail: ${tunnel.error}\n`);
640
+ }
641
+ catch (err) {
642
+ process.stdout.write(`WireGuard status detail: ${errorMessage(err)}\n`);
643
+ }
644
+ }
645
+ async function ensureLinuxWireGuardRuntime(installDir) {
646
+ let runtime = (0, electron_core_wireguard_1.resolveWireGuardConnectionRuntime)({
647
+ installDir,
648
+ allowSystemFallback: true,
649
+ });
650
+ if (runtime.available)
651
+ return runtime;
652
+ const installed = installLinuxWireGuardTools();
653
+ runtime = (0, electron_core_wireguard_1.resolveWireGuardConnectionRuntime)({
654
+ installDir,
655
+ allowSystemFallback: true,
656
+ });
657
+ if (runtime.available)
658
+ return runtime;
659
+ throw new Error(installed
660
+ ? runtime.error ?? 'WireGuard runtime unavailable after installing wireguard-tools.'
661
+ : `${runtime.error ?? 'WireGuard runtime unavailable'}. Install wireguard-tools or use an npm package with the matching @qpjoy/electron-core-wireguard-engine package.`);
662
+ }
663
+ function installLinuxWireGuardTools() {
664
+ const installers = [
665
+ { probe: 'apt-get', label: 'apt-get', commands: [['apt-get', 'update'], ['apt-get', 'install', '-y', 'wireguard-tools']] },
666
+ { probe: 'dnf', label: 'dnf', commands: [['dnf', 'install', '-y', 'wireguard-tools']] },
667
+ { probe: 'yum', label: 'yum', commands: [['yum', 'install', '-y', 'epel-release'], ['yum', 'install', '-y', 'wireguard-tools']] },
668
+ { probe: 'apk', label: 'apk', commands: [['apk', 'add', '--no-cache', 'wireguard-tools']] },
669
+ { probe: 'zypper', label: 'zypper', commands: [['zypper', '--non-interactive', 'install', 'wireguard-tools']] },
670
+ { probe: 'pacman', label: 'pacman', commands: [['pacman', '-Sy', '--noconfirm', 'wireguard-tools']] },
671
+ ];
672
+ for (const installer of installers) {
673
+ if (!commandAvailable(installer.probe))
674
+ continue;
675
+ process.stdout.write(`WireGuard tools are missing; installing wireguard-tools with ${installer.label}.\n`);
676
+ for (const command of installer.commands) {
677
+ const [name, ...args] = command;
678
+ const result = (0, node_child_process_1.spawnSync)(name, args, { stdio: 'inherit' });
679
+ if (result.status !== 0) {
680
+ process.stdout.write(`wireguard-tools install step failed: ${command.join(' ')}\n`);
681
+ return false;
682
+ }
683
+ }
684
+ return true;
685
+ }
686
+ return false;
687
+ }
688
+ function ensureLinuxWgQuickSystemdUnit(runtime) {
689
+ if (systemdUnitUsable())
690
+ return;
691
+ const wgQuick = runtime.wgQuick?.command;
692
+ if (!wgQuick) {
693
+ throw new Error(runtime.error ?? 'wg-quick unavailable; cannot install systemd boot service.');
694
+ }
695
+ const unitPath = '/etc/systemd/system/wg-quick@.service';
696
+ if ((0, node_fs_1.existsSync)(unitPath)) {
697
+ throw new Error(`${unitPath} exists but wg/wg-quick is not available in PATH. Install wireguard-tools or fix the existing unit.`);
698
+ }
699
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(unitPath), { recursive: true });
700
+ (0, node_fs_1.writeFileSync)(unitPath, renderLinuxWgQuickSystemdUnit(runtime, wgQuick), { mode: 0o644 });
701
+ chmodSyncSafe(unitPath, 0o644);
702
+ process.stdout.write(`Installed ${unitPath} using bundled WireGuard tools.\n`);
703
+ }
704
+ function renderLinuxWgQuickSystemdUnit(runtime, wgQuick) {
705
+ const pathDirs = uniqueStrings([
706
+ runtime.wg.command ? (0, node_path_1.dirname)(runtime.wg.command) : '',
707
+ runtime.wgQuick?.command ? (0, node_path_1.dirname)(runtime.wgQuick.command) : '',
708
+ runtime.wireGuardGo?.command ? (0, node_path_1.dirname)(runtime.wireGuardGo.command) : '',
709
+ '/usr/local/sbin',
710
+ '/usr/local/bin',
711
+ '/usr/sbin',
712
+ '/usr/bin',
713
+ '/sbin',
714
+ '/bin',
715
+ ].filter(Boolean));
716
+ return `[Unit]
717
+ Description=WireGuard via wg-quick(8) for %I
718
+ Documentation=man:wg-quick(8) man:wg(8)
719
+ Wants=network-online.target
720
+ After=network-online.target nss-lookup.target
721
+ ConditionPathExists=/etc/wireguard/%i.conf
722
+
723
+ [Service]
724
+ Type=oneshot
725
+ RemainAfterExit=yes
726
+ Environment=${systemdQuote(`PATH=${pathDirs.join(':')}`)}
727
+ Environment=WG_ENDPOINT_RESOLUTION_RETRIES=infinity
728
+ ExecStart=${systemdQuote(wgQuick)} up %i
729
+ ExecStop=${systemdQuote(wgQuick)} down %i
730
+
731
+ [Install]
732
+ WantedBy=multi-user.target
733
+ `;
734
+ }
735
+ function systemdUnitUsable() {
736
+ return systemdUnitExists('wg-quick@.service') && commandAvailable('wg') && commandAvailable('wg-quick');
737
+ }
738
+ function systemdUnitExists(unitName) {
739
+ const paths = [
740
+ `/etc/systemd/system/${unitName}`,
741
+ `/run/systemd/system/${unitName}`,
742
+ `/lib/systemd/system/${unitName}`,
743
+ `/usr/lib/systemd/system/${unitName}`,
744
+ ];
745
+ if (paths.some((path) => (0, node_fs_1.existsSync)(path)))
746
+ return true;
747
+ if (!commandAvailable('systemctl'))
748
+ return false;
749
+ const result = (0, node_child_process_1.spawnSync)('systemctl', ['cat', unitName], { stdio: 'ignore' });
750
+ return result.status === 0;
751
+ }
752
+ function commandAvailable(command) {
753
+ const result = (0, node_child_process_1.spawnSync)('sh', ['-c', 'command -v "$1" >/dev/null 2>&1', 'sh', command], {
754
+ stdio: 'ignore',
755
+ });
756
+ return result.status === 0;
757
+ }
599
758
  function uninstallDarwinLaunchDaemonByInterface(interfaceName) {
600
759
  const component = sanitizeLaunchDaemonComponent(interfaceName);
601
760
  const label = `com.qpjoy.hdo.wireguard.${component}`;
@@ -676,6 +835,9 @@ function sanitizeLaunchDaemonComponent(value) {
676
835
  function shellQuote(value) {
677
836
  return `'${String(value).replace(/'/g, `'\\''`)}'`;
678
837
  }
838
+ function systemdQuote(value) {
839
+ return `"${String(value).replace(/%/g, '%%').replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
840
+ }
679
841
  function appleScriptString(value) {
680
842
  return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
681
843
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qpjoy/tunnel-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Global QPJoy Tunnel CLI for mihomo-client and cross-platform HDO mesh enrollment.",
5
5
  "private": false,
6
6
  "type": "commonjs",