@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 +3 -1
- package/README.setup.md +3 -0
- package/dist/hdo.js +170 -8
- 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
|
|
package/README.setup.md
CHANGED
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
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
|
|
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
|
}
|