@qpjoy/tunnel-cli 0.1.9 → 0.1.10
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 +32 -3
- package/README.setup.md +7 -4
- package/dist/hdo.js +190 -4
- package/dist/index.js +4 -2
- package/package.json +1 -1
- package/resources/mihomo-client.sh +65 -17
package/README.md
CHANGED
|
@@ -23,6 +23,8 @@ server, enroll into the HDO mesh without installing Electron:
|
|
|
23
23
|
npm i -g @qpjoy/tunnel-cli
|
|
24
24
|
HDO_PASSWORD='<password>' qp-tunnel-cli hdo enroll \
|
|
25
25
|
--server-url 'https://domestic.example.com' \
|
|
26
|
+
--internal-url 'http://127.0.0.1:18090' \
|
|
27
|
+
--product-id h2o \
|
|
26
28
|
--username 'internal-i' \
|
|
27
29
|
--device-id internal-i \
|
|
28
30
|
--label 'Internal I'
|
|
@@ -40,6 +42,8 @@ HDO_PASSWORD='<password>' sudo -E qp-tunnel-cli hdo enroll \
|
|
|
40
42
|
The command:
|
|
41
43
|
|
|
42
44
|
- logs in with username/password, or uses `--token` when provided
|
|
45
|
+
- requests an MX Launcher Internal product lease when `--internal-url` is set
|
|
46
|
+
- uses that lease as the WireGuard client address and adds the product route CIDR
|
|
43
47
|
- uses `@qpjoy/electron-core-wireguard` to find/install the platform WireGuard engine
|
|
44
48
|
- registers the machine as an HDO device
|
|
45
49
|
- downloads the HDO manifest from `electron-server`
|
|
@@ -49,6 +53,22 @@ The command:
|
|
|
49
53
|
installed by the OS, the CLI writes a compatible systemd unit that uses the
|
|
50
54
|
bundled WireGuard tools from the npm package.
|
|
51
55
|
|
|
56
|
+
To test the new Internal allocator without applying WireGuard yet, run lease-only
|
|
57
|
+
mode. If `--server-url` is omitted and `--internal-url` is present, the command
|
|
58
|
+
automatically behaves as lease-only:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
qp-tunnel-cli hdo enroll \
|
|
62
|
+
--internal-url 'http://127.0.0.1:18090' \
|
|
63
|
+
--product-id h2o \
|
|
64
|
+
--identity-kind anonymous \
|
|
65
|
+
--lease-only
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
For H2O, Internal assigns logged-in users from `10.90.0.1-10.90.99.254` and
|
|
69
|
+
anonymous users from `10.90.100.1-10.90.254.254`, based on the Product Network
|
|
70
|
+
Registry.
|
|
71
|
+
|
|
52
72
|
Useful follow-up commands:
|
|
53
73
|
|
|
54
74
|
```bash
|
|
@@ -81,16 +101,25 @@ Run commands directly through the bundled script:
|
|
|
81
101
|
|
|
82
102
|
```bash
|
|
83
103
|
qp-tunnel-cli status
|
|
84
|
-
qp-tunnel-cli
|
|
104
|
+
qp-tunnel-cli egress-on
|
|
85
105
|
qp-tunnel-cli update-subscription
|
|
86
106
|
```
|
|
87
107
|
|
|
88
108
|
For server commands, `qp-tunnel-cli` re-runs itself with `sudo` when root is needed.
|
|
89
|
-
Use `
|
|
109
|
+
Use `egress-on` for public VPS hosts: it keeps Mihomo running as a local outbound
|
|
90
110
|
proxy and configures shell, SSH, Docker/containerd/buildkit proxy drop-ins without
|
|
91
111
|
enabling TUN route takeover. Reserve `tun-on` for machines that are not serving
|
|
92
112
|
public inbound traffic.
|
|
93
113
|
|
|
114
|
+
Domestic bootstrap can install from an Internal-pushed subscription file before
|
|
115
|
+
the WG relay can reach Internal:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
sudo qp-tunnel-cli install \
|
|
119
|
+
--file /opt/mx/current/qp-tunnel-cli/domestic-bootstrap-subscription.yaml
|
|
120
|
+
sudo qp-tunnel-cli egress-on
|
|
121
|
+
```
|
|
122
|
+
|
|
94
123
|
Run any command through the active Mihomo local proxy:
|
|
95
124
|
|
|
96
125
|
```bash
|
|
@@ -109,7 +138,7 @@ Install the bundled script as a normal Linux command:
|
|
|
109
138
|
sudo qp-tunnel-cli install-script
|
|
110
139
|
sudo qp-tunnel-cli upgrade-systemd
|
|
111
140
|
sudo mihomo-client status
|
|
112
|
-
sudo mihomo-client
|
|
141
|
+
sudo mihomo-client egress-on
|
|
113
142
|
```
|
|
114
143
|
|
|
115
144
|
Use a custom target when needed:
|
package/README.setup.md
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
```bash
|
|
2
|
-
npm i -g @qpjoy/tunnel-cli@0.1.
|
|
3
|
-
|
|
2
|
+
npm i -g @qpjoy/tunnel-cli@0.1.9
|
|
3
|
+
qp-tunnel-cli hdo enroll --internal-url 'http://127.0.0.1:18090' --product-id h2o --identity-kind anonymous --lease-only
|
|
4
|
+
HDO_PASSWORD='...' qp-tunnel-cli hdo enroll --server-url 'https://domestic.example.com' --internal-url 'http://127.0.0.1:18090' --product-id h2o --username internal-i
|
|
4
5
|
|
|
5
6
|
qp-tunnel-cli install --url 'http://user:pass@host:3434/peer_xxx.mihomo.yaml'
|
|
7
|
+
# Domestic bootstrap can use an Internal-pushed local YAML before WG relay reaches Internal.
|
|
8
|
+
qp-tunnel-cli install --file '/opt/mx/current/qp-tunnel-cli/domestic-bootstrap-subscription.yaml'
|
|
6
9
|
|
|
7
10
|
sudo qp-tunnel-cli install-script
|
|
8
11
|
sudo qp-tunnel-cli upgrade-systemd
|
|
9
|
-
sudo qp-tunnel-cli
|
|
12
|
+
sudo qp-tunnel-cli egress-on
|
|
10
13
|
|
|
11
14
|
sudo qp-tunnel-cli tun-off
|
|
12
|
-
sudo qp-tunnel-cli
|
|
15
|
+
sudo qp-tunnel-cli egress-on
|
|
13
16
|
sudo qp-tunnel-cli status
|
|
14
17
|
|
|
15
18
|
qp-tunnel-cli curl google.com
|
package/dist/hdo.js
CHANGED
|
@@ -46,6 +46,11 @@ Usage:
|
|
|
46
46
|
|
|
47
47
|
Enroll options:
|
|
48
48
|
--server-url URL HDO/electron-server base URL
|
|
49
|
+
--internal-url URL MX Launcher Internal URL for product IP lease allocation
|
|
50
|
+
--product-id ID Product network id. Default: h2o
|
|
51
|
+
--mode MODE Launcher mode: embed or standalone
|
|
52
|
+
--identity-kind KIND user or anonymous. Default: user when --username is set, else anonymous
|
|
53
|
+
--lease-only Only request/store the Internal lease; skip legacy HDO manifest/WireGuard apply
|
|
49
54
|
--username USER Login username/email/phone
|
|
50
55
|
--password PASS Login password. Prefer HDO_PASSWORD or --password-file
|
|
51
56
|
--password-file PATH Read login password from a file
|
|
@@ -69,6 +74,8 @@ Enroll options:
|
|
|
69
74
|
|
|
70
75
|
Environment:
|
|
71
76
|
HDO_SERVER_URL / QPJOY_HDO_SERVER_URL
|
|
77
|
+
HDO_INTERNAL_URL / QPJOY_HDO_INTERNAL_URL / MX_INTERNAL_URL
|
|
78
|
+
HDO_PRODUCT_ID / QPJOY_HDO_PRODUCT_ID
|
|
72
79
|
HDO_USERNAME / QPJOY_HDO_USERNAME
|
|
73
80
|
HDO_PASSWORD / QPJOY_HDO_PASSWORD
|
|
74
81
|
HDO_TOKEN / QPJOY_HDO_TOKEN
|
|
@@ -109,20 +116,87 @@ async function enrollCommand(args, refreshOnly) {
|
|
|
109
116
|
process.env.HDO_SERVER_URL ??
|
|
110
117
|
process.env.QPJOY_HDO_SERVER_URL ??
|
|
111
118
|
previous.serverUrl);
|
|
119
|
+
const internalUrl = normalizeBaseUrl(options.internalUrl ??
|
|
120
|
+
process.env.HDO_INTERNAL_URL ??
|
|
121
|
+
process.env.QPJOY_HDO_INTERNAL_URL ??
|
|
122
|
+
process.env.MX_INTERNAL_URL ??
|
|
123
|
+
previous.internalUrl);
|
|
112
124
|
const username = options.username ??
|
|
113
125
|
process.env.HDO_USERNAME ??
|
|
114
126
|
process.env.QPJOY_HDO_USERNAME ??
|
|
115
127
|
previous.username;
|
|
116
|
-
|
|
117
|
-
|
|
128
|
+
const productId = sanitizeId(options.productId ??
|
|
129
|
+
process.env.HDO_PRODUCT_ID ??
|
|
130
|
+
process.env.QPJOY_HDO_PRODUCT_ID ??
|
|
131
|
+
previous.productId ??
|
|
132
|
+
'h2o');
|
|
133
|
+
const launcherMode = options.mode ?? previous.launcherMode ?? (productId === 'launcher' ? 'standalone' : 'embed');
|
|
134
|
+
const identityKind = options.identityKind ?? previous.identityKind ?? (username ? 'user' : 'anonymous');
|
|
135
|
+
const leaseOnly = Boolean(options.leaseOnly || (internalUrl && !serverUrl));
|
|
136
|
+
if (!serverUrl && !leaseOnly) {
|
|
137
|
+
throw new Error('Missing --server-url/HDO_SERVER_URL or use --internal-url with --lease-only.');
|
|
118
138
|
}
|
|
119
|
-
const auth = await resolveAuth(serverUrl, options, previous, username);
|
|
120
139
|
const deviceId = options.deviceId ||
|
|
121
140
|
previous.deviceId ||
|
|
122
141
|
`hdo-${process.platform}-${sanitizeId((0, node_os_1.hostname)())}`;
|
|
123
142
|
const label = options.label || previous.label || `${process.platform} ${(0, node_os_1.hostname)()}`;
|
|
124
143
|
const keys = resolveKeypair(options, previous, installDir);
|
|
125
144
|
const direct = resolveDirectEndpoint(options, previous);
|
|
145
|
+
const launcherNetworkLease = internalUrl
|
|
146
|
+
? await enrollLauncherNetworkLease(internalUrl, {
|
|
147
|
+
productId,
|
|
148
|
+
mode: launcherMode,
|
|
149
|
+
identityKind,
|
|
150
|
+
installId: deviceId,
|
|
151
|
+
deviceId,
|
|
152
|
+
siteId: previous.launcherNetworkLease?.siteId,
|
|
153
|
+
userId: identityKind === 'user' ? username : undefined,
|
|
154
|
+
publicKey: keys.publicKey,
|
|
155
|
+
deviceLabel: label,
|
|
156
|
+
platform: `${process.platform}-${process.arch}`,
|
|
157
|
+
requestedBy: '@qpjoy/tunnel-cli',
|
|
158
|
+
requestId: `qp-tunnel-cli-hdo-enroll-${Date.now()}`,
|
|
159
|
+
})
|
|
160
|
+
: undefined;
|
|
161
|
+
if (leaseOnly) {
|
|
162
|
+
const now = new Date().toISOString();
|
|
163
|
+
writeState(stateFile, {
|
|
164
|
+
...previous,
|
|
165
|
+
internalUrl,
|
|
166
|
+
productId,
|
|
167
|
+
launcherMode,
|
|
168
|
+
identityKind,
|
|
169
|
+
username,
|
|
170
|
+
deviceId,
|
|
171
|
+
label,
|
|
172
|
+
interfaceName,
|
|
173
|
+
configPath,
|
|
174
|
+
installDir,
|
|
175
|
+
privateKey: keys.privateKey,
|
|
176
|
+
publicKey: keys.publicKey,
|
|
177
|
+
overlayIp: launcherNetworkLease?.leaseIp ?? previous.overlayIp,
|
|
178
|
+
launcherNetworkLease: launcherNetworkLease ?? previous.launcherNetworkLease,
|
|
179
|
+
enrolledAt: previous.enrolledAt || now,
|
|
180
|
+
updatedAt: now,
|
|
181
|
+
});
|
|
182
|
+
process.stdout.write([
|
|
183
|
+
refreshOnly ? 'HDO Internal lease refreshed.' : 'HDO Internal lease enrolled.',
|
|
184
|
+
`Product: ${productId}`,
|
|
185
|
+
`Mode: ${launcherMode}`,
|
|
186
|
+
`Identity: ${identityKind}`,
|
|
187
|
+
`Device: ${deviceId}`,
|
|
188
|
+
`Lease IP: ${launcherNetworkLease?.leaseIp ?? previous.overlayIp ?? 'unassigned'}`,
|
|
189
|
+
`Lease CIDR: ${launcherNetworkLease?.cidr ?? 'unassigned'}`,
|
|
190
|
+
`State file: ${stateFile}`,
|
|
191
|
+
'System tunnel not started (lease-only).',
|
|
192
|
+
'',
|
|
193
|
+
].join('\n'));
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (!serverUrl) {
|
|
197
|
+
throw new Error('Missing --server-url or HDO_SERVER_URL.');
|
|
198
|
+
}
|
|
199
|
+
const auth = await resolveAuth(serverUrl, options, previous, username);
|
|
126
200
|
const registered = await apiJson(serverUrl, auth.accessToken, '/api/v1/hdo/devices/register', {
|
|
127
201
|
method: 'POST',
|
|
128
202
|
body: {
|
|
@@ -154,6 +228,8 @@ async function enrollCommand(args, refreshOnly) {
|
|
|
154
228
|
});
|
|
155
229
|
const runtime = hdoRuntimeFromManifest(manifest, registered, keys.privateKey, {
|
|
156
230
|
allowEndpointlessDirectPeers: direct.directListener,
|
|
231
|
+
launcherNetworkLease,
|
|
232
|
+
ownPublicKey: keys.publicKey,
|
|
157
233
|
});
|
|
158
234
|
writeWireGuardConfig(configPath, (0, electron_core_wireguard_1.renderHdoClientWireGuardConfig)({
|
|
159
235
|
privateKey: runtime.privateKey,
|
|
@@ -168,11 +244,15 @@ async function enrollCommand(args, refreshOnly) {
|
|
|
168
244
|
const now = new Date().toISOString();
|
|
169
245
|
writeState(stateFile, {
|
|
170
246
|
serverUrl,
|
|
247
|
+
internalUrl,
|
|
171
248
|
bearerToken: auth.accessToken,
|
|
172
249
|
refreshToken: auth.refreshToken,
|
|
173
250
|
accessExpiresAt: auth.accessExpiresAt,
|
|
174
251
|
refreshExpiresAt: auth.refreshExpiresAt,
|
|
175
252
|
username: auth.username ?? username,
|
|
253
|
+
productId,
|
|
254
|
+
launcherMode,
|
|
255
|
+
identityKind,
|
|
176
256
|
deviceId,
|
|
177
257
|
label,
|
|
178
258
|
interfaceName,
|
|
@@ -181,6 +261,7 @@ async function enrollCommand(args, refreshOnly) {
|
|
|
181
261
|
privateKey: keys.privateKey,
|
|
182
262
|
publicKey: keys.publicKey,
|
|
183
263
|
overlayIp: runtime.overlayIp,
|
|
264
|
+
launcherNetworkLease: launcherNetworkLease ?? previous.launcherNetworkLease,
|
|
184
265
|
directListener: direct.directListener,
|
|
185
266
|
preferDirectPeers: direct.preferDirectPeers,
|
|
186
267
|
endpointHost: direct.endpointHost,
|
|
@@ -215,8 +296,13 @@ function statusCommand(input) {
|
|
|
215
296
|
});
|
|
216
297
|
process.stdout.write(`State file: ${stateFile}\n`);
|
|
217
298
|
process.stdout.write(`Server URL: ${state.serverUrl || 'unset'}\n`);
|
|
299
|
+
process.stdout.write(`Internal URL: ${state.internalUrl || 'unset'}\n`);
|
|
300
|
+
process.stdout.write(`Product: ${state.productId || 'unset'} (${state.launcherMode || 'unset'} / ${state.identityKind || 'unset'})\n`);
|
|
218
301
|
process.stdout.write(`Device: ${state.deviceId || 'unset'}\n`);
|
|
219
302
|
process.stdout.write(`Overlay IP: ${state.overlayIp || 'unset'}\n`);
|
|
303
|
+
if (state.launcherNetworkLease) {
|
|
304
|
+
process.stdout.write(`Internal lease: ${state.launcherNetworkLease.leaseIp} ${state.launcherNetworkLease.cidr} (${state.launcherNetworkLease.leaseId})\n`);
|
|
305
|
+
}
|
|
220
306
|
process.stdout.write(`WireGuard config: ${configPath}\n\n`);
|
|
221
307
|
if (process.platform === 'linux') {
|
|
222
308
|
if (commandAvailable('systemctl')) {
|
|
@@ -296,6 +382,7 @@ function parseEnrollOptions(args) {
|
|
|
296
382
|
interfaceName: defaultInterfaceName,
|
|
297
383
|
role: 'internal',
|
|
298
384
|
start: true,
|
|
385
|
+
leaseOnly: false,
|
|
299
386
|
rotateKey: false,
|
|
300
387
|
directListener: false,
|
|
301
388
|
preferDirectPeers: false,
|
|
@@ -313,6 +400,39 @@ function parseEnrollOptions(args) {
|
|
|
313
400
|
case '--server-url':
|
|
314
401
|
options.serverUrl = readValue();
|
|
315
402
|
break;
|
|
403
|
+
case '--internal-url':
|
|
404
|
+
case '--mx-internal-url':
|
|
405
|
+
options.internalUrl = readValue();
|
|
406
|
+
break;
|
|
407
|
+
case '--product-id':
|
|
408
|
+
case '--app-id':
|
|
409
|
+
options.productId = readValue();
|
|
410
|
+
break;
|
|
411
|
+
case '--mode': {
|
|
412
|
+
const value = readValue();
|
|
413
|
+
if (value !== 'standalone' && value !== 'embed')
|
|
414
|
+
throw new Error(`Invalid --mode: ${value}`);
|
|
415
|
+
options.mode = value;
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
case '--identity-kind': {
|
|
419
|
+
const value = readValue();
|
|
420
|
+
if (value !== 'user' && value !== 'anonymous')
|
|
421
|
+
throw new Error(`Invalid --identity-kind: ${value}`);
|
|
422
|
+
options.identityKind = value;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
case '--anonymous':
|
|
426
|
+
options.identityKind = 'anonymous';
|
|
427
|
+
break;
|
|
428
|
+
case '--user-identity':
|
|
429
|
+
case '--employee':
|
|
430
|
+
options.identityKind = 'user';
|
|
431
|
+
break;
|
|
432
|
+
case '--lease-only':
|
|
433
|
+
options.leaseOnly = true;
|
|
434
|
+
options.start = false;
|
|
435
|
+
break;
|
|
316
436
|
case '--token':
|
|
317
437
|
options.token = readValue();
|
|
318
438
|
break;
|
|
@@ -498,6 +618,68 @@ async function refreshAuth(serverUrl, refreshToken, username) {
|
|
|
498
618
|
username,
|
|
499
619
|
};
|
|
500
620
|
}
|
|
621
|
+
async function enrollLauncherNetworkLease(internalUrl, input) {
|
|
622
|
+
const raw = await apiJson(internalUrl, '', '/internal/v1/launcher-network/enrollments', {
|
|
623
|
+
method: 'POST',
|
|
624
|
+
auth: false,
|
|
625
|
+
body: input,
|
|
626
|
+
});
|
|
627
|
+
const root = requireRecord(raw, 'launcher network enroll response');
|
|
628
|
+
const lease = requireRecord(root.lease, 'launcher network lease');
|
|
629
|
+
return parseLauncherNetworkLease(lease);
|
|
630
|
+
}
|
|
631
|
+
function parseLauncherNetworkLease(lease) {
|
|
632
|
+
const leaseId = stringField(lease.leaseId);
|
|
633
|
+
const productId = stringField(lease.productId);
|
|
634
|
+
const launcherMode = stringField(lease.launcherMode);
|
|
635
|
+
const identityKind = stringField(lease.identityKind);
|
|
636
|
+
const installId = stringField(lease.installId);
|
|
637
|
+
const deviceId = stringField(lease.deviceId);
|
|
638
|
+
const siteId = stringField(lease.siteId);
|
|
639
|
+
const cidr = stringField(lease.cidr);
|
|
640
|
+
const leaseIp = stringField(lease.leaseIp);
|
|
641
|
+
if (!leaseId || !productId || !installId || !deviceId || !siteId || !cidr || !leaseIp) {
|
|
642
|
+
throw new Error('Launcher Network lease response is missing required fields.');
|
|
643
|
+
}
|
|
644
|
+
if (launcherMode !== 'standalone' && launcherMode !== 'embed') {
|
|
645
|
+
throw new Error(`Launcher Network lease response has invalid launcherMode: ${launcherMode ?? 'missing'}`);
|
|
646
|
+
}
|
|
647
|
+
if (identityKind !== 'user' && identityKind !== 'anonymous') {
|
|
648
|
+
throw new Error(`Launcher Network lease response has invalid identityKind: ${identityKind ?? 'missing'}`);
|
|
649
|
+
}
|
|
650
|
+
return {
|
|
651
|
+
leaseId,
|
|
652
|
+
leaseKey: stringField(lease.leaseKey) ?? undefined,
|
|
653
|
+
productId,
|
|
654
|
+
launcherMode,
|
|
655
|
+
identityKind,
|
|
656
|
+
sequence: numberField(lease.sequence),
|
|
657
|
+
installId,
|
|
658
|
+
deviceId,
|
|
659
|
+
siteId,
|
|
660
|
+
userId: stringField(lease.userId),
|
|
661
|
+
cidr,
|
|
662
|
+
leaseIp,
|
|
663
|
+
serviceVip: stringField(lease.serviceVip) ?? undefined,
|
|
664
|
+
internalControlIp: stringField(lease.internalControlIp) ?? undefined,
|
|
665
|
+
domesticGatewayIp: stringField(lease.domesticGatewayIp) ?? undefined,
|
|
666
|
+
domesticSiteId: stringField(lease.domesticSiteId) ?? undefined,
|
|
667
|
+
overseaSiteId: stringField(lease.overseaSiteId) ?? undefined,
|
|
668
|
+
publicKey: stringField(lease.publicKey),
|
|
669
|
+
status: stringField(lease.status) ?? undefined,
|
|
670
|
+
updatedAt: stringField(lease.updatedAt) ?? undefined,
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
function allowedIpsFromLauncherNetworkLease(lease) {
|
|
674
|
+
if (!lease)
|
|
675
|
+
return [];
|
|
676
|
+
return uniqueStrings([
|
|
677
|
+
lease.cidr,
|
|
678
|
+
lease.serviceVip ? `${lease.serviceVip}/32` : '',
|
|
679
|
+
lease.internalControlIp ? `${lease.internalControlIp}/32` : '',
|
|
680
|
+
lease.domesticGatewayIp ? `${lease.domesticGatewayIp}/32` : '',
|
|
681
|
+
]).filter((value) => value.includes('/'));
|
|
682
|
+
}
|
|
501
683
|
function resolveKeypair(options, previous, installDir) {
|
|
502
684
|
if (!options.rotateKey && previous.privateKey && previous.publicKey) {
|
|
503
685
|
return { privateKey: previous.privateKey, publicKey: previous.publicKey };
|
|
@@ -550,6 +732,8 @@ function directPeersFromManifest(wireGuard, ownOverlayIp, options = {}) {
|
|
|
550
732
|
const overlayIp = stringField(row.overlayIp);
|
|
551
733
|
if (!publicKey || !overlayIp || overlayIp === ownIp)
|
|
552
734
|
return [];
|
|
735
|
+
if (options.ownPublicKey && publicKey === options.ownPublicKey)
|
|
736
|
+
return [];
|
|
553
737
|
const allowedIps = stringArray(row.allowedIps);
|
|
554
738
|
const endpoint = stringField(row.endpoint);
|
|
555
739
|
if (!endpoint && options.allowEndpointlessDirectPeers !== true)
|
|
@@ -625,8 +809,9 @@ function hdoRuntimeFromManifest(manifest, registered, privateKey, options = {})
|
|
|
625
809
|
const wireGuard = requireRecord(root.wireGuard, 'manifest.wireGuard');
|
|
626
810
|
const client = requireRecord(wireGuard.client, 'manifest.wireGuard.client');
|
|
627
811
|
const domestic = requireRecord(wireGuard.domestic, 'manifest.wireGuard.domestic');
|
|
628
|
-
const
|
|
812
|
+
const manifestOverlayIp = stringField(client.overlayIp) ||
|
|
629
813
|
stringField(requireRecord(registered, 'registered device').overlayIp);
|
|
814
|
+
const overlayIp = options.launcherNetworkLease?.leaseIp || manifestOverlayIp;
|
|
630
815
|
const domesticPublicKey = stringField(domestic.publicKey);
|
|
631
816
|
const domesticEndpoint = stringField(domestic.endpoint);
|
|
632
817
|
if (!overlayIp)
|
|
@@ -638,6 +823,7 @@ function hdoRuntimeFromManifest(manifest, registered, privateKey, options = {})
|
|
|
638
823
|
const domesticOverlay = stringField(domestic.overlayIp);
|
|
639
824
|
const allowedIps = uniqueStrings([
|
|
640
825
|
...routeCidrs,
|
|
826
|
+
...allowedIpsFromLauncherNetworkLease(options.launcherNetworkLease),
|
|
641
827
|
...(domesticOverlay ? [`${domesticOverlay}/32`] : []),
|
|
642
828
|
]).filter((value) => value.includes('/'));
|
|
643
829
|
if (allowedIps.length === 0) {
|
package/dist/index.js
CHANGED
|
@@ -91,9 +91,10 @@ Usage:
|
|
|
91
91
|
|
|
92
92
|
Common commands:
|
|
93
93
|
qp-tunnel-cli install --url http://IP:3434/peer_user01.mihomo.yaml --user download --password pass
|
|
94
|
+
qp-tunnel-cli install --file /opt/mx/current/qp-tunnel-cli/domestic-bootstrap-subscription.yaml
|
|
94
95
|
qp-tunnel-cli status
|
|
95
96
|
qp-tunnel-cli start
|
|
96
|
-
qp-tunnel-cli
|
|
97
|
+
qp-tunnel-cli egress-on
|
|
97
98
|
qp-tunnel-cli tun-on
|
|
98
99
|
qp-tunnel-cli tun-off
|
|
99
100
|
qp-tunnel-cli update-subscription
|
|
@@ -113,6 +114,7 @@ QP_TUNNEL_CONTAINER_HTTP_PROXY=http://host.docker.internal:<mixed-port>.
|
|
|
113
114
|
Install the script as a normal server command:
|
|
114
115
|
sudo qp-tunnel-cli install-script
|
|
115
116
|
sudo mihomo-client status
|
|
117
|
+
sudo mihomo-client egress-on
|
|
116
118
|
|
|
117
119
|
Enroll this machine into an HDO mesh:
|
|
118
120
|
HDO_PASSWORD=... qp-tunnel-cli hdo enroll --server-url https://domestic.example.com --username user
|
|
@@ -185,7 +187,7 @@ function mixedPortFromConfig() {
|
|
|
185
187
|
}
|
|
186
188
|
const configFile = process.env.MIHOMO_CONFIG_FILE || defaultMihomoConfigFile;
|
|
187
189
|
if (!(0, node_fs_1.existsSync)(configFile)) {
|
|
188
|
-
process.stderr.write(`Mihomo config not found: ${configFile}\nRun: sudo qp-tunnel-cli install ... && sudo qp-tunnel-cli
|
|
190
|
+
process.stderr.write(`Mihomo config not found: ${configFile}\nRun: sudo qp-tunnel-cli install ... && sudo qp-tunnel-cli egress-on\n`);
|
|
189
191
|
process.exit(1);
|
|
190
192
|
}
|
|
191
193
|
const content = (0, node_fs_1.readFileSync)(configFile, 'utf8');
|
package/package.json
CHANGED
|
@@ -45,8 +45,8 @@ Commands:
|
|
|
45
45
|
upgrade-systemd Refresh the installed systemd unit from this script
|
|
46
46
|
server-on Enable persistent server-safe outbound proxy mode, without TUN
|
|
47
47
|
server-off Disable server-safe proxy integrations, keeping the service installed
|
|
48
|
-
egress-on
|
|
49
|
-
egress-off
|
|
48
|
+
egress-on Preferred server-safe outbound proxy mode for Domestic bootstrap
|
|
49
|
+
egress-off Disable server-safe outbound proxy integrations
|
|
50
50
|
proxy-on Write /etc/profile.d proxy exports for login shells
|
|
51
51
|
proxy-off Remove /etc/profile.d proxy exports
|
|
52
52
|
tun-on Enable persistent Mihomo TUN mode with cn-direct and local/private route bypasses
|
|
@@ -65,6 +65,7 @@ Commands:
|
|
|
65
65
|
|
|
66
66
|
Install options:
|
|
67
67
|
--url URL Subscription URL, e.g. http://IP:3434/peer_user01.mihomo.yaml
|
|
68
|
+
--file FILE Local subscription YAML file, for Internal-pushed bootstrap
|
|
68
69
|
--user USER Basic Auth username for subscription
|
|
69
70
|
--password PASS Basic Auth password for subscription
|
|
70
71
|
--version TAG Mihomo version tag. Default: latest stable release
|
|
@@ -73,6 +74,7 @@ Install options:
|
|
|
73
74
|
|
|
74
75
|
Update options:
|
|
75
76
|
--url URL Override saved subscription URL
|
|
77
|
+
--file FILE Replace subscription from a local YAML file
|
|
76
78
|
--user USER Override saved subscription username
|
|
77
79
|
--password PASS Override saved subscription password
|
|
78
80
|
|
|
@@ -89,9 +91,13 @@ Examples:
|
|
|
89
91
|
--url http://IP:3434/peer_user01.mihomo.yaml \
|
|
90
92
|
--binary-path /tmp/mihomo
|
|
91
93
|
|
|
94
|
+
sudo bash ./scripts/mihomo-client.sh install \
|
|
95
|
+
--file /opt/mx/current/qp-tunnel-cli/domestic-bootstrap-subscription.yaml
|
|
96
|
+
|
|
92
97
|
sudo bash ./scripts/mihomo-client.sh update-subscription
|
|
98
|
+
sudo bash ./scripts/mihomo-client.sh update-subscription --file /opt/mx/current/qp-tunnel-cli/domestic-bootstrap-subscription.yaml
|
|
93
99
|
sudo bash ./scripts/mihomo-client.sh start
|
|
94
|
-
sudo bash ./scripts/mihomo-client.sh
|
|
100
|
+
sudo bash ./scripts/mihomo-client.sh egress-on
|
|
95
101
|
sudo bash ./scripts/mihomo-client.sh proxy-on
|
|
96
102
|
sudo bash ./scripts/mihomo-client.sh tun-on
|
|
97
103
|
sudo bash ./scripts/mihomo-client.sh ssh-proxy-on
|
|
@@ -575,6 +581,20 @@ fetch_subscription() {
|
|
|
575
581
|
render_runtime_config
|
|
576
582
|
}
|
|
577
583
|
|
|
584
|
+
install_subscription_file() {
|
|
585
|
+
local file_path="$1"
|
|
586
|
+
|
|
587
|
+
[[ -n "$file_path" ]] || die "Subscription file is required."
|
|
588
|
+
[[ -f "$file_path" ]] || die "Subscription file not found: $file_path"
|
|
589
|
+
[[ -s "$file_path" ]] || die "Subscription file is empty: $file_path"
|
|
590
|
+
|
|
591
|
+
ensure_dirs
|
|
592
|
+
log "Installing local subscription file: $file_path"
|
|
593
|
+
cp "$file_path" "$MIHOMO_SUBSCRIPTION_FILE"
|
|
594
|
+
chmod 600 "$MIHOMO_SUBSCRIPTION_FILE"
|
|
595
|
+
render_runtime_config
|
|
596
|
+
}
|
|
597
|
+
|
|
578
598
|
mixed_port_from_config() {
|
|
579
599
|
if [[ -f "$MIHOMO_CONFIG_FILE" ]]; then
|
|
580
600
|
awk -F: '/^[[:space:]]*mixed-port[[:space:]]*:/ {gsub(/[[:space:]]/, "", $2); print $2; exit}' "$MIHOMO_CONFIG_FILE"
|
|
@@ -682,10 +702,29 @@ update_subscription_command() {
|
|
|
682
702
|
local url="${1:-}"
|
|
683
703
|
local username="${2:-}"
|
|
684
704
|
local password="${3:-}"
|
|
705
|
+
local file_path="${4:-}"
|
|
685
706
|
local -a normalized=()
|
|
686
707
|
|
|
687
708
|
load_env
|
|
688
709
|
|
|
710
|
+
if [[ -n "$file_path" ]]; then
|
|
711
|
+
install_subscription_file "$file_path"
|
|
712
|
+
set_env_value MIHOMO_SUBSCRIPTION_SOURCE "local-file"
|
|
713
|
+
set_env_value MIHOMO_SUBSCRIPTION_LOCAL_FILE "$file_path"
|
|
714
|
+
set_env_value MIHOMO_SUBSCRIPTION_URL ""
|
|
715
|
+
set_env_value MIHOMO_SUBSCRIPTION_USER ""
|
|
716
|
+
set_env_value MIHOMO_SUBSCRIPTION_PASSWORD ""
|
|
717
|
+
|
|
718
|
+
if service_is_active; then
|
|
719
|
+
log "Restarting Mihomo service after local subscription update"
|
|
720
|
+
systemctl restart "$MIHOMO_SERVICE_NAME"
|
|
721
|
+
echo "Subscription updated from local file and service restarted."
|
|
722
|
+
else
|
|
723
|
+
echo "Subscription updated from local file."
|
|
724
|
+
fi
|
|
725
|
+
return
|
|
726
|
+
fi
|
|
727
|
+
|
|
689
728
|
url="${url:-${MIHOMO_SUBSCRIPTION_URL:-}}"
|
|
690
729
|
username="${username:-${MIHOMO_SUBSCRIPTION_USER:-}}"
|
|
691
730
|
password="${password:-${MIHOMO_SUBSCRIPTION_PASSWORD:-}}"
|
|
@@ -697,6 +736,8 @@ update_subscription_command() {
|
|
|
697
736
|
[[ -n "$url" ]] || die "No subscription URL configured. Use install or pass --url."
|
|
698
737
|
|
|
699
738
|
fetch_subscription "$url" "$username" "$password"
|
|
739
|
+
set_env_value MIHOMO_SUBSCRIPTION_SOURCE "url"
|
|
740
|
+
set_env_value MIHOMO_SUBSCRIPTION_LOCAL_FILE ""
|
|
700
741
|
set_env_value MIHOMO_SUBSCRIPTION_URL "$url"
|
|
701
742
|
set_env_value MIHOMO_SUBSCRIPTION_USER "$username"
|
|
702
743
|
set_env_value MIHOMO_SUBSCRIPTION_PASSWORD "$password"
|
|
@@ -941,24 +982,27 @@ install_command() {
|
|
|
941
982
|
local version="${4:-latest}"
|
|
942
983
|
local autostart="${5:-true}"
|
|
943
984
|
local binary_path="${6:-}"
|
|
985
|
+
local file_path="${7:-}"
|
|
944
986
|
local -a normalized=()
|
|
945
987
|
|
|
946
988
|
ensure_dirs
|
|
947
989
|
load_env
|
|
948
990
|
log "Starting Mihomo client install/setup"
|
|
949
991
|
|
|
950
|
-
if [[ -z "$url" ]]; then
|
|
992
|
+
if [[ -z "$file_path" && -z "$url" ]]; then
|
|
951
993
|
url="$(prompt_default "Subscription URL" "${MIHOMO_SUBSCRIPTION_URL:-}")"
|
|
952
994
|
fi
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
995
|
+
if [[ -z "$file_path" ]]; then
|
|
996
|
+
mapfile -t normalized < <(normalize_subscription_inputs "$url" "$username" "$password")
|
|
997
|
+
url="${normalized[0]}"
|
|
998
|
+
username="${normalized[1]}"
|
|
999
|
+
password="${normalized[2]}"
|
|
1000
|
+
if [[ -z "$username" ]]; then
|
|
1001
|
+
username="$(prompt_default "Subscription username (empty if none)" "${MIHOMO_SUBSCRIPTION_USER:-}")"
|
|
1002
|
+
fi
|
|
1003
|
+
if [[ -z "$password" ]]; then
|
|
1004
|
+
password="$(prompt_password "Subscription password (empty if none)")"
|
|
1005
|
+
fi
|
|
962
1006
|
fi
|
|
963
1007
|
|
|
964
1008
|
if [[ -n "$binary_path" ]]; then
|
|
@@ -978,8 +1022,8 @@ install_command() {
|
|
|
978
1022
|
set_env_value MIHOMO_SUBSCRIPTION_USER "$username"
|
|
979
1023
|
set_env_value MIHOMO_SUBSCRIPTION_PASSWORD "$password"
|
|
980
1024
|
|
|
981
|
-
log "
|
|
982
|
-
update_subscription_command "$url" "$username" "$password"
|
|
1025
|
+
log "Installing initial subscription and rendering runtime config"
|
|
1026
|
+
update_subscription_command "$url" "$username" "$password" "$file_path"
|
|
983
1027
|
|
|
984
1028
|
systemctl enable "$MIHOMO_SERVICE_NAME" >/dev/null 2>&1 || true
|
|
985
1029
|
if [[ "$autostart" == "true" ]]; then
|
|
@@ -1080,9 +1124,11 @@ main() {
|
|
|
1080
1124
|
local version="latest"
|
|
1081
1125
|
local autostart="true"
|
|
1082
1126
|
local binary_path=""
|
|
1127
|
+
local file_path=""
|
|
1083
1128
|
while [[ $# -gt 0 ]]; do
|
|
1084
1129
|
case "$1" in
|
|
1085
1130
|
--url) url="$2"; shift 2 ;;
|
|
1131
|
+
--file) file_path="$2"; shift 2 ;;
|
|
1086
1132
|
--user) username="$2"; shift 2 ;;
|
|
1087
1133
|
--password) password="$2"; shift 2 ;;
|
|
1088
1134
|
--version) version="$2"; shift 2 ;;
|
|
@@ -1091,21 +1137,23 @@ main() {
|
|
|
1091
1137
|
*) die "Unknown install option: $1" ;;
|
|
1092
1138
|
esac
|
|
1093
1139
|
done
|
|
1094
|
-
install_command "$url" "$username" "$password" "$version" "$autostart" "$binary_path"
|
|
1140
|
+
install_command "$url" "$username" "$password" "$version" "$autostart" "$binary_path" "$file_path"
|
|
1095
1141
|
;;
|
|
1096
1142
|
update-subscription)
|
|
1097
1143
|
local url=""
|
|
1098
1144
|
local username=""
|
|
1099
1145
|
local password=""
|
|
1146
|
+
local file_path=""
|
|
1100
1147
|
while [[ $# -gt 0 ]]; do
|
|
1101
1148
|
case "$1" in
|
|
1102
1149
|
--url) url="$2"; shift 2 ;;
|
|
1150
|
+
--file) file_path="$2"; shift 2 ;;
|
|
1103
1151
|
--user) username="$2"; shift 2 ;;
|
|
1104
1152
|
--password) password="$2"; shift 2 ;;
|
|
1105
1153
|
*) die "Unknown update-subscription option: $1" ;;
|
|
1106
1154
|
esac
|
|
1107
1155
|
done
|
|
1108
|
-
update_subscription_command "$url" "$username" "$password"
|
|
1156
|
+
update_subscription_command "$url" "$username" "$password" "$file_path"
|
|
1109
1157
|
;;
|
|
1110
1158
|
start)
|
|
1111
1159
|
start_command
|