@qpjoy/tunnel-cli 0.1.4 → 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 +10 -1
- package/README.setup.md +3 -0
- package/dist/hdo.js +287 -42
- package/package.json +1 -1
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
|
|
|
@@ -55,6 +57,13 @@ qp-tunnel-cli hdo refresh
|
|
|
55
57
|
qp-tunnel-cli hdo down
|
|
56
58
|
```
|
|
57
59
|
|
|
60
|
+
If a tunnel was created by the Electron HDO plugin, its default interface is
|
|
61
|
+
`hdo-client`, so stop it with:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
qp-tunnel-cli hdo down --interface hdo-client
|
|
65
|
+
```
|
|
66
|
+
|
|
58
67
|
Platform behavior:
|
|
59
68
|
|
|
60
69
|
- Linux: writes `/etc/wireguard/hdo-internal.conf` and enables `wg-quick@hdo-internal`
|
package/README.setup.md
CHANGED
package/dist/hdo.js
CHANGED
|
@@ -23,10 +23,10 @@ async function runHdoCli(args, ctx) {
|
|
|
23
23
|
await enrollCommand(rest, command === 'refresh');
|
|
24
24
|
return;
|
|
25
25
|
case 'status':
|
|
26
|
-
statusCommand(
|
|
26
|
+
statusCommand(parseCommandOptions(rest));
|
|
27
27
|
return;
|
|
28
28
|
case 'down':
|
|
29
|
-
await downCommand(
|
|
29
|
+
await downCommand(parseCommandOptions(rest));
|
|
30
30
|
return;
|
|
31
31
|
default:
|
|
32
32
|
process.stderr.write(`Unknown hdo command: ${command}\n\n`);
|
|
@@ -41,8 +41,8 @@ Usage:
|
|
|
41
41
|
qp-tunnel-cli hdo enroll --server-url URL --username USER [options]
|
|
42
42
|
qp-tunnel-cli hdo enroll --server-url URL --token TOKEN [options]
|
|
43
43
|
qp-tunnel-cli hdo refresh [--server-url URL] [--username USER]
|
|
44
|
-
qp-tunnel-cli hdo status
|
|
45
|
-
qp-tunnel-cli hdo down
|
|
44
|
+
qp-tunnel-cli hdo status [--interface NAME]
|
|
45
|
+
qp-tunnel-cli hdo down [--interface NAME]
|
|
46
46
|
|
|
47
47
|
Enroll options:
|
|
48
48
|
--server-url URL HDO/electron-server base URL
|
|
@@ -78,8 +78,14 @@ 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.
|
|
85
|
+
|
|
86
|
+
Tip:
|
|
87
|
+
If the Electron HDO plugin created the tunnel, stop it with:
|
|
88
|
+
qp-tunnel-cli hdo down --interface hdo-client
|
|
83
89
|
`);
|
|
84
90
|
}
|
|
85
91
|
async function enrollCommand(args, refreshOnly) {
|
|
@@ -172,51 +178,72 @@ async function enrollCommand(args, refreshOnly) {
|
|
|
172
178
|
'',
|
|
173
179
|
].join('\n'));
|
|
174
180
|
}
|
|
175
|
-
function statusCommand(
|
|
176
|
-
const stateFile = resolveStateFile(
|
|
181
|
+
function statusCommand(input) {
|
|
182
|
+
const stateFile = resolveStateFile(input.stateFile);
|
|
177
183
|
const state = readState(stateFile);
|
|
178
|
-
const interfaceName = sanitizeInterfaceName(state.interfaceName || defaultInterfaceName);
|
|
179
|
-
const configPath = resolveConfigPath(state.configPath, interfaceName);
|
|
180
|
-
const installDir = resolveInstallDir(state.installDir);
|
|
184
|
+
const interfaceName = sanitizeInterfaceName(input.interfaceName || state.interfaceName || defaultInterfaceName);
|
|
185
|
+
const configPath = resolveConfigPath(input.configPath || state.configPath, interfaceName);
|
|
186
|
+
const installDir = resolveInstallDir(input.installDir || state.installDir);
|
|
187
|
+
const runtime = (0, electron_core_wireguard_1.resolveWireGuardConnectionRuntime)({
|
|
188
|
+
installDir,
|
|
189
|
+
allowSystemFallback: true,
|
|
190
|
+
});
|
|
181
191
|
process.stdout.write(`State file: ${stateFile}\n`);
|
|
182
192
|
process.stdout.write(`Server URL: ${state.serverUrl || 'unset'}\n`);
|
|
183
193
|
process.stdout.write(`Device: ${state.deviceId || 'unset'}\n`);
|
|
184
194
|
process.stdout.write(`Overlay IP: ${state.overlayIp || 'unset'}\n`);
|
|
185
195
|
process.stdout.write(`WireGuard config: ${configPath}\n\n`);
|
|
186
196
|
if (process.platform === 'linux') {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
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);
|
|
190
205
|
return;
|
|
191
206
|
}
|
|
192
|
-
const runtime = (0, electron_core_wireguard_1.resolveWireGuardConnectionRuntime)({
|
|
193
|
-
installDir,
|
|
194
|
-
allowSystemFallback: true,
|
|
195
|
-
});
|
|
196
207
|
if (process.platform === 'darwin') {
|
|
197
208
|
const daemon = (0, electron_core_wireguard_1.getDarwinWireGuardLaunchDaemonStatus)({ runtime, configPath });
|
|
198
209
|
process.stdout.write(`LaunchDaemon: ${daemon.loaded ? 'loaded' : 'not loaded'}; running=${daemon.running}\n`);
|
|
199
210
|
if (daemon.plistPath)
|
|
200
211
|
process.stdout.write(`plist: ${daemon.plistPath}\n`);
|
|
201
212
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
|
|
213
|
+
try {
|
|
214
|
+
const tunnel = (0, electron_core_wireguard_1.getWireGuardTunnelStatus)({ runtime, configPath });
|
|
215
|
+
process.stdout.write(`WireGuard active: ${tunnel.active}\n`);
|
|
216
|
+
process.stdout.write(`Runtime: ${runtime.method}\n`);
|
|
217
|
+
if (tunnel.realInterfaceName)
|
|
218
|
+
process.stdout.write(`Interface: ${tunnel.realInterfaceName}\n`);
|
|
219
|
+
if (tunnel.peers.length)
|
|
220
|
+
process.stdout.write(`Peers: ${tunnel.peers.length}\n`);
|
|
221
|
+
if (tunnel.error)
|
|
222
|
+
process.stdout.write(`Status detail: ${tunnel.error}\n`);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
process.stdout.write(`WireGuard status detail: ${errorMessage(err)}\n`);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
async function downCommand(input) {
|
|
229
|
+
const stateFile = resolveStateFile(input.stateFile);
|
|
214
230
|
const state = readState(stateFile);
|
|
215
|
-
const interfaceName = sanitizeInterfaceName(state.interfaceName || defaultInterfaceName);
|
|
216
|
-
const configPath = resolveConfigPath(state.configPath, interfaceName);
|
|
217
|
-
const installDir = resolveInstallDir(state.installDir);
|
|
231
|
+
const interfaceName = sanitizeInterfaceName(input.interfaceName || state.interfaceName || defaultInterfaceName);
|
|
232
|
+
const configPath = resolveConfigPath(input.configPath || state.configPath, interfaceName);
|
|
233
|
+
const installDir = resolveInstallDir(input.installDir || state.installDir);
|
|
218
234
|
if (process.platform === 'linux') {
|
|
219
|
-
|
|
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`);
|
|
220
247
|
return;
|
|
221
248
|
}
|
|
222
249
|
const runtime = (0, electron_core_wireguard_1.resolveWireGuardConnectionRuntime)({
|
|
@@ -224,6 +251,10 @@ async function downCommand(stateFileInput) {
|
|
|
224
251
|
allowSystemFallback: true,
|
|
225
252
|
});
|
|
226
253
|
if (process.platform === 'darwin') {
|
|
254
|
+
if (!canReadFile(configPath)) {
|
|
255
|
+
uninstallDarwinLaunchDaemonByInterface(interfaceName);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
227
258
|
const result = await (0, electron_core_wireguard_1.uninstallDarwinWireGuardLaunchDaemon)({ runtime, configPath });
|
|
228
259
|
if (!result.ok)
|
|
229
260
|
throw new Error(result.message);
|
|
@@ -304,22 +335,35 @@ function parseEnrollOptions(args) {
|
|
|
304
335
|
}
|
|
305
336
|
return options;
|
|
306
337
|
}
|
|
307
|
-
function
|
|
308
|
-
|
|
338
|
+
function parseCommandOptions(args) {
|
|
339
|
+
const options = {};
|
|
309
340
|
for (let index = 0; index < args.length; index += 1) {
|
|
310
341
|
const arg = args[index];
|
|
311
|
-
|
|
342
|
+
const readValue = () => {
|
|
312
343
|
const value = args[index + 1];
|
|
313
344
|
if (!value)
|
|
314
|
-
throw new Error(
|
|
315
|
-
stateFile = value;
|
|
345
|
+
throw new Error(`Missing value for ${arg}`);
|
|
316
346
|
index += 1;
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
347
|
+
return value;
|
|
348
|
+
};
|
|
349
|
+
switch (arg) {
|
|
350
|
+
case '--state-file':
|
|
351
|
+
options.stateFile = readValue();
|
|
352
|
+
break;
|
|
353
|
+
case '--interface':
|
|
354
|
+
options.interfaceName = readValue();
|
|
355
|
+
break;
|
|
356
|
+
case '--config-path':
|
|
357
|
+
options.configPath = readValue();
|
|
358
|
+
break;
|
|
359
|
+
case '--install-dir':
|
|
360
|
+
options.installDir = readValue();
|
|
361
|
+
break;
|
|
362
|
+
default:
|
|
363
|
+
throw new Error(`Unknown hdo option: ${arg}`);
|
|
320
364
|
}
|
|
321
365
|
}
|
|
322
|
-
return
|
|
366
|
+
return options;
|
|
323
367
|
}
|
|
324
368
|
async function resolveAuth(serverUrl, options, previous, username) {
|
|
325
369
|
const token = resolveExplicitToken(options);
|
|
@@ -425,6 +469,15 @@ function resolveKeypair(options, previous, installDir) {
|
|
|
425
469
|
}
|
|
426
470
|
async function startSystemTunnel(interfaceName, configPath, installDir) {
|
|
427
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']);
|
|
428
481
|
inheritRequired('systemctl', ['enable', `wg-quick@${interfaceName}`]);
|
|
429
482
|
inheritRequired('systemctl', ['restart', `wg-quick@${interfaceName}`]);
|
|
430
483
|
return `Enabled and restarted wg-quick@${interfaceName}.`;
|
|
@@ -524,7 +577,12 @@ function resolveStateFile(input) {
|
|
|
524
577
|
return (0, node_path_1.resolve)(input || defaultStateFile());
|
|
525
578
|
}
|
|
526
579
|
function resolveConfigPath(input, interfaceName) {
|
|
527
|
-
|
|
580
|
+
if (input)
|
|
581
|
+
return (0, node_path_1.resolve)(input);
|
|
582
|
+
const launchDaemonConfig = darwinLaunchDaemonConfigPath(interfaceName);
|
|
583
|
+
if (launchDaemonConfig && (0, node_fs_1.existsSync)(launchDaemonConfig))
|
|
584
|
+
return launchDaemonConfig;
|
|
585
|
+
return (0, node_path_1.resolve)(defaultConfigPath(interfaceName));
|
|
528
586
|
}
|
|
529
587
|
function resolveInstallDir(input) {
|
|
530
588
|
return (0, node_path_1.resolve)(input || defaultInstallDir());
|
|
@@ -543,6 +601,11 @@ function defaultConfigPath(interfaceName) {
|
|
|
543
601
|
return (0, node_path_1.join)(windowsUserDataDir(), `${interfaceName}.conf`);
|
|
544
602
|
return (0, node_path_1.join)((0, node_os_1.homedir)(), '.qpjoy', 'hdo', `${interfaceName}.conf`);
|
|
545
603
|
}
|
|
604
|
+
function darwinLaunchDaemonConfigPath(interfaceName) {
|
|
605
|
+
if (process.platform !== 'darwin')
|
|
606
|
+
return null;
|
|
607
|
+
return `/Library/Application Support/QPJoy/HDO/${interfaceName}/${interfaceName}.conf`;
|
|
608
|
+
}
|
|
546
609
|
function defaultInstallDir() {
|
|
547
610
|
if (process.platform === 'linux')
|
|
548
611
|
return '/usr/local/lib/qpjoy/hdo/bin';
|
|
@@ -560,6 +623,164 @@ function inheritRequired(command, args) {
|
|
|
560
623
|
const result = (0, node_child_process_1.spawnSync)(command, args, { stdio: 'inherit' });
|
|
561
624
|
assertSpawnOk(command, args, result);
|
|
562
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
|
+
}
|
|
758
|
+
function uninstallDarwinLaunchDaemonByInterface(interfaceName) {
|
|
759
|
+
const component = sanitizeLaunchDaemonComponent(interfaceName);
|
|
760
|
+
const label = `com.qpjoy.hdo.wireguard.${component}`;
|
|
761
|
+
const plist = `/Library/LaunchDaemons/${label}.plist`;
|
|
762
|
+
const supportDir = `/Library/Application Support/QPJoy/HDO/${component}`;
|
|
763
|
+
const script = [
|
|
764
|
+
'set -e',
|
|
765
|
+
`LABEL=${shellQuote(label)}`,
|
|
766
|
+
`PLIST=${shellQuote(plist)}`,
|
|
767
|
+
`SUPPORT_DIR=${shellQuote(supportDir)}`,
|
|
768
|
+
`PID_FILE=${shellQuote(`/var/run/wireguard/${interfaceName}.pid`)}`,
|
|
769
|
+
`NAME_FILE=${shellQuote(`/var/run/wireguard/${interfaceName}.name`)}`,
|
|
770
|
+
`WIREGUARD_GO=${shellQuote(`${supportDir}/bin/wireguard-go`)}`,
|
|
771
|
+
'launchctl bootout "system/$LABEL" >/dev/null 2>&1 || launchctl bootout system "$PLIST" >/dev/null 2>&1 || true',
|
|
772
|
+
'if [ -s "$PID_FILE" ]; then WG_PID="$(cat "$PID_FILE" 2>/dev/null || true)"; if [ -n "$WG_PID" ]; then kill "$WG_PID" >/dev/null 2>&1 || true; sleep 0.2; kill -9 "$WG_PID" >/dev/null 2>&1 || true; fi; fi',
|
|
773
|
+
'if command -v pgrep >/dev/null 2>&1; then for stale_pid in $(pgrep -x wireguard-go 2>/dev/null || true); do stale_command="$(ps -p "$stale_pid" -o command= 2>/dev/null || true)"; printf "%s\\n" "$stale_command" | grep -F "$WIREGUARD_GO" >/dev/null 2>&1 && kill "$stale_pid" >/dev/null 2>&1 || true; done; fi',
|
|
774
|
+
'rm -f "$PLIST" "$PID_FILE" "$NAME_FILE"',
|
|
775
|
+
'rm -rf "$SUPPORT_DIR"'
|
|
776
|
+
].join('\n');
|
|
777
|
+
const appleScript = `do shell script ${appleScriptString(script)} with administrator privileges`;
|
|
778
|
+
const result = (0, node_child_process_1.spawnSync)('osascript', ['-e', appleScript], {
|
|
779
|
+
encoding: 'utf8'
|
|
780
|
+
});
|
|
781
|
+
assertSpawnOk('osascript', ['-e', '<uninstall-hdo-launchdaemon>'], result);
|
|
782
|
+
process.stdout.write(`Stopped and removed ${label}.\n`);
|
|
783
|
+
}
|
|
563
784
|
function assertSpawnOk(command, args, result) {
|
|
564
785
|
if (result.error) {
|
|
565
786
|
throw new Error(`${command} failed: ${result.error.message}`);
|
|
@@ -594,11 +815,35 @@ function tokenExpired(value) {
|
|
|
594
815
|
const time = Date.parse(value);
|
|
595
816
|
return Number.isFinite(time) && Date.now() > time - 60_000;
|
|
596
817
|
}
|
|
818
|
+
function canReadFile(path) {
|
|
819
|
+
try {
|
|
820
|
+
(0, node_fs_1.readFileSync)(path, 'utf8');
|
|
821
|
+
return true;
|
|
822
|
+
}
|
|
823
|
+
catch {
|
|
824
|
+
return false;
|
|
825
|
+
}
|
|
826
|
+
}
|
|
597
827
|
function chmodSyncSafe(path, mode) {
|
|
598
828
|
if (process.platform === 'win32')
|
|
599
829
|
return;
|
|
600
830
|
(0, node_fs_1.chmodSync)(path, mode);
|
|
601
831
|
}
|
|
832
|
+
function sanitizeLaunchDaemonComponent(value) {
|
|
833
|
+
return value.replace(/[^A-Za-z0-9.-]/g, '-').replace(/^-+|-+$/g, '').slice(0, 48) || 'hdo-client';
|
|
834
|
+
}
|
|
835
|
+
function shellQuote(value) {
|
|
836
|
+
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
837
|
+
}
|
|
838
|
+
function systemdQuote(value) {
|
|
839
|
+
return `"${String(value).replace(/%/g, '%%').replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
840
|
+
}
|
|
841
|
+
function appleScriptString(value) {
|
|
842
|
+
return `"${value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
843
|
+
}
|
|
844
|
+
function errorMessage(err) {
|
|
845
|
+
return err instanceof Error ? err.message : String(err);
|
|
846
|
+
}
|
|
602
847
|
function requireRecord(value, label) {
|
|
603
848
|
if (!isRecord(value))
|
|
604
849
|
throw new Error(`${label} is not an object.`);
|