mihomo-cli 2.10.0 → 3.2.0
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 +68 -0
- package/README.md +33 -1
- package/dist/index.js +1916 -1406
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,73 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/paths.ts
|
|
4
|
-
import fs from "fs";
|
|
5
|
-
import os from "os";
|
|
6
|
-
import path from "path";
|
|
7
|
-
function getUserDataDir() {
|
|
8
|
-
if (process.env.MIHOMO_CLI_DIR) {
|
|
9
|
-
return process.env.MIHOMO_CLI_DIR;
|
|
10
|
-
}
|
|
11
|
-
return path.join(os.homedir(), ".mihomo-cli");
|
|
12
|
-
}
|
|
13
|
-
var USER_DATA_DIR = getUserDataDir();
|
|
14
|
-
var DIRS = {
|
|
15
|
-
kernel: path.join(USER_DATA_DIR, "kernel"),
|
|
16
|
-
subscriptions: path.join(USER_DATA_DIR, "subscriptions"),
|
|
17
|
-
logs: path.join(USER_DATA_DIR, "logs"),
|
|
18
|
-
data: path.join(USER_DATA_DIR, "data"),
|
|
19
|
-
runtime: path.join(USER_DATA_DIR, "runtime")
|
|
20
|
-
};
|
|
21
|
-
var PATHS = {
|
|
22
|
-
mihomoBinary: path.join(DIRS.kernel, "mihomo"),
|
|
23
|
-
settingsFile: path.join(USER_DATA_DIR, "settings.json"),
|
|
24
|
-
subscriptionsCacheFile: path.join(DIRS.subscriptions, "cache.json"),
|
|
25
|
-
configFile: path.join(DIRS.runtime, "config.yaml"),
|
|
26
|
-
logFile: path.join(DIRS.logs, "mihomo.log"),
|
|
27
|
-
pidFile: path.join(DIRS.runtime, "pid"),
|
|
28
|
-
configStage1Subscription: path.join(DIRS.runtime, "1.subscription.yaml"),
|
|
29
|
-
configStage2Overwrite: path.join(DIRS.runtime, "2.overwrite.yaml"),
|
|
30
|
-
configStage3System: path.join(DIRS.runtime, "3.system.yaml")
|
|
31
|
-
};
|
|
32
|
-
var DIRECTORY_TARGETS = {
|
|
33
|
-
root: { path: null, label: "\u6839\u76EE\u5F55" },
|
|
34
|
-
subs: { path: DIRS.subscriptions, label: "\u8BA2\u9605\u76EE\u5F55" },
|
|
35
|
-
logs: { path: DIRS.logs, label: "\u65E5\u5FD7\u76EE\u5F55" },
|
|
36
|
-
data: { path: DIRS.data, label: "mihomo \u6570\u636E\u76EE\u5F55" },
|
|
37
|
-
runtime: { path: DIRS.runtime, label: "\u8FD0\u884C\u65F6\u76EE\u5F55" },
|
|
38
|
-
kernel: { path: DIRS.kernel, label: "\u5185\u6838\u76EE\u5F55" }
|
|
39
|
-
};
|
|
40
|
-
function ensureDirs() {
|
|
41
|
-
for (const dir of Object.values(DIRS)) {
|
|
42
|
-
if (!fs.existsSync(dir)) {
|
|
43
|
-
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
function atomicWriteFileSync(filePath, content, options) {
|
|
48
|
-
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
49
|
-
try {
|
|
50
|
-
fs.writeFileSync(tmp, content, options);
|
|
51
|
-
fs.renameSync(tmp, filePath);
|
|
52
|
-
} catch (e) {
|
|
53
|
-
try {
|
|
54
|
-
fs.unlinkSync(tmp);
|
|
55
|
-
} catch {
|
|
56
|
-
}
|
|
57
|
-
throw e;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function rmrf(dir) {
|
|
61
|
-
fs.rmSync(dir, { recursive: true, force: true });
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// src/process.ts
|
|
65
|
-
import { spawn, spawnSync as spawnSync3 } from "child_process";
|
|
66
|
-
import fs5 from "fs";
|
|
67
|
-
import path3 from "path";
|
|
68
|
-
|
|
69
3
|
// src/config.ts
|
|
70
|
-
import { spawnSync } from "child_process";
|
|
4
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
71
5
|
import fs4 from "fs";
|
|
72
6
|
|
|
73
7
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
@@ -2989,6 +2923,11 @@ var UI_URLS = {
|
|
|
2989
2923
|
dash: "https://metacubex.github.io/metacubexd",
|
|
2990
2924
|
yacd: "https://yacd.metacubex.one"
|
|
2991
2925
|
};
|
|
2926
|
+
var LAUNCH_DAEMON_LABEL = process.env.MIHOMO_CLI_DAEMON_LABEL || "com.mihomo-cli.daemon";
|
|
2927
|
+
var CONTROLLER_PORT = 9090;
|
|
2928
|
+
var CONTROLLER_ADDR = `127.0.0.1:${CONTROLLER_PORT}`;
|
|
2929
|
+
var CONTROLLER_BASE_URL = `http://${CONTROLLER_ADDR}`;
|
|
2930
|
+
var TEST_CONTROLLER_ADDR = "127.0.0.1:29090";
|
|
2992
2931
|
var TUN_CONFIG = {
|
|
2993
2932
|
tun: {
|
|
2994
2933
|
enable: true,
|
|
@@ -3002,14 +2941,14 @@ var TUN_CONFIG = {
|
|
|
3002
2941
|
var TEST_CONFIG = {
|
|
3003
2942
|
"mixed-port": 27890,
|
|
3004
2943
|
"allow-lan": false,
|
|
3005
|
-
"external-controller":
|
|
2944
|
+
"external-controller": TEST_CONTROLLER_ADDR,
|
|
3006
2945
|
"log-level": "error",
|
|
3007
2946
|
"geodata-mode": true
|
|
3008
2947
|
};
|
|
3009
2948
|
var BASE_CONFIG = {
|
|
3010
2949
|
"mixed-port": 7890,
|
|
3011
2950
|
"allow-lan": false,
|
|
3012
|
-
"external-controller":
|
|
2951
|
+
"external-controller": CONTROLLER_ADDR,
|
|
3013
2952
|
"unified-delay": true,
|
|
3014
2953
|
"tcp-concurrent": true,
|
|
3015
2954
|
"geo-auto-update": true,
|
|
@@ -3038,10 +2977,74 @@ var AUTO_CLEAN_THRESHOLD_GITHUB = 50;
|
|
|
3038
2977
|
|
|
3039
2978
|
// src/overwrite.ts
|
|
3040
2979
|
import fs3 from "fs";
|
|
3041
|
-
import
|
|
2980
|
+
import path3 from "path";
|
|
2981
|
+
|
|
2982
|
+
// src/paths.ts
|
|
2983
|
+
import fs from "fs";
|
|
2984
|
+
import os from "os";
|
|
2985
|
+
import path from "path";
|
|
2986
|
+
function getUserDataDir() {
|
|
2987
|
+
if (process.env.MIHOMO_CLI_DIR) {
|
|
2988
|
+
return process.env.MIHOMO_CLI_DIR;
|
|
2989
|
+
}
|
|
2990
|
+
return path.join(os.homedir(), ".mihomo-cli");
|
|
2991
|
+
}
|
|
2992
|
+
var USER_DATA_DIR = getUserDataDir();
|
|
2993
|
+
var DIRS = {
|
|
2994
|
+
kernel: path.join(USER_DATA_DIR, "kernel"),
|
|
2995
|
+
subscriptions: path.join(USER_DATA_DIR, "subscriptions"),
|
|
2996
|
+
logs: path.join(USER_DATA_DIR, "logs"),
|
|
2997
|
+
data: path.join(USER_DATA_DIR, "data"),
|
|
2998
|
+
runtime: path.join(USER_DATA_DIR, "runtime")
|
|
2999
|
+
};
|
|
3000
|
+
var PATHS = {
|
|
3001
|
+
mihomoBinary: path.join(DIRS.kernel, "mihomo"),
|
|
3002
|
+
settingsFile: path.join(USER_DATA_DIR, "settings.json"),
|
|
3003
|
+
subscriptionsCacheFile: path.join(DIRS.subscriptions, "cache.json"),
|
|
3004
|
+
configFile: path.join(DIRS.runtime, "config.yaml"),
|
|
3005
|
+
logFile: path.join(DIRS.logs, "mihomo.log"),
|
|
3006
|
+
pidFile: path.join(DIRS.runtime, "pid"),
|
|
3007
|
+
configStage1Subscription: path.join(DIRS.runtime, "1.subscription.yaml"),
|
|
3008
|
+
configStage2Overwrite: path.join(DIRS.runtime, "2.overwrite.yaml"),
|
|
3009
|
+
configStage3System: path.join(DIRS.runtime, "3.system.yaml"),
|
|
3010
|
+
// launchd LaunchDaemon plist 位于系统级 /Library/LaunchDaemons/,root:wheel 拥有,与 homedir / MIHOMO_CLI_DIR 无关
|
|
3011
|
+
launchDaemonPlist: path.join("/Library/LaunchDaemons", `${LAUNCH_DAEMON_LABEL}.plist`)
|
|
3012
|
+
};
|
|
3013
|
+
var DIRECTORY_TARGETS = {
|
|
3014
|
+
root: { path: null, label: "\u6839\u76EE\u5F55" },
|
|
3015
|
+
subs: { path: DIRS.subscriptions, label: "\u8BA2\u9605\u76EE\u5F55" },
|
|
3016
|
+
logs: { path: DIRS.logs, label: "\u65E5\u5FD7\u76EE\u5F55" },
|
|
3017
|
+
data: { path: DIRS.data, label: "mihomo \u6570\u636E\u76EE\u5F55" },
|
|
3018
|
+
runtime: { path: DIRS.runtime, label: "\u8FD0\u884C\u65F6\u76EE\u5F55" },
|
|
3019
|
+
kernel: { path: DIRS.kernel, label: "\u5185\u6838\u76EE\u5F55" }
|
|
3020
|
+
};
|
|
3021
|
+
function ensureDirs() {
|
|
3022
|
+
for (const dir of Object.values(DIRS)) {
|
|
3023
|
+
if (!fs.existsSync(dir)) {
|
|
3024
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
}
|
|
3028
|
+
function atomicWriteFileSync(filePath, content, options) {
|
|
3029
|
+
const tmp = `${filePath}.${process.pid}.tmp`;
|
|
3030
|
+
try {
|
|
3031
|
+
fs.writeFileSync(tmp, content, options);
|
|
3032
|
+
fs.renameSync(tmp, filePath);
|
|
3033
|
+
} catch (e) {
|
|
3034
|
+
try {
|
|
3035
|
+
fs.unlinkSync(tmp);
|
|
3036
|
+
} catch {
|
|
3037
|
+
}
|
|
3038
|
+
throw e;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
function rmrf(dir) {
|
|
3042
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
3043
|
+
}
|
|
3042
3044
|
|
|
3043
3045
|
// src/settings.ts
|
|
3044
3046
|
import fs2 from "fs";
|
|
3047
|
+
import path2 from "path";
|
|
3045
3048
|
var settingsCache = null;
|
|
3046
3049
|
function readSettings() {
|
|
3047
3050
|
if (settingsCache !== null) return settingsCache;
|
|
@@ -3145,12 +3148,10 @@ function addSubscription(url, name = "default") {
|
|
|
3145
3148
|
validateSubscriptionName(name);
|
|
3146
3149
|
const settings = readSettings();
|
|
3147
3150
|
const subs = [...settings.subscriptions || []];
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
subs[existingIndex] = { name, url };
|
|
3151
|
-
} else {
|
|
3152
|
-
subs.push({ name, url });
|
|
3151
|
+
if (subs.some((s) => s.name === name)) {
|
|
3152
|
+
throw new Error(`\u8BA2\u9605 "${name}" \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E2A\u540D\u79F0\uFF08mihomo sub add <url> <\u540D\u79F0>\uFF09\uFF0C\u6216\u5148\u5220\u9664\uFF08mihomo sub remove ${name}\uFF09`);
|
|
3153
3153
|
}
|
|
3154
|
+
subs.push({ name, url });
|
|
3154
3155
|
const updates = { subscriptions: subs };
|
|
3155
3156
|
if (!settings.active_subscription && subs.length === 1) {
|
|
3156
3157
|
updates.active_subscription = name;
|
|
@@ -3175,7 +3176,10 @@ function removeSubscription(name) {
|
|
|
3175
3176
|
delete cache[name];
|
|
3176
3177
|
writeSubscriptionCache(cache);
|
|
3177
3178
|
}
|
|
3178
|
-
|
|
3179
|
+
try {
|
|
3180
|
+
fs2.rmSync(getSubscriptionRawConfigPath(name), { force: true });
|
|
3181
|
+
} catch {
|
|
3182
|
+
}
|
|
3179
3183
|
return switchedTo;
|
|
3180
3184
|
}
|
|
3181
3185
|
function setDefaultSubscription(name) {
|
|
@@ -3188,7 +3192,10 @@ function setDefaultSubscription(name) {
|
|
|
3188
3192
|
return true;
|
|
3189
3193
|
}
|
|
3190
3194
|
function getSubscriptionRawConfigPath(subName) {
|
|
3191
|
-
|
|
3195
|
+
if (!SAFE_NAME_RE.test(subName)) {
|
|
3196
|
+
throw new Error(`\u8BA2\u9605\u540D\u79F0\u65E0\u6548: "${subName}"`);
|
|
3197
|
+
}
|
|
3198
|
+
return path2.join(DIRS.subscriptions, `${subName}.yaml`);
|
|
3192
3199
|
}
|
|
3193
3200
|
function saveSubscriptionRawConfig(subName, content) {
|
|
3194
3201
|
ensureDirs();
|
|
@@ -3286,17 +3293,20 @@ function isOverwriteEnabled() {
|
|
|
3286
3293
|
function setOverwriteEnabled(enabled) {
|
|
3287
3294
|
writeSettings({ overwrite_enabled: enabled });
|
|
3288
3295
|
}
|
|
3296
|
+
function isOverwriteFilename(filename) {
|
|
3297
|
+
return filename === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(filename);
|
|
3298
|
+
}
|
|
3289
3299
|
function loadOverwriteFile() {
|
|
3290
3300
|
const dir = USER_DATA_DIR;
|
|
3291
3301
|
if (!fs3.existsSync(dir)) return [];
|
|
3292
|
-
const files = fs3.readdirSync(dir).filter(
|
|
3302
|
+
const files = fs3.readdirSync(dir).filter(isOverwriteFilename).sort((a, b) => {
|
|
3293
3303
|
if (a === "overwrite.yaml") return -1;
|
|
3294
3304
|
if (b === "overwrite.yaml") return 1;
|
|
3295
3305
|
return a.localeCompare(b);
|
|
3296
3306
|
});
|
|
3297
3307
|
const results = [];
|
|
3298
3308
|
for (const file of files) {
|
|
3299
|
-
const filePath =
|
|
3309
|
+
const filePath = path3.join(dir, file);
|
|
3300
3310
|
try {
|
|
3301
3311
|
const content = fs3.readFileSync(filePath, "utf8");
|
|
3302
3312
|
const parsed = load(content);
|
|
@@ -3333,57 +3343,267 @@ function listOverwriteFile() {
|
|
|
3333
3343
|
};
|
|
3334
3344
|
}
|
|
3335
3345
|
|
|
3336
|
-
// src/
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3346
|
+
// src/utils.ts
|
|
3347
|
+
import { spawnSync } from "child_process";
|
|
3348
|
+
import { createRequire } from "module";
|
|
3349
|
+
var require2 = createRequire(import.meta.url);
|
|
3350
|
+
var pkg = require2("../package.json");
|
|
3351
|
+
var VERSION = pkg.version;
|
|
3352
|
+
var sleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
3353
|
+
var NO_COLOR = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
3354
|
+
function colorize(code, str) {
|
|
3355
|
+
if (NO_COLOR) return String(str);
|
|
3356
|
+
return `${code + String(str)}\x1B[0m`;
|
|
3357
|
+
}
|
|
3358
|
+
var colors = {
|
|
3359
|
+
bold: (s) => colorize("\x1B[1m", s),
|
|
3360
|
+
red: (s) => colorize("\x1B[31m", s),
|
|
3361
|
+
green: (s) => colorize("\x1B[32m", s),
|
|
3362
|
+
yellow: (s) => colorize("\x1B[33m", s),
|
|
3363
|
+
cyan: (s) => colorize("\x1B[36m", s),
|
|
3364
|
+
gray: (s) => colorize("\x1B[90m", s)
|
|
3365
|
+
};
|
|
3366
|
+
function sleepSync(ms) {
|
|
3367
|
+
Atomics.wait(sleepBuf, 0, 0, ms);
|
|
3368
|
+
}
|
|
3369
|
+
function sleep(ms) {
|
|
3370
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3371
|
+
}
|
|
3372
|
+
function escapeRegExp(s) {
|
|
3373
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3374
|
+
}
|
|
3375
|
+
function shellQuote(s) {
|
|
3376
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
3377
|
+
}
|
|
3378
|
+
var TimeoutError = class extends Error {
|
|
3379
|
+
constructor() {
|
|
3380
|
+
super("timeout");
|
|
3381
|
+
this.name = "TimeoutError";
|
|
3340
3382
|
}
|
|
3383
|
+
};
|
|
3384
|
+
function withTimeout(promise, ms) {
|
|
3385
|
+
return new Promise((resolve, reject) => {
|
|
3386
|
+
const timer = setTimeout(() => reject(new TimeoutError()), ms);
|
|
3387
|
+
promise.then(
|
|
3388
|
+
(v) => {
|
|
3389
|
+
clearTimeout(timer);
|
|
3390
|
+
resolve(v);
|
|
3391
|
+
},
|
|
3392
|
+
(e) => {
|
|
3393
|
+
clearTimeout(timer);
|
|
3394
|
+
reject(e);
|
|
3395
|
+
}
|
|
3396
|
+
);
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
function formatBytes(bytes) {
|
|
3400
|
+
if (bytes === void 0 || bytes === null) return "\u672A\u77E5";
|
|
3401
|
+
const num = Number(bytes);
|
|
3402
|
+
if (!Number.isFinite(num) || num < 0) return "\u672A\u77E5";
|
|
3403
|
+
if (num === 0) return "0 B";
|
|
3404
|
+
const k = 1024;
|
|
3405
|
+
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
3406
|
+
const i = Math.min(Math.floor(Math.log(num) / Math.log(k)), sizes.length - 1);
|
|
3407
|
+
return `${parseFloat((num / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
3408
|
+
}
|
|
3409
|
+
function formatTimestamp(ts) {
|
|
3410
|
+
if (ts === void 0 || ts === null) return "\u672A\u77E5";
|
|
3341
3411
|
try {
|
|
3342
|
-
|
|
3343
|
-
if (result != null && typeof result === "object" && !Array.isArray(result)) return result;
|
|
3412
|
+
return new Date(ts * 1e3).toLocaleString("zh-CN");
|
|
3344
3413
|
} catch {
|
|
3414
|
+
return "\u672A\u77E5";
|
|
3345
3415
|
}
|
|
3416
|
+
}
|
|
3417
|
+
function formatLocalTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
3418
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
3419
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}_${p(d.getHours())}-${p(d.getMinutes())}-${p(d.getSeconds())}`;
|
|
3420
|
+
}
|
|
3421
|
+
function formatDate(dateOrIso) {
|
|
3422
|
+
if (dateOrIso === void 0 || dateOrIso === null) return "\u672A\u77E5";
|
|
3346
3423
|
try {
|
|
3347
|
-
|
|
3424
|
+
const d = dateOrIso instanceof Date ? dateOrIso : new Date(dateOrIso);
|
|
3425
|
+
if (Number.isNaN(d.getTime())) return "\u672A\u77E5";
|
|
3426
|
+
return d.toLocaleString("zh-CN");
|
|
3348
3427
|
} catch {
|
|
3349
|
-
|
|
3428
|
+
return "\u672A\u77E5";
|
|
3350
3429
|
}
|
|
3351
3430
|
}
|
|
3352
|
-
function
|
|
3353
|
-
|
|
3354
|
-
for (const file of overwriteFiles) {
|
|
3355
|
-
for (const [key, value] of Object.entries(file.config)) {
|
|
3356
|
-
if ((key === "+proxies" || key === "proxies+") && Array.isArray(value)) {
|
|
3357
|
-
for (const proxy of value) {
|
|
3358
|
-
if (proxy && typeof proxy === "object" && "name" in proxy) {
|
|
3359
|
-
names.push(proxy.name);
|
|
3360
|
-
}
|
|
3361
|
-
}
|
|
3362
|
-
}
|
|
3363
|
-
}
|
|
3364
|
-
}
|
|
3365
|
-
return names;
|
|
3431
|
+
function hasFlag(args, short, long) {
|
|
3432
|
+
return !!args && (args.includes(short) || args.includes(long));
|
|
3366
3433
|
}
|
|
3367
|
-
function
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
const existing = group["exclude-filter"];
|
|
3376
|
-
if (existing) {
|
|
3377
|
-
group["exclude-filter"] = `${existing}|${excludePattern}`;
|
|
3378
|
-
} else {
|
|
3379
|
-
group["exclude-filter"] = excludePattern;
|
|
3434
|
+
function parseIntArg(args, short, long, defaultValue) {
|
|
3435
|
+
if (!args) return defaultValue;
|
|
3436
|
+
for (let i = 0; i < args.length; i++) {
|
|
3437
|
+
if (args[i] === short || args[i] === long) {
|
|
3438
|
+
if (i + 1 < args.length) {
|
|
3439
|
+
const val = parseInt(args[i + 1], 10);
|
|
3440
|
+
return Number.isNaN(val) ? defaultValue : val;
|
|
3441
|
+
}
|
|
3380
3442
|
}
|
|
3381
3443
|
}
|
|
3444
|
+
return defaultValue;
|
|
3382
3445
|
}
|
|
3383
|
-
var
|
|
3384
|
-
function
|
|
3385
|
-
|
|
3386
|
-
|
|
3446
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["-t", "--timeout", "-j", "--concurrency", "-r", "--rounds", "-n", "--lines", "-u", "--update-timeout"]);
|
|
3447
|
+
function getNonFlagArg(args, startIdx, valueFlags = VALUE_FLAGS) {
|
|
3448
|
+
if (!args) return null;
|
|
3449
|
+
for (let i = startIdx; i < args.length; i++) {
|
|
3450
|
+
const a = args[i];
|
|
3451
|
+
if (a.startsWith("-")) {
|
|
3452
|
+
if (valueFlags.has(a)) i++;
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3455
|
+
return a;
|
|
3456
|
+
}
|
|
3457
|
+
return null;
|
|
3458
|
+
}
|
|
3459
|
+
function isProcessRunning(pid) {
|
|
3460
|
+
if (!pid) return false;
|
|
3461
|
+
try {
|
|
3462
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "pid="], { encoding: "utf8", timeout: 5e3 });
|
|
3463
|
+
return (result.stdout || "").trim().length > 0;
|
|
3464
|
+
} catch {
|
|
3465
|
+
return false;
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
function isProcessRoot(pid) {
|
|
3469
|
+
if (!pid) return false;
|
|
3470
|
+
try {
|
|
3471
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "uid="], { encoding: "utf8", timeout: 5e3 });
|
|
3472
|
+
return (result.stdout || "").trim() === "0";
|
|
3473
|
+
} catch {
|
|
3474
|
+
return false;
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
function createHttpClient(options = {}) {
|
|
3478
|
+
const { timeout = 6e4 } = options;
|
|
3479
|
+
return {
|
|
3480
|
+
async get(url, config) {
|
|
3481
|
+
const controller = new AbortController();
|
|
3482
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
3483
|
+
const signal = config?.signal ? AbortSignal.any([controller.signal, config.signal]) : controller.signal;
|
|
3484
|
+
try {
|
|
3485
|
+
const response = await fetch(url, {
|
|
3486
|
+
signal,
|
|
3487
|
+
headers: { "User-Agent": `mihomo-cli/${VERSION}` }
|
|
3488
|
+
});
|
|
3489
|
+
if (!response.ok) {
|
|
3490
|
+
const error = new Error(`HTTP ${response.status}`);
|
|
3491
|
+
error.response = { status: response.status };
|
|
3492
|
+
try {
|
|
3493
|
+
error.response.data = await response.json();
|
|
3494
|
+
} catch {
|
|
3495
|
+
}
|
|
3496
|
+
throw error;
|
|
3497
|
+
}
|
|
3498
|
+
const data = config?.responseType === "json" ? await response.json() : await response.text();
|
|
3499
|
+
return { data, headers: response.headers, status: response.status };
|
|
3500
|
+
} finally {
|
|
3501
|
+
clearTimeout(timer);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
function normalizeMirrorUrl(val) {
|
|
3507
|
+
if (!val) return null;
|
|
3508
|
+
if (val === "direct" || val === "no" || val === "none") return null;
|
|
3509
|
+
let url = val;
|
|
3510
|
+
if (!url.startsWith("http")) {
|
|
3511
|
+
url = `https://${url}`;
|
|
3512
|
+
}
|
|
3513
|
+
if (!url.endsWith("/")) {
|
|
3514
|
+
url += "/";
|
|
3515
|
+
}
|
|
3516
|
+
return url;
|
|
3517
|
+
}
|
|
3518
|
+
function parseMirrorArg(args) {
|
|
3519
|
+
if (!args || args.length < 2) {
|
|
3520
|
+
return { mirror: null, isOverride: false, type: "download" };
|
|
3521
|
+
}
|
|
3522
|
+
if (args.includes("--no-mirror") || args.includes("--direct")) {
|
|
3523
|
+
return { mirror: null, isOverride: true, type: "download" };
|
|
3524
|
+
}
|
|
3525
|
+
const mirrorAllIdx = args.indexOf("--mirror-all");
|
|
3526
|
+
if (mirrorAllIdx >= 0) {
|
|
3527
|
+
const nextArg = args[mirrorAllIdx + 1];
|
|
3528
|
+
if (!nextArg || nextArg.startsWith("-")) {
|
|
3529
|
+
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "all" };
|
|
3530
|
+
}
|
|
3531
|
+
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "all" };
|
|
3532
|
+
}
|
|
3533
|
+
const mirrorIdx = args.indexOf("--mirror");
|
|
3534
|
+
if (mirrorIdx >= 0) {
|
|
3535
|
+
const nextArg = args[mirrorIdx + 1];
|
|
3536
|
+
if (!nextArg || nextArg.startsWith("-")) {
|
|
3537
|
+
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "download" };
|
|
3538
|
+
}
|
|
3539
|
+
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "download" };
|
|
3540
|
+
}
|
|
3541
|
+
return { mirror: null, isOverride: false, type: "download" };
|
|
3542
|
+
}
|
|
3543
|
+
function isProxyValid(proxy) {
|
|
3544
|
+
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3545
|
+
if (!proxy.type) return false;
|
|
3546
|
+
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3547
|
+
const pw = String(proxy.password || "");
|
|
3548
|
+
if (!/^[A-Za-z0-9+/\-_]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3549
|
+
}
|
|
3550
|
+
return true;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3553
|
+
// src/config.ts
|
|
3554
|
+
function parseYamlOrJson(content, errorMsg) {
|
|
3555
|
+
if (!content?.trim()) {
|
|
3556
|
+
throw new Error(`${errorMsg || "\u5185\u5BB9"}\u4E3A\u7A7A`);
|
|
3557
|
+
}
|
|
3558
|
+
try {
|
|
3559
|
+
const result = load(content);
|
|
3560
|
+
if (result != null && typeof result === "object" && !Array.isArray(result)) return result;
|
|
3561
|
+
} catch {
|
|
3562
|
+
}
|
|
3563
|
+
try {
|
|
3564
|
+
return JSON.parse(content);
|
|
3565
|
+
} catch {
|
|
3566
|
+
throw new Error(`${errorMsg || "\u5185\u5BB9"}\u683C\u5F0F\u9519\u8BEF\uFF0C\u65E0\u6CD5\u89E3\u6790\u4E3A YAML \u6216 JSON`);
|
|
3567
|
+
}
|
|
3568
|
+
}
|
|
3569
|
+
function dumpYaml(obj) {
|
|
3570
|
+
return dump(obj, { indent: 2, lineWidth: -1, schema: CORE_SCHEMA });
|
|
3571
|
+
}
|
|
3572
|
+
function collectOverwriteProxyNames(overwriteFiles) {
|
|
3573
|
+
const names = [];
|
|
3574
|
+
for (const file of overwriteFiles) {
|
|
3575
|
+
for (const [key, value] of Object.entries(file.config)) {
|
|
3576
|
+
if ((key === "+proxies" || key === "proxies+") && Array.isArray(value)) {
|
|
3577
|
+
for (const proxy of value) {
|
|
3578
|
+
if (proxy && typeof proxy === "object" && "name" in proxy) {
|
|
3579
|
+
names.push(proxy.name);
|
|
3580
|
+
}
|
|
3581
|
+
}
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
}
|
|
3585
|
+
return names;
|
|
3586
|
+
}
|
|
3587
|
+
function excludeOverwriteProxiesFromIncludeAll(config, overwriteFiles) {
|
|
3588
|
+
const injectedNames = collectOverwriteProxyNames(overwriteFiles);
|
|
3589
|
+
if (injectedNames.length === 0) return;
|
|
3590
|
+
const groups = config["proxy-groups"];
|
|
3591
|
+
if (!groups) return;
|
|
3592
|
+
const excludePattern = injectedNames.map((n) => escapeRegExp(n)).join("|");
|
|
3593
|
+
for (const group of groups) {
|
|
3594
|
+
if (!group["include-all"] && !group["include-all-proxies"]) continue;
|
|
3595
|
+
const existing = group["exclude-filter"];
|
|
3596
|
+
if (existing) {
|
|
3597
|
+
group["exclude-filter"] = `${existing}|${excludePattern}`;
|
|
3598
|
+
} else {
|
|
3599
|
+
group["exclude-filter"] = excludePattern;
|
|
3600
|
+
}
|
|
3601
|
+
}
|
|
3602
|
+
}
|
|
3603
|
+
var BUILTIN_PROXY_NAMES = /* @__PURE__ */ new Set(["DIRECT", "REJECT", "REJECT-DROP", "PASS", "COMPATIBLE"]);
|
|
3604
|
+
function deduplicateByName(items) {
|
|
3605
|
+
const names = /* @__PURE__ */ new Set();
|
|
3606
|
+
const duplicates = [];
|
|
3387
3607
|
const result = items.filter((item) => {
|
|
3388
3608
|
if (names.has(item.name)) {
|
|
3389
3609
|
duplicates.push(item.name);
|
|
@@ -3394,6 +3614,15 @@ function deduplicateByName(items) {
|
|
|
3394
3614
|
});
|
|
3395
3615
|
return { result, names, duplicates };
|
|
3396
3616
|
}
|
|
3617
|
+
function getRuleTarget(rule) {
|
|
3618
|
+
const parts = rule.split(",");
|
|
3619
|
+
if (parts.length < 2) return "";
|
|
3620
|
+
const last = parts[parts.length - 1].trim();
|
|
3621
|
+
if (last.toLowerCase() === "no-resolve" && parts.length >= 3) {
|
|
3622
|
+
return parts[parts.length - 2].trim();
|
|
3623
|
+
}
|
|
3624
|
+
return last;
|
|
3625
|
+
}
|
|
3397
3626
|
function validateConfig(config) {
|
|
3398
3627
|
const warnings = [];
|
|
3399
3628
|
const proxies = config.proxies || [];
|
|
@@ -3438,9 +3667,7 @@ function validateConfig(config) {
|
|
|
3438
3667
|
if (rules.length > 0) {
|
|
3439
3668
|
const removedRules = [];
|
|
3440
3669
|
config.rules = rules.filter((rule) => {
|
|
3441
|
-
const
|
|
3442
|
-
if (parts.length < 2) return true;
|
|
3443
|
-
const target = parts[parts.length - 1].trim();
|
|
3670
|
+
const target = getRuleTarget(rule);
|
|
3444
3671
|
if (!target || validNames.has(target)) return true;
|
|
3445
3672
|
removedRules.push(rule);
|
|
3446
3673
|
return false;
|
|
@@ -3487,6 +3714,8 @@ function buildConfig(subRawContent, mode) {
|
|
|
3487
3714
|
if (Object.keys(dns).length > 0) {
|
|
3488
3715
|
systemConfig.dns = dns;
|
|
3489
3716
|
}
|
|
3717
|
+
} else {
|
|
3718
|
+
delete withOverwrites.tun;
|
|
3490
3719
|
}
|
|
3491
3720
|
const merged = { ...withOverwrites, ...systemConfig };
|
|
3492
3721
|
if (systemConfig.dns) {
|
|
@@ -3509,20 +3738,19 @@ function buildConfig(subRawContent, mode) {
|
|
|
3509
3738
|
}
|
|
3510
3739
|
function writeMihomoConfig(configObj) {
|
|
3511
3740
|
ensureDirs();
|
|
3512
|
-
const content =
|
|
3741
|
+
const content = dumpYaml(configObj);
|
|
3513
3742
|
atomicWriteFileSync(PATHS.configFile, content, { mode: 384 });
|
|
3514
3743
|
}
|
|
3515
3744
|
function writeDebugConfig(buildResult) {
|
|
3516
3745
|
ensureDirs();
|
|
3517
|
-
|
|
3518
|
-
fs4.writeFileSync(PATHS.configStage1Subscription, dump(buildResult.subscriptionConfig, dumpOpts), { mode: 384 });
|
|
3746
|
+
fs4.writeFileSync(PATHS.configStage1Subscription, dumpYaml(buildResult.subscriptionConfig), { mode: 384 });
|
|
3519
3747
|
const overwriteMerged = {};
|
|
3520
3748
|
for (const f of buildResult.overwriteFiles) {
|
|
3521
3749
|
Object.assign(overwriteMerged, f.config);
|
|
3522
3750
|
}
|
|
3523
|
-
const overwriteContent = buildResult.overwriteFiles.length > 0 ?
|
|
3751
|
+
const overwriteContent = buildResult.overwriteFiles.length > 0 ? dumpYaml(overwriteMerged) : "# overwrite \u5DF2\u7981\u7528\u6216\u65E0\u8986\u5199\u6587\u4EF6\n";
|
|
3524
3752
|
fs4.writeFileSync(PATHS.configStage2Overwrite, overwriteContent, { mode: 384 });
|
|
3525
|
-
fs4.writeFileSync(PATHS.configStage3System,
|
|
3753
|
+
fs4.writeFileSync(PATHS.configStage3System, dumpYaml(buildResult.systemConfig), { mode: 384 });
|
|
3526
3754
|
}
|
|
3527
3755
|
function hasConfig() {
|
|
3528
3756
|
return fs4.existsSync(PATHS.configFile);
|
|
@@ -3562,7 +3790,7 @@ function getKernelVersion() {
|
|
|
3562
3790
|
}
|
|
3563
3791
|
if (kernelVersionCached) return kernelVersionCache;
|
|
3564
3792
|
try {
|
|
3565
|
-
const result =
|
|
3793
|
+
const result = spawnSync2(PATHS.mihomoBinary, ["-v"], { encoding: "utf8", timeout: 5e3 });
|
|
3566
3794
|
const output = `${result.stdout || ""}${result.stderr || ""}`.trim();
|
|
3567
3795
|
if (output) {
|
|
3568
3796
|
const match = output.match(/v?[\d]+\.[\d]+\.[\d]+/);
|
|
@@ -3581,219 +3809,102 @@ function clearKernelVersionCache() {
|
|
|
3581
3809
|
kernelVersionCached = false;
|
|
3582
3810
|
}
|
|
3583
3811
|
|
|
3584
|
-
// src/
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3812
|
+
// src/commands/help.ts
|
|
3813
|
+
function printShortHelp() {
|
|
3814
|
+
console.log(`
|
|
3815
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))} (mihomo help \u67E5\u770B\u5B8C\u6574\u5E2E\u52A9)
|
|
3816
|
+
`);
|
|
3817
|
+
console.log(
|
|
3818
|
+
`\u5E38\u7528\u547D\u4EE4:
|
|
3819
|
+
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
3820
|
+
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
3821
|
+
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
3822
|
+
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
3823
|
+
`
|
|
3824
|
+
);
|
|
3595
3825
|
}
|
|
3596
|
-
var
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3826
|
+
var GROUP_TITLES = [
|
|
3827
|
+
["control", "\u63A7\u5236:"],
|
|
3828
|
+
["interface", "\u754C\u9762:"],
|
|
3829
|
+
["subscription", "\u8BA2\u9605:"],
|
|
3830
|
+
["config", "\u914D\u7F6E:"],
|
|
3831
|
+
["system", "\u7CFB\u7EDF:"]
|
|
3832
|
+
];
|
|
3833
|
+
function printHelp(commands) {
|
|
3834
|
+
const lines = [`
|
|
3835
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}`, "", "\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh", "", "\u7528\u6CD5:", " mihomo <\u547D\u4EE4> [\u9009\u9879]"];
|
|
3836
|
+
for (const [group, title] of GROUP_TITLES) {
|
|
3837
|
+
const usageLines = commands.filter((c) => c.group === group).flatMap((c) => c.usage);
|
|
3838
|
+
if (usageLines.length === 0) continue;
|
|
3839
|
+
lines.push("", colors.cyan(title));
|
|
3840
|
+
for (const u of usageLines) {
|
|
3841
|
+
lines.push(u.startsWith(" ") ? ` ${u}` : ` ${boldFirstToken(u)}`);
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
const meta = commands.filter((c) => c.group === "meta").flatMap((c) => c.usage);
|
|
3845
|
+
if (meta.length > 0) {
|
|
3846
|
+
lines.push("", colors.cyan("\u5143:"));
|
|
3847
|
+
for (const u of meta) lines.push(` ${boldFirstToken(u)}`);
|
|
3848
|
+
}
|
|
3849
|
+
lines.push(
|
|
3850
|
+
"",
|
|
3851
|
+
`${colors.cyan("\u793A\u4F8B:")}`,
|
|
3852
|
+
" mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F",
|
|
3853
|
+
" mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F",
|
|
3854
|
+
" mihomo start -s # \u8DF3\u8FC7\u81EA\u52A8\u66F4\u65B0\u8BA2\u9605",
|
|
3855
|
+
" mihomo start -u 30000 # \u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 30 \u79D2 (\u9ED8\u8BA4 10s)",
|
|
3856
|
+
" mihomo daemon on # \u5F00\u542F\u4FDD\u6D3B\uFF08\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F\uFF09",
|
|
3857
|
+
" mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)",
|
|
3858
|
+
" mihomo ui # \u6253\u5F00 Web UI",
|
|
3859
|
+
"",
|
|
3860
|
+
`${colors.cyan("\u5FEB\u6377\u547D\u4EE4:")}`,
|
|
3861
|
+
" tun = start tun use = sub use on/off = ow on/off open = dir open",
|
|
3862
|
+
" up = start down = stop upd/upgrade = update",
|
|
3863
|
+
"",
|
|
3864
|
+
`${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}`,
|
|
3865
|
+
" mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)",
|
|
3866
|
+
" tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo",
|
|
3867
|
+
"",
|
|
3868
|
+
`${colors.cyan("\u6570\u636E\u76EE\u5F55:")}`,
|
|
3869
|
+
" \u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E",
|
|
3870
|
+
` \u9ED8\u8BA4: ${USER_DATA_DIR}`
|
|
3871
|
+
);
|
|
3872
|
+
console.log(lines.join("\n"));
|
|
3606
3873
|
}
|
|
3607
|
-
function
|
|
3608
|
-
|
|
3874
|
+
function boldFirstToken(usage) {
|
|
3875
|
+
const spaceIdx = usage.indexOf(" ");
|
|
3876
|
+
if (spaceIdx < 0) return colors.bold(usage);
|
|
3877
|
+
return `${colors.bold(usage.slice(0, spaceIdx))}${usage.slice(spaceIdx)}`;
|
|
3609
3878
|
}
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
}
|
|
3615
|
-
};
|
|
3616
|
-
function withTimeout(promise, ms) {
|
|
3617
|
-
return new Promise((resolve, reject) => {
|
|
3618
|
-
const timer = setTimeout(() => reject(new TimeoutError()), ms);
|
|
3619
|
-
promise.then(
|
|
3620
|
-
(v) => {
|
|
3621
|
-
clearTimeout(timer);
|
|
3622
|
-
resolve(v);
|
|
3623
|
-
},
|
|
3624
|
-
(e) => {
|
|
3625
|
-
clearTimeout(timer);
|
|
3626
|
-
reject(e);
|
|
3627
|
-
}
|
|
3628
|
-
);
|
|
3629
|
-
});
|
|
3879
|
+
function printVersion() {
|
|
3880
|
+
const kv = getKernelVersion() || "\u672A\u5B89\u88C5";
|
|
3881
|
+
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
3882
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
3883
|
+
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
3630
3884
|
}
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3648
|
-
}
|
|
3649
|
-
function
|
|
3650
|
-
if (
|
|
3651
|
-
|
|
3652
|
-
|
|
3653
|
-
|
|
3654
|
-
return d.toLocaleString("zh-CN");
|
|
3655
|
-
} catch {
|
|
3656
|
-
return "\u672A\u77E5";
|
|
3657
|
-
}
|
|
3658
|
-
}
|
|
3659
|
-
function hasFlag(args, short, long) {
|
|
3660
|
-
return !!args && (args.includes(short) || args.includes(long));
|
|
3661
|
-
}
|
|
3662
|
-
function parseIntArg(args, short, long, defaultValue) {
|
|
3663
|
-
if (!args) return defaultValue;
|
|
3664
|
-
for (let i = 0; i < args.length; i++) {
|
|
3665
|
-
if (args[i] === short || args[i] === long) {
|
|
3666
|
-
if (i + 1 < args.length) {
|
|
3667
|
-
const val = parseInt(args[i + 1], 10);
|
|
3668
|
-
return Number.isNaN(val) ? defaultValue : val;
|
|
3669
|
-
}
|
|
3670
|
-
}
|
|
3671
|
-
}
|
|
3672
|
-
return defaultValue;
|
|
3673
|
-
}
|
|
3674
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set(["-t", "--timeout", "-j", "--concurrency", "-r", "--rounds", "-n", "--lines", "-u", "--update-timeout"]);
|
|
3675
|
-
function getNonFlagArg(args, startIdx, valueFlags = VALUE_FLAGS) {
|
|
3676
|
-
if (!args) return null;
|
|
3677
|
-
for (let i = startIdx; i < args.length; i++) {
|
|
3678
|
-
const a = args[i];
|
|
3679
|
-
if (a.startsWith("-")) {
|
|
3680
|
-
if (valueFlags.has(a)) i++;
|
|
3681
|
-
continue;
|
|
3682
|
-
}
|
|
3683
|
-
return a;
|
|
3684
|
-
}
|
|
3685
|
-
return null;
|
|
3686
|
-
}
|
|
3687
|
-
function isProcessRunning(pid) {
|
|
3688
|
-
if (!pid) return false;
|
|
3689
|
-
try {
|
|
3690
|
-
const result = spawnSync2("ps", ["-p", String(pid), "-o", "pid="], { encoding: "utf8", timeout: 5e3 });
|
|
3691
|
-
return (result.stdout || "").trim().length > 0;
|
|
3692
|
-
} catch {
|
|
3693
|
-
return false;
|
|
3694
|
-
}
|
|
3695
|
-
}
|
|
3696
|
-
function isProcessRoot(pid) {
|
|
3697
|
-
if (!pid) return false;
|
|
3698
|
-
try {
|
|
3699
|
-
const result = spawnSync2("ps", ["-p", String(pid), "-o", "uid="], { encoding: "utf8", timeout: 5e3 });
|
|
3700
|
-
return (result.stdout || "").trim() === "0";
|
|
3701
|
-
} catch {
|
|
3702
|
-
return false;
|
|
3703
|
-
}
|
|
3704
|
-
}
|
|
3705
|
-
function createHttpClient(options = {}) {
|
|
3706
|
-
const { timeout = 6e4 } = options;
|
|
3707
|
-
return {
|
|
3708
|
-
async get(url, config) {
|
|
3709
|
-
const controller = new AbortController();
|
|
3710
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
3711
|
-
const signal = config?.signal ? AbortSignal.any([controller.signal, config.signal]) : controller.signal;
|
|
3712
|
-
try {
|
|
3713
|
-
const response = await fetch(url, {
|
|
3714
|
-
signal,
|
|
3715
|
-
headers: { "User-Agent": `mihomo-cli/${VERSION}` }
|
|
3716
|
-
});
|
|
3717
|
-
if (!response.ok) {
|
|
3718
|
-
const error = new Error(`HTTP ${response.status}`);
|
|
3719
|
-
error.response = { status: response.status };
|
|
3720
|
-
try {
|
|
3721
|
-
error.response.data = await response.json();
|
|
3722
|
-
} catch {
|
|
3723
|
-
}
|
|
3724
|
-
throw error;
|
|
3725
|
-
}
|
|
3726
|
-
const data = config?.responseType === "json" ? await response.json() : await response.text();
|
|
3727
|
-
return { data, headers: response.headers, status: response.status };
|
|
3728
|
-
} finally {
|
|
3729
|
-
clearTimeout(timer);
|
|
3730
|
-
}
|
|
3731
|
-
}
|
|
3732
|
-
};
|
|
3733
|
-
}
|
|
3734
|
-
function normalizeMirrorUrl(val) {
|
|
3735
|
-
if (!val) return null;
|
|
3736
|
-
if (val === "direct" || val === "no" || val === "none") return null;
|
|
3737
|
-
let url = val;
|
|
3738
|
-
if (!url.startsWith("http")) {
|
|
3739
|
-
url = `https://${url}`;
|
|
3740
|
-
}
|
|
3741
|
-
if (!url.endsWith("/")) {
|
|
3742
|
-
url += "/";
|
|
3743
|
-
}
|
|
3744
|
-
return url;
|
|
3745
|
-
}
|
|
3746
|
-
function parseMirrorArg(args) {
|
|
3747
|
-
if (!args || args.length < 2) {
|
|
3748
|
-
return { mirror: null, isOverride: false, type: "download" };
|
|
3749
|
-
}
|
|
3750
|
-
if (args.includes("--no-mirror") || args.includes("--direct")) {
|
|
3751
|
-
return { mirror: null, isOverride: true, type: "download" };
|
|
3752
|
-
}
|
|
3753
|
-
const mirrorAllIdx = args.indexOf("--mirror-all");
|
|
3754
|
-
if (mirrorAllIdx >= 0) {
|
|
3755
|
-
const nextArg = args[mirrorAllIdx + 1];
|
|
3756
|
-
if (!nextArg || nextArg.startsWith("-")) {
|
|
3757
|
-
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "all" };
|
|
3758
|
-
}
|
|
3759
|
-
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "all" };
|
|
3760
|
-
}
|
|
3761
|
-
const mirrorIdx = args.indexOf("--mirror");
|
|
3762
|
-
if (mirrorIdx >= 0) {
|
|
3763
|
-
const nextArg = args[mirrorIdx + 1];
|
|
3764
|
-
if (!nextArg || nextArg.startsWith("-")) {
|
|
3765
|
-
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "download" };
|
|
3766
|
-
}
|
|
3767
|
-
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "download" };
|
|
3768
|
-
}
|
|
3769
|
-
return { mirror: null, isOverride: false, type: "download" };
|
|
3770
|
-
}
|
|
3771
|
-
function isProxyValid(proxy) {
|
|
3772
|
-
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3773
|
-
if (!proxy.type) return false;
|
|
3774
|
-
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3775
|
-
const pw = String(proxy.password || "");
|
|
3776
|
-
if (!/^[A-Za-z0-9+/\-_]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3777
|
-
}
|
|
3778
|
-
return true;
|
|
3779
|
-
}
|
|
3780
|
-
|
|
3781
|
-
// src/process.ts
|
|
3782
|
-
var PROCESS_WAIT_ATTEMPTS = 50;
|
|
3783
|
-
var PROCESS_WAIT_INTERVAL = 100;
|
|
3784
|
-
var STARTUP_WAIT_MS = 800;
|
|
3785
|
-
var SUDO_TIMEOUT_MS = 6e4;
|
|
3786
|
-
var TUN_MODE_POST_WAIT_MS = 500;
|
|
3787
|
-
var BATCH_KILL_THRESHOLD = 3;
|
|
3788
|
-
var DEFAULT_LOG_RETENTION_DAYS = 7;
|
|
3789
|
-
function escapeForPgrep(s) {
|
|
3790
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3791
|
-
}
|
|
3792
|
-
function clearRuntime() {
|
|
3793
|
-
if (fs5.existsSync(DIRS.runtime)) {
|
|
3794
|
-
rmrf(DIRS.runtime);
|
|
3795
|
-
}
|
|
3796
|
-
ensureDirs();
|
|
3885
|
+
|
|
3886
|
+
// src/daemon.ts
|
|
3887
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
3888
|
+
import fs6 from "fs";
|
|
3889
|
+
import path5 from "path";
|
|
3890
|
+
|
|
3891
|
+
// src/process.ts
|
|
3892
|
+
import { spawn, spawnSync as spawnSync3 } from "child_process";
|
|
3893
|
+
import fs5 from "fs";
|
|
3894
|
+
import path4 from "path";
|
|
3895
|
+
var PROCESS_WAIT_ATTEMPTS = 50;
|
|
3896
|
+
var PROCESS_WAIT_INTERVAL = 100;
|
|
3897
|
+
var STARTUP_WAIT_MS = 800;
|
|
3898
|
+
var SUDO_TIMEOUT_MS = 6e4;
|
|
3899
|
+
var TUN_MODE_POST_WAIT_MS = 500;
|
|
3900
|
+
var BATCH_KILL_THRESHOLD = 3;
|
|
3901
|
+
var DEFAULT_LOG_RETENTION_DAYS = 7;
|
|
3902
|
+
var MAIN_INSTANCE_PATTERN = `${escapeRegExp(PATHS.mihomoBinary)}.*${escapeRegExp(PATHS.configFile)}`;
|
|
3903
|
+
function clearRuntime() {
|
|
3904
|
+
if (fs5.existsSync(DIRS.runtime)) {
|
|
3905
|
+
rmrf(DIRS.runtime);
|
|
3906
|
+
}
|
|
3907
|
+
ensureDirs();
|
|
3797
3908
|
}
|
|
3798
3909
|
function getPid() {
|
|
3799
3910
|
if (!fs5.existsSync(PATHS.pidFile)) return null;
|
|
@@ -3808,10 +3919,9 @@ function isRunning() {
|
|
|
3808
3919
|
const pid = getPid();
|
|
3809
3920
|
return pid ? isProcessRunning(pid) : false;
|
|
3810
3921
|
}
|
|
3811
|
-
function
|
|
3812
|
-
const binaryPath = PATHS.mihomoBinary;
|
|
3922
|
+
function getMihomoPids() {
|
|
3813
3923
|
try {
|
|
3814
|
-
const result = spawnSync3("pgrep", ["-f",
|
|
3924
|
+
const result = spawnSync3("pgrep", ["-f", MAIN_INSTANCE_PATTERN], { encoding: "utf8", timeout: 1e4 });
|
|
3815
3925
|
const output = (result.stdout || "").trim();
|
|
3816
3926
|
if (!output) return [];
|
|
3817
3927
|
return output.split("\n").filter(Boolean).map((p) => parseInt(p, 10)).filter((p) => Number.isInteger(p) && p > 0);
|
|
@@ -3829,7 +3939,7 @@ function isPidFileOwnedByRoot() {
|
|
|
3829
3939
|
}
|
|
3830
3940
|
}
|
|
3831
3941
|
function checkStaleState() {
|
|
3832
|
-
const allPids =
|
|
3942
|
+
const allPids = getMihomoPids();
|
|
3833
3943
|
const hasRootProcess = allPids.some((p) => isProcessRoot(p));
|
|
3834
3944
|
const hasRootPidFile = isPidFileOwnedByRoot();
|
|
3835
3945
|
return {
|
|
@@ -3880,7 +3990,7 @@ function killProcess(pid, needsSudo = false) {
|
|
|
3880
3990
|
}
|
|
3881
3991
|
}
|
|
3882
3992
|
function killAllMihomo(forceSudo = false) {
|
|
3883
|
-
const pattern =
|
|
3993
|
+
const pattern = MAIN_INSTANCE_PATTERN;
|
|
3884
3994
|
if (forceSudo) {
|
|
3885
3995
|
try {
|
|
3886
3996
|
spawnSync3("sudo", ["pkill", "-9", "-f", pattern], { stdio: "inherit", timeout: 15e3 });
|
|
@@ -3898,7 +4008,7 @@ function killAllMihomo(forceSudo = false) {
|
|
|
3898
4008
|
}
|
|
3899
4009
|
}
|
|
3900
4010
|
function cleanupAll(forceSudo = false) {
|
|
3901
|
-
const pids =
|
|
4011
|
+
const pids = getMihomoPids();
|
|
3902
4012
|
if (pids.length === 0) {
|
|
3903
4013
|
clearPid();
|
|
3904
4014
|
return { killed: 0, failed: 0, remaining: [] };
|
|
@@ -3929,26 +4039,26 @@ function cleanupAll(forceSudo = false) {
|
|
|
3929
4039
|
}
|
|
3930
4040
|
}
|
|
3931
4041
|
for (let i = 0; i < PROCESS_WAIT_ATTEMPTS; i++) {
|
|
3932
|
-
if (
|
|
4042
|
+
if (getMihomoPids().length === 0) break;
|
|
3933
4043
|
sleepSync(PROCESS_WAIT_INTERVAL);
|
|
3934
4044
|
}
|
|
3935
4045
|
clearPid();
|
|
3936
|
-
return { killed: killedCount, failed: failedPids.length, remaining:
|
|
4046
|
+
return { killed: killedCount, failed: failedPids.length, remaining: getMihomoPids() };
|
|
3937
4047
|
}
|
|
3938
4048
|
function createTunLaunchScript() {
|
|
3939
|
-
const binary = PATHS.mihomoBinary;
|
|
3940
|
-
const configFile = PATHS.configFile;
|
|
3941
|
-
const logFile = PATHS.logFile;
|
|
3942
|
-
const pidFile = PATHS.pidFile;
|
|
3943
|
-
const dataDir = DIRS.data;
|
|
3944
|
-
const killPattern =
|
|
4049
|
+
const binary = shellQuote(PATHS.mihomoBinary);
|
|
4050
|
+
const configFile = shellQuote(PATHS.configFile);
|
|
4051
|
+
const logFile = shellQuote(PATHS.logFile);
|
|
4052
|
+
const pidFile = shellQuote(PATHS.pidFile);
|
|
4053
|
+
const dataDir = shellQuote(DIRS.data);
|
|
4054
|
+
const killPattern = shellQuote(MAIN_INSTANCE_PATTERN);
|
|
3945
4055
|
const scriptContent = `#!/bin/bash
|
|
3946
|
-
BINARY
|
|
3947
|
-
CONFIG_FILE
|
|
3948
|
-
LOG_FILE
|
|
3949
|
-
PID_FILE
|
|
3950
|
-
DATA_DIR
|
|
3951
|
-
KILL_PATTERN
|
|
4056
|
+
BINARY=${binary}
|
|
4057
|
+
CONFIG_FILE=${configFile}
|
|
4058
|
+
LOG_FILE=${logFile}
|
|
4059
|
+
PID_FILE=${pidFile}
|
|
4060
|
+
DATA_DIR=${dataDir}
|
|
4061
|
+
KILL_PATTERN=${killPattern}
|
|
3952
4062
|
|
|
3953
4063
|
# \u7EC8\u6B62\u65E7\u8FDB\u7A0B
|
|
3954
4064
|
pkill -9 -f "\${KILL_PATTERN}" 2>/dev/null || true
|
|
@@ -3979,7 +4089,7 @@ echo "--- \u65E5\u5FD7 ---"
|
|
|
3979
4089
|
tail -25 "\${LOG_FILE}" 2>/dev/null
|
|
3980
4090
|
exit 1
|
|
3981
4091
|
`;
|
|
3982
|
-
const scriptPath =
|
|
4092
|
+
const scriptPath = path4.join(DIRS.runtime, "launch-tun.sh");
|
|
3983
4093
|
fs5.writeFileSync(scriptPath, scriptContent, { mode: 448 });
|
|
3984
4094
|
return scriptPath;
|
|
3985
4095
|
}
|
|
@@ -4001,7 +4111,7 @@ function getProcessInfo(pid) {
|
|
|
4001
4111
|
function getStatus() {
|
|
4002
4112
|
const running = isRunning();
|
|
4003
4113
|
const pid = getPid();
|
|
4004
|
-
const allPids =
|
|
4114
|
+
const allPids = getMihomoPids();
|
|
4005
4115
|
return {
|
|
4006
4116
|
running,
|
|
4007
4117
|
pid: running ? pid : null,
|
|
@@ -4051,6 +4161,16 @@ async function startMixedMode(staleState) {
|
|
|
4051
4161
|
const configFile = PATHS.configFile;
|
|
4052
4162
|
const logFile = PATHS.logFile;
|
|
4053
4163
|
const args = ["-d", DIRS.data, "-f", configFile];
|
|
4164
|
+
if (fs5.existsSync(logFile)) {
|
|
4165
|
+
try {
|
|
4166
|
+
fs5.accessSync(logFile, fs5.constants.W_OK);
|
|
4167
|
+
} catch {
|
|
4168
|
+
try {
|
|
4169
|
+
fs5.unlinkSync(logFile);
|
|
4170
|
+
} catch {
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4054
4174
|
const logFd = fs5.openSync(logFile, "a");
|
|
4055
4175
|
const child = spawn(PATHS.mihomoBinary, args, {
|
|
4056
4176
|
detached: true,
|
|
@@ -4113,14 +4233,14 @@ async function startTunMode(staleState) {
|
|
|
4113
4233
|
return { success: true, pid: finalPid, mode: "tun" };
|
|
4114
4234
|
}
|
|
4115
4235
|
function stop(forceSudo = false) {
|
|
4116
|
-
const allPids =
|
|
4236
|
+
const allPids = getMihomoPids();
|
|
4117
4237
|
if (allPids.length === 0) {
|
|
4118
4238
|
clearPid();
|
|
4119
4239
|
clearRuntime();
|
|
4120
4240
|
return { success: true, notRunning: true };
|
|
4121
4241
|
}
|
|
4122
4242
|
const result = cleanupAll(forceSudo);
|
|
4123
|
-
const remaining =
|
|
4243
|
+
const remaining = getMihomoPids();
|
|
4124
4244
|
if (remaining.length > 0) {
|
|
4125
4245
|
console.log("");
|
|
4126
4246
|
console.log("\u4ECD\u6709\u8FDB\u7A0B\u6B8B\u7559\uFF0C\u9700\u8981\u624B\u52A8\u6E05\u7406:");
|
|
@@ -4144,9 +4264,8 @@ function rotateLog() {
|
|
|
4144
4264
|
if (!fs5.existsSync(logFile)) return null;
|
|
4145
4265
|
const stat = fs5.statSync(logFile);
|
|
4146
4266
|
if (stat.size === 0) return null;
|
|
4147
|
-
const
|
|
4148
|
-
const
|
|
4149
|
-
const rotatedPath = path3.join(DIRS.logs, rotatedName);
|
|
4267
|
+
const rotatedName = `mihomo.${formatLocalTimestamp()}.log`;
|
|
4268
|
+
const rotatedPath = path4.join(DIRS.logs, rotatedName);
|
|
4150
4269
|
fs5.renameSync(logFile, rotatedPath);
|
|
4151
4270
|
return rotatedPath;
|
|
4152
4271
|
}
|
|
@@ -4161,7 +4280,7 @@ function cleanupOldLogs(maxAgeDays = DEFAULT_LOG_RETENTION_DAYS) {
|
|
|
4161
4280
|
for (const file of files) {
|
|
4162
4281
|
if (!file.match(/^mihomo\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.log$/)) continue;
|
|
4163
4282
|
try {
|
|
4164
|
-
const filePath =
|
|
4283
|
+
const filePath = path4.join(logsDir, file);
|
|
4165
4284
|
const stat = fs5.statSync(filePath);
|
|
4166
4285
|
if (now - stat.mtimeMs > maxAgeMs) {
|
|
4167
4286
|
fs5.unlinkSync(filePath);
|
|
@@ -4192,7 +4311,7 @@ function listLogs() {
|
|
|
4192
4311
|
const match = file.match(/^mihomo\.(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.log$/);
|
|
4193
4312
|
if (!match) continue;
|
|
4194
4313
|
try {
|
|
4195
|
-
const filePath =
|
|
4314
|
+
const filePath = path4.join(logsDir, file);
|
|
4196
4315
|
const stat = fs5.statSync(filePath);
|
|
4197
4316
|
result.archives.push({
|
|
4198
4317
|
name: file,
|
|
@@ -4208,22 +4327,22 @@ function listLogs() {
|
|
|
4208
4327
|
return result;
|
|
4209
4328
|
}
|
|
4210
4329
|
function isPathUnderDir(filePath, baseDir) {
|
|
4211
|
-
const resolvedPath =
|
|
4212
|
-
const resolvedBase =
|
|
4213
|
-
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase +
|
|
4330
|
+
const resolvedPath = path4.resolve(filePath);
|
|
4331
|
+
const resolvedBase = path4.resolve(baseDir);
|
|
4332
|
+
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + path4.sep);
|
|
4214
4333
|
}
|
|
4215
4334
|
function getLogPathByName(name) {
|
|
4216
4335
|
const logsDir = DIRS.logs;
|
|
4217
4336
|
let targetName = name;
|
|
4218
4337
|
if (!name.endsWith(".log")) targetName = `mihomo.${name}.log`;
|
|
4219
4338
|
if (!targetName.startsWith("mihomo.")) targetName = `mihomo.${targetName}`;
|
|
4220
|
-
const filePath =
|
|
4339
|
+
const filePath = path4.join(logsDir, targetName);
|
|
4221
4340
|
if (fs5.existsSync(filePath) && isPathUnderDir(filePath, logsDir)) return filePath;
|
|
4222
4341
|
if (fs5.existsSync(logsDir)) {
|
|
4223
4342
|
const files = fs5.readdirSync(logsDir);
|
|
4224
4343
|
for (const file of files) {
|
|
4225
4344
|
if (file.includes(name)) {
|
|
4226
|
-
const candidatePath =
|
|
4345
|
+
const candidatePath = path4.join(logsDir, file);
|
|
4227
4346
|
if (isPathUnderDir(candidatePath, logsDir)) return candidatePath;
|
|
4228
4347
|
}
|
|
4229
4348
|
}
|
|
@@ -4271,1021 +4390,1232 @@ function viewLogWithTail(logPath, options) {
|
|
|
4271
4390
|
});
|
|
4272
4391
|
}
|
|
4273
4392
|
|
|
4274
|
-
// src/
|
|
4275
|
-
|
|
4276
|
-
|
|
4277
|
-
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4393
|
+
// src/daemon.ts
|
|
4394
|
+
var SERVICE_TARGET = `system/${LAUNCH_DAEMON_LABEL}`;
|
|
4395
|
+
var HOT_RELOAD_TIMEOUT_MS = 5e3;
|
|
4396
|
+
var DAEMON_BOOT_WAIT_MS = 500;
|
|
4397
|
+
var LOG_ROTATE_MAX_BYTES = 10 * 1024 * 1024;
|
|
4398
|
+
function logOversized() {
|
|
4399
|
+
try {
|
|
4400
|
+
return fs6.statSync(PATHS.logFile).size > LOG_ROTATE_MAX_BYTES;
|
|
4401
|
+
} catch {
|
|
4402
|
+
return false;
|
|
4403
|
+
}
|
|
4404
|
+
}
|
|
4405
|
+
function escapeXml(s) {
|
|
4406
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4407
|
+
}
|
|
4408
|
+
function buildPlist() {
|
|
4409
|
+
const programArguments = [PATHS.mihomoBinary, "-d", DIRS.data, "-f", PATHS.configFile];
|
|
4410
|
+
const argsXml = programArguments.map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
4411
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
4412
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
4413
|
+
<plist version="1.0">
|
|
4414
|
+
<dict>
|
|
4415
|
+
<key>Label</key>
|
|
4416
|
+
<string>${escapeXml(LAUNCH_DAEMON_LABEL)}</string>
|
|
4417
|
+
<key>ProgramArguments</key>
|
|
4418
|
+
<array>
|
|
4419
|
+
${argsXml}
|
|
4420
|
+
</array>
|
|
4421
|
+
<key>RunAtLoad</key>
|
|
4422
|
+
<true/>
|
|
4423
|
+
<key>KeepAlive</key>
|
|
4424
|
+
<true/>
|
|
4425
|
+
<key>StandardOutPath</key>
|
|
4426
|
+
<string>${escapeXml(PATHS.logFile)}</string>
|
|
4427
|
+
<key>StandardErrorPath</key>
|
|
4428
|
+
<string>${escapeXml(PATHS.logFile)}</string>
|
|
4429
|
+
<key>WorkingDirectory</key>
|
|
4430
|
+
<string>/tmp</string>
|
|
4431
|
+
</dict>
|
|
4432
|
+
</plist>
|
|
4433
|
+
`;
|
|
4434
|
+
}
|
|
4435
|
+
function runSudoScript(scriptBody, opts) {
|
|
4436
|
+
if (!process.stdin.isTTY) {
|
|
4437
|
+
throw new Error("\u5F53\u524D\u73AF\u5883\u65E0\u6CD5\u8F93\u5165\u7BA1\u7406\u5458\u5BC6\u7801\uFF08\u9700\u8981\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u8FD0\u884C sudo\uFF09");
|
|
4438
|
+
}
|
|
4439
|
+
ensureDirs();
|
|
4440
|
+
const scriptPath = path5.join(DIRS.runtime, opts.file);
|
|
4441
|
+
fs6.writeFileSync(scriptPath, scriptBody, { mode: 448 });
|
|
4442
|
+
try {
|
|
4443
|
+
const result = spawnSync4("sudo", [scriptPath], { stdio: "inherit", timeout: SUDO_TIMEOUT_MS });
|
|
4444
|
+
if (result.error) throw result.error;
|
|
4445
|
+
if (result.status !== 0) {
|
|
4446
|
+
if (result.status === 1) {
|
|
4447
|
+
throw new Error("\u5DF2\u53D6\u6D88\u6216\u5BC6\u7801\u9519\u8BEF");
|
|
4284
4448
|
}
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
const key = target.toLowerCase();
|
|
4288
|
-
const targetInfo = Object.hasOwn(DIRECTORY_TARGETS, key) ? DIRECTORY_TARGETS[key] : void 0;
|
|
4289
|
-
if (targetInfo) {
|
|
4290
|
-
const targetPath = targetInfo.path || USER_DATA_DIR;
|
|
4291
|
-
console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
|
|
4292
|
-
const success = openUrl(targetPath);
|
|
4293
|
-
if (!success) {
|
|
4294
|
-
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
|
|
4449
|
+
if (result.status == null) {
|
|
4450
|
+
throw new Error(`${opts.action}\u88AB\u4E2D\u65AD\uFF08sudo \u8FDB\u7A0B\u88AB\u4FE1\u53F7\u7EC8\u6B62\uFF09`);
|
|
4295
4451
|
}
|
|
4296
|
-
|
|
4452
|
+
const custom = opts.codeMessages?.[result.status];
|
|
4453
|
+
throw new Error(custom || `${opts.action}\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${result.status}\uFF09`);
|
|
4297
4454
|
}
|
|
4298
|
-
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4303
|
-
|
|
4304
|
-
|
|
4455
|
+
} finally {
|
|
4456
|
+
try {
|
|
4457
|
+
fs6.unlinkSync(scriptPath);
|
|
4458
|
+
} catch {
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
function isDaemonEnabled() {
|
|
4463
|
+
return fs6.existsSync(PATHS.launchDaemonPlist);
|
|
4464
|
+
}
|
|
4465
|
+
function getDaemonStatus() {
|
|
4466
|
+
if (!isDaemonEnabled()) {
|
|
4467
|
+
return { enabled: false, loaded: false, pid: null };
|
|
4468
|
+
}
|
|
4469
|
+
const rootPids = getMihomoPids().filter(isProcessRoot);
|
|
4470
|
+
return { enabled: true, loaded: rootPids.length > 0, pid: rootPids[0] ?? null };
|
|
4471
|
+
}
|
|
4472
|
+
function isDaemonRunning(status) {
|
|
4473
|
+
return status.loaded && status.pid !== null;
|
|
4474
|
+
}
|
|
4475
|
+
function enableDaemon() {
|
|
4476
|
+
if (!fs6.existsSync(PATHS.mihomoBinary)) {
|
|
4477
|
+
throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838\uFF0C\u8BF7\u5148\u4E0B\u8F7D\u5185\u6838");
|
|
4478
|
+
}
|
|
4479
|
+
if (!fs6.existsSync(PATHS.configFile)) {
|
|
4480
|
+
throw new Error("\u672A\u627E\u5230\u8FD0\u884C\u65F6\u914D\u7F6E\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
4481
|
+
}
|
|
4482
|
+
ensureDirs();
|
|
4483
|
+
const stagePath = path5.join(DIRS.runtime, "daemon.plist.stage");
|
|
4484
|
+
atomicWriteFileSync(stagePath, buildPlist(), { mode: 384 });
|
|
4485
|
+
const target = shellQuote(SERVICE_TARGET);
|
|
4486
|
+
const plistDest = shellQuote(PATHS.launchDaemonPlist);
|
|
4487
|
+
const stage = shellQuote(stagePath);
|
|
4488
|
+
const pattern = shellQuote(MAIN_INSTANCE_PATTERN);
|
|
4489
|
+
const script = [
|
|
4490
|
+
"#!/bin/bash",
|
|
4491
|
+
`launchctl bootout ${target} 2>/dev/null || true`,
|
|
4492
|
+
`pkill -9 -f ${pattern} 2>/dev/null || true`,
|
|
4493
|
+
"sleep 0.2",
|
|
4494
|
+
`install -m 644 -o root -g wheel ${stage} ${plistDest} || exit 2`,
|
|
4495
|
+
`launchctl bootstrap system ${plistDest} || { launchctl bootout ${target} 2>/dev/null; rm -f ${plistDest}; exit 3; }`,
|
|
4496
|
+
"exit 0",
|
|
4497
|
+
""
|
|
4498
|
+
].join("\n");
|
|
4499
|
+
try {
|
|
4500
|
+
runSudoScript(script, {
|
|
4501
|
+
action: "\u542F\u7528\u4FDD\u6D3B",
|
|
4502
|
+
file: "daemon-enable.sh",
|
|
4503
|
+
codeMessages: {
|
|
4504
|
+
2: "\u5B89\u88C5 plist \u5230 /Library/LaunchDaemons \u5931\u8D25",
|
|
4505
|
+
3: "\u88C5\u8F7D\u4FDD\u6D3B\u670D\u52A1\u5931\u8D25\uFF08launchctl bootstrap\uFF09"
|
|
4305
4506
|
}
|
|
4507
|
+
});
|
|
4508
|
+
} finally {
|
|
4509
|
+
try {
|
|
4510
|
+
fs6.unlinkSync(stagePath);
|
|
4511
|
+
} catch {
|
|
4306
4512
|
}
|
|
4513
|
+
}
|
|
4514
|
+
}
|
|
4515
|
+
function disableDaemon() {
|
|
4516
|
+
if (!isDaemonEnabled()) return;
|
|
4517
|
+
const target = shellQuote(SERVICE_TARGET);
|
|
4518
|
+
const plistDest = shellQuote(PATHS.launchDaemonPlist);
|
|
4519
|
+
const logFile = shellQuote(PATHS.logFile);
|
|
4520
|
+
const dataDir = shellQuote(DIRS.data);
|
|
4521
|
+
const script = [
|
|
4522
|
+
"#!/bin/bash",
|
|
4523
|
+
`launchctl bootout ${target} 2>/dev/null || true`,
|
|
4524
|
+
`rm -f ${plistDest}`,
|
|
4525
|
+
`chown "$SUDO_UID:$SUDO_GID" ${logFile} 2>/dev/null || true`,
|
|
4526
|
+
`chown -R "$SUDO_UID:$SUDO_GID" ${dataDir} 2>/dev/null || true`,
|
|
4527
|
+
"exit 0",
|
|
4528
|
+
""
|
|
4529
|
+
].join("\n");
|
|
4530
|
+
runSudoScript(script, { action: "\u5173\u95ED\u4FDD\u6D3B", file: "daemon-disable.sh" });
|
|
4531
|
+
const rootPids = getMihomoPids().filter(isProcessRoot);
|
|
4532
|
+
if (rootPids.length > 0) {
|
|
4307
4533
|
console.log("");
|
|
4308
|
-
|
|
4534
|
+
console.log(`\u4ECD\u6709 root \u5185\u6838\u8FDB\u7A0B\u6B8B\u7559 (PID ${rootPids.join(", ")})`);
|
|
4535
|
+
console.log("\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo");
|
|
4309
4536
|
}
|
|
4310
|
-
console.log("");
|
|
4311
|
-
console.log("\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E:");
|
|
4312
|
-
console.log(` \u6839\u76EE\u5F55: ${USER_DATA_DIR}`);
|
|
4313
|
-
console.log(` \u5168\u5C40\u8BBE\u7F6E: ${PATHS.settingsFile}`);
|
|
4314
|
-
console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
|
|
4315
|
-
console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
|
|
4316
|
-
console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
|
|
4317
|
-
console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
|
|
4318
|
-
console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
|
|
4319
|
-
console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
|
|
4320
|
-
console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
4321
|
-
console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
4322
|
-
console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
|
|
4323
|
-
console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
|
|
4324
|
-
console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
|
|
4325
|
-
console.log("");
|
|
4326
|
-
console.log("\u6253\u5F00\u76EE\u5F55:");
|
|
4327
|
-
console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
|
|
4328
|
-
console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
|
|
4329
|
-
console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
|
|
4330
|
-
console.log(" mihomo dir open data \u6253\u5F00 mihomo \u6570\u636E\u76EE\u5F55");
|
|
4331
|
-
console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
|
|
4332
|
-
console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
|
|
4333
|
-
console.log("");
|
|
4334
|
-
console.log("\u73AF\u5883\u53D8\u91CF:");
|
|
4335
|
-
console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
|
|
4336
|
-
console.log("");
|
|
4337
4537
|
}
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4343
|
-
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
4350
|
-
|
|
4351
|
-
|
|
4538
|
+
async function tryHotReload() {
|
|
4539
|
+
const controller = new AbortController();
|
|
4540
|
+
const timer = setTimeout(() => controller.abort(), HOT_RELOAD_TIMEOUT_MS);
|
|
4541
|
+
try {
|
|
4542
|
+
const res = await fetch(`${CONTROLLER_BASE_URL}/configs?force=true`, {
|
|
4543
|
+
method: "PUT",
|
|
4544
|
+
headers: { "Content-Type": "application/json" },
|
|
4545
|
+
body: "{}",
|
|
4546
|
+
signal: controller.signal
|
|
4547
|
+
});
|
|
4548
|
+
return res.status === 204 || res.ok;
|
|
4549
|
+
} catch {
|
|
4550
|
+
return false;
|
|
4551
|
+
} finally {
|
|
4552
|
+
clearTimeout(timer);
|
|
4553
|
+
}
|
|
4554
|
+
}
|
|
4555
|
+
async function restartDaemon() {
|
|
4556
|
+
if (!fs6.existsSync(PATHS.launchDaemonPlist)) {
|
|
4557
|
+
throw new Error("\u4FDD\u6D3B\u672A\u542F\u7528\uFF0C\u65E0\u6CD5\u91CD\u542F");
|
|
4558
|
+
}
|
|
4559
|
+
if (!logOversized() && await tryHotReload()) return;
|
|
4560
|
+
const target = shellQuote(SERVICE_TARGET);
|
|
4561
|
+
const plistDest = shellQuote(PATHS.launchDaemonPlist);
|
|
4562
|
+
const logFile = shellQuote(PATHS.logFile);
|
|
4563
|
+
const archiveFile = shellQuote(path5.join(DIRS.logs, `mihomo.${formatLocalTimestamp()}.log`));
|
|
4564
|
+
const script = [
|
|
4565
|
+
"#!/bin/bash",
|
|
4566
|
+
`if [ -f ${logFile} ] && [ "$(stat -f%z ${logFile} 2>/dev/null || echo 0)" -gt ${LOG_ROTATE_MAX_BYTES} ]; then`,
|
|
4567
|
+
` cp ${logFile} ${archiveFile} 2>/dev/null && : > ${logFile}`,
|
|
4568
|
+
"fi",
|
|
4569
|
+
`if launchctl kickstart -k ${target} 2>/dev/null; then exit 0; fi`,
|
|
4570
|
+
`launchctl bootstrap system ${plistDest} || exit 3`,
|
|
4571
|
+
"exit 0",
|
|
4572
|
+
""
|
|
4573
|
+
].join("\n");
|
|
4574
|
+
runSudoScript(script, {
|
|
4575
|
+
action: "\u91CD\u542F\u4FDD\u6D3B",
|
|
4576
|
+
file: "daemon-restart.sh",
|
|
4577
|
+
codeMessages: { 3: "\u91CD\u542F\u4FDD\u6D3B\u5931\u8D25\uFF08launchctl bootstrap\uFF09" }
|
|
4578
|
+
});
|
|
4579
|
+
cleanupOldLogs();
|
|
4352
4580
|
}
|
|
4353
|
-
function printHelp() {
|
|
4354
|
-
console.log(
|
|
4355
|
-
`
|
|
4356
|
-
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}
|
|
4357
|
-
|
|
4358
|
-
\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh
|
|
4359
|
-
|
|
4360
|
-
\u7528\u6CD5:
|
|
4361
|
-
mihomo <\u547D\u4EE4> [\u9009\u9879]
|
|
4362
|
-
|
|
4363
|
-
${colors.cyan("\u63A7\u5236:")}
|
|
4364
|
-
${colors.bold("start")} [tun|mixed] [-s] [-u ms] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)
|
|
4365
|
-
[-r N] [-t ms] [-j N]
|
|
4366
|
-
${colors.bold("stop")} \u505C\u6B62\u4EE3\u7406
|
|
4367
|
-
${colors.bold("status")} \u67E5\u770B\u72B6\u6001
|
|
4368
|
-
|
|
4369
|
-
${colors.cyan("\u754C\u9762:")}
|
|
4370
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)
|
|
4371
|
-
${colors.bold("log")} [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09
|
|
4372
|
-
${colors.bold("logs")} [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09
|
|
4373
|
-
|
|
4374
|
-
${colors.cyan("\u8BA2\u9605:")}
|
|
4375
|
-
${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
|
|
4376
|
-
${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
|
|
4377
|
-
${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
|
|
4378
|
-
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
4379
|
-
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
4380
|
-
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
4381
|
-
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
4382
|
-
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
4383
|
-
${colors.bold("test")} [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027
|
|
4384
|
-
${colors.bold("clean")} [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F
|
|
4385
4581
|
|
|
4386
|
-
|
|
4387
|
-
|
|
4388
|
-
|
|
4389
|
-
${colors.bold("directory")} \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09
|
|
4390
|
-
${colors.bold("directory")} open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|...
|
|
4391
|
-
|
|
4392
|
-
${colors.cyan("\u7CFB\u7EDF:")}
|
|
4393
|
-
${colors.bold("kernel")} [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09
|
|
4394
|
-
${colors.bold("update")} \u66F4\u65B0 mihomo-cli (npm install -g)
|
|
4395
|
-
${colors.bold("reset")} [\u76EE\u6807...] [--full] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8
|
|
4396
|
-
${colors.bold("help")}, -h \u663E\u793A\u5E2E\u52A9
|
|
4397
|
-
${colors.bold("version")}, -v \u663E\u793A\u7248\u672C
|
|
4398
|
-
|
|
4399
|
-
${colors.cyan("\u793A\u4F8B:")}
|
|
4400
|
-
mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F
|
|
4401
|
-
mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F
|
|
4402
|
-
mihomo start -s # \u8DF3\u8FC7\u81EA\u52A8\u66F4\u65B0\u8BA2\u9605
|
|
4403
|
-
mihomo start -u 30000 # \u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 30 \u79D2 (\u9ED8\u8BA4 10s)
|
|
4404
|
-
mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)
|
|
4405
|
-
mihomo ui # \u6253\u5F00 Web UI
|
|
4406
|
-
|
|
4407
|
-
${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}
|
|
4408
|
-
mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)
|
|
4409
|
-
tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo
|
|
4410
|
-
|
|
4411
|
-
${colors.cyan("\u6570\u636E\u76EE\u5F55:")}
|
|
4412
|
-
\u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E
|
|
4413
|
-
\u9ED8\u8BA4: ${USER_DATA_DIR}
|
|
4414
|
-
`
|
|
4415
|
-
);
|
|
4582
|
+
// src/subscription.ts
|
|
4583
|
+
function isGithubUrl(url) {
|
|
4584
|
+
return /github\.com|raw\.githubusercontent\.com/i.test(url);
|
|
4416
4585
|
}
|
|
4417
|
-
function
|
|
4418
|
-
|
|
4419
|
-
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
4420
|
-
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
4421
|
-
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
4586
|
+
function getDefaultUpdateInterval(url) {
|
|
4587
|
+
return isGithubUrl(url) ? DEFAULT_UPDATE_INTERVAL_HOURS_GITHUB : DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4422
4588
|
}
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
import { spawnSync as spawnSync4 } from "child_process";
|
|
4426
|
-
import fs6 from "fs";
|
|
4427
|
-
import path4 from "path";
|
|
4428
|
-
|
|
4429
|
-
// node_modules/compare-versions/lib/esm/utils.js
|
|
4430
|
-
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
4431
|
-
var validateAndParse = (version) => {
|
|
4432
|
-
if (typeof version !== "string") {
|
|
4433
|
-
throw new TypeError("Invalid argument expected string");
|
|
4434
|
-
}
|
|
4435
|
-
const match = version.match(semver);
|
|
4436
|
-
if (!match) {
|
|
4437
|
-
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
|
|
4438
|
-
}
|
|
4439
|
-
match.shift();
|
|
4440
|
-
return match;
|
|
4441
|
-
};
|
|
4442
|
-
var isWildcard = (s) => s === "*" || s === "x" || s === "X";
|
|
4443
|
-
var tryParse = (v) => {
|
|
4444
|
-
const n = parseInt(v, 10);
|
|
4445
|
-
return isNaN(n) ? v : n;
|
|
4446
|
-
};
|
|
4447
|
-
var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
|
|
4448
|
-
var compareStrings = (a, b) => {
|
|
4449
|
-
if (isWildcard(a) || isWildcard(b))
|
|
4450
|
-
return 0;
|
|
4451
|
-
const [ap, bp] = forceType(tryParse(a), tryParse(b));
|
|
4452
|
-
if (ap > bp)
|
|
4453
|
-
return 1;
|
|
4454
|
-
if (ap < bp)
|
|
4455
|
-
return -1;
|
|
4456
|
-
return 0;
|
|
4457
|
-
};
|
|
4458
|
-
var compareSegments = (a, b) => {
|
|
4459
|
-
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
4460
|
-
const r = compareStrings(a[i] || "0", b[i] || "0");
|
|
4461
|
-
if (r !== 0)
|
|
4462
|
-
return r;
|
|
4463
|
-
}
|
|
4464
|
-
return 0;
|
|
4465
|
-
};
|
|
4466
|
-
|
|
4467
|
-
// node_modules/compare-versions/lib/esm/compareVersions.js
|
|
4468
|
-
var compareVersions = (v1, v2) => {
|
|
4469
|
-
const n1 = validateAndParse(v1);
|
|
4470
|
-
const n2 = validateAndParse(v2);
|
|
4471
|
-
const p1 = n1.pop();
|
|
4472
|
-
const p2 = n2.pop();
|
|
4473
|
-
const r = compareSegments(n1, n2);
|
|
4474
|
-
if (r !== 0)
|
|
4475
|
-
return r;
|
|
4476
|
-
if (p1 && p2) {
|
|
4477
|
-
return compareSegments(p1.split("."), p2.split("."));
|
|
4478
|
-
} else if (p1 || p2) {
|
|
4479
|
-
return p1 ? -1 : 1;
|
|
4480
|
-
}
|
|
4481
|
-
return 0;
|
|
4482
|
-
};
|
|
4483
|
-
|
|
4484
|
-
// src/kernel.ts
|
|
4485
|
-
var GITHUB_REPO = "MetaCubeX/mihomo";
|
|
4486
|
-
var KERNEL_HTTP_TIMEOUT = 12e4;
|
|
4487
|
-
var KERNEL_DOWNLOAD_TIMEOUT = 18e4;
|
|
4488
|
-
var HTTP_CLIENT = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT });
|
|
4489
|
-
function withMirror(url, mirror) {
|
|
4490
|
-
if (mirror && (url.startsWith("https://github.com/") || url.startsWith("https://api.github.com/"))) {
|
|
4491
|
-
return mirror + url;
|
|
4492
|
-
}
|
|
4493
|
-
return url;
|
|
4589
|
+
function resolveUpdateInterval(url, cachedInterval) {
|
|
4590
|
+
return cachedInterval && cachedInterval > 0 ? cachedInterval : getDefaultUpdateInterval(url);
|
|
4494
4591
|
}
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
if (arch === "x64") return "amd64";
|
|
4499
|
-
return arch;
|
|
4592
|
+
var HTTP_CLIENT = createHttpClient({ timeout: 6e4 });
|
|
4593
|
+
function isMultiUrl(url) {
|
|
4594
|
+
return url.includes(",");
|
|
4500
4595
|
}
|
|
4501
|
-
function
|
|
4502
|
-
|
|
4503
|
-
const matchingAssets = assets.filter(
|
|
4504
|
-
(a) => a.name.startsWith(prefix) && a.name.endsWith(".gz") || a.name.startsWith(`${prefix}-`) && a.name.endsWith(".gz")
|
|
4505
|
-
);
|
|
4506
|
-
if (matchingAssets.length === 0) return null;
|
|
4507
|
-
if (matchingAssets.length === 1) return matchingAssets[0];
|
|
4508
|
-
const standardAsset = matchingAssets.find((a) => {
|
|
4509
|
-
const nameWithoutGz = a.name.slice(0, -3);
|
|
4510
|
-
const parts = nameWithoutGz.split("-");
|
|
4511
|
-
const lastPart = parts[parts.length - 1];
|
|
4512
|
-
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go");
|
|
4513
|
-
});
|
|
4514
|
-
return standardAsset || matchingAssets[0];
|
|
4596
|
+
function splitUrls(url) {
|
|
4597
|
+
return url.split(",").map((u) => u.trim()).filter(Boolean);
|
|
4515
4598
|
}
|
|
4516
|
-
|
|
4517
|
-
const
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
if (!Array.isArray(releases) || releases.length === 0) {
|
|
4521
|
-
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7248\u672C\u4FE1\u606F");
|
|
4599
|
+
function loadSubscriptionConfig(subName) {
|
|
4600
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4601
|
+
if (!rawContent) {
|
|
4602
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
4522
4603
|
}
|
|
4523
|
-
const
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4604
|
+
const raw = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
4605
|
+
return {
|
|
4606
|
+
raw,
|
|
4607
|
+
proxies: raw.proxies || [],
|
|
4608
|
+
proxyGroups: raw["proxy-groups"] || []
|
|
4609
|
+
};
|
|
4527
4610
|
}
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4611
|
+
function saveSubscriptionConfig(subName, parsed) {
|
|
4612
|
+
normalizeProxyNamesBeforeSave(parsed);
|
|
4613
|
+
parsed.raw.proxies = parsed.proxies;
|
|
4614
|
+
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4615
|
+
saveSubscriptionRawConfig(subName, dumpYaml(parsed.raw));
|
|
4616
|
+
}
|
|
4617
|
+
function parseUserInfo(header) {
|
|
4618
|
+
if (!header) return null;
|
|
4619
|
+
const info = {};
|
|
4620
|
+
const parts = header.split(";").map((p) => p.trim());
|
|
4621
|
+
for (const part of parts) {
|
|
4622
|
+
const [key, val] = part.split("=").map((s) => s.trim());
|
|
4623
|
+
if (key && val !== void 0) {
|
|
4624
|
+
const numVal = parseFloat(val);
|
|
4625
|
+
info[key] = Number.isNaN(numVal) ? 0 : numVal;
|
|
4541
4626
|
}
|
|
4542
4627
|
}
|
|
4628
|
+
return info;
|
|
4629
|
+
}
|
|
4630
|
+
function parsePositiveInterval(header) {
|
|
4631
|
+
if (!header) return null;
|
|
4632
|
+
const n = parseInt(header, 10);
|
|
4633
|
+
return Number.isFinite(n) && n > 0 ? n : null;
|
|
4634
|
+
}
|
|
4635
|
+
function parseUsernameFromContentDisposition(header) {
|
|
4636
|
+
if (!header) return null;
|
|
4637
|
+
const match = header.match(/filename\s*=\s*["']?([^"';\s]+)["']?/i);
|
|
4638
|
+
if (!match) return null;
|
|
4639
|
+
const filename = match[1];
|
|
4640
|
+
const parts = filename.split("/");
|
|
4641
|
+
return parts[parts.length - 1] || null;
|
|
4642
|
+
}
|
|
4643
|
+
function extractSubscriptionMeta(headers) {
|
|
4543
4644
|
return {
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
release: latest
|
|
4645
|
+
userInfo: parseUserInfo(headers?.get("subscription-userinfo") ?? null),
|
|
4646
|
+
updateInterval: parsePositiveInterval(headers?.get("profile-update-interval")),
|
|
4647
|
+
webPageUrl: headers?.get("profile-web-page-url") || null,
|
|
4648
|
+
username: parseUsernameFromContentDisposition(headers?.get("content-disposition") ?? null)
|
|
4549
4649
|
};
|
|
4550
4650
|
}
|
|
4551
|
-
function
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
const found = findBinaryInDir(fullPath, maxDepth - 1);
|
|
4559
|
-
if (found) return found;
|
|
4560
|
-
continue;
|
|
4561
|
-
}
|
|
4562
|
-
if (f === "mihomo") return fullPath;
|
|
4563
|
-
if (f.includes("mihomo") && !f.endsWith(".gz")) return fullPath;
|
|
4651
|
+
function saveSubscriptionMeta(subName, meta) {
|
|
4652
|
+
const cacheData = { updated_at: (/* @__PURE__ */ new Date()).toISOString() };
|
|
4653
|
+
if (meta.userInfo) {
|
|
4654
|
+
cacheData.upload = meta.userInfo.upload;
|
|
4655
|
+
cacheData.download = meta.userInfo.download;
|
|
4656
|
+
cacheData.total = meta.userInfo.total;
|
|
4657
|
+
cacheData.expire = meta.userInfo.expire;
|
|
4564
4658
|
}
|
|
4565
|
-
|
|
4659
|
+
if (meta.updateInterval) cacheData.update_interval = meta.updateInterval;
|
|
4660
|
+
if (meta.webPageUrl) cacheData.web_page_url = meta.webPageUrl;
|
|
4661
|
+
if (meta.username) cacheData.username = meta.username;
|
|
4662
|
+
saveSubscriptionCache(subName, cacheData);
|
|
4566
4663
|
}
|
|
4567
|
-
|
|
4568
|
-
|
|
4569
|
-
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
|
|
4573
|
-
|
|
4574
|
-
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4664
|
+
function formatProxySummary(info) {
|
|
4665
|
+
const parts = [];
|
|
4666
|
+
if (info.proxyGroups && info.proxyGroups > 0) parts.push(`${info.proxyGroups} \u7EC4`);
|
|
4667
|
+
parts.push(`${info.proxies || 0} \u8282\u70B9`);
|
|
4668
|
+
return parts.join(", ");
|
|
4669
|
+
}
|
|
4670
|
+
function getActiveSubscription() {
|
|
4671
|
+
const subs = getSubscriptions();
|
|
4672
|
+
if (subs.length === 0) return null;
|
|
4673
|
+
const settings = readSettings();
|
|
4674
|
+
const activeName = settings.active_subscription;
|
|
4675
|
+
if (activeName) {
|
|
4676
|
+
const found = subs.find((s) => s.name === activeName);
|
|
4677
|
+
if (found) return found;
|
|
4580
4678
|
}
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
|
|
4586
|
-
|
|
4587
|
-
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
try {
|
|
4597
|
-
fs6.unlinkSync(tempPath);
|
|
4598
|
-
} catch {
|
|
4679
|
+
return subs[0];
|
|
4680
|
+
}
|
|
4681
|
+
function findSubscriptionFuzzy(subs, pattern) {
|
|
4682
|
+
const lowerPattern = pattern.toLowerCase();
|
|
4683
|
+
const exact = [];
|
|
4684
|
+
const prefix = [];
|
|
4685
|
+
const includes = [];
|
|
4686
|
+
for (const s of subs) {
|
|
4687
|
+
const name = s.name.toLowerCase();
|
|
4688
|
+
if (name === lowerPattern) {
|
|
4689
|
+
exact.push(s);
|
|
4690
|
+
} else if (name.startsWith(lowerPattern)) {
|
|
4691
|
+
prefix.push(s);
|
|
4692
|
+
} else if (name.includes(lowerPattern)) {
|
|
4693
|
+
includes.push(s);
|
|
4599
4694
|
}
|
|
4600
|
-
throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
|
|
4601
|
-
}
|
|
4602
|
-
if (!fs6.existsSync(tempPath)) {
|
|
4603
|
-
throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u6587\u4EF6\u672A\u751F\u6210");
|
|
4604
4695
|
}
|
|
4605
|
-
if (
|
|
4606
|
-
|
|
4696
|
+
if (exact.length > 0) return exact;
|
|
4697
|
+
if (prefix.length > 0) return prefix;
|
|
4698
|
+
return includes;
|
|
4699
|
+
}
|
|
4700
|
+
function pickSingleSubscription(subs, pattern) {
|
|
4701
|
+
if (subs.length === 0) {
|
|
4702
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
|
|
4703
|
+
process.exit(1);
|
|
4607
4704
|
}
|
|
4608
|
-
|
|
4609
|
-
|
|
4705
|
+
if (subs.length === 1) return subs[0];
|
|
4706
|
+
console.error("\u9519\u8BEF: \u5339\u914D\u5230\u591A\u4E2A\u8BA2\u9605\uFF0C\u8BF7\u66F4\u7CBE\u786E\u6307\u5B9A");
|
|
4707
|
+
console.log("\n\u5339\u914D\u7684\u8BA2\u9605:");
|
|
4708
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
4709
|
+
process.exit(1);
|
|
4710
|
+
}
|
|
4711
|
+
async function downloadSubscription(url, subName = "default", signal) {
|
|
4712
|
+
let response;
|
|
4610
4713
|
try {
|
|
4611
|
-
|
|
4612
|
-
const tarResult = spawnSync4("tar", ["-xzf", tempPath, "-C", extractPath], { stdio: ["ignore", "ignore", "inherit"], timeout: 6e4 });
|
|
4613
|
-
if (tarResult.error) throw tarResult.error;
|
|
4614
|
-
if (tarResult.status !== 0) throw new Error(`tar \u9000\u51FA\u7801 ${tarResult.status}`);
|
|
4615
|
-
} else if (tempPath.endsWith(".gz")) {
|
|
4616
|
-
const baseName = path4.basename(tempPath, ".gz");
|
|
4617
|
-
const outputPath = path4.join(extractPath, baseName);
|
|
4618
|
-
const gzipResult = spawnSync4("gzip", ["-dc", tempPath], { maxBuffer: 256 * 1024 * 1024, timeout: 6e4 });
|
|
4619
|
-
if (gzipResult.error) throw gzipResult.error;
|
|
4620
|
-
if (gzipResult.status !== 0) throw new Error(`gzip \u9000\u51FA\u7801 ${gzipResult.status}`);
|
|
4621
|
-
fs6.writeFileSync(outputPath, gzipResult.stdout, { mode: 493 });
|
|
4622
|
-
extractedBinary = outputPath;
|
|
4623
|
-
}
|
|
4714
|
+
response = await HTTP_CLIENT.get(url, { responseType: "text", signal });
|
|
4624
4715
|
} catch (e) {
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4716
|
+
const maskedUrl = maskUrl(url);
|
|
4717
|
+
let errorMsg = `\u83B7\u53D6\u8BA2\u9605\u5931\u8D25: ${e.message}`;
|
|
4718
|
+
const err = e;
|
|
4719
|
+
if (err.response) {
|
|
4720
|
+
errorMsg += ` (HTTP ${err.response.status})`;
|
|
4628
4721
|
}
|
|
4629
|
-
|
|
4722
|
+
errorMsg += `
|
|
4723
|
+
URL: ${maskedUrl}`;
|
|
4724
|
+
throw new Error(errorMsg);
|
|
4630
4725
|
}
|
|
4631
|
-
const
|
|
4632
|
-
if (!
|
|
4633
|
-
|
|
4634
|
-
fs6.unlinkSync(tempPath);
|
|
4635
|
-
} catch {
|
|
4636
|
-
}
|
|
4637
|
-
throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
|
|
4726
|
+
const content = response.data;
|
|
4727
|
+
if (!content?.trim()) {
|
|
4728
|
+
throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4638
4729
|
}
|
|
4639
|
-
const
|
|
4640
|
-
if (
|
|
4641
|
-
|
|
4642
|
-
|
|
4730
|
+
const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
|
|
4731
|
+
if (!parsed) throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4732
|
+
saveSubscriptionRawConfig(subName, content);
|
|
4733
|
+
const meta = extractSubscriptionMeta(response.headers);
|
|
4734
|
+
saveSubscriptionMeta(subName, meta);
|
|
4735
|
+
const proxies = parsed.proxies;
|
|
4736
|
+
const proxyGroups = parsed["proxy-groups"];
|
|
4737
|
+
return {
|
|
4738
|
+
proxies: proxies ? proxies.length : 0,
|
|
4739
|
+
proxyGroups: proxyGroups ? proxyGroups.length : 0,
|
|
4740
|
+
userInfo: meta.userInfo,
|
|
4741
|
+
updateInterval: meta.updateInterval,
|
|
4742
|
+
webPageUrl: meta.webPageUrl,
|
|
4743
|
+
username: meta.username
|
|
4744
|
+
};
|
|
4745
|
+
}
|
|
4746
|
+
async function downloadMergedSubscription(urls, subName, signal) {
|
|
4747
|
+
const responses = await Promise.all(
|
|
4748
|
+
urls.map(async (url, index) => {
|
|
4643
4749
|
try {
|
|
4644
|
-
|
|
4645
|
-
|
|
4750
|
+
const response = await HTTP_CLIENT.get(url, { responseType: "text", signal });
|
|
4751
|
+
return { url, index, response, error: null };
|
|
4752
|
+
} catch (e) {
|
|
4753
|
+
return { url, index, response: null, error: e };
|
|
4646
4754
|
}
|
|
4755
|
+
})
|
|
4756
|
+
);
|
|
4757
|
+
for (const r of responses) {
|
|
4758
|
+
if (r.error) {
|
|
4759
|
+
const maskedUrl = maskUrl(r.url);
|
|
4760
|
+
throw new Error(`\u5408\u5E76\u8BA2\u9605\u7B2C ${r.index + 1} \u4E2A URL \u83B7\u53D6\u5931\u8D25: ${r.error.message}
|
|
4761
|
+
URL: ${maskedUrl}`);
|
|
4647
4762
|
}
|
|
4648
|
-
fs6.renameSync(foundBinary, targetPath);
|
|
4649
|
-
}
|
|
4650
|
-
fs6.chmodSync(targetPath, 493);
|
|
4651
|
-
if (progressCallback) {
|
|
4652
|
-
progressCallback("\u6821\u9A8C\u5185\u6838...");
|
|
4653
4763
|
}
|
|
4654
|
-
const
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4764
|
+
const parsed = responses.map((r, i) => {
|
|
4765
|
+
const content = r.response?.data;
|
|
4766
|
+
if (!content?.trim()) throw new Error(`\u5408\u5E76\u8BA2\u9605\u7B2C ${i + 1} \u4E2A URL \u5185\u5BB9\u4E3A\u7A7A`);
|
|
4767
|
+
return parseYamlOrJson(content, `\u5408\u5E76\u8BA2\u9605\u7B2C ${i + 1} \u4E2A`);
|
|
4768
|
+
});
|
|
4769
|
+
const base = parsed[0];
|
|
4770
|
+
const baseProxies = base.proxies || [];
|
|
4771
|
+
const seenNames = new Set(baseProxies.map((p) => p.name));
|
|
4772
|
+
for (let i = 1; i < parsed.length; i++) {
|
|
4773
|
+
const extraProxies = parsed[i].proxies || [];
|
|
4774
|
+
for (const proxy of extraProxies) {
|
|
4775
|
+
if (!seenNames.has(proxy.name)) {
|
|
4776
|
+
baseProxies.push(proxy);
|
|
4777
|
+
seenNames.add(proxy.name);
|
|
4778
|
+
}
|
|
4664
4779
|
}
|
|
4665
|
-
throw new Error(`\u5185\u6838\u81EA\u68C0\u5931\u8D25\uFF08\u53EF\u80FD\u4E0B\u8F7D\u635F\u574F\u6216\u67B6\u6784\u4E0D\u5339\u914D\uFF09\uFF0C\u5DF2\u5220\u9664
|
|
4666
|
-
\u9000\u51FA\u7801: ${check.status}
|
|
4667
|
-
\u8F93\u51FA: ${checkOutput || "(\u7A7A)"}`);
|
|
4668
|
-
}
|
|
4669
|
-
try {
|
|
4670
|
-
fs6.unlinkSync(tempPath);
|
|
4671
|
-
} catch {
|
|
4672
4780
|
}
|
|
4673
|
-
|
|
4674
|
-
|
|
4781
|
+
base.proxies = baseProxies;
|
|
4782
|
+
const mergedContent = dumpYaml(base);
|
|
4783
|
+
saveSubscriptionRawConfig(subName, mergedContent);
|
|
4784
|
+
const meta = extractSubscriptionMeta(responses[0].response?.headers);
|
|
4785
|
+
saveSubscriptionMeta(subName, meta);
|
|
4786
|
+
const proxyGroups = base["proxy-groups"];
|
|
4787
|
+
return {
|
|
4788
|
+
proxies: baseProxies.length,
|
|
4789
|
+
proxyGroups: proxyGroups ? proxyGroups.length : 0,
|
|
4790
|
+
userInfo: meta.userInfo,
|
|
4791
|
+
updateInterval: meta.updateInterval,
|
|
4792
|
+
webPageUrl: meta.webPageUrl,
|
|
4793
|
+
username: meta.username
|
|
4794
|
+
};
|
|
4675
4795
|
}
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
const effectiveMirror = mirrorInfo.mirror;
|
|
4681
|
-
if (effectiveMirror) {
|
|
4682
|
-
const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
|
|
4683
|
-
console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
|
|
4796
|
+
function prepareConfigForStart(mode, subName = "default") {
|
|
4797
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
4798
|
+
if (!rawContent) {
|
|
4799
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
|
|
4684
4800
|
}
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
console.log(" mihomo kernel --mirror-all hk.gh-proxy.org # API\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
4692
|
-
console.log("\n\u53EF\u7528\u955C\u50CF:");
|
|
4693
|
-
for (const m of AVAILABLE_MIRRORS) {
|
|
4694
|
-
const isCurrent = effectiveMirror && (effectiveMirror.includes(`//${m}/`) || effectiveMirror.includes(`//${m}:`) || effectiveMirror.endsWith(`//${m}`));
|
|
4695
|
-
console.log(` ${m}${isCurrent ? " (\u5F53\u524D)" : ""}`);
|
|
4801
|
+
const buildResult = buildConfig(rawContent, mode);
|
|
4802
|
+
if (buildResult.warnings.length > 0) {
|
|
4803
|
+
for (const warning of buildResult.warnings) {
|
|
4804
|
+
console.log(`${colors.yellow("\u81EA\u52A8\u4FEE\u590D:")} ${warning}`);
|
|
4805
|
+
}
|
|
4806
|
+
console.log("");
|
|
4696
4807
|
}
|
|
4697
|
-
|
|
4698
|
-
|
|
4808
|
+
writeMihomoConfig(buildResult.config);
|
|
4809
|
+
writeDebugConfig(buildResult);
|
|
4810
|
+
const proxies = buildResult.config.proxies;
|
|
4811
|
+
const proxyGroups = buildResult.config["proxy-groups"];
|
|
4812
|
+
return {
|
|
4813
|
+
proxies: proxies ? proxies.length : 0,
|
|
4814
|
+
proxyGroups: proxyGroups ? proxyGroups.length : 0
|
|
4815
|
+
};
|
|
4816
|
+
}
|
|
4817
|
+
function needsAutoUpdate(sub) {
|
|
4818
|
+
if (!sub.updated_at) return true;
|
|
4819
|
+
const lastUpdate = new Date(sub.updated_at).getTime();
|
|
4820
|
+
if (Number.isNaN(lastUpdate)) return true;
|
|
4821
|
+
const intervalHours = resolveUpdateInterval(sub.url, sub.update_interval);
|
|
4822
|
+
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
4823
|
+
return Date.now() - lastUpdate > intervalMs;
|
|
4824
|
+
}
|
|
4825
|
+
async function tryUpdateOne(sub, signal) {
|
|
4699
4826
|
try {
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
console.log(`\u6700\u65B0: ${info.latest}`);
|
|
4704
|
-
if (!info.needsUpdate) {
|
|
4705
|
-
console.log("\u5DF2\u662F\u6700\u65B0\u7248\u672C");
|
|
4827
|
+
let info;
|
|
4828
|
+
if (isMultiUrl(sub.url)) {
|
|
4829
|
+
info = await downloadMergedSubscription(splitUrls(sub.url), sub.name, signal);
|
|
4706
4830
|
} else {
|
|
4707
|
-
|
|
4708
|
-
const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
|
|
4709
|
-
console.log(`
|
|
4710
|
-
\u5DF2\u66F4\u65B0\u5230 ${result.version}`);
|
|
4831
|
+
info = await downloadSubscription(sub.url, sub.name, signal);
|
|
4711
4832
|
}
|
|
4833
|
+
return { name: sub.name, success: true, proxies: info.proxies, proxyGroups: info.proxyGroups };
|
|
4712
4834
|
} catch (e) {
|
|
4713
|
-
|
|
4714
|
-
\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
4715
|
-
const err = e;
|
|
4716
|
-
if (err.response?.data) {
|
|
4717
|
-
if (err.response.data.message) {
|
|
4718
|
-
console.error(`\u539F\u56E0: ${err.response.data.message}`);
|
|
4719
|
-
}
|
|
4720
|
-
if (err.response.data.documentation_url) {
|
|
4721
|
-
console.error(`\u6587\u6863: ${err.response.data.documentation_url}`);
|
|
4722
|
-
}
|
|
4723
|
-
}
|
|
4724
|
-
process.exit(1);
|
|
4835
|
+
return { name: sub.name, success: false, error: e.message };
|
|
4725
4836
|
}
|
|
4726
4837
|
}
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
openLogFile(logPath);
|
|
4733
|
-
return;
|
|
4838
|
+
function printUpdateResult(r) {
|
|
4839
|
+
if (r.success) {
|
|
4840
|
+
console.log(`${colors.green("\u2713")} ${r.name}: ${colors.green("\u5DF2\u66F4\u65B0")} (${formatProxySummary(r)})`);
|
|
4841
|
+
} else {
|
|
4842
|
+
console.log(`${colors.red("\u2717")} ${r.name}: ${colors.red("\u5931\u8D25")} (${(r.error || "").split("\n")[0]})`);
|
|
4734
4843
|
}
|
|
4735
|
-
viewLogWithTail(logPath, { follow: true, lines: 50 });
|
|
4736
4844
|
}
|
|
4737
|
-
function
|
|
4738
|
-
const
|
|
4739
|
-
const
|
|
4740
|
-
|
|
4741
|
-
|
|
4742
|
-
let logPath;
|
|
4743
|
-
if (targetName === "current" || targetName === "0") {
|
|
4744
|
-
logPath = getLogPath();
|
|
4745
|
-
} else {
|
|
4746
|
-
const parsedIdx = parseInt(targetName, 10);
|
|
4747
|
-
if (!Number.isNaN(parsedIdx) && parsedIdx > 0 && String(parsedIdx) === targetName) {
|
|
4748
|
-
const archiveLogs = listLogs();
|
|
4749
|
-
const archive = archiveLogs.archives[parsedIdx - 1];
|
|
4750
|
-
if (!archive) {
|
|
4751
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
4752
|
-
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
4753
|
-
process.exit(1);
|
|
4754
|
-
}
|
|
4755
|
-
logPath = archive.path;
|
|
4756
|
-
} else {
|
|
4757
|
-
logPath = getLogPathByName(targetName);
|
|
4758
|
-
}
|
|
4759
|
-
}
|
|
4760
|
-
if (!logPath) {
|
|
4761
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
4762
|
-
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
4763
|
-
process.exit(1);
|
|
4764
|
-
}
|
|
4765
|
-
if (openInViewer) {
|
|
4766
|
-
openLogFile(logPath);
|
|
4767
|
-
return;
|
|
4768
|
-
}
|
|
4769
|
-
viewLogWithTail(logPath, { follow: false, lines });
|
|
4770
|
-
return;
|
|
4845
|
+
async function autoUpdateStaleSubscription(options = {}) {
|
|
4846
|
+
const allSubs = getSubscriptionsWithCache();
|
|
4847
|
+
const staleSubs = allSubs.filter(needsAutoUpdate);
|
|
4848
|
+
if (staleSubs.length === 0) {
|
|
4849
|
+
return { total: 0, updated: 0, failed: 0 };
|
|
4771
4850
|
}
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
console.log(
|
|
4778
|
-
return;
|
|
4851
|
+
if (staleSubs.length === 1) {
|
|
4852
|
+
const sub = staleSubs[0];
|
|
4853
|
+
const interval = resolveUpdateInterval(sub.url, sub.update_interval);
|
|
4854
|
+
console.log(`\u8BA2\u9605 "${sub.name}" \u8D85\u8FC7 ${interval} \u5C0F\u65F6\u672A\u66F4\u65B0\uFF0C\u6B63\u5728\u66F4\u65B0...`);
|
|
4855
|
+
} else {
|
|
4856
|
+
console.log(`\u68C0\u67E5\u5230 ${staleSubs.length} \u4E2A\u8BA2\u9605\u9700\u8981\u66F4\u65B0\uFF0C\u6B63\u5728\u5E76\u884C\u66F4\u65B0...`);
|
|
4779
4857
|
}
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
if (
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
|
|
4791
|
-
}
|
|
4792
|
-
const time = formatDate(log.mtime);
|
|
4793
|
-
const size = formatBytes(log.size);
|
|
4794
|
-
const name = log.isCurrent ? "mihomo.log (\u5F53\u524D\u8FD0\u884C\u4E2D)" : log.name;
|
|
4795
|
-
console.log(` ${num}. ${name}`);
|
|
4796
|
-
console.log(` \u65F6\u95F4: ${time} \u5927\u5C0F: ${size}`);
|
|
4797
|
-
if (!log.isCurrent) {
|
|
4798
|
-
console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
|
|
4858
|
+
const timeoutMs = options.timeout ?? DEFAULT_AUTO_UPDATE_TIMEOUT;
|
|
4859
|
+
const controller = new AbortController();
|
|
4860
|
+
let results;
|
|
4861
|
+
try {
|
|
4862
|
+
results = await withTimeout(Promise.all(staleSubs.map((sub) => tryUpdateOne(sub, controller.signal))), timeoutMs);
|
|
4863
|
+
} catch (e) {
|
|
4864
|
+
if (e instanceof TimeoutError) {
|
|
4865
|
+
controller.abort();
|
|
4866
|
+
console.log(colors.yellow(`\u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 (${timeoutMs / 1e3}s)\uFF0C\u8DF3\u8FC7\u66F4\u65B0\uFF0C\u4F7F\u7528\u7F13\u5B58\u914D\u7F6E`));
|
|
4867
|
+
return { total: staleSubs.length, updated: 0, failed: staleSubs.length };
|
|
4799
4868
|
}
|
|
4800
|
-
|
|
4869
|
+
throw e;
|
|
4801
4870
|
}
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
|
|
4807
|
-
console.log("");
|
|
4808
|
-
}
|
|
4809
|
-
|
|
4810
|
-
// src/commands/overwrite.ts
|
|
4811
|
-
import path6 from "path";
|
|
4812
|
-
|
|
4813
|
-
// src/subscription.ts
|
|
4814
|
-
function isGithubUrl(url) {
|
|
4815
|
-
return /github\.com|raw\.githubusercontent\.com/i.test(url);
|
|
4816
|
-
}
|
|
4817
|
-
function getDefaultUpdateInterval(url) {
|
|
4818
|
-
return isGithubUrl(url) ? DEFAULT_UPDATE_INTERVAL_HOURS_GITHUB : DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
4819
|
-
}
|
|
4820
|
-
function resolveUpdateInterval(url, cachedInterval) {
|
|
4821
|
-
return cachedInterval && cachedInterval > 0 ? cachedInterval : getDefaultUpdateInterval(url);
|
|
4822
|
-
}
|
|
4823
|
-
var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, schema: CORE_SCHEMA };
|
|
4824
|
-
var HTTP_CLIENT2 = createHttpClient({ timeout: 6e4 });
|
|
4825
|
-
function isMultiUrl(url) {
|
|
4826
|
-
return url.includes(",");
|
|
4827
|
-
}
|
|
4828
|
-
function splitUrls(url) {
|
|
4829
|
-
return url.split(",").map((u) => u.trim()).filter(Boolean);
|
|
4830
|
-
}
|
|
4831
|
-
function loadSubscriptionConfig(subName) {
|
|
4832
|
-
const rawContent = readSubscriptionRawConfig(subName);
|
|
4833
|
-
if (!rawContent) {
|
|
4834
|
-
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
4871
|
+
let updatedCount = 0;
|
|
4872
|
+
for (const r of results) {
|
|
4873
|
+
if (r.success) updatedCount++;
|
|
4874
|
+
printUpdateResult(r);
|
|
4835
4875
|
}
|
|
4836
|
-
|
|
4837
|
-
return {
|
|
4838
|
-
raw,
|
|
4839
|
-
proxies: raw.proxies || [],
|
|
4840
|
-
proxyGroups: raw["proxy-groups"] || []
|
|
4841
|
-
};
|
|
4842
|
-
}
|
|
4843
|
-
function saveSubscriptionConfig(subName, parsed) {
|
|
4844
|
-
normalizeProxyNamesBeforeSave(parsed);
|
|
4845
|
-
parsed.raw.proxies = parsed.proxies;
|
|
4846
|
-
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4847
|
-
saveSubscriptionRawConfig(subName, dump(parsed.raw, YAML_DUMP_OPTS));
|
|
4876
|
+
return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
|
|
4848
4877
|
}
|
|
4849
|
-
function
|
|
4850
|
-
|
|
4851
|
-
const
|
|
4852
|
-
|
|
4853
|
-
|
|
4854
|
-
const
|
|
4855
|
-
if (
|
|
4856
|
-
|
|
4857
|
-
|
|
4878
|
+
async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = CONTROLLER_BASE_URL) {
|
|
4879
|
+
const encodedName = encodeURIComponent(proxyName);
|
|
4880
|
+
const url = `${apiBase}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4881
|
+
try {
|
|
4882
|
+
const response = await client.get(url);
|
|
4883
|
+
const data = JSON.parse(response.data);
|
|
4884
|
+
if (data.delay && data.delay > 0) {
|
|
4885
|
+
return { name: proxyName, delay: data.delay };
|
|
4886
|
+
}
|
|
4887
|
+
return { name: proxyName, delay: null, error: data.message || "no delay" };
|
|
4888
|
+
} catch (e) {
|
|
4889
|
+
const err = e;
|
|
4890
|
+
let errorMsg = "timeout";
|
|
4891
|
+
if (err.response?.data?.message) {
|
|
4892
|
+
errorMsg = String(err.response.data.message);
|
|
4893
|
+
} else if (err.message) {
|
|
4894
|
+
errorMsg = err.message;
|
|
4858
4895
|
}
|
|
4896
|
+
return { name: proxyName, delay: null, error: errorMsg };
|
|
4859
4897
|
}
|
|
4860
|
-
return info;
|
|
4861
|
-
}
|
|
4862
|
-
function parsePositiveInterval(header) {
|
|
4863
|
-
if (!header) return null;
|
|
4864
|
-
const n = parseInt(header, 10);
|
|
4865
|
-
return Number.isFinite(n) && n > 0 ? n : null;
|
|
4866
|
-
}
|
|
4867
|
-
function parseUsernameFromContentDisposition(header) {
|
|
4868
|
-
if (!header) return null;
|
|
4869
|
-
const match = header.match(/filename\s*=\s*["']?([^"';\s]+)["']?/i);
|
|
4870
|
-
if (!match) return null;
|
|
4871
|
-
const filename = match[1];
|
|
4872
|
-
const parts = filename.split("/");
|
|
4873
|
-
return parts[parts.length - 1] || null;
|
|
4874
|
-
}
|
|
4875
|
-
function extractSubscriptionMeta(headers) {
|
|
4876
|
-
return {
|
|
4877
|
-
userInfo: parseUserInfo(headers?.get("subscription-userinfo") ?? null),
|
|
4878
|
-
updateInterval: parsePositiveInterval(headers?.get("profile-update-interval")),
|
|
4879
|
-
webPageUrl: headers?.get("profile-web-page-url") || null,
|
|
4880
|
-
username: parseUsernameFromContentDisposition(headers?.get("content-disposition") ?? null)
|
|
4881
|
-
};
|
|
4882
4898
|
}
|
|
4883
|
-
function
|
|
4884
|
-
const
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4899
|
+
async function testSubscriptionProxies(subName, options = {}) {
|
|
4900
|
+
const {
|
|
4901
|
+
timeout = DEFAULT_TEST_TIMEOUT,
|
|
4902
|
+
concurrency = DEFAULT_TEST_CONCURRENCY,
|
|
4903
|
+
testUrl = DEFAULT_TEST_URL,
|
|
4904
|
+
apiBase = CONTROLLER_BASE_URL,
|
|
4905
|
+
onResult
|
|
4906
|
+
} = options;
|
|
4907
|
+
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4908
|
+
if (proxies.length === 0) {
|
|
4909
|
+
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
4890
4910
|
}
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
const subs = getSubscriptions();
|
|
4904
|
-
if (subs.length === 0) return null;
|
|
4905
|
-
const settings = readSettings();
|
|
4906
|
-
const activeName = settings.active_subscription;
|
|
4907
|
-
if (activeName) {
|
|
4908
|
-
const found = subs.find((s) => s.name === activeName);
|
|
4909
|
-
if (found) return found;
|
|
4911
|
+
const client = createHttpClient({ timeout: timeout + 3e3 });
|
|
4912
|
+
const results = new Array(proxies.length);
|
|
4913
|
+
let completedCount = 0;
|
|
4914
|
+
let nextIndex = 0;
|
|
4915
|
+
async function runNext() {
|
|
4916
|
+
while (nextIndex < proxies.length) {
|
|
4917
|
+
const idx = nextIndex++;
|
|
4918
|
+
const result = await testProxyDelay(proxies[idx].name, timeout, testUrl, client, apiBase);
|
|
4919
|
+
results[idx] = result;
|
|
4920
|
+
onResult?.(result, completedCount, proxies.length);
|
|
4921
|
+
completedCount++;
|
|
4922
|
+
}
|
|
4910
4923
|
}
|
|
4911
|
-
|
|
4924
|
+
const workers = Array.from({ length: Math.min(concurrency, proxies.length) }, () => runNext());
|
|
4925
|
+
await Promise.all(workers);
|
|
4926
|
+
const alive = results.filter((r) => r.delay !== null).length;
|
|
4927
|
+
return { total: results.length, alive, dead: results.length - alive, results };
|
|
4912
4928
|
}
|
|
4913
|
-
function
|
|
4914
|
-
const
|
|
4915
|
-
const
|
|
4916
|
-
const
|
|
4917
|
-
const
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
} else
|
|
4923
|
-
|
|
4924
|
-
} else if (name.includes(lowerPattern)) {
|
|
4925
|
-
includes.push(s);
|
|
4929
|
+
function normalizeProxyNamesBeforeSave(parsed) {
|
|
4930
|
+
const { proxies, proxyGroups } = parsed;
|
|
4931
|
+
const renameMap = /* @__PURE__ */ new Map();
|
|
4932
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
4933
|
+
for (const proxy of proxies) {
|
|
4934
|
+
const shortened = proxy.name.replace(/_github\.com\/[^_]+/, "");
|
|
4935
|
+
if (shortened !== proxy.name && !usedNames.has(shortened)) {
|
|
4936
|
+
renameMap.set(proxy.name, shortened);
|
|
4937
|
+
usedNames.add(shortened);
|
|
4938
|
+
} else {
|
|
4939
|
+
usedNames.add(proxy.name);
|
|
4926
4940
|
}
|
|
4927
4941
|
}
|
|
4928
|
-
if (
|
|
4929
|
-
|
|
4930
|
-
|
|
4931
|
-
|
|
4932
|
-
function pickSingleSubscription(subs, pattern) {
|
|
4933
|
-
if (subs.length === 0) {
|
|
4934
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u5339\u914D "${pattern}" \u7684\u8BA2\u9605`);
|
|
4935
|
-
process.exit(1);
|
|
4942
|
+
if (renameMap.size === 0) return 0;
|
|
4943
|
+
for (const proxy of proxies) {
|
|
4944
|
+
const newName = renameMap.get(proxy.name);
|
|
4945
|
+
if (newName) proxy.name = newName;
|
|
4936
4946
|
}
|
|
4937
|
-
|
|
4938
|
-
|
|
4939
|
-
|
|
4940
|
-
|
|
4941
|
-
|
|
4947
|
+
for (const group of proxyGroups) {
|
|
4948
|
+
if (Array.isArray(group.proxies)) {
|
|
4949
|
+
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
return renameMap.size;
|
|
4942
4953
|
}
|
|
4943
|
-
|
|
4944
|
-
|
|
4945
|
-
|
|
4946
|
-
|
|
4947
|
-
|
|
4948
|
-
|
|
4949
|
-
|
|
4950
|
-
|
|
4951
|
-
if (
|
|
4952
|
-
|
|
4954
|
+
function cleanDeadProxies(parsed, deadNames) {
|
|
4955
|
+
const { proxies, proxyGroups } = parsed;
|
|
4956
|
+
const originalCount = proxies.length;
|
|
4957
|
+
parsed.proxies = proxies.filter((p) => !deadNames.has(p.name));
|
|
4958
|
+
const removedProxies = originalCount - parsed.proxies.length;
|
|
4959
|
+
let updatedGroups = 0;
|
|
4960
|
+
const removedGroupNames = /* @__PURE__ */ new Set();
|
|
4961
|
+
for (const group of proxyGroups) {
|
|
4962
|
+
if (Array.isArray(group.proxies)) {
|
|
4963
|
+
const before = group.proxies.length;
|
|
4964
|
+
group.proxies = group.proxies.filter((name) => !deadNames.has(name));
|
|
4965
|
+
if (group.proxies.length < before) {
|
|
4966
|
+
updatedGroups++;
|
|
4967
|
+
}
|
|
4968
|
+
const hasOtherSource = group.use || group["include-all"] || group["include-all-proxies"];
|
|
4969
|
+
if (group.proxies.length === 0 && !hasOtherSource) {
|
|
4970
|
+
removedGroupNames.add(group.name);
|
|
4971
|
+
}
|
|
4953
4972
|
}
|
|
4954
|
-
errorMsg += `
|
|
4955
|
-
URL: ${maskedUrl}`;
|
|
4956
|
-
throw new Error(errorMsg);
|
|
4957
4973
|
}
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4974
|
+
if (removedGroupNames.size > 0) {
|
|
4975
|
+
parsed.proxyGroups = proxyGroups.filter((g) => !removedGroupNames.has(g.name));
|
|
4976
|
+
for (const group of parsed.proxyGroups) {
|
|
4977
|
+
if (Array.isArray(group.proxies)) {
|
|
4978
|
+
group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
|
|
4979
|
+
}
|
|
4980
|
+
}
|
|
4981
|
+
const rules = parsed.raw.rules;
|
|
4982
|
+
if (Array.isArray(rules)) {
|
|
4983
|
+
parsed.raw.rules = rules.filter((rule) => {
|
|
4984
|
+
if (typeof rule !== "string") return true;
|
|
4985
|
+
return !removedGroupNames.has(getRuleTarget(rule));
|
|
4986
|
+
});
|
|
4987
|
+
}
|
|
4961
4988
|
}
|
|
4962
|
-
|
|
4963
|
-
if (!parsed) throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4964
|
-
saveSubscriptionRawConfig(subName, content);
|
|
4965
|
-
const meta = extractSubscriptionMeta(response.headers);
|
|
4966
|
-
saveSubscriptionMeta(subName, meta);
|
|
4967
|
-
const proxies = parsed.proxies;
|
|
4968
|
-
const proxyGroups = parsed["proxy-groups"];
|
|
4969
|
-
return {
|
|
4970
|
-
proxies: proxies ? proxies.length : 0,
|
|
4971
|
-
proxyGroups: proxyGroups ? proxyGroups.length : 0,
|
|
4972
|
-
userInfo: meta.userInfo,
|
|
4973
|
-
updateInterval: meta.updateInterval,
|
|
4974
|
-
webPageUrl: meta.webPageUrl,
|
|
4975
|
-
username: meta.username
|
|
4976
|
-
};
|
|
4989
|
+
return { removedProxies, updatedGroups, removedGroups: removedGroupNames.size };
|
|
4977
4990
|
}
|
|
4978
|
-
async function
|
|
4979
|
-
const
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4991
|
+
async function autoCleanSubscription(subName, options = {}) {
|
|
4992
|
+
const parsed = loadSubscriptionConfig(subName);
|
|
4993
|
+
const { onResult, onRetryRound, rounds = DEFAULT_CLEAN_ROUNDS, ...testOptions } = options;
|
|
4994
|
+
const wrapOnResult = (round) => onResult ? (r, i, t) => onResult(r, i, t, round) : void 0;
|
|
4995
|
+
const summary = await testSubscriptionProxies(subName, {
|
|
4996
|
+
...testOptions,
|
|
4997
|
+
parsed,
|
|
4998
|
+
onResult: wrapOnResult(1)
|
|
4999
|
+
});
|
|
5000
|
+
let removedProxies = 0;
|
|
5001
|
+
let updatedGroups = 0;
|
|
5002
|
+
let removedGroups = 0;
|
|
5003
|
+
let skipped = false;
|
|
5004
|
+
if (summary.dead > 0) {
|
|
5005
|
+
if (summary.alive === 0 || summary.alive / summary.total < 0.01) {
|
|
5006
|
+
skipped = true;
|
|
5007
|
+
} else {
|
|
5008
|
+
const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
|
|
5009
|
+
const deadProxies = parsed.proxies.filter((p) => deadNames.has(p.name));
|
|
5010
|
+
for (let retry = 0; retry < rounds - 1; retry++) {
|
|
5011
|
+
const round = retry + 2;
|
|
5012
|
+
const retryTargets = deadProxies.filter((p) => deadNames.has(p.name));
|
|
5013
|
+
if (retryTargets.length === 0) break;
|
|
5014
|
+
onRetryRound?.(round, retryTargets.length);
|
|
5015
|
+
const retryParsed = { raw: {}, proxies: retryTargets, proxyGroups: [] };
|
|
5016
|
+
const retrySummary = await testSubscriptionProxies(subName, {
|
|
5017
|
+
...testOptions,
|
|
5018
|
+
parsed: retryParsed,
|
|
5019
|
+
onResult: wrapOnResult(round)
|
|
5020
|
+
});
|
|
5021
|
+
for (const r of retrySummary.results) {
|
|
5022
|
+
if (r.delay !== null) {
|
|
5023
|
+
deadNames.delete(r.name);
|
|
5024
|
+
}
|
|
5025
|
+
}
|
|
5026
|
+
}
|
|
5027
|
+
summary.dead = deadNames.size;
|
|
5028
|
+
summary.alive = summary.total - summary.dead;
|
|
5029
|
+
if (deadNames.size > 0) {
|
|
5030
|
+
const cleanResult = cleanDeadProxies(parsed, deadNames);
|
|
5031
|
+
removedProxies = cleanResult.removedProxies;
|
|
5032
|
+
updatedGroups = cleanResult.updatedGroups;
|
|
5033
|
+
removedGroups = cleanResult.removedGroups;
|
|
4986
5034
|
}
|
|
4987
|
-
})
|
|
4988
|
-
);
|
|
4989
|
-
for (const r of responses) {
|
|
4990
|
-
if (r.error) {
|
|
4991
|
-
const maskedUrl = maskUrl(r.url);
|
|
4992
|
-
throw new Error(`\u5408\u5E76\u8BA2\u9605\u7B2C ${r.index + 1} \u4E2A URL \u83B7\u53D6\u5931\u8D25: ${r.error.message}
|
|
4993
|
-
URL: ${maskedUrl}`);
|
|
4994
5035
|
}
|
|
4995
5036
|
}
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5008
|
-
|
|
5009
|
-
|
|
5037
|
+
if (!skipped && removedProxies > 0) {
|
|
5038
|
+
saveSubscriptionConfig(subName, parsed);
|
|
5039
|
+
}
|
|
5040
|
+
return { summary, removedProxies, updatedGroups, removedGroups, skipped };
|
|
5041
|
+
}
|
|
5042
|
+
|
|
5043
|
+
// src/commands/daemon.ts
|
|
5044
|
+
function printDaemonStatus() {
|
|
5045
|
+
const status = getDaemonStatus();
|
|
5046
|
+
const stateText = status.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
5047
|
+
console.log(`${colors.gray("\u4FDD\u6D3B: ")}${stateText}`);
|
|
5048
|
+
if (status.enabled) {
|
|
5049
|
+
const runText = isDaemonRunning(status) ? colors.green(`\u8FD0\u884C\u4E2D (PID ${status.pid})`) : colors.yellow("\u672A\u8FD0\u884C");
|
|
5050
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${runText}`);
|
|
5051
|
+
}
|
|
5052
|
+
console.log("");
|
|
5053
|
+
if (status.enabled) {
|
|
5054
|
+
console.log("\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
|
|
5055
|
+
} else {
|
|
5056
|
+
console.log("\u5F00\u542F\u4FDD\u6D3B: mihomo daemon on");
|
|
5057
|
+
console.log(colors.gray(" \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF08\u4EC5 Mixed \u6A21\u5F0F\uFF09"));
|
|
5058
|
+
}
|
|
5059
|
+
console.log("");
|
|
5060
|
+
}
|
|
5061
|
+
async function cmdDaemon(args) {
|
|
5062
|
+
const action = args?.[1];
|
|
5063
|
+
if (action === "on" || action === "enable") {
|
|
5064
|
+
if (!hasKernel()) {
|
|
5065
|
+
console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
5066
|
+
process.exit(1);
|
|
5067
|
+
}
|
|
5068
|
+
const sub = getActiveSubscription();
|
|
5069
|
+
if (!sub) {
|
|
5070
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
5071
|
+
process.exit(1);
|
|
5072
|
+
}
|
|
5073
|
+
let configInfo;
|
|
5074
|
+
try {
|
|
5075
|
+
configInfo = prepareConfigForStart("mixed", sub.name);
|
|
5076
|
+
} catch (e) {
|
|
5077
|
+
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
5078
|
+
process.exit(1);
|
|
5079
|
+
}
|
|
5080
|
+
console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u5B89\u88C5\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1\uFF08LaunchDaemon\uFF09"));
|
|
5081
|
+
console.log(colors.gray("\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u9700\u8981 root\uFF0C\u4EE5\u89E3\u51B3\u5C40\u57DF\u7F51\u8BBF\u95EE\u53D7\u9650\u95EE\u9898"));
|
|
5082
|
+
try {
|
|
5083
|
+
enableDaemon();
|
|
5084
|
+
} catch (e) {
|
|
5085
|
+
console.error(`${colors.red("\u542F\u7528\u4FDD\u6D3B\u5931\u8D25:")} ${e.message}`);
|
|
5086
|
+
process.exit(1);
|
|
5087
|
+
}
|
|
5088
|
+
console.log(`${colors.green("\u5DF2\u542F\u7528\u4FDD\u6D3B")} \xB7 ${sub.name} \xB7 ${formatProxySummary(configInfo)}`);
|
|
5089
|
+
console.log(colors.gray("\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF0C\u4EE3\u7406\u5C06\u5728\u540E\u53F0\u5E38\u9A7B"));
|
|
5090
|
+
console.log("");
|
|
5091
|
+
await sleep(DAEMON_BOOT_WAIT_MS);
|
|
5092
|
+
printDaemonStatus();
|
|
5093
|
+
return;
|
|
5094
|
+
}
|
|
5095
|
+
if (action === "off" || action === "disable") {
|
|
5096
|
+
if (!isDaemonEnabled()) {
|
|
5097
|
+
console.log("\u4FDD\u6D3B\u5DF2\u662F\u5173\u95ED\u72B6\u6001");
|
|
5098
|
+
console.log("");
|
|
5099
|
+
printDaemonStatus();
|
|
5100
|
+
return;
|
|
5101
|
+
}
|
|
5102
|
+
console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u79FB\u9664\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1"));
|
|
5103
|
+
try {
|
|
5104
|
+
disableDaemon();
|
|
5105
|
+
} catch (e) {
|
|
5106
|
+
console.error(`${colors.red("\u5173\u95ED\u4FDD\u6D3B\u5931\u8D25:")} ${e.message}`);
|
|
5107
|
+
process.exit(1);
|
|
5108
|
+
}
|
|
5109
|
+
console.log(`${colors.green("\u5DF2\u5173\u95ED\u4FDD\u6D3B")}\uFF0C\u4EE3\u7406\u5DF2\u505C\u6B62`);
|
|
5110
|
+
console.log(colors.gray("\u91CD\u65B0\u542F\u7528: mihomo daemon on"));
|
|
5111
|
+
console.log("");
|
|
5112
|
+
return;
|
|
5113
|
+
}
|
|
5114
|
+
if (action !== void 0 && action !== "status") {
|
|
5115
|
+
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684 daemon \u5B50\u547D\u4EE4: ${action}`);
|
|
5116
|
+
console.log("");
|
|
5117
|
+
console.log("\u53EF\u7528\u5B50\u547D\u4EE4: on, off, status");
|
|
5118
|
+
process.exit(1);
|
|
5119
|
+
}
|
|
5120
|
+
console.log("");
|
|
5121
|
+
printDaemonStatus();
|
|
5122
|
+
}
|
|
5123
|
+
|
|
5124
|
+
// src/commands/directory.ts
|
|
5125
|
+
function cmdDirectory(args) {
|
|
5126
|
+
const action = args?.[1];
|
|
5127
|
+
if (action === "open") {
|
|
5128
|
+
const target = args[2];
|
|
5129
|
+
if (!target || target === "root") {
|
|
5130
|
+
console.log("\u6B63\u5728\u6253\u5F00: \u6839\u76EE\u5F55");
|
|
5131
|
+
const success = openUrl(USER_DATA_DIR);
|
|
5132
|
+
if (!success) {
|
|
5133
|
+
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${USER_DATA_DIR}`);
|
|
5010
5134
|
}
|
|
5135
|
+
return;
|
|
5136
|
+
}
|
|
5137
|
+
const key = target.toLowerCase();
|
|
5138
|
+
const targetInfo = Object.hasOwn(DIRECTORY_TARGETS, key) ? DIRECTORY_TARGETS[key] : void 0;
|
|
5139
|
+
if (targetInfo) {
|
|
5140
|
+
const targetPath = targetInfo.path || USER_DATA_DIR;
|
|
5141
|
+
console.log(`\u6B63\u5728\u6253\u5F00: ${targetInfo.label}`);
|
|
5142
|
+
const success = openUrl(targetPath);
|
|
5143
|
+
if (!success) {
|
|
5144
|
+
console.log(`\u8BF7\u624B\u52A8\u6253\u5F00: ${targetPath}`);
|
|
5145
|
+
}
|
|
5146
|
+
return;
|
|
5147
|
+
}
|
|
5148
|
+
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u76EE\u5F55\u76EE\u6807 "${target}"`);
|
|
5149
|
+
console.log("");
|
|
5150
|
+
console.log("\u53EF\u7528\u76EE\u6807:");
|
|
5151
|
+
console.log(" root (\u9ED8\u8BA4) \u6839\u76EE\u5F55");
|
|
5152
|
+
for (const [key2, val] of Object.entries(DIRECTORY_TARGETS)) {
|
|
5153
|
+
if (key2 !== "root") {
|
|
5154
|
+
console.log(` ${key2.padEnd(14)}${val.label}`);
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
console.log("");
|
|
5158
|
+
process.exit(1);
|
|
5159
|
+
}
|
|
5160
|
+
console.log("");
|
|
5161
|
+
console.log("\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E:");
|
|
5162
|
+
console.log(` \u6839\u76EE\u5F55: ${USER_DATA_DIR}`);
|
|
5163
|
+
console.log(` \u5168\u5C40\u8BBE\u7F6E: ${PATHS.settingsFile}`);
|
|
5164
|
+
console.log(` \u5185\u6838\u76EE\u5F55: ${DIRS.kernel}`);
|
|
5165
|
+
console.log(` \u5185\u6838\u6587\u4EF6: ${PATHS.mihomoBinary}`);
|
|
5166
|
+
console.log(` \u8BA2\u9605\u76EE\u5F55: ${DIRS.subscriptions}`);
|
|
5167
|
+
console.log(" - cache.json (\u8BA2\u9605\u7F13\u5B58\uFF1A\u66F4\u65B0\u65F6\u95F4\u3001\u6D41\u91CF\u7B49)");
|
|
5168
|
+
console.log(" - xxx.yaml (\u8BA2\u9605\u539F\u59CB\u914D\u7F6E)");
|
|
5169
|
+
console.log(` \u8FD0\u884C\u65F6\u76EE\u5F55: ${DIRS.runtime}`);
|
|
5170
|
+
console.log(" - config.yaml (\u542F\u52A8\u65F6\u751F\u6210\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5171
|
+
console.log(" - pid (PID \u6587\u4EF6\uFF0Cstop \u81EA\u52A8\u6E05\u9664)");
|
|
5172
|
+
console.log(` \u65E5\u5FD7\u6587\u4EF6: ${PATHS.logFile}`);
|
|
5173
|
+
console.log(` mihomo \u6570\u636E: ${DIRS.data}`);
|
|
5174
|
+
console.log(" - cache.db, Geo*.dat \u7B49 (mihomo \u81EA\u884C\u7BA1\u7406)");
|
|
5175
|
+
console.log("");
|
|
5176
|
+
console.log("\u6253\u5F00\u76EE\u5F55:");
|
|
5177
|
+
console.log(" mihomo dir open \u6253\u5F00\u6839\u76EE\u5F55");
|
|
5178
|
+
console.log(" mihomo dir open subs \u6253\u5F00\u8BA2\u9605\u76EE\u5F55");
|
|
5179
|
+
console.log(" mihomo dir open logs \u6253\u5F00\u65E5\u5FD7\u76EE\u5F55");
|
|
5180
|
+
console.log(" mihomo dir open data \u6253\u5F00 mihomo \u6570\u636E\u76EE\u5F55");
|
|
5181
|
+
console.log(" mihomo dir open runtime \u6253\u5F00\u8FD0\u884C\u65F6\u76EE\u5F55");
|
|
5182
|
+
console.log(" mihomo dir open kernel \u6253\u5F00\u5185\u6838\u76EE\u5F55");
|
|
5183
|
+
console.log("");
|
|
5184
|
+
console.log("\u73AF\u5883\u53D8\u91CF:");
|
|
5185
|
+
console.log(" MIHOMO_CLI_DIR: \u81EA\u5B9A\u4E49\u6839\u76EE\u5F55\u4F4D\u7F6E");
|
|
5186
|
+
console.log("");
|
|
5187
|
+
}
|
|
5188
|
+
|
|
5189
|
+
// src/kernel.ts
|
|
5190
|
+
import { spawnSync as spawnSync5 } from "child_process";
|
|
5191
|
+
import fs7 from "fs";
|
|
5192
|
+
import path6 from "path";
|
|
5193
|
+
|
|
5194
|
+
// node_modules/compare-versions/lib/esm/utils.js
|
|
5195
|
+
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
5196
|
+
var validateAndParse = (version) => {
|
|
5197
|
+
if (typeof version !== "string") {
|
|
5198
|
+
throw new TypeError("Invalid argument expected string");
|
|
5199
|
+
}
|
|
5200
|
+
const match = version.match(semver);
|
|
5201
|
+
if (!match) {
|
|
5202
|
+
throw new Error(`Invalid argument not valid semver ('${version}' received)`);
|
|
5203
|
+
}
|
|
5204
|
+
match.shift();
|
|
5205
|
+
return match;
|
|
5206
|
+
};
|
|
5207
|
+
var isWildcard = (s) => s === "*" || s === "x" || s === "X";
|
|
5208
|
+
var tryParse = (v) => {
|
|
5209
|
+
const n = parseInt(v, 10);
|
|
5210
|
+
return isNaN(n) ? v : n;
|
|
5211
|
+
};
|
|
5212
|
+
var forceType = (a, b) => typeof a !== typeof b ? [String(a), String(b)] : [a, b];
|
|
5213
|
+
var compareStrings = (a, b) => {
|
|
5214
|
+
if (isWildcard(a) || isWildcard(b))
|
|
5215
|
+
return 0;
|
|
5216
|
+
const [ap, bp] = forceType(tryParse(a), tryParse(b));
|
|
5217
|
+
if (ap > bp)
|
|
5218
|
+
return 1;
|
|
5219
|
+
if (ap < bp)
|
|
5220
|
+
return -1;
|
|
5221
|
+
return 0;
|
|
5222
|
+
};
|
|
5223
|
+
var compareSegments = (a, b) => {
|
|
5224
|
+
for (let i = 0; i < Math.max(a.length, b.length); i++) {
|
|
5225
|
+
const r = compareStrings(a[i] || "0", b[i] || "0");
|
|
5226
|
+
if (r !== 0)
|
|
5227
|
+
return r;
|
|
5228
|
+
}
|
|
5229
|
+
return 0;
|
|
5230
|
+
};
|
|
5231
|
+
|
|
5232
|
+
// node_modules/compare-versions/lib/esm/compareVersions.js
|
|
5233
|
+
var compareVersions = (v1, v2) => {
|
|
5234
|
+
const n1 = validateAndParse(v1);
|
|
5235
|
+
const n2 = validateAndParse(v2);
|
|
5236
|
+
const p1 = n1.pop();
|
|
5237
|
+
const p2 = n2.pop();
|
|
5238
|
+
const r = compareSegments(n1, n2);
|
|
5239
|
+
if (r !== 0)
|
|
5240
|
+
return r;
|
|
5241
|
+
if (p1 && p2) {
|
|
5242
|
+
return compareSegments(p1.split("."), p2.split("."));
|
|
5243
|
+
} else if (p1 || p2) {
|
|
5244
|
+
return p1 ? -1 : 1;
|
|
5245
|
+
}
|
|
5246
|
+
return 0;
|
|
5247
|
+
};
|
|
5248
|
+
|
|
5249
|
+
// src/kernel.ts
|
|
5250
|
+
var GITHUB_REPO = "MetaCubeX/mihomo";
|
|
5251
|
+
var KERNEL_HTTP_TIMEOUT = 12e4;
|
|
5252
|
+
var KERNEL_DOWNLOAD_TIMEOUT = 18e4;
|
|
5253
|
+
var HTTP_CLIENT2 = createHttpClient({ timeout: KERNEL_HTTP_TIMEOUT });
|
|
5254
|
+
function withMirror(url, mirror) {
|
|
5255
|
+
if (mirror && (url.startsWith("https://github.com/") || url.startsWith("https://api.github.com/"))) {
|
|
5256
|
+
return mirror + url;
|
|
5257
|
+
}
|
|
5258
|
+
return url;
|
|
5259
|
+
}
|
|
5260
|
+
function getArch() {
|
|
5261
|
+
const arch = process.arch;
|
|
5262
|
+
if (arch === "arm64") return "arm64";
|
|
5263
|
+
if (arch === "x64") return "amd64";
|
|
5264
|
+
return arch;
|
|
5265
|
+
}
|
|
5266
|
+
function findMatchingAsset(assets, platform, arch) {
|
|
5267
|
+
const prefix = `mihomo-${platform}-${arch}`;
|
|
5268
|
+
const matchingAssets = assets.filter(
|
|
5269
|
+
(a) => a.name.startsWith(prefix) && a.name.endsWith(".gz") || a.name.startsWith(`${prefix}-`) && a.name.endsWith(".gz")
|
|
5270
|
+
);
|
|
5271
|
+
if (matchingAssets.length === 0) return null;
|
|
5272
|
+
if (matchingAssets.length === 1) return matchingAssets[0];
|
|
5273
|
+
const standardAsset = matchingAssets.find((a) => {
|
|
5274
|
+
const nameWithoutGz = a.name.slice(0, -3);
|
|
5275
|
+
const parts = nameWithoutGz.split("-");
|
|
5276
|
+
const lastPart = parts[parts.length - 1];
|
|
5277
|
+
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go") && !nameWithoutGz.includes("-compatible");
|
|
5278
|
+
});
|
|
5279
|
+
return standardAsset || matchingAssets[0];
|
|
5280
|
+
}
|
|
5281
|
+
async function getLatestRelease(repo, mirror) {
|
|
5282
|
+
const url = withMirror(`https://api.github.com/repos/${repo}/releases`, mirror);
|
|
5283
|
+
const response = await HTTP_CLIENT2.get(url, { responseType: "json" });
|
|
5284
|
+
const releases = response.data;
|
|
5285
|
+
if (!Array.isArray(releases) || releases.length === 0) {
|
|
5286
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7248\u672C\u4FE1\u606F");
|
|
5287
|
+
}
|
|
5288
|
+
const stableReleases = releases.filter(
|
|
5289
|
+
(r) => !r.prerelease && !r.tag_name.toLowerCase().includes("alpha") && !r.tag_name.toLowerCase().includes("beta") && !r.tag_name.toLowerCase().includes("prerelease")
|
|
5290
|
+
);
|
|
5291
|
+
return stableReleases.length > 0 ? stableReleases[0] : releases[0];
|
|
5292
|
+
}
|
|
5293
|
+
async function checkUpdate(mirror) {
|
|
5294
|
+
const currentVersion = getKernelVersion();
|
|
5295
|
+
const latest = await getLatestRelease(GITHUB_REPO, mirror);
|
|
5296
|
+
const latestVersion = latest.tag_name;
|
|
5297
|
+
let needsUpdate = false;
|
|
5298
|
+
const currentDisplay = currentVersion || "\u672A\u5B89\u88C5";
|
|
5299
|
+
if (!currentVersion) {
|
|
5300
|
+
needsUpdate = true;
|
|
5301
|
+
} else {
|
|
5302
|
+
try {
|
|
5303
|
+
needsUpdate = compareVersions(latestVersion.replace(/^v/, ""), currentVersion.replace(/^v/, "")) > 0;
|
|
5304
|
+
} catch {
|
|
5305
|
+
needsUpdate = latestVersion !== currentVersion;
|
|
5011
5306
|
}
|
|
5012
5307
|
}
|
|
5013
|
-
base.proxies = baseProxies;
|
|
5014
|
-
const mergedContent = dump(base, YAML_DUMP_OPTS);
|
|
5015
|
-
saveSubscriptionRawConfig(subName, mergedContent);
|
|
5016
|
-
const meta = extractSubscriptionMeta(responses[0].response?.headers);
|
|
5017
|
-
saveSubscriptionMeta(subName, meta);
|
|
5018
|
-
const proxyGroups = base["proxy-groups"];
|
|
5019
5308
|
return {
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
username: meta.username
|
|
5309
|
+
current: currentDisplay,
|
|
5310
|
+
latest: latestVersion,
|
|
5311
|
+
needsUpdate,
|
|
5312
|
+
assets: latest.assets,
|
|
5313
|
+
release: latest
|
|
5026
5314
|
};
|
|
5027
5315
|
}
|
|
5028
|
-
function
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5316
|
+
function findBinaryInDir(dir, maxDepth = 4) {
|
|
5317
|
+
if (maxDepth <= 0) return null;
|
|
5318
|
+
const files = fs7.readdirSync(dir);
|
|
5319
|
+
for (const f of files) {
|
|
5320
|
+
const fullPath = path6.join(dir, f);
|
|
5321
|
+
const stat = fs7.statSync(fullPath);
|
|
5322
|
+
if (stat.isDirectory()) {
|
|
5323
|
+
const found = findBinaryInDir(fullPath, maxDepth - 1);
|
|
5324
|
+
if (found) return found;
|
|
5325
|
+
continue;
|
|
5326
|
+
}
|
|
5327
|
+
if (f === "mihomo") return fullPath;
|
|
5328
|
+
if (f.includes("mihomo") && !f.endsWith(".gz")) return fullPath;
|
|
5329
|
+
}
|
|
5330
|
+
return null;
|
|
5331
|
+
}
|
|
5332
|
+
async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
5333
|
+
ensureDirs();
|
|
5334
|
+
const latest = releaseInfo || await getLatestRelease(GITHUB_REPO, mirror);
|
|
5335
|
+
const arch = getArch();
|
|
5336
|
+
const platform = process.platform;
|
|
5337
|
+
const asset = findMatchingAsset(latest.assets, platform, arch);
|
|
5338
|
+
if (!asset) {
|
|
5339
|
+
const available = latest.assets.map((a) => a.name).join(", ");
|
|
5340
|
+
let hint = "";
|
|
5341
|
+
if (available) hint = `
|
|
5342
|
+
\u53EF\u7528\u7248\u672C: ${available}`;
|
|
5343
|
+
throw new Error(`\u672A\u627E\u5230\u5339\u914D\u7684\u5185\u6838\u6587\u4EF6
|
|
5344
|
+
\u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
|
|
5032
5345
|
}
|
|
5033
|
-
const
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
5346
|
+
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
5347
|
+
const tempPath = path6.join(DIRS.kernel, asset.name);
|
|
5348
|
+
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
5349
|
+
if (mirror && progressCallback) {
|
|
5350
|
+
progressCallback("\u63D0\u793A: \u7ECF\u7B2C\u4E09\u65B9\u955C\u50CF\u4E2D\u8F6C\u4E0B\u8F7D\uFF0C\u65E0\u6CD5\u9A8C\u8BC1\u6765\u6E90\u5B8C\u6574\u6027\uFF0C\u5EFA\u8BAE\u76F4\u8FDE\u6216\u81EA\u884C\u6821\u9A8C\u4EA7\u7269");
|
|
5351
|
+
}
|
|
5352
|
+
if (progressCallback) {
|
|
5353
|
+
progressCallback(`\u4E0B\u8F7D\u5185\u6838: ${asset.name} (${sizeMB} MB)`);
|
|
5354
|
+
}
|
|
5355
|
+
const curlResult = spawnSync5(
|
|
5356
|
+
"curl",
|
|
5357
|
+
["-L", "--progress-bar", "--connect-timeout", "30", "--max-time", String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1e3)), "-o", tempPath, downloadUrl],
|
|
5358
|
+
{ stdio: "inherit" }
|
|
5359
|
+
);
|
|
5360
|
+
if (curlResult.status !== 0) {
|
|
5361
|
+
try {
|
|
5362
|
+
fs7.unlinkSync(tempPath);
|
|
5363
|
+
} catch {
|
|
5037
5364
|
}
|
|
5038
|
-
|
|
5365
|
+
throw new Error(`\u4E0B\u8F7D\u5931\u8D25 (curl \u9000\u51FA\u7801 ${curlResult.status})`);
|
|
5039
5366
|
}
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
}
|
|
5049
|
-
function needsAutoUpdate(sub) {
|
|
5050
|
-
if (!sub.updated_at) return true;
|
|
5051
|
-
const lastUpdate = new Date(sub.updated_at).getTime();
|
|
5052
|
-
if (Number.isNaN(lastUpdate)) return true;
|
|
5053
|
-
const intervalHours = resolveUpdateInterval(sub.url, sub.update_interval);
|
|
5054
|
-
const intervalMs = intervalHours * 60 * 60 * 1e3;
|
|
5055
|
-
return Date.now() - lastUpdate > intervalMs;
|
|
5056
|
-
}
|
|
5057
|
-
async function tryUpdateOne(sub, signal) {
|
|
5367
|
+
if (!fs7.existsSync(tempPath)) {
|
|
5368
|
+
throw new Error("\u4E0B\u8F7D\u5931\u8D25: \u6587\u4EF6\u672A\u751F\u6210");
|
|
5369
|
+
}
|
|
5370
|
+
if (progressCallback) {
|
|
5371
|
+
progressCallback("\u89E3\u538B\u5185\u6838...");
|
|
5372
|
+
}
|
|
5373
|
+
const extractPath = DIRS.kernel;
|
|
5374
|
+
let extractedBinary = null;
|
|
5058
5375
|
try {
|
|
5059
|
-
|
|
5060
|
-
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5376
|
+
if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
|
|
5377
|
+
const tarResult = spawnSync5("tar", ["-xzf", tempPath, "-C", extractPath], { stdio: ["ignore", "ignore", "inherit"], timeout: 6e4 });
|
|
5378
|
+
if (tarResult.error) throw tarResult.error;
|
|
5379
|
+
if (tarResult.status !== 0) throw new Error(`tar \u9000\u51FA\u7801 ${tarResult.status}`);
|
|
5380
|
+
} else if (tempPath.endsWith(".gz")) {
|
|
5381
|
+
const baseName = path6.basename(tempPath, ".gz");
|
|
5382
|
+
const outputPath = path6.join(extractPath, baseName);
|
|
5383
|
+
const gzipResult = spawnSync5("gzip", ["-dc", tempPath], { maxBuffer: 256 * 1024 * 1024, timeout: 6e4 });
|
|
5384
|
+
if (gzipResult.error) throw gzipResult.error;
|
|
5385
|
+
if (gzipResult.status !== 0) throw new Error(`gzip \u9000\u51FA\u7801 ${gzipResult.status}`);
|
|
5386
|
+
fs7.writeFileSync(outputPath, gzipResult.stdout, { mode: 493 });
|
|
5387
|
+
extractedBinary = outputPath;
|
|
5064
5388
|
}
|
|
5065
|
-
return { name: sub.name, success: true, proxies: info.proxies, proxyGroups: info.proxyGroups };
|
|
5066
5389
|
} catch (e) {
|
|
5067
|
-
|
|
5390
|
+
try {
|
|
5391
|
+
fs7.unlinkSync(tempPath);
|
|
5392
|
+
} catch {
|
|
5393
|
+
}
|
|
5394
|
+
throw new Error(`\u89E3\u538B\u5931\u8D25: ${e.message}`);
|
|
5068
5395
|
}
|
|
5069
|
-
|
|
5070
|
-
|
|
5071
|
-
|
|
5072
|
-
|
|
5073
|
-
|
|
5074
|
-
|
|
5396
|
+
const foundBinary = extractedBinary || findBinaryInDir(extractPath);
|
|
5397
|
+
if (!foundBinary) {
|
|
5398
|
+
try {
|
|
5399
|
+
fs7.unlinkSync(tempPath);
|
|
5400
|
+
} catch {
|
|
5401
|
+
}
|
|
5402
|
+
throw new Error("\u89E3\u538B\u540E\u672A\u627E\u5230\u53EF\u6267\u884C\u6587\u4EF6");
|
|
5075
5403
|
}
|
|
5076
|
-
|
|
5077
|
-
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5404
|
+
const targetPath = PATHS.mihomoBinary;
|
|
5405
|
+
if (foundBinary !== targetPath) {
|
|
5406
|
+
if (fs7.existsSync(targetPath)) {
|
|
5407
|
+
fs7.chmodSync(targetPath, 493);
|
|
5408
|
+
try {
|
|
5409
|
+
fs7.unlinkSync(targetPath);
|
|
5410
|
+
} catch {
|
|
5411
|
+
}
|
|
5412
|
+
}
|
|
5413
|
+
fs7.renameSync(foundBinary, targetPath);
|
|
5082
5414
|
}
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
console.log(`\u8BA2\u9605 "${sub.name}" \u8D85\u8FC7 ${interval} \u5C0F\u65F6\u672A\u66F4\u65B0\uFF0C\u6B63\u5728\u66F4\u65B0...`);
|
|
5087
|
-
} else {
|
|
5088
|
-
console.log(`\u68C0\u67E5\u5230 ${staleSubs.length} \u4E2A\u8BA2\u9605\u9700\u8981\u66F4\u65B0\uFF0C\u6B63\u5728\u5E76\u884C\u66F4\u65B0...`);
|
|
5415
|
+
fs7.chmodSync(targetPath, 493);
|
|
5416
|
+
if (progressCallback) {
|
|
5417
|
+
progressCallback("\u6821\u9A8C\u5185\u6838...");
|
|
5089
5418
|
}
|
|
5090
|
-
const
|
|
5091
|
-
const
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
if (e instanceof TimeoutError) {
|
|
5097
|
-
controller.abort();
|
|
5098
|
-
console.log(colors.yellow(`\u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 (${timeoutMs / 1e3}s)\uFF0C\u8DF3\u8FC7\u66F4\u65B0\uFF0C\u4F7F\u7528\u7F13\u5B58\u914D\u7F6E`));
|
|
5099
|
-
return { total: staleSubs.length, updated: 0, failed: staleSubs.length };
|
|
5419
|
+
const check = spawnSync5(targetPath, ["-v"], { encoding: "utf8", timeout: 5e3 });
|
|
5420
|
+
const checkOutput = `${check.stdout || ""}${check.stderr || ""}`.trim();
|
|
5421
|
+
if (check.error || check.status !== 0 || !/v?\d+\.\d+\.\d+/.test(checkOutput)) {
|
|
5422
|
+
try {
|
|
5423
|
+
fs7.unlinkSync(targetPath);
|
|
5424
|
+
} catch {
|
|
5100
5425
|
}
|
|
5101
|
-
|
|
5426
|
+
try {
|
|
5427
|
+
fs7.unlinkSync(tempPath);
|
|
5428
|
+
} catch {
|
|
5429
|
+
}
|
|
5430
|
+
throw new Error(`\u5185\u6838\u81EA\u68C0\u5931\u8D25\uFF08\u53EF\u80FD\u4E0B\u8F7D\u635F\u574F\u6216\u67B6\u6784\u4E0D\u5339\u914D\uFF09\uFF0C\u5DF2\u5220\u9664
|
|
5431
|
+
\u9000\u51FA\u7801: ${check.status}
|
|
5432
|
+
\u8F93\u51FA: ${checkOutput || "(\u7A7A)"}`);
|
|
5102
5433
|
}
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
printUpdateResult(r);
|
|
5434
|
+
try {
|
|
5435
|
+
fs7.unlinkSync(tempPath);
|
|
5436
|
+
} catch {
|
|
5107
5437
|
}
|
|
5108
|
-
|
|
5438
|
+
clearKernelVersionCache();
|
|
5439
|
+
return { version: latest.tag_name, path: targetPath };
|
|
5109
5440
|
}
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
const
|
|
5441
|
+
|
|
5442
|
+
// src/commands/kernel.ts
|
|
5443
|
+
async function cmdKernel(args) {
|
|
5444
|
+
const mirrorInfo = parseMirrorArg(args);
|
|
5445
|
+
const effectiveMirror = mirrorInfo.mirror;
|
|
5446
|
+
if (effectiveMirror) {
|
|
5447
|
+
const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
|
|
5448
|
+
console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
|
|
5449
|
+
console.log("");
|
|
5450
|
+
}
|
|
5451
|
+
console.log("\u68C0\u67E5\u5185\u6838\u66F4\u65B0...");
|
|
5114
5452
|
try {
|
|
5115
|
-
const
|
|
5116
|
-
const
|
|
5117
|
-
|
|
5118
|
-
|
|
5453
|
+
const apiMirror = mirrorInfo.type === "all" ? effectiveMirror : null;
|
|
5454
|
+
const info = await checkUpdate(apiMirror);
|
|
5455
|
+
console.log(`\u5F53\u524D: ${info.current}`);
|
|
5456
|
+
console.log(`\u6700\u65B0: ${info.latest}`);
|
|
5457
|
+
if (!info.needsUpdate) {
|
|
5458
|
+
console.log("\u5DF2\u662F\u6700\u65B0\u7248\u672C");
|
|
5459
|
+
} else {
|
|
5460
|
+
console.log("\n\u6B63\u5728\u4E0B\u8F7D...");
|
|
5461
|
+
const result = await downloadKernel((msg) => console.log(msg), mirrorInfo.mirror, info.release);
|
|
5462
|
+
console.log(`
|
|
5463
|
+
\u5DF2\u66F4\u65B0\u5230 ${result.version}`);
|
|
5119
5464
|
}
|
|
5120
|
-
return { name: proxyName, delay: null, error: data.message || "no delay" };
|
|
5121
5465
|
} catch (e) {
|
|
5466
|
+
console.error(`
|
|
5467
|
+
\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
5122
5468
|
const err = e;
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5469
|
+
if (err.response?.data) {
|
|
5470
|
+
if (err.response.data.message) {
|
|
5471
|
+
console.error(`\u539F\u56E0: ${err.response.data.message}`);
|
|
5472
|
+
}
|
|
5473
|
+
if (err.response.data.documentation_url) {
|
|
5474
|
+
console.error(`\u6587\u6863: ${err.response.data.documentation_url}`);
|
|
5475
|
+
}
|
|
5128
5476
|
}
|
|
5129
|
-
|
|
5477
|
+
if (!effectiveMirror) {
|
|
5478
|
+
console.error("");
|
|
5479
|
+
console.error("\u63D0\u793A: \u76F4\u8FDE\u5931\u8D25\u6216\u4E0B\u8F7D\u8FC7\u6162\u65F6\u53EF\u4F7F\u7528\u955C\u50CF:");
|
|
5480
|
+
console.error(" mihomo kernel --mirror [\u955C\u50CF] # \u4E0B\u8F7D\u8D70\u955C\u50CF\uFF08\u9ED8\u8BA4 v6.gh-proxy.org\uFF09");
|
|
5481
|
+
console.error(" mihomo kernel --mirror-all [\u955C\u50CF] # API \u548C\u4E0B\u8F7D\u90FD\u8D70\u955C\u50CF");
|
|
5482
|
+
console.error(` \u53EF\u7528\u955C\u50CF: ${AVAILABLE_MIRRORS.join(", ")}`);
|
|
5483
|
+
}
|
|
5484
|
+
process.exit(1);
|
|
5130
5485
|
}
|
|
5131
5486
|
}
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
const results = new Array(proxies.length);
|
|
5140
|
-
let completedCount = 0;
|
|
5141
|
-
let nextIndex = 0;
|
|
5142
|
-
async function runNext() {
|
|
5143
|
-
while (nextIndex < proxies.length) {
|
|
5144
|
-
const idx = nextIndex++;
|
|
5145
|
-
const result = await testProxyDelay(proxies[idx].name, timeout, testUrl, client, apiBase);
|
|
5146
|
-
results[idx] = result;
|
|
5147
|
-
onResult?.(result, completedCount, proxies.length);
|
|
5148
|
-
completedCount++;
|
|
5149
|
-
}
|
|
5487
|
+
|
|
5488
|
+
// src/commands/log.ts
|
|
5489
|
+
function cmdLog(args) {
|
|
5490
|
+
const logPath = getLogPath();
|
|
5491
|
+
if (hasFlag(args, "-o", "--open")) {
|
|
5492
|
+
openLogFile(logPath);
|
|
5493
|
+
return;
|
|
5150
5494
|
}
|
|
5151
|
-
|
|
5152
|
-
await Promise.all(workers);
|
|
5153
|
-
const alive = results.filter((r) => r.delay !== null).length;
|
|
5154
|
-
return { total: results.length, alive, dead: results.length - alive, results };
|
|
5495
|
+
viewLogWithTail(logPath, { follow: true, lines: 50 });
|
|
5155
5496
|
}
|
|
5156
|
-
function
|
|
5157
|
-
const
|
|
5158
|
-
const
|
|
5159
|
-
const
|
|
5160
|
-
|
|
5161
|
-
|
|
5162
|
-
if (
|
|
5163
|
-
|
|
5164
|
-
usedNames.add(shortened);
|
|
5497
|
+
function cmdLogs(args) {
|
|
5498
|
+
const targetName = getNonFlagArg(args, 1);
|
|
5499
|
+
const lines = parseIntArg(args, "-n", "--lines", 100);
|
|
5500
|
+
const openInViewer = hasFlag(args, "-o", "--open");
|
|
5501
|
+
if (targetName) {
|
|
5502
|
+
let logPath;
|
|
5503
|
+
if (targetName === "current" || targetName === "0") {
|
|
5504
|
+
logPath = getLogPath();
|
|
5165
5505
|
} else {
|
|
5166
|
-
|
|
5506
|
+
const parsedIdx = parseInt(targetName, 10);
|
|
5507
|
+
if (!Number.isNaN(parsedIdx) && parsedIdx > 0 && String(parsedIdx) === targetName) {
|
|
5508
|
+
const archiveLogs = listLogs();
|
|
5509
|
+
const archive = archiveLogs.archives[parsedIdx - 1];
|
|
5510
|
+
if (!archive) {
|
|
5511
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
5512
|
+
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
5513
|
+
process.exit(1);
|
|
5514
|
+
}
|
|
5515
|
+
logPath = archive.path;
|
|
5516
|
+
} else {
|
|
5517
|
+
logPath = getLogPathByName(targetName);
|
|
5518
|
+
}
|
|
5519
|
+
}
|
|
5520
|
+
if (!logPath) {
|
|
5521
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u65E5\u5FD7 "${targetName}"`);
|
|
5522
|
+
console.log('\u4F7F\u7528 "mihomo logs" \u67E5\u770B\u53EF\u7528\u65E5\u5FD7\u5217\u8868');
|
|
5523
|
+
process.exit(1);
|
|
5167
5524
|
}
|
|
5168
|
-
|
|
5169
|
-
|
|
5170
|
-
|
|
5171
|
-
const newName = renameMap.get(proxy.name);
|
|
5172
|
-
if (newName) proxy.name = newName;
|
|
5173
|
-
}
|
|
5174
|
-
for (const group of proxyGroups) {
|
|
5175
|
-
if (Array.isArray(group.proxies)) {
|
|
5176
|
-
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
5525
|
+
if (openInViewer) {
|
|
5526
|
+
openLogFile(logPath);
|
|
5527
|
+
return;
|
|
5177
5528
|
}
|
|
5529
|
+
viewLogWithTail(logPath, { follow: false, lines });
|
|
5530
|
+
return;
|
|
5178
5531
|
}
|
|
5179
|
-
|
|
5180
|
-
|
|
5181
|
-
|
|
5182
|
-
|
|
5183
|
-
|
|
5184
|
-
|
|
5185
|
-
|
|
5186
|
-
let updatedGroups = 0;
|
|
5187
|
-
const removedGroupNames = /* @__PURE__ */ new Set();
|
|
5188
|
-
for (const group of proxyGroups) {
|
|
5189
|
-
if (Array.isArray(group.proxies)) {
|
|
5190
|
-
const before = group.proxies.length;
|
|
5191
|
-
group.proxies = group.proxies.filter((name) => !deadNames.has(name));
|
|
5192
|
-
if (group.proxies.length < before) {
|
|
5193
|
-
updatedGroups++;
|
|
5194
|
-
}
|
|
5195
|
-
if (group.proxies.length === 0) {
|
|
5196
|
-
removedGroupNames.add(group.name);
|
|
5197
|
-
}
|
|
5198
|
-
}
|
|
5532
|
+
const logs = listLogs();
|
|
5533
|
+
const all = [];
|
|
5534
|
+
if (logs.current) all.push(logs.current);
|
|
5535
|
+
all.push(...logs.archives);
|
|
5536
|
+
if (all.length === 0) {
|
|
5537
|
+
console.log("\u6682\u65E0\u65E5\u5FD7");
|
|
5538
|
+
return;
|
|
5199
5539
|
}
|
|
5200
|
-
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
|
|
5540
|
+
console.log("");
|
|
5541
|
+
console.log("\u65E5\u5FD7\u5217\u8868:");
|
|
5542
|
+
console.log("");
|
|
5543
|
+
let archiveCounter = 0;
|
|
5544
|
+
for (const log of all) {
|
|
5545
|
+
let num;
|
|
5546
|
+
if (log.isCurrent) {
|
|
5547
|
+
num = " 0";
|
|
5548
|
+
} else {
|
|
5549
|
+
archiveCounter++;
|
|
5550
|
+
num = archiveCounter < 10 ? ` ${archiveCounter}` : `${archiveCounter}`;
|
|
5206
5551
|
}
|
|
5207
|
-
const
|
|
5208
|
-
|
|
5209
|
-
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
});
|
|
5552
|
+
const time = formatDate(log.mtime);
|
|
5553
|
+
const size = formatBytes(log.size);
|
|
5554
|
+
const name = log.isCurrent ? "mihomo.log (\u5F53\u524D\u8FD0\u884C\u4E2D)" : log.name;
|
|
5555
|
+
console.log(` ${num}. ${name}`);
|
|
5556
|
+
console.log(` \u65F6\u95F4: ${time} \u5927\u5C0F: ${size}`);
|
|
5557
|
+
if (!log.isCurrent) {
|
|
5558
|
+
console.log(` \u67E5\u770B: mihomo logs ${archiveCounter} \u6216 mihomo logs ${archiveCounter} -o`);
|
|
5215
5559
|
}
|
|
5560
|
+
console.log("");
|
|
5216
5561
|
}
|
|
5217
|
-
|
|
5562
|
+
console.log("\u7528\u6CD5:");
|
|
5563
|
+
console.log(" mihomo logs 0 # \u67E5\u770B\u5F53\u524D\u65E5\u5FD7 (\u6700\u540E 100 \u884C)");
|
|
5564
|
+
console.log(" mihomo logs 1 # \u67E5\u770B\u7B2C 1 \u4E2A\u5F52\u6863\u65E5\u5FD7\uFF08\u6700\u65B0\uFF09");
|
|
5565
|
+
console.log(" mihomo logs 1 -n 200 # \u67E5\u770B 200 \u884C");
|
|
5566
|
+
console.log(" mihomo logs 1 -o # \u7528\u7CFB\u7EDF\u9ED8\u8BA4\u7A0B\u5E8F\u6253\u5F00");
|
|
5567
|
+
console.log("");
|
|
5218
5568
|
}
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
|
|
5224
|
-
|
|
5225
|
-
|
|
5226
|
-
|
|
5227
|
-
|
|
5228
|
-
|
|
5229
|
-
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
if (summary.dead > 0) {
|
|
5233
|
-
if (summary.alive === 0 || summary.alive / summary.total < 0.01) {
|
|
5234
|
-
skipped = true;
|
|
5235
|
-
} else {
|
|
5236
|
-
const deadNames = new Set(summary.results.filter((r) => r.delay === null).map((r) => r.name));
|
|
5237
|
-
const deadProxies = parsed.proxies.filter((p) => deadNames.has(p.name));
|
|
5238
|
-
for (let retry = 0; retry < rounds - 1; retry++) {
|
|
5239
|
-
const round = retry + 2;
|
|
5240
|
-
const retryTargets = deadProxies.filter((p) => deadNames.has(p.name));
|
|
5241
|
-
if (retryTargets.length === 0) break;
|
|
5242
|
-
onRetryRound?.(round, retryTargets.length);
|
|
5243
|
-
const retryParsed = { raw: {}, proxies: retryTargets, proxyGroups: [] };
|
|
5244
|
-
const retrySummary = await testSubscriptionProxies(subName, {
|
|
5245
|
-
...testOptions,
|
|
5246
|
-
parsed: retryParsed,
|
|
5247
|
-
onResult: wrapOnResult(round)
|
|
5248
|
-
});
|
|
5249
|
-
for (const r of retrySummary.results) {
|
|
5250
|
-
if (r.delay !== null) {
|
|
5251
|
-
deadNames.delete(r.name);
|
|
5252
|
-
}
|
|
5253
|
-
}
|
|
5254
|
-
}
|
|
5255
|
-
summary.dead = deadNames.size;
|
|
5256
|
-
summary.alive = summary.total - summary.dead;
|
|
5257
|
-
if (deadNames.size > 0) {
|
|
5258
|
-
const cleanResult = cleanDeadProxies(parsed, deadNames);
|
|
5259
|
-
removedProxies = cleanResult.removedProxies;
|
|
5260
|
-
updatedGroups = cleanResult.updatedGroups;
|
|
5261
|
-
removedGroups = cleanResult.removedGroups;
|
|
5262
|
-
}
|
|
5263
|
-
}
|
|
5569
|
+
|
|
5570
|
+
// src/commands/overwrite.ts
|
|
5571
|
+
import path8 from "path";
|
|
5572
|
+
|
|
5573
|
+
// src/runtime.ts
|
|
5574
|
+
function getRuntimeMode() {
|
|
5575
|
+
if (isDaemonEnabled()) return "mixed";
|
|
5576
|
+
return getConfigInfo()?.tun ? "tun" : "mixed";
|
|
5577
|
+
}
|
|
5578
|
+
function getRunningState() {
|
|
5579
|
+
if (isDaemonEnabled()) {
|
|
5580
|
+
const daemon = getDaemonStatus();
|
|
5581
|
+
return { running: isDaemonRunning(daemon), pid: daemon.pid, daemon: true };
|
|
5264
5582
|
}
|
|
5265
|
-
|
|
5266
|
-
|
|
5583
|
+
const status = getStatus();
|
|
5584
|
+
return { running: status.running, pid: status.pid, daemon: false };
|
|
5585
|
+
}
|
|
5586
|
+
function isRestartNeededOnChange() {
|
|
5587
|
+
return isDaemonEnabled() || getStatus().running;
|
|
5588
|
+
}
|
|
5589
|
+
async function launchOrRestart(mode) {
|
|
5590
|
+
if (isDaemonEnabled()) {
|
|
5591
|
+
await restartDaemon();
|
|
5592
|
+
await sleep(DAEMON_BOOT_WAIT_MS);
|
|
5593
|
+
return getDaemonStatus().pid;
|
|
5267
5594
|
}
|
|
5268
|
-
|
|
5595
|
+
const result = await start(mode);
|
|
5596
|
+
return result.pid;
|
|
5269
5597
|
}
|
|
5270
5598
|
|
|
5271
5599
|
// src/commands/status.ts
|
|
5272
5600
|
function printStatus() {
|
|
5273
5601
|
const status = getStatus();
|
|
5602
|
+
const state = getRunningState();
|
|
5274
5603
|
const info = getConfigInfo();
|
|
5275
5604
|
const overwriteEnabled = isOverwriteEnabled();
|
|
5276
5605
|
const overwriteFiles = listOverwriteFile().files;
|
|
5277
5606
|
const activeSub = getActiveSubscription();
|
|
5607
|
+
const { running, pid, daemon: daemonManaged } = state;
|
|
5278
5608
|
console.log("");
|
|
5279
5609
|
let modeLabel = "";
|
|
5280
|
-
if (info &&
|
|
5610
|
+
if (info && running) {
|
|
5281
5611
|
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
5282
5612
|
}
|
|
5283
|
-
const statusText =
|
|
5613
|
+
const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
|
|
5284
5614
|
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}${modeLabel}`);
|
|
5285
5615
|
console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
|
|
5286
|
-
if (
|
|
5287
|
-
console.log(`${colors.gray("PID: ")}${
|
|
5288
|
-
if (status.processInfo) {
|
|
5616
|
+
if (pid) {
|
|
5617
|
+
console.log(`${colors.gray("PID: ")}${pid}`);
|
|
5618
|
+
if (!daemonManaged && status.processInfo) {
|
|
5289
5619
|
console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
|
|
5290
5620
|
}
|
|
5291
5621
|
}
|
|
@@ -5316,6 +5646,9 @@ function printStatus() {
|
|
|
5316
5646
|
} else {
|
|
5317
5647
|
console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
|
|
5318
5648
|
}
|
|
5649
|
+
if (isDaemonEnabled()) {
|
|
5650
|
+
console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
|
|
5651
|
+
}
|
|
5319
5652
|
console.log("");
|
|
5320
5653
|
}
|
|
5321
5654
|
|
|
@@ -5328,7 +5661,12 @@ function handleStopResult(result) {
|
|
|
5328
5661
|
}
|
|
5329
5662
|
}
|
|
5330
5663
|
async function cmdStop() {
|
|
5331
|
-
|
|
5664
|
+
if (isDaemonEnabled()) {
|
|
5665
|
+
console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u4EE3\u7406\u7531 launchd \u6258\u7BA1"));
|
|
5666
|
+
console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
|
|
5667
|
+
return;
|
|
5668
|
+
}
|
|
5669
|
+
const pids = getMihomoPids();
|
|
5332
5670
|
if (pids.length === 0) {
|
|
5333
5671
|
console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
|
|
5334
5672
|
return;
|
|
@@ -5340,8 +5678,8 @@ async function cmdStop() {
|
|
|
5340
5678
|
|
|
5341
5679
|
// src/test-instance.ts
|
|
5342
5680
|
import { spawn as spawn2 } from "child_process";
|
|
5343
|
-
import
|
|
5344
|
-
import
|
|
5681
|
+
import fs8 from "fs";
|
|
5682
|
+
import path7 from "path";
|
|
5345
5683
|
|
|
5346
5684
|
// src/lifecycle.ts
|
|
5347
5685
|
var cleanupFns = /* @__PURE__ */ new Set();
|
|
@@ -5362,20 +5700,20 @@ function runCleanup() {
|
|
|
5362
5700
|
}
|
|
5363
5701
|
|
|
5364
5702
|
// src/test-instance.ts
|
|
5365
|
-
var TEST_DIR =
|
|
5703
|
+
var TEST_DIR = path7.join(USER_DATA_DIR, "test");
|
|
5366
5704
|
var TEST_DIRS = {
|
|
5367
|
-
data:
|
|
5368
|
-
runtime:
|
|
5705
|
+
data: path7.join(TEST_DIR, "data"),
|
|
5706
|
+
runtime: path7.join(TEST_DIR, "runtime")
|
|
5369
5707
|
};
|
|
5370
5708
|
var TEST_PATHS = {
|
|
5371
|
-
configFile:
|
|
5372
|
-
pidFile:
|
|
5373
|
-
logFile:
|
|
5709
|
+
configFile: path7.join(TEST_DIRS.runtime, "config.yaml"),
|
|
5710
|
+
pidFile: path7.join(TEST_DIRS.runtime, "pid"),
|
|
5711
|
+
logFile: path7.join(TEST_DIR, "test.log")
|
|
5374
5712
|
};
|
|
5375
5713
|
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
5376
5714
|
function ensureTestDirs() {
|
|
5377
5715
|
for (const dir of Object.values(TEST_DIRS)) {
|
|
5378
|
-
|
|
5716
|
+
fs8.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
5379
5717
|
}
|
|
5380
5718
|
}
|
|
5381
5719
|
function cleanupTestDir() {
|
|
@@ -5412,22 +5750,22 @@ function buildTestConfig(subName) {
|
|
|
5412
5750
|
],
|
|
5413
5751
|
rules: ["MATCH,PROXY"]
|
|
5414
5752
|
};
|
|
5415
|
-
const content =
|
|
5416
|
-
|
|
5753
|
+
const content = dumpYaml(config);
|
|
5754
|
+
fs8.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
5417
5755
|
}
|
|
5418
5756
|
async function startTestInstance() {
|
|
5419
5757
|
const binary = PATHS.mihomoBinary;
|
|
5420
|
-
if (!
|
|
5758
|
+
if (!fs8.existsSync(binary)) throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838");
|
|
5421
5759
|
stopTestInstance();
|
|
5422
|
-
const logFd =
|
|
5760
|
+
const logFd = fs8.openSync(TEST_PATHS.logFile, "a");
|
|
5423
5761
|
const child = spawn2(binary, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
|
|
5424
5762
|
detached: true,
|
|
5425
5763
|
stdio: ["ignore", logFd, logFd]
|
|
5426
5764
|
});
|
|
5427
|
-
|
|
5765
|
+
fs8.closeSync(logFd);
|
|
5428
5766
|
child.unref();
|
|
5429
5767
|
const pid = child.pid;
|
|
5430
|
-
|
|
5768
|
+
fs8.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
5431
5769
|
const client = createHttpClient({ timeout: 2e3 });
|
|
5432
5770
|
let ready = false;
|
|
5433
5771
|
for (let i = 0; i < 60; i++) {
|
|
@@ -5443,7 +5781,7 @@ async function startTestInstance() {
|
|
|
5443
5781
|
if (!isProcessRunning(pid)) {
|
|
5444
5782
|
let errorDetail = "";
|
|
5445
5783
|
try {
|
|
5446
|
-
errorDetail =
|
|
5784
|
+
errorDetail = fs8.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
|
|
5447
5785
|
} catch {
|
|
5448
5786
|
}
|
|
5449
5787
|
throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
@@ -5456,7 +5794,7 @@ ${errorDetail}` : ""}`);
|
|
|
5456
5794
|
function stopTestInstance() {
|
|
5457
5795
|
let pid;
|
|
5458
5796
|
try {
|
|
5459
|
-
pid = parseInt(
|
|
5797
|
+
pid = parseInt(fs8.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
|
|
5460
5798
|
} catch {
|
|
5461
5799
|
return;
|
|
5462
5800
|
}
|
|
@@ -5468,7 +5806,7 @@ function stopTestInstance() {
|
|
|
5468
5806
|
}
|
|
5469
5807
|
}
|
|
5470
5808
|
try {
|
|
5471
|
-
|
|
5809
|
+
fs8.unlinkSync(TEST_PATHS.pidFile);
|
|
5472
5810
|
} catch {
|
|
5473
5811
|
}
|
|
5474
5812
|
}
|
|
@@ -5582,6 +5920,12 @@ function resolveTestTarget(args) {
|
|
|
5582
5920
|
}
|
|
5583
5921
|
return { target, timeout, concurrency };
|
|
5584
5922
|
}
|
|
5923
|
+
function printRestartHintIfRunning() {
|
|
5924
|
+
if (getRunningState().running) {
|
|
5925
|
+
console.log(colors.yellow("\u63D0\u793A: \u8FD0\u884C\u4E2D\u7684\u5B9E\u4F8B\u4ECD\u4F7F\u7528\u65E7\u914D\u7F6E\uFF0C\u6267\u884C mihomo start \u4F7F\u66F4\u65B0\u751F\u6548"));
|
|
5926
|
+
console.log("");
|
|
5927
|
+
}
|
|
5928
|
+
}
|
|
5585
5929
|
async function printSubscriptionList(options) {
|
|
5586
5930
|
if (options?.autoUpdate !== false) {
|
|
5587
5931
|
const updateResult = await autoUpdateStaleSubscription();
|
|
@@ -5705,6 +6049,7 @@ async function cmdSubscription(args) {
|
|
|
5705
6049
|
}
|
|
5706
6050
|
if (ok === 0) process.exit(1);
|
|
5707
6051
|
console.log("");
|
|
6052
|
+
printRestartHintIfRunning();
|
|
5708
6053
|
await printSubscriptionList();
|
|
5709
6054
|
return;
|
|
5710
6055
|
}
|
|
@@ -5718,6 +6063,7 @@ async function cmdSubscription(args) {
|
|
|
5718
6063
|
}
|
|
5719
6064
|
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
|
|
5720
6065
|
console.log("");
|
|
6066
|
+
printRestartHintIfRunning();
|
|
5721
6067
|
await printSubscriptionList();
|
|
5722
6068
|
return;
|
|
5723
6069
|
}
|
|
@@ -5742,9 +6088,8 @@ async function cmdSubscription(args) {
|
|
|
5742
6088
|
await printSubscriptionList();
|
|
5743
6089
|
return;
|
|
5744
6090
|
}
|
|
5745
|
-
const
|
|
5746
|
-
const
|
|
5747
|
-
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
6091
|
+
const currentMode = getRuntimeMode();
|
|
6092
|
+
const restartNeeded = isRestartNeededOnChange();
|
|
5748
6093
|
const success = setDefaultSubscription(target.name);
|
|
5749
6094
|
if (success) {
|
|
5750
6095
|
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
@@ -5752,7 +6097,7 @@ async function cmdSubscription(args) {
|
|
|
5752
6097
|
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
5753
6098
|
process.exit(1);
|
|
5754
6099
|
}
|
|
5755
|
-
if (
|
|
6100
|
+
if (restartNeeded) {
|
|
5756
6101
|
console.log("");
|
|
5757
6102
|
await cmdStart(["start", currentMode]);
|
|
5758
6103
|
return;
|
|
@@ -5846,8 +6191,7 @@ async function cmdSubscription(args) {
|
|
|
5846
6191
|
console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
|
|
5847
6192
|
} else if (result.removedProxies > 0) {
|
|
5848
6193
|
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
5849
|
-
|
|
5850
|
-
if (status.running) {
|
|
6194
|
+
if (getRunningState().running) {
|
|
5851
6195
|
console.log("");
|
|
5852
6196
|
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
5853
6197
|
}
|
|
@@ -5884,6 +6228,12 @@ async function cmdStart(args) {
|
|
|
5884
6228
|
process.exit(1);
|
|
5885
6229
|
}
|
|
5886
6230
|
const targetMode = args[1] === "tun" ? "tun" : "mixed";
|
|
6231
|
+
const daemonEnabled = isDaemonEnabled();
|
|
6232
|
+
if (targetMode === "tun" && daemonEnabled) {
|
|
6233
|
+
console.error(`${colors.red("\u9519\u8BEF:")} \u4FDD\u6D3B\u5DF2\u542F\u7528\uFF08\u4EC5\u652F\u6301 Mixed \u6A21\u5F0F\uFF09\uFF0C\u65E0\u6CD5\u542F\u52A8 TUN`);
|
|
6234
|
+
console.error("\u8BF7\u5148\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
|
|
6235
|
+
process.exit(1);
|
|
6236
|
+
}
|
|
5887
6237
|
const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
|
|
5888
6238
|
const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
|
|
5889
6239
|
const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
|
|
@@ -5897,16 +6247,18 @@ async function cmdStart(args) {
|
|
|
5897
6247
|
if (!skipUpdate) {
|
|
5898
6248
|
await autoUpdateStaleSubscription({ timeout: updateTimeout });
|
|
5899
6249
|
}
|
|
5900
|
-
|
|
5901
|
-
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
6250
|
+
if (!daemonEnabled) {
|
|
6251
|
+
const status = getStatus();
|
|
6252
|
+
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
6253
|
+
if (hasProcess) {
|
|
6254
|
+
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
6255
|
+
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
6256
|
+
}
|
|
6257
|
+
handleStopResult(stop());
|
|
6258
|
+
if (hasProcess) {
|
|
6259
|
+
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
5909
6260
|
`);
|
|
6261
|
+
}
|
|
5910
6262
|
}
|
|
5911
6263
|
let configInfo;
|
|
5912
6264
|
try {
|
|
@@ -5918,8 +6270,9 @@ async function cmdStart(args) {
|
|
|
5918
6270
|
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
5919
6271
|
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
5920
6272
|
try {
|
|
5921
|
-
const
|
|
5922
|
-
|
|
6273
|
+
const pid = await launchOrRestart(targetMode);
|
|
6274
|
+
const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
|
|
6275
|
+
console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
|
|
5923
6276
|
} catch (e) {
|
|
5924
6277
|
const msg = e.message;
|
|
5925
6278
|
const lines = msg.split("\n");
|
|
@@ -5951,11 +6304,11 @@ async function cmdStart(args) {
|
|
|
5951
6304
|
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
5952
6305
|
console.log("");
|
|
5953
6306
|
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
5954
|
-
handleStopResult(stop());
|
|
6307
|
+
if (!daemonEnabled) handleStopResult(stop());
|
|
5955
6308
|
try {
|
|
5956
6309
|
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
5957
|
-
const
|
|
5958
|
-
console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${
|
|
6310
|
+
const pid = await launchOrRestart(targetMode);
|
|
6311
|
+
console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
|
|
5959
6312
|
} catch (e) {
|
|
5960
6313
|
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
5961
6314
|
process.exit(1);
|
|
@@ -5975,8 +6328,8 @@ function printOverwriteList() {
|
|
|
5975
6328
|
if (info.files.length === 0) {
|
|
5976
6329
|
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
5977
6330
|
console.log("");
|
|
5978
|
-
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${
|
|
5979
|
-
console.log(` \u6216 ${
|
|
6331
|
+
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path8.join(info.dir, "overwrite.yaml")}`);
|
|
6332
|
+
console.log(` \u6216 ${path8.join(info.dir, "overwrite.dns.yaml")}`);
|
|
5980
6333
|
console.log("");
|
|
5981
6334
|
} else {
|
|
5982
6335
|
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
@@ -5996,9 +6349,8 @@ function printOverwriteList() {
|
|
|
5996
6349
|
}
|
|
5997
6350
|
async function cmdOverwrite(args) {
|
|
5998
6351
|
const action = args?.[1];
|
|
5999
|
-
const
|
|
6000
|
-
const
|
|
6001
|
-
const currentMode = configInfo?.tun ? "tun" : "mixed";
|
|
6352
|
+
const currentMode = getRuntimeMode();
|
|
6353
|
+
const restartNeeded = isRestartNeededOnChange();
|
|
6002
6354
|
if (action === "on" || action === "enable") {
|
|
6003
6355
|
if (isOverwriteEnabled()) {
|
|
6004
6356
|
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
|
|
@@ -6008,7 +6360,7 @@ async function cmdOverwrite(args) {
|
|
|
6008
6360
|
}
|
|
6009
6361
|
setOverwriteEnabled(true);
|
|
6010
6362
|
console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
|
|
6011
|
-
if (
|
|
6363
|
+
if (restartNeeded) {
|
|
6012
6364
|
console.log("");
|
|
6013
6365
|
await cmdStart(["start", currentMode]);
|
|
6014
6366
|
return;
|
|
@@ -6026,7 +6378,7 @@ async function cmdOverwrite(args) {
|
|
|
6026
6378
|
}
|
|
6027
6379
|
setOverwriteEnabled(false);
|
|
6028
6380
|
console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
|
|
6029
|
-
if (
|
|
6381
|
+
if (restartNeeded) {
|
|
6030
6382
|
console.log("");
|
|
6031
6383
|
await cmdStart(["start", currentMode]);
|
|
6032
6384
|
return;
|
|
@@ -6040,7 +6392,7 @@ async function cmdOverwrite(args) {
|
|
|
6040
6392
|
}
|
|
6041
6393
|
|
|
6042
6394
|
// src/commands/reset.ts
|
|
6043
|
-
import
|
|
6395
|
+
import fs9 from "fs";
|
|
6044
6396
|
import readline from "readline";
|
|
6045
6397
|
var RESET_TARGETS = [
|
|
6046
6398
|
{
|
|
@@ -6095,10 +6447,23 @@ var RESET_TARGETS = [
|
|
|
6095
6447
|
label: "\u8986\u5199",
|
|
6096
6448
|
paths: () => {
|
|
6097
6449
|
const dir = USER_DATA_DIR;
|
|
6098
|
-
if (!
|
|
6099
|
-
return
|
|
6450
|
+
if (!fs9.existsSync(dir)) return [];
|
|
6451
|
+
return fs9.readdirSync(dir).filter(isOverwriteFilename).map((f) => `${dir}/${f}`);
|
|
6100
6452
|
},
|
|
6101
6453
|
needsStop: false
|
|
6454
|
+
},
|
|
6455
|
+
{
|
|
6456
|
+
id: "daemon",
|
|
6457
|
+
aliases: ["daemon"],
|
|
6458
|
+
label: "\u4FDD\u6D3B",
|
|
6459
|
+
// 卸载由确认后的 disablesDaemon 段统一处理(需 sudo,受取消保护);
|
|
6460
|
+
// 此处 paths 返回空(plist 在系统目录,用户态删不掉,且不应提前删破坏卸载),
|
|
6461
|
+
// onAfter 因幂等守卫(plist 已删)成为 no-op,仅作单独 reset 未走前段时的兜底。
|
|
6462
|
+
paths: () => [],
|
|
6463
|
+
needsStop: false,
|
|
6464
|
+
onAfter: () => disableDaemon(),
|
|
6465
|
+
checkEmpty: () => !isDaemonEnabled(),
|
|
6466
|
+
emptyMsg: "\u4FDD\u6D3B\u672A\u542F\u7528\uFF0C\u65E0\u9700\u5220\u9664"
|
|
6102
6467
|
}
|
|
6103
6468
|
];
|
|
6104
6469
|
function resolveResetTargets(names) {
|
|
@@ -6148,7 +6513,7 @@ async function cmdReset(args) {
|
|
|
6148
6513
|
}
|
|
6149
6514
|
targets = matched;
|
|
6150
6515
|
} else {
|
|
6151
|
-
targets = RESET_TARGETS.filter((t) => !["settings", "kernel", "overwrites"].includes(t.id));
|
|
6516
|
+
targets = RESET_TARGETS.filter((t) => !["settings", "kernel", "overwrites", "daemon"].includes(t.id));
|
|
6152
6517
|
}
|
|
6153
6518
|
for (const t of targets) {
|
|
6154
6519
|
if (t.checkEmpty?.()) {
|
|
@@ -6160,25 +6525,40 @@ async function cmdReset(args) {
|
|
|
6160
6525
|
}
|
|
6161
6526
|
const needsStop = targets.some((t) => t.needsStop);
|
|
6162
6527
|
const warnRunning = targets.some((t) => t.warnIfRunning);
|
|
6163
|
-
const
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
if (getAllMihomoPids().length === 0) break;
|
|
6169
|
-
await new Promise((r) => setTimeout(r, PROCESS_WAIT_INTERVAL));
|
|
6170
|
-
}
|
|
6171
|
-
} else if (warnRunning && pids.length > 0) {
|
|
6528
|
+
const kernelTargeted = targets.some((t) => t.id === "kernel");
|
|
6529
|
+
const daemonTargeted = targets.some((t) => t.id === "daemon");
|
|
6530
|
+
const disablesDaemon = needsStop || kernelTargeted || daemonTargeted;
|
|
6531
|
+
const pids = needsStop || warnRunning ? getMihomoPids() : [];
|
|
6532
|
+
if (warnRunning && pids.length > 0) {
|
|
6172
6533
|
console.log(colors.yellow(`\u8B66\u544A: mihomo \u6B63\u5728\u8FD0\u884C (PID ${pids.join(", ")})\uFF0C\u5220\u9664\u5185\u6838\u540E\u5C06\u65E0\u6CD5\u91CD\u65B0\u542F\u52A8`));
|
|
6173
6534
|
}
|
|
6535
|
+
if (disablesDaemon && isDaemonEnabled()) {
|
|
6536
|
+
console.log(colors.yellow("\u4FDD\u6D3B\u5DF2\u542F\u7528\uFF0C\u91CD\u7F6E\u5C06\u4E00\u5E76\u5173\u95ED\u4FDD\u6D3B\uFF08\u79FB\u9664\u5F00\u673A\u81EA\u542F\uFF09"));
|
|
6537
|
+
}
|
|
6174
6538
|
console.log(`\u5C06\u5220\u9664: ${targets.map((t) => t.label).join("\u3001")}`);
|
|
6175
6539
|
if (!skipConfirm && !await confirmPrompt("\u786E\u8BA4?")) {
|
|
6176
6540
|
console.log("\u5DF2\u53D6\u6D88");
|
|
6177
6541
|
return;
|
|
6178
6542
|
}
|
|
6543
|
+
if (disablesDaemon && isDaemonEnabled()) {
|
|
6544
|
+
try {
|
|
6545
|
+
disableDaemon();
|
|
6546
|
+
} catch (e) {
|
|
6547
|
+
console.error(`${colors.red("\u4FDD\u6D3B\u5173\u95ED\u5DF2\u53D6\u6D88\uFF0C\u91CD\u7F6E\u4E2D\u6B62:")} ${e.message.split("\n")[0]}`);
|
|
6548
|
+
return;
|
|
6549
|
+
}
|
|
6550
|
+
}
|
|
6551
|
+
if (needsStop && getMihomoPids().length > 0) {
|
|
6552
|
+
console.log("\u505C\u6B62\u8FDB\u7A0B...");
|
|
6553
|
+
cleanupAll();
|
|
6554
|
+
for (let i = 0; i < PROCESS_WAIT_ATTEMPTS; i++) {
|
|
6555
|
+
if (getMihomoPids().length === 0) break;
|
|
6556
|
+
await new Promise((r) => setTimeout(r, PROCESS_WAIT_INTERVAL));
|
|
6557
|
+
}
|
|
6558
|
+
}
|
|
6179
6559
|
for (const t of targets) {
|
|
6180
6560
|
for (const p of t.paths()) {
|
|
6181
|
-
if (
|
|
6561
|
+
if (fs9.existsSync(p)) {
|
|
6182
6562
|
try {
|
|
6183
6563
|
rmrf(p);
|
|
6184
6564
|
} catch (e) {
|
|
@@ -6197,9 +6577,10 @@ async function cmdReset(args) {
|
|
|
6197
6577
|
|
|
6198
6578
|
// src/commands/test.ts
|
|
6199
6579
|
function requireRunning() {
|
|
6200
|
-
const
|
|
6201
|
-
if (!
|
|
6202
|
-
|
|
6580
|
+
const state = getRunningState();
|
|
6581
|
+
if (!state.running) {
|
|
6582
|
+
const hint = state.daemon ? "mihomo daemon on" : "mihomo start";
|
|
6583
|
+
console.error(`\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (${hint})`);
|
|
6203
6584
|
process.exit(1);
|
|
6204
6585
|
}
|
|
6205
6586
|
}
|
|
@@ -6256,11 +6637,18 @@ async function cmdClean(args) {
|
|
|
6256
6637
|
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6257
6638
|
console.log("");
|
|
6258
6639
|
console.log("\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548...");
|
|
6259
|
-
const
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6263
|
-
|
|
6640
|
+
const mode = getRuntimeMode();
|
|
6641
|
+
const daemonManaged = isDaemonEnabled();
|
|
6642
|
+
try {
|
|
6643
|
+
if (!daemonManaged) handleStopResult(stop());
|
|
6644
|
+
const configInfo = prepareConfigForStart(mode, activeSub.name);
|
|
6645
|
+
const pid = await launchOrRestart(mode);
|
|
6646
|
+
const label = daemonManaged ? "\u5DF2\u91CD\u542F (\u4FDD\u6D3B)" : "\u5DF2\u91CD\u542F";
|
|
6647
|
+
console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
|
|
6648
|
+
} catch (e) {
|
|
6649
|
+
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
6650
|
+
process.exit(1);
|
|
6651
|
+
}
|
|
6264
6652
|
}
|
|
6265
6653
|
}
|
|
6266
6654
|
|
|
@@ -6320,6 +6708,199 @@ async function cmdUpdate() {
|
|
|
6320
6708
|
}
|
|
6321
6709
|
}
|
|
6322
6710
|
|
|
6711
|
+
// src/commands/registry.ts
|
|
6712
|
+
var COMMANDS = [
|
|
6713
|
+
// === 控制 ===
|
|
6714
|
+
{
|
|
6715
|
+
name: "start",
|
|
6716
|
+
aliases: ["up"],
|
|
6717
|
+
handler: cmdStart,
|
|
6718
|
+
group: "control",
|
|
6719
|
+
usage: ["start [tun|mixed] [-s] [-u ms] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)", " [-r N] [-t ms] [-j N]"]
|
|
6720
|
+
},
|
|
6721
|
+
{
|
|
6722
|
+
name: "tun",
|
|
6723
|
+
aliases: [],
|
|
6724
|
+
handler: cmdStart,
|
|
6725
|
+
rewrite: (args) => ["start", "tun", ...args.slice(1)],
|
|
6726
|
+
group: "control",
|
|
6727
|
+
usage: []
|
|
6728
|
+
},
|
|
6729
|
+
{
|
|
6730
|
+
name: "stop",
|
|
6731
|
+
aliases: ["down"],
|
|
6732
|
+
handler: cmdStop,
|
|
6733
|
+
group: "control",
|
|
6734
|
+
usage: ["stop \u505C\u6B62\u4EE3\u7406"]
|
|
6735
|
+
},
|
|
6736
|
+
{
|
|
6737
|
+
name: "status",
|
|
6738
|
+
aliases: [],
|
|
6739
|
+
handler: printStatus,
|
|
6740
|
+
group: "control",
|
|
6741
|
+
usage: ["status \u67E5\u770B\u72B6\u6001"]
|
|
6742
|
+
},
|
|
6743
|
+
// === 界面 ===
|
|
6744
|
+
{
|
|
6745
|
+
name: "ui",
|
|
6746
|
+
aliases: [],
|
|
6747
|
+
handler: cmdUI,
|
|
6748
|
+
group: "interface",
|
|
6749
|
+
usage: ["ui [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)"]
|
|
6750
|
+
},
|
|
6751
|
+
{
|
|
6752
|
+
name: "log",
|
|
6753
|
+
aliases: [],
|
|
6754
|
+
handler: cmdLog,
|
|
6755
|
+
group: "interface",
|
|
6756
|
+
usage: ["log [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09"]
|
|
6757
|
+
},
|
|
6758
|
+
{
|
|
6759
|
+
name: "logs",
|
|
6760
|
+
aliases: [],
|
|
6761
|
+
handler: cmdLogs,
|
|
6762
|
+
group: "interface",
|
|
6763
|
+
usage: ["logs [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09"]
|
|
6764
|
+
},
|
|
6765
|
+
// === 订阅 ===
|
|
6766
|
+
{
|
|
6767
|
+
name: "subscription",
|
|
6768
|
+
aliases: ["sub", "subscriptions"],
|
|
6769
|
+
handler: cmdSubscription,
|
|
6770
|
+
group: "subscription",
|
|
6771
|
+
usage: [
|
|
6772
|
+
"subscription \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09",
|
|
6773
|
+
"subscription use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605",
|
|
6774
|
+
"subscription add <url> [name] \u6DFB\u52A0\u8BA2\u9605",
|
|
6775
|
+
"subscription update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09",
|
|
6776
|
+
"subscription remove <name> \u5220\u9664\u8BA2\u9605",
|
|
6777
|
+
"subscription web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762",
|
|
6778
|
+
"subscription test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027",
|
|
6779
|
+
"subscription clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9"
|
|
6780
|
+
]
|
|
6781
|
+
},
|
|
6782
|
+
{
|
|
6783
|
+
name: "use",
|
|
6784
|
+
aliases: [],
|
|
6785
|
+
handler: cmdSubscription,
|
|
6786
|
+
rewrite: (args) => ["sub", "use", ...args.slice(1)],
|
|
6787
|
+
group: "subscription",
|
|
6788
|
+
usage: []
|
|
6789
|
+
},
|
|
6790
|
+
{
|
|
6791
|
+
name: "test",
|
|
6792
|
+
aliases: [],
|
|
6793
|
+
handler: cmdTest,
|
|
6794
|
+
group: "subscription",
|
|
6795
|
+
usage: ["test [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027"]
|
|
6796
|
+
},
|
|
6797
|
+
{
|
|
6798
|
+
name: "clean",
|
|
6799
|
+
aliases: [],
|
|
6800
|
+
handler: cmdClean,
|
|
6801
|
+
group: "subscription",
|
|
6802
|
+
usage: ["clean [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F"]
|
|
6803
|
+
},
|
|
6804
|
+
// === 配置 ===
|
|
6805
|
+
{
|
|
6806
|
+
name: "overwrite",
|
|
6807
|
+
aliases: ["ow"],
|
|
6808
|
+
handler: cmdOverwrite,
|
|
6809
|
+
group: "config",
|
|
6810
|
+
usage: ["overwrite \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09", "overwrite on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E"]
|
|
6811
|
+
},
|
|
6812
|
+
{
|
|
6813
|
+
name: "on",
|
|
6814
|
+
aliases: [],
|
|
6815
|
+
handler: cmdOverwrite,
|
|
6816
|
+
rewrite: () => ["ow", "on"],
|
|
6817
|
+
group: "config",
|
|
6818
|
+
usage: []
|
|
6819
|
+
},
|
|
6820
|
+
{
|
|
6821
|
+
name: "off",
|
|
6822
|
+
aliases: [],
|
|
6823
|
+
handler: cmdOverwrite,
|
|
6824
|
+
rewrite: () => ["ow", "off"],
|
|
6825
|
+
group: "config",
|
|
6826
|
+
usage: []
|
|
6827
|
+
},
|
|
6828
|
+
{
|
|
6829
|
+
name: "directory",
|
|
6830
|
+
aliases: ["dir", "dirs", "directories"],
|
|
6831
|
+
handler: cmdDirectory,
|
|
6832
|
+
group: "config",
|
|
6833
|
+
usage: ["directory \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09", "directory open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|..."]
|
|
6834
|
+
},
|
|
6835
|
+
{
|
|
6836
|
+
name: "open",
|
|
6837
|
+
aliases: [],
|
|
6838
|
+
handler: cmdDirectory,
|
|
6839
|
+
rewrite: (args) => ["dir", "open", ...args.slice(1)],
|
|
6840
|
+
group: "config",
|
|
6841
|
+
usage: []
|
|
6842
|
+
},
|
|
6843
|
+
// === 系统 ===
|
|
6844
|
+
{
|
|
6845
|
+
name: "kernel",
|
|
6846
|
+
aliases: [],
|
|
6847
|
+
handler: cmdKernel,
|
|
6848
|
+
group: "system",
|
|
6849
|
+
usage: ["kernel [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09"]
|
|
6850
|
+
},
|
|
6851
|
+
{
|
|
6852
|
+
name: "daemon",
|
|
6853
|
+
aliases: [],
|
|
6854
|
+
handler: cmdDaemon,
|
|
6855
|
+
group: "system",
|
|
6856
|
+
usage: ["daemon on|off \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F\uFF08\u4EC5 Mixed\uFF0C\u9700\u7BA1\u7406\u5458\u5BC6\u7801\uFF09", "daemon status \u67E5\u770B\u4FDD\u6D3B\u72B6\u6001"]
|
|
6857
|
+
},
|
|
6858
|
+
{
|
|
6859
|
+
name: "update",
|
|
6860
|
+
aliases: ["upd", "upgrade"],
|
|
6861
|
+
handler: cmdUpdate,
|
|
6862
|
+
group: "system",
|
|
6863
|
+
usage: ["update \u66F4\u65B0 mihomo-cli (npm install -g)"]
|
|
6864
|
+
},
|
|
6865
|
+
{
|
|
6866
|
+
name: "reset",
|
|
6867
|
+
aliases: [],
|
|
6868
|
+
handler: cmdReset,
|
|
6869
|
+
group: "system",
|
|
6870
|
+
usage: ["reset [\u76EE\u6807...] [--full] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8"]
|
|
6871
|
+
},
|
|
6872
|
+
// === meta(不在分组清单展示,help 末尾单列) ===
|
|
6873
|
+
{
|
|
6874
|
+
name: "help",
|
|
6875
|
+
aliases: ["-h", "--help"],
|
|
6876
|
+
handler: () => printHelp(COMMANDS),
|
|
6877
|
+
group: "meta",
|
|
6878
|
+
usage: ["help, -h \u663E\u793A\u5E2E\u52A9"]
|
|
6879
|
+
},
|
|
6880
|
+
{
|
|
6881
|
+
name: "version",
|
|
6882
|
+
aliases: ["-v", "--version"],
|
|
6883
|
+
handler: printVersion,
|
|
6884
|
+
group: "meta",
|
|
6885
|
+
usage: ["version, -v \u663E\u793A\u7248\u672C"]
|
|
6886
|
+
}
|
|
6887
|
+
];
|
|
6888
|
+
var COMMAND_INDEX = (() => {
|
|
6889
|
+
const index = /* @__PURE__ */ new Map();
|
|
6890
|
+
for (const cmd of COMMANDS) {
|
|
6891
|
+
for (const token of [cmd.name, ...cmd.aliases]) {
|
|
6892
|
+
if (index.has(token)) {
|
|
6893
|
+
throw new Error(`\u547D\u4EE4\u6CE8\u518C\u8868\u5B58\u5728\u91CD\u590D token: "${token}"\uFF08${index.get(token)?.name} \u4E0E ${cmd.name}\uFF09`);
|
|
6894
|
+
}
|
|
6895
|
+
index.set(token, cmd);
|
|
6896
|
+
}
|
|
6897
|
+
}
|
|
6898
|
+
return index;
|
|
6899
|
+
})();
|
|
6900
|
+
function findCommand(token) {
|
|
6901
|
+
return COMMAND_INDEX.get(token);
|
|
6902
|
+
}
|
|
6903
|
+
|
|
6323
6904
|
// src/index.ts
|
|
6324
6905
|
process.on("SIGINT", () => {
|
|
6325
6906
|
console.log("\n\u6B63\u5728\u9000\u51FA...");
|
|
@@ -6336,12 +6917,14 @@ process.on("uncaughtException", (e) => {
|
|
|
6336
6917
|
if (e.stack) {
|
|
6337
6918
|
console.error(e.stack.split("\n").slice(1).join("\n"));
|
|
6338
6919
|
}
|
|
6920
|
+
runCleanup();
|
|
6339
6921
|
process.exit(1);
|
|
6340
6922
|
});
|
|
6341
6923
|
process.on("unhandledRejection", (reason) => {
|
|
6342
6924
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
6343
6925
|
console.error(`
|
|
6344
6926
|
\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${msg}`);
|
|
6927
|
+
runCleanup();
|
|
6345
6928
|
process.exit(1);
|
|
6346
6929
|
});
|
|
6347
6930
|
function clearProxyEnv() {
|
|
@@ -6361,91 +6944,18 @@ async function main() {
|
|
|
6361
6944
|
printShortHelp();
|
|
6362
6945
|
return;
|
|
6363
6946
|
}
|
|
6364
|
-
const
|
|
6365
|
-
|
|
6366
|
-
|
|
6367
|
-
|
|
6368
|
-
|
|
6369
|
-
|
|
6370
|
-
printVersion();
|
|
6371
|
-
return;
|
|
6372
|
-
}
|
|
6373
|
-
switch (cmd) {
|
|
6374
|
-
case "up":
|
|
6375
|
-
case "start":
|
|
6376
|
-
await cmdStart(args);
|
|
6377
|
-
break;
|
|
6378
|
-
case "tun":
|
|
6379
|
-
await cmdStart(["start", "tun", ...args.slice(1)]);
|
|
6380
|
-
break;
|
|
6381
|
-
case "down":
|
|
6382
|
-
case "stop":
|
|
6383
|
-
await cmdStop();
|
|
6384
|
-
break;
|
|
6385
|
-
case "status":
|
|
6386
|
-
printStatus();
|
|
6387
|
-
break;
|
|
6388
|
-
case "log":
|
|
6389
|
-
cmdLog(args);
|
|
6390
|
-
break;
|
|
6391
|
-
case "logs":
|
|
6392
|
-
cmdLogs(args);
|
|
6393
|
-
break;
|
|
6394
|
-
case "open":
|
|
6395
|
-
cmdDirectory(["dir", "open", ...args.slice(1)]);
|
|
6396
|
-
break;
|
|
6397
|
-
case "ui":
|
|
6398
|
-
cmdUI(args);
|
|
6399
|
-
break;
|
|
6400
|
-
case "kernel":
|
|
6401
|
-
await cmdKernel(args);
|
|
6402
|
-
break;
|
|
6403
|
-
case "upd":
|
|
6404
|
-
case "update":
|
|
6405
|
-
case "upgrade":
|
|
6406
|
-
await cmdUpdate();
|
|
6407
|
-
break;
|
|
6408
|
-
case "use":
|
|
6409
|
-
await cmdSubscription(["sub", "use", ...args.slice(1)]);
|
|
6410
|
-
break;
|
|
6411
|
-
case "sub":
|
|
6412
|
-
case "subscription":
|
|
6413
|
-
case "subscriptions":
|
|
6414
|
-
await cmdSubscription(args);
|
|
6415
|
-
break;
|
|
6416
|
-
case "dir":
|
|
6417
|
-
case "dirs":
|
|
6418
|
-
case "directory":
|
|
6419
|
-
case "directories":
|
|
6420
|
-
cmdDirectory(args);
|
|
6421
|
-
break;
|
|
6422
|
-
case "reset":
|
|
6423
|
-
await cmdReset(args);
|
|
6424
|
-
break;
|
|
6425
|
-
case "on":
|
|
6426
|
-
await cmdOverwrite(["ow", "on"]);
|
|
6427
|
-
break;
|
|
6428
|
-
case "off":
|
|
6429
|
-
await cmdOverwrite(["ow", "off"]);
|
|
6430
|
-
break;
|
|
6431
|
-
case "ow":
|
|
6432
|
-
case "overwrite":
|
|
6433
|
-
await cmdOverwrite(args);
|
|
6434
|
-
break;
|
|
6435
|
-
case "test":
|
|
6436
|
-
await cmdTest(args);
|
|
6437
|
-
break;
|
|
6438
|
-
case "clean":
|
|
6439
|
-
await cmdClean(args);
|
|
6440
|
-
break;
|
|
6441
|
-
default:
|
|
6442
|
-
console.error(`\u672A\u77E5\u547D\u4EE4: ${cmd}`);
|
|
6443
|
-
console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
|
|
6444
|
-
process.exit(1);
|
|
6947
|
+
const token = args[0].toLowerCase();
|
|
6948
|
+
const command = findCommand(token);
|
|
6949
|
+
if (!command) {
|
|
6950
|
+
console.error(`\u672A\u77E5\u547D\u4EE4: ${token}`);
|
|
6951
|
+
console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
|
|
6952
|
+
process.exit(1);
|
|
6445
6953
|
}
|
|
6954
|
+
await command.handler(command.rewrite ? command.rewrite(args) : args);
|
|
6446
6955
|
}
|
|
6447
6956
|
main().catch((e) => {
|
|
6448
6957
|
console.error(`\u9519\u8BEF: ${e.message}`);
|
|
6958
|
+
runCleanup();
|
|
6449
6959
|
process.exit(1);
|
|
6450
6960
|
});
|
|
6451
6961
|
/*! Bundled license information:
|