mihomo-cli 3.2.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 +59 -0
- package/README.md +51 -17
- package/dist/index.js +1161 -873
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2917,7 +2917,8 @@ 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",
|
|
@@ -2974,6 +2975,7 @@ var DEFAULT_TEST_URL = "http://www.gstatic.com/generate_204";
|
|
|
2974
2975
|
var DEFAULT_CLEAN_ROUNDS = 2;
|
|
2975
2976
|
var AUTO_CLEAN_THRESHOLD = 100;
|
|
2976
2977
|
var AUTO_CLEAN_THRESHOLD_GITHUB = 50;
|
|
2978
|
+
var AUTO_CLEAN_COOLDOWN_HOURS = 12;
|
|
2977
2979
|
|
|
2978
2980
|
// src/overwrite.ts
|
|
2979
2981
|
import fs3 from "fs";
|
|
@@ -3097,6 +3099,7 @@ function maskUrl(url) {
|
|
|
3097
3099
|
}
|
|
3098
3100
|
if (parsed.username) parsed.username = "***";
|
|
3099
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("/");
|
|
3100
3103
|
return parsed.toString();
|
|
3101
3104
|
} catch {
|
|
3102
3105
|
if (url.length > 30) {
|
|
@@ -3112,6 +3115,12 @@ function readSubscriptionCache() {
|
|
|
3112
3115
|
const content = fs2.readFileSync(PATHS.subscriptionsCacheFile, "utf8");
|
|
3113
3116
|
return JSON.parse(content);
|
|
3114
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
|
+
}
|
|
3115
3124
|
return {};
|
|
3116
3125
|
}
|
|
3117
3126
|
}
|
|
@@ -3214,6 +3223,7 @@ function parseOverrideKey(key) {
|
|
|
3214
3223
|
let forceOverwrite = false;
|
|
3215
3224
|
let arrayPrepend = false;
|
|
3216
3225
|
let arrayAppend = false;
|
|
3226
|
+
let arrayMergeByName = false;
|
|
3217
3227
|
const lastChar = key[key.length - 1];
|
|
3218
3228
|
const openAngleCount = (key.match(/</g) || []).length;
|
|
3219
3229
|
const closeAngleCount = (key.match(/>/g) || []).length;
|
|
@@ -3234,6 +3244,9 @@ function parseOverrideKey(key) {
|
|
|
3234
3244
|
} else {
|
|
3235
3245
|
actualKey = unwrapped;
|
|
3236
3246
|
}
|
|
3247
|
+
} else if (actualKey.startsWith("~")) {
|
|
3248
|
+
arrayMergeByName = true;
|
|
3249
|
+
actualKey = actualKey.slice(1);
|
|
3237
3250
|
} else {
|
|
3238
3251
|
if (actualKey.startsWith("+")) {
|
|
3239
3252
|
arrayPrepend = true;
|
|
@@ -3244,7 +3257,7 @@ function parseOverrideKey(key) {
|
|
|
3244
3257
|
actualKey = actualKey.slice(0, -1);
|
|
3245
3258
|
}
|
|
3246
3259
|
}
|
|
3247
|
-
return { key: actualKey, forceOverwrite, arrayPrepend, arrayAppend };
|
|
3260
|
+
return { key: actualKey, forceOverwrite, arrayPrepend, arrayAppend, arrayMergeByName };
|
|
3248
3261
|
}
|
|
3249
3262
|
function deepMergeWithOverrides(target, override) {
|
|
3250
3263
|
let t = target;
|
|
@@ -3262,8 +3275,24 @@ function deepMergeWithOverrides(target, override) {
|
|
|
3262
3275
|
}
|
|
3263
3276
|
const result = { ...t };
|
|
3264
3277
|
for (const [rawKey, value] of Object.entries(override)) {
|
|
3265
|
-
const { key, forceOverwrite, arrayPrepend, arrayAppend } = parseOverrideKey(rawKey);
|
|
3278
|
+
const { key, forceOverwrite, arrayPrepend, arrayAppend, arrayMergeByName } = parseOverrideKey(rawKey);
|
|
3266
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
|
+
}
|
|
3267
3296
|
if (arrayPrepend || arrayAppend) {
|
|
3268
3297
|
const existingArr = Array.isArray(existingValue) ? existingValue : [];
|
|
3269
3298
|
const overrideArr = Array.isArray(value) ? value : [value];
|
|
@@ -3296,6 +3325,69 @@ function setOverwriteEnabled(enabled) {
|
|
|
3296
3325
|
function isOverwriteFilename(filename) {
|
|
3297
3326
|
return filename === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(filename);
|
|
3298
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
|
+
}
|
|
3299
3391
|
function loadOverwriteFile() {
|
|
3300
3392
|
const dir = USER_DATA_DIR;
|
|
3301
3393
|
if (!fs3.existsSync(dir)) return [];
|
|
@@ -3310,8 +3402,11 @@ function loadOverwriteFile() {
|
|
|
3310
3402
|
try {
|
|
3311
3403
|
const content = fs3.readFileSync(filePath, "utf8");
|
|
3312
3404
|
const parsed = load(content);
|
|
3313
|
-
if (parsed && typeof parsed === "object") {
|
|
3314
|
-
|
|
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`);
|
|
3315
3410
|
}
|
|
3316
3411
|
} catch (e) {
|
|
3317
3412
|
console.warn(`\u8B66\u544A: \u8986\u5199\u6587\u4EF6 "${file}" \u89E3\u6790\u5931\u8D25: ${e.message}`);
|
|
@@ -3320,9 +3415,9 @@ function loadOverwriteFile() {
|
|
|
3320
3415
|
return results;
|
|
3321
3416
|
}
|
|
3322
3417
|
function applyOverwrite(baseConfig, preloadedFiles) {
|
|
3323
|
-
if (!isOverwriteEnabled()) return baseConfig;
|
|
3418
|
+
if (!isOverwriteEnabled()) return { ...baseConfig };
|
|
3324
3419
|
const overwriteFiles = preloadedFiles || loadOverwriteFile();
|
|
3325
|
-
if (overwriteFiles.length === 0) return baseConfig;
|
|
3420
|
+
if (overwriteFiles.length === 0) return { ...baseConfig };
|
|
3326
3421
|
let result = { ...baseConfig };
|
|
3327
3422
|
for (const file of overwriteFiles) {
|
|
3328
3423
|
result = deepMergeWithOverrides(result, file.config);
|
|
@@ -3338,7 +3433,8 @@ function listOverwriteFile() {
|
|
|
3338
3433
|
files: files.map((f) => ({
|
|
3339
3434
|
name: f.name,
|
|
3340
3435
|
path: f.path,
|
|
3341
|
-
keys: Object.keys(f.config || {})
|
|
3436
|
+
keys: Object.keys(f.config || {}),
|
|
3437
|
+
scope: summarizeMatch(f.match)
|
|
3342
3438
|
}))
|
|
3343
3439
|
};
|
|
3344
3440
|
}
|
|
@@ -3349,6 +3445,7 @@ import { createRequire } from "module";
|
|
|
3349
3445
|
var require2 = createRequire(import.meta.url);
|
|
3350
3446
|
var pkg = require2("../package.json");
|
|
3351
3447
|
var VERSION = pkg.version;
|
|
3448
|
+
var MAX_RESPONSE_BYTES = 50 * 1024 * 1024;
|
|
3352
3449
|
var sleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
3353
3450
|
var NO_COLOR = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
3354
3451
|
function colorize(code, str) {
|
|
@@ -3408,6 +3505,7 @@ function formatBytes(bytes) {
|
|
|
3408
3505
|
}
|
|
3409
3506
|
function formatTimestamp(ts) {
|
|
3410
3507
|
if (ts === void 0 || ts === null) return "\u672A\u77E5";
|
|
3508
|
+
if (ts === 0) return "\u6C38\u4E45";
|
|
3411
3509
|
try {
|
|
3412
3510
|
return new Date(ts * 1e3).toLocaleString("zh-CN");
|
|
3413
3511
|
} catch {
|
|
@@ -3429,7 +3527,7 @@ function formatDate(dateOrIso) {
|
|
|
3429
3527
|
}
|
|
3430
3528
|
}
|
|
3431
3529
|
function hasFlag(args, short, long) {
|
|
3432
|
-
return !!args && (args.includes(short) || args.includes(long));
|
|
3530
|
+
return !!args && (args.includes(short) || long !== void 0 && args.includes(long));
|
|
3433
3531
|
}
|
|
3434
3532
|
function parseIntArg(args, short, long, defaultValue) {
|
|
3435
3533
|
if (!args) return defaultValue;
|
|
@@ -3439,11 +3537,29 @@ function parseIntArg(args, short, long, defaultValue) {
|
|
|
3439
3537
|
const val = parseInt(args[i + 1], 10);
|
|
3440
3538
|
return Number.isNaN(val) ? defaultValue : val;
|
|
3441
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;
|
|
3442
3543
|
}
|
|
3443
3544
|
}
|
|
3444
3545
|
return defaultValue;
|
|
3445
3546
|
}
|
|
3446
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
|
+
}
|
|
3447
3563
|
function getNonFlagArg(args, startIdx, valueFlags = VALUE_FLAGS) {
|
|
3448
3564
|
if (!args) return null;
|
|
3449
3565
|
for (let i = startIdx; i < args.length; i++) {
|
|
@@ -3495,7 +3611,12 @@ function createHttpClient(options = {}) {
|
|
|
3495
3611
|
}
|
|
3496
3612
|
throw error;
|
|
3497
3613
|
}
|
|
3498
|
-
const
|
|
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;
|
|
3499
3620
|
return { data, headers: response.headers, status: response.status };
|
|
3500
3621
|
} finally {
|
|
3501
3622
|
clearTimeout(timer);
|
|
@@ -3503,6 +3624,29 @@ function createHttpClient(options = {}) {
|
|
|
3503
3624
|
}
|
|
3504
3625
|
};
|
|
3505
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
|
+
}
|
|
3506
3650
|
function normalizeMirrorUrl(val) {
|
|
3507
3651
|
if (!val) return null;
|
|
3508
3652
|
if (val === "direct" || val === "no" || val === "none") return null;
|
|
@@ -3522,19 +3666,23 @@ function parseMirrorArg(args) {
|
|
|
3522
3666
|
if (args.includes("--no-mirror") || args.includes("--direct")) {
|
|
3523
3667
|
return { mirror: null, isOverride: true, type: "download" };
|
|
3524
3668
|
}
|
|
3669
|
+
const mirrorAllEq = args.find((a) => a.startsWith("--mirror-all="));
|
|
3525
3670
|
const mirrorAllIdx = args.indexOf("--mirror-all");
|
|
3526
|
-
if (mirrorAllIdx >= 0) {
|
|
3527
|
-
const
|
|
3671
|
+
if (mirrorAllIdx >= 0 || mirrorAllEq) {
|
|
3672
|
+
const inline = mirrorAllEq?.slice("--mirror-all=".length);
|
|
3673
|
+
const nextArg = inline ?? args[mirrorAllIdx + 1];
|
|
3528
3674
|
if (!nextArg || nextArg.startsWith("-")) {
|
|
3529
|
-
return { mirror:
|
|
3675
|
+
return { mirror: DEFAULT_MIRROR, isOverride: true, type: "all" };
|
|
3530
3676
|
}
|
|
3531
3677
|
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "all" };
|
|
3532
3678
|
}
|
|
3679
|
+
const mirrorEq = args.find((a) => a.startsWith("--mirror="));
|
|
3533
3680
|
const mirrorIdx = args.indexOf("--mirror");
|
|
3534
|
-
if (mirrorIdx >= 0) {
|
|
3535
|
-
const
|
|
3681
|
+
if (mirrorIdx >= 0 || mirrorEq) {
|
|
3682
|
+
const inline = mirrorEq?.slice("--mirror=".length);
|
|
3683
|
+
const nextArg = inline ?? args[mirrorIdx + 1];
|
|
3536
3684
|
if (!nextArg || nextArg.startsWith("-")) {
|
|
3537
|
-
return { mirror:
|
|
3685
|
+
return { mirror: DEFAULT_MIRROR, isOverride: true, type: "download" };
|
|
3538
3686
|
}
|
|
3539
3687
|
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "download" };
|
|
3540
3688
|
}
|
|
@@ -3576,7 +3724,8 @@ function collectOverwriteProxyNames(overwriteFiles) {
|
|
|
3576
3724
|
if ((key === "+proxies" || key === "proxies+") && Array.isArray(value)) {
|
|
3577
3725
|
for (const proxy of value) {
|
|
3578
3726
|
if (proxy && typeof proxy === "object" && "name" in proxy) {
|
|
3579
|
-
|
|
3727
|
+
const name = proxy.name;
|
|
3728
|
+
if (typeof name === "string" && name.length > 0) names.push(name);
|
|
3580
3729
|
}
|
|
3581
3730
|
}
|
|
3582
3731
|
}
|
|
@@ -3614,6 +3763,7 @@ function deduplicateByName(items) {
|
|
|
3614
3763
|
});
|
|
3615
3764
|
return { result, names, duplicates };
|
|
3616
3765
|
}
|
|
3766
|
+
var NON_TARGET_RULE_TYPES = /* @__PURE__ */ new Set(["SUB-RULE"]);
|
|
3617
3767
|
function getRuleTarget(rule) {
|
|
3618
3768
|
const parts = rule.split(",");
|
|
3619
3769
|
if (parts.length < 2) return "";
|
|
@@ -3667,6 +3817,8 @@ function validateConfig(config) {
|
|
|
3667
3817
|
if (rules.length > 0) {
|
|
3668
3818
|
const removedRules = [];
|
|
3669
3819
|
config.rules = rules.filter((rule) => {
|
|
3820
|
+
const ruleType = rule.split(",")[0]?.trim().toUpperCase();
|
|
3821
|
+
if (NON_TARGET_RULE_TYPES.has(ruleType)) return true;
|
|
3670
3822
|
const target = getRuleTarget(rule);
|
|
3671
3823
|
if (!target || validNames.has(target)) return true;
|
|
3672
3824
|
removedRules.push(rule);
|
|
@@ -3678,13 +3830,14 @@ function validateConfig(config) {
|
|
|
3678
3830
|
}
|
|
3679
3831
|
return warnings;
|
|
3680
3832
|
}
|
|
3681
|
-
function buildConfig(subRawContent, mode) {
|
|
3833
|
+
function buildConfig(subRawContent, mode, scope) {
|
|
3682
3834
|
const subscriptionConfig = parseYamlOrJson(subRawContent, "\u8BA2\u9605\u5185\u5BB9");
|
|
3683
3835
|
if (!subscriptionConfig) {
|
|
3684
3836
|
throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
3685
3837
|
}
|
|
3686
3838
|
const overwriteEnabled = isOverwriteEnabled();
|
|
3687
|
-
const
|
|
3839
|
+
const allFiles = overwriteEnabled ? loadOverwriteFile() : [];
|
|
3840
|
+
const overwriteFiles = filterOverwriteFilesByScope(allFiles, scope);
|
|
3688
3841
|
const withOverwrites = applyOverwrite(subscriptionConfig, overwriteFiles);
|
|
3689
3842
|
if (overwriteFiles.length > 0) {
|
|
3690
3843
|
excludeOverwriteProxiesFromIncludeAll(withOverwrites, overwriteFiles);
|
|
@@ -3695,7 +3848,6 @@ function buildConfig(subRawContent, mode) {
|
|
|
3695
3848
|
systemConfig[key] = value;
|
|
3696
3849
|
}
|
|
3697
3850
|
}
|
|
3698
|
-
systemConfig["allow-lan"] = false;
|
|
3699
3851
|
systemConfig["external-controller"] = BASE_CONFIG["external-controller"];
|
|
3700
3852
|
systemConfig["mixed-port"] = BASE_CONFIG["mixed-port"];
|
|
3701
3853
|
delete withOverwrites["mixed-port"];
|
|
@@ -3704,6 +3856,11 @@ function buildConfig(subRawContent, mode) {
|
|
|
3704
3856
|
delete withOverwrites["external-ui"];
|
|
3705
3857
|
delete withOverwrites["external-ui-name"];
|
|
3706
3858
|
delete withOverwrites["external-ui-url"];
|
|
3859
|
+
delete withOverwrites.secret;
|
|
3860
|
+
const controllerSecret = readSettings().controller_secret;
|
|
3861
|
+
if (controllerSecret) {
|
|
3862
|
+
systemConfig.secret = controllerSecret;
|
|
3863
|
+
}
|
|
3707
3864
|
if (mode === "tun") {
|
|
3708
3865
|
systemConfig.tun = TUN_CONFIG.tun;
|
|
3709
3866
|
const subDns = withOverwrites.dns || {};
|
|
@@ -3892,6 +4049,33 @@ import path5 from "path";
|
|
|
3892
4049
|
import { spawn, spawnSync as spawnSync3 } from "child_process";
|
|
3893
4050
|
import fs5 from "fs";
|
|
3894
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;
|
|
4058
|
+
}
|
|
4059
|
+
function isSilentSigint() {
|
|
4060
|
+
return silentSigint;
|
|
4061
|
+
}
|
|
4062
|
+
function registerCleanup(fn) {
|
|
4063
|
+
cleanupFns.add(fn);
|
|
4064
|
+
return () => {
|
|
4065
|
+
cleanupFns.delete(fn);
|
|
4066
|
+
};
|
|
4067
|
+
}
|
|
4068
|
+
function runCleanup() {
|
|
4069
|
+
for (const fn of cleanupFns) {
|
|
4070
|
+
try {
|
|
4071
|
+
fn();
|
|
4072
|
+
} catch {
|
|
4073
|
+
}
|
|
4074
|
+
}
|
|
4075
|
+
cleanupFns.clear();
|
|
4076
|
+
}
|
|
4077
|
+
|
|
4078
|
+
// src/process.ts
|
|
3895
4079
|
var PROCESS_WAIT_ATTEMPTS = 50;
|
|
3896
4080
|
var PROCESS_WAIT_INTERVAL = 100;
|
|
3897
4081
|
var STARTUP_WAIT_MS = 800;
|
|
@@ -3950,6 +4134,9 @@ function checkStaleState() {
|
|
|
3950
4134
|
needsSudo: hasRootProcess || hasRootPidFile
|
|
3951
4135
|
};
|
|
3952
4136
|
}
|
|
4137
|
+
function hasRootResidue() {
|
|
4138
|
+
return checkStaleState().needsSudo;
|
|
4139
|
+
}
|
|
3953
4140
|
function savePid(pid) {
|
|
3954
4141
|
ensureDirs();
|
|
3955
4142
|
fs5.writeFileSync(PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
@@ -3968,23 +4155,10 @@ function clearPid() {
|
|
|
3968
4155
|
}
|
|
3969
4156
|
}
|
|
3970
4157
|
}
|
|
3971
|
-
function killProcess(pid
|
|
4158
|
+
function killProcess(pid) {
|
|
3972
4159
|
try {
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
if (result.status === 0) {
|
|
3976
|
-
return true;
|
|
3977
|
-
}
|
|
3978
|
-
try {
|
|
3979
|
-
process.kill(pid, "SIGKILL");
|
|
3980
|
-
return true;
|
|
3981
|
-
} catch {
|
|
3982
|
-
return false;
|
|
3983
|
-
}
|
|
3984
|
-
} else {
|
|
3985
|
-
process.kill(pid, "SIGKILL");
|
|
3986
|
-
return true;
|
|
3987
|
-
}
|
|
4160
|
+
process.kill(pid, "SIGKILL");
|
|
4161
|
+
return true;
|
|
3988
4162
|
} catch {
|
|
3989
4163
|
return false;
|
|
3990
4164
|
}
|
|
@@ -4030,7 +4204,7 @@ function cleanupAll(forceSudo = false) {
|
|
|
4030
4204
|
killedCount = pids.length;
|
|
4031
4205
|
} else {
|
|
4032
4206
|
for (const pid of pids) {
|
|
4033
|
-
if (killProcess(pid
|
|
4207
|
+
if (killProcess(pid)) {
|
|
4034
4208
|
killedCount++;
|
|
4035
4209
|
} else {
|
|
4036
4210
|
failedPids.push(pid);
|
|
@@ -4082,12 +4256,13 @@ for i in 1 2 3 4 5; do
|
|
|
4082
4256
|
fi
|
|
4083
4257
|
done
|
|
4084
4258
|
|
|
4085
|
-
# \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
|
|
4086
4261
|
echo "TUN \u542F\u52A8\u5931\u8D25"
|
|
4087
4262
|
echo ""
|
|
4088
4263
|
echo "--- \u65E5\u5FD7 ---"
|
|
4089
4264
|
tail -25 "\${LOG_FILE}" 2>/dev/null
|
|
4090
|
-
exit
|
|
4265
|
+
exit 2
|
|
4091
4266
|
`;
|
|
4092
4267
|
const scriptPath = path4.join(DIRS.runtime, "launch-tun.sh");
|
|
4093
4268
|
fs5.writeFileSync(scriptPath, scriptContent, { mode: 448 });
|
|
@@ -4145,7 +4320,7 @@ async function startMixedMode(staleState) {
|
|
|
4145
4320
|
if (staleState.needsCleanup) {
|
|
4146
4321
|
if (staleState.needsSudo) {
|
|
4147
4322
|
console.log("\n\u53D1\u73B0\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559\u8FDB\u7A0B/\u6587\u4EF6");
|
|
4148
|
-
console.log(
|
|
4323
|
+
console.log(`\u8BF7\u5148\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo && sudo rm -f ${PATHS.pidFile}`);
|
|
4149
4324
|
console.log("\u6216\u8005\u5207\u6362\u5230 TUN \u6A21\u5F0F\uFF0C\u542F\u52A8\u65F6\u4F1A\u81EA\u52A8\u6E05\u7406");
|
|
4150
4325
|
throw new Error("\u5B58\u5728\u9700\u8981 root \u6743\u9650\u6E05\u7406\u7684\u6B8B\u7559");
|
|
4151
4326
|
}
|
|
@@ -4176,9 +4351,15 @@ async function startMixedMode(staleState) {
|
|
|
4176
4351
|
detached: true,
|
|
4177
4352
|
stdio: ["ignore", logFd, logFd]
|
|
4178
4353
|
});
|
|
4354
|
+
child.on("error", () => {
|
|
4355
|
+
});
|
|
4179
4356
|
fs5.closeSync(logFd);
|
|
4180
4357
|
child.unref();
|
|
4181
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
|
+
}
|
|
4182
4363
|
savePid(pid);
|
|
4183
4364
|
await new Promise((resolve) => setTimeout(resolve, STARTUP_WAIT_MS));
|
|
4184
4365
|
if (!isRunning()) {
|
|
@@ -4219,6 +4400,9 @@ async function startTunMode(staleState) {
|
|
|
4219
4400
|
if (e.status === 1) {
|
|
4220
4401
|
throw new Error("\u5BC6\u7801\u9519\u8BEF\u6216\u53D6\u6D88");
|
|
4221
4402
|
}
|
|
4403
|
+
if (e.status === 2) {
|
|
4404
|
+
throw new Error("TUN \u542F\u52A8\u5931\u8D25\uFF08\u8BE6\u89C1\u4E0A\u65B9\u65E5\u5FD7\uFF09");
|
|
4405
|
+
}
|
|
4222
4406
|
throw new Error(e.message);
|
|
4223
4407
|
}
|
|
4224
4408
|
try {
|
|
@@ -4383,6 +4567,7 @@ function viewLogWithTail(logPath, options) {
|
|
|
4383
4567
|
tailArgs.push("-n", lines.toString());
|
|
4384
4568
|
tailArgs.push(logPath);
|
|
4385
4569
|
const tail = spawn("tail", tailArgs, { stdio: "inherit" });
|
|
4570
|
+
if (follow) setSilentSigint(true);
|
|
4386
4571
|
tail.on("close", () => process.exit(0));
|
|
4387
4572
|
tail.on("error", (e) => {
|
|
4388
4573
|
console.error(`\u65E0\u6CD5\u8BFB\u53D6\u65E5\u5FD7: ${e.message}`);
|
|
@@ -4581,7 +4766,11 @@ async function restartDaemon() {
|
|
|
4581
4766
|
|
|
4582
4767
|
// src/subscription.ts
|
|
4583
4768
|
function isGithubUrl(url) {
|
|
4584
|
-
|
|
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);
|
|
4585
4774
|
}
|
|
4586
4775
|
function getDefaultUpdateInterval(url) {
|
|
4587
4776
|
return isGithubUrl(url) ? DEFAULT_UPDATE_INTERVAL_HOURS_GITHUB : DEFAULT_UPDATE_INTERVAL_HOURS;
|
|
@@ -4593,6 +4782,14 @@ var HTTP_CLIENT = createHttpClient({ timeout: 6e4 });
|
|
|
4593
4782
|
function isMultiUrl(url) {
|
|
4594
4783
|
return url.includes(",");
|
|
4595
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
|
+
}
|
|
4596
4793
|
function splitUrls(url) {
|
|
4597
4794
|
return url.split(",").map((u) => u.trim()).filter(Boolean);
|
|
4598
4795
|
}
|
|
@@ -4708,7 +4905,7 @@ function pickSingleSubscription(subs, pattern) {
|
|
|
4708
4905
|
for (const s of subs) console.log(` ${s.name}`);
|
|
4709
4906
|
process.exit(1);
|
|
4710
4907
|
}
|
|
4711
|
-
async function downloadSubscription(url, subName = "default", signal) {
|
|
4908
|
+
async function downloadSubscription(url, subName = "default", signal, persist = true) {
|
|
4712
4909
|
let response;
|
|
4713
4910
|
try {
|
|
4714
4911
|
response = await HTTP_CLIENT.get(url, { responseType: "text", signal });
|
|
@@ -4729,9 +4926,13 @@ async function downloadSubscription(url, subName = "default", signal) {
|
|
|
4729
4926
|
}
|
|
4730
4927
|
const parsed = parseYamlOrJson(content, "\u8BA2\u9605\u5185\u5BB9");
|
|
4731
4928
|
if (!parsed) throw new Error("\u8BA2\u9605\u5185\u5BB9\u4E3A\u7A7A");
|
|
4732
|
-
|
|
4929
|
+
if (persist) {
|
|
4930
|
+
saveSubscriptionRawConfig(subName, content);
|
|
4931
|
+
}
|
|
4733
4932
|
const meta = extractSubscriptionMeta(response.headers);
|
|
4734
|
-
|
|
4933
|
+
if (persist) {
|
|
4934
|
+
saveSubscriptionMeta(subName, meta);
|
|
4935
|
+
}
|
|
4735
4936
|
const proxies = parsed.proxies;
|
|
4736
4937
|
const proxyGroups = parsed["proxy-groups"];
|
|
4737
4938
|
return {
|
|
@@ -4743,13 +4944,16 @@ async function downloadSubscription(url, subName = "default", signal) {
|
|
|
4743
4944
|
username: meta.username
|
|
4744
4945
|
};
|
|
4745
4946
|
}
|
|
4746
|
-
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;
|
|
4747
4950
|
const responses = await Promise.all(
|
|
4748
4951
|
urls.map(async (url, index) => {
|
|
4749
4952
|
try {
|
|
4750
|
-
const response = await HTTP_CLIENT.get(url, { responseType: "text", signal });
|
|
4953
|
+
const response = await HTTP_CLIENT.get(url, { responseType: "text", signal: combinedSignal });
|
|
4751
4954
|
return { url, index, response, error: null };
|
|
4752
4955
|
} catch (e) {
|
|
4956
|
+
internal.abort();
|
|
4753
4957
|
return { url, index, response: null, error: e };
|
|
4754
4958
|
}
|
|
4755
4959
|
})
|
|
@@ -4780,9 +4984,13 @@ async function downloadMergedSubscription(urls, subName, signal) {
|
|
|
4780
4984
|
}
|
|
4781
4985
|
base.proxies = baseProxies;
|
|
4782
4986
|
const mergedContent = dumpYaml(base);
|
|
4783
|
-
|
|
4987
|
+
if (persist) {
|
|
4988
|
+
saveSubscriptionRawConfig(subName, mergedContent);
|
|
4989
|
+
}
|
|
4784
4990
|
const meta = extractSubscriptionMeta(responses[0].response?.headers);
|
|
4785
|
-
|
|
4991
|
+
if (persist) {
|
|
4992
|
+
saveSubscriptionMeta(subName, meta);
|
|
4993
|
+
}
|
|
4786
4994
|
const proxyGroups = base["proxy-groups"];
|
|
4787
4995
|
return {
|
|
4788
4996
|
proxies: baseProxies.length,
|
|
@@ -4798,7 +5006,8 @@ function prepareConfigForStart(mode, subName = "default") {
|
|
|
4798
5006
|
if (!rawContent) {
|
|
4799
5007
|
throw new Error(`\u672A\u627E\u5230\u8BA2\u9605\u914D\u7F6E "${subName}"\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605`);
|
|
4800
5008
|
}
|
|
4801
|
-
const
|
|
5009
|
+
const subUrl = getSubscriptions().find((s) => s.name === subName)?.url;
|
|
5010
|
+
const buildResult = buildConfig(rawContent, mode, { subName, subUrl });
|
|
4802
5011
|
if (buildResult.warnings.length > 0) {
|
|
4803
5012
|
for (const warning of buildResult.warnings) {
|
|
4804
5013
|
console.log(`${colors.yellow("\u81EA\u52A8\u4FEE\u590D:")} ${warning}`);
|
|
@@ -4857,16 +5066,23 @@ async function autoUpdateStaleSubscription(options = {}) {
|
|
|
4857
5066
|
}
|
|
4858
5067
|
const timeoutMs = options.timeout ?? DEFAULT_AUTO_UPDATE_TIMEOUT;
|
|
4859
5068
|
const controller = new AbortController();
|
|
4860
|
-
|
|
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
|
+
);
|
|
4861
5078
|
try {
|
|
4862
|
-
|
|
5079
|
+
await withTimeout(updatePromise, timeoutMs);
|
|
4863
5080
|
} catch (e) {
|
|
4864
|
-
if (e instanceof TimeoutError)
|
|
4865
|
-
|
|
4866
|
-
|
|
4867
|
-
|
|
4868
|
-
}
|
|
4869
|
-
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`));
|
|
4870
5086
|
}
|
|
4871
5087
|
let updatedCount = 0;
|
|
4872
5088
|
for (const r of results) {
|
|
@@ -4949,6 +5165,19 @@ function normalizeProxyNamesBeforeSave(parsed) {
|
|
|
4949
5165
|
group.proxies = group.proxies.map((name) => renameMap.get(name) || name);
|
|
4950
5166
|
}
|
|
4951
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
|
+
}
|
|
4952
5181
|
return renameMap.size;
|
|
4953
5182
|
}
|
|
4954
5183
|
function cleanDeadProxies(parsed, deadNames) {
|
|
@@ -4978,11 +5207,14 @@ function cleanDeadProxies(parsed, deadNames) {
|
|
|
4978
5207
|
group.proxies = group.proxies.filter((name) => !removedGroupNames.has(name));
|
|
4979
5208
|
}
|
|
4980
5209
|
}
|
|
5210
|
+
}
|
|
5211
|
+
const removedTargets = /* @__PURE__ */ new Set([...removedGroupNames, ...deadNames]);
|
|
5212
|
+
if (removedTargets.size > 0) {
|
|
4981
5213
|
const rules = parsed.raw.rules;
|
|
4982
5214
|
if (Array.isArray(rules)) {
|
|
4983
5215
|
parsed.raw.rules = rules.filter((rule) => {
|
|
4984
5216
|
if (typeof rule !== "string") return true;
|
|
4985
|
-
return !
|
|
5217
|
+
return !removedTargets.has(getRuleTarget(rule));
|
|
4986
5218
|
});
|
|
4987
5219
|
}
|
|
4988
5220
|
}
|
|
@@ -5357,6 +5589,12 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5357
5589
|
["-L", "--progress-bar", "--connect-timeout", "30", "--max-time", String(Math.floor(KERNEL_DOWNLOAD_TIMEOUT / 1e3)), "-o", tempPath, downloadUrl],
|
|
5358
5590
|
{ stdio: "inherit" }
|
|
5359
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
|
+
}
|
|
5360
5598
|
if (curlResult.status !== 0) {
|
|
5361
5599
|
try {
|
|
5362
5600
|
fs7.unlinkSync(tempPath);
|
|
@@ -5374,6 +5612,15 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5374
5612
|
let extractedBinary = null;
|
|
5375
5613
|
try {
|
|
5376
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
|
+
}
|
|
5377
5624
|
const tarResult = spawnSync5("tar", ["-xzf", tempPath, "-C", extractPath], { stdio: ["ignore", "ignore", "inherit"], timeout: 6e4 });
|
|
5378
5625
|
if (tarResult.error) throw tarResult.error;
|
|
5379
5626
|
if (tarResult.status !== 0) throw new Error(`tar \u9000\u51FA\u7801 ${tarResult.status}`);
|
|
@@ -5568,7 +5815,7 @@ function cmdLogs(args) {
|
|
|
5568
5815
|
}
|
|
5569
5816
|
|
|
5570
5817
|
// src/commands/overwrite.ts
|
|
5571
|
-
import
|
|
5818
|
+
import path7 from "path";
|
|
5572
5819
|
|
|
5573
5820
|
// src/runtime.ts
|
|
5574
5821
|
function getRuntimeMode() {
|
|
@@ -5596,6 +5843,78 @@ async function launchOrRestart(mode) {
|
|
|
5596
5843
|
return result.pid;
|
|
5597
5844
|
}
|
|
5598
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
|
+
|
|
5599
5918
|
// src/commands/status.ts
|
|
5600
5919
|
function printStatus() {
|
|
5601
5920
|
const status = getStatus();
|
|
@@ -5607,7 +5926,7 @@ function printStatus() {
|
|
|
5607
5926
|
const { running, pid, daemon: daemonManaged } = state;
|
|
5608
5927
|
console.log("");
|
|
5609
5928
|
let modeLabel = "";
|
|
5610
|
-
if (info
|
|
5929
|
+
if (info) {
|
|
5611
5930
|
modeLabel = colors.cyan(info.tun ? " (TUN)" : " (Mixed)");
|
|
5612
5931
|
}
|
|
5613
5932
|
const statusText = running ? colors.green("\u25CF \u8FD0\u884C\u4E2D") : colors.yellow("\u4E0D\u5728\u8FD0\u884C");
|
|
@@ -5620,7 +5939,10 @@ function printStatus() {
|
|
|
5620
5939
|
}
|
|
5621
5940
|
}
|
|
5622
5941
|
if (info) {
|
|
5623
|
-
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) {
|
|
5624
5946
|
console.log(`${colors.gray("\u7AEF\u53E3: ")}${info.mixedPort}`);
|
|
5625
5947
|
} else {
|
|
5626
5948
|
const ports = [];
|
|
@@ -5676,903 +5998,851 @@ async function cmdStop() {
|
|
|
5676
5998
|
console.log(colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B"));
|
|
5677
5999
|
}
|
|
5678
6000
|
|
|
5679
|
-
// src/
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
// src/lifecycle.ts
|
|
5685
|
-
var cleanupFns = /* @__PURE__ */ new Set();
|
|
5686
|
-
function registerCleanup(fn) {
|
|
5687
|
-
cleanupFns.add(fn);
|
|
5688
|
-
return () => {
|
|
5689
|
-
cleanupFns.delete(fn);
|
|
5690
|
-
};
|
|
5691
|
-
}
|
|
5692
|
-
function runCleanup() {
|
|
5693
|
-
for (const fn of cleanupFns) {
|
|
5694
|
-
try {
|
|
5695
|
-
fn();
|
|
5696
|
-
} catch {
|
|
5697
|
-
}
|
|
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);
|
|
5698
6006
|
}
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
data: path7.join(TEST_DIR, "data"),
|
|
5706
|
-
runtime: path7.join(TEST_DIR, "runtime")
|
|
5707
|
-
};
|
|
5708
|
-
var TEST_PATHS = {
|
|
5709
|
-
configFile: path7.join(TEST_DIRS.runtime, "config.yaml"),
|
|
5710
|
-
pidFile: path7.join(TEST_DIRS.runtime, "pid"),
|
|
5711
|
-
logFile: path7.join(TEST_DIR, "test.log")
|
|
5712
|
-
};
|
|
5713
|
-
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
5714
|
-
function ensureTestDirs() {
|
|
5715
|
-
for (const dir of Object.values(TEST_DIRS)) {
|
|
5716
|
-
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);
|
|
5717
6013
|
}
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
const
|
|
5725
|
-
if (!
|
|
5726
|
-
|
|
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);
|
|
5727
6024
|
}
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
if (proxies.length === 0) {
|
|
5731
|
-
throw new Error(`\u8BA2\u9605 "${subName}" \u6CA1\u6709\u6709\u6548\u8282\u70B9`);
|
|
6025
|
+
if (!skipUpdate) {
|
|
6026
|
+
await autoUpdateStaleSubscription({ timeout: updateTimeout });
|
|
5732
6027
|
}
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
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);
|
|
5739
6034
|
}
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
{
|
|
5746
|
-
name: "PROXY",
|
|
5747
|
-
type: "select",
|
|
5748
|
-
proxies: proxies.map((p) => p.name)
|
|
5749
|
-
}
|
|
5750
|
-
],
|
|
5751
|
-
rules: ["MATCH,PROXY"]
|
|
5752
|
-
};
|
|
5753
|
-
const content = dumpYaml(config);
|
|
5754
|
-
fs8.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
5755
|
-
}
|
|
5756
|
-
async function startTestInstance() {
|
|
5757
|
-
const binary = PATHS.mihomoBinary;
|
|
5758
|
-
if (!fs8.existsSync(binary)) throw new Error("\u672A\u627E\u5230 mihomo \u5185\u6838");
|
|
5759
|
-
stopTestInstance();
|
|
5760
|
-
const logFd = fs8.openSync(TEST_PATHS.logFile, "a");
|
|
5761
|
-
const child = spawn2(binary, ["-d", TEST_DIRS.data, "-f", TEST_PATHS.configFile], {
|
|
5762
|
-
detached: true,
|
|
5763
|
-
stdio: ["ignore", logFd, logFd]
|
|
5764
|
-
});
|
|
5765
|
-
fs8.closeSync(logFd);
|
|
5766
|
-
child.unref();
|
|
5767
|
-
const pid = child.pid;
|
|
5768
|
-
fs8.writeFileSync(TEST_PATHS.pidFile, pid.toString(), { mode: 384 });
|
|
5769
|
-
const client = createHttpClient({ timeout: 2e3 });
|
|
5770
|
-
let ready = false;
|
|
5771
|
-
for (let i = 0; i < 60; i++) {
|
|
5772
|
-
if (!isProcessRunning(pid)) break;
|
|
5773
|
-
try {
|
|
5774
|
-
await client.get(`${TEST_API}/version`);
|
|
5775
|
-
ready = true;
|
|
5776
|
-
break;
|
|
5777
|
-
} catch {
|
|
5778
|
-
await sleep(500);
|
|
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...`);
|
|
5779
6040
|
}
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
errorDetail = fs8.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
|
|
5785
|
-
} catch {
|
|
6041
|
+
handleStopResult(stop());
|
|
6042
|
+
if (hasProcess) {
|
|
6043
|
+
console.log(`${colors.green("\u5DF2\u505C\u6B62\u8FDB\u7A0B")}
|
|
6044
|
+
`);
|
|
5786
6045
|
}
|
|
5787
|
-
throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
5788
|
-
${errorDetail}` : ""}`);
|
|
5789
6046
|
}
|
|
5790
|
-
|
|
5791
|
-
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
5792
|
-
}
|
|
5793
|
-
}
|
|
5794
|
-
function stopTestInstance() {
|
|
5795
|
-
let pid;
|
|
6047
|
+
let configInfo;
|
|
5796
6048
|
try {
|
|
5797
|
-
|
|
5798
|
-
} catch {
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
if (pid > 0 && isProcessRunning(pid)) {
|
|
5802
|
-
process.kill(pid, "SIGKILL");
|
|
5803
|
-
for (let i = 0; i < 20; i++) {
|
|
5804
|
-
if (!isProcessRunning(pid)) break;
|
|
5805
|
-
sleepSync(100);
|
|
5806
|
-
}
|
|
6049
|
+
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
6050
|
+
} catch (e) {
|
|
6051
|
+
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
6052
|
+
process.exit(1);
|
|
5807
6053
|
}
|
|
6054
|
+
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
6055
|
+
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
5808
6056
|
try {
|
|
5809
|
-
|
|
5810
|
-
|
|
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);
|
|
5811
6068
|
}
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
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) {
|
|
6075
|
+
console.log("");
|
|
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);
|
|
6103
|
+
}
|
|
6104
|
+
}
|
|
6105
|
+
saveSubscriptionCache(sub.name, { last_auto_clean_at: (/* @__PURE__ */ new Date()).toISOString() });
|
|
6106
|
+
}
|
|
5827
6107
|
}
|
|
6108
|
+
printStatus();
|
|
5828
6109
|
}
|
|
5829
6110
|
|
|
5830
|
-
// src/commands/
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
5843
|
-
}
|
|
5844
|
-
|
|
5845
|
-
|
|
5846
|
-
|
|
5847
|
-
|
|
5848
|
-
}
|
|
5849
|
-
if (
|
|
5850
|
-
|
|
5851
|
-
resultMap.set(result.name, { result, round });
|
|
5852
|
-
render(index + 1, total);
|
|
5853
|
-
},
|
|
5854
|
-
onRetryRound(round, count) {
|
|
5855
|
-
if (IS_TTY) {
|
|
5856
|
-
process.stdout.write("\n");
|
|
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");
|
|
6120
|
+
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")}`);
|
|
6123
|
+
console.log("");
|
|
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}`);
|
|
5857
6132
|
}
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
dead = 0;
|
|
5861
|
-
},
|
|
5862
|
-
finish() {
|
|
5863
|
-
if (IS_TTY) {
|
|
5864
|
-
process.stdout.write("\n");
|
|
6133
|
+
if (f.keys.length > 0) {
|
|
6134
|
+
console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
|
|
5865
6135
|
}
|
|
6136
|
+
});
|
|
6137
|
+
console.log("");
|
|
6138
|
+
}
|
|
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");
|
|
5866
6150
|
console.log("");
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
const prefix = `[${i + 1}/${total}]`;
|
|
5874
|
-
if (result.delay !== null) {
|
|
5875
|
-
const delayColor = result.delay < 300 ? colors.green : result.delay < 800 ? colors.yellow : colors.red;
|
|
5876
|
-
const retryNote = round > 1 ? colors.gray(` (\u7B2C${round}\u8F6E\u901A\u8FC7)`) : "";
|
|
5877
|
-
console.log(`${prefix} ${colors.green("\u2713")} ${result.name} ${delayColor(`${result.delay}ms`)}${retryNote}`);
|
|
5878
|
-
} else {
|
|
5879
|
-
console.log(`${prefix} ${colors.red("\u2717")} ${result.name} ${colors.gray(result.error || "timeout")}`);
|
|
5880
|
-
}
|
|
5881
|
-
}
|
|
6151
|
+
printOverwriteList();
|
|
6152
|
+
return;
|
|
6153
|
+
}
|
|
6154
|
+
setOverwriteEnabled(true);
|
|
6155
|
+
console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
|
|
6156
|
+
if (restartNeeded) {
|
|
5882
6157
|
console.log("");
|
|
6158
|
+
await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
|
|
6159
|
+
return;
|
|
5883
6160
|
}
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
const parts = [`\u79FB\u9664 ${result.removedProxies} \u4E2A\u8282\u70B9`];
|
|
5888
|
-
if (result.removedGroups > 0) parts.push(`\u5220\u9664 ${result.removedGroups} \u4E2A\u7A7A\u5206\u7EC4`);
|
|
5889
|
-
if (result.updatedGroups > 0) parts.push(`\u66F4\u65B0 ${result.updatedGroups} \u4E2A\u5206\u7EC4`);
|
|
5890
|
-
return parts.join(", ");
|
|
5891
|
-
}
|
|
5892
|
-
function formatTestSummary(summary) {
|
|
5893
|
-
return `\u7ED3\u679C: ${colors.green(`${summary.alive} \u5B58\u6D3B`)} / ${colors.red(`${summary.dead} \u5931\u8D25`)} / ${summary.total} \u603B\u8BA1`;
|
|
5894
|
-
}
|
|
5895
|
-
function githubRepoUrl(rawUrl) {
|
|
5896
|
-
const match = rawUrl.match(/raw\.githubusercontent\.com\/([^/]+\/[^/]+)/);
|
|
5897
|
-
if (match) return `https://github.com/${match[1]}`;
|
|
5898
|
-
return null;
|
|
5899
|
-
}
|
|
5900
|
-
function resolveTestTarget(args) {
|
|
5901
|
-
const subs = getSubscriptions();
|
|
5902
|
-
if (subs.length === 0) {
|
|
5903
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605");
|
|
5904
|
-
process.exit(1);
|
|
6161
|
+
console.log("");
|
|
6162
|
+
printOverwriteList();
|
|
6163
|
+
return;
|
|
5905
6164
|
}
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
if (
|
|
5916
|
-
console.
|
|
5917
|
-
|
|
6165
|
+
if (action === "off" || action === "disable") {
|
|
6166
|
+
if (!isOverwriteEnabled()) {
|
|
6167
|
+
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u7981\u7528\u72B6\u6001");
|
|
6168
|
+
console.log("");
|
|
6169
|
+
printOverwriteList();
|
|
6170
|
+
return;
|
|
6171
|
+
}
|
|
6172
|
+
setOverwriteEnabled(false);
|
|
6173
|
+
console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
|
|
6174
|
+
if (restartNeeded) {
|
|
6175
|
+
console.log("");
|
|
6176
|
+
await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
|
|
6177
|
+
return;
|
|
5918
6178
|
}
|
|
5919
|
-
target = activeSub;
|
|
5920
|
-
}
|
|
5921
|
-
return { target, timeout, concurrency };
|
|
5922
|
-
}
|
|
5923
|
-
function printRestartHintIfRunning() {
|
|
5924
|
-
if (getRunningState().running) {
|
|
5925
|
-
console.log(colors.yellow("\u63D0\u793A: \u8FD0\u884C\u4E2D\u7684\u5B9E\u4F8B\u4ECD\u4F7F\u7528\u65E7\u914D\u7F6E\uFF0C\u6267\u884C mihomo start \u4F7F\u66F4\u65B0\u751F\u6548"));
|
|
5926
6179
|
console.log("");
|
|
6180
|
+
printOverwriteList();
|
|
6181
|
+
return;
|
|
5927
6182
|
}
|
|
6183
|
+
console.log("");
|
|
6184
|
+
printOverwriteList();
|
|
5928
6185
|
}
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
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"
|
|
5941
6263
|
}
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
const
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
console.log(` ${colors.gray("\u7528\u6237: ")}${s.username}`);
|
|
5953
|
-
}
|
|
5954
|
-
if (s.download !== void 0 || s.total !== void 0) {
|
|
5955
|
-
const used = (s.upload || 0) + (s.download || 0);
|
|
5956
|
-
const usedStr = formatBytes(used);
|
|
5957
|
-
const totalStr = formatBytes(s.total);
|
|
5958
|
-
let percentStr = "";
|
|
5959
|
-
if (s.total && s.total > 0) {
|
|
5960
|
-
const percent = Math.min(used / s.total * 100, 100);
|
|
5961
|
-
percentStr = ` (${percent.toFixed(1)}%)`;
|
|
5962
|
-
}
|
|
5963
|
-
console.log(` ${colors.gray("\u6D41\u91CF: ")}${usedStr} / ${totalStr}${percentStr}`);
|
|
5964
|
-
}
|
|
5965
|
-
if (s.expire !== void 0) {
|
|
5966
|
-
console.log(` ${colors.gray("\u5230\u671F: ")}${formatTimestamp(s.expire)}`);
|
|
5967
|
-
}
|
|
5968
|
-
if (s.web_page_url) {
|
|
5969
|
-
console.log(` ${colors.gray("\u9875\u9762: ")}${s.web_page_url}`);
|
|
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);
|
|
5970
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
|
+
});
|
|
5971
6285
|
});
|
|
5972
|
-
|
|
5973
|
-
console.log("\u5207\u6362\u8BA2\u9605: mihomo sub use <name>");
|
|
5974
|
-
console.log("\u65B0\u589E\u8BA2\u9605: mihomo sub add <url> [name]");
|
|
5975
|
-
console.log("\u66F4\u65B0\u8BA2\u9605: mihomo sub update [name]");
|
|
5976
|
-
console.log("\u5220\u9664\u8BA2\u9605: mihomo sub remove <name>");
|
|
5977
|
-
console.log("\u6D4B\u8BD5\u8282\u70B9: mihomo sub test [name]");
|
|
5978
|
-
console.log("\u6E05\u7406\u8282\u70B9: mihomo sub clean [name]");
|
|
5979
|
-
console.log("\u6253\u5F00\u9875\u9762: mihomo sub web [name]");
|
|
5980
|
-
console.log("");
|
|
6286
|
+
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
5981
6287
|
}
|
|
5982
|
-
async function
|
|
5983
|
-
const
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
const url = args[2];
|
|
5990
|
-
const name = args[3] || "default";
|
|
5991
|
-
if (!url) {
|
|
5992
|
-
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
5993
|
-
process.exit(1);
|
|
5994
|
-
}
|
|
5995
|
-
if (isMultiUrl(url)) {
|
|
5996
|
-
const urls = splitUrls(url);
|
|
5997
|
-
for (const u of urls) {
|
|
5998
|
-
if (!u.startsWith("http")) {
|
|
5999
|
-
console.error(`\u9519\u8BEF: \u65E0\u6548\u7684 URL: ${u}`);
|
|
6000
|
-
process.exit(1);
|
|
6001
|
-
}
|
|
6002
|
-
}
|
|
6003
|
-
console.log(`\u6DFB\u52A0\u5408\u5E76\u8BA2\u9605: ${name} (${urls.length} \u4E2A\u6E90)`);
|
|
6004
|
-
try {
|
|
6005
|
-
addSubscription(url, name);
|
|
6006
|
-
setDefaultSubscription(name);
|
|
6007
|
-
const info = await downloadMergedSubscription(urls, name);
|
|
6008
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)}, \u5408\u5E76 ${urls.length} \u6E90)`);
|
|
6009
|
-
} catch (e) {
|
|
6010
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
6011
|
-
process.exit(1);
|
|
6012
|
-
}
|
|
6013
|
-
} else {
|
|
6014
|
-
if (!url.startsWith("http")) {
|
|
6015
|
-
console.error("\u9519\u8BEF: \u8BF7\u63D0\u4F9B\u6709\u6548\u7684\u8BA2\u9605 URL");
|
|
6016
|
-
process.exit(1);
|
|
6017
|
-
}
|
|
6018
|
-
console.log(`\u6DFB\u52A0\u8BA2\u9605: ${name}`);
|
|
6019
|
-
try {
|
|
6020
|
-
addSubscription(url, name);
|
|
6021
|
-
setDefaultSubscription(name);
|
|
6022
|
-
const info = await downloadSubscription(url, name);
|
|
6023
|
-
const repoUrl = githubRepoUrl(url);
|
|
6024
|
-
if (repoUrl) saveSubscriptionCache(name, { web_page_url: repoUrl });
|
|
6025
|
-
console.log(`\u5DF2\u6DFB\u52A0\u5E76\u5207\u6362\u5230 "${name}" (${formatProxySummary(info)})`);
|
|
6026
|
-
} catch (e) {
|
|
6027
|
-
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
6028
|
-
process.exit(1);
|
|
6029
|
-
}
|
|
6030
|
-
}
|
|
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(", ")}`);
|
|
6031
6295
|
console.log("");
|
|
6032
|
-
|
|
6033
|
-
|
|
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);
|
|
6034
6298
|
}
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
|
|
6041
|
-
}
|
|
6042
|
-
if (
|
|
6043
|
-
console.
|
|
6044
|
-
const results = await Promise.all(subs.map((sub) => tryUpdateOne(sub)));
|
|
6045
|
-
let ok = 0;
|
|
6046
|
-
for (const r of results) {
|
|
6047
|
-
if (r.success) ok++;
|
|
6048
|
-
printUpdateResult(r);
|
|
6049
|
-
}
|
|
6050
|
-
if (ok === 0) process.exit(1);
|
|
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(", ")}`);
|
|
6051
6308
|
console.log("");
|
|
6052
|
-
|
|
6053
|
-
await printSubscriptionList();
|
|
6054
|
-
return;
|
|
6055
|
-
}
|
|
6056
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
6057
|
-
const target = pickSingleSubscription(matches, name);
|
|
6058
|
-
console.log(`\u66F4\u65B0\u8BA2\u9605: ${target.name}`);
|
|
6059
|
-
const result = await tryUpdateOne(target);
|
|
6060
|
-
if (!result.success) {
|
|
6061
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${(result.error || "").split("\n")[0]}`);
|
|
6062
|
-
process.exit(1);
|
|
6063
|
-
}
|
|
6064
|
-
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
|
|
6065
|
-
console.log("");
|
|
6066
|
-
printRestartHintIfRunning();
|
|
6067
|
-
await printSubscriptionList();
|
|
6068
|
-
return;
|
|
6069
|
-
}
|
|
6070
|
-
if (action === "use") {
|
|
6071
|
-
const name = args[2];
|
|
6072
|
-
const subs = getSubscriptions();
|
|
6073
|
-
if (!name) {
|
|
6074
|
-
console.error("\u9519\u8BEF: \u8BF7\u6307\u5B9A\u8BA2\u9605\u540D\u79F0");
|
|
6075
|
-
if (subs.length > 0) {
|
|
6076
|
-
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
6077
|
-
for (const s of subs) console.log(` ${s.name}`);
|
|
6078
|
-
}
|
|
6079
|
-
process.exit(1);
|
|
6080
|
-
}
|
|
6081
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
6082
|
-
const target = pickSingleSubscription(matches, name);
|
|
6083
|
-
const currentDefault = getActiveSubscription();
|
|
6084
|
-
const isAlreadyDefault = currentDefault && currentDefault.name === target.name;
|
|
6085
|
-
if (isAlreadyDefault) {
|
|
6086
|
-
console.log(`"${target.name}" \u5DF2\u662F\u5F53\u524D\u4F7F\u7528\u7684\u8BA2\u9605`);
|
|
6309
|
+
console.log(`\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`);
|
|
6087
6310
|
console.log("");
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
const success = setDefaultSubscription(target.name);
|
|
6094
|
-
if (success) {
|
|
6095
|
-
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
6096
|
-
} else {
|
|
6097
|
-
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
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");
|
|
6098
6316
|
process.exit(1);
|
|
6099
6317
|
}
|
|
6100
|
-
|
|
6101
|
-
|
|
6102
|
-
|
|
6103
|
-
return;
|
|
6104
|
-
}
|
|
6105
|
-
console.log("");
|
|
6106
|
-
await printSubscriptionList();
|
|
6107
|
-
return;
|
|
6318
|
+
targets = matched;
|
|
6319
|
+
} else {
|
|
6320
|
+
targets = RESET_TARGETS.filter((t) => !["settings", "kernel", "overwrites", "daemon"].includes(t.id));
|
|
6108
6321
|
}
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
process.exit(1);
|
|
6115
|
-
}
|
|
6116
|
-
let target;
|
|
6117
|
-
if (name) {
|
|
6118
|
-
const matches = findSubscriptionFuzzy(subs, name);
|
|
6119
|
-
target = pickSingleSubscription(matches, name);
|
|
6120
|
-
} else {
|
|
6121
|
-
target = getActiveSubscription() || subs[0];
|
|
6122
|
-
}
|
|
6123
|
-
const cached = subs.find((s) => s.name === target.name);
|
|
6124
|
-
let webPageUrl = cached?.web_page_url;
|
|
6125
|
-
if (!webPageUrl) {
|
|
6126
|
-
console.log("\u8BA2\u9605\u4FE1\u606F\u4E2D\u7F3A\u5C11\u9875\u9762\u5730\u5740\uFF0C\u6B63\u5728\u66F4\u65B0\u8BA2\u9605...");
|
|
6127
|
-
try {
|
|
6128
|
-
await downloadSubscription(target.url, target.name);
|
|
6129
|
-
const cache = readSubscriptionCache();
|
|
6130
|
-
if (cache[target.name]?.web_page_url) {
|
|
6131
|
-
webPageUrl = cache[target.name].web_page_url;
|
|
6132
|
-
} else {
|
|
6133
|
-
console.error("\u9519\u8BEF: \u8BE5\u8BA2\u9605\u6CA1\u6709\u63D0\u4F9B\u9875\u9762\u5730\u5740");
|
|
6134
|
-
process.exit(1);
|
|
6135
|
-
}
|
|
6136
|
-
} catch (e) {
|
|
6137
|
-
console.error(`\u66F4\u65B0\u5931\u8D25: ${e.message}`);
|
|
6138
|
-
process.exit(1);
|
|
6322
|
+
for (const t of targets) {
|
|
6323
|
+
if (t.checkEmpty?.()) {
|
|
6324
|
+
if (targets.length === 1) {
|
|
6325
|
+
console.log(t.emptyMsg);
|
|
6326
|
+
return;
|
|
6139
6327
|
}
|
|
6140
6328
|
}
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
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");
|
|
6146
6345
|
return;
|
|
6147
6346
|
}
|
|
6148
|
-
if (
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
console.error("\
|
|
6153
|
-
|
|
6154
|
-
console.log("\n\u53EF\u7528\u8BA2\u9605:");
|
|
6155
|
-
for (const s of subs) console.log(` ${s.name}`);
|
|
6156
|
-
}
|
|
6157
|
-
process.exit(1);
|
|
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;
|
|
6158
6353
|
}
|
|
6159
|
-
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
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));
|
|
6165
6361
|
}
|
|
6166
|
-
console.log("");
|
|
6167
|
-
await printSubscriptionList({ autoUpdate: false });
|
|
6168
|
-
return;
|
|
6169
6362
|
}
|
|
6170
|
-
|
|
6171
|
-
const
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
return autoCleanSubscription(target.name, {
|
|
6179
|
-
timeout,
|
|
6180
|
-
concurrency,
|
|
6181
|
-
rounds,
|
|
6182
|
-
apiBase,
|
|
6183
|
-
onResult: progress.onResult,
|
|
6184
|
-
onRetryRound: progress.onRetryRound
|
|
6185
|
-
});
|
|
6186
|
-
});
|
|
6187
|
-
progress.finish();
|
|
6188
|
-
console.log(formatTestSummary(result.summary));
|
|
6189
|
-
if (result.skipped) {
|
|
6190
|
-
console.log("");
|
|
6191
|
-
console.log(colors.yellow("\u5B58\u6D3B\u8282\u70B9\u4E0D\u8DB3 1%\uFF0C\u8DF3\u8FC7\u6E05\u7406\u3002\u8BF7\u68C0\u67E5\u539F\u59CB\u8BA2\u9605\u662F\u5426\u6709\u6548"));
|
|
6192
|
-
} else if (result.removedProxies > 0) {
|
|
6193
|
-
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6194
|
-
if (getRunningState().running) {
|
|
6195
|
-
console.log("");
|
|
6196
|
-
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
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}`);
|
|
6370
|
+
}
|
|
6197
6371
|
}
|
|
6198
6372
|
}
|
|
6199
|
-
|
|
6373
|
+
t.onAfter?.();
|
|
6200
6374
|
}
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
console.log(`\u8D85\u65F6: ${timeout}ms \u5E76\u53D1: ${concurrency}`);
|
|
6205
|
-
console.log("");
|
|
6206
|
-
const progress = createProgressPrinter();
|
|
6207
|
-
const summary = await withTestInstance(target.name, async (apiBase) => {
|
|
6208
|
-
return testSubscriptionProxies(target.name, {
|
|
6209
|
-
timeout,
|
|
6210
|
-
concurrency,
|
|
6211
|
-
apiBase,
|
|
6212
|
-
onResult: progress.onResult
|
|
6213
|
-
});
|
|
6214
|
-
});
|
|
6215
|
-
progress.finish();
|
|
6216
|
-
console.log(formatTestSummary(summary));
|
|
6217
|
-
return;
|
|
6375
|
+
ensureDirs();
|
|
6376
|
+
if (targets.some((t) => t.id === "settings")) {
|
|
6377
|
+
invalidateSettingsCache();
|
|
6218
6378
|
}
|
|
6219
|
-
console.
|
|
6220
|
-
console.log("\u7528\u6CD5: mihomo sub [list|use|add|update|remove|web|test|clean]");
|
|
6221
|
-
process.exit(1);
|
|
6379
|
+
console.log(colors.green(`\u5DF2\u91CD\u7F6E: ${targets.map((t) => t.label).join("\u3001")}`));
|
|
6222
6380
|
}
|
|
6223
6381
|
|
|
6224
|
-
// src/
|
|
6225
|
-
|
|
6226
|
-
|
|
6227
|
-
|
|
6228
|
-
|
|
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 });
|
|
6229
6400
|
}
|
|
6230
|
-
|
|
6231
|
-
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
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}"`);
|
|
6236
6410
|
}
|
|
6237
|
-
const
|
|
6238
|
-
const
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
const updateTimeout = parseIntArg(args, "-u", "--update-timeout", DEFAULT_AUTO_UPDATE_TIMEOUT);
|
|
6242
|
-
const sub = getActiveSubscription();
|
|
6243
|
-
if (!sub) {
|
|
6244
|
-
console.error("\u9519\u8BEF: \u6CA1\u6709\u8BA2\u9605\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
6245
|
-
process.exit(1);
|
|
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`);
|
|
6246
6415
|
}
|
|
6247
|
-
|
|
6248
|
-
|
|
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}`;
|
|
6422
|
+
}
|
|
6249
6423
|
}
|
|
6250
|
-
|
|
6251
|
-
|
|
6252
|
-
|
|
6253
|
-
|
|
6254
|
-
|
|
6255
|
-
|
|
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)
|
|
6432
|
+
}
|
|
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);
|
|
6256
6465
|
}
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6466
|
+
}
|
|
6467
|
+
if (!isProcessRunning(pid)) {
|
|
6468
|
+
let errorDetail = "";
|
|
6469
|
+
try {
|
|
6470
|
+
errorDetail = fs9.readFileSync(TEST_PATHS.logFile, "utf8").slice(-1e3);
|
|
6471
|
+
} catch {
|
|
6261
6472
|
}
|
|
6473
|
+
throw new Error(`\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u5931\u8D25${errorDetail ? `
|
|
6474
|
+
${errorDetail}` : ""}`);
|
|
6262
6475
|
}
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
6266
|
-
} catch (e) {
|
|
6267
|
-
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
6268
|
-
process.exit(1);
|
|
6476
|
+
if (!ready) {
|
|
6477
|
+
throw new Error("\u6D4B\u8BD5\u5B9E\u4F8B\u542F\u52A8\u8D85\u65F6\uFF0CAPI \u672A\u54CD\u5E94");
|
|
6269
6478
|
}
|
|
6270
|
-
|
|
6271
|
-
|
|
6479
|
+
}
|
|
6480
|
+
function stopTestInstance() {
|
|
6481
|
+
let pid;
|
|
6272
6482
|
try {
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
|
|
6276
|
-
}
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6483
|
+
pid = parseInt(fs9.readFileSync(TEST_PATHS.pidFile, "utf8").trim(), 10);
|
|
6484
|
+
} catch {
|
|
6485
|
+
return;
|
|
6486
|
+
}
|
|
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);
|
|
6282
6492
|
}
|
|
6493
|
+
}
|
|
6494
|
+
try {
|
|
6495
|
+
fs9.unlinkSync(TEST_PATHS.pidFile);
|
|
6496
|
+
} catch {
|
|
6497
|
+
}
|
|
6498
|
+
}
|
|
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();
|
|
6513
|
+
}
|
|
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");
|
|
6283
6526
|
process.exit(1);
|
|
6284
6527
|
}
|
|
6285
|
-
const
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
onRetryRound: progress.onRetryRound
|
|
6298
|
-
});
|
|
6299
|
-
progress.finish();
|
|
6300
|
-
console.log(formatTestSummary(cleanResult.summary));
|
|
6301
|
-
if (cleanResult.skipped) {
|
|
6302
|
-
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"));
|
|
6303
|
-
} else if (cleanResult.removedProxies > 0) {
|
|
6304
|
-
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(cleanResult)}`);
|
|
6305
|
-
console.log("");
|
|
6306
|
-
console.log("\u91CD\u65B0\u52A0\u8F7D\u914D\u7F6E...");
|
|
6307
|
-
if (!daemonEnabled) handleStopResult(stop());
|
|
6308
|
-
try {
|
|
6309
|
-
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
6310
|
-
const pid = await launchOrRestart(targetMode);
|
|
6311
|
-
console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
|
|
6312
|
-
} catch (e) {
|
|
6313
|
-
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
6314
|
-
process.exit(1);
|
|
6315
|
-
}
|
|
6528
|
+
const nameArg = getNonFlagArg(args, 2);
|
|
6529
|
+
const timeout = parseIntArg(args, "-t", "--timeout", DEFAULT_TEST_TIMEOUT);
|
|
6530
|
+
const concurrency = parseIntArg(args, "-j", "--concurrency", DEFAULT_TEST_CONCURRENCY);
|
|
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);
|
|
6316
6540
|
}
|
|
6541
|
+
target = activeSub;
|
|
6317
6542
|
}
|
|
6318
|
-
|
|
6543
|
+
return { target, timeout, concurrency };
|
|
6319
6544
|
}
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
const info = listOverwriteFile();
|
|
6324
|
-
const statusText = info.enabled ? colors.green("\u5DF2\u542F\u7528") : colors.yellow("\u5DF2\u7981\u7528");
|
|
6325
|
-
console.log(`${colors.gray("\u72B6\u6001: ")}${statusText}`);
|
|
6326
|
-
console.log(`${colors.gray("\u4F4D\u7F6E: ")}${info.dir}`);
|
|
6327
|
-
console.log("");
|
|
6328
|
-
if (info.files.length === 0) {
|
|
6329
|
-
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
6330
|
-
console.log("");
|
|
6331
|
-
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path8.join(info.dir, "overwrite.yaml")}`);
|
|
6332
|
-
console.log(` \u6216 ${path8.join(info.dir, "overwrite.dns.yaml")}`);
|
|
6333
|
-
console.log("");
|
|
6334
|
-
} else {
|
|
6335
|
-
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
6336
|
-
console.log("");
|
|
6337
|
-
info.files.forEach((f, i) => {
|
|
6338
|
-
const num = i < 10 ? ` ${i}` : `${i}`;
|
|
6339
|
-
console.log(` ${num}. ${f.name}`);
|
|
6340
|
-
if (f.keys.length > 0) {
|
|
6341
|
-
console.log(` ${colors.gray("\u5B57\u6BB5: ")}${f.keys.join(", ")}`);
|
|
6342
|
-
}
|
|
6343
|
-
});
|
|
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"));
|
|
6344
6548
|
console.log("");
|
|
6345
6549
|
}
|
|
6346
|
-
console.log("\u542F\u7528\u8986\u5199: mihomo ow on");
|
|
6347
|
-
console.log("\u7981\u7528\u8986\u5199: mihomo ow off");
|
|
6348
|
-
console.log("");
|
|
6349
6550
|
}
|
|
6350
|
-
|
|
6351
|
-
const
|
|
6352
|
-
|
|
6353
|
-
|
|
6354
|
-
|
|
6355
|
-
|
|
6356
|
-
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
|
|
6357
|
-
console.log("");
|
|
6358
|
-
printOverwriteList();
|
|
6359
|
-
return;
|
|
6360
|
-
}
|
|
6361
|
-
setOverwriteEnabled(true);
|
|
6362
|
-
console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
|
|
6363
|
-
if (restartNeeded) {
|
|
6364
|
-
console.log("");
|
|
6365
|
-
await cmdStart(["start", currentMode]);
|
|
6366
|
-
return;
|
|
6367
|
-
}
|
|
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]");
|
|
6368
6557
|
console.log("");
|
|
6369
|
-
printOverwriteList();
|
|
6370
6558
|
return;
|
|
6371
6559
|
}
|
|
6372
|
-
|
|
6373
|
-
|
|
6374
|
-
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
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}`);
|
|
6378
6571
|
}
|
|
6379
|
-
|
|
6380
|
-
|
|
6381
|
-
|
|
6382
|
-
|
|
6383
|
-
|
|
6384
|
-
|
|
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}`);
|
|
6385
6582
|
}
|
|
6386
|
-
|
|
6387
|
-
|
|
6388
|
-
|
|
6389
|
-
|
|
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]");
|
|
6390
6598
|
console.log("");
|
|
6391
|
-
printOverwriteList();
|
|
6392
6599
|
}
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
{
|
|
6399
|
-
id: "subs",
|
|
6400
|
-
aliases: ["sub", "subs", "subscription", "subscriptions"],
|
|
6401
|
-
label: "\u8BA2\u9605",
|
|
6402
|
-
paths: () => [DIRS.subscriptions],
|
|
6403
|
-
needsStop: true
|
|
6404
|
-
},
|
|
6405
|
-
{
|
|
6406
|
-
id: "logs",
|
|
6407
|
-
aliases: ["log", "logs"],
|
|
6408
|
-
label: "\u65E5\u5FD7",
|
|
6409
|
-
paths: () => [DIRS.logs],
|
|
6410
|
-
needsStop: false
|
|
6411
|
-
},
|
|
6412
|
-
{
|
|
6413
|
-
id: "data",
|
|
6414
|
-
aliases: ["data"],
|
|
6415
|
-
label: "\u8FD0\u884C\u6570\u636E",
|
|
6416
|
-
paths: () => [DIRS.data],
|
|
6417
|
-
needsStop: true
|
|
6418
|
-
},
|
|
6419
|
-
{
|
|
6420
|
-
id: "runtime",
|
|
6421
|
-
aliases: ["runtime"],
|
|
6422
|
-
label: "\u8FD0\u884C\u65F6",
|
|
6423
|
-
paths: () => [DIRS.runtime],
|
|
6424
|
-
needsStop: true
|
|
6425
|
-
},
|
|
6426
|
-
{
|
|
6427
|
-
id: "settings",
|
|
6428
|
-
aliases: ["setting", "settings", "config"],
|
|
6429
|
-
label: "\u8BBE\u7F6E",
|
|
6430
|
-
paths: () => [PATHS.settingsFile],
|
|
6431
|
-
needsStop: false
|
|
6432
|
-
},
|
|
6433
|
-
{
|
|
6434
|
-
id: "kernel",
|
|
6435
|
-
aliases: ["kernel", "core"],
|
|
6436
|
-
label: "\u5185\u6838",
|
|
6437
|
-
paths: () => [DIRS.kernel],
|
|
6438
|
-
needsStop: false,
|
|
6439
|
-
onAfter: () => clearKernelVersionCache(),
|
|
6440
|
-
checkEmpty: () => !hasKernel(),
|
|
6441
|
-
emptyMsg: "\u5185\u6838\u672A\u5B89\u88C5\uFF0C\u65E0\u9700\u5220\u9664",
|
|
6442
|
-
warnIfRunning: true
|
|
6443
|
-
},
|
|
6444
|
-
{
|
|
6445
|
-
id: "overwrites",
|
|
6446
|
-
aliases: ["overwrite", "overwrites", "ow"],
|
|
6447
|
-
label: "\u8986\u5199",
|
|
6448
|
-
paths: () => {
|
|
6449
|
-
const dir = USER_DATA_DIR;
|
|
6450
|
-
if (!fs9.existsSync(dir)) return [];
|
|
6451
|
-
return fs9.readdirSync(dir).filter(isOverwriteFilename).map((f) => `${dir}/${f}`);
|
|
6452
|
-
},
|
|
6453
|
-
needsStop: false
|
|
6454
|
-
},
|
|
6455
|
-
{
|
|
6456
|
-
id: "daemon",
|
|
6457
|
-
aliases: ["daemon"],
|
|
6458
|
-
label: "\u4FDD\u6D3B",
|
|
6459
|
-
// 卸载由确认后的 disablesDaemon 段统一处理(需 sudo,受取消保护);
|
|
6460
|
-
// 此处 paths 返回空(plist 在系统目录,用户态删不掉,且不应提前删破坏卸载),
|
|
6461
|
-
// onAfter 因幂等守卫(plist 已删)成为 no-op,仅作单独 reset 未走前段时的兜底。
|
|
6462
|
-
paths: () => [],
|
|
6463
|
-
needsStop: false,
|
|
6464
|
-
onAfter: () => disableDaemon(),
|
|
6465
|
-
checkEmpty: () => !isDaemonEnabled(),
|
|
6466
|
-
emptyMsg: "\u4FDD\u6D3B\u672A\u542F\u7528\uFF0C\u65E0\u9700\u5220\u9664"
|
|
6600
|
+
async function cmdSubscription(args) {
|
|
6601
|
+
const action = args[1];
|
|
6602
|
+
if (!action || action === "list") {
|
|
6603
|
+
printSubscriptionList();
|
|
6604
|
+
return;
|
|
6467
6605
|
}
|
|
6468
|
-
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
|
|
6474
|
-
|
|
6475
|
-
|
|
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);
|
|
6612
|
+
}
|
|
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)`);
|
|
6627
|
+
try {
|
|
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)`);
|
|
6632
|
+
} catch (e) {
|
|
6633
|
+
removeSubscription(name);
|
|
6634
|
+
console.error(`\u6DFB\u52A0\u5931\u8D25: ${e.message}`);
|
|
6635
|
+
process.exit(1);
|
|
6636
|
+
}
|
|
6476
6637
|
} else {
|
|
6477
|
-
|
|
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);
|
|
6654
|
+
}
|
|
6478
6655
|
}
|
|
6656
|
+
console.log("");
|
|
6657
|
+
printSubscriptionList();
|
|
6658
|
+
return;
|
|
6479
6659
|
}
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
|
|
6487
|
-
|
|
6488
|
-
|
|
6489
|
-
|
|
6490
|
-
|
|
6491
|
-
|
|
6492
|
-
|
|
6493
|
-
|
|
6494
|
-
|
|
6495
|
-
|
|
6496
|
-
const skipConfirm = flags.includes("--yes") || flags.includes("-y");
|
|
6497
|
-
let targets;
|
|
6498
|
-
if (fullReset) {
|
|
6499
|
-
targets = RESET_TARGETS;
|
|
6500
|
-
} else if (names.length > 0) {
|
|
6501
|
-
const { matched, unmatched } = resolveResetTargets(names);
|
|
6502
|
-
if (unmatched.length > 0) {
|
|
6503
|
-
console.error(`\u9519\u8BEF: \u672A\u77E5\u7684\u91CD\u7F6E\u76EE\u6807: ${unmatched.join(", ")}`);
|
|
6504
|
-
console.log("");
|
|
6505
|
-
console.log(`\u53EF\u7528\u76EE\u6807: ${RESET_TARGETS.map((t) => t.aliases[0]).join(", ")}`);
|
|
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);
|
|
6666
|
+
}
|
|
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);
|
|
6506
6676
|
console.log("");
|
|
6507
|
-
|
|
6508
|
-
|
|
6509
|
-
|
|
6510
|
-
|
|
6511
|
-
|
|
6677
|
+
printRestartHintIfRunning();
|
|
6678
|
+
printSubscriptionList();
|
|
6679
|
+
return;
|
|
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]}`);
|
|
6512
6687
|
process.exit(1);
|
|
6513
6688
|
}
|
|
6514
|
-
|
|
6515
|
-
|
|
6516
|
-
|
|
6689
|
+
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
|
|
6690
|
+
console.log("");
|
|
6691
|
+
printRestartHintIfRunning();
|
|
6692
|
+
printSubscriptionList();
|
|
6693
|
+
return;
|
|
6517
6694
|
}
|
|
6518
|
-
|
|
6519
|
-
|
|
6520
|
-
|
|
6521
|
-
|
|
6522
|
-
|
|
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}`);
|
|
6523
6703
|
}
|
|
6704
|
+
process.exit(1);
|
|
6524
6705
|
}
|
|
6525
|
-
|
|
6526
|
-
|
|
6527
|
-
|
|
6528
|
-
|
|
6529
|
-
|
|
6530
|
-
|
|
6531
|
-
|
|
6532
|
-
|
|
6533
|
-
|
|
6534
|
-
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
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`);
|
|
6712
|
+
console.log("");
|
|
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}"`);
|
|
6723
|
+
process.exit(1);
|
|
6724
|
+
}
|
|
6725
|
+
if (restartNeeded) {
|
|
6726
|
+
console.log("");
|
|
6727
|
+
await cmdStart(["start", currentMode, ...extractStartOptions(args)]);
|
|
6728
|
+
return;
|
|
6729
|
+
}
|
|
6730
|
+
console.log("");
|
|
6731
|
+
printSubscriptionList();
|
|
6541
6732
|
return;
|
|
6542
6733
|
}
|
|
6543
|
-
if (
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
|
|
6547
|
-
console.error(
|
|
6548
|
-
|
|
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);
|
|
6763
|
+
}
|
|
6764
|
+
}
|
|
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");
|
|
6549
6769
|
}
|
|
6770
|
+
return;
|
|
6550
6771
|
}
|
|
6551
|
-
if (
|
|
6552
|
-
|
|
6553
|
-
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
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}"`);
|
|
6557
6789
|
}
|
|
6790
|
+
console.log("");
|
|
6791
|
+
printSubscriptionList();
|
|
6792
|
+
return;
|
|
6558
6793
|
}
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
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)");
|
|
6567
6821
|
}
|
|
6568
6822
|
}
|
|
6569
|
-
|
|
6823
|
+
return;
|
|
6570
6824
|
}
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
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;
|
|
6574
6842
|
}
|
|
6575
|
-
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);
|
|
6576
6846
|
}
|
|
6577
6847
|
|
|
6578
6848
|
// src/commands/test.ts
|
|
@@ -6640,7 +6910,14 @@ async function cmdClean(args) {
|
|
|
6640
6910
|
const mode = getRuntimeMode();
|
|
6641
6911
|
const daemonManaged = isDaemonEnabled();
|
|
6642
6912
|
try {
|
|
6643
|
-
if (!daemonManaged)
|
|
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());
|
|
6920
|
+
}
|
|
6644
6921
|
const configInfo = prepareConfigForStart(mode, activeSub.name);
|
|
6645
6922
|
const pid = await launchOrRestart(mode);
|
|
6646
6923
|
const label = daemonManaged ? "\u5DF2\u91CD\u542F (\u4FDD\u6D3B)" : "\u5DF2\u91CD\u542F";
|
|
@@ -6663,6 +6940,10 @@ function cmdUI(args) {
|
|
|
6663
6940
|
const url = UI_URLS[uiName];
|
|
6664
6941
|
console.log(`\u6253\u5F00 Web UI: ${uiName}`);
|
|
6665
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
|
+
}
|
|
6666
6947
|
const success = openUrl(url);
|
|
6667
6948
|
if (!success) {
|
|
6668
6949
|
console.log("\u8BF7\u624B\u52A8\u8BBF\u95EE\u4E0A\u9762\u7684\u5730\u5740");
|
|
@@ -6684,11 +6965,16 @@ async function cmdUpdate() {
|
|
|
6684
6965
|
if (code === 0) {
|
|
6685
6966
|
resolve();
|
|
6686
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");
|
|
6687
6969
|
process.exit(code || 1);
|
|
6688
6970
|
}
|
|
6689
6971
|
});
|
|
6690
6972
|
npm.on("error", (e) => {
|
|
6691
|
-
|
|
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
|
+
}
|
|
6692
6978
|
process.exit(1);
|
|
6693
6979
|
});
|
|
6694
6980
|
});
|
|
@@ -6716,7 +7002,7 @@ var COMMANDS = [
|
|
|
6716
7002
|
aliases: ["up"],
|
|
6717
7003
|
handler: cmdStart,
|
|
6718
7004
|
group: "control",
|
|
6719
|
-
usage: ["start [tun|mixed] [-s] [-u ms] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)", " [-r N] [-t ms] [-j N]"]
|
|
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]"]
|
|
6720
7006
|
},
|
|
6721
7007
|
{
|
|
6722
7008
|
name: "tun",
|
|
@@ -6775,8 +7061,8 @@ var COMMANDS = [
|
|
|
6775
7061
|
"subscription update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09",
|
|
6776
7062
|
"subscription remove <name> \u5220\u9664\u8BA2\u9605",
|
|
6777
7063
|
"subscription web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762",
|
|
6778
|
-
"subscription test [name] \u6D4B\u8BD5\u8282\u70B9\
|
|
6779
|
-
"subscription clean [name] \u6D4B\u901F\
|
|
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"
|
|
6780
7066
|
]
|
|
6781
7067
|
},
|
|
6782
7068
|
{
|
|
@@ -6792,14 +7078,14 @@ var COMMANDS = [
|
|
|
6792
7078
|
aliases: [],
|
|
6793
7079
|
handler: cmdTest,
|
|
6794
7080
|
group: "subscription",
|
|
6795
|
-
usage: ["test [-t ms] [-j N] \
|
|
7081
|
+
usage: ["test [-t ms] [-j N] \u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\uFF08\u7ECF\u8FD0\u884C\u4E2D\u7684\u4E3B\u5B9E\u4F8B\uFF09"]
|
|
6796
7082
|
},
|
|
6797
7083
|
{
|
|
6798
7084
|
name: "clean",
|
|
6799
7085
|
aliases: [],
|
|
6800
7086
|
handler: cmdClean,
|
|
6801
7087
|
group: "subscription",
|
|
6802
|
-
usage: ["clean [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\
|
|
7088
|
+
usage: ["clean [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u91CD\u542F\uFF08\u7ECF\u4E3B\u5B9E\u4F8B\uFF09"]
|
|
6803
7089
|
},
|
|
6804
7090
|
// === 配置 ===
|
|
6805
7091
|
{
|
|
@@ -6867,7 +7153,7 @@ var COMMANDS = [
|
|
|
6867
7153
|
aliases: [],
|
|
6868
7154
|
handler: cmdReset,
|
|
6869
7155
|
group: "system",
|
|
6870
|
-
usage: ["reset [\u76EE\u6807...] [--full] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8"]
|
|
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"]
|
|
6871
7157
|
},
|
|
6872
7158
|
// === meta(不在分组清单展示,help 末尾单列) ===
|
|
6873
7159
|
{
|
|
@@ -6903,7 +7189,9 @@ function findCommand(token) {
|
|
|
6903
7189
|
|
|
6904
7190
|
// src/index.ts
|
|
6905
7191
|
process.on("SIGINT", () => {
|
|
6906
|
-
|
|
7192
|
+
if (!isSilentSigint()) {
|
|
7193
|
+
console.log("\n\u6B63\u5728\u9000\u51FA...");
|
|
7194
|
+
}
|
|
6907
7195
|
runCleanup();
|
|
6908
7196
|
process.exit(130);
|
|
6909
7197
|
});
|