dsh-mobile 0.6.7 → 0.6.9
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/CHANGELOG.md +24 -2
- package/CONTRIBUTORS.md +4 -2
- package/README.en.md +28 -16
- package/README.md +28 -16
- package/SECURITY.md +9 -0
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/apps/mobile/README.md +11 -7
- package/apps/mobile/release.json +3 -3
- package/docs/CADDY_MANAGED.en.md +21 -17
- package/docs/CADDY_MANAGED.md +21 -17
- package/docs/README.md +3 -3
- package/docs/SELF_HOSTED_FRP.en.md +1 -1
- package/docs/SELF_HOSTED_FRP.md +1 -1
- package/docs/SELF_HOSTED_ORIGIN.en.md +3 -1
- package/docs/SELF_HOSTED_ORIGIN.md +4 -2
- package/lib/client.js +9 -9
- package/lib/client.js.map +1 -1
- package/lib/index.d.mts +18 -2
- package/lib/index.mjs +314 -81
- package/lib/index.mjs.map +1 -1
- package/package.json +1 -1
- package/packages/question-fixes/package.json +1 -1
package/lib/index.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { basename, dirname, extname, isAbsolute, join, relative, resolve, win32
|
|
|
4
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
5
5
|
import z from "@deepseek-ai/schemastery";
|
|
6
6
|
import { connect, createServer } from "node:net";
|
|
7
|
-
import { chmod, copyFile, lstat, mkdir, mkdtemp, open, opendir, readFile, readdir, realpath, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
7
|
+
import { chmod, copyFile, link, lstat, mkdir, mkdtemp, open, opendir, readFile, readdir, realpath, rename, rm, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
8
8
|
import { performance as performance$1 } from "node:perf_hooks";
|
|
9
9
|
import { execFile, spawn } from "node:child_process";
|
|
10
10
|
import { createSocket } from "node:dgram";
|
|
@@ -144,10 +144,14 @@ var AccessController = class {
|
|
|
144
144
|
}
|
|
145
145
|
snapshot(devices) {
|
|
146
146
|
return Object.freeze({
|
|
147
|
-
version:
|
|
147
|
+
version: 2,
|
|
148
148
|
devices: Object.freeze([...devices])
|
|
149
149
|
});
|
|
150
150
|
}
|
|
151
|
+
nativeIdentity(device) {
|
|
152
|
+
const nativeInstanceId = device.nativeInstanceId ?? this.options.legacyInstanceId ?? this.options.pairingInstanceId;
|
|
153
|
+
return nativeInstanceId === void 0 ? {} : { nativeInstanceId };
|
|
154
|
+
}
|
|
151
155
|
emitSessionEnded(session, reason) {
|
|
152
156
|
const authorization = Object.freeze({
|
|
153
157
|
sessionKey: session.key,
|
|
@@ -165,7 +169,7 @@ var AccessController = class {
|
|
|
165
169
|
pruneSessions(now) {
|
|
166
170
|
for (const [key, session] of this.sessions) if (session.expiresAt <= now) this.removeSession(key, "expired");
|
|
167
171
|
}
|
|
168
|
-
createSession(
|
|
172
|
+
createSession(device, now) {
|
|
169
173
|
this.pruneSessions(now);
|
|
170
174
|
if (this.sessions.size >= this.options.maxSessions) {
|
|
171
175
|
const oldest = [...this.sessions.values()].sort((left, right) => left.createdAt - right.createdAt)[0];
|
|
@@ -176,14 +180,15 @@ var AccessController = class {
|
|
|
176
180
|
const key = digestHex(sessionToken);
|
|
177
181
|
const record = Object.freeze({
|
|
178
182
|
key,
|
|
179
|
-
deviceId,
|
|
183
|
+
deviceId: device.id,
|
|
180
184
|
csrfDigest: digest$1(csrfToken),
|
|
181
185
|
createdAt: now,
|
|
182
|
-
expiresAt: Math.min(now + this.options.sessionTtlMs,
|
|
186
|
+
expiresAt: Math.min(now + this.options.sessionTtlMs, device.expiresAt)
|
|
183
187
|
});
|
|
184
188
|
this.sessions.set(key, record);
|
|
185
189
|
return Object.freeze({
|
|
186
|
-
|
|
190
|
+
...this.nativeIdentity(device),
|
|
191
|
+
deviceId: device.id,
|
|
187
192
|
sessionToken,
|
|
188
193
|
csrfToken,
|
|
189
194
|
sessionExpiresAt: record.expiresAt
|
|
@@ -228,12 +233,13 @@ var AccessController = class {
|
|
|
228
233
|
tokenDigest: digestHex(deviceToken),
|
|
229
234
|
createdAt: now,
|
|
230
235
|
expiresAt: now + this.options.deviceTtlMs,
|
|
231
|
-
lastSeenAt: now
|
|
236
|
+
lastSeenAt: now,
|
|
237
|
+
...this.options.pairingInstanceId === void 0 ? {} : { nativeInstanceId: this.options.pairingInstanceId }
|
|
232
238
|
});
|
|
233
239
|
const next = [...this.devices.filter((candidate) => candidate.revokedAt === void 0 && candidate.expiresAt > now), device];
|
|
234
240
|
await this.store.save(this.snapshot(next));
|
|
235
241
|
this.devices = next;
|
|
236
|
-
const session = this.createSession(device
|
|
242
|
+
const session = this.createSession(device, now);
|
|
237
243
|
return Object.freeze({
|
|
238
244
|
...session,
|
|
239
245
|
deviceToken,
|
|
@@ -256,7 +262,7 @@ var AccessController = class {
|
|
|
256
262
|
next[index] = updated;
|
|
257
263
|
await this.store.save(this.snapshot(next));
|
|
258
264
|
this.devices = next;
|
|
259
|
-
return this.createSession(device
|
|
265
|
+
return this.createSession(device, now);
|
|
260
266
|
});
|
|
261
267
|
}
|
|
262
268
|
/** Validate a persistent device credential without consuming a Session slot. */
|
|
@@ -275,6 +281,7 @@ var AccessController = class {
|
|
|
275
281
|
await this.store.save(this.snapshot(next));
|
|
276
282
|
this.devices = next;
|
|
277
283
|
return Object.freeze({
|
|
284
|
+
...this.nativeIdentity(device),
|
|
278
285
|
deviceId: device.id,
|
|
279
286
|
deviceExpiresAt: device.expiresAt
|
|
280
287
|
});
|
|
@@ -285,6 +292,7 @@ var AccessController = class {
|
|
|
285
292
|
this.requireInitialized();
|
|
286
293
|
const device = this.requireDevice(deviceToken, this.now());
|
|
287
294
|
return Object.freeze({
|
|
295
|
+
...this.nativeIdentity(device),
|
|
288
296
|
deviceId: device.id,
|
|
289
297
|
deviceExpiresAt: device.expiresAt
|
|
290
298
|
});
|
|
@@ -5289,6 +5297,8 @@ var MobileAccessGateway = class {
|
|
|
5289
5297
|
this.listenerTlsEnabled = config.tls.mode === "provided";
|
|
5290
5298
|
this.tlsEnabled = config.publicTls;
|
|
5291
5299
|
this.access = new AccessController(store, {
|
|
5300
|
+
pairingInstanceId: config.instanceId,
|
|
5301
|
+
...config.legacyInstanceId === void 0 ? {} : { legacyInstanceId: config.legacyInstanceId },
|
|
5292
5302
|
pairingTtlMs: config.pairingTtlMs,
|
|
5293
5303
|
deviceTtlMs: config.deviceTtlMs,
|
|
5294
5304
|
sessionTtlMs: config.sessionTtlMs,
|
|
@@ -5665,12 +5675,16 @@ var MobileAccessGateway = class {
|
|
|
5665
5675
|
sessionExpiresAt: result.sessionExpiresAt
|
|
5666
5676
|
}, this.tlsEnabled);
|
|
5667
5677
|
}
|
|
5678
|
+
/** CA-backed entries retain their pin; public entries project the authenticated credential's identity. */
|
|
5679
|
+
nativeResponseInstanceId(result) {
|
|
5680
|
+
return this.config.pairingCaFile === void 0 ? result.nativeInstanceId ?? this.config.instanceId : this.config.instanceId;
|
|
5681
|
+
}
|
|
5668
5682
|
async handleNativePair(request, response) {
|
|
5669
5683
|
const body = await readJsonObject(request, MAX_CONTROL_BODY_BYTES);
|
|
5670
5684
|
if (typeof body.token !== "string" || body.label !== void 0 && typeof body.label !== "string") throw new HttpError(400, "bad_request");
|
|
5671
5685
|
const result = await this.access.pair(request.socket.remoteAddress ?? "unknown", body.token, body.label);
|
|
5672
5686
|
sendJson(response, 201, {
|
|
5673
|
-
instanceId: this.
|
|
5687
|
+
instanceId: this.nativeResponseInstanceId(result),
|
|
5674
5688
|
deviceId: result.deviceId,
|
|
5675
5689
|
deviceToken: result.deviceToken,
|
|
5676
5690
|
deviceExpiresAt: result.deviceExpiresAt,
|
|
@@ -5685,7 +5699,7 @@ var MobileAccessGateway = class {
|
|
|
5685
5699
|
if (typeof body.deviceToken !== "string") throw new HttpError(400, "bad_request");
|
|
5686
5700
|
const result = await this.access.renew(body.deviceToken);
|
|
5687
5701
|
sendJson(response, 200, {
|
|
5688
|
-
instanceId: this.
|
|
5702
|
+
instanceId: this.nativeResponseInstanceId(result),
|
|
5689
5703
|
deviceId: result.deviceId,
|
|
5690
5704
|
sessionToken: result.sessionToken,
|
|
5691
5705
|
csrfToken: result.csrfToken,
|
|
@@ -5698,7 +5712,7 @@ var MobileAccessGateway = class {
|
|
|
5698
5712
|
if (typeof body.deviceToken !== "string") throw new HttpError(400, "bad_request");
|
|
5699
5713
|
const result = await this.access.probe(body.deviceToken);
|
|
5700
5714
|
sendJson(response, 200, {
|
|
5701
|
-
instanceId: this.
|
|
5715
|
+
instanceId: this.nativeResponseInstanceId(result),
|
|
5702
5716
|
deviceId: result.deviceId,
|
|
5703
5717
|
deviceExpiresAt: result.deviceExpiresAt
|
|
5704
5718
|
}, this.tlsEnabled);
|
|
@@ -7367,6 +7381,7 @@ function parseDevice(value) {
|
|
|
7367
7381
|
if (typeof record.id !== "string" || !/^[a-f\d]{32}$/u.test(record.id)) throw new Error("device state contains an invalid id");
|
|
7368
7382
|
if (typeof record.label !== "string" || record.label.length < 1 || record.label.length > 64 || /[\u0000-\u001f\u007f]/u.test(record.label)) throw new Error("device state contains an invalid label");
|
|
7369
7383
|
if (typeof record.tokenDigest !== "string" || !/^[a-f\d]{64}$/u.test(record.tokenDigest)) throw new Error("device state contains an invalid credential digest");
|
|
7384
|
+
if (record.nativeInstanceId !== void 0 && (typeof record.nativeInstanceId !== "string" || !/^[a-f\d]{64}$/u.test(record.nativeInstanceId))) throw new Error("device state contains an invalid native instance id");
|
|
7370
7385
|
assertInteger(record.createdAt, "createdAt");
|
|
7371
7386
|
assertInteger(record.expiresAt, "expiresAt");
|
|
7372
7387
|
assertInteger(record.lastSeenAt, "lastSeenAt");
|
|
@@ -7379,6 +7394,7 @@ function parseDevice(value) {
|
|
|
7379
7394
|
createdAt: record.createdAt,
|
|
7380
7395
|
expiresAt: record.expiresAt,
|
|
7381
7396
|
lastSeenAt: record.lastSeenAt,
|
|
7397
|
+
...record.nativeInstanceId === void 0 ? {} : { nativeInstanceId: record.nativeInstanceId },
|
|
7382
7398
|
...record.revokedAt === void 0 ? {} : { revokedAt: record.revokedAt }
|
|
7383
7399
|
});
|
|
7384
7400
|
}
|
|
@@ -7386,14 +7402,21 @@ function parseDevice(value) {
|
|
|
7386
7402
|
function parseDeviceSnapshot(value, maximumDevices = 256) {
|
|
7387
7403
|
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error("device state must be an object");
|
|
7388
7404
|
const snapshot = value;
|
|
7389
|
-
if (snapshot.version !== 1 || !Array.isArray(snapshot.devices) || snapshot.devices.length > maximumDevices) throw new Error("device state has an unsupported version or device count");
|
|
7405
|
+
if (snapshot.version !== 1 && snapshot.version !== 2 || !Array.isArray(snapshot.devices) || snapshot.devices.length > maximumDevices) throw new Error("device state has an unsupported version or device count");
|
|
7390
7406
|
const devices = snapshot.devices.map(parseDevice);
|
|
7391
7407
|
if (new Set(devices.map((device) => device.id)).size !== devices.length || new Set(devices.map((device) => device.tokenDigest)).size !== devices.length) throw new Error("device state contains duplicate device identities");
|
|
7392
7408
|
return Object.freeze({
|
|
7393
|
-
version:
|
|
7409
|
+
version: snapshot.version,
|
|
7394
7410
|
devices: Object.freeze(devices)
|
|
7395
7411
|
});
|
|
7396
7412
|
}
|
|
7413
|
+
function snapshotForSave(snapshot, maximumDevices) {
|
|
7414
|
+
const validated = parseDeviceSnapshot(snapshot, maximumDevices);
|
|
7415
|
+
return Object.freeze({
|
|
7416
|
+
version: 2,
|
|
7417
|
+
devices: validated.devices
|
|
7418
|
+
});
|
|
7419
|
+
}
|
|
7397
7420
|
/** Atomic JSON implementation with symlink refusal and owner-only file creation. */
|
|
7398
7421
|
var JsonDeviceStore = class {
|
|
7399
7422
|
file;
|
|
@@ -7424,7 +7447,7 @@ var JsonDeviceStore = class {
|
|
|
7424
7447
|
return parseDeviceSnapshot(parsed, this.maximumDevices);
|
|
7425
7448
|
}
|
|
7426
7449
|
async save(snapshot) {
|
|
7427
|
-
const validated =
|
|
7450
|
+
const validated = snapshotForSave(snapshot, this.maximumDevices);
|
|
7428
7451
|
const directory = dirname(this.file);
|
|
7429
7452
|
await mkdir(directory, {
|
|
7430
7453
|
recursive: true,
|
|
@@ -7468,7 +7491,7 @@ var MemoryDeviceStore = class {
|
|
|
7468
7491
|
return structuredClone(this.snapshot);
|
|
7469
7492
|
}
|
|
7470
7493
|
async save(snapshot) {
|
|
7471
|
-
this.snapshot = structuredClone(
|
|
7494
|
+
this.snapshot = structuredClone(snapshotForSave(snapshot));
|
|
7472
7495
|
}
|
|
7473
7496
|
/** Return a defensive copy for assertions or administrative export. */
|
|
7474
7497
|
inspect() {
|
|
@@ -10587,6 +10610,8 @@ var RemoteProviderCoordinator = class {
|
|
|
10587
10610
|
store;
|
|
10588
10611
|
selectedValue;
|
|
10589
10612
|
queue = Promise.resolve();
|
|
10613
|
+
closing = false;
|
|
10614
|
+
closeTask;
|
|
10590
10615
|
constructor(selected, controllers, store) {
|
|
10591
10616
|
this.controllers = controllers;
|
|
10592
10617
|
this.store = store;
|
|
@@ -10638,8 +10663,16 @@ var RemoteProviderCoordinator = class {
|
|
|
10638
10663
|
}
|
|
10639
10664
|
});
|
|
10640
10665
|
}
|
|
10666
|
+
/** Stop admitting mutations and wait for accepted work before callers release provider resources. */
|
|
10667
|
+
close() {
|
|
10668
|
+
this.closing = true;
|
|
10669
|
+
this.closeTask ??= this.queue;
|
|
10670
|
+
return this.closeTask;
|
|
10671
|
+
}
|
|
10641
10672
|
enqueue(operation) {
|
|
10642
|
-
|
|
10673
|
+
if (this.closing) return Promise.reject(/* @__PURE__ */ new Error("remote_provider_closed"));
|
|
10674
|
+
const run = () => this.closing ? Promise.reject(/* @__PURE__ */ new Error("remote_provider_closed")) : this.runAndEnforce(operation);
|
|
10675
|
+
const task = this.queue.then(run, run);
|
|
10643
10676
|
this.queue = task.then(() => void 0, () => void 0);
|
|
10644
10677
|
return task;
|
|
10645
10678
|
}
|
|
@@ -13330,6 +13363,8 @@ const MOBILE_CUSTOMIZATION_GUIDE_BODY = `你在为用户定制 DSH Mobile 的手
|
|
|
13330
13363
|
|
|
13331
13364
|
动手前先读已有的定制文件。若要调整 DSH 自带的输入框、侧栏或设置等现有界面,先确认当前 DSH 版本与手机页面模式(专用移动页面或 ?frontend=stock),只读核对运行页面的 DOM、属性和样式;本机有同版本前端源码时可参考相应组件,没有源码时可查看已安装的客户端产物,不必下载源码。优先使用扩展 API、插槽和稳定的 data-* 标记,不凭猜测依赖构建类名。只读查看不改变前述写入范围。
|
|
13332
13365
|
|
|
13366
|
+
旧 CSS 可能覆盖新版内置修复。先备份并在同一会话、方向和字号下对比有无该样式,不要无症状叠加旧补丁。CSS Modules 的后缀/子串匹配必须限定到已核对的区域祖先,例如 [data-dsh-mobile-header] [class*="_titleRow"];不要裸匹配整页。display: none 会让其中的操作不可用,不得把隐藏功能描述成折叠。
|
|
13367
|
+
|
|
13333
13368
|
2. 电脑端能力 —— 手机需要读电脑文件、执行命令或访问硬件时,创建扩展:
|
|
13334
13369
|
- 目录:$DSH_HOME/mobile-access/extensions/<id>/,id 用小写字母数字和连字符(如 media-remote)
|
|
13335
13370
|
- extension.json:{"schemaVersion":1,"id":"<id>","name":"显示名","version":"0.1.0","description":"说明"}
|
|
@@ -15272,10 +15307,35 @@ async function removeCaddyTree(root, target) {
|
|
|
15272
15307
|
}
|
|
15273
15308
|
//#endregion
|
|
15274
15309
|
//#region src/caddy-component.ts
|
|
15275
|
-
const CADDY_VERSION = "2.11.
|
|
15310
|
+
const CADDY_VERSION = "2.11.7";
|
|
15276
15311
|
const CADDY_DNS_PLUGIN_VERSION = "v0.4.3";
|
|
15277
|
-
const CADDY_DOWNLOAD_PAGE = "https://
|
|
15278
|
-
const CADDY_COMPONENT_RELEASES = Object.freeze({
|
|
15312
|
+
const CADDY_DOWNLOAD_PAGE = "https://github.com/saya-ch/dsh-mobile/releases/tag/caddy-component-2.11.7-tencentcloud-0.4.3-r1";
|
|
15313
|
+
const CADDY_COMPONENT_RELEASES = Object.freeze({
|
|
15314
|
+
"linux-x64": Object.freeze({
|
|
15315
|
+
version: CADDY_VERSION,
|
|
15316
|
+
dnsPluginVersion: CADDY_DNS_PLUGIN_VERSION,
|
|
15317
|
+
platform: "linux",
|
|
15318
|
+
arch: "x64",
|
|
15319
|
+
executableName: "caddy",
|
|
15320
|
+
downloadUrl: "https://github.com/saya-ch/dsh-mobile/releases/download/caddy-component-2.11.7-tencentcloud-0.4.3-r1/caddy-2.11.7-tencentcloud-0.4.3-linux-x64",
|
|
15321
|
+
downloadBytes: 81995704,
|
|
15322
|
+
executableBytes: 81995704,
|
|
15323
|
+
downloadSha256: "13652ef9ea3792ebff772a5b873ade63ed6de1c0a794bb3403a523639b8f57af",
|
|
15324
|
+
executableSha256: "13652ef9ea3792ebff772a5b873ade63ed6de1c0a794bb3403a523639b8f57af"
|
|
15325
|
+
}),
|
|
15326
|
+
"win32-x64": Object.freeze({
|
|
15327
|
+
version: CADDY_VERSION,
|
|
15328
|
+
dnsPluginVersion: CADDY_DNS_PLUGIN_VERSION,
|
|
15329
|
+
platform: "win32",
|
|
15330
|
+
arch: "x64",
|
|
15331
|
+
executableName: "caddy.exe",
|
|
15332
|
+
downloadUrl: "https://github.com/saya-ch/dsh-mobile/releases/download/caddy-component-2.11.7-tencentcloud-0.4.3-r1/caddy-2.11.7-tencentcloud-0.4.3-win32-x64.exe",
|
|
15333
|
+
downloadBytes: 82816e3,
|
|
15334
|
+
executableBytes: 82816e3,
|
|
15335
|
+
downloadSha256: "962f7937a06e1e737513d2d1d4ad75df54ba503d36fa2a458912bd38fbd0094a",
|
|
15336
|
+
executableSha256: "962f7937a06e1e737513d2d1d4ad75df54ba503d36fa2a458912bd38fbd0094a"
|
|
15337
|
+
})
|
|
15338
|
+
});
|
|
15279
15339
|
Object.freeze({
|
|
15280
15340
|
version: CADDY_VERSION,
|
|
15281
15341
|
dnsPluginVersion: CADDY_DNS_PLUGIN_VERSION
|
|
@@ -15299,14 +15359,16 @@ function caddyInspectionEnvironment(environment = process.env) {
|
|
|
15299
15359
|
]) if (environment[name] !== void 0) result[name] = environment[name];
|
|
15300
15360
|
return result;
|
|
15301
15361
|
}
|
|
15302
|
-
async function inspect(executable) {
|
|
15362
|
+
async function inspect(executable, assertOpen) {
|
|
15303
15363
|
const options = {
|
|
15304
15364
|
env: caddyInspectionEnvironment(),
|
|
15305
15365
|
timeout: 15e3,
|
|
15306
15366
|
maxBuffer: 262144
|
|
15307
15367
|
};
|
|
15368
|
+
const version = (await execFileText(executable, ["version"], options)).stdout.trim();
|
|
15369
|
+
assertOpen();
|
|
15308
15370
|
return {
|
|
15309
|
-
version
|
|
15371
|
+
version,
|
|
15310
15372
|
modules: (await execFileText(executable, ["list-modules", "--versions"], options)).stdout
|
|
15311
15373
|
};
|
|
15312
15374
|
}
|
|
@@ -15325,6 +15387,10 @@ var CaddyComponentManager = class {
|
|
|
15325
15387
|
installed = false;
|
|
15326
15388
|
errorCode;
|
|
15327
15389
|
queue = Promise.resolve();
|
|
15390
|
+
closed = false;
|
|
15391
|
+
closing;
|
|
15392
|
+
download;
|
|
15393
|
+
cleanupPaths = /* @__PURE__ */ new Set();
|
|
15328
15394
|
constructor(options) {
|
|
15329
15395
|
if (!isAbsolute(options.stateDirectory)) throw new Error("caddy state directory must be absolute");
|
|
15330
15396
|
this.stateDirectory = resolve(options.stateDirectory);
|
|
@@ -15334,7 +15400,7 @@ var CaddyComponentManager = class {
|
|
|
15334
15400
|
if (artifact !== void 0 && (artifact.platform !== platform || artifact.arch !== arch || !/^https:\/\//u.test(artifact.downloadUrl) || !/^[a-f0-9]{64}$/u.test(artifact.downloadSha256) || !/^[a-f0-9]{64}$/u.test(artifact.executableSha256) || artifact.downloadBytes < 1 || artifact.executableBytes < 1 || !/^[a-z0-9.-]+$/u.test(artifact.executableName))) throw new Error("caddy_artifact_invalid");
|
|
15335
15401
|
this.artifact = artifact;
|
|
15336
15402
|
this.componentRoot = join(this.stateDirectory, "components", "caddy");
|
|
15337
|
-
this.componentStorage = join(this.componentRoot, artifact?.version ?? "2.11.
|
|
15403
|
+
this.componentStorage = join(this.componentRoot, artifact?.version ?? "2.11.7");
|
|
15338
15404
|
this.executable = join(this.componentStorage, artifact?.executableName ?? (platform === "win32" ? "caddy.exe" : "caddy"));
|
|
15339
15405
|
this.logRoot = join(this.stateDirectory, "caddy", "logs");
|
|
15340
15406
|
this.stagingRoot = join(this.stateDirectory, "staging", "caddy");
|
|
@@ -15344,41 +15410,48 @@ var CaddyComponentManager = class {
|
|
|
15344
15410
|
expectedBytes: artifact?.downloadBytes ?? 0,
|
|
15345
15411
|
errorPrefix: "caddy"
|
|
15346
15412
|
}));
|
|
15347
|
-
this.inspectExecutable = options.inspectExecutable ?? inspect;
|
|
15413
|
+
this.inspectExecutable = options.inspectExecutable ?? ((executable) => inspect(executable, () => this.assertOpen()));
|
|
15348
15414
|
this.promote = options.promoteDirectory ?? ((source, destination) => renameCaddyPath(this.stateDirectory, source, destination));
|
|
15349
15415
|
}
|
|
15350
15416
|
/** Recheck a pinned executable before launching; no unverified local binary can run. */
|
|
15351
|
-
|
|
15417
|
+
ensureExecutable() {
|
|
15418
|
+
return this.enqueue(() => this.checkExecutable());
|
|
15419
|
+
}
|
|
15420
|
+
async checkExecutable() {
|
|
15352
15421
|
const artifact = this.artifact;
|
|
15353
15422
|
if (artifact === void 0) throw new Error("caddy_component_unavailable");
|
|
15354
15423
|
await assertCaddyParents(this.stateDirectory, this.executable);
|
|
15424
|
+
this.assertOpen();
|
|
15355
15425
|
const entry = await lstat(this.executable);
|
|
15356
15426
|
if (!entry.isFile() || entry.isSymbolicLink() || entry.size !== artifact.executableBytes || await digest(this.executable) !== artifact.executableSha256) throw new Error("caddy_component_invalid");
|
|
15357
15427
|
await this.verifyMetadata(this.executable, artifact);
|
|
15358
15428
|
}
|
|
15359
15429
|
async initialize() {
|
|
15360
|
-
this.
|
|
15361
|
-
|
|
15362
|
-
|
|
15363
|
-
this.
|
|
15364
|
-
|
|
15365
|
-
|
|
15366
|
-
|
|
15367
|
-
|
|
15368
|
-
|
|
15369
|
-
|
|
15370
|
-
|
|
15371
|
-
|
|
15430
|
+
return this.enqueue(async () => {
|
|
15431
|
+
this.installed = false;
|
|
15432
|
+
this.errorCode = void 0;
|
|
15433
|
+
if (this.artifact === void 0) {
|
|
15434
|
+
this.errorCode = "caddy_component_unavailable";
|
|
15435
|
+
return;
|
|
15436
|
+
}
|
|
15437
|
+
try {
|
|
15438
|
+
await this.checkExecutable();
|
|
15439
|
+
this.installed = true;
|
|
15440
|
+
} catch (error) {
|
|
15441
|
+
this.assertOpen();
|
|
15442
|
+
if (error.code !== "ENOENT") this.errorCode = "caddy_component_invalid";
|
|
15443
|
+
}
|
|
15444
|
+
});
|
|
15372
15445
|
}
|
|
15373
15446
|
status() {
|
|
15374
15447
|
const artifact = this.artifact;
|
|
15375
15448
|
return Object.freeze({
|
|
15376
15449
|
supported: artifact !== void 0,
|
|
15377
15450
|
installed: this.installed,
|
|
15378
|
-
version: artifact?.version ?? "2.11.
|
|
15451
|
+
version: artifact?.version ?? "2.11.7",
|
|
15379
15452
|
downloadBytes: artifact?.downloadBytes ?? 0,
|
|
15380
15453
|
installedBytes: this.installed ? artifact?.executableBytes ?? 0 : 0,
|
|
15381
|
-
sourceUrl: artifact?.downloadUrl ?? "https://
|
|
15454
|
+
sourceUrl: artifact?.downloadUrl ?? "https://github.com/saya-ch/dsh-mobile/releases/tag/caddy-component-2.11.7-tencentcloud-0.4.3-r1",
|
|
15382
15455
|
downloadPage: CADDY_DOWNLOAD_PAGE,
|
|
15383
15456
|
storagePath: this.componentRoot,
|
|
15384
15457
|
...this.errorCode === void 0 ? {} : { errorCode: this.errorCode }
|
|
@@ -15390,28 +15463,40 @@ var CaddyComponentManager = class {
|
|
|
15390
15463
|
const artifact = this.artifact;
|
|
15391
15464
|
if (artifact === void 0) throw new Error("caddy_component_unavailable");
|
|
15392
15465
|
await ensureCaddyDirectory(this.stateDirectory, this.stagingRoot);
|
|
15466
|
+
this.assertOpen();
|
|
15393
15467
|
await ensureCaddyDirectory(this.stateDirectory, this.componentRoot);
|
|
15468
|
+
this.assertOpen();
|
|
15394
15469
|
const staging = await mkdtemp(join(this.stagingRoot, "install-"));
|
|
15395
15470
|
const candidate = join(this.componentRoot, ".install-" + randomBytes(12).toString("hex"));
|
|
15396
15471
|
const backup = join(this.componentRoot, ".previous-" + randomBytes(12).toString("hex"));
|
|
15397
15472
|
let previous = false;
|
|
15398
15473
|
let promoted = false;
|
|
15399
15474
|
let failed = false;
|
|
15475
|
+
let operationError;
|
|
15400
15476
|
try {
|
|
15477
|
+
this.assertOpen();
|
|
15401
15478
|
const controller = new AbortController();
|
|
15402
15479
|
const timeout = setTimeout(() => {
|
|
15403
15480
|
controller.abort();
|
|
15404
15481
|
}, 6e5);
|
|
15405
15482
|
timeout.unref();
|
|
15483
|
+
this.download = {
|
|
15484
|
+
controller,
|
|
15485
|
+
timeout
|
|
15486
|
+
};
|
|
15406
15487
|
let bytes;
|
|
15407
15488
|
try {
|
|
15408
15489
|
bytes = await this.fetchArtifact(artifact.downloadUrl, controller.signal);
|
|
15409
15490
|
} finally {
|
|
15410
15491
|
clearTimeout(timeout);
|
|
15492
|
+
this.download = void 0;
|
|
15411
15493
|
}
|
|
15494
|
+
this.assertOpen();
|
|
15495
|
+
controller.signal.throwIfAborted();
|
|
15412
15496
|
if (bytes.byteLength !== artifact.downloadBytes) throw new Error("caddy_download_size_mismatch");
|
|
15413
15497
|
if (createHash("sha256").update(bytes).digest("hex") !== artifact.downloadSha256) throw new Error("caddy_download_hash_mismatch");
|
|
15414
15498
|
await ensureCaddyDirectory(this.stateDirectory, candidate);
|
|
15499
|
+
this.assertOpen();
|
|
15415
15500
|
const executable = join(candidate, artifact.executableName);
|
|
15416
15501
|
await writeFile(executable, bytes, {
|
|
15417
15502
|
flag: "wx",
|
|
@@ -15420,8 +15505,10 @@ var CaddyComponentManager = class {
|
|
|
15420
15505
|
await chmod(executable, 448);
|
|
15421
15506
|
if (bytes.byteLength !== artifact.executableBytes || await digest(executable) !== artifact.executableSha256) throw new Error("caddy_executable_hash_mismatch");
|
|
15422
15507
|
await this.verifyMetadata(executable, artifact);
|
|
15508
|
+
this.assertOpen();
|
|
15423
15509
|
try {
|
|
15424
15510
|
const entry = await lstat(this.componentStorage);
|
|
15511
|
+
this.assertOpen();
|
|
15425
15512
|
if (!entry.isDirectory() || entry.isSymbolicLink()) throw new Error("caddy_path_invalid");
|
|
15426
15513
|
await renameCaddyPath(this.stateDirectory, this.componentStorage, backup);
|
|
15427
15514
|
previous = true;
|
|
@@ -15429,6 +15516,7 @@ var CaddyComponentManager = class {
|
|
|
15429
15516
|
if (error.code !== "ENOENT") throw error;
|
|
15430
15517
|
}
|
|
15431
15518
|
try {
|
|
15519
|
+
this.assertOpen();
|
|
15432
15520
|
await this.promote(candidate, this.componentStorage);
|
|
15433
15521
|
promoted = true;
|
|
15434
15522
|
} catch (error) {
|
|
@@ -15446,21 +15534,29 @@ var CaddyComponentManager = class {
|
|
|
15446
15534
|
this.errorCode = void 0;
|
|
15447
15535
|
} catch (error) {
|
|
15448
15536
|
failed = true;
|
|
15537
|
+
operationError = error;
|
|
15449
15538
|
throw error;
|
|
15450
15539
|
} finally {
|
|
15451
|
-
|
|
15540
|
+
const cleanupErrors = [];
|
|
15452
15541
|
for (const path of [
|
|
15453
15542
|
staging,
|
|
15454
15543
|
candidate,
|
|
15455
15544
|
...previous && promoted ? [backup] : []
|
|
15456
|
-
])
|
|
15457
|
-
|
|
15458
|
-
|
|
15459
|
-
|
|
15545
|
+
]) {
|
|
15546
|
+
this.cleanupPaths.add(path);
|
|
15547
|
+
try {
|
|
15548
|
+
await removeCaddyTree(this.stateDirectory, path);
|
|
15549
|
+
this.cleanupPaths.delete(path);
|
|
15550
|
+
} catch (error) {
|
|
15551
|
+
cleanupErrors.push(error);
|
|
15552
|
+
}
|
|
15553
|
+
}
|
|
15554
|
+
if (cleanupErrors.length > 0) {
|
|
15555
|
+
if (!failed && cleanupErrors.length === 1) throw cleanupErrors[0];
|
|
15556
|
+
throw new AggregateError([...failed ? [operationError] : [], ...cleanupErrors], "caddy_component_cleanup_failed", failed ? { cause: operationError } : void 0);
|
|
15460
15557
|
}
|
|
15461
|
-
if (!failed && cleanupError !== void 0) throw cleanupError;
|
|
15462
15558
|
}
|
|
15463
|
-
});
|
|
15559
|
+
}).then(() => this.status());
|
|
15464
15560
|
}
|
|
15465
15561
|
/** Remove optional component files; callers stop the controller and purge runtime configuration separately. */
|
|
15466
15562
|
purge() {
|
|
@@ -15469,18 +15565,57 @@ var CaddyComponentManager = class {
|
|
|
15469
15565
|
await removeCaddyTree(this.stateDirectory, this.stagingRoot);
|
|
15470
15566
|
this.installed = false;
|
|
15471
15567
|
this.errorCode = this.artifact === void 0 ? "caddy_component_unavailable" : void 0;
|
|
15568
|
+
}).then(() => this.status());
|
|
15569
|
+
}
|
|
15570
|
+
/**
|
|
15571
|
+
* Permanently stop admission and abort downloads, then await admitted work and cleanup.
|
|
15572
|
+
* An already started promotion/purge settles normally; cancellation does not undo committed bytes.
|
|
15573
|
+
* A downloader that ignores abort remains owned until its promise settles. Failed temporary cleanup
|
|
15574
|
+
* is retried here; a failed close can be called again without reopening admission or deleting recovery backups.
|
|
15575
|
+
*/
|
|
15576
|
+
close() {
|
|
15577
|
+
if (this.closing !== void 0) return this.closing;
|
|
15578
|
+
this.closed = true;
|
|
15579
|
+
if (this.download !== void 0) {
|
|
15580
|
+
clearTimeout(this.download.timeout);
|
|
15581
|
+
this.download.controller.abort(/* @__PURE__ */ new Error("caddy_component_closed"));
|
|
15582
|
+
}
|
|
15583
|
+
const task = this.queue.then(async () => {
|
|
15584
|
+
const errors = [];
|
|
15585
|
+
for (const path of this.cleanupPaths) try {
|
|
15586
|
+
await removeCaddyTree(this.stateDirectory, path);
|
|
15587
|
+
this.cleanupPaths.delete(path);
|
|
15588
|
+
} catch (error) {
|
|
15589
|
+
errors.push(error);
|
|
15590
|
+
}
|
|
15591
|
+
if (errors.length === 1) throw errors[0];
|
|
15592
|
+
if (errors.length > 1) throw new AggregateError(errors, "caddy_component_cleanup_failed");
|
|
15593
|
+
});
|
|
15594
|
+
this.closing = task;
|
|
15595
|
+
task.catch(() => {
|
|
15596
|
+
if (this.closing === task) this.closing = void 0;
|
|
15472
15597
|
});
|
|
15598
|
+
return task;
|
|
15473
15599
|
}
|
|
15474
15600
|
async verifyMetadata(executable, artifact) {
|
|
15601
|
+
this.assertOpen();
|
|
15475
15602
|
const info = await this.inspectExecutable(executable);
|
|
15603
|
+
this.assertOpen();
|
|
15476
15604
|
if (!info.version.startsWith("v" + artifact.version + " ")) throw new Error("caddy_executable_version_mismatch");
|
|
15477
15605
|
const nonstandard = info.modules.split(/\r?\n/u).filter((line) => line.startsWith("dns.providers."));
|
|
15478
15606
|
if (nonstandard.length !== 1 || nonstandard[0] !== "dns.providers.tencentcloud " + artifact.dnsPluginVersion || !info.modules.includes("Non-standard modules: 1")) throw new Error("caddy_executable_modules_mismatch");
|
|
15479
15607
|
}
|
|
15608
|
+
assertOpen() {
|
|
15609
|
+
if (this.closed) throw new Error("caddy_component_closed");
|
|
15610
|
+
}
|
|
15480
15611
|
enqueue(operation) {
|
|
15481
|
-
|
|
15612
|
+
if (this.closed) return Promise.reject(/* @__PURE__ */ new Error("caddy_component_closed"));
|
|
15613
|
+
const task = this.queue.then(() => {
|
|
15614
|
+
this.assertOpen();
|
|
15615
|
+
return operation();
|
|
15616
|
+
});
|
|
15482
15617
|
this.queue = task.then(() => void 0, () => void 0);
|
|
15483
|
-
return task
|
|
15618
|
+
return task;
|
|
15484
15619
|
}
|
|
15485
15620
|
};
|
|
15486
15621
|
const MAX_CONFIG_BYTES = 16384;
|
|
@@ -15839,6 +15974,7 @@ var CaddyController = class {
|
|
|
15839
15974
|
} catch {}
|
|
15840
15975
|
}
|
|
15841
15976
|
async start() {
|
|
15977
|
+
if (this.child !== void 0 || this.gatewayValue !== void 0) await this.stop();
|
|
15842
15978
|
const generation = ++this.generation;
|
|
15843
15979
|
const startup = new AbortController();
|
|
15844
15980
|
this.startupAbort = startup;
|
|
@@ -15888,12 +16024,19 @@ var CaddyController = class {
|
|
|
15888
16024
|
"--adapter",
|
|
15889
16025
|
"caddyfile"
|
|
15890
16026
|
], environment);
|
|
15891
|
-
|
|
16027
|
+
const ownedChild = {
|
|
16028
|
+
process: child,
|
|
16029
|
+
closed: false
|
|
16030
|
+
};
|
|
16031
|
+
this.child = ownedChild;
|
|
16032
|
+
child.once("close", () => {
|
|
16033
|
+
ownedChild.closed = true;
|
|
16034
|
+
});
|
|
15892
16035
|
child.stdout.resume();
|
|
15893
16036
|
child.stderr.resume();
|
|
15894
16037
|
child.stdin.end();
|
|
15895
16038
|
const failed = (code) => {
|
|
15896
|
-
if (generation !== this.generation || this.child !==
|
|
16039
|
+
if (generation !== this.generation || this.child !== ownedChild) return;
|
|
15897
16040
|
childFailure = code;
|
|
15898
16041
|
startup.abort();
|
|
15899
16042
|
if (this.latest.state === "ready") this.enqueue(async () => {
|
|
@@ -15968,20 +16111,49 @@ var CaddyController = class {
|
|
|
15968
16111
|
this.startupAbort?.abort();
|
|
15969
16112
|
await this.stopResources();
|
|
15970
16113
|
}
|
|
16114
|
+
waitForChildClose(child) {
|
|
16115
|
+
if (child.closed) return Promise.resolve();
|
|
16116
|
+
return new Promise((resolve, reject) => {
|
|
16117
|
+
const finish = (error) => {
|
|
16118
|
+
clearTimeout(timer);
|
|
16119
|
+
child.process.removeListener("close", closed);
|
|
16120
|
+
if (error === void 0) resolve();
|
|
16121
|
+
else reject(error);
|
|
16122
|
+
};
|
|
16123
|
+
const closed = () => {
|
|
16124
|
+
finish();
|
|
16125
|
+
};
|
|
16126
|
+
const timer = setTimeout(() => {
|
|
16127
|
+
finish(/* @__PURE__ */ new Error("caddy_process_close_timeout"));
|
|
16128
|
+
}, 1500);
|
|
16129
|
+
timer.unref();
|
|
16130
|
+
child.process.once("close", closed);
|
|
16131
|
+
});
|
|
16132
|
+
}
|
|
15971
16133
|
async stopResources() {
|
|
15972
16134
|
const child = this.child;
|
|
15973
16135
|
const gateway = this.gatewayValue;
|
|
15974
|
-
|
|
15975
|
-
|
|
15976
|
-
|
|
15977
|
-
|
|
15978
|
-
|
|
15979
|
-
|
|
15980
|
-
|
|
15981
|
-
|
|
15982
|
-
if (
|
|
15983
|
-
|
|
15984
|
-
|
|
16136
|
+
try {
|
|
16137
|
+
await settleRemoteResources([async () => {
|
|
16138
|
+
if (child !== void 0) {
|
|
16139
|
+
await (this.options.terminateProcess ?? terminateRemoteProcess)(child.process);
|
|
16140
|
+
await this.waitForChildClose(child);
|
|
16141
|
+
if (this.child === child) this.child = void 0;
|
|
16142
|
+
}
|
|
16143
|
+
}, async () => {
|
|
16144
|
+
if (gateway !== void 0) {
|
|
16145
|
+
await gateway.close();
|
|
16146
|
+
if (this.gatewayValue === gateway) this.gatewayValue = void 0;
|
|
16147
|
+
}
|
|
16148
|
+
}], "caddy cleanup failed");
|
|
16149
|
+
} catch (error) {
|
|
16150
|
+
this.publish({
|
|
16151
|
+
enabled: this.enabled,
|
|
16152
|
+
state: "error",
|
|
16153
|
+
errorCode: "caddy_cleanup_failed"
|
|
16154
|
+
});
|
|
16155
|
+
throw error;
|
|
16156
|
+
}
|
|
15985
16157
|
}
|
|
15986
16158
|
};
|
|
15987
16159
|
//#endregion
|
|
@@ -16691,23 +16863,75 @@ async function readInstallationId(file) {
|
|
|
16691
16863
|
await restrictPrivateFile(file);
|
|
16692
16864
|
return contents.slice(0, -1);
|
|
16693
16865
|
}
|
|
16694
|
-
/** Keep one random plugin identity across setup changes, restarts, and FRP resets. */
|
|
16866
|
+
/** Keep one random plugin identity across setup changes, restarts, and FRP resets; creation requires hard-link support in the state directory. */
|
|
16695
16867
|
async function loadOrCreateInstallationId(file) {
|
|
16696
16868
|
if (!isAbsolute(file)) throw new Error("installation ID path must be absolute");
|
|
16697
16869
|
const target = resolve(file);
|
|
16698
|
-
|
|
16699
|
-
|
|
16700
|
-
mode: 448
|
|
16701
|
-
});
|
|
16870
|
+
const directory = dirname(target);
|
|
16871
|
+
await ensureManagedDirectory(directory, directory, "installation_id");
|
|
16702
16872
|
try {
|
|
16703
|
-
await
|
|
16704
|
-
flag: "wx",
|
|
16705
|
-
mode: 384
|
|
16706
|
-
});
|
|
16873
|
+
return await readInstallationId(target);
|
|
16707
16874
|
} catch (error) {
|
|
16708
|
-
if (error.code !== "
|
|
16875
|
+
if (error.code !== "ENOENT") throw error;
|
|
16709
16876
|
}
|
|
16710
|
-
|
|
16877
|
+
const temporary = join(directory, `.${basename(target)}.${randomBytes(12).toString("hex")}.tmp`);
|
|
16878
|
+
let created = false;
|
|
16879
|
+
let failure;
|
|
16880
|
+
try {
|
|
16881
|
+
await assertManagedParents(directory, temporary, "installation_id");
|
|
16882
|
+
const handle = await open(temporary, "wx", 384);
|
|
16883
|
+
created = true;
|
|
16884
|
+
let writeFailure;
|
|
16885
|
+
try {
|
|
16886
|
+
await handle.writeFile(`${randomBytes(32).toString("hex")}\n`, "utf8");
|
|
16887
|
+
} catch (error) {
|
|
16888
|
+
writeFailure = error;
|
|
16889
|
+
throw error;
|
|
16890
|
+
} finally {
|
|
16891
|
+
try {
|
|
16892
|
+
await handle.close();
|
|
16893
|
+
} catch (closeError) {
|
|
16894
|
+
if (writeFailure !== void 0) throw new AggregateError([writeFailure, closeError], "installation ID candidate write and close failed");
|
|
16895
|
+
throw closeError;
|
|
16896
|
+
}
|
|
16897
|
+
}
|
|
16898
|
+
await restrictPrivateFile(temporary);
|
|
16899
|
+
await assertManagedParents(directory, target, "installation_id");
|
|
16900
|
+
try {
|
|
16901
|
+
await link(temporary, target);
|
|
16902
|
+
} catch (error) {
|
|
16903
|
+
if (error.code !== "EEXIST") throw new Error("installation ID publication failed; the state filesystem must support hard links", { cause: error });
|
|
16904
|
+
}
|
|
16905
|
+
return await readInstallationId(target);
|
|
16906
|
+
} catch (error) {
|
|
16907
|
+
failure = error;
|
|
16908
|
+
throw error;
|
|
16909
|
+
} finally {
|
|
16910
|
+
if (created) try {
|
|
16911
|
+
await assertManagedParents(directory, temporary, "installation_id");
|
|
16912
|
+
await unlink(temporary);
|
|
16913
|
+
} catch (cleanupError) {
|
|
16914
|
+
if (failure !== void 0) throw new AggregateError([failure, cleanupError], "installation ID publication and temporary cleanup failed");
|
|
16915
|
+
throw cleanupError;
|
|
16916
|
+
}
|
|
16917
|
+
}
|
|
16918
|
+
}
|
|
16919
|
+
//#endregion
|
|
16920
|
+
//#region src/gateway-identity.ts
|
|
16921
|
+
/**
|
|
16922
|
+
* Use the persisted installation identity for an unpinned, automatically identified entry.
|
|
16923
|
+
* @param config - Validated gateway configuration containing the legacy identity.
|
|
16924
|
+
* @param installationId - Confirmed random identity read from this installation's state.
|
|
16925
|
+
* @param explicitIdentity - Whether the operator supplied the gateway identity.
|
|
16926
|
+
* @returns Configuration retaining certificate/operator identities or the new and legacy public identities.
|
|
16927
|
+
*/
|
|
16928
|
+
function withInstallationIdentity(config, installationId, explicitIdentity) {
|
|
16929
|
+
if (config.pairingCaFile !== void 0 || explicitIdentity) return config;
|
|
16930
|
+
return Object.freeze({
|
|
16931
|
+
...config,
|
|
16932
|
+
instanceId: installationId,
|
|
16933
|
+
legacyInstanceId: config.instanceId
|
|
16934
|
+
});
|
|
16711
16935
|
}
|
|
16712
16936
|
//#endregion
|
|
16713
16937
|
//#region src/lan-setup.ts
|
|
@@ -16955,7 +17179,7 @@ async function stableInstanceId(loaded, template) {
|
|
|
16955
17179
|
if (loaded.kind !== "managed") return loaded.config.instanceId ?? template.instanceId;
|
|
16956
17180
|
return new X509Certificate(await readFile(loaded.setup.tls.caCertFile)).fingerprint256.replaceAll(":", "").toLowerCase();
|
|
16957
17181
|
}
|
|
16958
|
-
function remoteGatewayConfig(template, publicOrigin, stateFile, instanceId, listenPort = 0) {
|
|
17182
|
+
function remoteGatewayConfig(template, publicOrigin, stateFile, instanceId, listenPort = 0, legacyInstanceId) {
|
|
16959
17183
|
const origin = new URL(publicOrigin);
|
|
16960
17184
|
if (origin.protocol !== "https:" || origin.username !== "" || origin.password !== "" || origin.pathname !== "/" || origin.search !== "" || origin.hash !== "") throw new Error("remote public origin must be an HTTPS origin");
|
|
16961
17185
|
const publicAuthority = origin.port === "" ? `${origin.hostname}:443` : origin.host;
|
|
@@ -16968,6 +17192,7 @@ function remoteGatewayConfig(template, publicOrigin, stateFile, instanceId, list
|
|
|
16968
17192
|
allowedCidrs: Object.freeze([parseCidr("127.0.0.0/8")]),
|
|
16969
17193
|
stateFile,
|
|
16970
17194
|
instanceId,
|
|
17195
|
+
...legacyInstanceId === void 0 ? {} : { legacyInstanceId },
|
|
16971
17196
|
tls: Object.freeze({ mode: "disabled" }),
|
|
16972
17197
|
publicTls: true,
|
|
16973
17198
|
discovery: false
|
|
@@ -17028,11 +17253,11 @@ async function readFrpIngressTrustAnchor(settings, stateFile, ownedRoot = dirnam
|
|
|
17028
17253
|
}
|
|
17029
17254
|
}
|
|
17030
17255
|
/** Reuse remote HTTPS policy while binding a separately validated private HTTP origin. */
|
|
17031
|
-
function originGatewayConfig(template, settings, stateFile, instanceId, listenPort = settings.listenPort) {
|
|
17256
|
+
function originGatewayConfig(template, settings, stateFile, instanceId, listenPort = settings.listenPort, legacyInstanceId) {
|
|
17032
17257
|
const validated = parseOriginSettings(settings);
|
|
17033
17258
|
if (listenPort !== 0) validateOriginListenPort(listenPort);
|
|
17034
17259
|
return Object.freeze({
|
|
17035
|
-
...remoteGatewayConfig(template, validated.publicOrigin, stateFile, instanceId, listenPort),
|
|
17260
|
+
...remoteGatewayConfig(template, validated.publicOrigin, stateFile, instanceId, listenPort, legacyInstanceId),
|
|
17036
17261
|
listenHost: validated.listenHost,
|
|
17037
17262
|
allowedCidrs: Object.freeze(validated.allowedCidrs.map(parseCidr))
|
|
17038
17263
|
});
|
|
@@ -17115,6 +17340,7 @@ async function apply(ctx, config) {
|
|
|
17115
17340
|
const frpComponent = new FrpComponentManager({ stateDirectory });
|
|
17116
17341
|
await frpComponent.initialize();
|
|
17117
17342
|
const frpIdentity = await loadOrCreateInstallationId(join(stateDirectory, "installation-id"));
|
|
17343
|
+
const remoteInstanceId = loaded.kind === "managed" ? frpIdentity : loaded.config.instanceId ?? frpIdentity;
|
|
17118
17344
|
const frpConfig = new FrpConfigStore(join(remoteDirectory, "frp", "config"), frpProxyName(frpIdentity), stateDirectory);
|
|
17119
17345
|
await frpConfig.initialize();
|
|
17120
17346
|
const originConfig = new OriginConfigStore(join(remoteDirectory, "origin", "config"), stateDirectory);
|
|
@@ -17166,10 +17392,10 @@ async function apply(ctx, config) {
|
|
|
17166
17392
|
let trustedNetworksWrite = Promise.resolve();
|
|
17167
17393
|
let adminClosing = false;
|
|
17168
17394
|
const startGateway = async (candidateConfig) => {
|
|
17169
|
-
const resolved = parseGatewayConfig({
|
|
17395
|
+
const resolved = withInstallationIdentity(parseGatewayConfig({
|
|
17170
17396
|
...candidateConfig,
|
|
17171
17397
|
upstreamOrigin: template.upstreamOrigin.origin
|
|
17172
|
-
});
|
|
17398
|
+
}), frpIdentity, candidateConfig.instanceId !== void 0);
|
|
17173
17399
|
const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths, (source, code) => {
|
|
17174
17400
|
logger.warn("%s discovery degraded while DSH remains available: %s", source, code);
|
|
17175
17401
|
}, clientModulePreferences);
|
|
@@ -17227,7 +17453,7 @@ async function apply(ctx, config) {
|
|
|
17227
17453
|
}
|
|
17228
17454
|
}
|
|
17229
17455
|
const createRemoteGateway = async (publicOrigin, listenPort = 0) => {
|
|
17230
|
-
const resolved = remoteGatewayConfig(template, publicOrigin, remoteDeviceFile,
|
|
17456
|
+
const resolved = remoteGatewayConfig(template, publicOrigin, remoteDeviceFile, remoteInstanceId, listenPort, instanceId);
|
|
17231
17457
|
const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths, void 0, clientModulePreferences);
|
|
17232
17458
|
await candidate.start();
|
|
17233
17459
|
return candidate;
|
|
@@ -17254,7 +17480,7 @@ async function apply(ctx, config) {
|
|
|
17254
17480
|
return candidate;
|
|
17255
17481
|
};
|
|
17256
17482
|
const createOriginGateway = async (settings) => {
|
|
17257
|
-
const resolved = originGatewayConfig(template, settings, remoteDeviceFile, instanceId);
|
|
17483
|
+
const resolved = originGatewayConfig(template, settings, remoteDeviceFile, remoteInstanceId, settings.listenPort, instanceId);
|
|
17258
17484
|
const candidate = new MobileAccessGateway(resolved, new JsonDeviceStore(resolved.stateFile, resolved.maxDevices), mobileAccess, upstreamLoginUrl, webSocketPaths, blockedUpgradePaths, void 0, clientModulePreferences);
|
|
17259
17485
|
try {
|
|
17260
17486
|
await candidate.start();
|
|
@@ -17273,7 +17499,7 @@ async function apply(ctx, config) {
|
|
|
17273
17499
|
store: createVolatileRemoteControlStore(),
|
|
17274
17500
|
component: caddyComponent,
|
|
17275
17501
|
config: caddyConfig,
|
|
17276
|
-
instanceId,
|
|
17502
|
+
instanceId: remoteInstanceId,
|
|
17277
17503
|
createGateway: (settings) => createRemoteGateway(settings.publicOrigin)
|
|
17278
17504
|
});
|
|
17279
17505
|
const originController = new ManagedOriginController({
|
|
@@ -17313,7 +17539,7 @@ async function apply(ctx, config) {
|
|
|
17313
17539
|
store: frpStore,
|
|
17314
17540
|
executable: frpComponent.executable,
|
|
17315
17541
|
config: frpConfig,
|
|
17316
|
-
instanceId,
|
|
17542
|
+
instanceId: remoteInstanceId,
|
|
17317
17543
|
createGateway: createFrpGateway,
|
|
17318
17544
|
/**
|
|
17319
17545
|
* Hand the start-up self-check the CA the app itself pins.
|
|
@@ -17943,6 +18169,11 @@ async function apply(ctx, config) {
|
|
|
17943
18169
|
adminClosing = true;
|
|
17944
18170
|
unregister();
|
|
17945
18171
|
};
|
|
18172
|
+
const drainRemoteWork = async () => {
|
|
18173
|
+
const draining = remoteProviders.close();
|
|
18174
|
+
caddyController.cancelPendingStart();
|
|
18175
|
+
await settleCleanupSteps([() => caddyComponent.close(), () => draining]);
|
|
18176
|
+
};
|
|
17946
18177
|
const disposeMobileCommand = ctx.commands.register({
|
|
17947
18178
|
name: "mobile",
|
|
17948
18179
|
description: "定制手机界面或添加电脑能力",
|
|
@@ -18009,6 +18240,7 @@ async function apply(ctx, config) {
|
|
|
18009
18240
|
try {
|
|
18010
18241
|
await settleCleanupSteps([
|
|
18011
18242
|
stopAdmin,
|
|
18243
|
+
drainRemoteWork,
|
|
18012
18244
|
() => trustedNetworksWrite,
|
|
18013
18245
|
disposeMobileCommand,
|
|
18014
18246
|
disposeTaskEvents,
|
|
@@ -18028,6 +18260,7 @@ async function apply(ctx, config) {
|
|
|
18028
18260
|
return async () => {
|
|
18029
18261
|
await settleCleanupSteps([
|
|
18030
18262
|
stopAdmin,
|
|
18263
|
+
drainRemoteWork,
|
|
18031
18264
|
() => trustedNetworksWrite,
|
|
18032
18265
|
disposeMobileCommand,
|
|
18033
18266
|
disposeTaskEvents,
|