mihomo-cli 3.0.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +16 -10
- package/dist/index.js +824 -604
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/config.ts
|
|
4
|
-
import { spawnSync } from "child_process";
|
|
4
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
5
5
|
import fs4 from "fs";
|
|
6
6
|
|
|
7
7
|
// node_modules/js-yaml/dist/js-yaml.mjs
|
|
@@ -2923,7 +2923,11 @@ var UI_URLS = {
|
|
|
2923
2923
|
dash: "https://metacubex.github.io/metacubexd",
|
|
2924
2924
|
yacd: "https://yacd.metacubex.one"
|
|
2925
2925
|
};
|
|
2926
|
-
var
|
|
2926
|
+
var LAUNCH_DAEMON_LABEL = process.env.MIHOMO_CLI_DAEMON_LABEL || "com.mihomo-cli.daemon";
|
|
2927
|
+
var CONTROLLER_PORT = 9090;
|
|
2928
|
+
var CONTROLLER_ADDR = `127.0.0.1:${CONTROLLER_PORT}`;
|
|
2929
|
+
var CONTROLLER_BASE_URL = `http://${CONTROLLER_ADDR}`;
|
|
2930
|
+
var TEST_CONTROLLER_ADDR = "127.0.0.1:29090";
|
|
2927
2931
|
var TUN_CONFIG = {
|
|
2928
2932
|
tun: {
|
|
2929
2933
|
enable: true,
|
|
@@ -2937,14 +2941,14 @@ var TUN_CONFIG = {
|
|
|
2937
2941
|
var TEST_CONFIG = {
|
|
2938
2942
|
"mixed-port": 27890,
|
|
2939
2943
|
"allow-lan": false,
|
|
2940
|
-
"external-controller":
|
|
2944
|
+
"external-controller": TEST_CONTROLLER_ADDR,
|
|
2941
2945
|
"log-level": "error",
|
|
2942
2946
|
"geodata-mode": true
|
|
2943
2947
|
};
|
|
2944
2948
|
var BASE_CONFIG = {
|
|
2945
2949
|
"mixed-port": 7890,
|
|
2946
2950
|
"allow-lan": false,
|
|
2947
|
-
"external-controller":
|
|
2951
|
+
"external-controller": CONTROLLER_ADDR,
|
|
2948
2952
|
"unified-delay": true,
|
|
2949
2953
|
"tcp-concurrent": true,
|
|
2950
2954
|
"geo-auto-update": true,
|
|
@@ -2973,7 +2977,7 @@ var AUTO_CLEAN_THRESHOLD_GITHUB = 50;
|
|
|
2973
2977
|
|
|
2974
2978
|
// src/overwrite.ts
|
|
2975
2979
|
import fs3 from "fs";
|
|
2976
|
-
import
|
|
2980
|
+
import path3 from "path";
|
|
2977
2981
|
|
|
2978
2982
|
// src/paths.ts
|
|
2979
2983
|
import fs from "fs";
|
|
@@ -3003,8 +3007,8 @@ var PATHS = {
|
|
|
3003
3007
|
configStage1Subscription: path.join(DIRS.runtime, "1.subscription.yaml"),
|
|
3004
3008
|
configStage2Overwrite: path.join(DIRS.runtime, "2.overwrite.yaml"),
|
|
3005
3009
|
configStage3System: path.join(DIRS.runtime, "3.system.yaml"),
|
|
3006
|
-
// launchd
|
|
3007
|
-
|
|
3010
|
+
// launchd LaunchDaemon plist 位于系统级 /Library/LaunchDaemons/,root:wheel 拥有,与 homedir / MIHOMO_CLI_DIR 无关
|
|
3011
|
+
launchDaemonPlist: path.join("/Library/LaunchDaemons", `${LAUNCH_DAEMON_LABEL}.plist`)
|
|
3008
3012
|
};
|
|
3009
3013
|
var DIRECTORY_TARGETS = {
|
|
3010
3014
|
root: { path: null, label: "\u6839\u76EE\u5F55" },
|
|
@@ -3040,6 +3044,7 @@ function rmrf(dir) {
|
|
|
3040
3044
|
|
|
3041
3045
|
// src/settings.ts
|
|
3042
3046
|
import fs2 from "fs";
|
|
3047
|
+
import path2 from "path";
|
|
3043
3048
|
var settingsCache = null;
|
|
3044
3049
|
function readSettings() {
|
|
3045
3050
|
if (settingsCache !== null) return settingsCache;
|
|
@@ -3143,12 +3148,10 @@ function addSubscription(url, name = "default") {
|
|
|
3143
3148
|
validateSubscriptionName(name);
|
|
3144
3149
|
const settings = readSettings();
|
|
3145
3150
|
const subs = [...settings.subscriptions || []];
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
subs[existingIndex] = { name, url };
|
|
3149
|
-
} else {
|
|
3150
|
-
subs.push({ name, url });
|
|
3151
|
+
if (subs.some((s) => s.name === name)) {
|
|
3152
|
+
throw new Error(`\u8BA2\u9605 "${name}" \u5DF2\u5B58\u5728\uFF0C\u8BF7\u6362\u4E2A\u540D\u79F0\uFF08mihomo sub add <url> <\u540D\u79F0>\uFF09\uFF0C\u6216\u5148\u5220\u9664\uFF08mihomo sub remove ${name}\uFF09`);
|
|
3151
3153
|
}
|
|
3154
|
+
subs.push({ name, url });
|
|
3152
3155
|
const updates = { subscriptions: subs };
|
|
3153
3156
|
if (!settings.active_subscription && subs.length === 1) {
|
|
3154
3157
|
updates.active_subscription = name;
|
|
@@ -3173,7 +3176,10 @@ function removeSubscription(name) {
|
|
|
3173
3176
|
delete cache[name];
|
|
3174
3177
|
writeSubscriptionCache(cache);
|
|
3175
3178
|
}
|
|
3176
|
-
|
|
3179
|
+
try {
|
|
3180
|
+
fs2.rmSync(getSubscriptionRawConfigPath(name), { force: true });
|
|
3181
|
+
} catch {
|
|
3182
|
+
}
|
|
3177
3183
|
return switchedTo;
|
|
3178
3184
|
}
|
|
3179
3185
|
function setDefaultSubscription(name) {
|
|
@@ -3186,7 +3192,10 @@ function setDefaultSubscription(name) {
|
|
|
3186
3192
|
return true;
|
|
3187
3193
|
}
|
|
3188
3194
|
function getSubscriptionRawConfigPath(subName) {
|
|
3189
|
-
|
|
3195
|
+
if (!SAFE_NAME_RE.test(subName)) {
|
|
3196
|
+
throw new Error(`\u8BA2\u9605\u540D\u79F0\u65E0\u6548: "${subName}"`);
|
|
3197
|
+
}
|
|
3198
|
+
return path2.join(DIRS.subscriptions, `${subName}.yaml`);
|
|
3190
3199
|
}
|
|
3191
3200
|
function saveSubscriptionRawConfig(subName, content) {
|
|
3192
3201
|
ensureDirs();
|
|
@@ -3284,17 +3293,20 @@ function isOverwriteEnabled() {
|
|
|
3284
3293
|
function setOverwriteEnabled(enabled) {
|
|
3285
3294
|
writeSettings({ overwrite_enabled: enabled });
|
|
3286
3295
|
}
|
|
3296
|
+
function isOverwriteFilename(filename) {
|
|
3297
|
+
return filename === "overwrite.yaml" || /^overwrite\..+\.ya?ml$/.test(filename);
|
|
3298
|
+
}
|
|
3287
3299
|
function loadOverwriteFile() {
|
|
3288
3300
|
const dir = USER_DATA_DIR;
|
|
3289
3301
|
if (!fs3.existsSync(dir)) return [];
|
|
3290
|
-
const files = fs3.readdirSync(dir).filter(
|
|
3302
|
+
const files = fs3.readdirSync(dir).filter(isOverwriteFilename).sort((a, b) => {
|
|
3291
3303
|
if (a === "overwrite.yaml") return -1;
|
|
3292
3304
|
if (b === "overwrite.yaml") return 1;
|
|
3293
3305
|
return a.localeCompare(b);
|
|
3294
3306
|
});
|
|
3295
3307
|
const results = [];
|
|
3296
3308
|
for (const file of files) {
|
|
3297
|
-
const filePath =
|
|
3309
|
+
const filePath = path3.join(dir, file);
|
|
3298
3310
|
try {
|
|
3299
3311
|
const content = fs3.readFileSync(filePath, "utf8");
|
|
3300
3312
|
const parsed = load(content);
|
|
@@ -3331,6 +3343,213 @@ function listOverwriteFile() {
|
|
|
3331
3343
|
};
|
|
3332
3344
|
}
|
|
3333
3345
|
|
|
3346
|
+
// src/utils.ts
|
|
3347
|
+
import { spawnSync } from "child_process";
|
|
3348
|
+
import { createRequire } from "module";
|
|
3349
|
+
var require2 = createRequire(import.meta.url);
|
|
3350
|
+
var pkg = require2("../package.json");
|
|
3351
|
+
var VERSION = pkg.version;
|
|
3352
|
+
var sleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
3353
|
+
var NO_COLOR = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
3354
|
+
function colorize(code, str) {
|
|
3355
|
+
if (NO_COLOR) return String(str);
|
|
3356
|
+
return `${code + String(str)}\x1B[0m`;
|
|
3357
|
+
}
|
|
3358
|
+
var colors = {
|
|
3359
|
+
bold: (s) => colorize("\x1B[1m", s),
|
|
3360
|
+
red: (s) => colorize("\x1B[31m", s),
|
|
3361
|
+
green: (s) => colorize("\x1B[32m", s),
|
|
3362
|
+
yellow: (s) => colorize("\x1B[33m", s),
|
|
3363
|
+
cyan: (s) => colorize("\x1B[36m", s),
|
|
3364
|
+
gray: (s) => colorize("\x1B[90m", s)
|
|
3365
|
+
};
|
|
3366
|
+
function sleepSync(ms) {
|
|
3367
|
+
Atomics.wait(sleepBuf, 0, 0, ms);
|
|
3368
|
+
}
|
|
3369
|
+
function sleep(ms) {
|
|
3370
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3371
|
+
}
|
|
3372
|
+
function escapeRegExp(s) {
|
|
3373
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3374
|
+
}
|
|
3375
|
+
function shellQuote(s) {
|
|
3376
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
3377
|
+
}
|
|
3378
|
+
var TimeoutError = class extends Error {
|
|
3379
|
+
constructor() {
|
|
3380
|
+
super("timeout");
|
|
3381
|
+
this.name = "TimeoutError";
|
|
3382
|
+
}
|
|
3383
|
+
};
|
|
3384
|
+
function withTimeout(promise, ms) {
|
|
3385
|
+
return new Promise((resolve, reject) => {
|
|
3386
|
+
const timer = setTimeout(() => reject(new TimeoutError()), ms);
|
|
3387
|
+
promise.then(
|
|
3388
|
+
(v) => {
|
|
3389
|
+
clearTimeout(timer);
|
|
3390
|
+
resolve(v);
|
|
3391
|
+
},
|
|
3392
|
+
(e) => {
|
|
3393
|
+
clearTimeout(timer);
|
|
3394
|
+
reject(e);
|
|
3395
|
+
}
|
|
3396
|
+
);
|
|
3397
|
+
});
|
|
3398
|
+
}
|
|
3399
|
+
function formatBytes(bytes) {
|
|
3400
|
+
if (bytes === void 0 || bytes === null) return "\u672A\u77E5";
|
|
3401
|
+
const num = Number(bytes);
|
|
3402
|
+
if (!Number.isFinite(num) || num < 0) return "\u672A\u77E5";
|
|
3403
|
+
if (num === 0) return "0 B";
|
|
3404
|
+
const k = 1024;
|
|
3405
|
+
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
3406
|
+
const i = Math.min(Math.floor(Math.log(num) / Math.log(k)), sizes.length - 1);
|
|
3407
|
+
return `${parseFloat((num / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
3408
|
+
}
|
|
3409
|
+
function formatTimestamp(ts) {
|
|
3410
|
+
if (ts === void 0 || ts === null) return "\u672A\u77E5";
|
|
3411
|
+
try {
|
|
3412
|
+
return new Date(ts * 1e3).toLocaleString("zh-CN");
|
|
3413
|
+
} catch {
|
|
3414
|
+
return "\u672A\u77E5";
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
function formatLocalTimestamp(d = /* @__PURE__ */ new Date()) {
|
|
3418
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
3419
|
+
return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}_${p(d.getHours())}-${p(d.getMinutes())}-${p(d.getSeconds())}`;
|
|
3420
|
+
}
|
|
3421
|
+
function formatDate(dateOrIso) {
|
|
3422
|
+
if (dateOrIso === void 0 || dateOrIso === null) return "\u672A\u77E5";
|
|
3423
|
+
try {
|
|
3424
|
+
const d = dateOrIso instanceof Date ? dateOrIso : new Date(dateOrIso);
|
|
3425
|
+
if (Number.isNaN(d.getTime())) return "\u672A\u77E5";
|
|
3426
|
+
return d.toLocaleString("zh-CN");
|
|
3427
|
+
} catch {
|
|
3428
|
+
return "\u672A\u77E5";
|
|
3429
|
+
}
|
|
3430
|
+
}
|
|
3431
|
+
function hasFlag(args, short, long) {
|
|
3432
|
+
return !!args && (args.includes(short) || args.includes(long));
|
|
3433
|
+
}
|
|
3434
|
+
function parseIntArg(args, short, long, defaultValue) {
|
|
3435
|
+
if (!args) return defaultValue;
|
|
3436
|
+
for (let i = 0; i < args.length; i++) {
|
|
3437
|
+
if (args[i] === short || args[i] === long) {
|
|
3438
|
+
if (i + 1 < args.length) {
|
|
3439
|
+
const val = parseInt(args[i + 1], 10);
|
|
3440
|
+
return Number.isNaN(val) ? defaultValue : val;
|
|
3441
|
+
}
|
|
3442
|
+
}
|
|
3443
|
+
}
|
|
3444
|
+
return defaultValue;
|
|
3445
|
+
}
|
|
3446
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["-t", "--timeout", "-j", "--concurrency", "-r", "--rounds", "-n", "--lines", "-u", "--update-timeout"]);
|
|
3447
|
+
function getNonFlagArg(args, startIdx, valueFlags = VALUE_FLAGS) {
|
|
3448
|
+
if (!args) return null;
|
|
3449
|
+
for (let i = startIdx; i < args.length; i++) {
|
|
3450
|
+
const a = args[i];
|
|
3451
|
+
if (a.startsWith("-")) {
|
|
3452
|
+
if (valueFlags.has(a)) i++;
|
|
3453
|
+
continue;
|
|
3454
|
+
}
|
|
3455
|
+
return a;
|
|
3456
|
+
}
|
|
3457
|
+
return null;
|
|
3458
|
+
}
|
|
3459
|
+
function isProcessRunning(pid) {
|
|
3460
|
+
if (!pid) return false;
|
|
3461
|
+
try {
|
|
3462
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "pid="], { encoding: "utf8", timeout: 5e3 });
|
|
3463
|
+
return (result.stdout || "").trim().length > 0;
|
|
3464
|
+
} catch {
|
|
3465
|
+
return false;
|
|
3466
|
+
}
|
|
3467
|
+
}
|
|
3468
|
+
function isProcessRoot(pid) {
|
|
3469
|
+
if (!pid) return false;
|
|
3470
|
+
try {
|
|
3471
|
+
const result = spawnSync("ps", ["-p", String(pid), "-o", "uid="], { encoding: "utf8", timeout: 5e3 });
|
|
3472
|
+
return (result.stdout || "").trim() === "0";
|
|
3473
|
+
} catch {
|
|
3474
|
+
return false;
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
function createHttpClient(options = {}) {
|
|
3478
|
+
const { timeout = 6e4 } = options;
|
|
3479
|
+
return {
|
|
3480
|
+
async get(url, config) {
|
|
3481
|
+
const controller = new AbortController();
|
|
3482
|
+
const timer = setTimeout(() => controller.abort(), timeout);
|
|
3483
|
+
const signal = config?.signal ? AbortSignal.any([controller.signal, config.signal]) : controller.signal;
|
|
3484
|
+
try {
|
|
3485
|
+
const response = await fetch(url, {
|
|
3486
|
+
signal,
|
|
3487
|
+
headers: { "User-Agent": `mihomo-cli/${VERSION}` }
|
|
3488
|
+
});
|
|
3489
|
+
if (!response.ok) {
|
|
3490
|
+
const error = new Error(`HTTP ${response.status}`);
|
|
3491
|
+
error.response = { status: response.status };
|
|
3492
|
+
try {
|
|
3493
|
+
error.response.data = await response.json();
|
|
3494
|
+
} catch {
|
|
3495
|
+
}
|
|
3496
|
+
throw error;
|
|
3497
|
+
}
|
|
3498
|
+
const data = config?.responseType === "json" ? await response.json() : await response.text();
|
|
3499
|
+
return { data, headers: response.headers, status: response.status };
|
|
3500
|
+
} finally {
|
|
3501
|
+
clearTimeout(timer);
|
|
3502
|
+
}
|
|
3503
|
+
}
|
|
3504
|
+
};
|
|
3505
|
+
}
|
|
3506
|
+
function normalizeMirrorUrl(val) {
|
|
3507
|
+
if (!val) return null;
|
|
3508
|
+
if (val === "direct" || val === "no" || val === "none") return null;
|
|
3509
|
+
let url = val;
|
|
3510
|
+
if (!url.startsWith("http")) {
|
|
3511
|
+
url = `https://${url}`;
|
|
3512
|
+
}
|
|
3513
|
+
if (!url.endsWith("/")) {
|
|
3514
|
+
url += "/";
|
|
3515
|
+
}
|
|
3516
|
+
return url;
|
|
3517
|
+
}
|
|
3518
|
+
function parseMirrorArg(args) {
|
|
3519
|
+
if (!args || args.length < 2) {
|
|
3520
|
+
return { mirror: null, isOverride: false, type: "download" };
|
|
3521
|
+
}
|
|
3522
|
+
if (args.includes("--no-mirror") || args.includes("--direct")) {
|
|
3523
|
+
return { mirror: null, isOverride: true, type: "download" };
|
|
3524
|
+
}
|
|
3525
|
+
const mirrorAllIdx = args.indexOf("--mirror-all");
|
|
3526
|
+
if (mirrorAllIdx >= 0) {
|
|
3527
|
+
const nextArg = args[mirrorAllIdx + 1];
|
|
3528
|
+
if (!nextArg || nextArg.startsWith("-")) {
|
|
3529
|
+
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "all" };
|
|
3530
|
+
}
|
|
3531
|
+
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "all" };
|
|
3532
|
+
}
|
|
3533
|
+
const mirrorIdx = args.indexOf("--mirror");
|
|
3534
|
+
if (mirrorIdx >= 0) {
|
|
3535
|
+
const nextArg = args[mirrorIdx + 1];
|
|
3536
|
+
if (!nextArg || nextArg.startsWith("-")) {
|
|
3537
|
+
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "download" };
|
|
3538
|
+
}
|
|
3539
|
+
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "download" };
|
|
3540
|
+
}
|
|
3541
|
+
return { mirror: null, isOverride: false, type: "download" };
|
|
3542
|
+
}
|
|
3543
|
+
function isProxyValid(proxy) {
|
|
3544
|
+
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3545
|
+
if (!proxy.type) return false;
|
|
3546
|
+
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3547
|
+
const pw = String(proxy.password || "");
|
|
3548
|
+
if (!/^[A-Za-z0-9+/\-_]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3549
|
+
}
|
|
3550
|
+
return true;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3334
3553
|
// src/config.ts
|
|
3335
3554
|
function parseYamlOrJson(content, errorMsg) {
|
|
3336
3555
|
if (!content?.trim()) {
|
|
@@ -3347,6 +3566,9 @@ function parseYamlOrJson(content, errorMsg) {
|
|
|
3347
3566
|
throw new Error(`${errorMsg || "\u5185\u5BB9"}\u683C\u5F0F\u9519\u8BEF\uFF0C\u65E0\u6CD5\u89E3\u6790\u4E3A YAML \u6216 JSON`);
|
|
3348
3567
|
}
|
|
3349
3568
|
}
|
|
3569
|
+
function dumpYaml(obj) {
|
|
3570
|
+
return dump(obj, { indent: 2, lineWidth: -1, schema: CORE_SCHEMA });
|
|
3571
|
+
}
|
|
3350
3572
|
function collectOverwriteProxyNames(overwriteFiles) {
|
|
3351
3573
|
const names = [];
|
|
3352
3574
|
for (const file of overwriteFiles) {
|
|
@@ -3367,7 +3589,7 @@ function excludeOverwriteProxiesFromIncludeAll(config, overwriteFiles) {
|
|
|
3367
3589
|
if (injectedNames.length === 0) return;
|
|
3368
3590
|
const groups = config["proxy-groups"];
|
|
3369
3591
|
if (!groups) return;
|
|
3370
|
-
const excludePattern = injectedNames.map((n) => n
|
|
3592
|
+
const excludePattern = injectedNames.map((n) => escapeRegExp(n)).join("|");
|
|
3371
3593
|
for (const group of groups) {
|
|
3372
3594
|
if (!group["include-all"] && !group["include-all-proxies"]) continue;
|
|
3373
3595
|
const existing = group["exclude-filter"];
|
|
@@ -3392,6 +3614,15 @@ function deduplicateByName(items) {
|
|
|
3392
3614
|
});
|
|
3393
3615
|
return { result, names, duplicates };
|
|
3394
3616
|
}
|
|
3617
|
+
function getRuleTarget(rule) {
|
|
3618
|
+
const parts = rule.split(",");
|
|
3619
|
+
if (parts.length < 2) return "";
|
|
3620
|
+
const last = parts[parts.length - 1].trim();
|
|
3621
|
+
if (last.toLowerCase() === "no-resolve" && parts.length >= 3) {
|
|
3622
|
+
return parts[parts.length - 2].trim();
|
|
3623
|
+
}
|
|
3624
|
+
return last;
|
|
3625
|
+
}
|
|
3395
3626
|
function validateConfig(config) {
|
|
3396
3627
|
const warnings = [];
|
|
3397
3628
|
const proxies = config.proxies || [];
|
|
@@ -3436,9 +3667,7 @@ function validateConfig(config) {
|
|
|
3436
3667
|
if (rules.length > 0) {
|
|
3437
3668
|
const removedRules = [];
|
|
3438
3669
|
config.rules = rules.filter((rule) => {
|
|
3439
|
-
const
|
|
3440
|
-
if (parts.length < 2) return true;
|
|
3441
|
-
const target = parts[parts.length - 1].trim();
|
|
3670
|
+
const target = getRuleTarget(rule);
|
|
3442
3671
|
if (!target || validNames.has(target)) return true;
|
|
3443
3672
|
removedRules.push(rule);
|
|
3444
3673
|
return false;
|
|
@@ -3485,6 +3714,8 @@ function buildConfig(subRawContent, mode) {
|
|
|
3485
3714
|
if (Object.keys(dns).length > 0) {
|
|
3486
3715
|
systemConfig.dns = dns;
|
|
3487
3716
|
}
|
|
3717
|
+
} else {
|
|
3718
|
+
delete withOverwrites.tun;
|
|
3488
3719
|
}
|
|
3489
3720
|
const merged = { ...withOverwrites, ...systemConfig };
|
|
3490
3721
|
if (systemConfig.dns) {
|
|
@@ -3507,20 +3738,19 @@ function buildConfig(subRawContent, mode) {
|
|
|
3507
3738
|
}
|
|
3508
3739
|
function writeMihomoConfig(configObj) {
|
|
3509
3740
|
ensureDirs();
|
|
3510
|
-
const content =
|
|
3741
|
+
const content = dumpYaml(configObj);
|
|
3511
3742
|
atomicWriteFileSync(PATHS.configFile, content, { mode: 384 });
|
|
3512
3743
|
}
|
|
3513
3744
|
function writeDebugConfig(buildResult) {
|
|
3514
3745
|
ensureDirs();
|
|
3515
|
-
|
|
3516
|
-
fs4.writeFileSync(PATHS.configStage1Subscription, dump(buildResult.subscriptionConfig, dumpOpts), { mode: 384 });
|
|
3746
|
+
fs4.writeFileSync(PATHS.configStage1Subscription, dumpYaml(buildResult.subscriptionConfig), { mode: 384 });
|
|
3517
3747
|
const overwriteMerged = {};
|
|
3518
3748
|
for (const f of buildResult.overwriteFiles) {
|
|
3519
3749
|
Object.assign(overwriteMerged, f.config);
|
|
3520
3750
|
}
|
|
3521
|
-
const overwriteContent = buildResult.overwriteFiles.length > 0 ?
|
|
3751
|
+
const overwriteContent = buildResult.overwriteFiles.length > 0 ? dumpYaml(overwriteMerged) : "# overwrite \u5DF2\u7981\u7528\u6216\u65E0\u8986\u5199\u6587\u4EF6\n";
|
|
3522
3752
|
fs4.writeFileSync(PATHS.configStage2Overwrite, overwriteContent, { mode: 384 });
|
|
3523
|
-
fs4.writeFileSync(PATHS.configStage3System,
|
|
3753
|
+
fs4.writeFileSync(PATHS.configStage3System, dumpYaml(buildResult.systemConfig), { mode: 384 });
|
|
3524
3754
|
}
|
|
3525
3755
|
function hasConfig() {
|
|
3526
3756
|
return fs4.existsSync(PATHS.configFile);
|
|
@@ -3560,7 +3790,7 @@ function getKernelVersion() {
|
|
|
3560
3790
|
}
|
|
3561
3791
|
if (kernelVersionCached) return kernelVersionCache;
|
|
3562
3792
|
try {
|
|
3563
|
-
const result =
|
|
3793
|
+
const result = spawnSync2(PATHS.mihomoBinary, ["-v"], { encoding: "utf8", timeout: 5e3 });
|
|
3564
3794
|
const output = `${result.stdout || ""}${result.stderr || ""}`.trim();
|
|
3565
3795
|
if (output) {
|
|
3566
3796
|
const match = output.match(/v?[\d]+\.[\d]+\.[\d]+/);
|
|
@@ -3579,214 +3809,89 @@ function clearKernelVersionCache() {
|
|
|
3579
3809
|
kernelVersionCached = false;
|
|
3580
3810
|
}
|
|
3581
3811
|
|
|
3812
|
+
// src/commands/help.ts
|
|
3813
|
+
function printShortHelp() {
|
|
3814
|
+
console.log(`
|
|
3815
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))} (mihomo help \u67E5\u770B\u5B8C\u6574\u5E2E\u52A9)
|
|
3816
|
+
`);
|
|
3817
|
+
console.log(
|
|
3818
|
+
`\u5E38\u7528\u547D\u4EE4:
|
|
3819
|
+
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
3820
|
+
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
3821
|
+
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
3822
|
+
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
3823
|
+
`
|
|
3824
|
+
);
|
|
3825
|
+
}
|
|
3826
|
+
var GROUP_TITLES = [
|
|
3827
|
+
["control", "\u63A7\u5236:"],
|
|
3828
|
+
["interface", "\u754C\u9762:"],
|
|
3829
|
+
["subscription", "\u8BA2\u9605:"],
|
|
3830
|
+
["config", "\u914D\u7F6E:"],
|
|
3831
|
+
["system", "\u7CFB\u7EDF:"]
|
|
3832
|
+
];
|
|
3833
|
+
function printHelp(commands) {
|
|
3834
|
+
const lines = [`
|
|
3835
|
+
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}`, "", "\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh", "", "\u7528\u6CD5:", " mihomo <\u547D\u4EE4> [\u9009\u9879]"];
|
|
3836
|
+
for (const [group, title] of GROUP_TITLES) {
|
|
3837
|
+
const usageLines = commands.filter((c) => c.group === group).flatMap((c) => c.usage);
|
|
3838
|
+
if (usageLines.length === 0) continue;
|
|
3839
|
+
lines.push("", colors.cyan(title));
|
|
3840
|
+
for (const u of usageLines) {
|
|
3841
|
+
lines.push(u.startsWith(" ") ? ` ${u}` : ` ${boldFirstToken(u)}`);
|
|
3842
|
+
}
|
|
3843
|
+
}
|
|
3844
|
+
const meta = commands.filter((c) => c.group === "meta").flatMap((c) => c.usage);
|
|
3845
|
+
if (meta.length > 0) {
|
|
3846
|
+
lines.push("", colors.cyan("\u5143:"));
|
|
3847
|
+
for (const u of meta) lines.push(` ${boldFirstToken(u)}`);
|
|
3848
|
+
}
|
|
3849
|
+
lines.push(
|
|
3850
|
+
"",
|
|
3851
|
+
`${colors.cyan("\u793A\u4F8B:")}`,
|
|
3852
|
+
" mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F",
|
|
3853
|
+
" mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F",
|
|
3854
|
+
" mihomo start -s # \u8DF3\u8FC7\u81EA\u52A8\u66F4\u65B0\u8BA2\u9605",
|
|
3855
|
+
" mihomo start -u 30000 # \u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 30 \u79D2 (\u9ED8\u8BA4 10s)",
|
|
3856
|
+
" mihomo daemon on # \u5F00\u542F\u4FDD\u6D3B\uFF08\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F\uFF09",
|
|
3857
|
+
" mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)",
|
|
3858
|
+
" mihomo ui # \u6253\u5F00 Web UI",
|
|
3859
|
+
"",
|
|
3860
|
+
`${colors.cyan("\u5FEB\u6377\u547D\u4EE4:")}`,
|
|
3861
|
+
" tun = start tun use = sub use on/off = ow on/off open = dir open",
|
|
3862
|
+
" up = start down = stop upd/upgrade = update",
|
|
3863
|
+
"",
|
|
3864
|
+
`${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}`,
|
|
3865
|
+
" mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)",
|
|
3866
|
+
" tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo",
|
|
3867
|
+
"",
|
|
3868
|
+
`${colors.cyan("\u6570\u636E\u76EE\u5F55:")}`,
|
|
3869
|
+
" \u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E",
|
|
3870
|
+
` \u9ED8\u8BA4: ${USER_DATA_DIR}`
|
|
3871
|
+
);
|
|
3872
|
+
console.log(lines.join("\n"));
|
|
3873
|
+
}
|
|
3874
|
+
function boldFirstToken(usage) {
|
|
3875
|
+
const spaceIdx = usage.indexOf(" ");
|
|
3876
|
+
if (spaceIdx < 0) return colors.bold(usage);
|
|
3877
|
+
return `${colors.bold(usage.slice(0, spaceIdx))}${usage.slice(spaceIdx)}`;
|
|
3878
|
+
}
|
|
3879
|
+
function printVersion() {
|
|
3880
|
+
const kv = getKernelVersion() || "\u672A\u5B89\u88C5";
|
|
3881
|
+
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
3882
|
+
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
3883
|
+
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
3884
|
+
}
|
|
3885
|
+
|
|
3582
3886
|
// src/daemon.ts
|
|
3583
3887
|
import { spawnSync as spawnSync4 } from "child_process";
|
|
3584
3888
|
import fs6 from "fs";
|
|
3585
|
-
import
|
|
3889
|
+
import path5 from "path";
|
|
3586
3890
|
|
|
3587
3891
|
// src/process.ts
|
|
3588
3892
|
import { spawn, spawnSync as spawnSync3 } from "child_process";
|
|
3589
3893
|
import fs5 from "fs";
|
|
3590
|
-
import
|
|
3591
|
-
|
|
3592
|
-
// src/utils.ts
|
|
3593
|
-
import { spawnSync as spawnSync2 } from "child_process";
|
|
3594
|
-
import { createRequire } from "module";
|
|
3595
|
-
var require2 = createRequire(import.meta.url);
|
|
3596
|
-
var pkg = require2("../package.json");
|
|
3597
|
-
var VERSION = pkg.version;
|
|
3598
|
-
var sleepBuf = new Int32Array(new SharedArrayBuffer(4));
|
|
3599
|
-
var NO_COLOR = process.env.NO_COLOR !== void 0 || !process.stdout.isTTY;
|
|
3600
|
-
function colorize(code, str) {
|
|
3601
|
-
if (NO_COLOR) return String(str);
|
|
3602
|
-
return `${code + String(str)}\x1B[0m`;
|
|
3603
|
-
}
|
|
3604
|
-
var colors = {
|
|
3605
|
-
bold: (s) => colorize("\x1B[1m", s),
|
|
3606
|
-
red: (s) => colorize("\x1B[31m", s),
|
|
3607
|
-
green: (s) => colorize("\x1B[32m", s),
|
|
3608
|
-
yellow: (s) => colorize("\x1B[33m", s),
|
|
3609
|
-
cyan: (s) => colorize("\x1B[36m", s),
|
|
3610
|
-
gray: (s) => colorize("\x1B[90m", s)
|
|
3611
|
-
};
|
|
3612
|
-
function sleepSync(ms) {
|
|
3613
|
-
Atomics.wait(sleepBuf, 0, 0, ms);
|
|
3614
|
-
}
|
|
3615
|
-
function sleep(ms) {
|
|
3616
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3617
|
-
}
|
|
3618
|
-
var TimeoutError = class extends Error {
|
|
3619
|
-
constructor() {
|
|
3620
|
-
super("timeout");
|
|
3621
|
-
this.name = "TimeoutError";
|
|
3622
|
-
}
|
|
3623
|
-
};
|
|
3624
|
-
function withTimeout(promise, ms) {
|
|
3625
|
-
return new Promise((resolve, reject) => {
|
|
3626
|
-
const timer = setTimeout(() => reject(new TimeoutError()), ms);
|
|
3627
|
-
promise.then(
|
|
3628
|
-
(v) => {
|
|
3629
|
-
clearTimeout(timer);
|
|
3630
|
-
resolve(v);
|
|
3631
|
-
},
|
|
3632
|
-
(e) => {
|
|
3633
|
-
clearTimeout(timer);
|
|
3634
|
-
reject(e);
|
|
3635
|
-
}
|
|
3636
|
-
);
|
|
3637
|
-
});
|
|
3638
|
-
}
|
|
3639
|
-
function formatBytes(bytes) {
|
|
3640
|
-
if (bytes === void 0 || bytes === null) return "\u672A\u77E5";
|
|
3641
|
-
const num = Number(bytes);
|
|
3642
|
-
if (!Number.isFinite(num) || num < 0) return "\u672A\u77E5";
|
|
3643
|
-
if (num === 0) return "0 B";
|
|
3644
|
-
const k = 1024;
|
|
3645
|
-
const sizes = ["B", "KB", "MB", "GB", "TB"];
|
|
3646
|
-
const i = Math.min(Math.floor(Math.log(num) / Math.log(k)), sizes.length - 1);
|
|
3647
|
-
return `${parseFloat((num / k ** i).toFixed(2))} ${sizes[i]}`;
|
|
3648
|
-
}
|
|
3649
|
-
function formatTimestamp(ts) {
|
|
3650
|
-
if (ts === void 0 || ts === null) return "\u672A\u77E5";
|
|
3651
|
-
try {
|
|
3652
|
-
return new Date(ts * 1e3).toLocaleString("zh-CN");
|
|
3653
|
-
} catch {
|
|
3654
|
-
return "\u672A\u77E5";
|
|
3655
|
-
}
|
|
3656
|
-
}
|
|
3657
|
-
function formatDate(dateOrIso) {
|
|
3658
|
-
if (dateOrIso === void 0 || dateOrIso === null) return "\u672A\u77E5";
|
|
3659
|
-
try {
|
|
3660
|
-
const d = dateOrIso instanceof Date ? dateOrIso : new Date(dateOrIso);
|
|
3661
|
-
if (Number.isNaN(d.getTime())) return "\u672A\u77E5";
|
|
3662
|
-
return d.toLocaleString("zh-CN");
|
|
3663
|
-
} catch {
|
|
3664
|
-
return "\u672A\u77E5";
|
|
3665
|
-
}
|
|
3666
|
-
}
|
|
3667
|
-
function hasFlag(args, short, long) {
|
|
3668
|
-
return !!args && (args.includes(short) || args.includes(long));
|
|
3669
|
-
}
|
|
3670
|
-
function parseIntArg(args, short, long, defaultValue) {
|
|
3671
|
-
if (!args) return defaultValue;
|
|
3672
|
-
for (let i = 0; i < args.length; i++) {
|
|
3673
|
-
if (args[i] === short || args[i] === long) {
|
|
3674
|
-
if (i + 1 < args.length) {
|
|
3675
|
-
const val = parseInt(args[i + 1], 10);
|
|
3676
|
-
return Number.isNaN(val) ? defaultValue : val;
|
|
3677
|
-
}
|
|
3678
|
-
}
|
|
3679
|
-
}
|
|
3680
|
-
return defaultValue;
|
|
3681
|
-
}
|
|
3682
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set(["-t", "--timeout", "-j", "--concurrency", "-r", "--rounds", "-n", "--lines", "-u", "--update-timeout"]);
|
|
3683
|
-
function getNonFlagArg(args, startIdx, valueFlags = VALUE_FLAGS) {
|
|
3684
|
-
if (!args) return null;
|
|
3685
|
-
for (let i = startIdx; i < args.length; i++) {
|
|
3686
|
-
const a = args[i];
|
|
3687
|
-
if (a.startsWith("-")) {
|
|
3688
|
-
if (valueFlags.has(a)) i++;
|
|
3689
|
-
continue;
|
|
3690
|
-
}
|
|
3691
|
-
return a;
|
|
3692
|
-
}
|
|
3693
|
-
return null;
|
|
3694
|
-
}
|
|
3695
|
-
function isProcessRunning(pid) {
|
|
3696
|
-
if (!pid) return false;
|
|
3697
|
-
try {
|
|
3698
|
-
const result = spawnSync2("ps", ["-p", String(pid), "-o", "pid="], { encoding: "utf8", timeout: 5e3 });
|
|
3699
|
-
return (result.stdout || "").trim().length > 0;
|
|
3700
|
-
} catch {
|
|
3701
|
-
return false;
|
|
3702
|
-
}
|
|
3703
|
-
}
|
|
3704
|
-
function isProcessRoot(pid) {
|
|
3705
|
-
if (!pid) return false;
|
|
3706
|
-
try {
|
|
3707
|
-
const result = spawnSync2("ps", ["-p", String(pid), "-o", "uid="], { encoding: "utf8", timeout: 5e3 });
|
|
3708
|
-
return (result.stdout || "").trim() === "0";
|
|
3709
|
-
} catch {
|
|
3710
|
-
return false;
|
|
3711
|
-
}
|
|
3712
|
-
}
|
|
3713
|
-
function createHttpClient(options = {}) {
|
|
3714
|
-
const { timeout = 6e4 } = options;
|
|
3715
|
-
return {
|
|
3716
|
-
async get(url, config) {
|
|
3717
|
-
const controller = new AbortController();
|
|
3718
|
-
const timer = setTimeout(() => controller.abort(), timeout);
|
|
3719
|
-
const signal = config?.signal ? AbortSignal.any([controller.signal, config.signal]) : controller.signal;
|
|
3720
|
-
try {
|
|
3721
|
-
const response = await fetch(url, {
|
|
3722
|
-
signal,
|
|
3723
|
-
headers: { "User-Agent": `mihomo-cli/${VERSION}` }
|
|
3724
|
-
});
|
|
3725
|
-
if (!response.ok) {
|
|
3726
|
-
const error = new Error(`HTTP ${response.status}`);
|
|
3727
|
-
error.response = { status: response.status };
|
|
3728
|
-
try {
|
|
3729
|
-
error.response.data = await response.json();
|
|
3730
|
-
} catch {
|
|
3731
|
-
}
|
|
3732
|
-
throw error;
|
|
3733
|
-
}
|
|
3734
|
-
const data = config?.responseType === "json" ? await response.json() : await response.text();
|
|
3735
|
-
return { data, headers: response.headers, status: response.status };
|
|
3736
|
-
} finally {
|
|
3737
|
-
clearTimeout(timer);
|
|
3738
|
-
}
|
|
3739
|
-
}
|
|
3740
|
-
};
|
|
3741
|
-
}
|
|
3742
|
-
function normalizeMirrorUrl(val) {
|
|
3743
|
-
if (!val) return null;
|
|
3744
|
-
if (val === "direct" || val === "no" || val === "none") return null;
|
|
3745
|
-
let url = val;
|
|
3746
|
-
if (!url.startsWith("http")) {
|
|
3747
|
-
url = `https://${url}`;
|
|
3748
|
-
}
|
|
3749
|
-
if (!url.endsWith("/")) {
|
|
3750
|
-
url += "/";
|
|
3751
|
-
}
|
|
3752
|
-
return url;
|
|
3753
|
-
}
|
|
3754
|
-
function parseMirrorArg(args) {
|
|
3755
|
-
if (!args || args.length < 2) {
|
|
3756
|
-
return { mirror: null, isOverride: false, type: "download" };
|
|
3757
|
-
}
|
|
3758
|
-
if (args.includes("--no-mirror") || args.includes("--direct")) {
|
|
3759
|
-
return { mirror: null, isOverride: true, type: "download" };
|
|
3760
|
-
}
|
|
3761
|
-
const mirrorAllIdx = args.indexOf("--mirror-all");
|
|
3762
|
-
if (mirrorAllIdx >= 0) {
|
|
3763
|
-
const nextArg = args[mirrorAllIdx + 1];
|
|
3764
|
-
if (!nextArg || nextArg.startsWith("-")) {
|
|
3765
|
-
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "all" };
|
|
3766
|
-
}
|
|
3767
|
-
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "all" };
|
|
3768
|
-
}
|
|
3769
|
-
const mirrorIdx = args.indexOf("--mirror");
|
|
3770
|
-
if (mirrorIdx >= 0) {
|
|
3771
|
-
const nextArg = args[mirrorIdx + 1];
|
|
3772
|
-
if (!nextArg || nextArg.startsWith("-")) {
|
|
3773
|
-
return { mirror: "https://v6.gh-proxy.org/", isOverride: true, type: "download" };
|
|
3774
|
-
}
|
|
3775
|
-
return { mirror: normalizeMirrorUrl(nextArg), isOverride: true, type: "download" };
|
|
3776
|
-
}
|
|
3777
|
-
return { mirror: null, isOverride: false, type: "download" };
|
|
3778
|
-
}
|
|
3779
|
-
function isProxyValid(proxy) {
|
|
3780
|
-
if (!proxy.name || !proxy.server || !proxy.port) return false;
|
|
3781
|
-
if (!proxy.type) return false;
|
|
3782
|
-
if (proxy.type === "ss" && typeof proxy.cipher === "string" && proxy.cipher.startsWith("2022-blake3")) {
|
|
3783
|
-
const pw = String(proxy.password || "");
|
|
3784
|
-
if (!/^[A-Za-z0-9+/\-_]+=*$/.test(pw) || pw.length < 20) return false;
|
|
3785
|
-
}
|
|
3786
|
-
return true;
|
|
3787
|
-
}
|
|
3788
|
-
|
|
3789
|
-
// src/process.ts
|
|
3894
|
+
import path4 from "path";
|
|
3790
3895
|
var PROCESS_WAIT_ATTEMPTS = 50;
|
|
3791
3896
|
var PROCESS_WAIT_INTERVAL = 100;
|
|
3792
3897
|
var STARTUP_WAIT_MS = 800;
|
|
@@ -3794,9 +3899,7 @@ var SUDO_TIMEOUT_MS = 6e4;
|
|
|
3794
3899
|
var TUN_MODE_POST_WAIT_MS = 500;
|
|
3795
3900
|
var BATCH_KILL_THRESHOLD = 3;
|
|
3796
3901
|
var DEFAULT_LOG_RETENTION_DAYS = 7;
|
|
3797
|
-
|
|
3798
|
-
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3799
|
-
}
|
|
3902
|
+
var MAIN_INSTANCE_PATTERN = `${escapeRegExp(PATHS.mihomoBinary)}.*${escapeRegExp(PATHS.configFile)}`;
|
|
3800
3903
|
function clearRuntime() {
|
|
3801
3904
|
if (fs5.existsSync(DIRS.runtime)) {
|
|
3802
3905
|
rmrf(DIRS.runtime);
|
|
@@ -3816,10 +3919,9 @@ function isRunning() {
|
|
|
3816
3919
|
const pid = getPid();
|
|
3817
3920
|
return pid ? isProcessRunning(pid) : false;
|
|
3818
3921
|
}
|
|
3819
|
-
function
|
|
3820
|
-
const binaryPath = PATHS.mihomoBinary;
|
|
3922
|
+
function getMihomoPids() {
|
|
3821
3923
|
try {
|
|
3822
|
-
const result = spawnSync3("pgrep", ["-f",
|
|
3924
|
+
const result = spawnSync3("pgrep", ["-f", MAIN_INSTANCE_PATTERN], { encoding: "utf8", timeout: 1e4 });
|
|
3823
3925
|
const output = (result.stdout || "").trim();
|
|
3824
3926
|
if (!output) return [];
|
|
3825
3927
|
return output.split("\n").filter(Boolean).map((p) => parseInt(p, 10)).filter((p) => Number.isInteger(p) && p > 0);
|
|
@@ -3837,7 +3939,7 @@ function isPidFileOwnedByRoot() {
|
|
|
3837
3939
|
}
|
|
3838
3940
|
}
|
|
3839
3941
|
function checkStaleState() {
|
|
3840
|
-
const allPids =
|
|
3942
|
+
const allPids = getMihomoPids();
|
|
3841
3943
|
const hasRootProcess = allPids.some((p) => isProcessRoot(p));
|
|
3842
3944
|
const hasRootPidFile = isPidFileOwnedByRoot();
|
|
3843
3945
|
return {
|
|
@@ -3888,7 +3990,7 @@ function killProcess(pid, needsSudo = false) {
|
|
|
3888
3990
|
}
|
|
3889
3991
|
}
|
|
3890
3992
|
function killAllMihomo(forceSudo = false) {
|
|
3891
|
-
const pattern =
|
|
3993
|
+
const pattern = MAIN_INSTANCE_PATTERN;
|
|
3892
3994
|
if (forceSudo) {
|
|
3893
3995
|
try {
|
|
3894
3996
|
spawnSync3("sudo", ["pkill", "-9", "-f", pattern], { stdio: "inherit", timeout: 15e3 });
|
|
@@ -3906,7 +4008,7 @@ function killAllMihomo(forceSudo = false) {
|
|
|
3906
4008
|
}
|
|
3907
4009
|
}
|
|
3908
4010
|
function cleanupAll(forceSudo = false) {
|
|
3909
|
-
const pids =
|
|
4011
|
+
const pids = getMihomoPids();
|
|
3910
4012
|
if (pids.length === 0) {
|
|
3911
4013
|
clearPid();
|
|
3912
4014
|
return { killed: 0, failed: 0, remaining: [] };
|
|
@@ -3937,26 +4039,26 @@ function cleanupAll(forceSudo = false) {
|
|
|
3937
4039
|
}
|
|
3938
4040
|
}
|
|
3939
4041
|
for (let i = 0; i < PROCESS_WAIT_ATTEMPTS; i++) {
|
|
3940
|
-
if (
|
|
4042
|
+
if (getMihomoPids().length === 0) break;
|
|
3941
4043
|
sleepSync(PROCESS_WAIT_INTERVAL);
|
|
3942
4044
|
}
|
|
3943
4045
|
clearPid();
|
|
3944
|
-
return { killed: killedCount, failed: failedPids.length, remaining:
|
|
4046
|
+
return { killed: killedCount, failed: failedPids.length, remaining: getMihomoPids() };
|
|
3945
4047
|
}
|
|
3946
4048
|
function createTunLaunchScript() {
|
|
3947
|
-
const binary = PATHS.mihomoBinary;
|
|
3948
|
-
const configFile = PATHS.configFile;
|
|
3949
|
-
const logFile = PATHS.logFile;
|
|
3950
|
-
const pidFile = PATHS.pidFile;
|
|
3951
|
-
const dataDir = DIRS.data;
|
|
3952
|
-
const killPattern =
|
|
4049
|
+
const binary = shellQuote(PATHS.mihomoBinary);
|
|
4050
|
+
const configFile = shellQuote(PATHS.configFile);
|
|
4051
|
+
const logFile = shellQuote(PATHS.logFile);
|
|
4052
|
+
const pidFile = shellQuote(PATHS.pidFile);
|
|
4053
|
+
const dataDir = shellQuote(DIRS.data);
|
|
4054
|
+
const killPattern = shellQuote(MAIN_INSTANCE_PATTERN);
|
|
3953
4055
|
const scriptContent = `#!/bin/bash
|
|
3954
|
-
BINARY
|
|
3955
|
-
CONFIG_FILE
|
|
3956
|
-
LOG_FILE
|
|
3957
|
-
PID_FILE
|
|
3958
|
-
DATA_DIR
|
|
3959
|
-
KILL_PATTERN
|
|
4056
|
+
BINARY=${binary}
|
|
4057
|
+
CONFIG_FILE=${configFile}
|
|
4058
|
+
LOG_FILE=${logFile}
|
|
4059
|
+
PID_FILE=${pidFile}
|
|
4060
|
+
DATA_DIR=${dataDir}
|
|
4061
|
+
KILL_PATTERN=${killPattern}
|
|
3960
4062
|
|
|
3961
4063
|
# \u7EC8\u6B62\u65E7\u8FDB\u7A0B
|
|
3962
4064
|
pkill -9 -f "\${KILL_PATTERN}" 2>/dev/null || true
|
|
@@ -3987,7 +4089,7 @@ echo "--- \u65E5\u5FD7 ---"
|
|
|
3987
4089
|
tail -25 "\${LOG_FILE}" 2>/dev/null
|
|
3988
4090
|
exit 1
|
|
3989
4091
|
`;
|
|
3990
|
-
const scriptPath =
|
|
4092
|
+
const scriptPath = path4.join(DIRS.runtime, "launch-tun.sh");
|
|
3991
4093
|
fs5.writeFileSync(scriptPath, scriptContent, { mode: 448 });
|
|
3992
4094
|
return scriptPath;
|
|
3993
4095
|
}
|
|
@@ -4009,7 +4111,7 @@ function getProcessInfo(pid) {
|
|
|
4009
4111
|
function getStatus() {
|
|
4010
4112
|
const running = isRunning();
|
|
4011
4113
|
const pid = getPid();
|
|
4012
|
-
const allPids =
|
|
4114
|
+
const allPids = getMihomoPids();
|
|
4013
4115
|
return {
|
|
4014
4116
|
running,
|
|
4015
4117
|
pid: running ? pid : null,
|
|
@@ -4059,6 +4161,16 @@ async function startMixedMode(staleState) {
|
|
|
4059
4161
|
const configFile = PATHS.configFile;
|
|
4060
4162
|
const logFile = PATHS.logFile;
|
|
4061
4163
|
const args = ["-d", DIRS.data, "-f", configFile];
|
|
4164
|
+
if (fs5.existsSync(logFile)) {
|
|
4165
|
+
try {
|
|
4166
|
+
fs5.accessSync(logFile, fs5.constants.W_OK);
|
|
4167
|
+
} catch {
|
|
4168
|
+
try {
|
|
4169
|
+
fs5.unlinkSync(logFile);
|
|
4170
|
+
} catch {
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4062
4174
|
const logFd = fs5.openSync(logFile, "a");
|
|
4063
4175
|
const child = spawn(PATHS.mihomoBinary, args, {
|
|
4064
4176
|
detached: true,
|
|
@@ -4121,14 +4233,14 @@ async function startTunMode(staleState) {
|
|
|
4121
4233
|
return { success: true, pid: finalPid, mode: "tun" };
|
|
4122
4234
|
}
|
|
4123
4235
|
function stop(forceSudo = false) {
|
|
4124
|
-
const allPids =
|
|
4236
|
+
const allPids = getMihomoPids();
|
|
4125
4237
|
if (allPids.length === 0) {
|
|
4126
4238
|
clearPid();
|
|
4127
4239
|
clearRuntime();
|
|
4128
4240
|
return { success: true, notRunning: true };
|
|
4129
4241
|
}
|
|
4130
4242
|
const result = cleanupAll(forceSudo);
|
|
4131
|
-
const remaining =
|
|
4243
|
+
const remaining = getMihomoPids();
|
|
4132
4244
|
if (remaining.length > 0) {
|
|
4133
4245
|
console.log("");
|
|
4134
4246
|
console.log("\u4ECD\u6709\u8FDB\u7A0B\u6B8B\u7559\uFF0C\u9700\u8981\u624B\u52A8\u6E05\u7406:");
|
|
@@ -4152,9 +4264,8 @@ function rotateLog() {
|
|
|
4152
4264
|
if (!fs5.existsSync(logFile)) return null;
|
|
4153
4265
|
const stat = fs5.statSync(logFile);
|
|
4154
4266
|
if (stat.size === 0) return null;
|
|
4155
|
-
const
|
|
4156
|
-
const
|
|
4157
|
-
const rotatedPath = path3.join(DIRS.logs, rotatedName);
|
|
4267
|
+
const rotatedName = `mihomo.${formatLocalTimestamp()}.log`;
|
|
4268
|
+
const rotatedPath = path4.join(DIRS.logs, rotatedName);
|
|
4158
4269
|
fs5.renameSync(logFile, rotatedPath);
|
|
4159
4270
|
return rotatedPath;
|
|
4160
4271
|
}
|
|
@@ -4169,7 +4280,7 @@ function cleanupOldLogs(maxAgeDays = DEFAULT_LOG_RETENTION_DAYS) {
|
|
|
4169
4280
|
for (const file of files) {
|
|
4170
4281
|
if (!file.match(/^mihomo\.\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}\.log$/)) continue;
|
|
4171
4282
|
try {
|
|
4172
|
-
const filePath =
|
|
4283
|
+
const filePath = path4.join(logsDir, file);
|
|
4173
4284
|
const stat = fs5.statSync(filePath);
|
|
4174
4285
|
if (now - stat.mtimeMs > maxAgeMs) {
|
|
4175
4286
|
fs5.unlinkSync(filePath);
|
|
@@ -4200,7 +4311,7 @@ function listLogs() {
|
|
|
4200
4311
|
const match = file.match(/^mihomo\.(\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2})\.log$/);
|
|
4201
4312
|
if (!match) continue;
|
|
4202
4313
|
try {
|
|
4203
|
-
const filePath =
|
|
4314
|
+
const filePath = path4.join(logsDir, file);
|
|
4204
4315
|
const stat = fs5.statSync(filePath);
|
|
4205
4316
|
result.archives.push({
|
|
4206
4317
|
name: file,
|
|
@@ -4216,22 +4327,22 @@ function listLogs() {
|
|
|
4216
4327
|
return result;
|
|
4217
4328
|
}
|
|
4218
4329
|
function isPathUnderDir(filePath, baseDir) {
|
|
4219
|
-
const resolvedPath =
|
|
4220
|
-
const resolvedBase =
|
|
4221
|
-
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase +
|
|
4330
|
+
const resolvedPath = path4.resolve(filePath);
|
|
4331
|
+
const resolvedBase = path4.resolve(baseDir);
|
|
4332
|
+
return resolvedPath === resolvedBase || resolvedPath.startsWith(resolvedBase + path4.sep);
|
|
4222
4333
|
}
|
|
4223
4334
|
function getLogPathByName(name) {
|
|
4224
4335
|
const logsDir = DIRS.logs;
|
|
4225
4336
|
let targetName = name;
|
|
4226
4337
|
if (!name.endsWith(".log")) targetName = `mihomo.${name}.log`;
|
|
4227
4338
|
if (!targetName.startsWith("mihomo.")) targetName = `mihomo.${targetName}`;
|
|
4228
|
-
const filePath =
|
|
4339
|
+
const filePath = path4.join(logsDir, targetName);
|
|
4229
4340
|
if (fs5.existsSync(filePath) && isPathUnderDir(filePath, logsDir)) return filePath;
|
|
4230
4341
|
if (fs5.existsSync(logsDir)) {
|
|
4231
4342
|
const files = fs5.readdirSync(logsDir);
|
|
4232
4343
|
for (const file of files) {
|
|
4233
4344
|
if (file.includes(name)) {
|
|
4234
|
-
const candidatePath =
|
|
4345
|
+
const candidatePath = path4.join(logsDir, file);
|
|
4235
4346
|
if (isPathUnderDir(candidatePath, logsDir)) return candidatePath;
|
|
4236
4347
|
}
|
|
4237
4348
|
}
|
|
@@ -4280,31 +4391,16 @@ function viewLogWithTail(logPath, options) {
|
|
|
4280
4391
|
}
|
|
4281
4392
|
|
|
4282
4393
|
// src/daemon.ts
|
|
4283
|
-
var
|
|
4284
|
-
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4289
|
-
|
|
4290
|
-
}
|
|
4291
|
-
|
|
4292
|
-
function getUid() {
|
|
4293
|
-
const uid = process.getuid?.();
|
|
4294
|
-
if (uid === void 0) {
|
|
4295
|
-
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u7528\u6237 UID\uFF0C\u4FDD\u6D3B\u529F\u80FD\u4EC5\u652F\u6301 macOS");
|
|
4394
|
+
var SERVICE_TARGET = `system/${LAUNCH_DAEMON_LABEL}`;
|
|
4395
|
+
var HOT_RELOAD_TIMEOUT_MS = 5e3;
|
|
4396
|
+
var DAEMON_BOOT_WAIT_MS = 500;
|
|
4397
|
+
var LOG_ROTATE_MAX_BYTES = 10 * 1024 * 1024;
|
|
4398
|
+
function logOversized() {
|
|
4399
|
+
try {
|
|
4400
|
+
return fs6.statSync(PATHS.logFile).size > LOG_ROTATE_MAX_BYTES;
|
|
4401
|
+
} catch {
|
|
4402
|
+
return false;
|
|
4296
4403
|
}
|
|
4297
|
-
return uid;
|
|
4298
|
-
}
|
|
4299
|
-
function guiDomain(uid) {
|
|
4300
|
-
return `gui/${uid}`;
|
|
4301
|
-
}
|
|
4302
|
-
function serviceTarget(uid) {
|
|
4303
|
-
return `gui/${uid}/${LAUNCH_AGENT_LABEL}`;
|
|
4304
|
-
}
|
|
4305
|
-
function launchctlError(action, result) {
|
|
4306
|
-
const detail = (result.stderr || result.stdout || "").trim() || `\u9000\u51FA\u7801 ${result.status}`;
|
|
4307
|
-
return new Error(`launchctl ${action} \u5931\u8D25: ${detail}`);
|
|
4308
4404
|
}
|
|
4309
4405
|
function escapeXml(s) {
|
|
4310
4406
|
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
@@ -4317,7 +4413,7 @@ function buildPlist() {
|
|
|
4317
4413
|
<plist version="1.0">
|
|
4318
4414
|
<dict>
|
|
4319
4415
|
<key>Label</key>
|
|
4320
|
-
<string>${escapeXml(
|
|
4416
|
+
<string>${escapeXml(LAUNCH_DAEMON_LABEL)}</string>
|
|
4321
4417
|
<key>ProgramArguments</key>
|
|
4322
4418
|
<array>
|
|
4323
4419
|
${argsXml}
|
|
@@ -4336,25 +4432,42 @@ ${argsXml}
|
|
|
4336
4432
|
</plist>
|
|
4337
4433
|
`;
|
|
4338
4434
|
}
|
|
4435
|
+
function runSudoScript(scriptBody, opts) {
|
|
4436
|
+
if (!process.stdin.isTTY) {
|
|
4437
|
+
throw new Error("\u5F53\u524D\u73AF\u5883\u65E0\u6CD5\u8F93\u5165\u7BA1\u7406\u5458\u5BC6\u7801\uFF08\u9700\u8981\u5728\u4EA4\u4E92\u5F0F\u7EC8\u7AEF\u8FD0\u884C sudo\uFF09");
|
|
4438
|
+
}
|
|
4439
|
+
ensureDirs();
|
|
4440
|
+
const scriptPath = path5.join(DIRS.runtime, opts.file);
|
|
4441
|
+
fs6.writeFileSync(scriptPath, scriptBody, { mode: 448 });
|
|
4442
|
+
try {
|
|
4443
|
+
const result = spawnSync4("sudo", [scriptPath], { stdio: "inherit", timeout: SUDO_TIMEOUT_MS });
|
|
4444
|
+
if (result.error) throw result.error;
|
|
4445
|
+
if (result.status !== 0) {
|
|
4446
|
+
if (result.status === 1) {
|
|
4447
|
+
throw new Error("\u5DF2\u53D6\u6D88\u6216\u5BC6\u7801\u9519\u8BEF");
|
|
4448
|
+
}
|
|
4449
|
+
if (result.status == null) {
|
|
4450
|
+
throw new Error(`${opts.action}\u88AB\u4E2D\u65AD\uFF08sudo \u8FDB\u7A0B\u88AB\u4FE1\u53F7\u7EC8\u6B62\uFF09`);
|
|
4451
|
+
}
|
|
4452
|
+
const custom = opts.codeMessages?.[result.status];
|
|
4453
|
+
throw new Error(custom || `${opts.action}\u5931\u8D25\uFF08\u9000\u51FA\u7801 ${result.status}\uFF09`);
|
|
4454
|
+
}
|
|
4455
|
+
} finally {
|
|
4456
|
+
try {
|
|
4457
|
+
fs6.unlinkSync(scriptPath);
|
|
4458
|
+
} catch {
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4339
4462
|
function isDaemonEnabled() {
|
|
4340
|
-
return fs6.existsSync(PATHS.
|
|
4463
|
+
return fs6.existsSync(PATHS.launchDaemonPlist);
|
|
4341
4464
|
}
|
|
4342
4465
|
function getDaemonStatus() {
|
|
4343
4466
|
if (!isDaemonEnabled()) {
|
|
4344
4467
|
return { enabled: false, loaded: false, pid: null };
|
|
4345
4468
|
}
|
|
4346
|
-
const
|
|
4347
|
-
|
|
4348
|
-
const pids = getAllMihomoPids();
|
|
4349
|
-
return { enabled: true, loaded: pids.length > 0, pid: pids[0] ?? null };
|
|
4350
|
-
}
|
|
4351
|
-
const result = launchctl(["print", serviceTarget(uid)]);
|
|
4352
|
-
if (result.status !== 0) {
|
|
4353
|
-
return { enabled: true, loaded: false, pid: null };
|
|
4354
|
-
}
|
|
4355
|
-
const pidMatch = result.stdout.match(/\bpid = (\d+)/);
|
|
4356
|
-
const pid = pidMatch ? parseInt(pidMatch[1], 10) : null;
|
|
4357
|
-
return { enabled: true, loaded: true, pid };
|
|
4469
|
+
const rootPids = getMihomoPids().filter(isProcessRoot);
|
|
4470
|
+
return { enabled: true, loaded: rootPids.length > 0, pid: rootPids[0] ?? null };
|
|
4358
4471
|
}
|
|
4359
4472
|
function isDaemonRunning(status) {
|
|
4360
4473
|
return status.loaded && status.pid !== null;
|
|
@@ -4366,47 +4479,104 @@ function enableDaemon() {
|
|
|
4366
4479
|
if (!fs6.existsSync(PATHS.configFile)) {
|
|
4367
4480
|
throw new Error("\u672A\u627E\u5230\u8FD0\u884C\u65F6\u914D\u7F6E\uFF0C\u8BF7\u5148\u6DFB\u52A0\u8BA2\u9605");
|
|
4368
4481
|
}
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
const
|
|
4375
|
-
|
|
4376
|
-
|
|
4482
|
+
ensureDirs();
|
|
4483
|
+
const stagePath = path5.join(DIRS.runtime, "daemon.plist.stage");
|
|
4484
|
+
atomicWriteFileSync(stagePath, buildPlist(), { mode: 384 });
|
|
4485
|
+
const target = shellQuote(SERVICE_TARGET);
|
|
4486
|
+
const plistDest = shellQuote(PATHS.launchDaemonPlist);
|
|
4487
|
+
const stage = shellQuote(stagePath);
|
|
4488
|
+
const pattern = shellQuote(MAIN_INSTANCE_PATTERN);
|
|
4489
|
+
const script = [
|
|
4490
|
+
"#!/bin/bash",
|
|
4491
|
+
`launchctl bootout ${target} 2>/dev/null || true`,
|
|
4492
|
+
`pkill -9 -f ${pattern} 2>/dev/null || true`,
|
|
4493
|
+
"sleep 0.2",
|
|
4494
|
+
`install -m 644 -o root -g wheel ${stage} ${plistDest} || exit 2`,
|
|
4495
|
+
`launchctl bootstrap system ${plistDest} || { launchctl bootout ${target} 2>/dev/null; rm -f ${plistDest}; exit 3; }`,
|
|
4496
|
+
"exit 0",
|
|
4497
|
+
""
|
|
4498
|
+
].join("\n");
|
|
4499
|
+
try {
|
|
4500
|
+
runSudoScript(script, {
|
|
4501
|
+
action: "\u542F\u7528\u4FDD\u6D3B",
|
|
4502
|
+
file: "daemon-enable.sh",
|
|
4503
|
+
codeMessages: {
|
|
4504
|
+
2: "\u5B89\u88C5 plist \u5230 /Library/LaunchDaemons \u5931\u8D25",
|
|
4505
|
+
3: "\u88C5\u8F7D\u4FDD\u6D3B\u670D\u52A1\u5931\u8D25\uFF08launchctl bootstrap\uFF09"
|
|
4506
|
+
}
|
|
4507
|
+
});
|
|
4508
|
+
} finally {
|
|
4377
4509
|
try {
|
|
4378
|
-
fs6.unlinkSync(
|
|
4510
|
+
fs6.unlinkSync(stagePath);
|
|
4379
4511
|
} catch {
|
|
4380
4512
|
}
|
|
4381
|
-
throw launchctlError("bootstrap", result);
|
|
4382
4513
|
}
|
|
4383
4514
|
}
|
|
4384
4515
|
function disableDaemon() {
|
|
4385
|
-
|
|
4386
|
-
|
|
4387
|
-
|
|
4516
|
+
if (!isDaemonEnabled()) return;
|
|
4517
|
+
const target = shellQuote(SERVICE_TARGET);
|
|
4518
|
+
const plistDest = shellQuote(PATHS.launchDaemonPlist);
|
|
4519
|
+
const logFile = shellQuote(PATHS.logFile);
|
|
4520
|
+
const dataDir = shellQuote(DIRS.data);
|
|
4521
|
+
const script = [
|
|
4522
|
+
"#!/bin/bash",
|
|
4523
|
+
`launchctl bootout ${target} 2>/dev/null || true`,
|
|
4524
|
+
`rm -f ${plistDest}`,
|
|
4525
|
+
`chown "$SUDO_UID:$SUDO_GID" ${logFile} 2>/dev/null || true`,
|
|
4526
|
+
`chown -R "$SUDO_UID:$SUDO_GID" ${dataDir} 2>/dev/null || true`,
|
|
4527
|
+
"exit 0",
|
|
4528
|
+
""
|
|
4529
|
+
].join("\n");
|
|
4530
|
+
runSudoScript(script, { action: "\u5173\u95ED\u4FDD\u6D3B", file: "daemon-disable.sh" });
|
|
4531
|
+
const rootPids = getMihomoPids().filter(isProcessRoot);
|
|
4532
|
+
if (rootPids.length > 0) {
|
|
4533
|
+
console.log("");
|
|
4534
|
+
console.log(`\u4ECD\u6709 root \u5185\u6838\u8FDB\u7A0B\u6B8B\u7559 (PID ${rootPids.join(", ")})`);
|
|
4535
|
+
console.log("\u624B\u52A8\u6E05\u7406: sudo pkill -9 mihomo");
|
|
4388
4536
|
}
|
|
4537
|
+
}
|
|
4538
|
+
async function tryHotReload() {
|
|
4539
|
+
const controller = new AbortController();
|
|
4540
|
+
const timer = setTimeout(() => controller.abort(), HOT_RELOAD_TIMEOUT_MS);
|
|
4389
4541
|
try {
|
|
4390
|
-
|
|
4542
|
+
const res = await fetch(`${CONTROLLER_BASE_URL}/configs?force=true`, {
|
|
4543
|
+
method: "PUT",
|
|
4544
|
+
headers: { "Content-Type": "application/json" },
|
|
4545
|
+
body: "{}",
|
|
4546
|
+
signal: controller.signal
|
|
4547
|
+
});
|
|
4548
|
+
return res.status === 204 || res.ok;
|
|
4391
4549
|
} catch {
|
|
4550
|
+
return false;
|
|
4551
|
+
} finally {
|
|
4552
|
+
clearTimeout(timer);
|
|
4392
4553
|
}
|
|
4393
4554
|
}
|
|
4394
|
-
function restartDaemon() {
|
|
4395
|
-
|
|
4396
|
-
if (!fs6.existsSync(PATHS.launchAgentPlist)) {
|
|
4555
|
+
async function restartDaemon() {
|
|
4556
|
+
if (!fs6.existsSync(PATHS.launchDaemonPlist)) {
|
|
4397
4557
|
throw new Error("\u4FDD\u6D3B\u672A\u542F\u7528\uFF0C\u65E0\u6CD5\u91CD\u542F");
|
|
4398
4558
|
}
|
|
4399
|
-
if (
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4559
|
+
if (!logOversized() && await tryHotReload()) return;
|
|
4560
|
+
const target = shellQuote(SERVICE_TARGET);
|
|
4561
|
+
const plistDest = shellQuote(PATHS.launchDaemonPlist);
|
|
4562
|
+
const logFile = shellQuote(PATHS.logFile);
|
|
4563
|
+
const archiveFile = shellQuote(path5.join(DIRS.logs, `mihomo.${formatLocalTimestamp()}.log`));
|
|
4564
|
+
const script = [
|
|
4565
|
+
"#!/bin/bash",
|
|
4566
|
+
`if [ -f ${logFile} ] && [ "$(stat -f%z ${logFile} 2>/dev/null || echo 0)" -gt ${LOG_ROTATE_MAX_BYTES} ]; then`,
|
|
4567
|
+
` cp ${logFile} ${archiveFile} 2>/dev/null && : > ${logFile}`,
|
|
4568
|
+
"fi",
|
|
4569
|
+
`if launchctl kickstart -k ${target} 2>/dev/null; then exit 0; fi`,
|
|
4570
|
+
`launchctl bootstrap system ${plistDest} || exit 3`,
|
|
4571
|
+
"exit 0",
|
|
4572
|
+
""
|
|
4573
|
+
].join("\n");
|
|
4574
|
+
runSudoScript(script, {
|
|
4575
|
+
action: "\u91CD\u542F\u4FDD\u6D3B",
|
|
4576
|
+
file: "daemon-restart.sh",
|
|
4577
|
+
codeMessages: { 3: "\u91CD\u542F\u4FDD\u6D3B\u5931\u8D25\uFF08launchctl bootstrap\uFF09" }
|
|
4578
|
+
});
|
|
4579
|
+
cleanupOldLogs();
|
|
4410
4580
|
}
|
|
4411
4581
|
|
|
4412
4582
|
// src/subscription.ts
|
|
@@ -4419,7 +4589,6 @@ function getDefaultUpdateInterval(url) {
|
|
|
4419
4589
|
function resolveUpdateInterval(url, cachedInterval) {
|
|
4420
4590
|
return cachedInterval && cachedInterval > 0 ? cachedInterval : getDefaultUpdateInterval(url);
|
|
4421
4591
|
}
|
|
4422
|
-
var YAML_DUMP_OPTS = { indent: 2, lineWidth: -1, schema: CORE_SCHEMA };
|
|
4423
4592
|
var HTTP_CLIENT = createHttpClient({ timeout: 6e4 });
|
|
4424
4593
|
function isMultiUrl(url) {
|
|
4425
4594
|
return url.includes(",");
|
|
@@ -4443,7 +4612,7 @@ function saveSubscriptionConfig(subName, parsed) {
|
|
|
4443
4612
|
normalizeProxyNamesBeforeSave(parsed);
|
|
4444
4613
|
parsed.raw.proxies = parsed.proxies;
|
|
4445
4614
|
parsed.raw["proxy-groups"] = parsed.proxyGroups;
|
|
4446
|
-
saveSubscriptionRawConfig(subName,
|
|
4615
|
+
saveSubscriptionRawConfig(subName, dumpYaml(parsed.raw));
|
|
4447
4616
|
}
|
|
4448
4617
|
function parseUserInfo(header) {
|
|
4449
4618
|
if (!header) return null;
|
|
@@ -4610,7 +4779,7 @@ async function downloadMergedSubscription(urls, subName, signal) {
|
|
|
4610
4779
|
}
|
|
4611
4780
|
}
|
|
4612
4781
|
base.proxies = baseProxies;
|
|
4613
|
-
const mergedContent =
|
|
4782
|
+
const mergedContent = dumpYaml(base);
|
|
4614
4783
|
saveSubscriptionRawConfig(subName, mergedContent);
|
|
4615
4784
|
const meta = extractSubscriptionMeta(responses[0].response?.headers);
|
|
4616
4785
|
saveSubscriptionMeta(subName, meta);
|
|
@@ -4706,8 +4875,7 @@ async function autoUpdateStaleSubscription(options = {}) {
|
|
|
4706
4875
|
}
|
|
4707
4876
|
return { total: staleSubs.length, updated: updatedCount, failed: staleSubs.length - updatedCount };
|
|
4708
4877
|
}
|
|
4709
|
-
|
|
4710
|
-
async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = API_BASE) {
|
|
4878
|
+
async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = CONTROLLER_BASE_URL) {
|
|
4711
4879
|
const encodedName = encodeURIComponent(proxyName);
|
|
4712
4880
|
const url = `${apiBase}/proxies/${encodedName}/delay?timeout=${timeout}&url=${encodeURIComponent(testUrl)}`;
|
|
4713
4881
|
try {
|
|
@@ -4729,7 +4897,13 @@ async function testProxyDelay(proxyName, timeout, testUrl, client, apiBase = API
|
|
|
4729
4897
|
}
|
|
4730
4898
|
}
|
|
4731
4899
|
async function testSubscriptionProxies(subName, options = {}) {
|
|
4732
|
-
const {
|
|
4900
|
+
const {
|
|
4901
|
+
timeout = DEFAULT_TEST_TIMEOUT,
|
|
4902
|
+
concurrency = DEFAULT_TEST_CONCURRENCY,
|
|
4903
|
+
testUrl = DEFAULT_TEST_URL,
|
|
4904
|
+
apiBase = CONTROLLER_BASE_URL,
|
|
4905
|
+
onResult
|
|
4906
|
+
} = options;
|
|
4733
4907
|
const { proxies } = options.parsed || loadSubscriptionConfig(subName);
|
|
4734
4908
|
if (proxies.length === 0) {
|
|
4735
4909
|
return { total: 0, alive: 0, dead: 0, results: [] };
|
|
@@ -4791,7 +4965,8 @@ function cleanDeadProxies(parsed, deadNames) {
|
|
|
4791
4965
|
if (group.proxies.length < before) {
|
|
4792
4966
|
updatedGroups++;
|
|
4793
4967
|
}
|
|
4794
|
-
|
|
4968
|
+
const hasOtherSource = group.use || group["include-all"] || group["include-all-proxies"];
|
|
4969
|
+
if (group.proxies.length === 0 && !hasOtherSource) {
|
|
4795
4970
|
removedGroupNames.add(group.name);
|
|
4796
4971
|
}
|
|
4797
4972
|
}
|
|
@@ -4807,9 +4982,7 @@ function cleanDeadProxies(parsed, deadNames) {
|
|
|
4807
4982
|
if (Array.isArray(rules)) {
|
|
4808
4983
|
parsed.raw.rules = rules.filter((rule) => {
|
|
4809
4984
|
if (typeof rule !== "string") return true;
|
|
4810
|
-
|
|
4811
|
-
if (parts.length < 2) return true;
|
|
4812
|
-
return !removedGroupNames.has(parts[parts.length - 1].trim());
|
|
4985
|
+
return !removedGroupNames.has(getRuleTarget(rule));
|
|
4813
4986
|
});
|
|
4814
4987
|
}
|
|
4815
4988
|
}
|
|
@@ -4904,6 +5077,8 @@ async function cmdDaemon(args) {
|
|
|
4904
5077
|
console.error(`${colors.red("\u914D\u7F6E\u9519\u8BEF:")} ${e.message}`);
|
|
4905
5078
|
process.exit(1);
|
|
4906
5079
|
}
|
|
5080
|
+
console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u5B89\u88C5\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1\uFF08LaunchDaemon\uFF09"));
|
|
5081
|
+
console.log(colors.gray("\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u9700\u8981 root\uFF0C\u4EE5\u89E3\u51B3\u5C40\u57DF\u7F51\u8BBF\u95EE\u53D7\u9650\u95EE\u9898"));
|
|
4907
5082
|
try {
|
|
4908
5083
|
enableDaemon();
|
|
4909
5084
|
} catch (e) {
|
|
@@ -4913,6 +5088,7 @@ async function cmdDaemon(args) {
|
|
|
4913
5088
|
console.log(`${colors.green("\u5DF2\u542F\u7528\u4FDD\u6D3B")} \xB7 ${sub.name} \xB7 ${formatProxySummary(configInfo)}`);
|
|
4914
5089
|
console.log(colors.gray("\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u81EA\u52A8\u91CD\u542F\uFF0C\u4EE3\u7406\u5C06\u5728\u540E\u53F0\u5E38\u9A7B"));
|
|
4915
5090
|
console.log("");
|
|
5091
|
+
await sleep(DAEMON_BOOT_WAIT_MS);
|
|
4916
5092
|
printDaemonStatus();
|
|
4917
5093
|
return;
|
|
4918
5094
|
}
|
|
@@ -4923,6 +5099,7 @@ async function cmdDaemon(args) {
|
|
|
4923
5099
|
printDaemonStatus();
|
|
4924
5100
|
return;
|
|
4925
5101
|
}
|
|
5102
|
+
console.log(colors.gray("\u5C06\u8BF7\u6C42\u7BA1\u7406\u5458\u6743\u9650\u4EE5\u79FB\u9664\u7CFB\u7EDF\u7EA7\u4FDD\u6D3B\u670D\u52A1"));
|
|
4926
5103
|
try {
|
|
4927
5104
|
disableDaemon();
|
|
4928
5105
|
} catch (e) {
|
|
@@ -5009,98 +5186,10 @@ function cmdDirectory(args) {
|
|
|
5009
5186
|
console.log("");
|
|
5010
5187
|
}
|
|
5011
5188
|
|
|
5012
|
-
// src/commands/help.ts
|
|
5013
|
-
function printShortHelp() {
|
|
5014
|
-
console.log(`
|
|
5015
|
-
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))} (mihomo help \u67E5\u770B\u5B8C\u6574\u5E2E\u52A9)
|
|
5016
|
-
`);
|
|
5017
|
-
console.log(
|
|
5018
|
-
`\u5E38\u7528\u547D\u4EE4:
|
|
5019
|
-
${colors.bold("start")} [tun|mixed] \u542F\u52A8/\u5207\u6362\u4EE3\u7406
|
|
5020
|
-
${colors.bold("sub")} [use|update] \u8BA2\u9605\u7BA1\u7406
|
|
5021
|
-
${colors.bold("ow")} [on|off] \u8986\u5199\u914D\u7F6E
|
|
5022
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI
|
|
5023
|
-
`
|
|
5024
|
-
);
|
|
5025
|
-
}
|
|
5026
|
-
function printHelp() {
|
|
5027
|
-
console.log(
|
|
5028
|
-
`
|
|
5029
|
-
${colors.cyan(colors.bold(`mihomo-cli v${VERSION}`))}
|
|
5030
|
-
|
|
5031
|
-
\u547D\u4EE4\u522B\u540D: mihomo, mhm, mh
|
|
5032
|
-
|
|
5033
|
-
\u7528\u6CD5:
|
|
5034
|
-
mihomo <\u547D\u4EE4> [\u9009\u9879]
|
|
5035
|
-
|
|
5036
|
-
${colors.cyan("\u63A7\u5236:")}
|
|
5037
|
-
${colors.bold("start")} [tun|mixed] [-s] [-u ms] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)
|
|
5038
|
-
[-r N] [-t ms] [-j N]
|
|
5039
|
-
${colors.bold("stop")} \u505C\u6B62\u4EE3\u7406
|
|
5040
|
-
${colors.bold("status")} \u67E5\u770B\u72B6\u6001
|
|
5041
|
-
|
|
5042
|
-
${colors.cyan("\u754C\u9762:")}
|
|
5043
|
-
${colors.bold("ui")} [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)
|
|
5044
|
-
${colors.bold("log")} [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09
|
|
5045
|
-
${colors.bold("logs")} [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09
|
|
5046
|
-
|
|
5047
|
-
${colors.cyan("\u8BA2\u9605:")}
|
|
5048
|
-
${colors.bold("subscription")} \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09
|
|
5049
|
-
${colors.bold("subscription")} use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605
|
|
5050
|
-
${colors.bold("subscription")} add <url> [name] \u6DFB\u52A0\u8BA2\u9605
|
|
5051
|
-
${colors.bold("subscription")} update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09
|
|
5052
|
-
${colors.bold("subscription")} remove <name> \u5220\u9664\u8BA2\u9605
|
|
5053
|
-
${colors.bold("subscription")} web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762
|
|
5054
|
-
${colors.bold("subscription")} test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027
|
|
5055
|
-
${colors.bold("subscription")} clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9
|
|
5056
|
-
${colors.bold("test")} [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027
|
|
5057
|
-
${colors.bold("clean")} [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F
|
|
5058
|
-
|
|
5059
|
-
${colors.cyan("\u914D\u7F6E:")}
|
|
5060
|
-
${colors.bold("overwrite")} \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09
|
|
5061
|
-
${colors.bold("overwrite")} on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E
|
|
5062
|
-
${colors.bold("directory")} \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09
|
|
5063
|
-
${colors.bold("directory")} open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|...
|
|
5064
|
-
|
|
5065
|
-
${colors.cyan("\u7CFB\u7EDF:")}
|
|
5066
|
-
${colors.bold("kernel")} [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09
|
|
5067
|
-
${colors.bold("daemon")} on|off \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F\uFF08\u4EC5 Mixed\uFF09
|
|
5068
|
-
${colors.bold("daemon")} status \u67E5\u770B\u4FDD\u6D3B\u72B6\u6001
|
|
5069
|
-
${colors.bold("update")} \u66F4\u65B0 mihomo-cli (npm install -g)
|
|
5070
|
-
${colors.bold("reset")} [\u76EE\u6807...] [--full] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8
|
|
5071
|
-
${colors.bold("help")}, -h \u663E\u793A\u5E2E\u52A9
|
|
5072
|
-
${colors.bold("version")}, -v \u663E\u793A\u7248\u672C
|
|
5073
|
-
|
|
5074
|
-
${colors.cyan("\u793A\u4F8B:")}
|
|
5075
|
-
mihomo start # \u542F\u52A8/\u91CD\u542F Mixed \u6A21\u5F0F
|
|
5076
|
-
mihomo start tun # \u5207\u6362\u5230 TUN \u900F\u660E\u4EE3\u7406\u6A21\u5F0F
|
|
5077
|
-
mihomo start -s # \u8DF3\u8FC7\u81EA\u52A8\u66F4\u65B0\u8BA2\u9605
|
|
5078
|
-
mihomo start -u 30000 # \u81EA\u52A8\u66F4\u65B0\u8D85\u65F6 30 \u79D2 (\u9ED8\u8BA4 10s)
|
|
5079
|
-
mihomo daemon on # \u5F00\u542F\u4FDD\u6D3B\uFF08\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F\uFF09
|
|
5080
|
-
mihomo sub add <url> # \u6DFB\u52A0\u8BA2\u9605 (sub \u662F subscription \u522B\u540D)
|
|
5081
|
-
mihomo ui # \u6253\u5F00 Web UI
|
|
5082
|
-
|
|
5083
|
-
${colors.cyan("\u6A21\u5F0F\u8BF4\u660E:")}
|
|
5084
|
-
mixed HTTP + SOCKS5 \u6DF7\u5408\u7AEF\u53E3 (\u9ED8\u8BA4)
|
|
5085
|
-
tun \u900F\u660E\u4EE3\u7406\uFF0C\u5168\u5C40\u81EA\u52A8\u8DEF\u7531\uFF0C\u9700\u8981 sudo
|
|
5086
|
-
|
|
5087
|
-
${colors.cyan("\u6570\u636E\u76EE\u5F55:")}
|
|
5088
|
-
\u73AF\u5883\u53D8\u91CF MIHOMO_CLI_DIR \u53EF\u81EA\u5B9A\u4E49\u4F4D\u7F6E
|
|
5089
|
-
\u9ED8\u8BA4: ${USER_DATA_DIR}
|
|
5090
|
-
`
|
|
5091
|
-
);
|
|
5092
|
-
}
|
|
5093
|
-
function printVersion() {
|
|
5094
|
-
const kv = getKernelVersion() || "\u672A\u5B89\u88C5";
|
|
5095
|
-
console.log(colors.cyan(colors.bold(`mihomo-cli v${VERSION}`)));
|
|
5096
|
-
console.log(`${colors.gray("\u5185\u6838: ")}${kv}`);
|
|
5097
|
-
console.log(`${colors.gray("\u6570\u636E\u76EE\u5F55: ")}${USER_DATA_DIR}`);
|
|
5098
|
-
}
|
|
5099
|
-
|
|
5100
5189
|
// src/kernel.ts
|
|
5101
5190
|
import { spawnSync as spawnSync5 } from "child_process";
|
|
5102
5191
|
import fs7 from "fs";
|
|
5103
|
-
import
|
|
5192
|
+
import path6 from "path";
|
|
5104
5193
|
|
|
5105
5194
|
// node_modules/compare-versions/lib/esm/utils.js
|
|
5106
5195
|
var semver = /^[v^~<>=]*?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;
|
|
@@ -5185,7 +5274,7 @@ function findMatchingAsset(assets, platform, arch) {
|
|
|
5185
5274
|
const nameWithoutGz = a.name.slice(0, -3);
|
|
5186
5275
|
const parts = nameWithoutGz.split("-");
|
|
5187
5276
|
const lastPart = parts[parts.length - 1];
|
|
5188
|
-
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go");
|
|
5277
|
+
return /^v?\d+\.\d+\.\d+/.test(lastPart) && !nameWithoutGz.includes("-go") && !nameWithoutGz.includes("-compatible");
|
|
5189
5278
|
});
|
|
5190
5279
|
return standardAsset || matchingAssets[0];
|
|
5191
5280
|
}
|
|
@@ -5228,7 +5317,7 @@ function findBinaryInDir(dir, maxDepth = 4) {
|
|
|
5228
5317
|
if (maxDepth <= 0) return null;
|
|
5229
5318
|
const files = fs7.readdirSync(dir);
|
|
5230
5319
|
for (const f of files) {
|
|
5231
|
-
const fullPath =
|
|
5320
|
+
const fullPath = path6.join(dir, f);
|
|
5232
5321
|
const stat = fs7.statSync(fullPath);
|
|
5233
5322
|
if (stat.isDirectory()) {
|
|
5234
5323
|
const found = findBinaryInDir(fullPath, maxDepth - 1);
|
|
@@ -5255,7 +5344,7 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5255
5344
|
\u5E73\u53F0: ${platform}, \u67B6\u6784: ${arch}${hint}`);
|
|
5256
5345
|
}
|
|
5257
5346
|
const downloadUrl = withMirror(asset.browser_download_url, mirror);
|
|
5258
|
-
const tempPath =
|
|
5347
|
+
const tempPath = path6.join(DIRS.kernel, asset.name);
|
|
5259
5348
|
const sizeMB = (asset.size / 1024 / 1024).toFixed(2);
|
|
5260
5349
|
if (mirror && progressCallback) {
|
|
5261
5350
|
progressCallback("\u63D0\u793A: \u7ECF\u7B2C\u4E09\u65B9\u955C\u50CF\u4E2D\u8F6C\u4E0B\u8F7D\uFF0C\u65E0\u6CD5\u9A8C\u8BC1\u6765\u6E90\u5B8C\u6574\u6027\uFF0C\u5EFA\u8BAE\u76F4\u8FDE\u6216\u81EA\u884C\u6821\u9A8C\u4EA7\u7269");
|
|
@@ -5289,8 +5378,8 @@ async function downloadKernel(progressCallback, mirror, releaseInfo) {
|
|
|
5289
5378
|
if (tarResult.error) throw tarResult.error;
|
|
5290
5379
|
if (tarResult.status !== 0) throw new Error(`tar \u9000\u51FA\u7801 ${tarResult.status}`);
|
|
5291
5380
|
} else if (tempPath.endsWith(".gz")) {
|
|
5292
|
-
const baseName =
|
|
5293
|
-
const outputPath =
|
|
5381
|
+
const baseName = path6.basename(tempPath, ".gz");
|
|
5382
|
+
const outputPath = path6.join(extractPath, baseName);
|
|
5294
5383
|
const gzipResult = spawnSync5("gzip", ["-dc", tempPath], { maxBuffer: 256 * 1024 * 1024, timeout: 6e4 });
|
|
5295
5384
|
if (gzipResult.error) throw gzipResult.error;
|
|
5296
5385
|
if (gzipResult.status !== 0) throw new Error(`gzip \u9000\u51FA\u7801 ${gzipResult.status}`);
|
|
@@ -5357,20 +5446,8 @@ async function cmdKernel(args) {
|
|
|
5357
5446
|
if (effectiveMirror) {
|
|
5358
5447
|
const mirrorDesc = mirrorInfo.type === "all" ? " (API\u548C\u4E0B\u8F7D\u5747\u4F7F\u7528\u955C\u50CF)" : " (\u4E0B\u8F7D\u65F6\u4F7F\u7528\u955C\u50CF)";
|
|
5359
5448
|
console.log(`\u955C\u50CF: ${effectiveMirror}${mirrorDesc}`);
|
|
5449
|
+
console.log("");
|
|
5360
5450
|
}
|
|
5361
|
-
console.log("\n\u63D0\u793A: \u5982\u679C\u4E0B\u8F7D\u901F\u5EA6\u8FC7\u6162\u6216\u76F4\u8FDE\u5931\u8D25\uFF0C\u53EF\u4F7F\u7528 --mirror \u53C2\u6570\u901A\u8FC7\u955C\u50CF\u4E0B\u8F7D");
|
|
5362
|
-
console.log("\n\u7528\u6CD5:");
|
|
5363
|
-
console.log(" mihomo kernel # \u76F4\u8FDE");
|
|
5364
|
-
console.log(" mihomo kernel --mirror # \u4E0B\u8F7D\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF (v6.gh-proxy.org)");
|
|
5365
|
-
console.log(" mihomo kernel --mirror hk.gh-proxy.org # \u4E0B\u8F7D\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5366
|
-
console.log(" mihomo kernel --mirror-all # API\u8BF7\u6C42\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u9ED8\u8BA4\u955C\u50CF");
|
|
5367
|
-
console.log(" mihomo kernel --mirror-all hk.gh-proxy.org # API\u548C\u4E0B\u8F7D\u90FD\u4F7F\u7528\u6307\u5B9A\u955C\u50CF");
|
|
5368
|
-
console.log("\n\u53EF\u7528\u955C\u50CF:");
|
|
5369
|
-
for (const m of AVAILABLE_MIRRORS) {
|
|
5370
|
-
const isCurrent = effectiveMirror && (effectiveMirror.includes(`//${m}/`) || effectiveMirror.includes(`//${m}:`) || effectiveMirror.endsWith(`//${m}`));
|
|
5371
|
-
console.log(` ${m}${isCurrent ? " (\u5F53\u524D)" : ""}`);
|
|
5372
|
-
}
|
|
5373
|
-
console.log("");
|
|
5374
5451
|
console.log("\u68C0\u67E5\u5185\u6838\u66F4\u65B0...");
|
|
5375
5452
|
try {
|
|
5376
5453
|
const apiMirror = mirrorInfo.type === "all" ? effectiveMirror : null;
|
|
@@ -5397,6 +5474,13 @@ async function cmdKernel(args) {
|
|
|
5397
5474
|
console.error(`\u6587\u6863: ${err.response.data.documentation_url}`);
|
|
5398
5475
|
}
|
|
5399
5476
|
}
|
|
5477
|
+
if (!effectiveMirror) {
|
|
5478
|
+
console.error("");
|
|
5479
|
+
console.error("\u63D0\u793A: \u76F4\u8FDE\u5931\u8D25\u6216\u4E0B\u8F7D\u8FC7\u6162\u65F6\u53EF\u4F7F\u7528\u955C\u50CF:");
|
|
5480
|
+
console.error(" mihomo kernel --mirror [\u955C\u50CF] # \u4E0B\u8F7D\u8D70\u955C\u50CF\uFF08\u9ED8\u8BA4 v6.gh-proxy.org\uFF09");
|
|
5481
|
+
console.error(" mihomo kernel --mirror-all [\u955C\u50CF] # API \u548C\u4E0B\u8F7D\u90FD\u8D70\u955C\u50CF");
|
|
5482
|
+
console.error(` \u53EF\u7528\u955C\u50CF: ${AVAILABLE_MIRRORS.join(", ")}`);
|
|
5483
|
+
}
|
|
5400
5484
|
process.exit(1);
|
|
5401
5485
|
}
|
|
5402
5486
|
}
|
|
@@ -5484,18 +5568,43 @@ function cmdLogs(args) {
|
|
|
5484
5568
|
}
|
|
5485
5569
|
|
|
5486
5570
|
// src/commands/overwrite.ts
|
|
5487
|
-
import
|
|
5571
|
+
import path8 from "path";
|
|
5572
|
+
|
|
5573
|
+
// src/runtime.ts
|
|
5574
|
+
function getRuntimeMode() {
|
|
5575
|
+
if (isDaemonEnabled()) return "mixed";
|
|
5576
|
+
return getConfigInfo()?.tun ? "tun" : "mixed";
|
|
5577
|
+
}
|
|
5578
|
+
function getRunningState() {
|
|
5579
|
+
if (isDaemonEnabled()) {
|
|
5580
|
+
const daemon = getDaemonStatus();
|
|
5581
|
+
return { running: isDaemonRunning(daemon), pid: daemon.pid, daemon: true };
|
|
5582
|
+
}
|
|
5583
|
+
const status = getStatus();
|
|
5584
|
+
return { running: status.running, pid: status.pid, daemon: false };
|
|
5585
|
+
}
|
|
5586
|
+
function isRestartNeededOnChange() {
|
|
5587
|
+
return isDaemonEnabled() || getStatus().running;
|
|
5588
|
+
}
|
|
5589
|
+
async function launchOrRestart(mode) {
|
|
5590
|
+
if (isDaemonEnabled()) {
|
|
5591
|
+
await restartDaemon();
|
|
5592
|
+
await sleep(DAEMON_BOOT_WAIT_MS);
|
|
5593
|
+
return getDaemonStatus().pid;
|
|
5594
|
+
}
|
|
5595
|
+
const result = await start(mode);
|
|
5596
|
+
return result.pid;
|
|
5597
|
+
}
|
|
5488
5598
|
|
|
5489
5599
|
// src/commands/status.ts
|
|
5490
5600
|
function printStatus() {
|
|
5491
5601
|
const status = getStatus();
|
|
5492
|
-
const
|
|
5602
|
+
const state = getRunningState();
|
|
5493
5603
|
const info = getConfigInfo();
|
|
5494
5604
|
const overwriteEnabled = isOverwriteEnabled();
|
|
5495
5605
|
const overwriteFiles = listOverwriteFile().files;
|
|
5496
5606
|
const activeSub = getActiveSubscription();
|
|
5497
|
-
const running
|
|
5498
|
-
const pid = daemon.enabled ? daemon.pid : status.pid;
|
|
5607
|
+
const { running, pid, daemon: daemonManaged } = state;
|
|
5499
5608
|
console.log("");
|
|
5500
5609
|
let modeLabel = "";
|
|
5501
5610
|
if (info && running) {
|
|
@@ -5506,7 +5615,7 @@ function printStatus() {
|
|
|
5506
5615
|
console.log(`${colors.gray("\u5185\u6838: ")}${status.kernelVersion || "\u672A\u5B89\u88C5"}`);
|
|
5507
5616
|
if (pid) {
|
|
5508
5617
|
console.log(`${colors.gray("PID: ")}${pid}`);
|
|
5509
|
-
if (!
|
|
5618
|
+
if (!daemonManaged && status.processInfo) {
|
|
5510
5619
|
console.log(`${colors.gray("\u5185\u5B58: ")}${status.processInfo.memory}`);
|
|
5511
5620
|
}
|
|
5512
5621
|
}
|
|
@@ -5537,7 +5646,7 @@ function printStatus() {
|
|
|
5537
5646
|
} else {
|
|
5538
5647
|
console.log(`${colors.gray("\u8986\u5199: ")}${colors.yellow("\u5DF2\u7981\u7528")}`);
|
|
5539
5648
|
}
|
|
5540
|
-
if (
|
|
5649
|
+
if (isDaemonEnabled()) {
|
|
5541
5650
|
console.log(`${colors.gray("\u4FDD\u6D3B: ")}${colors.green("\u5DF2\u542F\u7528")} ${colors.gray("(\u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F)")}`);
|
|
5542
5651
|
}
|
|
5543
5652
|
console.log("");
|
|
@@ -5557,7 +5666,7 @@ async function cmdStop() {
|
|
|
5557
5666
|
console.log("\u76F4\u63A5\u505C\u6B62\u4F1A\u88AB\u81EA\u52A8\u91CD\u65B0\u62C9\u8D77\uFF0C\u8BF7\u7528: mihomo daemon off");
|
|
5558
5667
|
return;
|
|
5559
5668
|
}
|
|
5560
|
-
const pids =
|
|
5669
|
+
const pids = getMihomoPids();
|
|
5561
5670
|
if (pids.length === 0) {
|
|
5562
5671
|
console.log(colors.yellow("\u4E0D\u5728\u8FD0\u884C"));
|
|
5563
5672
|
return;
|
|
@@ -5570,7 +5679,7 @@ async function cmdStop() {
|
|
|
5570
5679
|
// src/test-instance.ts
|
|
5571
5680
|
import { spawn as spawn2 } from "child_process";
|
|
5572
5681
|
import fs8 from "fs";
|
|
5573
|
-
import
|
|
5682
|
+
import path7 from "path";
|
|
5574
5683
|
|
|
5575
5684
|
// src/lifecycle.ts
|
|
5576
5685
|
var cleanupFns = /* @__PURE__ */ new Set();
|
|
@@ -5591,15 +5700,15 @@ function runCleanup() {
|
|
|
5591
5700
|
}
|
|
5592
5701
|
|
|
5593
5702
|
// src/test-instance.ts
|
|
5594
|
-
var TEST_DIR =
|
|
5703
|
+
var TEST_DIR = path7.join(USER_DATA_DIR, "test");
|
|
5595
5704
|
var TEST_DIRS = {
|
|
5596
|
-
data:
|
|
5597
|
-
runtime:
|
|
5705
|
+
data: path7.join(TEST_DIR, "data"),
|
|
5706
|
+
runtime: path7.join(TEST_DIR, "runtime")
|
|
5598
5707
|
};
|
|
5599
5708
|
var TEST_PATHS = {
|
|
5600
|
-
configFile:
|
|
5601
|
-
pidFile:
|
|
5602
|
-
logFile:
|
|
5709
|
+
configFile: path7.join(TEST_DIRS.runtime, "config.yaml"),
|
|
5710
|
+
pidFile: path7.join(TEST_DIRS.runtime, "pid"),
|
|
5711
|
+
logFile: path7.join(TEST_DIR, "test.log")
|
|
5603
5712
|
};
|
|
5604
5713
|
var TEST_API = `http://${TEST_CONFIG["external-controller"]}`;
|
|
5605
5714
|
function ensureTestDirs() {
|
|
@@ -5641,7 +5750,7 @@ function buildTestConfig(subName) {
|
|
|
5641
5750
|
],
|
|
5642
5751
|
rules: ["MATCH,PROXY"]
|
|
5643
5752
|
};
|
|
5644
|
-
const content =
|
|
5753
|
+
const content = dumpYaml(config);
|
|
5645
5754
|
fs8.writeFileSync(TEST_PATHS.configFile, content, { mode: 384 });
|
|
5646
5755
|
}
|
|
5647
5756
|
async function startTestInstance() {
|
|
@@ -5811,6 +5920,12 @@ function resolveTestTarget(args) {
|
|
|
5811
5920
|
}
|
|
5812
5921
|
return { target, timeout, concurrency };
|
|
5813
5922
|
}
|
|
5923
|
+
function printRestartHintIfRunning() {
|
|
5924
|
+
if (getRunningState().running) {
|
|
5925
|
+
console.log(colors.yellow("\u63D0\u793A: \u8FD0\u884C\u4E2D\u7684\u5B9E\u4F8B\u4ECD\u4F7F\u7528\u65E7\u914D\u7F6E\uFF0C\u6267\u884C mihomo start \u4F7F\u66F4\u65B0\u751F\u6548"));
|
|
5926
|
+
console.log("");
|
|
5927
|
+
}
|
|
5928
|
+
}
|
|
5814
5929
|
async function printSubscriptionList(options) {
|
|
5815
5930
|
if (options?.autoUpdate !== false) {
|
|
5816
5931
|
const updateResult = await autoUpdateStaleSubscription();
|
|
@@ -5934,6 +6049,7 @@ async function cmdSubscription(args) {
|
|
|
5934
6049
|
}
|
|
5935
6050
|
if (ok === 0) process.exit(1);
|
|
5936
6051
|
console.log("");
|
|
6052
|
+
printRestartHintIfRunning();
|
|
5937
6053
|
await printSubscriptionList();
|
|
5938
6054
|
return;
|
|
5939
6055
|
}
|
|
@@ -5947,6 +6063,7 @@ async function cmdSubscription(args) {
|
|
|
5947
6063
|
}
|
|
5948
6064
|
console.log(`\u5DF2\u66F4\u65B0 (${formatProxySummary(result)})`);
|
|
5949
6065
|
console.log("");
|
|
6066
|
+
printRestartHintIfRunning();
|
|
5950
6067
|
await printSubscriptionList();
|
|
5951
6068
|
return;
|
|
5952
6069
|
}
|
|
@@ -5971,9 +6088,8 @@ async function cmdSubscription(args) {
|
|
|
5971
6088
|
await printSubscriptionList();
|
|
5972
6089
|
return;
|
|
5973
6090
|
}
|
|
5974
|
-
const
|
|
5975
|
-
const
|
|
5976
|
-
const currentMode = isDaemonEnabled() ? "mixed" : configInfo?.tun ? "tun" : "mixed";
|
|
6091
|
+
const currentMode = getRuntimeMode();
|
|
6092
|
+
const restartNeeded = isRestartNeededOnChange();
|
|
5977
6093
|
const success = setDefaultSubscription(target.name);
|
|
5978
6094
|
if (success) {
|
|
5979
6095
|
console.log(`\u5DF2\u5207\u6362\u5230 "${target.name}"`);
|
|
@@ -5981,7 +6097,7 @@ async function cmdSubscription(args) {
|
|
|
5981
6097
|
console.error(`\u9519\u8BEF: \u672A\u627E\u5230\u8BA2\u9605 "${name}"`);
|
|
5982
6098
|
process.exit(1);
|
|
5983
6099
|
}
|
|
5984
|
-
if (
|
|
6100
|
+
if (restartNeeded) {
|
|
5985
6101
|
console.log("");
|
|
5986
6102
|
await cmdStart(["start", currentMode]);
|
|
5987
6103
|
return;
|
|
@@ -6075,8 +6191,7 @@ async function cmdSubscription(args) {
|
|
|
6075
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"));
|
|
6076
6192
|
} else if (result.removedProxies > 0) {
|
|
6077
6193
|
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6078
|
-
|
|
6079
|
-
if (status.running) {
|
|
6194
|
+
if (getRunningState().running) {
|
|
6080
6195
|
console.log("");
|
|
6081
6196
|
console.log("\u63D0\u793A: \u9700\u8981\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548 (mihomo start)");
|
|
6082
6197
|
}
|
|
@@ -6107,7 +6222,6 @@ async function cmdSubscription(args) {
|
|
|
6107
6222
|
}
|
|
6108
6223
|
|
|
6109
6224
|
// src/commands/start.ts
|
|
6110
|
-
var DAEMON_RESTART_WAIT_MS = 500;
|
|
6111
6225
|
async function cmdStart(args) {
|
|
6112
6226
|
if (!hasKernel()) {
|
|
6113
6227
|
console.error('\u9519\u8BEF: \u672A\u627E\u5230\u5185\u6838\uFF0C\u8BF7\u8FD0\u884C "mihomo kernel"');
|
|
@@ -6133,15 +6247,6 @@ async function cmdStart(args) {
|
|
|
6133
6247
|
if (!skipUpdate) {
|
|
6134
6248
|
await autoUpdateStaleSubscription({ timeout: updateTimeout });
|
|
6135
6249
|
}
|
|
6136
|
-
async function launchOrRestart() {
|
|
6137
|
-
if (daemonEnabled) {
|
|
6138
|
-
restartDaemon();
|
|
6139
|
-
await sleep(DAEMON_RESTART_WAIT_MS);
|
|
6140
|
-
return getDaemonStatus().pid;
|
|
6141
|
-
}
|
|
6142
|
-
const result = await start(targetMode);
|
|
6143
|
-
return result.pid;
|
|
6144
|
-
}
|
|
6145
6250
|
if (!daemonEnabled) {
|
|
6146
6251
|
const status = getStatus();
|
|
6147
6252
|
const hasProcess = status.running || status.allProcesses.length > 0;
|
|
@@ -6165,7 +6270,7 @@ async function cmdStart(args) {
|
|
|
6165
6270
|
const modeLabel = targetMode === "tun" ? "TUN" : "Mixed";
|
|
6166
6271
|
console.log([colors.cyan(modeLabel), sub.name, formatProxySummary(configInfo)].join(" \xB7 "));
|
|
6167
6272
|
try {
|
|
6168
|
-
const pid = await launchOrRestart();
|
|
6273
|
+
const pid = await launchOrRestart(targetMode);
|
|
6169
6274
|
const label = daemonEnabled ? "\u5DF2\u542F\u52A8 (\u4FDD\u6D3B)" : "\u5DF2\u542F\u52A8";
|
|
6170
6275
|
console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""}`);
|
|
6171
6276
|
} catch (e) {
|
|
@@ -6202,7 +6307,7 @@ async function cmdStart(args) {
|
|
|
6202
6307
|
if (!daemonEnabled) handleStopResult(stop());
|
|
6203
6308
|
try {
|
|
6204
6309
|
configInfo = prepareConfigForStart(targetMode, sub.name);
|
|
6205
|
-
const pid = await launchOrRestart();
|
|
6310
|
+
const pid = await launchOrRestart(targetMode);
|
|
6206
6311
|
console.log(`${colors.green("\u5DF2\u91CD\u542F")}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
|
|
6207
6312
|
} catch (e) {
|
|
6208
6313
|
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
@@ -6223,8 +6328,8 @@ function printOverwriteList() {
|
|
|
6223
6328
|
if (info.files.length === 0) {
|
|
6224
6329
|
console.log("\u6682\u65E0\u8986\u5199\u6587\u4EF6");
|
|
6225
6330
|
console.log("");
|
|
6226
|
-
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${
|
|
6227
|
-
console.log(` \u6216 ${
|
|
6331
|
+
console.log(`\u7528\u6CD5\u793A\u4F8B: \u521B\u5EFA\u6587\u4EF6 ${path8.join(info.dir, "overwrite.yaml")}`);
|
|
6332
|
+
console.log(` \u6216 ${path8.join(info.dir, "overwrite.dns.yaml")}`);
|
|
6228
6333
|
console.log("");
|
|
6229
6334
|
} else {
|
|
6230
6335
|
console.log(`${colors.cyan("\u8986\u5199\u6587\u4EF6")} (${info.files.length} \u4E2A\uFF0C\u6309\u987A\u5E8F\u52A0\u8F7D):`);
|
|
@@ -6244,9 +6349,8 @@ function printOverwriteList() {
|
|
|
6244
6349
|
}
|
|
6245
6350
|
async function cmdOverwrite(args) {
|
|
6246
6351
|
const action = args?.[1];
|
|
6247
|
-
const
|
|
6248
|
-
const
|
|
6249
|
-
const currentMode = isDaemonEnabled() ? "mixed" : configInfo?.tun ? "tun" : "mixed";
|
|
6352
|
+
const currentMode = getRuntimeMode();
|
|
6353
|
+
const restartNeeded = isRestartNeededOnChange();
|
|
6250
6354
|
if (action === "on" || action === "enable") {
|
|
6251
6355
|
if (isOverwriteEnabled()) {
|
|
6252
6356
|
console.log("\u8986\u5199\u914D\u7F6E\u5DF2\u662F\u542F\u7528\u72B6\u6001");
|
|
@@ -6256,7 +6360,7 @@ async function cmdOverwrite(args) {
|
|
|
6256
6360
|
}
|
|
6257
6361
|
setOverwriteEnabled(true);
|
|
6258
6362
|
console.log("\u5DF2\u542F\u7528\u8986\u5199\u914D\u7F6E");
|
|
6259
|
-
if (
|
|
6363
|
+
if (restartNeeded) {
|
|
6260
6364
|
console.log("");
|
|
6261
6365
|
await cmdStart(["start", currentMode]);
|
|
6262
6366
|
return;
|
|
@@ -6274,7 +6378,7 @@ async function cmdOverwrite(args) {
|
|
|
6274
6378
|
}
|
|
6275
6379
|
setOverwriteEnabled(false);
|
|
6276
6380
|
console.log("\u5DF2\u7981\u7528\u8986\u5199\u914D\u7F6E");
|
|
6277
|
-
if (
|
|
6381
|
+
if (restartNeeded) {
|
|
6278
6382
|
console.log("");
|
|
6279
6383
|
await cmdStart(["start", currentMode]);
|
|
6280
6384
|
return;
|
|
@@ -6344,7 +6448,7 @@ var RESET_TARGETS = [
|
|
|
6344
6448
|
paths: () => {
|
|
6345
6449
|
const dir = USER_DATA_DIR;
|
|
6346
6450
|
if (!fs9.existsSync(dir)) return [];
|
|
6347
|
-
return fs9.readdirSync(dir).filter(
|
|
6451
|
+
return fs9.readdirSync(dir).filter(isOverwriteFilename).map((f) => `${dir}/${f}`);
|
|
6348
6452
|
},
|
|
6349
6453
|
needsStop: false
|
|
6350
6454
|
},
|
|
@@ -6352,8 +6456,9 @@ var RESET_TARGETS = [
|
|
|
6352
6456
|
id: "daemon",
|
|
6353
6457
|
aliases: ["daemon"],
|
|
6354
6458
|
label: "\u4FDD\u6D3B",
|
|
6355
|
-
//
|
|
6356
|
-
//
|
|
6459
|
+
// 卸载由确认后的 disablesDaemon 段统一处理(需 sudo,受取消保护);
|
|
6460
|
+
// 此处 paths 返回空(plist 在系统目录,用户态删不掉,且不应提前删破坏卸载),
|
|
6461
|
+
// onAfter 因幂等守卫(plist 已删)成为 no-op,仅作单独 reset 未走前段时的兜底。
|
|
6357
6462
|
paths: () => [],
|
|
6358
6463
|
needsStop: false,
|
|
6359
6464
|
onAfter: () => disableDaemon(),
|
|
@@ -6421,8 +6526,9 @@ async function cmdReset(args) {
|
|
|
6421
6526
|
const needsStop = targets.some((t) => t.needsStop);
|
|
6422
6527
|
const warnRunning = targets.some((t) => t.warnIfRunning);
|
|
6423
6528
|
const kernelTargeted = targets.some((t) => t.id === "kernel");
|
|
6424
|
-
const
|
|
6425
|
-
const
|
|
6529
|
+
const daemonTargeted = targets.some((t) => t.id === "daemon");
|
|
6530
|
+
const disablesDaemon = needsStop || kernelTargeted || daemonTargeted;
|
|
6531
|
+
const pids = needsStop || warnRunning ? getMihomoPids() : [];
|
|
6426
6532
|
if (warnRunning && pids.length > 0) {
|
|
6427
6533
|
console.log(colors.yellow(`\u8B66\u544A: mihomo \u6B63\u5728\u8FD0\u884C (PID ${pids.join(", ")})\uFF0C\u5220\u9664\u5185\u6838\u540E\u5C06\u65E0\u6CD5\u91CD\u65B0\u542F\u52A8`));
|
|
6428
6534
|
}
|
|
@@ -6435,13 +6541,18 @@ async function cmdReset(args) {
|
|
|
6435
6541
|
return;
|
|
6436
6542
|
}
|
|
6437
6543
|
if (disablesDaemon && isDaemonEnabled()) {
|
|
6438
|
-
|
|
6544
|
+
try {
|
|
6545
|
+
disableDaemon();
|
|
6546
|
+
} catch (e) {
|
|
6547
|
+
console.error(`${colors.red("\u4FDD\u6D3B\u5173\u95ED\u5DF2\u53D6\u6D88\uFF0C\u91CD\u7F6E\u4E2D\u6B62:")} ${e.message.split("\n")[0]}`);
|
|
6548
|
+
return;
|
|
6549
|
+
}
|
|
6439
6550
|
}
|
|
6440
|
-
if (needsStop &&
|
|
6551
|
+
if (needsStop && getMihomoPids().length > 0) {
|
|
6441
6552
|
console.log("\u505C\u6B62\u8FDB\u7A0B...");
|
|
6442
6553
|
cleanupAll();
|
|
6443
6554
|
for (let i = 0; i < PROCESS_WAIT_ATTEMPTS; i++) {
|
|
6444
|
-
if (
|
|
6555
|
+
if (getMihomoPids().length === 0) break;
|
|
6445
6556
|
await new Promise((r) => setTimeout(r, PROCESS_WAIT_INTERVAL));
|
|
6446
6557
|
}
|
|
6447
6558
|
}
|
|
@@ -6466,16 +6577,10 @@ async function cmdReset(args) {
|
|
|
6466
6577
|
|
|
6467
6578
|
// src/commands/test.ts
|
|
6468
6579
|
function requireRunning() {
|
|
6469
|
-
|
|
6470
|
-
|
|
6471
|
-
|
|
6472
|
-
|
|
6473
|
-
}
|
|
6474
|
-
return;
|
|
6475
|
-
}
|
|
6476
|
-
const status = getStatus();
|
|
6477
|
-
if (!status.running) {
|
|
6478
|
-
console.error("\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (mihomo start)");
|
|
6580
|
+
const state = getRunningState();
|
|
6581
|
+
if (!state.running) {
|
|
6582
|
+
const hint = state.daemon ? "mihomo daemon on" : "mihomo start";
|
|
6583
|
+
console.error(`\u9519\u8BEF: mihomo \u672A\u8FD0\u884C\uFF0C\u8BF7\u5148\u542F\u52A8 (${hint})`);
|
|
6479
6584
|
process.exit(1);
|
|
6480
6585
|
}
|
|
6481
6586
|
}
|
|
@@ -6532,21 +6637,17 @@ async function cmdClean(args) {
|
|
|
6532
6637
|
console.log(`${colors.green("\u5DF2\u6E05\u7406")}: ${formatCleanSummary(result)}`);
|
|
6533
6638
|
console.log("");
|
|
6534
6639
|
console.log("\u91CD\u542F mihomo \u4F7F\u66F4\u6539\u751F\u6548...");
|
|
6535
|
-
|
|
6536
|
-
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
}
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
|
|
6546
|
-
handleStopResult(stop());
|
|
6547
|
-
const configInfo = prepareConfigForStart(currentMode, activeSub.name);
|
|
6548
|
-
const startResult = await start(currentMode);
|
|
6549
|
-
console.log(`${colors.green("\u5DF2\u91CD\u542F")} (PID ${startResult.pid}) \xB7 ${formatProxySummary(configInfo)}`);
|
|
6640
|
+
const mode = getRuntimeMode();
|
|
6641
|
+
const daemonManaged = isDaemonEnabled();
|
|
6642
|
+
try {
|
|
6643
|
+
if (!daemonManaged) handleStopResult(stop());
|
|
6644
|
+
const configInfo = prepareConfigForStart(mode, activeSub.name);
|
|
6645
|
+
const pid = await launchOrRestart(mode);
|
|
6646
|
+
const label = daemonManaged ? "\u5DF2\u91CD\u542F (\u4FDD\u6D3B)" : "\u5DF2\u91CD\u542F";
|
|
6647
|
+
console.log(`${colors.green(label)}${pid ? ` (PID ${pid})` : ""} \xB7 ${formatProxySummary(configInfo)}`);
|
|
6648
|
+
} catch (e) {
|
|
6649
|
+
console.error(`${colors.red("\u91CD\u542F\u5931\u8D25:")} ${e.message.split("\n")[0]}`);
|
|
6650
|
+
process.exit(1);
|
|
6550
6651
|
}
|
|
6551
6652
|
}
|
|
6552
6653
|
}
|
|
@@ -6607,6 +6708,199 @@ async function cmdUpdate() {
|
|
|
6607
6708
|
}
|
|
6608
6709
|
}
|
|
6609
6710
|
|
|
6711
|
+
// src/commands/registry.ts
|
|
6712
|
+
var COMMANDS = [
|
|
6713
|
+
// === 控制 ===
|
|
6714
|
+
{
|
|
6715
|
+
name: "start",
|
|
6716
|
+
aliases: ["up"],
|
|
6717
|
+
handler: cmdStart,
|
|
6718
|
+
group: "control",
|
|
6719
|
+
usage: ["start [tun|mixed] [-s] [-u ms] \u542F\u52A8/\u5207\u6362\u4EE3\u7406 (\u9ED8\u8BA4 mixed)", " [-r N] [-t ms] [-j N]"]
|
|
6720
|
+
},
|
|
6721
|
+
{
|
|
6722
|
+
name: "tun",
|
|
6723
|
+
aliases: [],
|
|
6724
|
+
handler: cmdStart,
|
|
6725
|
+
rewrite: (args) => ["start", "tun", ...args.slice(1)],
|
|
6726
|
+
group: "control",
|
|
6727
|
+
usage: []
|
|
6728
|
+
},
|
|
6729
|
+
{
|
|
6730
|
+
name: "stop",
|
|
6731
|
+
aliases: ["down"],
|
|
6732
|
+
handler: cmdStop,
|
|
6733
|
+
group: "control",
|
|
6734
|
+
usage: ["stop \u505C\u6B62\u4EE3\u7406"]
|
|
6735
|
+
},
|
|
6736
|
+
{
|
|
6737
|
+
name: "status",
|
|
6738
|
+
aliases: [],
|
|
6739
|
+
handler: printStatus,
|
|
6740
|
+
group: "control",
|
|
6741
|
+
usage: ["status \u67E5\u770B\u72B6\u6001"]
|
|
6742
|
+
},
|
|
6743
|
+
// === 界面 ===
|
|
6744
|
+
{
|
|
6745
|
+
name: "ui",
|
|
6746
|
+
aliases: [],
|
|
6747
|
+
handler: cmdUI,
|
|
6748
|
+
group: "interface",
|
|
6749
|
+
usage: ["ui [zash|dash|yacd] \u6253\u5F00 Web UI (\u9ED8\u8BA4 zash)"]
|
|
6750
|
+
},
|
|
6751
|
+
{
|
|
6752
|
+
name: "log",
|
|
6753
|
+
aliases: [],
|
|
6754
|
+
handler: cmdLog,
|
|
6755
|
+
group: "interface",
|
|
6756
|
+
usage: ["log [-o] \u5B9E\u65F6\u65E5\u5FD7\uFF08-o \u6253\u5F00\u6587\u4EF6\uFF09"]
|
|
6757
|
+
},
|
|
6758
|
+
{
|
|
6759
|
+
name: "logs",
|
|
6760
|
+
aliases: [],
|
|
6761
|
+
handler: cmdLogs,
|
|
6762
|
+
group: "interface",
|
|
6763
|
+
usage: ["logs [\u7F16\u53F7] [-n N] [-o] \u65E5\u5FD7\u5217\u8868\uFF080=\u5F53\u524D\uFF0C1+=\u5F52\u6863\uFF09"]
|
|
6764
|
+
},
|
|
6765
|
+
// === 订阅 ===
|
|
6766
|
+
{
|
|
6767
|
+
name: "subscription",
|
|
6768
|
+
aliases: ["sub", "subscriptions"],
|
|
6769
|
+
handler: cmdSubscription,
|
|
6770
|
+
group: "subscription",
|
|
6771
|
+
usage: [
|
|
6772
|
+
"subscription \u5217\u51FA\u6240\u6709\u8BA2\u9605\uFF08\u522B\u540D sub\uFF09",
|
|
6773
|
+
"subscription use <name> \u5207\u6362\u5F53\u524D\u8BA2\u9605",
|
|
6774
|
+
"subscription add <url> [name] \u6DFB\u52A0\u8BA2\u9605",
|
|
6775
|
+
"subscription update [name] \u66F4\u65B0\u8BA2\u9605\uFF08\u65E0\u53C2\u66F4\u65B0\u6240\u6709\uFF09",
|
|
6776
|
+
"subscription remove <name> \u5220\u9664\u8BA2\u9605",
|
|
6777
|
+
"subscription web [name] \u6253\u5F00\u8BA2\u9605\u9875\u9762",
|
|
6778
|
+
"subscription test [name] \u6D4B\u8BD5\u8282\u70B9\u8FDE\u901A\u6027",
|
|
6779
|
+
"subscription clean [name] \u6D4B\u901F\u5E76\u6E05\u7406\u5931\u8D25\u8282\u70B9"
|
|
6780
|
+
]
|
|
6781
|
+
},
|
|
6782
|
+
{
|
|
6783
|
+
name: "use",
|
|
6784
|
+
aliases: [],
|
|
6785
|
+
handler: cmdSubscription,
|
|
6786
|
+
rewrite: (args) => ["sub", "use", ...args.slice(1)],
|
|
6787
|
+
group: "subscription",
|
|
6788
|
+
usage: []
|
|
6789
|
+
},
|
|
6790
|
+
{
|
|
6791
|
+
name: "test",
|
|
6792
|
+
aliases: [],
|
|
6793
|
+
handler: cmdTest,
|
|
6794
|
+
group: "subscription",
|
|
6795
|
+
usage: ["test [-t ms] [-j N] \u5FEB\u901F\u6D4B\u8BD5\u5F53\u524D\u8282\u70B9\u8FDE\u901A\u6027"]
|
|
6796
|
+
},
|
|
6797
|
+
{
|
|
6798
|
+
name: "clean",
|
|
6799
|
+
aliases: [],
|
|
6800
|
+
handler: cmdClean,
|
|
6801
|
+
group: "subscription",
|
|
6802
|
+
usage: ["clean [-t ms] [-j N] [-r N] \u6E05\u7406\u5931\u8D25\u8282\u70B9\u5E76\u81EA\u52A8\u91CD\u542F"]
|
|
6803
|
+
},
|
|
6804
|
+
// === 配置 ===
|
|
6805
|
+
{
|
|
6806
|
+
name: "overwrite",
|
|
6807
|
+
aliases: ["ow"],
|
|
6808
|
+
handler: cmdOverwrite,
|
|
6809
|
+
group: "config",
|
|
6810
|
+
usage: ["overwrite \u67E5\u770B\u8986\u5199\u72B6\u6001\uFF08\u522B\u540D ow\uFF09", "overwrite on|off \u542F\u7528/\u7981\u7528\u8986\u5199\u914D\u7F6E"]
|
|
6811
|
+
},
|
|
6812
|
+
{
|
|
6813
|
+
name: "on",
|
|
6814
|
+
aliases: [],
|
|
6815
|
+
handler: cmdOverwrite,
|
|
6816
|
+
rewrite: () => ["ow", "on"],
|
|
6817
|
+
group: "config",
|
|
6818
|
+
usage: []
|
|
6819
|
+
},
|
|
6820
|
+
{
|
|
6821
|
+
name: "off",
|
|
6822
|
+
aliases: [],
|
|
6823
|
+
handler: cmdOverwrite,
|
|
6824
|
+
rewrite: () => ["ow", "off"],
|
|
6825
|
+
group: "config",
|
|
6826
|
+
usage: []
|
|
6827
|
+
},
|
|
6828
|
+
{
|
|
6829
|
+
name: "directory",
|
|
6830
|
+
aliases: ["dir", "dirs", "directories"],
|
|
6831
|
+
handler: cmdDirectory,
|
|
6832
|
+
group: "config",
|
|
6833
|
+
usage: ["directory \u663E\u793A\u6570\u636E\u76EE\u5F55\u4F4D\u7F6E\uFF08\u522B\u540D dir\uFF09", "directory open [target] \u6253\u5F00\u76EE\u5F55: root|subs|logs|runtime|..."]
|
|
6834
|
+
},
|
|
6835
|
+
{
|
|
6836
|
+
name: "open",
|
|
6837
|
+
aliases: [],
|
|
6838
|
+
handler: cmdDirectory,
|
|
6839
|
+
rewrite: (args) => ["dir", "open", ...args.slice(1)],
|
|
6840
|
+
group: "config",
|
|
6841
|
+
usage: []
|
|
6842
|
+
},
|
|
6843
|
+
// === 系统 ===
|
|
6844
|
+
{
|
|
6845
|
+
name: "kernel",
|
|
6846
|
+
aliases: [],
|
|
6847
|
+
handler: cmdKernel,
|
|
6848
|
+
group: "system",
|
|
6849
|
+
usage: ["kernel [--mirror [\u955C\u50CF]] \u66F4\u65B0\u5185\u6838\uFF08\u9ED8\u8BA4\u76F4\u8FDE\uFF0C--mirror \u4F7F\u7528 v6\uFF09"]
|
|
6850
|
+
},
|
|
6851
|
+
{
|
|
6852
|
+
name: "daemon",
|
|
6853
|
+
aliases: [],
|
|
6854
|
+
handler: cmdDaemon,
|
|
6855
|
+
group: "system",
|
|
6856
|
+
usage: ["daemon on|off \u5F00\u673A\u81EA\u542F + \u5D29\u6E83\u91CD\u542F\uFF08\u4EC5 Mixed\uFF0C\u9700\u7BA1\u7406\u5458\u5BC6\u7801\uFF09", "daemon status \u67E5\u770B\u4FDD\u6D3B\u72B6\u6001"]
|
|
6857
|
+
},
|
|
6858
|
+
{
|
|
6859
|
+
name: "update",
|
|
6860
|
+
aliases: ["upd", "upgrade"],
|
|
6861
|
+
handler: cmdUpdate,
|
|
6862
|
+
group: "system",
|
|
6863
|
+
usage: ["update \u66F4\u65B0 mihomo-cli (npm install -g)"]
|
|
6864
|
+
},
|
|
6865
|
+
{
|
|
6866
|
+
name: "reset",
|
|
6867
|
+
aliases: [],
|
|
6868
|
+
handler: cmdReset,
|
|
6869
|
+
group: "system",
|
|
6870
|
+
usage: ["reset [\u76EE\u6807...] [--full] \u91CD\u7F6E: \u7559\u7A7A\u4FDD\u7559\u8BBE\u7F6E/\u5185\u6838/\u8986\u5199, \u6307\u5B9A\u76EE\u6807\u5220\u5BF9\u5E94\u9879, --full \u5220\u5168\u90E8"]
|
|
6871
|
+
},
|
|
6872
|
+
// === meta(不在分组清单展示,help 末尾单列) ===
|
|
6873
|
+
{
|
|
6874
|
+
name: "help",
|
|
6875
|
+
aliases: ["-h", "--help"],
|
|
6876
|
+
handler: () => printHelp(COMMANDS),
|
|
6877
|
+
group: "meta",
|
|
6878
|
+
usage: ["help, -h \u663E\u793A\u5E2E\u52A9"]
|
|
6879
|
+
},
|
|
6880
|
+
{
|
|
6881
|
+
name: "version",
|
|
6882
|
+
aliases: ["-v", "--version"],
|
|
6883
|
+
handler: printVersion,
|
|
6884
|
+
group: "meta",
|
|
6885
|
+
usage: ["version, -v \u663E\u793A\u7248\u672C"]
|
|
6886
|
+
}
|
|
6887
|
+
];
|
|
6888
|
+
var COMMAND_INDEX = (() => {
|
|
6889
|
+
const index = /* @__PURE__ */ new Map();
|
|
6890
|
+
for (const cmd of COMMANDS) {
|
|
6891
|
+
for (const token of [cmd.name, ...cmd.aliases]) {
|
|
6892
|
+
if (index.has(token)) {
|
|
6893
|
+
throw new Error(`\u547D\u4EE4\u6CE8\u518C\u8868\u5B58\u5728\u91CD\u590D token: "${token}"\uFF08${index.get(token)?.name} \u4E0E ${cmd.name}\uFF09`);
|
|
6894
|
+
}
|
|
6895
|
+
index.set(token, cmd);
|
|
6896
|
+
}
|
|
6897
|
+
}
|
|
6898
|
+
return index;
|
|
6899
|
+
})();
|
|
6900
|
+
function findCommand(token) {
|
|
6901
|
+
return COMMAND_INDEX.get(token);
|
|
6902
|
+
}
|
|
6903
|
+
|
|
6610
6904
|
// src/index.ts
|
|
6611
6905
|
process.on("SIGINT", () => {
|
|
6612
6906
|
console.log("\n\u6B63\u5728\u9000\u51FA...");
|
|
@@ -6623,12 +6917,14 @@ process.on("uncaughtException", (e) => {
|
|
|
6623
6917
|
if (e.stack) {
|
|
6624
6918
|
console.error(e.stack.split("\n").slice(1).join("\n"));
|
|
6625
6919
|
}
|
|
6920
|
+
runCleanup();
|
|
6626
6921
|
process.exit(1);
|
|
6627
6922
|
});
|
|
6628
6923
|
process.on("unhandledRejection", (reason) => {
|
|
6629
6924
|
const msg = reason instanceof Error ? reason.message : String(reason);
|
|
6630
6925
|
console.error(`
|
|
6631
6926
|
\u672A\u5904\u7406\u7684 Promise \u62D2\u7EDD: ${msg}`);
|
|
6927
|
+
runCleanup();
|
|
6632
6928
|
process.exit(1);
|
|
6633
6929
|
});
|
|
6634
6930
|
function clearProxyEnv() {
|
|
@@ -6648,94 +6944,18 @@ async function main() {
|
|
|
6648
6944
|
printShortHelp();
|
|
6649
6945
|
return;
|
|
6650
6946
|
}
|
|
6651
|
-
const
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6656
|
-
|
|
6657
|
-
printVersion();
|
|
6658
|
-
return;
|
|
6659
|
-
}
|
|
6660
|
-
switch (cmd) {
|
|
6661
|
-
case "up":
|
|
6662
|
-
case "start":
|
|
6663
|
-
await cmdStart(args);
|
|
6664
|
-
break;
|
|
6665
|
-
case "tun":
|
|
6666
|
-
await cmdStart(["start", "tun", ...args.slice(1)]);
|
|
6667
|
-
break;
|
|
6668
|
-
case "down":
|
|
6669
|
-
case "stop":
|
|
6670
|
-
await cmdStop();
|
|
6671
|
-
break;
|
|
6672
|
-
case "status":
|
|
6673
|
-
printStatus();
|
|
6674
|
-
break;
|
|
6675
|
-
case "log":
|
|
6676
|
-
cmdLog(args);
|
|
6677
|
-
break;
|
|
6678
|
-
case "logs":
|
|
6679
|
-
cmdLogs(args);
|
|
6680
|
-
break;
|
|
6681
|
-
case "open":
|
|
6682
|
-
cmdDirectory(["dir", "open", ...args.slice(1)]);
|
|
6683
|
-
break;
|
|
6684
|
-
case "ui":
|
|
6685
|
-
cmdUI(args);
|
|
6686
|
-
break;
|
|
6687
|
-
case "kernel":
|
|
6688
|
-
await cmdKernel(args);
|
|
6689
|
-
break;
|
|
6690
|
-
case "upd":
|
|
6691
|
-
case "update":
|
|
6692
|
-
case "upgrade":
|
|
6693
|
-
await cmdUpdate();
|
|
6694
|
-
break;
|
|
6695
|
-
case "use":
|
|
6696
|
-
await cmdSubscription(["sub", "use", ...args.slice(1)]);
|
|
6697
|
-
break;
|
|
6698
|
-
case "sub":
|
|
6699
|
-
case "subscription":
|
|
6700
|
-
case "subscriptions":
|
|
6701
|
-
await cmdSubscription(args);
|
|
6702
|
-
break;
|
|
6703
|
-
case "dir":
|
|
6704
|
-
case "dirs":
|
|
6705
|
-
case "directory":
|
|
6706
|
-
case "directories":
|
|
6707
|
-
cmdDirectory(args);
|
|
6708
|
-
break;
|
|
6709
|
-
case "reset":
|
|
6710
|
-
await cmdReset(args);
|
|
6711
|
-
break;
|
|
6712
|
-
case "daemon":
|
|
6713
|
-
await cmdDaemon(args);
|
|
6714
|
-
break;
|
|
6715
|
-
case "on":
|
|
6716
|
-
await cmdOverwrite(["ow", "on"]);
|
|
6717
|
-
break;
|
|
6718
|
-
case "off":
|
|
6719
|
-
await cmdOverwrite(["ow", "off"]);
|
|
6720
|
-
break;
|
|
6721
|
-
case "ow":
|
|
6722
|
-
case "overwrite":
|
|
6723
|
-
await cmdOverwrite(args);
|
|
6724
|
-
break;
|
|
6725
|
-
case "test":
|
|
6726
|
-
await cmdTest(args);
|
|
6727
|
-
break;
|
|
6728
|
-
case "clean":
|
|
6729
|
-
await cmdClean(args);
|
|
6730
|
-
break;
|
|
6731
|
-
default:
|
|
6732
|
-
console.error(`\u672A\u77E5\u547D\u4EE4: ${cmd}`);
|
|
6733
|
-
console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
|
|
6734
|
-
process.exit(1);
|
|
6947
|
+
const token = args[0].toLowerCase();
|
|
6948
|
+
const command = findCommand(token);
|
|
6949
|
+
if (!command) {
|
|
6950
|
+
console.error(`\u672A\u77E5\u547D\u4EE4: ${token}`);
|
|
6951
|
+
console.error('\u4F7F\u7528 "mihomo help" \u67E5\u770B\u5E2E\u52A9');
|
|
6952
|
+
process.exit(1);
|
|
6735
6953
|
}
|
|
6954
|
+
await command.handler(command.rewrite ? command.rewrite(args) : args);
|
|
6736
6955
|
}
|
|
6737
6956
|
main().catch((e) => {
|
|
6738
6957
|
console.error(`\u9519\u8BEF: ${e.message}`);
|
|
6958
|
+
runCleanup();
|
|
6739
6959
|
process.exit(1);
|
|
6740
6960
|
});
|
|
6741
6961
|
/*! Bundled license information:
|