mihomo-cli 3.0.0 → 3.3.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 +109 -0
- package/README.md +65 -25
- package/dist/index.js +1911 -1403
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/config.ts
|
|
4
|
-
import { spawnSync } from "child_process";
|
|
4
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5
5
|
import fs4 from "fs";
|
|
6
6
|
|
|
7
7
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
@@ -2917,13 +2917,18 @@ function dump(input, options = {}) {
|
|
|
2917
2917
|
}
|
|
2918
2918
|
|
|
2919
2919
|
// src/constants.ts
|
|
2920
|
-
var AVAILABLE_MIRRORS = ["gh-proxy.org", "
|
|
2920
|
+
var AVAILABLE_MIRRORS = ["v6.gh-proxy.org", "gh-proxy.org", "hk.gh-proxy.org", "cdn.gh-proxy.org"];
|
|
2921
|
+
var DEFAULT_MIRROR = "https://v6.gh-proxy.org/";
|
|
2921
2922
|
var UI_URLS = {
|
|
2922
2923
|
zash: "https://board.zash.run.place",
|
|
2923
2924
|
dash: "https://metacubex.github.io/metacubexd",
|
|
2924
2925
|
yacd: "https://yacd.metacubex.one"
|
|
2925
2926
|
};
|
|
2926
|
-
var
|
|
2927
|
+
var LAUNCH_DAEMON_LABEL = process.env.MIHOMO_CLI_DAEMON_LABEL || "com.mihomo-cli.daemon";
|
|
2928
|
+
var CONTROLLER_PORT = 9090;
|
|
2929
|
+
var CONTROLLER_ADDR = `127.0.0.1:${CONTROLLER_PORT}`;
|
|
2930
|
+
var CONTROLLER_BASE_URL = `http://${CONTROLLER_ADDR}`;
|
|
2931
|
+
var TEST_CONTROLLER_ADDR = "127.0.0.1:29090";
|
|
2927
2932
|
var TUN_CONFIG = {
|
|
2928
2933
|
tun: {
|
|
2929
2934
|
enable: true,
|
|
@@ -2937,14 +2942,14 @@ var TUN_CONFIG = {
|
|
|
2937
2942
|
var TEST_CONFIG = {
|
|
2938
2943
|
"mixed-port": 27890,
|
|
2939
2944
|
"allow-lan": false,
|
|
2940
|
-
"external-controller":
|
|
2945
|
+
"external-controller": TEST_CONTROLLER_ADDR,
|
|
2941
2946
|
"log-level": "error",
|
|
2942
2947
|
"geodata-mode": true
|
|
2943
2948
|
};
|
|
2944
2949
|
var BASE_CONFIG = {
|
|
2945
2950
|
"mixed-port": 7890,
|
|
2946
2951
|
"allow-lan": false,
|
|
2947
|
-
"external-controller":
|
|
2952
|
+
"external-controller": CONTROLLER_ADDR,
|
|
2948
2953
|
"unified-delay": true,
|
|
2949
2954
|
"tcp-concurrent": true,
|
|
2950
2955
|
"geo-auto-update": true,
|
|
@@ -2970,10 +2975,11 @@ var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
|
2970
2975
|
var DEFAULT_CLEAN_ROUNDS = 2;
|
|
2971
2976
|
var AUTO_CLEAN_THRESHOLD = 100;
|
|
2972
2977
|
var AUTO_CLEAN_THRESHOLD_GITHUB = 50;
|
|
2978
|
+
var AUTO_CLEAN_COOLDOWN_HOURS = 12;
|
|
2973
2979
|
|
|
2974
2980
|
// src/overwrite.ts
|
|
2975
2981
|
import fs3 from "fs";
|
|
2976
|
-
import
|
|
2982
|
+
import path3 from "path";
|
|
2977
2983
|
|
|
2978
2984
|
// src/paths.ts
|
|
2979
2985
|
import fs from "fs";
|
|
@@ -3003,8 +3009,8 @@ var PATHS = {
|
|
|
3003
3009
|
configStage1Subscription: path.join(DIRS.runtime, "1.subscription.yaml"),
|
|
3004
3010
|
configStage2Overwrite: path.join(DIRS.runtime, "2.overwrite.yaml"),
|
|
3005
3011
|
configStage3System: path.join(DIRS.runtime, "3.system.yaml"),
|
|
3006
|
-
// launchd
|
|
3007
|
-
|
|
3012
|
+
// launchd LaunchDaemon plist 位于系统级 /Library/LaunchDaemons/,root:wheel 拥有,与 homedir / MIHOMO_CLI_DIR 无关
|
|
3013
|
+
launchDaemonPlist: path.join("/Library/LaunchDaemons", `${LAUNCH_DAEMON_LABEL}.plist`)
|
|
3008
3014
|
};
|
|
3009
3015
|
var DIRECTORY_TARGETS = {
|
|
3010
3016
|
root: { path: null, label: "\u6839\u76EE\u5F55" },
|
|
@@ -3040,6 +3046,7 @@ function rmrf(dir) {
|
|
|
3040
3046
|
|
|
3041
3047
|
// src/settings.ts
|
|
3042
3048
|
import fs2 from "fs";
|
|
3049
|
+
import path2 from "path";
|
|
3043
3050
|
var settingsCache = null;
|
|
3044
3051
|
function readSettings() {
|
|
3045
3052
|
if (settingsCache !== null) return settingsCache;
|
|
@@ -3092,6 +3099,7 @@ function maskUrl(url) {
|
|
|
3092
3099
|
}
|
|
3093
3100
|
if (parsed.username) parsed.username = "***";
|
|
3094
3101
|
if (parsed.password) parsed.password = "***";
|
|
3102
|
+
parsed.pathname = parsed.pathname.split("/").map((seg) => seg.length >= 16 ? `${seg.slice(0, 4)}***${seg.slice(-4)}` : seg).join("/");
|
|
3095
3103
|
return parsed.toString();
|
|
3096
3104
|
} catch {
|
|
3097
3105
|
if (url.length > 30) {
|
|
@@ -3107,6 +3115,12 @@ function readSubscriptionCache() {
|
|
|
3107
3115
|
const content = fs2.readFileSync(PATHS.subscriptionsCacheFile, "utf8");
|
|
3108
3116
|
return JSON.parse(content);
|
|
3109
3117
|
} catch {
|
|
3118
|
+
try {
|
|
3119
|
+
fs2.copyFileSync(PATHS.subscriptionsCacheFile, `${PATHS.subscriptionsCacheFile}.bak`);
|
|
3120
|
+
console.warn(`\u8B66\u544A: \u8BA2\u9605\u7F13\u5B58\u683C\u5F0F\u635F\u574F\uFF0C\u5DF2\u5907\u4EFD\u5230 ${PATHS.subscriptionsCacheFile}.bak`);
|
|
3121
|
+
} catch {
|
|
3122
|
+
console.warn("\u8B66\u544A: \u8BA2\u9605\u7F13\u5B58\u683C\u5F0F\u635F\u574F\uFF0C\u5DF2\u5FFD\u7565");
|
|
3123
|
+
}
|
|
3110
3124
|
return {};
|
|
3111
3125
|
}
|
|
3112
3126
|
}
|
|
@@ -3143,12 +3157,10 @@ function addSubscription(url, name = "default") {
|
|
|
3143
3157
|
validateSubscriptionName(name);
|
|
3144
3158
|
const settings = readSettings();
|
|
3145
3159
|
const subs = [...settings.subscriptions || []];
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
subs[existingIndex] = { name, url };
|
|
3149
|
-
} else {
|
|
3150
|
-
subs.push({ name, url });
|
|
3160
|
+
if (subs.some((s) => s.name === name)) {
|
|
3161
|
+
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`);
|
|
3151
3162
|
}
|
|
3163
|
+
subs.push({ name, url });
|
|
3152
3164
|
const updates = { subscriptions: subs };
|
|
3153
3165
|
if (!settings.active_subscription && subs.length === 1) {
|
|
3154
3166
|
updates.active_subscription = name;
|
|
@@ -3173,7 +3185,10 @@ function removeSubscription(name) {
|
|
|
3173
3185
|
delete cache[name];
|
|
3174
3186
|
writeSubscriptionCache(cache);
|
|
3175
3187
|
}
|
|
3176
|
-
|
|
3188
|
+
try {
|
|
3189
|
+
fs2.rmSync(getSubscriptionRawConfigPath(name), { force: true });
|
|
3190
|
+
} catch {
|
|
3191
|
+
}
|
|
3177
3192
|
return switchedTo;
|
|
3178
3193
|
}
|
|
3179
3194
|
function setDefaultSubscription(name) {
|
|
@@ -3186,7 +3201,10 @@ function setDefaultSubscription(name) {
|
|
|
3186
3201
|
return true;
|
|
3187
3202
|
}
|
|
3188
3203
|
function getSubscriptionRawConfigPath(subName) {
|
|
3189
|
-
|
|
3204
|
+
if (!SAFE_NAME_RE.test(subName)) {
|
|
3205
|
+
throw new Error(`\u8BA2\u9605\u540D\u79F0\u65E0\u6548: "${subName}"`);
|
|
3206
|
+
}
|
|
3207
|
+
return path2.join(DIRS.subscriptions, `${subName}.yaml`);
|
|
3190
3208
|
}
|
|
3191
3209
|
function saveSubscriptionRawConfig(subName, content) {
|
|
3192
3210
|
ensureDirs();
|
|
@@ -3205,6 +3223,7 @@ function parseOverrideKey(key) {
|
|
|
3205
3223
|
let forceOverwrite = false;
|
|
3206
3224
|
let arrayPrepend = false;
|
|
3207
3225
|
let arrayAppend = false;
|
|
3226
|
+
let arrayMergeByName = false;
|
|
3208
3227
|
const lastChar = key[key.length - 1];
|
|
3209
3228
|
const openAngleCount = (key.match(/</g) || []).length;
|
|
3210
3229
|
const closeAngleCount = (key.match(/>/g) || []).length;
|
|
@@ -3225,6 +3244,9 @@ function parseOverrideKey(key) {
|
|
|
3225
3244
|
} else {
|
|
3226
3245
|
actualKey = unwrapped;
|
|
3227
3246
|
}
|
|
3247
|
+
} else if (actualKey.startsWith("~")) {
|
|
3248
|
+
arrayMergeByName = true;
|
|
3249
|
+
actualKey = actualKey.slice(1);
|
|
3228
3250
|
} else {
|
|
3229
3251
|
if (actualKey.startsWith("+")) {
|
|
3230
3252
|
arrayPrepend = true;
|
|
@@ -3235,7 +3257,7 @@ function parseOverrideKey(key) {
|
|
|
3235
3257
|
actualKey = actualKey.slice(0, -1);
|
|
3236
3258
|
}
|
|
3237
3259
|
}
|
|
3238
|
-
return { key: actualKey, forceOverwrite, arrayPrepend, arrayAppend };
|
|
3260
|
+
return { key: actualKey, forceOverwrite, arrayPrepend, arrayAppend, arrayMergeByName };
|
|
3239
3261
|
}
|
|
3240
3262
|
function deepMergeWithOverrides(target, override) {
|
|
3241
3263
|
let t = target;
|
|
@@ -3253,8 +3275,24 @@ function deepMergeWithOverrides(target, override) {
|
|
|
3253
3275
|
}
|
|
3254
3276
|
const result = { ...t };
|
|
3255
3277
|
for (const [rawKey, value] of Object.entries(override)) {
|
|
3256
|
-
const { key, forceOverwrite, arrayPrepend, arrayAppend } = parseOverrideKey(rawKey);
|
|
3278
|
+
const { key, forceOverwrite, arrayPrepend, arrayAppend, arrayMergeByName } = parseOverrideKey(rawKey);
|
|
3257
3279
|
const existingValue = result[key];
|
|
3280
|
+
if (arrayMergeByName) {
|
|
3281
|
+
const existingArr = Array.isArray(existingValue) ? existingValue : [];
|
|
3282
|
+
const overrideArr = Array.isArray(value) ? value : [value];
|
|
3283
|
+
const merged = [...existingArr];
|
|
3284
|
+
for (const item of overrideArr) {
|
|
3285
|
+
const name = item && typeof item === "object" && !Array.isArray(item) ? item.name : void 0;
|
|
3286
|
+
const idx = name != null ? merged.findIndex((e) => e && typeof e === "object" && e.name === name) : -1;
|
|
3287
|
+
if (idx >= 0) {
|
|
3288
|
+
merged[idx] = deepMergeWithOverrides(merged[idx], item);
|
|
3289
|
+
} else {
|
|
3290
|
+
merged.push(item);
|
|
3291
|
+
}
|
|
3292
|
+
}
|
|
3293
|
+
result[key] = merged;
|
|
3294
|
+
continue;
|
|
3295
|
+
}
|
|
3258
3296
|
if (arrayPrepend || arrayAppend) {
|
|
3259
3297
|
const existingArr = Array.isArray(existingValue) ? existingValue : [];
|
|
3260
3298
|
const overrideArr = Array.isArray(value) ? value : [value];
|
|
@@ -3284,22 +3322,91 @@ function isOverwriteEnabled() {
|
|
|
3284
3322
|
function setOverwriteEnabled(enabled) {
|
|
3285
3323
|
writeSettings({ overwrite_enabled: enabled });
|
|
3286
3324
|
}
|
|
3325
|
+
function isOverwriteFilename(filename) {
|
|
3326
|
+
return filename === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(filename);
|
|
3327
|
+
}
|
|
3328
|
+
var MATCH_KEYS = /* @__PURE__ */ new Set(["subscription", "url-domain"]);
|
|
3329
|
+
function normalizeMatch(raw, fileName) {
|
|
3330
|
+
if (raw == null) return void 0;
|
|
3331
|
+
if (typeof raw !== "object" || Array.isArray(raw)) {
|
|
3332
|
+
console.warn(`\u8B66\u544A: \u8986\u5199\u6587\u4EF6 "${fileName}" \u7684 match \u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5DF2\u5FFD\u7565\u4F5C\u7528\u57DF\u9650\u5B9A`);
|
|
3333
|
+
return void 0;
|
|
3334
|
+
}
|
|
3335
|
+
const result = {};
|
|
3336
|
+
let hasValid = false;
|
|
3337
|
+
for (const [key, value] of Object.entries(raw)) {
|
|
3338
|
+
if (!MATCH_KEYS.has(key)) {
|
|
3339
|
+
console.warn(`\u8B66\u544A: \u8986\u5199\u6587\u4EF6 "${fileName}" \u7684 match \u542B\u672A\u77E5\u952E "${key}"\uFF0C\u5DF2\u5FFD\u7565`);
|
|
3340
|
+
continue;
|
|
3341
|
+
}
|
|
3342
|
+
const arr = (Array.isArray(value) ? value : [value]).filter((v) => typeof v === "string" && v.length > 0);
|
|
3343
|
+
if (arr.length === 0) continue;
|
|
3344
|
+
result[key] = arr;
|
|
3345
|
+
hasValid = true;
|
|
3346
|
+
}
|
|
3347
|
+
return hasValid ? result : void 0;
|
|
3348
|
+
}
|
|
3349
|
+
function summarizeMatch(match) {
|
|
3350
|
+
if (!match) return void 0;
|
|
3351
|
+
const parts = [];
|
|
3352
|
+
for (const [key, value] of Object.entries(match)) {
|
|
3353
|
+
const vals = Array.isArray(value) ? value : [value];
|
|
3354
|
+
parts.push(`${key}=${vals.join("/")}`);
|
|
3355
|
+
}
|
|
3356
|
+
return parts.length > 0 ? parts.join(", ") : void 0;
|
|
3357
|
+
}
|
|
3358
|
+
function splitUrlsLocal(url) {
|
|
3359
|
+
return url.split(",").map((u) => u.trim()).filter(Boolean);
|
|
3360
|
+
}
|
|
3361
|
+
function hostMatchesDomain(host, domain) {
|
|
3362
|
+
const h = host.toLowerCase();
|
|
3363
|
+
const d = domain.toLowerCase();
|
|
3364
|
+
return h === d || h.endsWith(`.${d}`);
|
|
3365
|
+
}
|
|
3366
|
+
function matchesScope(match, scope) {
|
|
3367
|
+
if (!match) return true;
|
|
3368
|
+
if (match.subscription) {
|
|
3369
|
+
const names = Array.isArray(match.subscription) ? match.subscription : [match.subscription];
|
|
3370
|
+
if (!scope?.subName || !names.includes(scope.subName)) return false;
|
|
3371
|
+
}
|
|
3372
|
+
if (match["url-domain"]) {
|
|
3373
|
+
const domains = Array.isArray(match["url-domain"]) ? match["url-domain"] : [match["url-domain"]];
|
|
3374
|
+
if (!scope?.subUrl) return false;
|
|
3375
|
+
const hosts = [];
|
|
3376
|
+
for (const u of splitUrlsLocal(scope.subUrl)) {
|
|
3377
|
+
try {
|
|
3378
|
+
hosts.push(new URL(u).hostname);
|
|
3379
|
+
} catch {
|
|
3380
|
+
}
|
|
3381
|
+
}
|
|
3382
|
+
if (hosts.length === 0) return false;
|
|
3383
|
+
const ok = domains.some((d) => hosts.some((h) => hostMatchesDomain(h, d)));
|
|
3384
|
+
if (!ok) return false;
|
|
3385
|
+
}
|
|
3386
|
+
return true;
|
|
3387
|
+
}
|
|
3388
|
+
function filterOverwriteFilesByScope(files, scope) {
|
|
3389
|
+
return files.filter((f) => matchesScope(f.match, scope));
|
|
3390
|
+
}
|
|
3287
3391
|
function loadOverwriteFile() {
|
|
3288
3392
|
const dir = USER_DATA_DIR;
|
|
3289
3393
|
if (!fs3.existsSync(dir)) return [];
|
|
3290
|
-
const files = fs3.readdirSync(dir).filter(
|
|
3394
|
+
const files = fs3.readdirSync(dir).filter(isOverwriteFilename).sort((a, b) => {
|
|
3291
3395
|
if (a === "overwrite.yaml") return -1;
|
|
3292
3396
|
if (b === "overwrite.yaml") return 1;
|
|
3293
3397
|
return a.localeCompare(b);
|
|
3294
3398
|
});
|
|
3295
3399
|
const results = [];
|
|
3296
3400
|
for (const file of files) {
|
|
3297
|
-
const filePath =
|
|
3401
|
+
const filePath = path3.join(dir, file);
|
|
3298
3402
|
try {
|
|
3299
3403
|
const content = fs3.readFileSync(filePath, "utf8");
|
|
3300
3404
|
const parsed = load(content);
|
|
3301
|
-
if (parsed && typeof parsed === "object") {
|
|
3302
|
-
|
|
3405
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
3406
|
+
const { match, ...config } = parsed;
|
|
3407
|
+
results.push({ name: file, path: filePath, config, match: normalizeMatch(match, file) });
|
|
3408
|
+
} else if (parsed !== null) {
|
|
3409
|
+
console.warn(`\u8B66\u544A: \u8986\u5199\u6587\u4EF6 "${file}" \u9876\u5C42\u5FC5\u987B\u662F\u5BF9\u8C61\uFF0C\u5DF2\u8DF3\u8FC7`);
|
|
3303
3410
|
}
|
|
3304
3411
|
} catch (e) {
|
|
3305
3412
|
console.warn(`\u8B66\u544A: \u8986\u5199\u6587\u4EF6 "${file}" \u89E3\u6790\u5931\u8D25: ${e.message}`);
|
|
@@ -3308,9 +3415,9 @@ function loadOverwriteFile() {
|
|
|
3308
3415
|
return results;
|
|
3309
3416
|
}
|
|
3310
3417
|
function applyOverwrite(baseConfig, preloadedFiles) {
|
|
3311
|
-
if (!isOverwriteEnabled()) return baseConfig;
|
|
3418
|
+
if (!isOverwriteEnabled()) return { ...baseConfig };
|
|
3312
3419
|
const overwriteFiles = preloadedFiles || loadOverwriteFile();
|
|
3313
|
-
if (overwriteFiles.length === 0) return baseConfig;
|
|
3420
|
+
if (overwriteFiles.length === 0) return { ...baseConfig };
|
|
3314
3421
|
let result = { ...baseConfig };
|
|
3315
3422
|
for (const file of overwriteFiles) {
|
|
3316
3423
|
result = deepMergeWithOverrides(result, file.config);
|
|
@@ -3326,11 +3433,271 @@ function listOverwriteFile() {
|
|
|
3326
3433
|
files: files.map((f) => ({
|
|
3327
3434
|
name: f.name,
|
|
3328
3435
|
path: f.path,
|
|
3329
|
-
keys: Object.keys(f.config || {})
|
|
3436
|
+
keys: Object.keys(f.config || {}),
|
|
3437
|
+
scope: summarizeMatch(f.match)
|
|
3330
3438
|
}))
|
|
3331
3439
|
};
|
|
3332
3440
|
}
|
|
3333
3441
|
|
|
3442
|
+
// src/utils.ts
|
|
3443
|
+
import { spawnSync } from "child_process";
|
|
3444
|
+
import { createRequire } from "module";
|
|
3445
|
+
var require2 = createRequire(import.meta.url);
|
|
3446
|
+
var pkg = require2("../package.json");
|
|
3447
|
+
var VERSION = pkg.version;
|
|
3448
|
+
var MAX_RESPONSE_BYTES = 50 * 1024 * 1024;
|
|
3449
|
+
var sleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
3450
|
+
var NO_COLOR = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
3451
|
+
function colorize(code, str) {
|
|
3452
|
+
if (NO_COLOR) return String(str);
|
|
3453
|
+
return `${code + String(str)}\x1B[0m`;
|
|
3454
|
+
}
|
|
3455
|
+
var colors = {
|
|
3456
|
+
bold: (s) => colorize("\x1B[1m", s),
|
|
3457
|
+
red: (s) => colorize("\x1B[31m", s),
|
|
3458
|
+
green: (s) => colorize("\x1B[32m", s),
|
|
3459
|
+
yellow: (s) => colorize("\x1B[33m", s),
|
|
3460
|
+
cyan: (s) => colorize("\x1B[36m", s),
|
|
3461
|
+
gray: (s) => colorize("\x1B[90m", s)
|
|
3462
|
+
};
|
|
3463
|
+
function sleepSync(ms) {
|
|
3464
|
+
Atomics.wait(sleepBuf, 0, 0, ms);
|
|
3465
|
+
}
|
|
3466
|
+
function sleep(ms) {
|
|
3467
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3468
|
+
}
|
|
3469
|
+
function escapeRegExp(s) {
|
|
3470
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3471
|
+
}
|
|
3472
|
+
function shellQuote(s) {
|
|
3473
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
3474
|
+
}
|
|
3475
|
+
var TimeoutError = class extends Error {
|
|
3476
|
+
constructor() {
|
|
3477
|
+
super("timeout");
|
|
3478
|
+
this.name = "TimeoutError";
|
|
3479
|
+
}
|
|
3480
|
+
};
|
|
3481
|
+
function withTimeout(promise, ms) {
|
|
3482
|
+
return new Promise((resolve, reject) => {
|
|
3483
|
+
const timer = setTimeout(() => reject(new TimeoutError()), ms);
|
|
3484
|
+
promise.then(
|
|
3485
|
+
(v) => {
|
|
3486
|
+
clearTimeout(timer);
|
|
3487
|
+
resolve(v);
|
|
3488
|
+
},
|
|
3489
|
+
(e) => {
|
|
3490
|
+
clearTimeout(timer);
|
|
3491
|
+
reject(e);
|
|
3492
|
+
}
|
|
3493
|
+
);
|
|
3494
|
+
});
|
|
3495
|
+
}
|
|
3496
|
+
function formatBytes(bytes) {
|
|
3497
|
+
if (bytes === void 0 || bytes === null) return "\u672A\u77E5";
|
|
3498
|
+
const num = Number(bytes);
|
|
3499
|
+
if (!Number.isFinite(num) || num < 0) return "\u672A\u77E5";
|
|
3500
|
+
if (num === 0) return "0 B";
|
|
3501
|
+
const k = 1024;
|
|
3502
|
+
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
3503
|
+
const i = Math.min(Math.floor(Math.log(num) / Math.log(k)), sizes.length - 1);
|
|
3504
|
+
return `${parseFloat((num / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
3505
|
+
}
|
|
3506
|
+
function formatTimestamp(ts) {
|
|
3507
|
+
if (ts === void 0 || ts === null) return "\u672A\u77E5";
|
|
3508
|
+
if (ts === 0) return "\u6C38\u4E45";
|
|
3509
|
+
try {
|
|
3510
|
+
return new Date(ts * 1e3).toLocaleString("zh-CN");
|
|
3511
|
+
} catch {
|
|
3512
|
+
return "\u672A\u77E5";
|
|
3513
|
+
}
|
|
3514
|
+
}
|
|
3515
|
+
function formatLocalTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
3516
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
3517
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}_${p(d.getHours())}-${p(d.getMinutes())}-${p(d.getSeconds())}`;
|
|
3518
|
+
}
|
|
3519
|
+
function formatDate(dateOrIso) {
|
|
3520
|
+
if (dateOrIso === void 0 || dateOrIso === null) return "\u672A\u77E5";
|
|
3521
|
+
try {
|
|
3522
|
+
const d = dateOrIso instanceof Date ? dateOrIso : new Date(dateOrIso);
|
|
3523
|
+
if (Number.isNaN(d.getTime())) return "\u672A\u77E5";
|
|
3524
|
+
return d.toLocaleString("zh-CN");
|
|
3525
|
+
} catch {
|
|
3526
|
+
return "\u672A\u77E5";
|
|
3527
|
+
}
|
|
3528
|
+
}
|
|
3529
|
+
function hasFlag(args, short, long) {
|
|
3530
|
+
return !!args && (args.includes(short) || long !== void 0 && args.includes(long));
|
|
3531
|
+
}
|
|
3532
|
+
function parseIntArg(args, short, long, defaultValue) {
|
|
3533
|
+
if (!args) return defaultValue;
|
|
3534
|
+
for (let i = 0; i < args.length; i++) {
|
|
3535
|
+
if (args[i] === short || args[i] === long) {
|
|
3536
|
+
if (i + 1 < args.length) {
|
|
3537
|
+
const val = parseInt(args[i + 1], 10);
|
|
3538
|
+
return Number.isNaN(val) ? defaultValue : val;
|
|
3539
|
+
}
|
|
3540
|
+
} else if (args[i].startsWith(`${long}=`)) {
|
|
3541
|
+
const val = parseInt(args[i].slice(long.length + 1), 10);
|
|
3542
|
+
if (!Number.isNaN(val)) return val;
|
|
3543
|
+
}
|
|
3544
|
+
}
|
|
3545
|
+
return defaultValue;
|
|
3546
|
+
}
|
|
3547
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["-t", "--timeout", "-j", "--concurrency", "-r", "--rounds", "-n", "--lines", "-u", "--update-timeout"]);
|
|
3548
|
+
function extractStartOptions(args) {
|
|
3549
|
+
if (!args) return [];
|
|
3550
|
+
const BOOL_FLAGS = /* @__PURE__ */ new Set(["-s", "--no-update", "--no-clean"]);
|
|
3551
|
+
const out = [];
|
|
3552
|
+
for (let i = 0; i < args.length; i++) {
|
|
3553
|
+
const a = args[i];
|
|
3554
|
+
if (VALUE_FLAGS.has(a)) {
|
|
3555
|
+
out.push(a);
|
|
3556
|
+
if (i + 1 < args.length) out.push(args[++i]);
|
|
3557
|
+
} else if (BOOL_FLAGS.has(a) || /^--(timeout|concurrency|rounds|update-timeout)=/.test(a)) {
|
|
3558
|
+
out.push(a);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
return out;
|
|
3562
|
+
}
|
|
3563
|
+
function getNonFlagArg(args, startIdx, valueFlags = VALUE_FLAGS) {
|
|
3564
|
+
if (!args) return null;
|
|
3565
|
+
for (let i = startIdx; i < args.length; i++) {
|
|
3566
|
+
const a = args[i];
|
|
3567
|
+
if (a.startsWith("-")) {
|
|
3568
|
+
if (valueFlags.has(a)) i++;
|
|
3569
|
+
continue;
|
|
3570
|
+
}
|
|
3571
|
+
return a;
|
|
3572
|
+
}
|
|
3573
|
+
return null;
|
|
3574
|
+
}
|
|
3575
|
+
function isProcessRunning(pid) {
|
|
3576
|
+
if (!pid) return false;
|
|
3577
|
+
try {
|
|
3578
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "pid="], { encoding: "utf8", timeout: 5e3 });
|
|
3579
|
+
return (result.stdout || "").trim().length > 0;
|
|
3580
|
+
} catch {
|
|
3581
|
+
return false;
|
|
3582
|
+
}
|
|
3583
|
+
}
|
|
3584
|
+
function isProcessRoot(pid) {
|
|
3585
|
+
if (!pid) return false;
|
|
3586
|
+
try {
|
|
3587
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "uid="], { encoding: "utf8", timeout: 5e3 });
|
|
3588
|
+
return (result.stdout || "").trim() === "0";
|
|
3589
|
+
} catch {
|
|
3590
|
+
return false;
|
|
3591
|
+
}
|
|
3592
|
+
}
|
|
3593
|
+
function createHttpClient(options = {}) {
|
|
3594
|
+
const { timeout = 6e4 } = options;
|
|
3595
|
+
return {
|
|
3596
|
+
async get(url, config) {
|
|
3597
|
+
const controller = new AbortController();
|
|
3598
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
3599
|
+
const signal = config?.signal ? AbortSignal.any([controller.signal, config.signal]) : controller.signal;
|
|
3600
|
+
try {
|
|
3601
|
+
const response = await fetch(url, {
|
|
3602
|
+
signal,
|
|
3603
|
+
headers: { "User-Agent": `mihomo-cli/${VERSION}` }
|
|
3604
|
+
});
|
|
3605
|
+
if (!response.ok) {
|
|
3606
|
+
const error = new Error(`HTTP ${response.status}`);
|
|
3607
|
+
error.response = { status: response.status };
|
|
3608
|
+
try {
|
|
3609
|
+
error.response.data = await response.json();
|
|
3610
|
+
} catch {
|
|
3611
|
+
}
|
|
3612
|
+
throw error;
|
|
3613
|
+
}
|
|
3614
|
+
const declaredLen = Number(response.headers.get("content-length"));
|
|
3615
|
+
if (Number.isFinite(declaredLen) && declaredLen > MAX_RESPONSE_BYTES) {
|
|
3616
|
+
throw new Error(`\u54CD\u5E94\u4F53\u8FC7\u5927\uFF08${formatBytes(declaredLen)}\uFF0C\u4E0A\u9650 ${formatBytes(MAX_RESPONSE_BYTES)}\uFF09`);
|
|
3617
|
+
}
|
|
3618
|
+
const text = await readBodyWithLimit(response, controller);
|
|
3619
|
+
const data = config?.responseType === "json" ? JSON.parse(text) : text;
|
|
3620
|
+
return { data, headers: response.headers, status: response.status };
|
|
3621
|
+
} finally {
|
|
3622
|
+
clearTimeout(timer);
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
};
|
|
3626
|
+
}
|
|
3627
|
+
async function readBodyWithLimit(response, controller) {
|
|
3628
|
+
if (!response.body) return response.text();
|
|
3629
|
+
const reader = response.body.getReader();
|
|
3630
|
+
const chunks = [];
|
|
3631
|
+
let total = 0;
|
|
3632
|
+
try {
|
|
3633
|
+
while (true) {
|
|
3634
|
+
const { done, value } = await reader.read();
|
|
3635
|
+
if (done) break;
|
|
3636
|
+
if (value) {
|
|
3637
|
+
total += value.byteLength;
|
|
3638
|
+
if (total > MAX_RESPONSE_BYTES) {
|
|
3639
|
+
controller.abort();
|
|
3640
|
+
throw new Error(`\u54CD\u5E94\u4F53\u8D85\u8FC7\u5927\u5C0F\u4E0A\u9650\uFF08${formatBytes(MAX_RESPONSE_BYTES)}\uFF09`);
|
|
3641
|
+
}
|
|
3642
|
+
chunks.push(value);
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
} finally {
|
|
3646
|
+
reader.releaseLock();
|
|
3647
|
+
}
|
|
3648
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
3649
|
+
}
|
|
3650
|
+
function normalizeMirrorUrl(val) {
|
|
3651
|
+
if (!val) return null;
|
|
3652
|
+
if (val === "direct" || val === "no" || val === "none") return null;
|
|
3653
|
+
let url = val;
|
|
3654
|
+
if (!url.startsWith("http")) {
|
|
3655
|
+
url = `https://${url}`;
|
|
3656
|
+
}
|
|
3657
|
+
if (!url.endsWith("/")) {
|
|
3658
|
+
url += "/";
|
|
3659
|
+
}
|
|
3660
|
+
return url;
|
|
3661
|
+
}
|
|
3662
|
+
function parseMirrorArg(args) {
|
|
3663
|
+
if (!args || args.length < 2) {
|
|
3664
|
+
return { mirror: null, isOverride: false, type: "download" };
|
|
3665
|
+
}
|
|
3666
|
+
if (args.includes("--no-mirror") || args.includes("--direct")) {
|
|
3667
|
+
return { mirror: null, isOverride: true, type: "download" };
|
|
3668
|
+
}
|
|
3669
|
+
const mirrorAllEq = args.find((a) => a.startsWith("--mirror-all="));
|
|
3670
|
+
const mirrorAllIdx = args.indexOf("--mirror-all");
|
|
3671
|
+
if (mirrorAllIdx >= 0 || mirrorAllEq) {
|
|
3672
|
+
const inline = mirrorAllEq?.slice("--mirror-all=".length);
|
|
3673
|
+
const nextArg = inline ?? args[mirrorAllIdx + 1];
|
|
3674
|
+
if (!nextArg || nextArg.startsWith("-")) {
|
|
3675
|
+
return { mirror: DEFAULT_MIRROR, isOverride: true, type: "all" };
|
|
3676
|
+
}
|
|
3677
|
+
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "all" };
|
|
3678
|
+
}
|
|
3679
|
+
const mirrorEq = args.find((a) => a.startsWith("--mirror="));
|
|
3680
|
+
const mirrorIdx = args.indexOf("--mirror");
|
|
3681
|
+
if (mirrorIdx >= 0 || mirrorEq) {
|
|
3682
|
+
const inline = mirrorEq?.slice("--mirror=".length);
|
|
3683
|
+
const nextArg = inline ?? args[mirrorIdx + 1];
|
|
3684
|
+
if (!nextArg || nextArg.startsWith("-")) {
|
|
3685
|
+
return { mirror: DEFAULT_MIRROR, isOverride: true, type: "download" };
|
|
3686
|
+
}
|
|
3687
|
+
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "download" };
|
|
3688
|
+
}
|
|
3689
|
+
return { mirror: null, isOverride: false, type: "download" };
|
|
3690
|
+
}
|
|
3691
|
+
function isProxyValid(proxy) {
|
|
3692
|
+
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3693
|
+
if (!proxy.type) return false;
|
|
3694
|
+
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3695
|
+
const pw = String(proxy.password || "");
|
|
3696
|
+
if (!/^[A-Za-z0-9+/\-_]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3697
|
+
}
|
|
3698
|
+
return true;
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3334
3701
|
// src/config.ts
|
|
3335
3702
|
function parseYamlOrJson(content, errorMsg) {
|
|
3336
3703
|
if (!content?.trim()) {
|
|
@@ -3347,6 +3714,9 @@ function parseYamlOrJson(content, errorMsg) {
|
|
|
3347
3714
|
throw new Error(`${errorMsg || "\u5185\u5BB9"}\u683C\u5F0F\u9519\u8BEF\uFF0C\u65E0\u6CD5\u89E3\u6790\u4E3A YAML \u6216 JSON`);
|
|
3348
3715
|
}
|
|
3349
3716
|
}
|
|
3717
|
+
function dumpYaml(obj) {
|
|
3718
|
+
return dump(obj, { indent: 2, lineWidth: -1, schema: CORE_SCHEMA });
|
|
3719
|
+
}
|
|
3350
3720
|
function collectOverwriteProxyNames(overwriteFiles) {
|
|
3351
3721
|
const names = [];
|
|
3352
3722
|
for (const file of overwriteFiles) {
|
|
@@ -3354,7 +3724,8 @@ function collectOverwriteProxyNames(overwriteFiles) {
|
|
|
3354
3724
|
if ((key === "+proxies" || key === "proxies+") && Array.isArray(value)) {
|
|
3355
3725
|
for (const proxy of value) {
|
|
3356
3726
|
if (proxy && typeof proxy === "object" && "name" in proxy) {
|
|
3357
|
-
|
|
3727
|
+
const name = proxy.name;
|
|
3728
|
+
if (typeof name === "string" && name.length > 0) names.push(name);
|
|
3358
3729
|
}
|
|
3359
3730
|
}
|
|
3360
3731
|
}
|
|
@@ -3367,7 +3738,7 @@ function excludeOverwriteProxiesFromIncludeAll(config, overwriteFiles) {
|
|
|
3367
3738
|
if (injectedNames.length === 0) return;
|
|
3368
3739
|
const groups = config["proxy-groups"];
|
|
3369
3740
|
if (!groups) return;
|
|
3370
|
-
const excludePattern = injectedNames.map((n) => n
|
|
3741
|
+
const excludePattern = injectedNames.map((n) => escapeRegExp(n)).join("|");
|
|
3371
3742
|
for (const group of groups) {
|
|
3372
3743
|
if (!group["include-all"] && !group["include-all-proxies"]) continue;
|
|
3373
3744
|
const existing = group["exclude-filter"];
|
|
@@ -3392,6 +3763,16 @@ function deduplicateByName(items) {
|
|
|
3392
3763
|
});
|
|
3393
3764
|
return { result, names, duplicates };
|
|
3394
3765
|
}
|
|
3766
|
+
var NON_TARGET_RULE_TYPES = /* @__PURE__ */ new Set(["SUB-RULE"]);
|
|
3767
|
+
function getRuleTarget(rule) {
|
|
3768
|
+
const parts = rule.split(",");
|
|
3769
|
+
if (parts.length < 2) return "";
|
|
3770
|
+
const last = parts[parts.length - 1].trim();
|
|
3771
|
+
if (last.toLowerCase() === "no-resolve" && parts.length >= 3) {
|
|
3772
|
+
return parts[parts.length - 2].trim();
|
|
3773
|
+
}
|
|
3774
|
+
return last;
|
|
3775
|
+
}
|
|
3395
3776
|
function validateConfig(config) {
|
|
3396
3777
|
const warnings = [];
|
|
3397
3778
|
const proxies = config.proxies || [];
|
|
@@ -3436,9 +3817,9 @@ function validateConfig(config) {
|
|
|
3436
3817
|
if (rules.length > 0) {
|
|
3437
3818
|
const removedRules = [];
|
|
3438
3819
|
config.rules = rules.filter((rule) => {
|
|
3439
|
-
const
|
|
3440
|
-
if (
|
|
3441
|
-
const target =
|
|
3820
|
+
const ruleType = rule.split(",")[0]?.trim().toUpperCase();
|
|
3821
|
+
if (NON_TARGET_RULE_TYPES.has(ruleType)) return true;
|
|
3822
|
+
const target = getRuleTarget(rule);
|
|
3442
3823
|
if (!target || validNames.has(target)) return true;
|
|
3443
3824
|
removedRules.push(rule);
|
|
3444
3825
|
return false;
|
|
@@ -3449,13 +3830,14 @@ function validateConfig(config) {
|
|
|
3449
3830
|
}
|
|
3450
3831
|
return warnings;
|
|
3451
3832
|
}
|
|
3452
|
-
function buildConfig(subRawContent, mode) {
|
|
3833
|
+
function buildConfig(subRawContent, mode, scope) {
|
|
3453
3834
|
const subscriptionConfig = parseYamlOrJson(subRawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
3454
3835
|
if (!subscriptionConfig) {
|
|
3455
3836
|
throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
3456
3837
|
}
|
|
3457
3838
|
const overwriteEnabled = isOverwriteEnabled();
|
|
3458
|
-
const
|
|
3839
|
+
const allFiles = overwriteEnabled ? loadOverwriteFile() : [];
|
|
3840
|
+
const overwriteFiles = filterOverwriteFilesByScope(allFiles, scope);
|
|
3459
3841
|
const withOverwrites = applyOverwrite(subscriptionConfig, overwriteFiles);
|
|
3460
3842
|
if (overwriteFiles.length > 0) {
|
|
3461
3843
|
excludeOverwriteProxiesFromIncludeAll(withOverwrites, overwriteFiles);
|
|
@@ -3466,7 +3848,6 @@ function buildConfig(subRawContent, mode) {
|
|
|
3466
3848
|
systemConfig[key] = value;
|
|
3467
3849
|
}
|
|
3468
3850
|
}
|
|
3469
|
-
systemConfig["allow-lan"] = false;
|
|
3470
3851
|
systemConfig["external-controller"] = BASE_CONFIG["external-controller"];
|
|
3471
3852
|
systemConfig["mixed-port"] = BASE_CONFIG["mixed-port"];
|
|
3472
3853
|
delete withOverwrites["mixed-port"];
|
|
@@ -3475,6 +3856,11 @@ function buildConfig(subRawContent, mode) {
|
|
|
3475
3856
|
delete withOverwrites["external-ui"];
|
|
3476
3857
|
delete withOverwrites["external-ui-name"];
|
|
3477
3858
|
delete withOverwrites["external-ui-url"];
|
|
3859
|
+
delete withOverwrites.secret;
|
|
3860
|
+
const controllerSecret = readSettings().controller_secret;
|
|
3861
|
+
if (controllerSecret) {
|
|
3862
|
+
systemConfig.secret = controllerSecret;
|
|
3863
|
+
}
|
|
3478
3864
|
if (mode === "tun") {
|
|
3479
3865
|
systemConfig.tun = TUN_CONFIG.tun;
|
|
3480
3866
|
const subDns = withOverwrites.dns || {};
|
|
@@ -3485,6 +3871,8 @@ function buildConfig(subRawContent, mode) {
|
|
|
3485
3871
|
if (Object.keys(dns).length > 0) {
|
|
3486
3872
|
systemConfig.dns = dns;
|
|
3487
3873
|
}
|
|
3874
|
+
} else {
|
|
3875
|
+
delete withOverwrites.tun;
|
|
3488
3876
|
}
|
|
3489
3877
|
const merged = { ...withOverwrites, ...systemConfig };
|
|
3490
3878
|
if (systemConfig.dns) {
|
|
@@ -3507,20 +3895,19 @@ function buildConfig(subRawContent, mode) {
|
|
|
3507
3895
|
}
|
|
3508
3896
|
function writeMihomoConfig(configObj) {
|
|
3509
3897
|
ensureDirs();
|
|
3510
|
-
const content =
|
|
3898
|
+
const content = dumpYaml(configObj);
|
|
3511
3899
|
atomicWriteFileSync(PATHS.configFile, content, { mode: 384 });
|
|
3512
3900
|
}
|
|
3513
3901
|
function writeDebugConfig(buildResult) {
|
|
3514
3902
|
ensureDirs();
|
|
3515
|
-
|
|
3516
|
-
fs4.writeFileSync(PATHS.configStage1Subscription, dump(buildResult.subscriptionConfig, dumpOpts), { mode: 384 });
|
|
3903
|
+
fs4.writeFileSync(PATHS.configStage1Subscription, dumpYaml(buildResult.subscriptionConfig), { mode: 384 });
|
|
3517
3904
|
const overwriteMerged = {};
|
|
3518
3905
|
for (const f of buildResult.overwriteFiles) {
|
|
3519
3906
|
Object.assign(overwriteMerged, f.config);
|
|
3520
3907
|
}
|
|
3521
|
-
const overwriteContent = buildResult.overwriteFiles.length > 0 ?
|
|
3908
|
+
const overwriteContent = buildResult.overwriteFiles.length > 0 ? dumpYaml(overwriteMerged) : "# overwrite \u5DF2\u7981\u7528\u6216\u65E0\u8986\u5199\u6587\u4EF6\n";
|
|
3522
3909
|
fs4.writeFileSync(PATHS.configStage2Overwrite, overwriteContent, { mode: 384 });
|
|
3523
|
-
fs4.writeFileSync(PATHS.configStage3System,
|
|
3910
|
+
fs4.writeFileSync(PATHS.configStage3System, dumpYaml(buildResult.systemConfig), { mode: 384 });
|
|
3524
3911
|
}
|
|
3525
3912
|
function hasConfig() {
|
|
3526
3913
|
return fs4.existsSync(PATHS.configFile);
|
|
@@ -3560,7 +3947,7 @@ function getKernelVersion() {
|
|
|
3560
3947
|
}
|
|
3561
3948
|
if (kernelVersionCached) return kernelVersionCache;
|
|
3562
3949
|
try {
|
|
3563
|
-
const result =
|
|
3950
|
+
const result = spawnSync2(PATHS.mihomoBinary, ["-v"], { encoding: "utf8", timeout: 5e3 });
|
|
3564
3951
|
const output = `${result.stdout || ""}${result.stderr || ""}`.trim();
|
|
3565
3952
|
if (output) {
|
|
3566
3953
|
const match = output.match(/v?[\d]+\.[\d]+\.[\d]+/);
|
|
@@ -3579,211 +3966,113 @@ function clearKernelVersionCache() {
|
|
|
3579
3966
|
kernelVersionCached = false;
|
|
3580
3967
|
}
|
|
3581
3968
|
|
|
3582
|
-
// src/
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
|
|
3592
|
-
|
|
3593
|
-
|
|
3594
|
-
|
|
3595
|
-
var require2 = createRequire(import.meta.url);
|
|
3596
|
-
var pkg = require2("../package.json");
|
|
3597
|
-
var VERSION = pkg.version;
|
|
3598
|
-
var sleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
3599
|
-
var NO_COLOR = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
3600
|
-
function colorize(code, str) {
|
|
3601
|
-
if (NO_COLOR) return String(str);
|
|
3602
|
-
return `${code + String(str)}\x1B[0m`;
|
|
3603
|
-
}
|
|
3604
|
-
var colors = {
|
|
3605
|
-
bold: (s) => colorize("\x1B[1m", s),
|
|
3606
|
-
red: (s) => colorize("\x1B[31m", s),
|
|
3607
|
-
green: (s) => colorize("\x1B[32m", s),
|
|
3608
|
-
yellow: (s) => colorize("\x1B[33m", s),
|
|
3609
|
-
cyan: (s) => colorize("\x1B[36m", s),
|
|
3610
|
-
gray: (s) => colorize("\x1B[90m", s)
|
|
3611
|
-
};
|
|
3612
|
-
function sleepSync(ms) {
|
|
3613
|
-
Atomics.wait(sleepBuf, 0, 0, ms);
|
|
3614
|
-
}
|
|
3615
|
-
function sleep(ms) {
|
|
3616
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3617
|
-
}
|
|
3618
|
-
var TimeoutError = class extends Error {
|
|
3619
|
-
constructor() {
|
|
3620
|
-
super("timeout");
|
|
3621
|
-
this.name = "TimeoutError";
|
|
3622
|
-
}
|
|
3623
|
-
};
|
|
3624
|
-
function withTimeout(promise, ms) {
|
|
3625
|
-
return new Promise((resolve, reject) => {
|
|
3626
|
-
const timer = setTimeout(() => reject(new TimeoutError()), ms);
|
|
3627
|
-
promise.then(
|
|
3628
|
-
(v) => {
|
|
3629
|
-
clearTimeout(timer);
|
|
3630
|
-
resolve(v);
|
|
3631
|
-
},
|
|
3632
|
-
(e) => {
|
|
3633
|
-
clearTimeout(timer);
|
|
3634
|
-
reject(e);
|
|
3635
|
-
}
|
|
3636
|
-
);
|
|
3637
|
-
});
|
|
3638
|
-
}
|
|
3639
|
-
function formatBytes(bytes) {
|
|
3640
|
-
if (bytes === void 0 || bytes === null) return "\u672A\u77E5";
|
|
3641
|
-
const num = Number(bytes);
|
|
3642
|
-
if (!Number.isFinite(num) || num < 0) return "\u672A\u77E5";
|
|
3643
|
-
if (num === 0) return "0 B";
|
|
3644
|
-
const k = 1024;
|
|
3645
|
-
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
3646
|
-
const i = Math.min(Math.floor(Math.log(num) / Math.log(k)), sizes.length - 1);
|
|
3647
|
-
return `${parseFloat((num / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
3648
|
-
}
|
|
3649
|
-
function formatTimestamp(ts) {
|
|
3650
|
-
if (ts === void 0 || ts === null) return "\u672A\u77E5";
|
|
3651
|
-
try {
|
|
3652
|
-
return new Date(ts * 1e3).toLocaleString("zh-CN");
|
|
3653
|
-
} catch {
|
|
3654
|
-
return "\u672A\u77E5";
|
|
3655
|
-
}
|
|
3656
|
-
}
|
|
3657
|
-
function formatDate(dateOrIso) {
|
|
3658
|
-
if (dateOrIso === void 0 || dateOrIso === null) return "\u672A\u77E5";
|
|
3659
|
-
try {
|
|
3660
|
-
const d = dateOrIso instanceof Date ? dateOrIso : new Date(dateOrIso);
|
|
3661
|
-
if (Number.isNaN(d.getTime())) return "\u672A\u77E5";
|
|
3662
|
-
return d.toLocaleString("zh-CN");
|
|
3663
|
-
} catch {
|
|
3664
|
-
return "\u672A\u77E5";
|
|
3665
|
-
}
|
|
3969
|
+
// src/commands/help.ts
|
|
3970
|
+
function printShortHelp() {
|
|
3971
|
+
console.log(`
|
|
3972
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))} (mihomo help \u67E5\u770B\u5B8C\u6574\u5E2E\u52A9)
|
|
3973
|
+
`);
|
|
3974
|
+
console.log(
|
|
3975
|
+
`\u5E38\u7528\u547D\u4EE4:
|
|
3976
|
+
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
3977
|
+
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
3978
|
+
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
3979
|
+
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
3980
|
+
`
|
|
3981
|
+
);
|
|
3666
3982
|
}
|
|
3667
|
-
|
|
3668
|
-
|
|
3983
|
+
var GROUP_TITLES = [
|
|
3984
|
+
["control", "\u63A7\u5236:"],
|
|
3985
|
+
["interface", "\u754C\u9762:"],
|
|
3986
|
+
["subscription", "\u8BA2\u9605:"],
|
|
3987
|
+
["config", "\u914D\u7F6E:"],
|
|
3988
|
+
["system", "\u7CFB\u7EDF:"]
|
|
3989
|
+
];
|
|
3990
|
+
function printHelp(commands) {
|
|
3991
|
+
const lines = [`
|
|
3992
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}`, "", "\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh", "", "\u7528\u6CD5:", " mihomo <\u547D\u4EE4> [\u9009\u9879]"];
|
|
3993
|
+
for (const [group, title] of GROUP_TITLES) {
|
|
3994
|
+
const usageLines = commands.filter((c) => c.group === group).flatMap((c) => c.usage);
|
|
3995
|
+
if (usageLines.length === 0) continue;
|
|
3996
|
+
lines.push("", colors.cyan(title));
|
|
3997
|
+
for (const u of usageLines) {
|
|
3998
|
+
lines.push(u.startsWith(" ") ? ` ${u}` : ` ${boldFirstToken(u)}`);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
const meta = commands.filter((c) => c.group === "meta").flatMap((c) => c.usage);
|
|
4002
|
+
if (meta.length > 0) {
|
|
4003
|
+
lines.push("", colors.cyan("\u5143:"));
|
|
4004
|
+
for (const u of meta) lines.push(` ${boldFirstToken(u)}`);
|
|
4005
|
+
}
|
|
4006
|
+
lines.push(
|
|
4007
|
+
"",
|
|
4008
|
+
`${colors.cyan("\u793A\u4F8B:")}`,
|
|
4009
|
+
" mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F",
|
|
4010
|
+
" mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F",
|
|
4011
|
+
" mihomo start -s # \u8DF3\u8FC7\u81EA\u52A8\u66F4\u65B0\u8BA2\u9605",
|
|
4012
|
+
" mihomo start -u 30000 # \u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 30 \u79D2 (\u9ED8\u8BA4 10s)",
|
|
4013
|
+
" mihomo daemon on # \u5F00\u542F\u4FDD\u6D3B\uFF08\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F\uFF09",
|
|
4014
|
+
" mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)",
|
|
4015
|
+
" mihomo ui # \u6253\u5F00 Web UI",
|
|
4016
|
+
"",
|
|
4017
|
+
`${colors.cyan("\u5FEB\u6377\u547D\u4EE4:")}`,
|
|
4018
|
+
" tun = start tun use = sub use on/off = ow on/off open = dir open",
|
|
4019
|
+
" up = start down = stop upd/upgrade = update",
|
|
4020
|
+
"",
|
|
4021
|
+
`${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}`,
|
|
4022
|
+
" mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)",
|
|
4023
|
+
" tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo",
|
|
4024
|
+
"",
|
|
4025
|
+
`${colors.cyan("\u6570\u636E\u76EE\u5F55:")}`,
|
|
4026
|
+
" \u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E",
|
|
4027
|
+
` \u9ED8\u8BA4: ${USER_DATA_DIR}`
|
|
4028
|
+
);
|
|
4029
|
+
console.log(lines.join("\n"));
|
|
3669
4030
|
}
|
|
3670
|
-
function
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
if (i + 1 < args.length) {
|
|
3675
|
-
const val = parseInt(args[i + 1], 10);
|
|
3676
|
-
return Number.isNaN(val) ? defaultValue : val;
|
|
3677
|
-
}
|
|
3678
|
-
}
|
|
3679
|
-
}
|
|
3680
|
-
return defaultValue;
|
|
4031
|
+
function boldFirstToken(usage) {
|
|
4032
|
+
const spaceIdx = usage.indexOf(" ");
|
|
4033
|
+
if (spaceIdx < 0) return colors.bold(usage);
|
|
4034
|
+
return `${colors.bold(usage.slice(0, spaceIdx))}${usage.slice(spaceIdx)}`;
|
|
3681
4035
|
}
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
if (a.startsWith("-")) {
|
|
3688
|
-
if (valueFlags.has(a)) i++;
|
|
3689
|
-
continue;
|
|
3690
|
-
}
|
|
3691
|
-
return a;
|
|
3692
|
-
}
|
|
3693
|
-
return null;
|
|
4036
|
+
function printVersion() {
|
|
4037
|
+
const kv = getKernelVersion() || "\u672A\u5B89\u88C5";
|
|
4038
|
+
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
4039
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
4040
|
+
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
3694
4041
|
}
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
4042
|
+
|
|
4043
|
+
// src/daemon.ts
|
|
4044
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
4045
|
+
import fs6 from "fs";
|
|
4046
|
+
import path5 from "path";
|
|
4047
|
+
|
|
4048
|
+
// src/process.ts
|
|
4049
|
+
import { spawn, spawnSync as spawnSync3 } from "child_process";
|
|
4050
|
+
import fs5 from "fs";
|
|
4051
|
+
import path4 from "path";
|
|
4052
|
+
|
|
4053
|
+
// src/lifecycle.ts
|
|
4054
|
+
var cleanupFns = /* @__PURE__ */ new Set();
|
|
4055
|
+
var silentSigint = false;
|
|
4056
|
+
function setSilentSigint(value) {
|
|
4057
|
+
silentSigint = value;
|
|
3703
4058
|
}
|
|
3704
|
-
function
|
|
3705
|
-
|
|
3706
|
-
try {
|
|
3707
|
-
const result = spawnSync2("ps", ["-p", String(pid), "-o", "uid="], { encoding: "utf8", timeout: 5e3 });
|
|
3708
|
-
return (result.stdout || "").trim() === "0";
|
|
3709
|
-
} catch {
|
|
3710
|
-
return false;
|
|
3711
|
-
}
|
|
4059
|
+
function isSilentSigint() {
|
|
4060
|
+
return silentSigint;
|
|
3712
4061
|
}
|
|
3713
|
-
function
|
|
3714
|
-
|
|
3715
|
-
return {
|
|
3716
|
-
|
|
3717
|
-
const controller = new AbortController();
|
|
3718
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
3719
|
-
const signal = config?.signal ? AbortSignal.any([controller.signal, config.signal]) : controller.signal;
|
|
3720
|
-
try {
|
|
3721
|
-
const response = await fetch(url, {
|
|
3722
|
-
signal,
|
|
3723
|
-
headers: { "User-Agent": `mihomo-cli/${VERSION}` }
|
|
3724
|
-
});
|
|
3725
|
-
if (!response.ok) {
|
|
3726
|
-
const error = new Error(`HTTP ${response.status}`);
|
|
3727
|
-
error.response = { status: response.status };
|
|
3728
|
-
try {
|
|
3729
|
-
error.response.data = await response.json();
|
|
3730
|
-
} catch {
|
|
3731
|
-
}
|
|
3732
|
-
throw error;
|
|
3733
|
-
}
|
|
3734
|
-
const data = config?.responseType === "json" ? await response.json() : await response.text();
|
|
3735
|
-
return { data, headers: response.headers, status: response.status };
|
|
3736
|
-
} finally {
|
|
3737
|
-
clearTimeout(timer);
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
4062
|
+
function registerCleanup(fn) {
|
|
4063
|
+
cleanupFns.add(fn);
|
|
4064
|
+
return () => {
|
|
4065
|
+
cleanupFns.delete(fn);
|
|
3740
4066
|
};
|
|
3741
4067
|
}
|
|
3742
|
-
function
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
url = `https://${url}`;
|
|
3748
|
-
}
|
|
3749
|
-
if (!url.endsWith("/")) {
|
|
3750
|
-
url += "/";
|
|
3751
|
-
}
|
|
3752
|
-
return url;
|
|
3753
|
-
}
|
|
3754
|
-
function parseMirrorArg(args) {
|
|
3755
|
-
if (!args || args.length < 2) {
|
|
3756
|
-
return { mirror: null, isOverride: false, type: "download" };
|
|
3757
|
-
}
|
|
3758
|
-
if (args.includes("--no-mirror") || args.includes("--direct")) {
|
|
3759
|
-
return { mirror: null, isOverride: true, type: "download" };
|
|
3760
|
-
}
|
|
3761
|
-
const mirrorAllIdx = args.indexOf("--mirror-all");
|
|
3762
|
-
if (mirrorAllIdx >= 0) {
|
|
3763
|
-
const nextArg = args[mirrorAllIdx + 1];
|
|
3764
|
-
if (!nextArg || nextArg.startsWith("-")) {
|
|
3765
|
-
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "all" };
|
|
3766
|
-
}
|
|
3767
|
-
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "all" };
|
|
3768
|
-
}
|
|
3769
|
-
const mirrorIdx = args.indexOf("--mirror");
|
|
3770
|
-
if (mirrorIdx >= 0) {
|
|
3771
|
-
const nextArg = args[mirrorIdx + 1];
|
|
3772
|
-
if (!nextArg || nextArg.startsWith("-")) {
|
|
3773
|
-
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "download" };
|
|
4068
|
+
function runCleanup() {
|
|
4069
|
+
for (const fn of cleanupFns) {
|
|
4070
|
+
try {
|
|
4071
|
+
fn();
|
|
4072
|
+
} catch {
|
|
3774
4073
|
}
|
|
3775
|
-
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "download" };
|
|
3776
|
-
}
|
|
3777
|
-
return { mirror: null, isOverride: false, type: "download" };
|
|
3778
|
-
}
|
|
3779
|
-
function isProxyValid(proxy) {
|
|
3780
|
-
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3781
|
-
if (!proxy.type) return false;
|
|
3782
|
-
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3783
|
-
const pw = String(proxy.password || "");
|
|
3784
|
-
if (!/^[A-Za-z0-9+/\-_]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3785
4074
|
}
|
|
3786
|
-
|
|
4075
|
+
cleanupFns.clear();
|
|
3787
4076
|
}
|
|
3788
4077
|
|
|
3789
4078
|
// src/process.ts
|
|
@@ -3794,9 +4083,7 @@ var SUDO_TIMEOUT_MS = 6e4;
|
|
|
3794
4083
|
var TUN_MODE_POST_WAIT_MS = 500;
|
|
3795
4084
|
var BATCH_KILL_THRESHOLD = 3;
|
|
3796
4085
|
var DEFAULT_LOG_RETENTION_DAYS = 7;
|
|
3797
|
-
|
|
3798
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3799
|
-
}
|
|
4086
|
+
var MAIN_INSTANCE_PATTERN = `${escapeRegExp(PATHS.mihomoBinary)}.*${escapeRegExp(PATHS.configFile)}`;
|
|
3800
4087
|
function clearRuntime() {
|
|
3801
4088
|
if (fs5.existsSync(DIRS.runtime)) {
|
|
3802
4089
|
rmrf(DIRS.runtime);
|
|
@@ -3816,10 +4103,9 @@ function isRunning() {
|
|
|
3816
4103
|
const pid = getPid();
|
|
3817
4104
|
return pid ? isProcessRunning(pid) : false;
|
|
3818
4105
|
}
|
|
3819
|
-
function
|
|
3820
|
-
const binaryPath = PATHS.mihomoBinary;
|
|
4106
|
+
function getMihomoPids() {
|
|
3821
4107
|
try {
|
|
3822
|
-
const result = spawnSync3("pgrep", ["-f",
|
|
4108
|
+
const result = spawnSync3("pgrep", ["-f", MAIN_INSTANCE_PATTERN], { encoding: "utf8", timeout: 1e4 });
|
|
3823
4109
|
const output = (result.stdout || "").trim();
|
|
3824
4110
|
if (!output) return [];
|
|
3825
4111
|
return output.split("\n").filter(Boolean).map((p) => parseInt(p, 10)).filter((p) => Number.isInteger(p) && p > 0);
|
|
@@ -3837,7 +4123,7 @@ function isPidFileOwnedByRoot() {
|
|
|
3837
4123
|
}
|
|
3838
4124
|
}
|
|
3839
4125
|
function checkStaleState() {
|
|
3840
|
-
const allPids =
|
|
4126
|
+
const allPids = getMihomoPids();
|
|
3841
4127
|
const hasRootProcess = allPids.some((p) => isProcessRoot(p));
|
|
3842
4128
|
const hasRootPidFile = isPidFileOwnedByRoot();
|
|
3843
4129
|
return {
|
|
@@ -3848,6 +4134,9 @@ function checkStaleState() {
|
|
|
3848
4134
|
needsSudo: hasRootProcess || hasRootPidFile
|
|
3849
4135
|
};
|
|
3850
4136
|
}
|
|
4137
|
+
function hasRootResidue() {
|
|
4138
|
+
return checkStaleState().needsSudo;
|
|
4139
|
+
}
|
|
3851
4140
|
function savePid(pid) {
|
|
3852
4141
|
ensureDirs();
|
|
3853
4142
|
fs5.writeFileSync(PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
@@ -3866,29 +4155,16 @@ function clearPid() {
|
|
|
3866
4155
|
}
|
|
3867
4156
|
}
|
|
3868
4157
|
}
|
|
3869
|
-
function killProcess(pid
|
|
4158
|
+
function killProcess(pid) {
|
|
3870
4159
|
try {
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
if (result.status === 0) {
|
|
3874
|
-
return true;
|
|
3875
|
-
}
|
|
3876
|
-
try {
|
|
3877
|
-
process.kill(pid, "SIGKILL");
|
|
3878
|
-
return true;
|
|
3879
|
-
} catch {
|
|
3880
|
-
return false;
|
|
3881
|
-
}
|
|
3882
|
-
} else {
|
|
3883
|
-
process.kill(pid, "SIGKILL");
|
|
3884
|
-
return true;
|
|
3885
|
-
}
|
|
4160
|
+
process.kill(pid, "SIGKILL");
|
|
4161
|
+
return true;
|
|
3886
4162
|
} catch {
|
|
3887
4163
|
return false;
|
|
3888
4164
|
}
|
|
3889
4165
|
}
|
|
3890
4166
|
function killAllMihomo(forceSudo = false) {
|
|
3891
|
-
const pattern =
|
|
4167
|
+
const pattern = MAIN_INSTANCE_PATTERN;
|
|
3892
4168
|
if (forceSudo) {
|
|
3893
4169
|
try {
|
|
3894
4170
|
spawnSync3("sudo", ["pkill", "-9", "-f", pattern], { stdio: "inherit", timeout: 15e3 });
|
|
@@ -3906,7 +4182,7 @@ function killAllMihomo(forceSudo = false) {
|
|
|
3906
4182
|
}
|
|
3907
4183
|
}
|
|
3908
4184
|
function cleanupAll(forceSudo = false) {
|
|
3909
|
-
const pids =
|
|
4185
|
+
const pids = getMihomoPids();
|
|
3910
4186
|
if (pids.length === 0) {
|
|
3911
4187
|
clearPid();
|
|
3912
4188
|
return { killed: 0, failed: 0, remaining: [] };
|
|
@@ -3928,7 +4204,7 @@ function cleanupAll(forceSudo = false) {
|
|
|
3928
4204
|
killedCount = pids.length;
|
|
3929
4205
|
} else {
|
|
3930
4206
|
for (const pid of pids) {
|
|
3931
|
-
if (killProcess(pid
|
|
4207
|
+
if (killProcess(pid)) {
|
|
3932
4208
|
killedCount++;
|
|
3933
4209
|
} else {
|
|
3934
4210
|
failedPids.push(pid);
|
|
@@ -3937,26 +4213,26 @@ function cleanupAll(forceSudo = false) {
|
|
|
3937
4213
|
}
|
|
3938
4214
|
}
|
|
3939
4215
|
for (let i = 0; i < PROCESS_WAIT_ATTEMPTS; i++) {
|
|
3940
|
-
if (
|
|
4216
|
+
if (getMihomoPids().length === 0) break;
|
|
3941
4217
|
sleepSync(PROCESS_WAIT_INTERVAL);
|
|
3942
4218
|
}
|
|
3943
4219
|
clearPid();
|
|
3944
|
-
return { killed: killedCount, failed: failedPids.length, remaining:
|
|
4220
|
+
return { killed: killedCount, failed: failedPids.length, remaining: getMihomoPids() };
|
|
3945
4221
|
}
|
|
3946
4222
|
function createTunLaunchScript() {
|
|
3947
|
-
const binary = PATHS.mihomoBinary;
|
|
3948
|
-
const configFile = PATHS.configFile;
|
|
3949
|
-
const logFile = PATHS.logFile;
|
|
3950
|
-
const pidFile = PATHS.pidFile;
|
|
3951
|
-
const dataDir = DIRS.data;
|
|
3952
|
-
const killPattern =
|
|
4223
|
+
const binary = shellQuote(PATHS.mihomoBinary);
|
|
4224
|
+
const configFile = shellQuote(PATHS.configFile);
|
|
4225
|
+
const logFile = shellQuote(PATHS.logFile);
|
|
4226
|
+
const pidFile = shellQuote(PATHS.pidFile);
|
|
4227
|
+
const dataDir = shellQuote(DIRS.data);
|
|
4228
|
+
const killPattern = shellQuote(MAIN_INSTANCE_PATTERN);
|
|
3953
4229
|
const scriptContent = `#!/bin/bash
|
|
3954
|
-
BINARY
|
|
3955
|
-
CONFIG_FILE
|
|
3956
|
-
LOG_FILE
|
|
3957
|
-
PID_FILE
|
|
3958
|
-
DATA_DIR
|
|
3959
|
-
KILL_PATTERN
|
|
4230
|
+
BINARY=${binary}
|
|
4231
|
+
CONFIG_FILE=${configFile}
|
|
4232
|
+
LOG_FILE=${logFile}
|
|
4233
|
+
PID_FILE=${pidFile}
|
|
4234
|
+
DATA_DIR=${dataDir}
|
|
4235
|
+
KILL_PATTERN=${killPattern}
|
|
3960
4236
|
|
|
3961
4237
|
# \u7EC8\u6B62\u65E7\u8FDB\u7A0B
|
|
3962
4238
|
pkill -9 -f "\${KILL_PATTERN}" 2>/dev/null || true
|
|
@@ -3980,14 +4256,15 @@ for i in 1 2 3 4 5; do
|
|
|
3980
4256
|
fi
|
|
3981
4257
|
done
|
|
3982
4258
|
|
|
3983
|
-
# \u5931\u8D25\uFF0C\u663E\u793A\u65E5\u5FD7
|
|
4259
|
+
# \u5931\u8D25\uFF0C\u663E\u793A\u65E5\u5FD7\uFF08\u9000\u51FA\u7801 2\uFF1A\u907F\u5F00 sudo \u7684 1=\u9274\u6743\u5931\u8D25/\u53D6\u6D88\uFF0C\u4F9B\u8C03\u7528\u65B9\u533A\u5206\uFF09
|
|
4260
|
+
rm -f "\${PID_FILE}" 2>/dev/null || true
|
|
3984
4261
|
echo "TUN \u542F\u52A8\u5931\u8D25"
|
|
3985
4262
|
echo ""
|
|
3986
4263
|
echo "--- \u65E5\u5FD7 ---"
|
|
3987
4264
|
tail -25 "\${LOG_FILE}" 2>/dev/null
|
|
3988
|
-
exit
|
|
4265
|
+
exit 2
|
|
3989
4266
|
`;
|
|
3990
|
-
const scriptPath =
|
|
4267
|
+
const scriptPath = path4.join(DIRS.runtime, "launch-tun.sh");
|
|
3991
4268
|
fs5.writeFileSync(scriptPath, scriptContent, { mode: 448 });
|
|
3992
4269
|
return scriptPath;
|
|
3993
4270
|
}
|
|
@@ -4009,7 +4286,7 @@ function getProcessInfo(pid) {
|
|
|
4009
4286
|
function getStatus() {
|
|
4010
4287
|
const running = isRunning();
|
|
4011
4288
|
const pid = getPid();
|
|
4012
|
-
const allPids =
|
|
4289
|
+
const allPids = getMihomoPids();
|
|
4013
4290
|
return {
|
|
4014
4291
|
running,
|
|
4015
4292
|
pid: running ? pid : null,
|
|
@@ -4043,7 +4320,7 @@ async function startMixedMode(staleState) {
|
|
|
4043
4320
|
if (staleState.needsCleanup) {
|
|
4044
4321
|
if (staleState.needsSudo) {
|
|
4045
4322
|
console.log("\n\u53D1\u73B0\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6");
|
|
4046
|
-
console.log(
|
|
4323
|
+
console.log(`\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo && sudo rm -f ${PATHS.pidFile}`);
|
|
4047
4324
|
console.log("\u6216\u8005\u5207\u6362\u5230 TUN \u6A21\u5F0F\uFF0C\u542F\u52A8\u65F6\u4F1A\u81EA\u52A8\u6E05\u7406");
|
|
4048
4325
|
throw new Error("\u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559");
|
|
4049
4326
|
}
|
|
@@ -4059,14 +4336,30 @@ async function startMixedMode(staleState) {
|
|
|
4059
4336
|
const configFile = PATHS.configFile;
|
|
4060
4337
|
const logFile = PATHS.logFile;
|
|
4061
4338
|
const args = ["-d", DIRS.data, "-f", configFile];
|
|
4339
|
+
if (fs5.existsSync(logFile)) {
|
|
4340
|
+
try {
|
|
4341
|
+
fs5.accessSync(logFile, fs5.constants.W_OK);
|
|
4342
|
+
} catch {
|
|
4343
|
+
try {
|
|
4344
|
+
fs5.unlinkSync(logFile);
|
|
4345
|
+
} catch {
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
}
|
|
4062
4349
|
const logFd = fs5.openSync(logFile, "a");
|
|
4063
4350
|
const child = spawn(PATHS.mihomoBinary, args, {
|
|
4064
4351
|
detached: true,
|
|
4065
4352
|
stdio: ["ignore", logFd, logFd]
|
|
4066
4353
|
});
|
|
4354
|
+
child.on("error", () => {
|
|
4355
|
+
});
|
|
4067
4356
|
fs5.closeSync(logFd);
|
|
4068
4357
|
child.unref();
|
|
4069
4358
|
const pid = child.pid;
|
|
4359
|
+
if (!pid) {
|
|
4360
|
+
clearPid();
|
|
4361
|
+
throw new Error("\u542F\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u521B\u5EFA\u5185\u6838\u8FDB\u7A0B\uFF08\u5185\u6838\u4E8C\u8FDB\u5236\u53EF\u80FD\u4E0D\u53EF\u6267\u884C\uFF09");
|
|
4362
|
+
}
|
|
4070
4363
|
savePid(pid);
|
|
4071
4364
|
await new Promise((resolve) => setTimeout(resolve, STARTUP_WAIT_MS));
|
|
4072
4365
|
if (!isRunning()) {
|
|
@@ -4107,6 +4400,9 @@ async function startTunMode(staleState) {
|
|
|
4107
4400
|
if (e.status === 1) {
|
|
4108
4401
|
throw new Error("\u5BC6\u7801\u9519\u8BEF\u6216\u53D6\u6D88");
|
|
4109
4402
|
}
|
|
4403
|
+
if (e.status === 2) {
|
|
4404
|
+
throw new Error("TUN \u542F\u52A8\u5931\u8D25\uFF08\u8BE6\u89C1\u4E0A\u65B9\u65E5\u5FD7\uFF09");
|
|
4405
|
+
}
|
|
4110
4406
|
throw new Error(e.message);
|
|
4111
4407
|
}
|
|
4112
4408
|
try {
|
|
@@ -4121,14 +4417,14 @@ async function startTunMode(staleState) {
|
|
|
4121
4417
|
return { success: true, pid: finalPid, mode: "tun" };
|
|
4122
4418
|
}
|
|
4123
4419
|
function stop(forceSudo = false) {
|
|
4124
|
-
const allPids =
|
|
4420
|
+
const allPids = getMihomoPids();
|
|
4125
4421
|
if (allPids.length === 0) {
|
|
4126
4422
|
clearPid();
|
|
4127
4423
|
clearRuntime();
|
|
4128
4424
|
return { success: true, notRunning: true };
|
|
4129
4425
|
}
|
|
4130
4426
|
const result = cleanupAll(forceSudo);
|
|
4131
|
-
const remaining =
|
|
4427
|
+
const remaining = getMihomoPids();
|
|
4132
4428
|
if (remaining.length > 0) {
|
|
4133
4429
|
console.log("");
|
|
4134
4430
|
console.log("\u4ECD\u6709\u8FDB\u7A0B\u6B8B\u7559\uFF0C\u9700\u8981\u624B\u52A8\u6E05\u7406:");
|
|
@@ -4152,9 +4448,8 @@ function rotateLog() {
|
|
|
4152
4448
|
if (!fs5.existsSync(logFile)) return null;
|
|
4153
4449
|
const stat = fs5.statSync(logFile);
|
|
4154
4450
|
if (stat.size === 0) return null;
|
|
4155
|
-
const
|
|
4156
|
-
const
|
|
4157
|
-
const rotatedPath = path3.join(DIRS.logs, rotatedName);
|
|
4451
|
+
const rotatedName = `mihomo.${formatLocalTimestamp()}.log`;
|
|
4452
|
+
const rotatedPath = path4.join(DIRS.logs, rotatedName);
|
|
4158
4453
|
fs5.renameSync(logFile, rotatedPath);
|
|
4159
4454
|
return rotatedPath;
|
|
4160
4455
|
}
|
|
@@ -4169,7 +4464,7 @@ function cleanupOldLogs(maxAgeDays = DEFAULT_LOG_RETENTION_DAYS) {
|
|
|
4169
4464
|
for (const file of files) {
|
|
4170
4465
|
if (!file.match(/^mihomo\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.log$/)) continue;
|
|
4171
4466
|
try {
|
|
4172
|
-
const filePath =
|
|
4467
|
+
const filePath = path4.join(logsDir, file);
|
|
4173
4468
|
const stat = fs5.statSync(filePath);
|
|
4174
4469
|
if (now - stat.mtimeMs > maxAgeMs) {
|
|
4175
4470
|
fs5.unlinkSync(filePath);
|
|
@@ -4200,7 +4495,7 @@ function listLogs() {
|
|
|
4200
4495
|
const match = file.match(/^mihomo\.(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.log$/);
|
|
4201
4496
|
if (!match) continue;
|
|
4202
4497
|
try {
|
|
4203
|
-
const filePath =
|
|
4498
|
+
const filePath = path4.join(logsDir, file);
|
|
4204
4499
|
const stat = fs5.statSync(filePath);
|
|
4205
4500
|
result.archives.push({
|
|
4206
4501
|
name: file,
|
|
@@ -4216,22 +4511,22 @@ function listLogs() {
|
|
|
4216
4511
|
return result;
|
|
4217
4512
|
}
|
|
4218
4513
|
function isPathUnderDir(filePath, baseDir) {
|
|
4219
|
-
const resolvedPath =
|
|
4220
|
-
const resolvedBase =
|
|
4221
|
-
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase +
|
|
4514
|
+
const resolvedPath = path4.resolve(filePath);
|
|
4515
|
+
const resolvedBase = path4.resolve(baseDir);
|
|
4516
|
+
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + path4.sep);
|
|
4222
4517
|
}
|
|
4223
4518
|
function getLogPathByName(name) {
|
|
4224
4519
|
const logsDir = DIRS.logs;
|
|
4225
4520
|
let targetName = name;
|
|
4226
4521
|
if (!name.endsWith(".log")) targetName = `mihomo.${name}.log`;
|
|
4227
4522
|
if (!targetName.startsWith("mihomo.")) targetName = `mihomo.${targetName}`;
|
|
4228
|
-
const filePath =
|
|
4523
|
+
const filePath = path4.join(logsDir, targetName);
|
|
4229
4524
|
if (fs5.existsSync(filePath) && isPathUnderDir(filePath, logsDir)) return filePath;
|
|
4230
4525
|
if (fs5.existsSync(logsDir)) {
|
|
4231
4526
|
const files = fs5.readdirSync(logsDir);
|
|
4232
4527
|
for (const file of files) {
|
|
4233
4528
|
if (file.includes(name)) {
|
|
4234
|
-
const candidatePath =
|
|
4529
|
+
const candidatePath = path4.join(logsDir, file);
|
|
4235
4530
|
if (isPathUnderDir(candidatePath, logsDir)) return candidatePath;
|
|
4236
4531
|
}
|
|
4237
4532
|
}
|
|
@@ -4272,6 +4567,7 @@ function viewLogWithTail(logPath, options) {
|
|
|
4272
4567
|
tailArgs.push("-n", lines.toString());
|
|
4273
4568
|
tailArgs.push(logPath);
|
|
4274
4569
|
const tail = spawn("tail", tailArgs, { stdio: "inherit" });
|
|
4570
|
+
if (follow) setSilentSigint(true);
|
|
4275
4571
|
tail.on("close", () => process.exit(0));
|
|
4276
4572
|
tail.on("error", (e) => {
|
|
4277
4573
|
console.error(`\u65E0\u6CD5\u8BFB\u53D6\u65E5\u5FD7: ${e.message}`);
|
|
@@ -4280,31 +4576,16 @@ function viewLogWithTail(logPath, options) {
|
|
|
4280
4576
|
}
|
|
4281
4577
|
|
|
4282
4578
|
// src/daemon.ts
|
|
4283
|
-
var
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
}
|
|
4291
|
-
|
|
4292
|
-
function getUid() {
|
|
4293
|
-
const uid = process.getuid?.();
|
|
4294
|
-
if (uid === void 0) {
|
|
4295
|
-
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7528\u6237 UID\uFF0C\u4FDD\u6D3B\u529F\u80FD\u4EC5\u652F\u6301 macOS");
|
|
4579
|
+
var SERVICE_TARGET = `system/${LAUNCH_DAEMON_LABEL}`;
|
|
4580
|
+
var HOT_RELOAD_TIMEOUT_MS = 5e3;
|
|
4581
|
+
var DAEMON_BOOT_WAIT_MS = 500;
|
|
4582
|
+
var LOG_ROTATE_MAX_BYTES = 10 * 1024 * 1024;
|
|
4583
|
+
function logOversized() {
|
|
4584
|
+
try {
|
|
4585
|
+
return fs6.statSync(PATHS.logFile).size > LOG_ROTATE_MAX_BYTES;
|
|
4586
|
+
} catch {
|
|
4587
|
+
return false;
|
|
4296
4588
|
}
|
|
4297
|
-
return uid;
|
|
4298
|
-
}
|
|
4299
|
-
function guiDomain(uid) {
|
|
4300
|
-
return `gui/${uid}`;
|
|
4301
|
-
}
|
|
4302
|
-
function serviceTarget(uid) {
|
|
4303
|
-
return `gui/${uid}/${LAUNCH_AGENT_LABEL}`;
|
|
4304
|
-
}
|
|
4305
|
-
function launchctlError(action, result) {
|
|
4306
|
-
const detail = (result.stderr || result.stdout || "").trim() || `\u9000\u51FA\u7801 ${result.status}`;
|
|
4307
|
-
return new Error(`launchctl ${action} \u5931\u8D25: ${detail}`);
|
|
4308
4589
|
}
|
|
4309
4590
|
function escapeXml(s) {
|
|
4310
4591
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -4317,7 +4598,7 @@ function buildPlist() {
|
|
|
4317
4598
|
<plist version="1.0">
|
|
4318
4599
|
<dict>
|
|
4319
4600
|
<key>Label</key>
|
|
4320
|
-
<string>${escapeXml(
|
|
4601
|
+
<string>${escapeXml(LAUNCH_DAEMON_LABEL)}</string>
|
|
4321
4602
|
<key>ProgramArguments</key>
|
|
4322
4603
|
<array>
|
|
4323
4604
|
${argsXml}
|
|
@@ -4336,25 +4617,42 @@ ${argsXml}
|
|
|
4336
4617
|
</plist>
|
|
4337
4618
|
`;
|
|
4338
4619
|
}
|
|
4620
|
+
function runSudoScript(scriptBody, opts) {
|
|
4621
|
+
if (!process.stdin.isTTY) {
|
|
4622
|
+
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");
|
|
4623
|
+
}
|
|
4624
|
+
ensureDirs();
|
|
4625
|
+
const scriptPath = path5.join(DIRS.runtime, opts.file);
|
|
4626
|
+
fs6.writeFileSync(scriptPath, scriptBody, { mode: 448 });
|
|
4627
|
+
try {
|
|
4628
|
+
const result = spawnSync4("sudo", [scriptPath], { stdio: "inherit", timeout: SUDO_TIMEOUT_MS });
|
|
4629
|
+
if (result.error) throw result.error;
|
|
4630
|
+
if (result.status !== 0) {
|
|
4631
|
+
if (result.status === 1) {
|
|
4632
|
+
throw new Error("\u5DF2\u53D6\u6D88\u6216\u5BC6\u7801\u9519\u8BEF");
|
|
4633
|
+
}
|
|
4634
|
+
if (result.status == null) {
|
|
4635
|
+
throw new Error(`${opts.action}\u88AB\u4E2D\u65AD\uFF08sudo \u8FDB\u7A0B\u88AB\u4FE1\u53F7\u7EC8\u6B62\uFF09`);
|
|
4636
|
+
}
|
|
4637
|
+
const custom = opts.codeMessages?.[result.status];
|
|
4638
|
+
throw new Error(custom || `${opts.action}\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${result.status}\uFF09`);
|
|
4639
|
+
}
|
|
4640
|
+
} finally {
|
|
4641
|
+
try {
|
|
4642
|
+
fs6.unlinkSync(scriptPath);
|
|
4643
|
+
} catch {
|
|
4644
|
+
}
|
|
4645
|
+
}
|
|
4646
|
+
}
|
|
4339
4647
|
function isDaemonEnabled() {
|
|
4340
|
-
return fs6.existsSync(PATHS.
|
|
4648
|
+
return fs6.existsSync(PATHS.launchDaemonPlist);
|
|
4341
4649
|
}
|
|
4342
4650
|
function getDaemonStatus() {
|
|
4343
4651
|
if (!isDaemonEnabled()) {
|
|
4344
4652
|
return { enabled: false, loaded: false, pid: null };
|
|
4345
4653
|
}
|
|
4346
|
-
const
|
|
4347
|
-
|
|
4348
|
-
const pids = getAllMihomoPids();
|
|
4349
|
-
return { enabled: true, loaded: pids.length > 0, pid: pids[0] ?? null };
|
|
4350
|
-
}
|
|
4351
|
-
const result = launchctl(["print", serviceTarget(uid)]);
|
|
4352
|
-
if (result.status !== 0) {
|
|
4353
|
-
return { enabled: true, loaded: false, pid: null };
|
|
4354
|
-
}
|
|
4355
|
-
const pidMatch = result.stdout.match(/\bpid = (\d+)/);
|
|
4356
|
-
const pid = pidMatch ? parseInt(pidMatch[1], 10) : null;
|
|
4357
|
-
return { enabled: true, loaded: true, pid };
|
|
4654
|
+
const rootPids = getMihomoPids().filter(isProcessRoot);
|
|
4655
|
+
return { enabled: true, loaded: rootPids.length > 0, pid: rootPids[0] ?? null };
|
|
4358
4656
|
}
|
|
4359
4657
|
function isDaemonRunning(status) {
|
|
4360
4658
|
return status.loaded && status.pid !== null;
|
|
@@ -4366,52 +4664,113 @@ function enableDaemon() {
|
|
|
4366
4664
|
if (!fs6.existsSync(PATHS.configFile)) {
|
|
4367
4665
|
throw new Error("\u672A\u627E\u5230\u8FD0\u884C\u65F6\u914D\u7F6E\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
4368
4666
|
}
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
const
|
|
4375
|
-
|
|
4376
|
-
|
|
4667
|
+
ensureDirs();
|
|
4668
|
+
const stagePath = path5.join(DIRS.runtime, "daemon.plist.stage");
|
|
4669
|
+
atomicWriteFileSync(stagePath, buildPlist(), { mode: 384 });
|
|
4670
|
+
const target = shellQuote(SERVICE_TARGET);
|
|
4671
|
+
const plistDest = shellQuote(PATHS.launchDaemonPlist);
|
|
4672
|
+
const stage = shellQuote(stagePath);
|
|
4673
|
+
const pattern = shellQuote(MAIN_INSTANCE_PATTERN);
|
|
4674
|
+
const script = [
|
|
4675
|
+
"#!/bin/bash",
|
|
4676
|
+
`launchctl bootout ${target} 2>/dev/null || true`,
|
|
4677
|
+
`pkill -9 -f ${pattern} 2>/dev/null || true`,
|
|
4678
|
+
"sleep 0.2",
|
|
4679
|
+
`install -m 644 -o root -g wheel ${stage} ${plistDest} || exit 2`,
|
|
4680
|
+
`launchctl bootstrap system ${plistDest} || { launchctl bootout ${target} 2>/dev/null; rm -f ${plistDest}; exit 3; }`,
|
|
4681
|
+
"exit 0",
|
|
4682
|
+
""
|
|
4683
|
+
].join("\n");
|
|
4684
|
+
try {
|
|
4685
|
+
runSudoScript(script, {
|
|
4686
|
+
action: "\u542F\u7528\u4FDD\u6D3B",
|
|
4687
|
+
file: "daemon-enable.sh",
|
|
4688
|
+
codeMessages: {
|
|
4689
|
+
2: "\u5B89\u88C5 plist \u5230 /Library/LaunchDaemons \u5931\u8D25",
|
|
4690
|
+
3: "\u88C5\u8F7D\u4FDD\u6D3B\u670D\u52A1\u5931\u8D25\uFF08launchctl bootstrap\uFF09"
|
|
4691
|
+
}
|
|
4692
|
+
});
|
|
4693
|
+
} finally {
|
|
4377
4694
|
try {
|
|
4378
|
-
fs6.unlinkSync(
|
|
4695
|
+
fs6.unlinkSync(stagePath);
|
|
4379
4696
|
} catch {
|
|
4380
4697
|
}
|
|
4381
|
-
throw launchctlError("bootstrap", result);
|
|
4382
4698
|
}
|
|
4383
4699
|
}
|
|
4384
4700
|
function disableDaemon() {
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4701
|
+
if (!isDaemonEnabled()) return;
|
|
4702
|
+
const target = shellQuote(SERVICE_TARGET);
|
|
4703
|
+
const plistDest = shellQuote(PATHS.launchDaemonPlist);
|
|
4704
|
+
const logFile = shellQuote(PATHS.logFile);
|
|
4705
|
+
const dataDir = shellQuote(DIRS.data);
|
|
4706
|
+
const script = [
|
|
4707
|
+
"#!/bin/bash",
|
|
4708
|
+
`launchctl bootout ${target} 2>/dev/null || true`,
|
|
4709
|
+
`rm -f ${plistDest}`,
|
|
4710
|
+
`chown "$SUDO_UID:$SUDO_GID" ${logFile} 2>/dev/null || true`,
|
|
4711
|
+
`chown -R "$SUDO_UID:$SUDO_GID" ${dataDir} 2>/dev/null || true`,
|
|
4712
|
+
"exit 0",
|
|
4713
|
+
""
|
|
4714
|
+
].join("\n");
|
|
4715
|
+
runSudoScript(script, { action: "\u5173\u95ED\u4FDD\u6D3B", file: "daemon-disable.sh" });
|
|
4716
|
+
const rootPids = getMihomoPids().filter(isProcessRoot);
|
|
4717
|
+
if (rootPids.length > 0) {
|
|
4718
|
+
console.log("");
|
|
4719
|
+
console.log(`\u4ECD\u6709 root \u5185\u6838\u8FDB\u7A0B\u6B8B\u7559 (PID ${rootPids.join(", ")})`);
|
|
4720
|
+
console.log("\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo");
|
|
4388
4721
|
}
|
|
4722
|
+
}
|
|
4723
|
+
async function tryHotReload() {
|
|
4724
|
+
const controller = new AbortController();
|
|
4725
|
+
const timer = setTimeout(() => controller.abort(), HOT_RELOAD_TIMEOUT_MS);
|
|
4389
4726
|
try {
|
|
4390
|
-
|
|
4727
|
+
const res = await fetch(`${CONTROLLER_BASE_URL}/configs?force=true`, {
|
|
4728
|
+
method: "PUT",
|
|
4729
|
+
headers: { "Content-Type": "application/json" },
|
|
4730
|
+
body: "{}",
|
|
4731
|
+
signal: controller.signal
|
|
4732
|
+
});
|
|
4733
|
+
return res.status === 204 || res.ok;
|
|
4391
4734
|
} catch {
|
|
4735
|
+
return false;
|
|
4736
|
+
} finally {
|
|
4737
|
+
clearTimeout(timer);
|
|
4392
4738
|
}
|
|
4393
4739
|
}
|
|
4394
|
-
function restartDaemon() {
|
|
4395
|
-
|
|
4396
|
-
if (!fs6.existsSync(PATHS.launchAgentPlist)) {
|
|
4740
|
+
async function restartDaemon() {
|
|
4741
|
+
if (!fs6.existsSync(PATHS.launchDaemonPlist)) {
|
|
4397
4742
|
throw new Error("\u4FDD\u6D3B\u672A\u542F\u7528\uFF0C\u65E0\u6CD5\u91CD\u542F");
|
|
4398
4743
|
}
|
|
4399
|
-
if (
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4744
|
+
if (!logOversized() && await tryHotReload()) return;
|
|
4745
|
+
const target = shellQuote(SERVICE_TARGET);
|
|
4746
|
+
const plistDest = shellQuote(PATHS.launchDaemonPlist);
|
|
4747
|
+
const logFile = shellQuote(PATHS.logFile);
|
|
4748
|
+
const archiveFile = shellQuote(path5.join(DIRS.logs, `mihomo.${formatLocalTimestamp()}.log`));
|
|
4749
|
+
const script = [
|
|
4750
|
+
"#!/bin/bash",
|
|
4751
|
+
`if [ -f ${logFile} ] && [ "$(stat -f%z ${logFile} 2>/dev/null || echo 0)" -gt ${LOG_ROTATE_MAX_BYTES} ]; then`,
|
|
4752
|
+
` cp ${logFile} ${archiveFile} 2>/dev/null && : > ${logFile}`,
|
|
4753
|
+
"fi",
|
|
4754
|
+
`if launchctl kickstart -k ${target} 2>/dev/null; then exit 0; fi`,
|
|
4755
|
+
`launchctl bootstrap system ${plistDest} || exit 3`,
|
|
4756
|
+
"exit 0",
|
|
4757
|
+
""
|
|
4758
|
+
].join("\n");
|
|
4759
|
+
runSudoScript(script, {
|
|
4760
|
+
action: "\u91CD\u542F\u4FDD\u6D3B",
|
|
4761
|
+
file: "daemon-restart.sh",
|
|
4762
|
+
codeMessages: { 3: "\u91CD\u542F\u4FDD\u6D3B\u5931\u8D25\uFF08launchctl bootstrap\uFF09" }
|
|
4763
|
+
});
|
|
4764
|
+
cleanupOldLogs();
|
|
4410
4765
|
}
|
|
4411
4766
|
|
|
4412
4767
|
// src/subscription.ts
|
|
4413
4768
|
function isGithubUrl(url) {
|
|
4414
|
-
|
|
4769
|
+
const githubRe = /github\.com|raw\.githubusercontent\.com/i;
|
|
4770
|
+
if (isMultiUrl(url)) {
|
|
4771
|
+
return splitUrls(url).every((u) => githubRe.test(u));
|
|
4772
|
+
}
|
|
4773
|
+
return githubRe.test(url);
|
|
4415
4774
|
}
|
|
4416
4775
|
function getDefaultUpdateInterval(url) {
|
|
4417
4776
|
return isGithubUrl(url) ? DEFAULT_UPDATE_INTERVAL_HOURS_GITHUB : DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
@@ -4419,11 +4778,18 @@ function getDefaultUpdateInterval(url) {
|
|
|
4419
4778
|
function resolveUpdateInterval(url, cachedInterval) {
|
|
4420
4779
|
return cachedInterval && cachedInterval > 0 ? cachedInterval : getDefaultUpdateInterval(url);
|
|
4421
4780
|
}
|
|
4422
|
-
var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, schema: CORE_SCHEMA };
|
|
4423
4781
|
var HTTP_CLIENT = createHttpClient({ timeout: 6e4 });
|
|
4424
4782
|
function isMultiUrl(url) {
|
|
4425
4783
|
return url.includes(",");
|
|
4426
4784
|
}
|
|
4785
|
+
function isValidHttpUrl(url) {
|
|
4786
|
+
try {
|
|
4787
|
+
const u = new URL(url.trim());
|
|
4788
|
+
return u.protocol === "http:" || u.protocol === "https:";
|
|
4789
|
+
} catch {
|
|
4790
|
+
return false;
|
|
4791
|
+
}
|
|
4792
|
+
}
|
|
4427
4793
|
function splitUrls(url) {
|
|
4428
4794
|
return url.split(",").map((u) => u.trim()).filter(Boolean);
|
|
4429
4795
|
}
|
|
@@ -4443,7 +4809,7 @@ function saveSubscriptionConfig(subName, parsed) {
|
|
|
4443
4809
|
normalizeProxyNamesBeforeSave(parsed);
|
|
4444
4810
|
parsed.raw.proxies = parsed.proxies;
|
|
4445
4811
|
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4446
|
-
saveSubscriptionRawConfig(subName,
|
|
4812
|
+
saveSubscriptionRawConfig(subName, dumpYaml(parsed.raw));
|
|
4447
4813
|
}
|
|
4448
4814
|
function parseUserInfo(header) {
|
|
4449
4815
|
if (!header) return null;
|
|
@@ -4539,7 +4905,7 @@ function pickSingleSubscription(subs, pattern) {
|
|
|
4539
4905
|
for (const s of subs) console.log(` ${s.name}`);
|
|
4540
4906
|
process.exit(1);
|
|
4541
4907
|
}
|
|
4542
|
-
async function downloadSubscription(url, subName = "default", signal) {
|
|
4908
|
+
async function downloadSubscription(url, subName = "default", signal, persist = true) {
|
|
4543
4909
|
let response;
|
|
4544
4910
|
try {
|
|
4545
4911
|
response = await HTTP_CLIENT.get(url, { responseType: "text", signal });
|
|
@@ -4560,9 +4926,13 @@ async function downloadSubscription(url, subName = "default", signal) {
|
|
|
4560
4926
|
}
|
|
4561
4927
|
const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
|
|
4562
4928
|
if (!parsed) throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4563
|
-
|
|
4929
|
+
if (persist) {
|
|
4930
|
+
saveSubscriptionRawConfig(subName, content);
|
|
4931
|
+
}
|
|
4564
4932
|
const meta = extractSubscriptionMeta(response.headers);
|
|
4565
|
-
|
|
4933
|
+
if (persist) {
|
|
4934
|
+
saveSubscriptionMeta(subName, meta);
|
|
4935
|
+
}
|
|
4566
4936
|
const proxies = parsed.proxies;
|
|
4567
4937
|
const proxyGroups = parsed["proxy-groups"];
|
|
4568
4938
|
return {
|
|
@@ -4574,13 +4944,16 @@ async function downloadSubscription(url, subName = "default", signal) {
|
|
|
4574
4944
|
username: meta.username
|
|
4575
4945
|
};
|
|
4576
4946
|
}
|
|
4577
|
-
async function downloadMergedSubscription(urls, subName, signal) {
|
|
4947
|
+
async function downloadMergedSubscription(urls, subName, signal, persist = true) {
|
|
4948
|
+
const internal = new AbortController();
|
|
4949
|
+
const combinedSignal = signal ? AbortSignal.any([signal, internal.signal]) : internal.signal;
|
|
4578
4950
|
const responses = await Promise.all(
|
|
4579
4951
|
urls.map(async (url, index) => {
|
|
4580
4952
|
try {
|
|
4581
|
-
const response = await HTTP_CLIENT.get(url, { responseType: "text", signal });
|
|
4953
|
+
const response = await HTTP_CLIENT.get(url, { responseType: "text", signal: combinedSignal });
|
|
4582
4954
|
return { url, index, response, error: null };
|
|
4583
4955
|
} catch (e) {
|
|
4956
|
+
internal.abort();
|
|
4584
4957
|
return { url, index, response: null, error: e };
|
|
4585
4958
|
}
|
|
4586
4959
|
})
|
|
@@ -4610,10 +4983,14 @@ async function downloadMergedSubscription(urls, subName, signal) {
|
|
|
4610
4983
|
}
|
|
4611
4984
|
}
|
|
4612
4985
|
base.proxies = baseProxies;
|
|
4613
|
-
const mergedContent =
|
|
4614
|
-
|
|
4986
|
+
const mergedContent = dumpYaml(base);
|
|
4987
|
+
if (persist) {
|
|
4988
|
+
saveSubscriptionRawConfig(subName, mergedContent);
|
|
4989
|
+
}
|
|
4615
4990
|
const meta = extractSubscriptionMeta(responses[0].response?.headers);
|
|
4616
|
-
|
|
4991
|
+
if (persist) {
|
|
4992
|
+
saveSubscriptionMeta(subName, meta);
|
|
4993
|
+
}
|
|
4617
4994
|
const proxyGroups = base["proxy-groups"];
|
|
4618
4995
|
return {
|
|
4619
4996
|
proxies: baseProxies.length,
|
|
@@ -4629,7 +5006,8 @@ function prepareConfigForStart(mode, subName = "default") {
|
|
|
4629
5006
|
if (!rawContent) {
|
|
4630
5007
|
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
|
|
4631
5008
|
}
|
|
4632
|
-
const
|
|
5009
|
+
const subUrl = getSubscriptions().find((s) => s.name === subName)?.url;
|
|
5010
|
+
const buildResult = buildConfig(rawContent, mode, { subName, subUrl });
|
|
4633
5011
|
if (buildResult.warnings.length > 0) {
|
|
4634
5012
|
for (const warning of buildResult.warnings) {
|
|
4635
5013
|
console.log(`${colors.yellow("\u81EA\u52A8\u4FEE\u590D:")} ${warning}`);
|
|
@@ -4688,16 +5066,23 @@ async function autoUpdateStaleSubscription(options = {}) {
|
|
|
4688
5066
|
}
|
|
4689
5067
|
const timeoutMs = options.timeout ?? DEFAULT_AUTO_UPDATE_TIMEOUT;
|
|
4690
5068
|
const controller = new AbortController();
|
|
4691
|
-
|
|
5069
|
+
const results = [];
|
|
5070
|
+
const updatePromise = Promise.all(
|
|
5071
|
+
staleSubs.map(
|
|
5072
|
+
(sub) => tryUpdateOne(sub, controller.signal).then((r) => {
|
|
5073
|
+
results.push(r);
|
|
5074
|
+
return r;
|
|
5075
|
+
})
|
|
5076
|
+
)
|
|
5077
|
+
);
|
|
4692
5078
|
try {
|
|
4693
|
-
|
|
5079
|
+
await withTimeout(updatePromise, timeoutMs);
|
|
4694
5080
|
} catch (e) {
|
|
4695
|
-
if (e instanceof TimeoutError)
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
}
|
|
4700
|
-
throw e;
|
|
5081
|
+
if (!(e instanceof TimeoutError)) throw e;
|
|
5082
|
+
controller.abort();
|
|
5083
|
+
await updatePromise.catch(() => {
|
|
5084
|
+
});
|
|
5085
|
+
console.log(colors.yellow(`\u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 (${timeoutMs / 1e3}s)\uFF0C\u5DF2\u5B8C\u6210\u7684\u66F4\u65B0\u751F\u6548\uFF0C\u5176\u4F59\u4F7F\u7528\u7F13\u5B58\u914D\u7F6E`));
|
|
4701
5086
|
}
|
|
4702
5087
|
let updatedCount = 0;
|
|
4703
5088
|
for (const r of results) {
|
|
@@ -4706,8 +5091,7 @@ async function autoUpdateStaleSubscription(options = {}) {
|
|
|
4706
5091
|
}
|
|
4707
5092
|
return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
|
|
4708
5093
|
}
|
|
4709
|
-
|
|
4710
|
-
async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = API_BASE) {
|
|
5094
|
+
async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = CONTROLLER_BASE_URL) {
|
|
4711
5095
|
const encodedName = encodeURIComponent(proxyName);
|
|
4712
5096
|
const url = `${apiBase}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4713
5097
|
try {
|
|
@@ -4729,7 +5113,13 @@ async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = API
|
|
|
4729
5113
|
}
|
|
4730
5114
|
}
|
|
4731
5115
|
async function testSubscriptionProxies(subName, options = {}) {
|
|
4732
|
-
const {
|
|
5116
|
+
const {
|
|
5117
|
+
timeout = DEFAULT_TEST_TIMEOUT,
|
|
5118
|
+
concurrency = DEFAULT_TEST_CONCURRENCY,
|
|
5119
|
+
testUrl = DEFAULT_TEST_URL,
|
|
5120
|
+
apiBase = CONTROLLER_BASE_URL,
|
|
5121
|
+
onResult
|
|
5122
|
+
} = options;
|
|
4733
5123
|
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4734
5124
|
if (proxies.length === 0) {
|
|
4735
5125
|
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
@@ -4775,6 +5165,19 @@ function normalizeProxyNamesBeforeSave(parsed) {
|
|
|
4775
5165
|
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
4776
5166
|
}
|
|
4777
5167
|
}
|
|
5168
|
+
const rules = parsed.raw.rules;
|
|
5169
|
+
if (Array.isArray(rules)) {
|
|
5170
|
+
parsed.raw.rules = rules.map((rule) => {
|
|
5171
|
+
if (typeof rule !== "string") return rule;
|
|
5172
|
+
const parts = rule.split(",");
|
|
5173
|
+
if (parts.length < 2) return rule;
|
|
5174
|
+
const targetIdx = parts[parts.length - 1].trim().toLowerCase() === "no-resolve" && parts.length >= 3 ? parts.length - 2 : parts.length - 1;
|
|
5175
|
+
const target = parts[targetIdx].trim();
|
|
5176
|
+
const renamed = renameMap.get(target);
|
|
5177
|
+
if (renamed) parts[targetIdx] = renamed;
|
|
5178
|
+
return parts.join(",");
|
|
5179
|
+
});
|
|
5180
|
+
}
|
|
4778
5181
|
return renameMap.size;
|
|
4779
5182
|
}
|
|
4780
5183
|
function cleanDeadProxies(parsed, deadNames) {
|
|
@@ -4791,7 +5194,8 @@ function cleanDeadProxies(parsed, deadNames) {
|
|
|
4791
5194
|
if (group.proxies.length < before) {
|
|
4792
5195
|
updatedGroups++;
|
|
4793
5196
|
}
|
|
4794
|
-
|
|
5197
|
+
const hasOtherSource = group.use || group["include-all"] || group["include-all-proxies"];
|
|
5198
|
+
if (group.proxies.length === 0 && !hasOtherSource) {
|
|
4795
5199
|
removedGroupNames.add(group.name);
|
|
4796
5200
|
}
|
|
4797
5201
|
}
|
|
@@ -4803,13 +5207,14 @@ function cleanDeadProxies(parsed, deadNames) {
|
|
|
4803
5207
|
group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
|
|
4804
5208
|
}
|
|
4805
5209
|
}
|
|
5210
|
+
}
|
|
5211
|
+
const removedTargets = /* @__PURE__ */ new Set([...removedGroupNames, ...deadNames]);
|
|
5212
|
+
if (removedTargets.size > 0) {
|
|
4806
5213
|
const rules = parsed.raw.rules;
|
|
4807
5214
|
if (Array.isArray(rules)) {
|
|
4808
5215
|
parsed.raw.rules = rules.filter((rule) => {
|
|
4809
5216
|
if (typeof rule !== "string") return true;
|
|
4810
|
-
|
|
4811
|
-
if (parts.length < 2) return true;
|
|
4812
|
-
return !removedGroupNames.has(parts[parts.length - 1].trim());
|
|
5217
|
+
return !removedTargets.has(getRuleTarget(rule));
|
|
4813
5218
|
});
|
|
4814
5219
|
}
|
|
4815
5220
|
}
|
|
@@ -4904,6 +5309,8 @@ async function cmdDaemon(args) {
|
|
|
4904
5309
|
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
4905
5310
|
process.exit(1);
|
|
4906
5311
|
}
|
|
5312
|
+
console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u5B89\u88C5\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1\uFF08LaunchDaemon\uFF09"));
|
|
5313
|
+
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"));
|
|
4907
5314
|
try {
|
|
4908
5315
|
enableDaemon();
|
|
4909
5316
|
} catch (e) {
|
|
@@ -4913,6 +5320,7 @@ async function cmdDaemon(args) {
|
|
|
4913
5320
|
console.log(`${colors.green("\u5DF2\u542F\u7528\u4FDD\u6D3B")} \xB7 ${sub.name} \xB7 ${formatProxySummary(configInfo)}`);
|
|
4914
5321
|
console.log(colors.gray("\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF0C\u4EE3\u7406\u5C06\u5728\u540E\u53F0\u5E38\u9A7B"));
|
|
4915
5322
|
console.log("");
|
|
5323
|
+
await sleep(DAEMON_BOOT_WAIT_MS);
|
|
4916
5324
|
printDaemonStatus();
|
|
4917
5325
|
return;
|
|
4918
5326
|
}
|
|
@@ -4923,6 +5331,7 @@ async function cmdDaemon(args) {
|
|
|
4923
5331
|
printDaemonStatus();
|
|
4924
5332
|
return;
|
|
4925
5333
|
}
|
|
5334
|
+
console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u79FB\u9664\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1"));
|
|
4926
5335
|
try {
|
|
4927
5336
|
disableDaemon();
|
|
4928
5337
|
} catch (e) {
|
|
@@ -5009,98 +5418,10 @@ function cmdDirectory(args) {
|
|
|
5009
5418
|
console.log("");
|
|
5010
5419
|
}
|
|
5011
5420
|
|
|
5012
|
-
// src/commands/help.ts
|
|
5013
|
-
function printShortHelp() {
|
|
5014
|
-
console.log(`
|
|
5015
|
-
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))} (mihomo help \u67E5\u770B\u5B8C\u6574\u5E2E\u52A9)
|
|
5016
|
-
`);
|
|
5017
|
-
console.log(
|
|
5018
|
-
`\u5E38\u7528\u547D\u4EE4:
|
|
5019
|
-
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
5020
|
-
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
5021
|
-
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
5022
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
5023
|
-
`
|
|
5024
|
-
);
|
|
5025
|
-
}
|
|
5026
|
-
function printHelp() {
|
|
5027
|
-
console.log(
|
|
5028
|
-
`
|
|
5029
|
-
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}
|
|
5030
|
-
|
|
5031
|
-
\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh
|
|
5032
|
-
|
|
5033
|
-
\u7528\u6CD5:
|
|
5034
|
-
mihomo <\u547D\u4EE4> [\u9009\u9879]
|
|
5035
|
-
|
|
5036
|
-
${colors.cyan("\u63A7\u5236:")}
|
|
5037
|
-
${colors.bold("start")} [tun|mixed] [-s] [-u ms] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)
|
|
5038
|
-
[-r N] [-t ms] [-j N]
|
|
5039
|
-
${colors.bold("stop")} \u505C\u6B62\u4EE3\u7406
|
|
5040
|
-
${colors.bold("status")} \u67E5\u770B\u72B6\u6001
|
|
5041
|
-
|
|
5042
|
-
${colors.cyan("\u754C\u9762:")}
|
|
5043
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)
|
|
5044
|
-
${colors.bold("log")} [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09
|
|
5045
|
-
${colors.bold("logs")} [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09
|
|
5046
|
-
|
|
5047
|
-
${colors.cyan("\u8BA2\u9605:")}
|
|
5048
|
-
${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
|
|
5049
|
-
${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
|
|
5050
|
-
${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
|
|
5051
|
-
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
5052
|
-
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
5053
|
-
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
5054
|
-
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
5055
|
-
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
5056
|
-
${colors.bold("test")} [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027
|
|
5057
|
-
${colors.bold("clean")} [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F
|
|
5058
|
-
|
|
5059
|
-
${colors.cyan("\u914D\u7F6E:")}
|
|
5060
|
-
${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
|
|
5061
|
-
${colors.bold("overwrite")} on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E
|
|
5062
|
-
${colors.bold("directory")} \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09
|
|
5063
|
-
${colors.bold("directory")} open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|...
|
|
5064
|
-
|
|
5065
|
-
${colors.cyan("\u7CFB\u7EDF:")}
|
|
5066
|
-
${colors.bold("kernel")} [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09
|
|
5067
|
-
${colors.bold("daemon")} on|off \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F\uFF08\u4EC5 Mixed\uFF09
|
|
5068
|
-
${colors.bold("daemon")} status \u67E5\u770B\u4FDD\u6D3B\u72B6\u6001
|
|
5069
|
-
${colors.bold("update")} \u66F4\u65B0 mihomo-cli (npm install -g)
|
|
5070
|
-
${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
|
|
5071
|
-
${colors.bold("help")}, -h \u663E\u793A\u5E2E\u52A9
|
|
5072
|
-
${colors.bold("version")}, -v \u663E\u793A\u7248\u672C
|
|
5073
|
-
|
|
5074
|
-
${colors.cyan("\u793A\u4F8B:")}
|
|
5075
|
-
mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F
|
|
5076
|
-
mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F
|
|
5077
|
-
mihomo start -s # \u8DF3\u8FC7\u81EA\u52A8\u66F4\u65B0\u8BA2\u9605
|
|
5078
|
-
mihomo start -u 30000 # \u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 30 \u79D2 (\u9ED8\u8BA4 10s)
|
|
5079
|
-
mihomo daemon on # \u5F00\u542F\u4FDD\u6D3B\uFF08\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F\uFF09
|
|
5080
|
-
mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)
|
|
5081
|
-
mihomo ui # \u6253\u5F00 Web UI
|
|
5082
|
-
|
|
5083
|
-
${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}
|
|
5084
|
-
mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)
|
|
5085
|
-
tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo
|
|
5086
|
-
|
|
5087
|
-
${colors.cyan("\u6570\u636E\u76EE\u5F55:")}
|
|
5088
|
-
\u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E
|
|
5089
|
-
\u9ED8\u8BA4: ${USER_DATA_DIR}
|
|
5090
|
-
`
|
|
5091
|
-
);
|
|
5092
|
-
}
|
|
5093
|
-
function printVersion() {
|
|
5094
|
-
const kv = getKernelVersion() || "\u672A\u5B89\u88C5";
|
|
5095
|
-
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
5096
|
-
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
5097
|
-
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
5098
|
-
}
|
|
5099
|
-
|
|
5100
5421
|
// src/kernel.ts
|
|
5101
5422
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5102
5423
|
import fs7 from "fs";
|
|
5103
|
-
import
|
|
5424
|
+
import path6 from "path";
|
|
5104
5425
|
|
|
5105
5426
|
// node_modules/compare-versions/lib/esm/utils.js
|
|
5106
5427
|
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
@@ -5185,7 +5506,7 @@ function findMatchingAsset(assets, platform, arch) {
|
|
|
5185
5506
|
const nameWithoutGz = a.name.slice(0, -3);
|
|
5186
5507
|
const parts = nameWithoutGz.split("-");
|
|
5187
5508
|
const lastPart = parts[parts.length - 1];
|
|
5188
|
-
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go");
|
|
5509
|
+
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go") && !nameWithoutGz.includes("-compatible");
|
|
5189
5510
|
});
|
|
5190
5511
|
return standardAsset || matchingAssets[0];
|
|
5191
5512
|
}
|
|
@@ -5228,7 +5549,7 @@ function findBinaryInDir(dir, maxDepth = 4) {
|
|
|
5228
5549
|
if (maxDepth <= 0) return null;
|
|
5229
5550
|
const files = fs7.readdirSync(dir);
|
|
5230
5551
|
for (const f of files) {
|
|
5231
|
-
const fullPath =
|
|
5552
|
+
const fullPath = path6.join(dir, f);
|
|
5232
5553
|
const stat = fs7.statSync(fullPath);
|
|
5233
5554
|
if (stat.isDirectory()) {
|
|
5234
5555
|
const found = findBinaryInDir(fullPath, maxDepth - 1);
|
|
@@ -5255,7 +5576,7 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5255
5576
|
\u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
|
|
5256
5577
|
}
|
|
5257
5578
|
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
5258
|
-
const tempPath =
|
|
5579
|
+
const tempPath = path6.join(DIRS.kernel, asset.name);
|
|
5259
5580
|
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
5260
5581
|
if (mirror && progressCallback) {
|
|
5261
5582
|
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");
|
|
@@ -5268,6 +5589,12 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5268
5589
|
["-L", "--progress-bar", "--connect-timeout", "30", "--max-time", String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1e3)), "-o", tempPath, downloadUrl],
|
|
5269
5590
|
{ stdio: "inherit" }
|
|
5270
5591
|
);
|
|
5592
|
+
if (curlResult.error) {
|
|
5593
|
+
if (curlResult.error.code === "ENOENT") {
|
|
5594
|
+
throw new Error("\u672A\u627E\u5230 curl \u547D\u4EE4\uFF0C\u8BF7\u5148\u5B89\u88C5 curl \u540E\u91CD\u8BD5");
|
|
5595
|
+
}
|
|
5596
|
+
throw new Error(`\u4E0B\u8F7D\u5931\u8D25: ${curlResult.error.message}`);
|
|
5597
|
+
}
|
|
5271
5598
|
if (curlResult.status !== 0) {
|
|
5272
5599
|
try {
|
|
5273
5600
|
fs7.unlinkSync(tempPath);
|
|
@@ -5285,12 +5612,21 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5285
5612
|
let extractedBinary = null;
|
|
5286
5613
|
try {
|
|
5287
5614
|
if (tempPath.endsWith(".tar.gz") || tempPath.endsWith(".tgz")) {
|
|
5615
|
+
const listResult = spawnSync5("tar", ["-tzf", tempPath], { encoding: "utf8", timeout: 6e4 });
|
|
5616
|
+
if (listResult.error) throw listResult.error;
|
|
5617
|
+
if (listResult.status !== 0) throw new Error(`tar \u5217\u8868\u9000\u51FA\u7801 ${listResult.status}`);
|
|
5618
|
+
const entries = (listResult.stdout || "").split("\n").filter(Boolean);
|
|
5619
|
+
for (const entry of entries) {
|
|
5620
|
+
if (entry.startsWith("/") || entry.split("/").includes("..")) {
|
|
5621
|
+
throw new Error(`\u5F52\u6863\u542B\u975E\u6CD5\u8DEF\u5F84\u6761\u76EE: ${entry}`);
|
|
5622
|
+
}
|
|
5623
|
+
}
|
|
5288
5624
|
const tarResult = spawnSync5("tar", ["-xzf", tempPath, "-C", extractPath], { stdio: ["ignore", "ignore", "inherit"], timeout: 6e4 });
|
|
5289
5625
|
if (tarResult.error) throw tarResult.error;
|
|
5290
5626
|
if (tarResult.status !== 0) throw new Error(`tar \u9000\u51FA\u7801 ${tarResult.status}`);
|
|
5291
5627
|
} else if (tempPath.endsWith(".gz")) {
|
|
5292
|
-
const baseName =
|
|
5293
|
-
const outputPath =
|
|
5628
|
+
const baseName = path6.basename(tempPath, ".gz");
|
|
5629
|
+
const outputPath = path6.join(extractPath, baseName);
|
|
5294
5630
|
const gzipResult = spawnSync5("gzip", ["-dc", tempPath], { maxBuffer: 256 * 1024 * 1024, timeout: 6e4 });
|
|
5295
5631
|
if (gzipResult.error) throw gzipResult.error;
|
|
5296
5632
|
if (gzipResult.status !== 0) throw new Error(`gzip \u9000\u51FA\u7801 ${gzipResult.status}`);
|
|
@@ -5357,20 +5693,8 @@ async function cmdKernel(args) {
|
|
|
5357
5693
|
if (effectiveMirror) {
|
|
5358
5694
|
const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
|
|
5359
5695
|
console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
|
|
5696
|
+
console.log("");
|
|
5360
5697
|
}
|
|
5361
|
-
console.log("\n\u63D0\u793A: \u5982\u679C\u4E0B\u8F7D\u901F\u5EA6\u8FC7\u6162\u6216\u76F4\u8FDE\u5931\u8D25\uFF0C\u53EF\u4F7F\u7528 --mirror \u53C2\u6570\u901A\u8FC7\u955C\u50CF\u4E0B\u8F7D");
|
|
5362
|
-
console.log("\n\u7528\u6CD5:");
|
|
5363
|
-
console.log(" mihomo kernel # \u76F4\u8FDE");
|
|
5364
|
-
console.log(" mihomo kernel --mirror # \u4E0B\u8F7D\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF (v6.gh-proxy.org)");
|
|
5365
|
-
console.log(" mihomo kernel --mirror hk.gh-proxy.org # \u4E0B\u8F7D\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5366
|
-
console.log(" mihomo kernel --mirror-all # API\u8BF7\u6C42\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF");
|
|
5367
|
-
console.log(" mihomo kernel --mirror-all hk.gh-proxy.org # API\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5368
|
-
console.log("\n\u53EF\u7528\u955C\u50CF:");
|
|
5369
|
-
for (const m of AVAILABLE_MIRRORS) {
|
|
5370
|
-
const isCurrent = effectiveMirror && (effectiveMirror.includes(`//${m}/`) || effectiveMirror.includes(`//${m}:`) || effectiveMirror.endsWith(`//${m}`));
|
|
5371
|
-
console.log(` ${m}${isCurrent ? " (\u5F53\u524D)" : ""}`);
|
|
5372
|
-
}
|
|
5373
|
-
console.log("");
|
|
5374
5698
|
console.log("\u68C0\u67E5\u5185\u6838\u66F4\u65B0...");
|
|
5375
5699
|
try {
|
|
5376
5700
|
const apiMirror = mirrorInfo.type === "all" ? effectiveMirror : null;
|
|
@@ -5397,6 +5721,13 @@ async function cmdKernel(args) {
|
|
|
5397
5721
|
console.error(`\u6587\u6863: ${err.response.data.documentation_url}`);
|
|
5398
5722
|
}
|
|
5399
5723
|
}
|
|
5724
|
+
if (!effectiveMirror) {
|
|
5725
|
+
console.error("");
|
|
5726
|
+
console.error("\u63D0\u793A: \u76F4\u8FDE\u5931\u8D25\u6216\u4E0B\u8F7D\u8FC7\u6162\u65F6\u53EF\u4F7F\u7528\u955C\u50CF:");
|
|
5727
|
+
console.error(" mihomo kernel --mirror [\u955C\u50CF] # \u4E0B\u8F7D\u8D70\u955C\u50CF\uFF08\u9ED8\u8BA4 v6.gh-proxy.org\uFF09");
|
|
5728
|
+
console.error(" mihomo kernel --mirror-all [\u955C\u50CF] # API \u548C\u4E0B\u8F7D\u90FD\u8D70\u955C\u50CF");
|
|
5729
|
+
console.error(` \u53EF\u7528\u955C\u50CF: ${AVAILABLE_MIRRORS.join(", ")}`);
|
|
5730
|
+
}
|
|
5400
5731
|
process.exit(1);
|
|
5401
5732
|
}
|
|
5402
5733
|
}
|
|
@@ -5486,19 +5817,116 @@ function cmdLogs(args) {
|
|
|
5486
5817
|
// src/commands/overwrite.ts
|
|
5487
5818
|
import path7 from "path";
|
|
5488
5819
|
|
|
5820
|
+
// src/runtime.ts
|
|
5821
|
+
function getRuntimeMode() {
|
|
5822
|
+
if (isDaemonEnabled()) return "mixed";
|
|
5823
|
+
return getConfigInfo()?.tun ? "tun" : "mixed";
|
|
5824
|
+
}
|
|
5825
|
+
function getRunningState() {
|
|
5826
|
+
if (isDaemonEnabled()) {
|
|
5827
|
+
const daemon = getDaemonStatus();
|
|
5828
|
+
return { running: isDaemonRunning(daemon), pid: daemon.pid, daemon: true };
|
|
5829
|
+
}
|
|
5830
|
+
const status = getStatus();
|
|
5831
|
+
return { running: status.running, pid: status.pid, daemon: false };
|
|
5832
|
+
}
|
|
5833
|
+
function isRestartNeededOnChange() {
|
|
5834
|
+
return isDaemonEnabled() || getStatus().running;
|
|
5835
|
+
}
|
|
5836
|
+
async function launchOrRestart(mode) {
|
|
5837
|
+
if (isDaemonEnabled()) {
|
|
5838
|
+
await restartDaemon();
|
|
5839
|
+
await sleep(DAEMON_BOOT_WAIT_MS);
|
|
5840
|
+
return getDaemonStatus().pid;
|
|
5841
|
+
}
|
|
5842
|
+
const result = await start(mode);
|
|
5843
|
+
return result.pid;
|
|
5844
|
+
}
|
|
5845
|
+
|
|
5846
|
+
// src/progress.ts
|
|
5847
|
+
var IS_TTY = process.stdout.isTTY === true;
|
|
5848
|
+
var BAR_WIDTH = 20;
|
|
5849
|
+
function createProgressPrinter(totalRounds = 1) {
|
|
5850
|
+
let alive = 0;
|
|
5851
|
+
let dead = 0;
|
|
5852
|
+
const resultMap = /* @__PURE__ */ new Map();
|
|
5853
|
+
function render(done, total) {
|
|
5854
|
+
if (!IS_TTY) return;
|
|
5855
|
+
const pct = Math.round(done / total * 100);
|
|
5856
|
+
const filled = Math.round(done / total * BAR_WIDTH);
|
|
5857
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
|
|
5858
|
+
process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
|
|
5859
|
+
}
|
|
5860
|
+
return {
|
|
5861
|
+
onResult(result, index, total, round = 1) {
|
|
5862
|
+
if (resultMap.size === 0 && totalRounds > 1) {
|
|
5863
|
+
console.log(`--- \u7B2C 1 \u8F6E\u6D4B\u8BD5 (${total} \u4E2A\u8282\u70B9) ---`);
|
|
5864
|
+
}
|
|
5865
|
+
const prev = resultMap.get(result.name);
|
|
5866
|
+
if (prev) {
|
|
5867
|
+
if (prev.result.delay !== null) alive--;
|
|
5868
|
+
else dead--;
|
|
5869
|
+
}
|
|
5870
|
+
if (result.delay !== null) alive++;
|
|
5871
|
+
else dead++;
|
|
5872
|
+
resultMap.set(result.name, { result, round });
|
|
5873
|
+
render(index + 1, total);
|
|
5874
|
+
},
|
|
5875
|
+
onRetryRound(round, count) {
|
|
5876
|
+
if (IS_TTY) {
|
|
5877
|
+
process.stdout.write("\n");
|
|
5878
|
+
}
|
|
5879
|
+
console.log(`--- \u7B2C ${round} \u8F6E\u91CD\u8BD5 (${count} \u4E2A\u8282\u70B9) ---`);
|
|
5880
|
+
alive = 0;
|
|
5881
|
+
dead = 0;
|
|
5882
|
+
},
|
|
5883
|
+
finish() {
|
|
5884
|
+
if (IS_TTY) {
|
|
5885
|
+
process.stdout.write("\n");
|
|
5886
|
+
}
|
|
5887
|
+
console.log("");
|
|
5888
|
+
if (!IS_TTY) return;
|
|
5889
|
+
const entries = [...resultMap.values()];
|
|
5890
|
+
entries.sort((a, b) => a.result.name.localeCompare(b.result.name));
|
|
5891
|
+
const total = entries.length;
|
|
5892
|
+
console.log("\u8282\u70B9\u6700\u7EC8\u72B6\u6001:");
|
|
5893
|
+
for (let i = 0; i < entries.length; i++) {
|
|
5894
|
+
const { result, round } = entries[i];
|
|
5895
|
+
const prefix = `[${i + 1}/${total}]`;
|
|
5896
|
+
if (result.delay !== null) {
|
|
5897
|
+
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
5898
|
+
const retryNote = round > 1 ? colors.gray(` (\u7B2C${round}\u8F6E\u901A\u8FC7)`) : "";
|
|
5899
|
+
console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}${retryNote}`);
|
|
5900
|
+
} else {
|
|
5901
|
+
console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
5902
|
+
}
|
|
5903
|
+
}
|
|
5904
|
+
console.log("");
|
|
5905
|
+
}
|
|
5906
|
+
};
|
|
5907
|
+
}
|
|
5908
|
+
function formatCleanSummary(result) {
|
|
5909
|
+
const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
|
|
5910
|
+
if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
|
|
5911
|
+
if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
|
|
5912
|
+
return parts.join(", ");
|
|
5913
|
+
}
|
|
5914
|
+
function formatTestSummary(summary) {
|
|
5915
|
+
return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
|
|
5916
|
+
}
|
|
5917
|
+
|
|
5489
5918
|
// src/commands/status.ts
|
|
5490
5919
|
function printStatus() {
|
|
5491
5920
|
const status = getStatus();
|
|
5492
|
-
const
|
|
5921
|
+
const state = getRunningState();
|
|
5493
5922
|
const info = getConfigInfo();
|
|
5494
5923
|
const overwriteEnabled = isOverwriteEnabled();
|
|
5495
5924
|
const overwriteFiles = listOverwriteFile().files;
|
|
5496
5925
|
const activeSub = getActiveSubscription();
|
|
5497
|
-
const running
|
|
5498
|
-
const pid = daemon.enabled ? daemon.pid : status.pid;
|
|
5926
|
+
const { running, pid, daemon: daemonManaged } = state;
|
|
5499
5927
|
console.log("");
|
|
5500
5928
|
let modeLabel = "";
|
|
5501
|
-
if (info
|
|
5929
|
+
if (info) {
|
|
5502
5930
|
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
5503
5931
|
}
|
|
5504
5932
|
const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
|
|
@@ -5506,12 +5934,15 @@ function printStatus() {
|
|
|
5506
5934
|
console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
|
|
5507
5935
|
if (pid) {
|
|
5508
5936
|
console.log(`${colors.gray("PID: ")}${pid}`);
|
|
5509
|
-
if (!
|
|
5937
|
+
if (!daemonManaged && status.processInfo) {
|
|
5510
5938
|
console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
|
|
5511
5939
|
}
|
|
5512
5940
|
}
|
|
5513
5941
|
if (info) {
|
|
5514
|
-
if (info.
|
|
5942
|
+
if (info.tun) {
|
|
5943
|
+
const extra = info.mixedPort ? `\uFF0C\u53E6\u76D1\u542C ${info.mixedPort}` : "";
|
|
5944
|
+
console.log(`${colors.gray("\u7AEF\u53E3: ")}TUN \u63A5\u7BA1${extra}`);
|
|
5945
|
+
} else if (info.mixedPort) {
|
|
5515
5946
|
console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
|
|
5516
5947
|
} else {
|
|
5517
5948
|
const ports = [];
|
|
@@ -5537,7 +5968,7 @@ function printStatus() {
|
|
|
5537
5968
|
} else {
|
|
5538
5969
|
console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
|
|
5539
5970
|
}
|
|
5540
|
-
if (
|
|
5971
|
+
if (isDaemonEnabled()) {
|
|
5541
5972
|
console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
|
|
5542
5973
|
}
|
|
5543
5974
|
console.log("");
|
|
@@ -5557,7 +5988,7 @@ async function cmdStop() {
|
|
|
5557
5988
|
console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
|
|
5558
5989
|
return;
|
|
5559
5990
|
}
|
|
5560
|
-
const pids =
|
|
5991
|
+
const pids = getMihomoPids();
|
|
5561
5992
|
if (pids.length === 0) {
|
|
5562
5993
|
console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
|
|
5563
5994
|
return;
|
|
@@ -5567,915 +5998,859 @@ async function cmdStop() {
|
|
|
5567
5998
|
console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
|
|
5568
5999
|
}
|
|
5569
6000
|
|
|
5570
|
-
// src/
|
|
5571
|
-
|
|
5572
|
-
|
|
5573
|
-
|
|
5574
|
-
|
|
5575
|
-
// src/lifecycle.ts
|
|
5576
|
-
var cleanupFns = /* @__PURE__ */ new Set();
|
|
5577
|
-
function registerCleanup(fn) {
|
|
5578
|
-
cleanupFns.add(fn);
|
|
5579
|
-
return () => {
|
|
5580
|
-
cleanupFns.delete(fn);
|
|
5581
|
-
};
|
|
5582
|
-
}
|
|
5583
|
-
function runCleanup() {
|
|
5584
|
-
for (const fn of cleanupFns) {
|
|
5585
|
-
try {
|
|
5586
|
-
fn();
|
|
5587
|
-
} catch {
|
|
5588
|
-
}
|
|
6001
|
+
// src/commands/start.ts
|
|
6002
|
+
async function cmdStart(args) {
|
|
6003
|
+
if (!hasKernel()) {
|
|
6004
|
+
console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
6005
|
+
process.exit(1);
|
|
5589
6006
|
}
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5593
|
-
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
data: path6.join(TEST_DIR, "data"),
|
|
5597
|
-
runtime: path6.join(TEST_DIR, "runtime")
|
|
5598
|
-
};
|
|
5599
|
-
var TEST_PATHS = {
|
|
5600
|
-
configFile: path6.join(TEST_DIRS.runtime, "config.yaml"),
|
|
5601
|
-
pidFile: path6.join(TEST_DIRS.runtime, "pid"),
|
|
5602
|
-
logFile: path6.join(TEST_DIR, "test.log")
|
|
5603
|
-
};
|
|
5604
|
-
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
5605
|
-
function ensureTestDirs() {
|
|
5606
|
-
for (const dir of Object.values(TEST_DIRS)) {
|
|
5607
|
-
fs8.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
6007
|
+
const targetMode = args[1] === "tun" ? "tun" : "mixed";
|
|
6008
|
+
const daemonEnabled = isDaemonEnabled();
|
|
6009
|
+
if (targetMode === "tun" && daemonEnabled) {
|
|
6010
|
+
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`);
|
|
6011
|
+
console.error("\u8BF7\u5148\u5173\u95ED\u4FDD\u6D3B: mihomo daemon off");
|
|
6012
|
+
process.exit(1);
|
|
5608
6013
|
}
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
const
|
|
5616
|
-
if (!
|
|
5617
|
-
|
|
6014
|
+
const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
|
|
6015
|
+
const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
|
|
6016
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
|
|
6017
|
+
const skipUpdate = hasFlag(args, "-s", "--no-update");
|
|
6018
|
+
const skipClean = hasFlag(args, "--no-clean");
|
|
6019
|
+
const updateTimeout = parseIntArg(args, "-u", "--update-timeout", DEFAULT_AUTO_UPDATE_TIMEOUT);
|
|
6020
|
+
const sub = getActiveSubscription();
|
|
6021
|
+
if (!sub) {
|
|
6022
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
6023
|
+
process.exit(1);
|
|
5618
6024
|
}
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
if (proxies.length === 0) {
|
|
5622
|
-
throw new Error(`\u8BA2\u9605 "${subName}" \u6CA1\u6709\u6709\u6548\u8282\u70B9`);
|
|
6025
|
+
if (!skipUpdate) {
|
|
6026
|
+
await autoUpdateStaleSubscription({ timeout: updateTimeout });
|
|
5623
6027
|
}
|
|
5624
|
-
|
|
5625
|
-
|
|
5626
|
-
|
|
5627
|
-
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
}
|
|
5631
|
-
}
|
|
5632
|
-
const config = {
|
|
5633
|
-
...TEST_CONFIG,
|
|
5634
|
-
proxies,
|
|
5635
|
-
"proxy-groups": [
|
|
5636
|
-
{
|
|
5637
|
-
name: "PROXY",
|
|
5638
|
-
type: "select",
|
|
5639
|
-
proxies: proxies.map((p) => p.name)
|
|
5640
|
-
}
|
|
5641
|
-
],
|
|
5642
|
-
rules: ["MATCH,PROXY"]
|
|
5643
|
-
};
|
|
5644
|
-
const content = dump(config, { indent: 2, lineWidth: -1, schema: CORE_SCHEMA });
|
|
5645
|
-
fs8.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
5646
|
-
}
|
|
5647
|
-
async function startTestInstance() {
|
|
5648
|
-
const binary = PATHS.mihomoBinary;
|
|
5649
|
-
if (!fs8.existsSync(binary)) throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838");
|
|
5650
|
-
stopTestInstance();
|
|
5651
|
-
const logFd = fs8.openSync(TEST_PATHS.logFile, "a");
|
|
5652
|
-
const child = spawn2(binary, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
|
|
5653
|
-
detached: true,
|
|
5654
|
-
stdio: ["ignore", logFd, logFd]
|
|
5655
|
-
});
|
|
5656
|
-
fs8.closeSync(logFd);
|
|
5657
|
-
child.unref();
|
|
5658
|
-
const pid = child.pid;
|
|
5659
|
-
fs8.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
5660
|
-
const client = createHttpClient({ timeout: 2e3 });
|
|
5661
|
-
let ready = false;
|
|
5662
|
-
for (let i = 0; i < 60; i++) {
|
|
5663
|
-
if (!isProcessRunning(pid)) break;
|
|
5664
|
-
try {
|
|
5665
|
-
await client.get(`${TEST_API}/version`);
|
|
5666
|
-
ready = true;
|
|
5667
|
-
break;
|
|
5668
|
-
} catch {
|
|
5669
|
-
await sleep(500);
|
|
6028
|
+
if (!daemonEnabled) {
|
|
6029
|
+
if (hasRootResidue()) {
|
|
6030
|
+
console.error(`${colors.red("\u9519\u8BEF:")} \u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6`);
|
|
6031
|
+
console.error(`\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo && sudo rm -f ${PATHS.pidFile}`);
|
|
6032
|
+
console.error("\u6216\u5207\u6362\u5230 TUN \u6A21\u5F0F\u542F\u52A8\uFF08\u81EA\u52A8\u6E05\u7406\uFF09: mihomo start tun");
|
|
6033
|
+
process.exit(1);
|
|
5670
6034
|
}
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
} catch {
|
|
6035
|
+
const status = getStatus();
|
|
6036
|
+
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
6037
|
+
if (hasProcess) {
|
|
6038
|
+
const count = status.allProcesses.length > 0 ? status.allProcesses.length : 1;
|
|
6039
|
+
console.log(`\u505C\u6B62 ${count} \u4E2A\u8FDB\u7A0B...`);
|
|
5677
6040
|
}
|
|
5678
|
-
|
|
5679
|
-
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
5683
|
-
}
|
|
5684
|
-
}
|
|
5685
|
-
function stopTestInstance() {
|
|
5686
|
-
let pid;
|
|
5687
|
-
try {
|
|
5688
|
-
pid = parseInt(fs8.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
|
|
5689
|
-
} catch {
|
|
5690
|
-
return;
|
|
5691
|
-
}
|
|
5692
|
-
if (pid > 0 && isProcessRunning(pid)) {
|
|
5693
|
-
process.kill(pid, "SIGKILL");
|
|
5694
|
-
for (let i = 0; i < 20; i++) {
|
|
5695
|
-
if (!isProcessRunning(pid)) break;
|
|
5696
|
-
sleepSync(100);
|
|
6041
|
+
handleStopResult(stop());
|
|
6042
|
+
if (hasProcess) {
|
|
6043
|
+
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
6044
|
+
`);
|
|
5697
6045
|
}
|
|
5698
6046
|
}
|
|
6047
|
+
let configInfo;
|
|
5699
6048
|
try {
|
|
5700
|
-
|
|
5701
|
-
} catch {
|
|
6049
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
6050
|
+
} catch (e) {
|
|
6051
|
+
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
6052
|
+
process.exit(1);
|
|
5702
6053
|
}
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
cleanupTestDir();
|
|
5706
|
-
buildTestConfig(subName);
|
|
5707
|
-
const unregister = registerCleanup(() => {
|
|
5708
|
-
stopTestInstance();
|
|
5709
|
-
cleanupTestDir();
|
|
5710
|
-
});
|
|
6054
|
+
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
6055
|
+
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
5711
6056
|
try {
|
|
5712
|
-
await
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
var BAR_WIDTH = 20;
|
|
5724
|
-
function createProgressPrinter(totalRounds = 1) {
|
|
5725
|
-
let alive = 0;
|
|
5726
|
-
let dead = 0;
|
|
5727
|
-
const resultMap = /* @__PURE__ */ new Map();
|
|
5728
|
-
function render(done, total) {
|
|
5729
|
-
if (!IS_TTY) return;
|
|
5730
|
-
const pct = Math.round(done / total * 100);
|
|
5731
|
-
const filled = Math.round(done / total * BAR_WIDTH);
|
|
5732
|
-
const bar = "\u2588".repeat(filled) + "\u2591".repeat(BAR_WIDTH - filled);
|
|
5733
|
-
process.stdout.write(`\r${bar} ${done}/${total} (${pct}%) | ${colors.green(`\u2713${alive}`)} ${colors.red(`\u2717${dead}`)}`);
|
|
6057
|
+
const pid = await launchOrRestart(targetMode);
|
|
6058
|
+
const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
|
|
6059
|
+
console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
|
|
6060
|
+
} catch (e) {
|
|
6061
|
+
const msg = e.message;
|
|
6062
|
+
const lines = msg.split("\n");
|
|
6063
|
+
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
|
|
6064
|
+
if (lines.length > 1) {
|
|
6065
|
+
for (const line of lines.slice(1)) console.error(line);
|
|
6066
|
+
}
|
|
6067
|
+
process.exit(1);
|
|
5734
6068
|
}
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
else dead++;
|
|
5742
|
-
resultMap.set(result.name, { result, round });
|
|
5743
|
-
render(index + 1, total);
|
|
5744
|
-
},
|
|
5745
|
-
onRetryRound(round, count) {
|
|
5746
|
-
if (IS_TTY) {
|
|
5747
|
-
process.stdout.write("\n");
|
|
5748
|
-
}
|
|
5749
|
-
console.log(`--- \u7B2C ${round} \u8F6E\u91CD\u8BD5 (${count} \u4E2A\u8282\u70B9) ---`);
|
|
5750
|
-
alive = 0;
|
|
5751
|
-
dead = 0;
|
|
5752
|
-
},
|
|
5753
|
-
finish() {
|
|
5754
|
-
if (IS_TTY) {
|
|
5755
|
-
process.stdout.write("\n");
|
|
5756
|
-
}
|
|
6069
|
+
const cleanThreshold = isGithubUrl(sub.url) ? AUTO_CLEAN_THRESHOLD_GITHUB : AUTO_CLEAN_THRESHOLD;
|
|
6070
|
+
if (!skipClean && configInfo.proxies > cleanThreshold) {
|
|
6071
|
+
const cache = readSubscriptionCache();
|
|
6072
|
+
const lastCleanAt = cache[sub.name]?.last_auto_clean_at;
|
|
6073
|
+
const withinCooldown = !!lastCleanAt && Date.now() - new Date(lastCleanAt).getTime() < AUTO_CLEAN_COOLDOWN_HOURS * 60 * 60 * 1e3;
|
|
6074
|
+
if (!withinCooldown) {
|
|
5757
6075
|
console.log("");
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
6076
|
+
console.log(`\u8282\u70B9\u6570 ${configInfo.proxies} \u8D85\u8FC7 ${cleanThreshold}\uFF0C\u81EA\u52A8\u6E05\u7406\uFF08${AUTO_CLEAN_COOLDOWN_HOURS}h \u5185\u4EC5\u4E00\u6B21\uFF0C--no-clean \u8DF3\u8FC7\uFF09...`);
|
|
6077
|
+
console.log("");
|
|
6078
|
+
await sleep(1e3);
|
|
6079
|
+
const progress = createProgressPrinter(rounds);
|
|
6080
|
+
const cleanResult = await autoCleanSubscription(sub.name, {
|
|
6081
|
+
timeout,
|
|
6082
|
+
concurrency,
|
|
6083
|
+
rounds,
|
|
6084
|
+
onResult: progress.onResult,
|
|
6085
|
+
onRetryRound: progress.onRetryRound
|
|
6086
|
+
});
|
|
6087
|
+
progress.finish();
|
|
6088
|
+
console.log(formatTestSummary(cleanResult.summary));
|
|
6089
|
+
if (cleanResult.skipped) {
|
|
6090
|
+
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"));
|
|
6091
|
+
} else if (cleanResult.removedProxies > 0) {
|
|
6092
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
6093
|
+
console.log("");
|
|
6094
|
+
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
6095
|
+
if (!daemonEnabled) handleStopResult(stop());
|
|
6096
|
+
try {
|
|
6097
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
6098
|
+
const pid = await launchOrRestart(targetMode);
|
|
6099
|
+
console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
|
|
6100
|
+
} catch (e) {
|
|
6101
|
+
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
6102
|
+
process.exit(1);
|
|
5771
6103
|
}
|
|
5772
6104
|
}
|
|
5773
|
-
|
|
6105
|
+
saveSubscriptionCache(sub.name, { last_auto_clean_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
5774
6106
|
}
|
|
5775
|
-
};
|
|
5776
|
-
}
|
|
5777
|
-
function formatCleanSummary(result) {
|
|
5778
|
-
const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
|
|
5779
|
-
if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
|
|
5780
|
-
if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
|
|
5781
|
-
return parts.join(", ");
|
|
5782
|
-
}
|
|
5783
|
-
function formatTestSummary(summary) {
|
|
5784
|
-
return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
|
|
5785
|
-
}
|
|
5786
|
-
function githubRepoUrl(rawUrl) {
|
|
5787
|
-
const match = rawUrl.match(/raw\.githubusercontent\.com\/([^/]+\/[^/]+)/);
|
|
5788
|
-
if (match) return `https://github.com/${match[1]}`;
|
|
5789
|
-
return null;
|
|
5790
|
-
}
|
|
5791
|
-
function resolveTestTarget(args) {
|
|
5792
|
-
const subs = getSubscriptions();
|
|
5793
|
-
if (subs.length === 0) {
|
|
5794
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
5795
|
-
process.exit(1);
|
|
5796
|
-
}
|
|
5797
|
-
const nameArg = getNonFlagArg(args, 2);
|
|
5798
|
-
const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
|
|
5799
|
-
const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
|
|
5800
|
-
let target;
|
|
5801
|
-
if (nameArg) {
|
|
5802
|
-
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
5803
|
-
target = pickSingleSubscription(matches, nameArg);
|
|
5804
|
-
} else {
|
|
5805
|
-
const activeSub = getActiveSubscription();
|
|
5806
|
-
if (!activeSub) {
|
|
5807
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
5808
|
-
process.exit(1);
|
|
5809
|
-
}
|
|
5810
|
-
target = activeSub;
|
|
5811
6107
|
}
|
|
5812
|
-
|
|
6108
|
+
printStatus();
|
|
5813
6109
|
}
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
6110
|
+
|
|
6111
|
+
// src/commands/overwrite.ts
|
|
6112
|
+
function printOverwriteList() {
|
|
6113
|
+
const info = listOverwriteFile();
|
|
6114
|
+
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
6115
|
+
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}`);
|
|
6116
|
+
console.log(`${colors.gray("\u4F4D\u7F6E: ")}${info.dir}`);
|
|
6117
|
+
console.log("");
|
|
6118
|
+
if (info.files.length === 0) {
|
|
6119
|
+
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
5822
6120
|
console.log("");
|
|
5823
|
-
console.log(
|
|
6121
|
+
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path7.join(info.dir, "overwrite.yaml")}`);
|
|
6122
|
+
console.log(` \u6216 ${path7.join(info.dir, "overwrite.dns.yaml")}`);
|
|
5824
6123
|
console.log("");
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
const interval = resolveUpdateInterval(s.url, s.update_interval);
|
|
5834
|
-
console.log(` ${i + 1}. ${s.name}${defaultMark}${mergeBadge}`);
|
|
5835
|
-
console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
|
|
5836
|
-
if (s.username) {
|
|
5837
|
-
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
5838
|
-
}
|
|
5839
|
-
if (s.download !== void 0 || s.total !== void 0) {
|
|
5840
|
-
const used = (s.upload || 0) + (s.download || 0);
|
|
5841
|
-
const usedStr = formatBytes(used);
|
|
5842
|
-
const totalStr = formatBytes(s.total);
|
|
5843
|
-
let percentStr = "";
|
|
5844
|
-
if (s.total && s.total > 0) {
|
|
5845
|
-
const percent = Math.min(used / s.total * 100, 100);
|
|
5846
|
-
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
5847
|
-
}
|
|
5848
|
-
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
5849
|
-
}
|
|
5850
|
-
if (s.expire !== void 0) {
|
|
5851
|
-
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
5852
|
-
}
|
|
5853
|
-
if (s.web_page_url) {
|
|
5854
|
-
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
5855
|
-
}
|
|
5856
|
-
});
|
|
5857
|
-
console.log("");
|
|
5858
|
-
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
5859
|
-
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
5860
|
-
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
5861
|
-
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
5862
|
-
console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
|
|
5863
|
-
console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
|
|
5864
|
-
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
5865
|
-
console.log("");
|
|
5866
|
-
}
|
|
5867
|
-
async function cmdSubscription(args) {
|
|
5868
|
-
const action = args[1];
|
|
5869
|
-
if (!action || action === "list") {
|
|
5870
|
-
await printSubscriptionList();
|
|
5871
|
-
return;
|
|
5872
|
-
}
|
|
5873
|
-
if (action === "add") {
|
|
5874
|
-
const url = args[2];
|
|
5875
|
-
const name = args[3] || "default";
|
|
5876
|
-
if (!url) {
|
|
5877
|
-
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
5878
|
-
process.exit(1);
|
|
5879
|
-
}
|
|
5880
|
-
if (isMultiUrl(url)) {
|
|
5881
|
-
const urls = splitUrls(url);
|
|
5882
|
-
for (const u of urls) {
|
|
5883
|
-
if (!u.startsWith("http")) {
|
|
5884
|
-
console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
|
|
5885
|
-
process.exit(1);
|
|
5886
|
-
}
|
|
5887
|
-
}
|
|
5888
|
-
console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
|
|
5889
|
-
try {
|
|
5890
|
-
addSubscription(url, name);
|
|
5891
|
-
setDefaultSubscription(name);
|
|
5892
|
-
const info = await downloadMergedSubscription(urls, name);
|
|
5893
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
|
|
5894
|
-
} catch (e) {
|
|
5895
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
5896
|
-
process.exit(1);
|
|
5897
|
-
}
|
|
5898
|
-
} else {
|
|
5899
|
-
if (!url.startsWith("http")) {
|
|
5900
|
-
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
5901
|
-
process.exit(1);
|
|
6124
|
+
} else {
|
|
6125
|
+
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
6126
|
+
console.log("");
|
|
6127
|
+
info.files.forEach((f, i) => {
|
|
6128
|
+
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
6129
|
+
console.log(` ${num}. ${f.name}`);
|
|
6130
|
+
if (f.scope) {
|
|
6131
|
+
console.log(` ${colors.gray("\u4F5C\u7528\u57DF: ")}${f.scope}`);
|
|
5902
6132
|
}
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
addSubscription(url, name);
|
|
5906
|
-
setDefaultSubscription(name);
|
|
5907
|
-
const info = await downloadSubscription(url, name);
|
|
5908
|
-
const repoUrl = githubRepoUrl(url);
|
|
5909
|
-
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
5910
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
5911
|
-
} catch (e) {
|
|
5912
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
5913
|
-
process.exit(1);
|
|
6133
|
+
if (f.keys.length > 0) {
|
|
6134
|
+
console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
|
|
5914
6135
|
}
|
|
5915
|
-
}
|
|
6136
|
+
});
|
|
5916
6137
|
console.log("");
|
|
5917
|
-
await printSubscriptionList();
|
|
5918
|
-
return;
|
|
5919
6138
|
}
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
for (const r of results) {
|
|
5932
|
-
if (r.success) ok++;
|
|
5933
|
-
printUpdateResult(r);
|
|
5934
|
-
}
|
|
5935
|
-
if (ok === 0) process.exit(1);
|
|
6139
|
+
console.log("\u542F\u7528\u8986\u5199: mihomo ow on");
|
|
6140
|
+
console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
|
|
6141
|
+
console.log("");
|
|
6142
|
+
}
|
|
6143
|
+
async function cmdOverwrite(args) {
|
|
6144
|
+
const action = args?.[1];
|
|
6145
|
+
const currentMode = getRuntimeMode();
|
|
6146
|
+
const restartNeeded = isRestartNeededOnChange();
|
|
6147
|
+
if (action === "on" || action === "enable") {
|
|
6148
|
+
if (isOverwriteEnabled()) {
|
|
6149
|
+
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
|
|
5936
6150
|
console.log("");
|
|
5937
|
-
|
|
6151
|
+
printOverwriteList();
|
|
5938
6152
|
return;
|
|
5939
6153
|
}
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
process.exit(1);
|
|
6154
|
+
setOverwriteEnabled(true);
|
|
6155
|
+
console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
|
|
6156
|
+
if (restartNeeded) {
|
|
6157
|
+
console.log("");
|
|
6158
|
+
await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
|
|
6159
|
+
return;
|
|
5947
6160
|
}
|
|
5948
|
-
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
|
|
5949
6161
|
console.log("");
|
|
5950
|
-
|
|
6162
|
+
printOverwriteList();
|
|
5951
6163
|
return;
|
|
5952
6164
|
}
|
|
5953
|
-
if (action === "
|
|
5954
|
-
|
|
5955
|
-
|
|
5956
|
-
if (!name) {
|
|
5957
|
-
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
5958
|
-
if (subs.length > 0) {
|
|
5959
|
-
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
5960
|
-
for (const s of subs) console.log(` ${s.name}`);
|
|
5961
|
-
}
|
|
5962
|
-
process.exit(1);
|
|
5963
|
-
}
|
|
5964
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
5965
|
-
const target = pickSingleSubscription(matches, name);
|
|
5966
|
-
const currentDefault = getActiveSubscription();
|
|
5967
|
-
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
5968
|
-
if (isAlreadyDefault) {
|
|
5969
|
-
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
6165
|
+
if (action === "off" || action === "disable") {
|
|
6166
|
+
if (!isOverwriteEnabled()) {
|
|
6167
|
+
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
|
|
5970
6168
|
console.log("");
|
|
5971
|
-
|
|
6169
|
+
printOverwriteList();
|
|
5972
6170
|
return;
|
|
5973
6171
|
}
|
|
5974
|
-
|
|
5975
|
-
|
|
5976
|
-
|
|
5977
|
-
const success = setDefaultSubscription(target.name);
|
|
5978
|
-
if (success) {
|
|
5979
|
-
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
5980
|
-
} else {
|
|
5981
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
5982
|
-
process.exit(1);
|
|
5983
|
-
}
|
|
5984
|
-
if (status.running || isDaemonEnabled()) {
|
|
6172
|
+
setOverwriteEnabled(false);
|
|
6173
|
+
console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
|
|
6174
|
+
if (restartNeeded) {
|
|
5985
6175
|
console.log("");
|
|
5986
|
-
await cmdStart(["start", currentMode]);
|
|
6176
|
+
await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
|
|
5987
6177
|
return;
|
|
5988
6178
|
}
|
|
5989
6179
|
console.log("");
|
|
5990
|
-
|
|
6180
|
+
printOverwriteList();
|
|
5991
6181
|
return;
|
|
5992
6182
|
}
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
6183
|
+
console.log("");
|
|
6184
|
+
printOverwriteList();
|
|
6185
|
+
}
|
|
6186
|
+
|
|
6187
|
+
// src/commands/reset.ts
|
|
6188
|
+
import fs8 from "fs";
|
|
6189
|
+
import readline from "readline";
|
|
6190
|
+
var RESET_TARGETS = [
|
|
6191
|
+
{
|
|
6192
|
+
id: "subs",
|
|
6193
|
+
aliases: ["sub", "subs", "subscription", "subscriptions"],
|
|
6194
|
+
label: "\u8BA2\u9605",
|
|
6195
|
+
paths: () => [DIRS.subscriptions],
|
|
6196
|
+
needsStop: true,
|
|
6197
|
+
// 同步清空 settings 里的订阅列表:只删缓存文件会留下"列表存在但无配置"的半重置状态
|
|
6198
|
+
// (start 会报"未找到订阅配置")。active_subscription 一并清除
|
|
6199
|
+
onAfter: () => writeSettings({ subscriptions: void 0, active_subscription: void 0 })
|
|
6200
|
+
},
|
|
6201
|
+
{
|
|
6202
|
+
id: "logs",
|
|
6203
|
+
aliases: ["log", "logs"],
|
|
6204
|
+
label: "\u65E5\u5FD7",
|
|
6205
|
+
paths: () => [DIRS.logs],
|
|
6206
|
+
needsStop: false
|
|
6207
|
+
},
|
|
6208
|
+
{
|
|
6209
|
+
id: "data",
|
|
6210
|
+
aliases: ["data"],
|
|
6211
|
+
label: "\u8FD0\u884C\u6570\u636E",
|
|
6212
|
+
paths: () => [DIRS.data],
|
|
6213
|
+
needsStop: true
|
|
6214
|
+
},
|
|
6215
|
+
{
|
|
6216
|
+
id: "runtime",
|
|
6217
|
+
aliases: ["runtime"],
|
|
6218
|
+
label: "\u8FD0\u884C\u65F6",
|
|
6219
|
+
paths: () => [DIRS.runtime],
|
|
6220
|
+
needsStop: true
|
|
6221
|
+
},
|
|
6222
|
+
{
|
|
6223
|
+
id: "settings",
|
|
6224
|
+
aliases: ["setting", "settings", "config"],
|
|
6225
|
+
label: "\u8BBE\u7F6E",
|
|
6226
|
+
paths: () => [PATHS.settingsFile],
|
|
6227
|
+
needsStop: false
|
|
6228
|
+
},
|
|
6229
|
+
{
|
|
6230
|
+
id: "kernel",
|
|
6231
|
+
aliases: ["kernel", "core"],
|
|
6232
|
+
label: "\u5185\u6838",
|
|
6233
|
+
paths: () => [DIRS.kernel],
|
|
6234
|
+
needsStop: false,
|
|
6235
|
+
onAfter: () => clearKernelVersionCache(),
|
|
6236
|
+
checkEmpty: () => !hasKernel(),
|
|
6237
|
+
emptyMsg: "\u5185\u6838\u672A\u5B89\u88C5\uFF0C\u65E0\u9700\u5220\u9664",
|
|
6238
|
+
warnIfRunning: true
|
|
6239
|
+
},
|
|
6240
|
+
{
|
|
6241
|
+
id: "overwrites",
|
|
6242
|
+
aliases: ["overwrite", "overwrites", "ow"],
|
|
6243
|
+
label: "\u8986\u5199",
|
|
6244
|
+
paths: () => {
|
|
6245
|
+
const dir = USER_DATA_DIR;
|
|
6246
|
+
if (!fs8.existsSync(dir)) return [];
|
|
6247
|
+
return fs8.readdirSync(dir).filter(isOverwriteFilename).map((f) => `${dir}/${f}`);
|
|
6248
|
+
},
|
|
6249
|
+
needsStop: false
|
|
6250
|
+
},
|
|
6251
|
+
{
|
|
6252
|
+
id: "daemon",
|
|
6253
|
+
aliases: ["daemon"],
|
|
6254
|
+
label: "\u4FDD\u6D3B",
|
|
6255
|
+
// 卸载由确认后的 disablesDaemon 段统一处理(需 sudo,受取消保护);
|
|
6256
|
+
// 此处 paths 返回空(plist 在系统目录,用户态删不掉,且不应提前删破坏卸载),
|
|
6257
|
+
// onAfter 因幂等守卫(plist 已删)成为 no-op,仅作单独 reset 未走前段时的兜底。
|
|
6258
|
+
paths: () => [],
|
|
6259
|
+
needsStop: false,
|
|
6260
|
+
onAfter: () => disableDaemon(),
|
|
6261
|
+
checkEmpty: () => !isDaemonEnabled(),
|
|
6262
|
+
emptyMsg: "\u4FDD\u6D3B\u672A\u542F\u7528\uFF0C\u65E0\u9700\u5220\u9664"
|
|
6263
|
+
}
|
|
6264
|
+
];
|
|
6265
|
+
function resolveResetTargets(names) {
|
|
6266
|
+
const matched = [];
|
|
6267
|
+
const unmatched = [];
|
|
6268
|
+
for (const name of names) {
|
|
6269
|
+
const t = RESET_TARGETS.find((t2) => t2.aliases.includes(name.toLowerCase()));
|
|
6270
|
+
if (t) {
|
|
6271
|
+
if (!matched.find((m) => m.id === t.id)) matched.push(t);
|
|
6272
|
+
} else {
|
|
6273
|
+
unmatched.push(name);
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
return { matched, unmatched };
|
|
6277
|
+
}
|
|
6278
|
+
async function confirmPrompt(question) {
|
|
6279
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
6280
|
+
const answer = await new Promise((resolve) => {
|
|
6281
|
+
rl.question(`${question} (y/N) `, (a) => {
|
|
6282
|
+
rl.close();
|
|
6283
|
+
resolve(a);
|
|
6284
|
+
});
|
|
6285
|
+
});
|
|
6286
|
+
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
6287
|
+
}
|
|
6288
|
+
async function cmdReset(args) {
|
|
6289
|
+
const flags = (args || []).filter((a) => a.startsWith("-"));
|
|
6290
|
+
const names = (args || []).slice(1).filter((a) => !a.startsWith("-"));
|
|
6291
|
+
const KNOWN_FLAGS = /* @__PURE__ */ new Set(["--full", "--yes", "-y"]);
|
|
6292
|
+
const unknownFlags = flags.filter((f) => !KNOWN_FLAGS.has(f));
|
|
6293
|
+
if (unknownFlags.length > 0) {
|
|
6294
|
+
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u9009\u9879: ${unknownFlags.join(", ")}`);
|
|
6295
|
+
console.log("");
|
|
6296
|
+
console.log("\u53EF\u7528\u9009\u9879: --full\uFF08\u5220\u5168\u90E8\uFF09, -y/--yes\uFF08\u8DF3\u8FC7\u786E\u8BA4\uFF09");
|
|
6297
|
+
process.exit(1);
|
|
6298
|
+
}
|
|
6299
|
+
const fullReset = flags.includes("--full");
|
|
6300
|
+
const skipConfirm = flags.includes("--yes") || flags.includes("-y");
|
|
6301
|
+
let targets;
|
|
6302
|
+
if (fullReset) {
|
|
6303
|
+
targets = RESET_TARGETS;
|
|
6304
|
+
} else if (names.length > 0) {
|
|
6305
|
+
const { matched, unmatched } = resolveResetTargets(names);
|
|
6306
|
+
if (unmatched.length > 0) {
|
|
6307
|
+
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`);
|
|
6308
|
+
console.log("");
|
|
6309
|
+
console.log(`\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`);
|
|
6310
|
+
console.log("");
|
|
6311
|
+
console.log("\u793A\u4F8B:");
|
|
6312
|
+
console.log(" mihomo reset sub log # \u5220\u9664\u8BA2\u9605\u548C\u65E5\u5FD7");
|
|
6313
|
+
console.log(" mihomo reset kernel # \u53EA\u5220\u5185\u6838");
|
|
6314
|
+
console.log(" mihomo reset --full # \u5220\u9664\u5168\u90E8");
|
|
6315
|
+
console.log(" mihomo reset # \u5220\u9664\u5168\u90E8\uFF08\u4FDD\u7559\u8BBE\u7F6E\u3001\u5185\u6838\u3001\u8986\u5199\uFF09");
|
|
5998
6316
|
process.exit(1);
|
|
5999
6317
|
}
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6318
|
+
targets = matched;
|
|
6319
|
+
} else {
|
|
6320
|
+
targets = RESET_TARGETS.filter((t) => !["settings", "kernel", "overwrites", "daemon"].includes(t.id));
|
|
6321
|
+
}
|
|
6322
|
+
for (const t of targets) {
|
|
6323
|
+
if (t.checkEmpty?.()) {
|
|
6324
|
+
if (targets.length === 1) {
|
|
6325
|
+
console.log(t.emptyMsg);
|
|
6326
|
+
return;
|
|
6327
|
+
}
|
|
6006
6328
|
}
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
|
|
6010
|
-
|
|
6011
|
-
|
|
6012
|
-
|
|
6013
|
-
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6017
|
-
|
|
6018
|
-
|
|
6329
|
+
}
|
|
6330
|
+
const needsStop = targets.some((t) => t.needsStop);
|
|
6331
|
+
const warnRunning = targets.some((t) => t.warnIfRunning);
|
|
6332
|
+
const kernelTargeted = targets.some((t) => t.id === "kernel");
|
|
6333
|
+
const daemonTargeted = targets.some((t) => t.id === "daemon");
|
|
6334
|
+
const disablesDaemon = needsStop || kernelTargeted || daemonTargeted;
|
|
6335
|
+
const pids = needsStop || warnRunning ? getMihomoPids() : [];
|
|
6336
|
+
if (warnRunning && pids.length > 0) {
|
|
6337
|
+
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`));
|
|
6338
|
+
}
|
|
6339
|
+
if (disablesDaemon && isDaemonEnabled()) {
|
|
6340
|
+
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"));
|
|
6341
|
+
}
|
|
6342
|
+
console.log(`\u5C06\u5220\u9664: ${targets.map((t) => t.label).join("\u3001")}`);
|
|
6343
|
+
if (!skipConfirm && !await confirmPrompt("\u786E\u8BA4?")) {
|
|
6344
|
+
console.log("\u5DF2\u53D6\u6D88");
|
|
6345
|
+
return;
|
|
6346
|
+
}
|
|
6347
|
+
if (disablesDaemon && isDaemonEnabled()) {
|
|
6348
|
+
try {
|
|
6349
|
+
disableDaemon();
|
|
6350
|
+
} catch (e) {
|
|
6351
|
+
console.error(`${colors.red("\u4FDD\u6D3B\u5173\u95ED\u5DF2\u53D6\u6D88\uFF0C\u91CD\u7F6E\u4E2D\u6B62:")} ${e.message.split("\n")[0]}`);
|
|
6352
|
+
return;
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
if (needsStop && getMihomoPids().length > 0) {
|
|
6356
|
+
console.log("\u505C\u6B62\u8FDB\u7A0B...");
|
|
6357
|
+
cleanupAll();
|
|
6358
|
+
for (let i = 0; i < PROCESS_WAIT_ATTEMPTS; i++) {
|
|
6359
|
+
if (getMihomoPids().length === 0) break;
|
|
6360
|
+
await new Promise((r) => setTimeout(r, PROCESS_WAIT_INTERVAL));
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6363
|
+
for (const t of targets) {
|
|
6364
|
+
for (const p of t.paths()) {
|
|
6365
|
+
if (fs8.existsSync(p)) {
|
|
6366
|
+
try {
|
|
6367
|
+
rmrf(p);
|
|
6368
|
+
} catch (e) {
|
|
6369
|
+
console.warn(` \u8B66\u544A: \u65E0\u6CD5\u5220\u9664 ${p}: ${e.message}`);
|
|
6019
6370
|
}
|
|
6020
|
-
} catch (e) {
|
|
6021
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
6022
|
-
process.exit(1);
|
|
6023
6371
|
}
|
|
6024
6372
|
}
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6373
|
+
t.onAfter?.();
|
|
6374
|
+
}
|
|
6375
|
+
ensureDirs();
|
|
6376
|
+
if (targets.some((t) => t.id === "settings")) {
|
|
6377
|
+
invalidateSettingsCache();
|
|
6378
|
+
}
|
|
6379
|
+
console.log(colors.green(`\u5DF2\u91CD\u7F6E: ${targets.map((t) => t.label).join("\u3001")}`));
|
|
6380
|
+
}
|
|
6381
|
+
|
|
6382
|
+
// src/test-instance.ts
|
|
6383
|
+
import { spawn as spawn2 } from "child_process";
|
|
6384
|
+
import fs9 from "fs";
|
|
6385
|
+
import path8 from "path";
|
|
6386
|
+
var TEST_DIR = path8.join(USER_DATA_DIR, "test");
|
|
6387
|
+
var TEST_DIRS = {
|
|
6388
|
+
data: path8.join(TEST_DIR, "data"),
|
|
6389
|
+
runtime: path8.join(TEST_DIR, "runtime")
|
|
6390
|
+
};
|
|
6391
|
+
var TEST_PATHS = {
|
|
6392
|
+
configFile: path8.join(TEST_DIRS.runtime, "config.yaml"),
|
|
6393
|
+
pidFile: path8.join(TEST_DIRS.runtime, "pid"),
|
|
6394
|
+
logFile: path8.join(TEST_DIR, "test.log")
|
|
6395
|
+
};
|
|
6396
|
+
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
6397
|
+
function ensureTestDirs() {
|
|
6398
|
+
for (const dir of Object.values(TEST_DIRS)) {
|
|
6399
|
+
fs9.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
6400
|
+
}
|
|
6401
|
+
}
|
|
6402
|
+
function cleanupTestDir() {
|
|
6403
|
+
rmrf(TEST_DIR);
|
|
6404
|
+
}
|
|
6405
|
+
function buildTestConfig(subName) {
|
|
6406
|
+
ensureTestDirs();
|
|
6407
|
+
const rawContent = readSubscriptionRawConfig(subName);
|
|
6408
|
+
if (!rawContent) {
|
|
6409
|
+
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"`);
|
|
6410
|
+
}
|
|
6411
|
+
const parsed = parseYamlOrJson(rawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
6412
|
+
const proxies = (parsed.proxies || []).filter(isProxyValid);
|
|
6413
|
+
if (proxies.length === 0) {
|
|
6414
|
+
throw new Error(`\u8BA2\u9605 "${subName}" \u6CA1\u6709\u6709\u6548\u8282\u70B9`);
|
|
6415
|
+
}
|
|
6416
|
+
const nameCount = /* @__PURE__ */ new Map();
|
|
6417
|
+
for (const proxy of proxies) {
|
|
6418
|
+
const count = (nameCount.get(proxy.name) || 0) + 1;
|
|
6419
|
+
nameCount.set(proxy.name, count);
|
|
6420
|
+
if (count > 1) {
|
|
6421
|
+
proxy.name = `${proxy.name} #${count}`;
|
|
6029
6422
|
}
|
|
6030
|
-
return;
|
|
6031
6423
|
}
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6424
|
+
const config = {
|
|
6425
|
+
...TEST_CONFIG,
|
|
6426
|
+
proxies,
|
|
6427
|
+
"proxy-groups": [
|
|
6428
|
+
{
|
|
6429
|
+
name: "PROXY",
|
|
6430
|
+
type: "select",
|
|
6431
|
+
proxies: proxies.map((p) => p.name)
|
|
6040
6432
|
}
|
|
6041
|
-
|
|
6433
|
+
],
|
|
6434
|
+
rules: ["MATCH,PROXY"]
|
|
6435
|
+
};
|
|
6436
|
+
const content = dumpYaml(config);
|
|
6437
|
+
fs9.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
6438
|
+
}
|
|
6439
|
+
async function startTestInstance() {
|
|
6440
|
+
const binary = PATHS.mihomoBinary;
|
|
6441
|
+
if (!fs9.existsSync(binary)) throw new Error('\u672A\u627E\u5230 mihomo \u5185\u6838\uFF0C\u8BF7\u5148\u8FD0\u884C "mihomo kernel" \u4E0B\u8F7D');
|
|
6442
|
+
stopTestInstance();
|
|
6443
|
+
const logFd = fs9.openSync(TEST_PATHS.logFile, "a");
|
|
6444
|
+
const child = spawn2(binary, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
|
|
6445
|
+
detached: true,
|
|
6446
|
+
stdio: ["ignore", logFd, logFd]
|
|
6447
|
+
});
|
|
6448
|
+
child.on("error", () => {
|
|
6449
|
+
});
|
|
6450
|
+
fs9.closeSync(logFd);
|
|
6451
|
+
child.unref();
|
|
6452
|
+
const pid = child.pid;
|
|
6453
|
+
if (!pid) throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25\uFF1A\u65E0\u6CD5\u521B\u5EFA\u8FDB\u7A0B\uFF08\u5185\u6838\u4E8C\u8FDB\u5236\u53EF\u80FD\u4E0D\u53EF\u6267\u884C\uFF09");
|
|
6454
|
+
fs9.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
6455
|
+
const client = createHttpClient({ timeout: 2e3 });
|
|
6456
|
+
let ready = false;
|
|
6457
|
+
for (let i = 0; i < 60; i++) {
|
|
6458
|
+
if (!isProcessRunning(pid)) break;
|
|
6459
|
+
try {
|
|
6460
|
+
await client.get(`${TEST_API}/version`);
|
|
6461
|
+
ready = true;
|
|
6462
|
+
break;
|
|
6463
|
+
} catch {
|
|
6464
|
+
await sleep(500);
|
|
6042
6465
|
}
|
|
6043
|
-
|
|
6044
|
-
|
|
6045
|
-
|
|
6046
|
-
|
|
6047
|
-
|
|
6048
|
-
|
|
6466
|
+
}
|
|
6467
|
+
if (!isProcessRunning(pid)) {
|
|
6468
|
+
let errorDetail = "";
|
|
6469
|
+
try {
|
|
6470
|
+
errorDetail = fs9.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
|
|
6471
|
+
} catch {
|
|
6049
6472
|
}
|
|
6050
|
-
|
|
6051
|
-
|
|
6473
|
+
throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
6474
|
+
${errorDetail}` : ""}`);
|
|
6475
|
+
}
|
|
6476
|
+
if (!ready) {
|
|
6477
|
+
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
6478
|
+
}
|
|
6479
|
+
}
|
|
6480
|
+
function stopTestInstance() {
|
|
6481
|
+
let pid;
|
|
6482
|
+
try {
|
|
6483
|
+
pid = parseInt(fs9.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
|
|
6484
|
+
} catch {
|
|
6052
6485
|
return;
|
|
6053
6486
|
}
|
|
6054
|
-
if (
|
|
6055
|
-
|
|
6056
|
-
|
|
6057
|
-
|
|
6058
|
-
|
|
6059
|
-
console.log("");
|
|
6060
|
-
const progress = createProgressPrinter(rounds);
|
|
6061
|
-
const result = await withTestInstance(target.name, async (apiBase) => {
|
|
6062
|
-
return autoCleanSubscription(target.name, {
|
|
6063
|
-
timeout,
|
|
6064
|
-
concurrency,
|
|
6065
|
-
rounds,
|
|
6066
|
-
apiBase,
|
|
6067
|
-
onResult: progress.onResult,
|
|
6068
|
-
onRetryRound: progress.onRetryRound
|
|
6069
|
-
});
|
|
6070
|
-
});
|
|
6071
|
-
progress.finish();
|
|
6072
|
-
console.log(formatTestSummary(result.summary));
|
|
6073
|
-
if (result.skipped) {
|
|
6074
|
-
console.log("");
|
|
6075
|
-
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"));
|
|
6076
|
-
} else if (result.removedProxies > 0) {
|
|
6077
|
-
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6078
|
-
const status = getStatus();
|
|
6079
|
-
if (status.running) {
|
|
6080
|
-
console.log("");
|
|
6081
|
-
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
6082
|
-
}
|
|
6487
|
+
if (pid > 0 && isProcessRunning(pid)) {
|
|
6488
|
+
process.kill(pid, "SIGKILL");
|
|
6489
|
+
for (let i = 0; i < 20; i++) {
|
|
6490
|
+
if (!isProcessRunning(pid)) break;
|
|
6491
|
+
sleepSync(100);
|
|
6083
6492
|
}
|
|
6084
|
-
return;
|
|
6085
6493
|
}
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6090
|
-
console.log("");
|
|
6091
|
-
const progress = createProgressPrinter();
|
|
6092
|
-
const summary = await withTestInstance(target.name, async (apiBase) => {
|
|
6093
|
-
return testSubscriptionProxies(target.name, {
|
|
6094
|
-
timeout,
|
|
6095
|
-
concurrency,
|
|
6096
|
-
apiBase,
|
|
6097
|
-
onResult: progress.onResult
|
|
6098
|
-
});
|
|
6099
|
-
});
|
|
6100
|
-
progress.finish();
|
|
6101
|
-
console.log(formatTestSummary(summary));
|
|
6102
|
-
return;
|
|
6494
|
+
try {
|
|
6495
|
+
fs9.unlinkSync(TEST_PATHS.pidFile);
|
|
6496
|
+
} catch {
|
|
6103
6497
|
}
|
|
6104
|
-
console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
|
|
6105
|
-
console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
|
|
6106
|
-
process.exit(1);
|
|
6107
6498
|
}
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6499
|
+
async function withTestInstance(subName, fn) {
|
|
6500
|
+
cleanupTestDir();
|
|
6501
|
+
buildTestConfig(subName);
|
|
6502
|
+
const unregister = registerCleanup(() => {
|
|
6503
|
+
stopTestInstance();
|
|
6504
|
+
cleanupTestDir();
|
|
6505
|
+
});
|
|
6506
|
+
try {
|
|
6507
|
+
await startTestInstance();
|
|
6508
|
+
return await fn(TEST_API);
|
|
6509
|
+
} finally {
|
|
6510
|
+
unregister();
|
|
6511
|
+
stopTestInstance();
|
|
6512
|
+
cleanupTestDir();
|
|
6115
6513
|
}
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6514
|
+
}
|
|
6515
|
+
|
|
6516
|
+
// src/commands/subscription.ts
|
|
6517
|
+
function githubRepoUrl(rawUrl) {
|
|
6518
|
+
const match = rawUrl.match(/raw\.githubusercontent\.com\/([^/]+\/[^/]+)/);
|
|
6519
|
+
if (match) return `https://github.com/${match[1]}`;
|
|
6520
|
+
return null;
|
|
6521
|
+
}
|
|
6522
|
+
function resolveTestTarget(args) {
|
|
6523
|
+
const subs = getSubscriptions();
|
|
6524
|
+
if (subs.length === 0) {
|
|
6525
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
6121
6526
|
process.exit(1);
|
|
6122
6527
|
}
|
|
6123
|
-
const
|
|
6528
|
+
const nameArg = getNonFlagArg(args, 2);
|
|
6124
6529
|
const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
|
|
6125
6530
|
const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
|
|
6126
|
-
|
|
6127
|
-
|
|
6128
|
-
|
|
6129
|
-
|
|
6130
|
-
|
|
6131
|
-
|
|
6531
|
+
let target;
|
|
6532
|
+
if (nameArg) {
|
|
6533
|
+
const matches = findSubscriptionFuzzy(subs, nameArg);
|
|
6534
|
+
target = pickSingleSubscription(matches, nameArg);
|
|
6535
|
+
} else {
|
|
6536
|
+
const activeSub = getActiveSubscription();
|
|
6537
|
+
if (!activeSub) {
|
|
6538
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u6D3B\u8DC3\u8BA2\u9605\uFF0C\u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
6539
|
+
process.exit(1);
|
|
6540
|
+
}
|
|
6541
|
+
target = activeSub;
|
|
6132
6542
|
}
|
|
6133
|
-
|
|
6134
|
-
|
|
6543
|
+
return { target, timeout, concurrency };
|
|
6544
|
+
}
|
|
6545
|
+
function printRestartHintIfRunning() {
|
|
6546
|
+
if (getRunningState().running) {
|
|
6547
|
+
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"));
|
|
6548
|
+
console.log("");
|
|
6135
6549
|
}
|
|
6136
|
-
|
|
6137
|
-
|
|
6138
|
-
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6550
|
+
}
|
|
6551
|
+
function printSubscriptionList() {
|
|
6552
|
+
const subs = getSubscriptionsWithCache();
|
|
6553
|
+
if (subs.length === 0) {
|
|
6554
|
+
console.log("\u6CA1\u6709\u8BA2\u9605");
|
|
6555
|
+
console.log("");
|
|
6556
|
+
console.log("\u6DFB\u52A0\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
6557
|
+
console.log("");
|
|
6558
|
+
return;
|
|
6144
6559
|
}
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6560
|
+
const activeSub = getActiveSubscription();
|
|
6561
|
+
console.log(colors.cyan("\u8BA2\u9605\u5217\u8868:"));
|
|
6562
|
+
subs.forEach((s, i) => {
|
|
6563
|
+
const time = formatDate(s.updated_at);
|
|
6564
|
+
const defaultMark = activeSub && s.name === activeSub.name ? colors.green(" [\u4F7F\u7528\u4E2D]") : "";
|
|
6565
|
+
const mergeBadge = isMultiUrl(s.url) ? colors.cyan(` [\u5408\u5E76 ${splitUrls(s.url).length} \u6E90]`) : "";
|
|
6566
|
+
const interval = resolveUpdateInterval(s.url, s.update_interval);
|
|
6567
|
+
console.log(` ${i + 1}. ${s.name}${defaultMark}${mergeBadge}`);
|
|
6568
|
+
console.log(` ${colors.gray("\u66F4\u65B0: ")}${time} (\u95F4\u9694: ${interval}h)`);
|
|
6569
|
+
if (s.username) {
|
|
6570
|
+
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
6151
6571
|
}
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6572
|
+
if (s.download !== void 0 || s.total !== void 0) {
|
|
6573
|
+
const used = (s.upload || 0) + (s.download || 0);
|
|
6574
|
+
const usedStr = formatBytes(used);
|
|
6575
|
+
const totalStr = formatBytes(s.total);
|
|
6576
|
+
let percentStr = "";
|
|
6577
|
+
if (s.total && s.total > 0) {
|
|
6578
|
+
const percent = Math.min(used / s.total * 100, 100);
|
|
6579
|
+
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
6580
|
+
}
|
|
6581
|
+
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
6156
6582
|
}
|
|
6583
|
+
if (s.expire !== void 0) {
|
|
6584
|
+
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
6585
|
+
}
|
|
6586
|
+
if (s.web_page_url) {
|
|
6587
|
+
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
6588
|
+
}
|
|
6589
|
+
});
|
|
6590
|
+
console.log("");
|
|
6591
|
+
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
6592
|
+
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
6593
|
+
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
6594
|
+
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
6595
|
+
console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
|
|
6596
|
+
console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
|
|
6597
|
+
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
6598
|
+
console.log("");
|
|
6599
|
+
}
|
|
6600
|
+
async function cmdSubscription(args) {
|
|
6601
|
+
const action = args[1];
|
|
6602
|
+
if (!action || action === "list") {
|
|
6603
|
+
printSubscriptionList();
|
|
6604
|
+
return;
|
|
6157
6605
|
}
|
|
6158
|
-
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
}
|
|
6165
|
-
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
6166
|
-
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
6167
|
-
try {
|
|
6168
|
-
const pid = await launchOrRestart();
|
|
6169
|
-
const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
|
|
6170
|
-
console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
|
|
6171
|
-
} catch (e) {
|
|
6172
|
-
const msg = e.message;
|
|
6173
|
-
const lines = msg.split("\n");
|
|
6174
|
-
console.error(`${colors.red("\u542F\u52A8\u5931\u8D25:")} ${lines[0]}`);
|
|
6175
|
-
if (lines.length > 1) {
|
|
6176
|
-
for (const line of lines.slice(1)) console.error(line);
|
|
6606
|
+
if (action === "add") {
|
|
6607
|
+
const url = args[2]?.trim();
|
|
6608
|
+
const name = args[3] || "default";
|
|
6609
|
+
if (!url) {
|
|
6610
|
+
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
6611
|
+
process.exit(1);
|
|
6177
6612
|
}
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
|
|
6184
|
-
|
|
6185
|
-
|
|
6186
|
-
|
|
6187
|
-
|
|
6188
|
-
|
|
6189
|
-
|
|
6190
|
-
|
|
6191
|
-
|
|
6192
|
-
onRetryRound: progress.onRetryRound
|
|
6193
|
-
});
|
|
6194
|
-
progress.finish();
|
|
6195
|
-
console.log(formatTestSummary(cleanResult.summary));
|
|
6196
|
-
if (cleanResult.skipped) {
|
|
6197
|
-
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"));
|
|
6198
|
-
} else if (cleanResult.removedProxies > 0) {
|
|
6199
|
-
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
6200
|
-
console.log("");
|
|
6201
|
-
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
6202
|
-
if (!daemonEnabled) handleStopResult(stop());
|
|
6613
|
+
if (isMultiUrl(url)) {
|
|
6614
|
+
const urls = splitUrls(url);
|
|
6615
|
+
if (urls.length === 0) {
|
|
6616
|
+
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
6617
|
+
process.exit(1);
|
|
6618
|
+
}
|
|
6619
|
+
for (const u of urls) {
|
|
6620
|
+
if (!isValidHttpUrl(u)) {
|
|
6621
|
+
console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
|
|
6622
|
+
process.exit(1);
|
|
6623
|
+
}
|
|
6624
|
+
}
|
|
6625
|
+
const normalizedUrl = urls.join(",");
|
|
6626
|
+
console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
|
|
6203
6627
|
try {
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6628
|
+
addSubscription(normalizedUrl, name);
|
|
6629
|
+
setDefaultSubscription(name);
|
|
6630
|
+
const info = await downloadMergedSubscription(urls, name);
|
|
6631
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
|
|
6207
6632
|
} catch (e) {
|
|
6208
|
-
|
|
6633
|
+
removeSubscription(name);
|
|
6634
|
+
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
6209
6635
|
process.exit(1);
|
|
6210
6636
|
}
|
|
6211
|
-
}
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
console.log("");
|
|
6229
|
-
} else {
|
|
6230
|
-
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
6231
|
-
console.log("");
|
|
6232
|
-
info.files.forEach((f, i) => {
|
|
6233
|
-
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
6234
|
-
console.log(` ${num}. ${f.name}`);
|
|
6235
|
-
if (f.keys.length > 0) {
|
|
6236
|
-
console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
|
|
6637
|
+
} else {
|
|
6638
|
+
if (!isValidHttpUrl(url)) {
|
|
6639
|
+
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL\uFF08\u9700\u4EE5 http:// \u6216 https:// \u5F00\u5934\uFF09");
|
|
6640
|
+
process.exit(1);
|
|
6641
|
+
}
|
|
6642
|
+
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
6643
|
+
try {
|
|
6644
|
+
addSubscription(url, name);
|
|
6645
|
+
setDefaultSubscription(name);
|
|
6646
|
+
const info = await downloadSubscription(url, name);
|
|
6647
|
+
const repoUrl = githubRepoUrl(url);
|
|
6648
|
+
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
6649
|
+
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
6650
|
+
} catch (e) {
|
|
6651
|
+
removeSubscription(name);
|
|
6652
|
+
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
6653
|
+
process.exit(1);
|
|
6237
6654
|
}
|
|
6238
|
-
});
|
|
6239
|
-
console.log("");
|
|
6240
|
-
}
|
|
6241
|
-
console.log("\u542F\u7528\u8986\u5199: mihomo ow on");
|
|
6242
|
-
console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
|
|
6243
|
-
console.log("");
|
|
6244
|
-
}
|
|
6245
|
-
async function cmdOverwrite(args) {
|
|
6246
|
-
const action = args?.[1];
|
|
6247
|
-
const status = getStatus();
|
|
6248
|
-
const configInfo = getConfigInfo();
|
|
6249
|
-
const currentMode = isDaemonEnabled() ? "mixed" : configInfo?.tun ? "tun" : "mixed";
|
|
6250
|
-
if (action === "on" || action === "enable") {
|
|
6251
|
-
if (isOverwriteEnabled()) {
|
|
6252
|
-
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
|
|
6253
|
-
console.log("");
|
|
6254
|
-
printOverwriteList();
|
|
6255
|
-
return;
|
|
6256
|
-
}
|
|
6257
|
-
setOverwriteEnabled(true);
|
|
6258
|
-
console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
|
|
6259
|
-
if (status.running || isDaemonEnabled()) {
|
|
6260
|
-
console.log("");
|
|
6261
|
-
await cmdStart(["start", currentMode]);
|
|
6262
|
-
return;
|
|
6263
6655
|
}
|
|
6264
6656
|
console.log("");
|
|
6265
|
-
|
|
6657
|
+
printSubscriptionList();
|
|
6266
6658
|
return;
|
|
6267
6659
|
}
|
|
6268
|
-
if (action === "
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6660
|
+
if (action === "update") {
|
|
6661
|
+
const name = args[2];
|
|
6662
|
+
const subs = getSubscriptions();
|
|
6663
|
+
if (subs.length === 0) {
|
|
6664
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
6665
|
+
process.exit(1);
|
|
6274
6666
|
}
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6667
|
+
if (!name) {
|
|
6668
|
+
console.log(`\u66F4\u65B0\u6240\u6709 ${subs.length} \u4E2A\u8BA2\u9605...`);
|
|
6669
|
+
const results = await Promise.all(subs.map((sub) => tryUpdateOne(sub)));
|
|
6670
|
+
let ok = 0;
|
|
6671
|
+
for (const r of results) {
|
|
6672
|
+
if (r.success) ok++;
|
|
6673
|
+
printUpdateResult(r);
|
|
6674
|
+
}
|
|
6675
|
+
if (ok === 0) process.exit(1);
|
|
6278
6676
|
console.log("");
|
|
6279
|
-
|
|
6677
|
+
printRestartHintIfRunning();
|
|
6678
|
+
printSubscriptionList();
|
|
6280
6679
|
return;
|
|
6281
6680
|
}
|
|
6681
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
6682
|
+
const target = pickSingleSubscription(matches, name);
|
|
6683
|
+
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
6684
|
+
const result = await tryUpdateOne(target);
|
|
6685
|
+
if (!result.success) {
|
|
6686
|
+
console.error(`\u66F4\u65B0\u5931\u8D25: ${(result.error || "").split("\n")[0]}`);
|
|
6687
|
+
process.exit(1);
|
|
6688
|
+
}
|
|
6689
|
+
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
|
|
6282
6690
|
console.log("");
|
|
6283
|
-
|
|
6691
|
+
printRestartHintIfRunning();
|
|
6692
|
+
printSubscriptionList();
|
|
6284
6693
|
return;
|
|
6285
6694
|
}
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
aliases: ["sub", "subs", "subscription", "subscriptions"],
|
|
6297
|
-
label: "\u8BA2\u9605",
|
|
6298
|
-
paths: () => [DIRS.subscriptions],
|
|
6299
|
-
needsStop: true
|
|
6300
|
-
},
|
|
6301
|
-
{
|
|
6302
|
-
id: "logs",
|
|
6303
|
-
aliases: ["log", "logs"],
|
|
6304
|
-
label: "\u65E5\u5FD7",
|
|
6305
|
-
paths: () => [DIRS.logs],
|
|
6306
|
-
needsStop: false
|
|
6307
|
-
},
|
|
6308
|
-
{
|
|
6309
|
-
id: "data",
|
|
6310
|
-
aliases: ["data"],
|
|
6311
|
-
label: "\u8FD0\u884C\u6570\u636E",
|
|
6312
|
-
paths: () => [DIRS.data],
|
|
6313
|
-
needsStop: true
|
|
6314
|
-
},
|
|
6315
|
-
{
|
|
6316
|
-
id: "runtime",
|
|
6317
|
-
aliases: ["runtime"],
|
|
6318
|
-
label: "\u8FD0\u884C\u65F6",
|
|
6319
|
-
paths: () => [DIRS.runtime],
|
|
6320
|
-
needsStop: true
|
|
6321
|
-
},
|
|
6322
|
-
{
|
|
6323
|
-
id: "settings",
|
|
6324
|
-
aliases: ["setting", "settings", "config"],
|
|
6325
|
-
label: "\u8BBE\u7F6E",
|
|
6326
|
-
paths: () => [PATHS.settingsFile],
|
|
6327
|
-
needsStop: false
|
|
6328
|
-
},
|
|
6329
|
-
{
|
|
6330
|
-
id: "kernel",
|
|
6331
|
-
aliases: ["kernel", "core"],
|
|
6332
|
-
label: "\u5185\u6838",
|
|
6333
|
-
paths: () => [DIRS.kernel],
|
|
6334
|
-
needsStop: false,
|
|
6335
|
-
onAfter: () => clearKernelVersionCache(),
|
|
6336
|
-
checkEmpty: () => !hasKernel(),
|
|
6337
|
-
emptyMsg: "\u5185\u6838\u672A\u5B89\u88C5\uFF0C\u65E0\u9700\u5220\u9664",
|
|
6338
|
-
warnIfRunning: true
|
|
6339
|
-
},
|
|
6340
|
-
{
|
|
6341
|
-
id: "overwrites",
|
|
6342
|
-
aliases: ["overwrite", "overwrites", "ow"],
|
|
6343
|
-
label: "\u8986\u5199",
|
|
6344
|
-
paths: () => {
|
|
6345
|
-
const dir = USER_DATA_DIR;
|
|
6346
|
-
if (!fs9.existsSync(dir)) return [];
|
|
6347
|
-
return fs9.readdirSync(dir).filter((f) => f === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(f)).map((f) => `${dir}/${f}`);
|
|
6348
|
-
},
|
|
6349
|
-
needsStop: false
|
|
6350
|
-
},
|
|
6351
|
-
{
|
|
6352
|
-
id: "daemon",
|
|
6353
|
-
aliases: ["daemon"],
|
|
6354
|
-
label: "\u4FDD\u6D3B",
|
|
6355
|
-
// 删除全部交给 onAfter:disableDaemon 会先 bootout 卸载、再删 plist、再兜底清理进程,
|
|
6356
|
-
// 顺序正确(先卸载再删文件);此处返回空避免提前删掉 plist 破坏卸载。
|
|
6357
|
-
paths: () => [],
|
|
6358
|
-
needsStop: false,
|
|
6359
|
-
onAfter: () => disableDaemon(),
|
|
6360
|
-
checkEmpty: () => !isDaemonEnabled(),
|
|
6361
|
-
emptyMsg: "\u4FDD\u6D3B\u672A\u542F\u7528\uFF0C\u65E0\u9700\u5220\u9664"
|
|
6362
|
-
}
|
|
6363
|
-
];
|
|
6364
|
-
function resolveResetTargets(names) {
|
|
6365
|
-
const matched = [];
|
|
6366
|
-
const unmatched = [];
|
|
6367
|
-
for (const name of names) {
|
|
6368
|
-
const t = RESET_TARGETS.find((t2) => t2.aliases.includes(name.toLowerCase()));
|
|
6369
|
-
if (t) {
|
|
6370
|
-
if (!matched.find((m) => m.id === t.id)) matched.push(t);
|
|
6371
|
-
} else {
|
|
6372
|
-
unmatched.push(name);
|
|
6695
|
+
if (action === "use") {
|
|
6696
|
+
const name = args[2];
|
|
6697
|
+
const subs = getSubscriptions();
|
|
6698
|
+
if (!name) {
|
|
6699
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
6700
|
+
if (subs.length > 0) {
|
|
6701
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
6702
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
6703
|
+
}
|
|
6704
|
+
process.exit(1);
|
|
6373
6705
|
}
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6380
|
-
rl.question(`${question} (y/N) `, (a) => {
|
|
6381
|
-
rl.close();
|
|
6382
|
-
resolve(a);
|
|
6383
|
-
});
|
|
6384
|
-
});
|
|
6385
|
-
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
6386
|
-
}
|
|
6387
|
-
async function cmdReset(args) {
|
|
6388
|
-
const flags = (args || []).filter((a) => a.startsWith("-"));
|
|
6389
|
-
const names = (args || []).slice(1).filter((a) => !a.startsWith("-"));
|
|
6390
|
-
const fullReset = flags.includes("--full") || flags.includes("-f");
|
|
6391
|
-
const skipConfirm = flags.includes("--yes") || flags.includes("-y");
|
|
6392
|
-
let targets;
|
|
6393
|
-
if (fullReset) {
|
|
6394
|
-
targets = RESET_TARGETS;
|
|
6395
|
-
} else if (names.length > 0) {
|
|
6396
|
-
const { matched, unmatched } = resolveResetTargets(names);
|
|
6397
|
-
if (unmatched.length > 0) {
|
|
6398
|
-
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`);
|
|
6399
|
-
console.log("");
|
|
6400
|
-
console.log(`\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`);
|
|
6706
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
6707
|
+
const target = pickSingleSubscription(matches, name);
|
|
6708
|
+
const currentDefault = getActiveSubscription();
|
|
6709
|
+
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
6710
|
+
if (isAlreadyDefault) {
|
|
6711
|
+
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
6401
6712
|
console.log("");
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6713
|
+
printSubscriptionList();
|
|
6714
|
+
return;
|
|
6715
|
+
}
|
|
6716
|
+
const currentMode = getRuntimeMode();
|
|
6717
|
+
const restartNeeded = isRestartNeededOnChange();
|
|
6718
|
+
const success = setDefaultSubscription(target.name);
|
|
6719
|
+
if (success) {
|
|
6720
|
+
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
6721
|
+
} else {
|
|
6722
|
+
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
6407
6723
|
process.exit(1);
|
|
6408
6724
|
}
|
|
6409
|
-
|
|
6410
|
-
|
|
6411
|
-
|
|
6725
|
+
if (restartNeeded) {
|
|
6726
|
+
console.log("");
|
|
6727
|
+
await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
|
|
6728
|
+
return;
|
|
6729
|
+
}
|
|
6730
|
+
console.log("");
|
|
6731
|
+
printSubscriptionList();
|
|
6732
|
+
return;
|
|
6412
6733
|
}
|
|
6413
|
-
|
|
6414
|
-
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
|
|
6734
|
+
if (action === "web" || action === "open") {
|
|
6735
|
+
const name = args[2];
|
|
6736
|
+
const subs = getSubscriptionsWithCache();
|
|
6737
|
+
if (subs.length === 0) {
|
|
6738
|
+
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
6739
|
+
process.exit(1);
|
|
6740
|
+
}
|
|
6741
|
+
let target;
|
|
6742
|
+
if (name) {
|
|
6743
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
6744
|
+
target = pickSingleSubscription(matches, name);
|
|
6745
|
+
} else {
|
|
6746
|
+
target = getActiveSubscription() || subs[0];
|
|
6747
|
+
}
|
|
6748
|
+
const cached = subs.find((s) => s.name === target.name);
|
|
6749
|
+
let webPageUrl = cached?.web_page_url;
|
|
6750
|
+
if (!webPageUrl) {
|
|
6751
|
+
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u67E5\u8BE2\u8BA2\u9605...");
|
|
6752
|
+
try {
|
|
6753
|
+
const info = isMultiUrl(target.url) ? await downloadMergedSubscription(splitUrls(target.url), target.name, void 0, false) : await downloadSubscription(target.url, target.name, void 0, false);
|
|
6754
|
+
if (info.webPageUrl) {
|
|
6755
|
+
webPageUrl = info.webPageUrl;
|
|
6756
|
+
} else {
|
|
6757
|
+
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
6758
|
+
process.exit(1);
|
|
6759
|
+
}
|
|
6760
|
+
} catch (e) {
|
|
6761
|
+
console.error(`\u67E5\u8BE2\u5931\u8D25: ${e.message}`);
|
|
6762
|
+
process.exit(1);
|
|
6418
6763
|
}
|
|
6419
6764
|
}
|
|
6420
|
-
|
|
6421
|
-
|
|
6422
|
-
|
|
6423
|
-
|
|
6424
|
-
|
|
6425
|
-
const pids = needsStop || warnRunning ? getAllMihomoPids() : [];
|
|
6426
|
-
if (warnRunning && pids.length > 0) {
|
|
6427
|
-
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`));
|
|
6428
|
-
}
|
|
6429
|
-
if (disablesDaemon && isDaemonEnabled()) {
|
|
6430
|
-
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"));
|
|
6431
|
-
}
|
|
6432
|
-
console.log(`\u5C06\u5220\u9664: ${targets.map((t) => t.label).join("\u3001")}`);
|
|
6433
|
-
if (!skipConfirm && !await confirmPrompt("\u786E\u8BA4?")) {
|
|
6434
|
-
console.log("\u5DF2\u53D6\u6D88");
|
|
6765
|
+
console.log(`\u6253\u5F00\u8BA2\u9605\u9875\u9762: ${webPageUrl}`);
|
|
6766
|
+
const opened = openUrl(webPageUrl);
|
|
6767
|
+
if (!opened) {
|
|
6768
|
+
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
6769
|
+
}
|
|
6435
6770
|
return;
|
|
6436
6771
|
}
|
|
6437
|
-
if (
|
|
6438
|
-
|
|
6439
|
-
|
|
6440
|
-
|
|
6441
|
-
|
|
6442
|
-
|
|
6443
|
-
|
|
6444
|
-
|
|
6445
|
-
|
|
6772
|
+
if (action === "remove" || action === "rm" || action === "delete") {
|
|
6773
|
+
const name = args[2];
|
|
6774
|
+
const subs = getSubscriptions();
|
|
6775
|
+
if (!name) {
|
|
6776
|
+
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8981\u5220\u9664\u7684\u8BA2\u9605\u540D\u79F0");
|
|
6777
|
+
if (subs.length > 0) {
|
|
6778
|
+
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
6779
|
+
for (const s of subs) console.log(` ${s.name}`);
|
|
6780
|
+
}
|
|
6781
|
+
process.exit(1);
|
|
6782
|
+
}
|
|
6783
|
+
const matches = findSubscriptionFuzzy(subs, name);
|
|
6784
|
+
const target = pickSingleSubscription(matches, name);
|
|
6785
|
+
const switchedTo = removeSubscription(target.name);
|
|
6786
|
+
console.log(`\u5DF2\u5220\u9664\u8BA2\u9605 "${target.name}"`);
|
|
6787
|
+
if (switchedTo) {
|
|
6788
|
+
console.log(`\u5DF2\u81EA\u52A8\u5207\u6362\u5230 "${switchedTo}"`);
|
|
6446
6789
|
}
|
|
6790
|
+
console.log("");
|
|
6791
|
+
printSubscriptionList();
|
|
6792
|
+
return;
|
|
6447
6793
|
}
|
|
6448
|
-
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
|
|
6452
|
-
|
|
6453
|
-
|
|
6454
|
-
|
|
6455
|
-
|
|
6794
|
+
if (action === "clean") {
|
|
6795
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
6796
|
+
const rounds = parseIntArg(args, "-r", "--rounds", DEFAULT_CLEAN_ROUNDS);
|
|
6797
|
+
console.log(`\u6E05\u7406\u8BA2\u9605 "${target.name}"...`);
|
|
6798
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6799
|
+
console.log("");
|
|
6800
|
+
const progress = createProgressPrinter(rounds);
|
|
6801
|
+
const result = await withTestInstance(target.name, async (apiBase) => {
|
|
6802
|
+
return autoCleanSubscription(target.name, {
|
|
6803
|
+
timeout,
|
|
6804
|
+
concurrency,
|
|
6805
|
+
rounds,
|
|
6806
|
+
apiBase,
|
|
6807
|
+
onResult: progress.onResult,
|
|
6808
|
+
onRetryRound: progress.onRetryRound
|
|
6809
|
+
});
|
|
6810
|
+
});
|
|
6811
|
+
progress.finish();
|
|
6812
|
+
console.log(formatTestSummary(result.summary));
|
|
6813
|
+
if (result.skipped) {
|
|
6814
|
+
console.log("");
|
|
6815
|
+
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"));
|
|
6816
|
+
} else if (result.removedProxies > 0) {
|
|
6817
|
+
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6818
|
+
if (getRunningState().running) {
|
|
6819
|
+
console.log("");
|
|
6820
|
+
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
6456
6821
|
}
|
|
6457
6822
|
}
|
|
6458
|
-
|
|
6823
|
+
return;
|
|
6459
6824
|
}
|
|
6460
|
-
|
|
6461
|
-
|
|
6462
|
-
|
|
6825
|
+
if (action === "test") {
|
|
6826
|
+
const { target, timeout, concurrency } = resolveTestTarget(args);
|
|
6827
|
+
console.log(`\u6D4B\u8BD5\u8BA2\u9605 "${target.name}" \u7684\u8282\u70B9\u8FDE\u901A\u6027...`);
|
|
6828
|
+
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6829
|
+
console.log("");
|
|
6830
|
+
const progress = createProgressPrinter();
|
|
6831
|
+
const summary = await withTestInstance(target.name, async (apiBase) => {
|
|
6832
|
+
return testSubscriptionProxies(target.name, {
|
|
6833
|
+
timeout,
|
|
6834
|
+
concurrency,
|
|
6835
|
+
apiBase,
|
|
6836
|
+
onResult: progress.onResult
|
|
6837
|
+
});
|
|
6838
|
+
});
|
|
6839
|
+
progress.finish();
|
|
6840
|
+
console.log(formatTestSummary(summary));
|
|
6841
|
+
return;
|
|
6463
6842
|
}
|
|
6464
|
-
console.
|
|
6843
|
+
console.error("\u9519\u8BEF: \u672A\u77E5\u7684\u8BA2\u9605\u547D\u4EE4");
|
|
6844
|
+
console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
|
|
6845
|
+
process.exit(1);
|
|
6465
6846
|
}
|
|
6466
6847
|
|
|
6467
6848
|
// src/commands/test.ts
|
|
6468
6849
|
function requireRunning() {
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
}
|
|
6474
|
-
return;
|
|
6475
|
-
}
|
|
6476
|
-
const status = getStatus();
|
|
6477
|
-
if (!status.running) {
|
|
6478
|
-
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
6850
|
+
const state = getRunningState();
|
|
6851
|
+
if (!state.running) {
|
|
6852
|
+
const hint = state.daemon ? "mihomo daemon on" : "mihomo start";
|
|
6853
|
+
console.error(`\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (${hint})`);
|
|
6479
6854
|
process.exit(1);
|
|
6480
6855
|
}
|
|
6481
6856
|
}
|
|
@@ -6532,21 +6907,24 @@ async function cmdClean(args) {
|
|
|
6532
6907
|
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6533
6908
|
console.log("");
|
|
6534
6909
|
console.log("\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548...");
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6910
|
+
const mode = getRuntimeMode();
|
|
6911
|
+
const daemonManaged = isDaemonEnabled();
|
|
6912
|
+
try {
|
|
6913
|
+
if (!daemonManaged) {
|
|
6914
|
+
if (hasRootResidue()) {
|
|
6915
|
+
console.error(`${colors.red("\u9519\u8BEF:")} \u4E3B\u5B9E\u4F8B\u4EE5 root \u8FD0\u884C\uFF08TUN\uFF09\uFF0C\u505C\u6B62\u5B83\u9700\u8981 sudo`);
|
|
6916
|
+
console.error("\u8BF7\u6539\u7528 mihomo sub clean\uFF08\u9694\u79BB\u5B9E\u4F8B\u6D4B\u901F\uFF0C\u65E0\u9700\u505C\u6B62\u4E3B\u5B9E\u4F8B\uFF09");
|
|
6917
|
+
process.exit(1);
|
|
6918
|
+
}
|
|
6919
|
+
handleStopResult(stop());
|
|
6542
6920
|
}
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
const
|
|
6546
|
-
|
|
6547
|
-
|
|
6548
|
-
|
|
6549
|
-
|
|
6921
|
+
const configInfo = prepareConfigForStart(mode, activeSub.name);
|
|
6922
|
+
const pid = await launchOrRestart(mode);
|
|
6923
|
+
const label = daemonManaged ? "\u5DF2\u91CD\u542F (\u4FDD\u6D3B)" : "\u5DF2\u91CD\u542F";
|
|
6924
|
+
console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
|
|
6925
|
+
} catch (e) {
|
|
6926
|
+
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
6927
|
+
process.exit(1);
|
|
6550
6928
|
}
|
|
6551
6929
|
}
|
|
6552
6930
|
}
|
|
@@ -6562,6 +6940,10 @@ function cmdUI(args) {
|
|
|
6562
6940
|
const url = UI_URLS[uiName];
|
|
6563
6941
|
console.log(`\u6253\u5F00 Web UI: ${uiName}`);
|
|
6564
6942
|
console.log(`\u5730\u5740: ${url}`);
|
|
6943
|
+
const secret = readSettings().controller_secret;
|
|
6944
|
+
if (secret) {
|
|
6945
|
+
console.log("\u5DF2\u914D\u7F6E\u8BBF\u95EE\u5BC6\u94A5\uFF08UI \u8FDE\u63A5 127.0.0.1:9090 \u65F6\u9700\u8F93\u5165\uFF0C\u5BC6\u94A5\u89C1 settings.json\uFF09");
|
|
6946
|
+
}
|
|
6565
6947
|
const success = openUrl(url);
|
|
6566
6948
|
if (!success) {
|
|
6567
6949
|
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
@@ -6583,11 +6965,16 @@ async function cmdUpdate() {
|
|
|
6583
6965
|
if (code === 0) {
|
|
6584
6966
|
resolve();
|
|
6585
6967
|
} else {
|
|
6968
|
+
console.error("\u66F4\u65B0\u5931\u8D25\u3002\u82E5\u4E3A\u6743\u9650\u95EE\u9898\uFF08EACCES\uFF09\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli");
|
|
6586
6969
|
process.exit(code || 1);
|
|
6587
6970
|
}
|
|
6588
6971
|
});
|
|
6589
6972
|
npm.on("error", (e) => {
|
|
6590
|
-
|
|
6973
|
+
if (e.message.includes("EACCES") || e.message.includes("permission")) {
|
|
6974
|
+
console.error("\u6743\u9650\u4E0D\u8DB3\uFF0C\u53EF\u5C1D\u8BD5: sudo npm install -g mihomo-cli");
|
|
6975
|
+
} else {
|
|
6976
|
+
console.error(`\u6267\u884C\u5931\u8D25: ${e.message}`);
|
|
6977
|
+
}
|
|
6591
6978
|
process.exit(1);
|
|
6592
6979
|
});
|
|
6593
6980
|
});
|
|
@@ -6607,9 +6994,204 @@ async function cmdUpdate() {
|
|
|
6607
6994
|
}
|
|
6608
6995
|
}
|
|
6609
6996
|
|
|
6997
|
+
// src/commands/registry.ts
|
|
6998
|
+
var COMMANDS = [
|
|
6999
|
+
// === 控制 ===
|
|
7000
|
+
{
|
|
7001
|
+
name: "start",
|
|
7002
|
+
aliases: ["up"],
|
|
7003
|
+
handler: cmdStart,
|
|
7004
|
+
group: "control",
|
|
7005
|
+
usage: ["start [tun|mixed] [-s] [-u ms] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)", " [-r N] [-t ms] [-j N] [--no-clean]"]
|
|
7006
|
+
},
|
|
7007
|
+
{
|
|
7008
|
+
name: "tun",
|
|
7009
|
+
aliases: [],
|
|
7010
|
+
handler: cmdStart,
|
|
7011
|
+
rewrite: (args) => ["start", "tun", ...args.slice(1)],
|
|
7012
|
+
group: "control",
|
|
7013
|
+
usage: []
|
|
7014
|
+
},
|
|
7015
|
+
{
|
|
7016
|
+
name: "stop",
|
|
7017
|
+
aliases: ["down"],
|
|
7018
|
+
handler: cmdStop,
|
|
7019
|
+
group: "control",
|
|
7020
|
+
usage: ["stop \u505C\u6B62\u4EE3\u7406"]
|
|
7021
|
+
},
|
|
7022
|
+
{
|
|
7023
|
+
name: "status",
|
|
7024
|
+
aliases: [],
|
|
7025
|
+
handler: printStatus,
|
|
7026
|
+
group: "control",
|
|
7027
|
+
usage: ["status \u67E5\u770B\u72B6\u6001"]
|
|
7028
|
+
},
|
|
7029
|
+
// === 界面 ===
|
|
7030
|
+
{
|
|
7031
|
+
name: "ui",
|
|
7032
|
+
aliases: [],
|
|
7033
|
+
handler: cmdUI,
|
|
7034
|
+
group: "interface",
|
|
7035
|
+
usage: ["ui [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)"]
|
|
7036
|
+
},
|
|
7037
|
+
{
|
|
7038
|
+
name: "log",
|
|
7039
|
+
aliases: [],
|
|
7040
|
+
handler: cmdLog,
|
|
7041
|
+
group: "interface",
|
|
7042
|
+
usage: ["log [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09"]
|
|
7043
|
+
},
|
|
7044
|
+
{
|
|
7045
|
+
name: "logs",
|
|
7046
|
+
aliases: [],
|
|
7047
|
+
handler: cmdLogs,
|
|
7048
|
+
group: "interface",
|
|
7049
|
+
usage: ["logs [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09"]
|
|
7050
|
+
},
|
|
7051
|
+
// === 订阅 ===
|
|
7052
|
+
{
|
|
7053
|
+
name: "subscription",
|
|
7054
|
+
aliases: ["sub", "subscriptions"],
|
|
7055
|
+
handler: cmdSubscription,
|
|
7056
|
+
group: "subscription",
|
|
7057
|
+
usage: [
|
|
7058
|
+
"subscription \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09",
|
|
7059
|
+
"subscription use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605",
|
|
7060
|
+
"subscription add <url> [name] \u6DFB\u52A0\u8BA2\u9605",
|
|
7061
|
+
"subscription update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09",
|
|
7062
|
+
"subscription remove <name> \u5220\u9664\u8BA2\u9605",
|
|
7063
|
+
"subscription web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762",
|
|
7064
|
+
"subscription test [name] \u6D4B\u8BD5\u8282\u70B9\uFF08\u72EC\u7ACB\u5B9E\u4F8B\uFF0C\u65E0\u9700\u8FD0\u884C\uFF09",
|
|
7065
|
+
"subscription clean [name] \u6D4B\u901F\u6E05\u7406\uFF08\u72EC\u7ACB\u5B9E\u4F8B\uFF0C\u4E0D\u52A8\u4E3B\u5B9E\u4F8B\uFF09"
|
|
7066
|
+
]
|
|
7067
|
+
},
|
|
7068
|
+
{
|
|
7069
|
+
name: "use",
|
|
7070
|
+
aliases: [],
|
|
7071
|
+
handler: cmdSubscription,
|
|
7072
|
+
rewrite: (args) => ["sub", "use", ...args.slice(1)],
|
|
7073
|
+
group: "subscription",
|
|
7074
|
+
usage: []
|
|
7075
|
+
},
|
|
7076
|
+
{
|
|
7077
|
+
name: "test",
|
|
7078
|
+
aliases: [],
|
|
7079
|
+
handler: cmdTest,
|
|
7080
|
+
group: "subscription",
|
|
7081
|
+
usage: ["test [-t ms] [-j N] \u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\uFF08\u7ECF\u8FD0\u884C\u4E2D\u7684\u4E3B\u5B9E\u4F8B\uFF09"]
|
|
7082
|
+
},
|
|
7083
|
+
{
|
|
7084
|
+
name: "clean",
|
|
7085
|
+
aliases: [],
|
|
7086
|
+
handler: cmdClean,
|
|
7087
|
+
group: "subscription",
|
|
7088
|
+
usage: ["clean [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u91CD\u542F\uFF08\u7ECF\u4E3B\u5B9E\u4F8B\uFF09"]
|
|
7089
|
+
},
|
|
7090
|
+
// === 配置 ===
|
|
7091
|
+
{
|
|
7092
|
+
name: "overwrite",
|
|
7093
|
+
aliases: ["ow"],
|
|
7094
|
+
handler: cmdOverwrite,
|
|
7095
|
+
group: "config",
|
|
7096
|
+
usage: ["overwrite \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09", "overwrite on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E"]
|
|
7097
|
+
},
|
|
7098
|
+
{
|
|
7099
|
+
name: "on",
|
|
7100
|
+
aliases: [],
|
|
7101
|
+
handler: cmdOverwrite,
|
|
7102
|
+
rewrite: () => ["ow", "on"],
|
|
7103
|
+
group: "config",
|
|
7104
|
+
usage: []
|
|
7105
|
+
},
|
|
7106
|
+
{
|
|
7107
|
+
name: "off",
|
|
7108
|
+
aliases: [],
|
|
7109
|
+
handler: cmdOverwrite,
|
|
7110
|
+
rewrite: () => ["ow", "off"],
|
|
7111
|
+
group: "config",
|
|
7112
|
+
usage: []
|
|
7113
|
+
},
|
|
7114
|
+
{
|
|
7115
|
+
name: "directory",
|
|
7116
|
+
aliases: ["dir", "dirs", "directories"],
|
|
7117
|
+
handler: cmdDirectory,
|
|
7118
|
+
group: "config",
|
|
7119
|
+
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|..."]
|
|
7120
|
+
},
|
|
7121
|
+
{
|
|
7122
|
+
name: "open",
|
|
7123
|
+
aliases: [],
|
|
7124
|
+
handler: cmdDirectory,
|
|
7125
|
+
rewrite: (args) => ["dir", "open", ...args.slice(1)],
|
|
7126
|
+
group: "config",
|
|
7127
|
+
usage: []
|
|
7128
|
+
},
|
|
7129
|
+
// === 系统 ===
|
|
7130
|
+
{
|
|
7131
|
+
name: "kernel",
|
|
7132
|
+
aliases: [],
|
|
7133
|
+
handler: cmdKernel,
|
|
7134
|
+
group: "system",
|
|
7135
|
+
usage: ["kernel [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09"]
|
|
7136
|
+
},
|
|
7137
|
+
{
|
|
7138
|
+
name: "daemon",
|
|
7139
|
+
aliases: [],
|
|
7140
|
+
handler: cmdDaemon,
|
|
7141
|
+
group: "system",
|
|
7142
|
+
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"]
|
|
7143
|
+
},
|
|
7144
|
+
{
|
|
7145
|
+
name: "update",
|
|
7146
|
+
aliases: ["upd", "upgrade"],
|
|
7147
|
+
handler: cmdUpdate,
|
|
7148
|
+
group: "system",
|
|
7149
|
+
usage: ["update \u66F4\u65B0 mihomo-cli (npm install -g)"]
|
|
7150
|
+
},
|
|
7151
|
+
{
|
|
7152
|
+
name: "reset",
|
|
7153
|
+
aliases: [],
|
|
7154
|
+
handler: cmdReset,
|
|
7155
|
+
group: "system",
|
|
7156
|
+
usage: ["reset [\u76EE\u6807...] [--full] [-y] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8, -y \u8DF3\u8FC7\u786E\u8BA4"]
|
|
7157
|
+
},
|
|
7158
|
+
// === meta(不在分组清单展示,help 末尾单列) ===
|
|
7159
|
+
{
|
|
7160
|
+
name: "help",
|
|
7161
|
+
aliases: ["-h", "--help"],
|
|
7162
|
+
handler: () => printHelp(COMMANDS),
|
|
7163
|
+
group: "meta",
|
|
7164
|
+
usage: ["help, -h \u663E\u793A\u5E2E\u52A9"]
|
|
7165
|
+
},
|
|
7166
|
+
{
|
|
7167
|
+
name: "version",
|
|
7168
|
+
aliases: ["-v", "--version"],
|
|
7169
|
+
handler: printVersion,
|
|
7170
|
+
group: "meta",
|
|
7171
|
+
usage: ["version, -v \u663E\u793A\u7248\u672C"]
|
|
7172
|
+
}
|
|
7173
|
+
];
|
|
7174
|
+
var COMMAND_INDEX = (() => {
|
|
7175
|
+
const index = /* @__PURE__ */ new Map();
|
|
7176
|
+
for (const cmd of COMMANDS) {
|
|
7177
|
+
for (const token of [cmd.name, ...cmd.aliases]) {
|
|
7178
|
+
if (index.has(token)) {
|
|
7179
|
+
throw new Error(`\u547D\u4EE4\u6CE8\u518C\u8868\u5B58\u5728\u91CD\u590D token: "${token}"\uFF08${index.get(token)?.name} \u4E0E ${cmd.name}\uFF09`);
|
|
7180
|
+
}
|
|
7181
|
+
index.set(token, cmd);
|
|
7182
|
+
}
|
|
7183
|
+
}
|
|
7184
|
+
return index;
|
|
7185
|
+
})();
|
|
7186
|
+
function findCommand(token) {
|
|
7187
|
+
return COMMAND_INDEX.get(token);
|
|
7188
|
+
}
|
|
7189
|
+
|
|
6610
7190
|
// src/index.ts
|
|
6611
7191
|
process.on("SIGINT", () => {
|
|
6612
|
-
|
|
7192
|
+
if (!isSilentSigint()) {
|
|
7193
|
+
console.log("\n\u6B63\u5728\u9000\u51FA...");
|
|
7194
|
+
}
|
|
6613
7195
|
runCleanup();
|
|
6614
7196
|
process.exit(130);
|
|
6615
7197
|
});
|
|
@@ -6623,12 +7205,14 @@ process.on("uncaughtException", (e) => {
|
|
|
6623
7205
|
if (e.stack) {
|
|
6624
7206
|
console.error(e.stack.split("\n").slice(1).join("\n"));
|
|
6625
7207
|
}
|
|
7208
|
+
runCleanup();
|
|
6626
7209
|
process.exit(1);
|
|
6627
7210
|
});
|
|
6628
7211
|
process.on("unhandledRejection", (reason) => {
|
|
6629
7212
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
6630
7213
|
console.error(`
|
|
6631
7214
|
\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${msg}`);
|
|
7215
|
+
runCleanup();
|
|
6632
7216
|
process.exit(1);
|
|
6633
7217
|
});
|
|
6634
7218
|
function clearProxyEnv() {
|
|
@@ -6648,94 +7232,18 @@ async function main() {
|
|
|
6648
7232
|
printShortHelp();
|
|
6649
7233
|
return;
|
|
6650
7234
|
}
|
|
6651
|
-
const
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
printVersion();
|
|
6658
|
-
return;
|
|
6659
|
-
}
|
|
6660
|
-
switch (cmd) {
|
|
6661
|
-
case "up":
|
|
6662
|
-
case "start":
|
|
6663
|
-
await cmdStart(args);
|
|
6664
|
-
break;
|
|
6665
|
-
case "tun":
|
|
6666
|
-
await cmdStart(["start", "tun", ...args.slice(1)]);
|
|
6667
|
-
break;
|
|
6668
|
-
case "down":
|
|
6669
|
-
case "stop":
|
|
6670
|
-
await cmdStop();
|
|
6671
|
-
break;
|
|
6672
|
-
case "status":
|
|
6673
|
-
printStatus();
|
|
6674
|
-
break;
|
|
6675
|
-
case "log":
|
|
6676
|
-
cmdLog(args);
|
|
6677
|
-
break;
|
|
6678
|
-
case "logs":
|
|
6679
|
-
cmdLogs(args);
|
|
6680
|
-
break;
|
|
6681
|
-
case "open":
|
|
6682
|
-
cmdDirectory(["dir", "open", ...args.slice(1)]);
|
|
6683
|
-
break;
|
|
6684
|
-
case "ui":
|
|
6685
|
-
cmdUI(args);
|
|
6686
|
-
break;
|
|
6687
|
-
case "kernel":
|
|
6688
|
-
await cmdKernel(args);
|
|
6689
|
-
break;
|
|
6690
|
-
case "upd":
|
|
6691
|
-
case "update":
|
|
6692
|
-
case "upgrade":
|
|
6693
|
-
await cmdUpdate();
|
|
6694
|
-
break;
|
|
6695
|
-
case "use":
|
|
6696
|
-
await cmdSubscription(["sub", "use", ...args.slice(1)]);
|
|
6697
|
-
break;
|
|
6698
|
-
case "sub":
|
|
6699
|
-
case "subscription":
|
|
6700
|
-
case "subscriptions":
|
|
6701
|
-
await cmdSubscription(args);
|
|
6702
|
-
break;
|
|
6703
|
-
case "dir":
|
|
6704
|
-
case "dirs":
|
|
6705
|
-
case "directory":
|
|
6706
|
-
case "directories":
|
|
6707
|
-
cmdDirectory(args);
|
|
6708
|
-
break;
|
|
6709
|
-
case "reset":
|
|
6710
|
-
await cmdReset(args);
|
|
6711
|
-
break;
|
|
6712
|
-
case "daemon":
|
|
6713
|
-
await cmdDaemon(args);
|
|
6714
|
-
break;
|
|
6715
|
-
case "on":
|
|
6716
|
-
await cmdOverwrite(["ow", "on"]);
|
|
6717
|
-
break;
|
|
6718
|
-
case "off":
|
|
6719
|
-
await cmdOverwrite(["ow", "off"]);
|
|
6720
|
-
break;
|
|
6721
|
-
case "ow":
|
|
6722
|
-
case "overwrite":
|
|
6723
|
-
await cmdOverwrite(args);
|
|
6724
|
-
break;
|
|
6725
|
-
case "test":
|
|
6726
|
-
await cmdTest(args);
|
|
6727
|
-
break;
|
|
6728
|
-
case "clean":
|
|
6729
|
-
await cmdClean(args);
|
|
6730
|
-
break;
|
|
6731
|
-
default:
|
|
6732
|
-
console.error(`\u672A\u77E5\u547D\u4EE4: ${cmd}`);
|
|
6733
|
-
console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
|
|
6734
|
-
process.exit(1);
|
|
7235
|
+
const token = args[0].toLowerCase();
|
|
7236
|
+
const command = findCommand(token);
|
|
7237
|
+
if (!command) {
|
|
7238
|
+
console.error(`\u672A\u77E5\u547D\u4EE4: ${token}`);
|
|
7239
|
+
console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
|
|
7240
|
+
process.exit(1);
|
|
6735
7241
|
}
|
|
7242
|
+
await command.handler(command.rewrite ? command.rewrite(args) : args);
|
|
6736
7243
|
}
|
|
6737
7244
|
main().catch((e) => {
|
|
6738
7245
|
console.error(`\u9519\u8BEF: ${e.message}`);
|
|
7246
|
+
runCleanup();
|
|
6739
7247
|
process.exit(1);
|
|
6740
7248
|
});
|
|
6741
7249
|
/*! Bundled license information:
|