Files
Clash/clash
T
lukeopen e94e38e39c fix: 彻底修复 stop 后 TUN 残留导致断网
根因:mihomo 退出后 0.0.0.0/1 + 128.0.0.0/1 黑洞路由未清掉,
所有流量指向已消失的 TUN 网关 198.18.0.1 → 断网。

macOS (clash):
- start 解析 mihomo 真实 PID 写入 PID 文件(原记的是 sudo PID)
- cmd_stop 直接 pkill -TERM/-9 mihomo,不再依赖 sudo PID
- network_restore 无条件删除 0.0.0.0/1 + 128.0.0.0/1 黑洞路由,
  并放宽 TUN 识别到整个 198.18.x fake-IP 段(不再只认 198.18.0.1)
- network_leftovers 检测同步放宽

Windows (clash.ps1):
- NetworkRestore 删除 wintun 网卡上的全部路由(不限网段)+ 清理
  网关落在 198.18.x 的所有路由
- Test-NetworkLeftovers 路由检测放宽到 198.18.x
- CmdStop 强制终止时增加按进程名强杀兜底(防止 PID 文件不准杀不掉)

两版已做语法检查与入口冒烟测试。
2026-08-10 17:29:24 +08:00

690 lines
22 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/bin/zsh
# ═══════════════════════════════════════════════════════════
# Mihomo Kernel Manager — macOS
# clash <command> | clash 进入交互菜单 | clash help
# ═══════════════════════════════════════════════════════════
CONFIG_DIR="${HOME}/.config/mihomo"
CONFIG_FILE="${CONFIG_DIR}/config.yaml"
PID_FILE="${CONFIG_DIR}/mihomo.pid"
LOG_FILE="${CONFIG_DIR}/mihomo.log"
LINK_FILE="${CONFIG_DIR}/source.txt" # 源 YAML 路径
SOURCE_DIR="${HOME}/Mihomo" # 源配置目录(自动检测最新 YAML)
MIXED_PORT="7890"
API_PORT="9090"
MIHOMO_BIN="/opt/homebrew/bin/mihomo"
[[ -d "$CONFIG_DIR" ]] || mkdir -p "$CONFIG_DIR"
# ─── 颜色 ───
BOLD='\033[1m'; DIM='\033[2m'
GREEN='\033[0;32m'; BOLD_GREEN='\033[1;32m'
RED='\033[0;31m'; BOLD_RED='\033[1;31m'
YELLOW='\033[0;33m'; BOLD_YELLOW='\033[1;33m'
BLUE='\033[0;34m'; BOLD_BLUE='\033[1;34m'
PURPLE='\033[0;35m'; BOLD_PURPLE='\033[1;35m'
CYAN='\033[0;36m'; BOLD_CYAN='\033[1;36m'
WHITE='\033[1;37m'
NC='\033[0m'
SEP="${DIM}──────────────────────────────────────────${NC}"
# ─── 输出辅助 ───
info() { echo " ${CYAN}${NC} $*"; }
ok() { echo " ${GREEN}${NC} $*"; }
warn() { echo " ${YELLOW}${NC} $*"; }
err() { echo " ${RED}${NC} $*"; }
dim() { echo " ${DIM}$*${NC}"; }
section(){ echo ""; echo " ${BOLD}── $* ──${NC}"; }
# ─── 运行状态 ───
is_running() {
[[ -f "$PID_FILE" ]] && pgrep -F "$PID_FILE" &>/dev/null
}
# ========== 源文件自动检测 ==========
# 优先级:link 固定路径 > SOURCE_DIR 最新 .yaml/.yml
auto_detect_source() {
if [[ -f "$LINK_FILE" ]]; then
local linked=$(cat "$LINK_FILE")
if [[ -f "$linked" ]]; then
echo "$linked"
return 0
fi
warn "链接文件不存在: $linked,尝试自动检测..."
fi
if [[ -d "$SOURCE_DIR" ]]; then
local latest=$(ls -t "$SOURCE_DIR"/*.yaml(N) "$SOURCE_DIR"/*.yml(N) 2>/dev/null | head -1)
if [[ -n "$latest" && -f "$latest" ]]; then
echo "$latest"
return 0
fi
fi
return 1
}
# ========== macOS 适配:复制源文件并做兼容转换(不改原件)==========
macos_adapt_config() {
local src="${1:-}"
if [[ -z "$src" ]]; then
src=$(auto_detect_source)
fi
if [[ -z "$src" || ! -f "$src" ]]; then
err "未找到 YAML 配置文件"
dim "请把 .yaml 放到 $SOURCE_DIR,或用: clash link /path/to/file.yaml"
return 1
fi
echo -n " 适配 macOS 配置 ... "
cp "$src" "$CONFIG_FILE"
# macOS 兼容修复
sed -i '' '/^[[:space:]]*device: nikki[[:space:]]*$/d' "$CONFIG_FILE"
sed -i '' '/^[[:space:]]*device: nikki$/d' "$CONFIG_FILE"
sed -i '' 's/^\([[:space:]]*stack:\) system/\1 gvisor/' "$CONFIG_FILE"
sed -i '' '/^[[:space:]]*auto-redirect: true/d' "$CONFIG_FILE"
sed -i '' 's/ - tcp:\/\// - /g' "$CONFIG_FILE"
sed -i '' 's/ - udp:\/\// - /g' "$CONFIG_FILE"
ok "完成"
dim "源文件 $src"
dim "适配后 $CONFIG_FILE"
return 0
}
# ========== 管理面板 ==========
# /ui 下可能有多个面板子目录(如 zashboard),自动定位并生成首页跳转
ui_url() {
local ui_dir="$CONFIG_DIR/ui"
if [[ -d "$ui_dir" ]]; then
local dash
dash=$(find "$ui_dir" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sort | head -1)
if [[ -n "$dash" ]]; then
echo "http://127.0.0.1:${API_PORT}/ui/$(basename "$dash")/"
return 0
fi
fi
echo "http://127.0.0.1:${API_PORT}/ui"
}
ensure_ui_index() {
local ui_dir="$CONFIG_DIR/ui"
[[ -d "$ui_dir" ]] || return 0
local dash
dash=$(find "$ui_dir" -maxdepth 1 -mindepth 1 -type d 2>/dev/null | sort | head -1)
[[ -z "$dash" ]] && return 0
local dash_name=$(basename "$dash")
local index_file="$ui_dir/index.html"
if [[ ! -f "$index_file" ]]; then
cat > "$index_file" <<EOF
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta http-equiv="refresh" content="0; url=./${dash_name}/">
<title>Mihomo Dashboard</title>
</head>
<body>
<p>正在打开面板 <a href="./${dash_name}/">${dash_name}</a> ...</p>
</body>
</html>
EOF
info "已生成面板首页跳转: $index_file"
fi
}
# ========== 残留网络状态检测 ==========
# 返回 0 = 存在 mihomo 残留进程 / 残留 TUN 劫持 / 系统代理 / DNS 劫持
network_leftovers() {
pgrep -l mihomo >/dev/null 2>&1 && return 0
local dev
for dev in $(ifconfig -l 2>/dev/null); do
[[ "$dev" != utun* ]] && continue
local idx="${dev#utun}"
[[ "$idx" != <-> ]] && continue
if ifconfig "$dev" 2>/dev/null | grep -qE "inet 198\.18\."; then
return 0
fi
if netstat -rn -f inet 2>/dev/null | grep -qE "198\.18\.[[:space:]]+.*[[:space:]]+$dev"; then
return 0
fi
(( idx >= 1024 )) && return 0
done
local hijacked=0
while IFS= read -r service; do
local clean=$(printf '%s' "$service" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -z "$clean" ]] && continue
local wp=$(networksetup -getwebproxy "$clean" 2>/dev/null)
local sp=$(networksetup -getsocksfirewallproxy "$clean" 2>/dev/null)
if echo "$wp$sp" | grep -qE "Server: 127\.0\.0\.1" && echo "$wp$sp" | grep -qE "Port: ${MIXED_PORT}"; then
hijacked=1
break
fi
done < <(networksetup -listallnetworkservices 2>/dev/null | tail -n +2)
[[ $hijacked -eq 1 ]] && return 0
scutil --dns 2>/dev/null | grep -qE "nameserver\[[0-9]+\].*(127\.0\.0\.1|198\.18\.)" && return 0
return 1
}
# ========== 网络恢复(TUN/路由/代理/DNS 全面清理)==========
network_restore() {
local verbose=${1:-true}
$verbose && section "网络恢复"
# 1. 清理残留 mihomo 进程
if pgrep -l mihomo >/dev/null 2>&1; then
$verbose && warn "发现残留进程,正在清理..."
sudo pkill -9 mihomo 2>/dev/null
rm -f "$PID_FILE"
fi
# 2. 清理 mihomo 残留 TUN / 全量劫持路由
# 放宽识别:fake-IP 网关只要是 198.18.x 段都算(不限于 198.18.0.1),
# 并无条件删除 0.0.0.0/1 + 128.0.0.0/1 这两条黑洞路由,防止残留导致断网
local tun_list=$(ifconfig -l 2>/dev/null)
# fake-IP 网关段(默认 198.18.x,放宽不再只认 198.18.0.1
local fake_gws=$(netstat -rn -f inet 2>/dev/null | awk '$4 ~ /^utun[0-9]+$/ && $2 ~ /^198\.18\./ {print $2}' | sort -u)
local route_devs=$(netstat -rn -f inet 2>/dev/null | awk '$4 ~ /^utun[0-9]+$/ && $2 ~ /^198\.18\./ {print $4}' | sort -u)
local mihomo_tuns=""
for dev in ${(s: :)tun_list}; do
[[ "$dev" != utun* ]] && continue
local idx="${dev#utun}"
[[ "$idx" != <-> ]] && continue
if ifconfig "$dev" 2>/dev/null | grep -qE "inet 198\.18\."; then
mihomo_tuns+=" $dev"
continue
fi
if [[ -n "$route_devs" ]] && echo "$route_devs" | grep -qx "$dev"; then
mihomo_tuns+=" $dev"
continue
fi
(( idx >= 1024 )) && mihomo_tuns+=" $dev"
done
# 无条件清理全量劫持路由(0.0.0.0/1 + 128.0.0.0/1),几乎必定是 mihomo 创建
for gw in ${(s: :)fake_gws}; do
sudo route -n delete -net 0.0.0.0/1 "$gw" 2>/dev/null
sudo route -n delete -net 128.0.0.0/1 "$gw" 2>/dev/null
sudo route -n delete -net 198.18.0.0/16 "$gw" 2>/dev/null
done
# 兜底:常见默认网关组合,防止识别遗漏留下黑洞
sudo route -n delete -net 0.0.0.0/1 198.18.0.1 2>/dev/null
sudo route -n delete -net 128.0.0.0/1 198.18.0.1 2>/dev/null
for dev in ${(s: :)mihomo_tuns}; do
$verbose && echo -n " 清理残留 TUN $dev ... "
sudo ifconfig "$dev" down 2>/dev/null
sudo ifconfig "$dev" destroy 2>/dev/null
$verbose && echo "${GREEN}${NC}"
done
# 3. 关闭系统 HTTP/HTTPS/SOCKS 代理(遍历所有网络服务)
while IFS= read -r service; do
local clean=$(printf '%s' "$service" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -z "$clean" ]] && continue
local wp=$(networksetup -getwebproxy "$clean" 2>/dev/null)
local sp=$(networksetup -getsocksfirewallproxy "$clean" 2>/dev/null)
if echo "$wp$sp" | grep -qE "Server: 127\.0\.0\.1" && echo "$wp$sp" | grep -qE "Port: ${MIXED_PORT}"; then
$verbose && echo -n " 关闭系统代理 ($clean) ... "
sudo networksetup -setwebproxystate "$clean" off 2>/dev/null
sudo networksetup -setsecurewebproxystate "$clean" off 2>/dev/null
sudo networksetup -setsocksfirewallproxystate "$clean" off 2>/dev/null
$verbose && echo "${GREEN}${NC}"
fi
done < <(networksetup -listallnetworkservices 2>/dev/null | tail -n +2)
# 4. 刷新 DNS 缓存
$verbose && echo -n " 刷新 DNS 缓存 ... "
sudo dscacheutil -flushcache 2>/dev/null
sudo killall -HUP mDNSResponder 2>/dev/null
$verbose && echo "${GREEN}${NC}"
# 5. 还原系统 DNS(如果被 mihomo 指到 127.0.0.1 / 198.18.x
if scutil --dns 2>/dev/null | grep -qE "nameserver\[[0-9]+\].*(127\.0\.0\.1|198\.18\.)"; then
$verbose && echo -n " 还原系统 DNS ... "
while IFS= read -r service; do
local clean=$(printf '%s' "$service" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -z "$clean" ]] && continue
if networksetup -getdnsservers "$clean" 2>/dev/null | grep -qE "^(127\.0\.0\.1|198\.18\.)"; then
sudo networksetup -setdnsservers "$clean" empty 2>/dev/null
fi
done < <(networksetup -listallnetworkservices 2>/dev/null | tail -n +2)
sudo dscacheutil -flushcache 2>/dev/null
sudo killall -HUP mDNSResponder 2>/dev/null
$verbose && echo "${GREEN}${NC}"
fi
$verbose && echo ""
$verbose && ok "网络已恢复"
}
# ========== 网络诊断 ==========
network_health_check() {
local issues=0
section "网络诊断"
if nslookup baidu.com >/dev/null 2>&1; then
ok "DNS 解析正常 (baidu.com)"
else
err "DNS 解析失败"
issues=1
fi
if ping -c 1 -W 2 8.8.8.8 >/dev/null 2>&1; then
ok "外网连通 (8.8.8.8)"
else
err "外网不可达"
issues=1
fi
local tun_count=$(ifconfig -l 2>/dev/null | tr ' ' '\n' | grep -c "^utun")
local tun_high=0
local dev
for dev in $(ifconfig -l 2>/dev/null); do
[[ "$dev" != utun* ]] && continue
local idx="${dev#utun}"
[[ "$idx" != <-> ]] && continue
(( idx >= 1024 )) && (( tun_high++ ))
done
if [[ $tun_high -eq 0 ]]; then
ok "TUN 接口 ${tun_count} 个,无残留"
else
warn "TUN ${tun_count} 个(${tun_high} 个高编号残留)"
issues=1
fi
if [[ $issues -eq 1 ]]; then
warn "网络可能有异常,试试: clash cleanup"
else
ok "网络一切正常"
fi
}
# ========== 启动 ==========
cmd_start() {
local src="${1:-}"
local do_adapt=true
if [[ -n "$src" ]]; then
if [[ "$src" == "$CONFIG_FILE" ]] || [[ "$src" == "$(cat "$LINK_FILE" 2>/dev/null)" ]]; then
do_adapt=false
fi
fi
if [[ -z "$src" ]]; then
src=$(auto_detect_source)
fi
if $do_adapt && [[ "$src" != "$CONFIG_FILE" ]]; then
macos_adapt_config "$src" || return 1
elif [[ ! -f "$CONFIG_FILE" ]]; then
err "配置文件不存在: $CONFIG_FILE"
dim "试试: clash link /path/to/your.yaml"
return 1
fi
if is_running; then
warn "mihomo 已经在运行了"
return 0
fi
# 启动前清理上次异常退出残留(旧 TUN/路由会继续劫持全部流量)
if network_leftovers; then
echo ""
info "检测到上次残留网络状态,先清理 ..."
network_restore false
fi
echo ""
echo -n " 启动中 ... "
nohup sudo "$MIHOMO_BIN" -d "$CONFIG_DIR" -f "$CONFIG_FILE" > "$LOG_FILE" 2>&1 &
local sudo_pid=$!
sleep 1
# 解析 mihomo 真实 PIDsudo 的子进程),PID 文件记它,stop 才能精确杀
local mihomo_pid=$(pgrep -P "$sudo_pid" 2>/dev/null | head -1)
echo "${mihomo_pid:-$sudo_pid}" > "$PID_FILE"
if is_running; then
ok "已就绪"
ensure_ui_index
echo ""
printf " ${DIM}%-10s${NC} ${CYAN}127.0.0.1:${MIXED_PORT}${NC} ${DIM}(HTTP/SOCKS5)${NC}\n" "代理端口"
printf " ${DIM}%-10s${NC} ${CYAN}127.0.0.1:${API_PORT}${NC}\n" "API 控制"
printf " ${DIM}%-10s${NC} ${CYAN}$(ui_url)${NC}\n" "管理面板"
echo ""
else
err "启动失败"
dim "查看日志: tail $LOG_FILE"
rm -f "$PID_FILE"
return 1
fi
}
# ========== 停止 ==========
cmd_stop() {
if ! is_running; then
warn "mihomo 没有在运行"
# 进程没了但可能残留 TUN/路由/代理(例如被强杀),顺手清理
if network_leftovers; then
echo ""
info "检测到残留网络状态,正在清理 ..."
network_restore true
echo ""
network_health_check
fi
return 0
fi
local pid=$(cat "$PID_FILE")
echo ""
echo -n " 停止中 ... "
# 直接给 mihomo 发 SIGTERMPID 文件记的是 sudo,按进程名杀最稳)
sudo pkill -TERM -x mihomo 2>/dev/null
sudo kill "$pid" 2>/dev/null # 兜底:也通知 sudo 进程
for i in {1..10}; do
if ! pgrep -x mihomo >/dev/null 2>&1; then
ok "已停止"
rm -f "$PID_FILE"
echo ""
network_restore false
network_health_check
return 0
fi
sleep 0.5
done
echo ""
warn "超时,强制终止..."
sudo pkill -9 -x mihomo 2>/dev/null
sleep 0.5
rm -f "$PID_FILE"
if pgrep -x mihomo >/dev/null 2>&1; then
err "停止失败,残留进程仍在运行"
dim "试试: sudo pkill -9 mihomo"
return 1
fi
warn "已强制停止"
info "正在恢复网络 ..."
network_restore true
echo ""
network_health_check
}
# ========== 重启 / 热重载 ==========
cmd_restart() {
cmd_stop
sleep 1
cmd_start "$@"
}
cmd_reload() {
local cfg="${1:-$CONFIG_FILE}"
if ! is_running; then
warn "mihomo 没在运行,直接启动"
cmd_start "$cfg"
return $?
fi
if [[ ! -f "$cfg" ]]; then
err "配置文件不存在: $cfg"
return 1
fi
echo -n " 热重载配置 ... "
if curl -s -X PUT "http://127.0.0.1:${API_PORT}/configs" \
-H "Content-Type: application/json" \
-d "{\"path\":\"${cfg}\"}" > /dev/null 2>&1; then
ok "成功"
else
err "API 不可达,尝试重启..."
cmd_restart "$cfg"
fi
}
# ========== 状态 ==========
cmd_status() {
if is_running; then
local pid=$(cat "$PID_FILE")
echo ""
echo " ${BOLD_GREEN}● 运行中${NC} ${DIM}PID: $pid${NC}"
echo " $SEP"
printf " ${DIM}%-10s${NC} ${CYAN}127.0.0.1:${MIXED_PORT}${NC} (HTTP/SOCKS5)\n" "代理端口"
printf " ${DIM}%-10s${NC} ${CYAN}127.0.0.1:${API_PORT}${NC}\n" "API 端口"
printf " ${DIM}%-10s${NC} ${CYAN}$(ui_url)${NC}\n" "管理面板"
printf " ${DIM}%-10s${NC} %s\n" "配置文件" "$CONFIG_FILE"
printf " ${DIM}%-10s${NC} %s\n" "日志文件" "$LOG_FILE"
local src=$(cat "$LINK_FILE" 2>/dev/null || auto_detect_source 2>/dev/null)
[[ -n "$src" ]] && printf " ${DIM}%-10s${NC} %s\n" "源文件" "$src"
echo ""
else
echo ""
echo " ${BOLD_RED}○ 已停止${NC}"
echo " $SEP"
dim "mihomo 当前未运行"
echo ""
rm -f "$PID_FILE"
fi
}
# ========== 链接源配置(查看/设置,合并原 import==========
cmd_link() {
local src="${1:-}"
if [[ -z "$src" ]]; then
echo ""
section "源文件链接"
if [[ -f "$LINK_FILE" ]]; then
info "当前链接: $(cat "$LINK_FILE")"
else
local detected=$(auto_detect_source)
if [[ -n "$detected" ]]; then
info "自动检测: $detected"
else
warn "未检测到源文件"
fi
fi
echo ""
echo -n " 请输入 YAML 文件路径 ${DIM}(Enter 取消)${NC}: "
read src
[[ -z "$src" ]] && { dim "已取消"; return 0; }
src=$(echo "$src" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/^"//;s/"$//')
fi
if [[ ! -f "$src" ]]; then
err "文件不存在: $src"
return 1
fi
echo "$src" > "$LINK_FILE"
ok "已链接"
dim "源文件 $src"
echo ""
echo -n " 立即适配并启动? (y/n) "
read -q ans
echo ""
if [[ "$ans" == "y" ]]; then
macos_adapt_config "$src" || return 1
if is_running; then
cmd_restart
else
cmd_start
fi
fi
}
# ========== 日志 / 编辑 / 面板 / 修复 / 诊断 ==========
cmd_log() {
if [[ -f "$LOG_FILE" ]]; then
echo ""
section "实时日志 (Ctrl+C 退出)"
tail -f "$LOG_FILE"
else
warn "日志文件不存在"
fi
}
cmd_edit() {
info "打开编辑器: $CONFIG_FILE"
${EDITOR:-vim} "$CONFIG_FILE"
}
cmd_open() {
if is_running; then
ensure_ui_index
local url=$(ui_url)
open "$url"
ok "已打开管理面板"
dim "$url"
else
warn "mihomo 未运行,请先启动"
fi
}
cmd_cleanup() {
network_restore true
}
cmd_doctor() {
network_health_check
}
# ========== 关闭 Clash Verge 后台服务 ==========
cmd_verge_off() {
echo ""
section "关闭 Clash Verge"
local svc_pid=$(pgrep -f "clash-verge-service" 2>/dev/null)
if [[ -n "$svc_pid" ]]; then
info "发现 Clash Verge 服务 (PID: $svc_pid)"
echo -n " 正在停止 ... "
sudo kill "$svc_pid" 2>/dev/null
sleep 1
if pgrep -f "clash-verge-service" >/dev/null 2>&1; then
sudo kill -9 "$svc_pid" 2>/dev/null
fi
ok "已停止"
else
ok "Clash Verge 服务未运行"
fi
echo ""
echo -n " 是否同时卸载开机自启? (y/n) "
read -q ans
echo ""
if [[ "$ans" == "y" ]]; then
sudo /bin/launchctl unload /Library/LaunchDaemons/io.github.clash-verge-rev.clash-verge-rev.plist 2>/dev/null
ok "已卸载开机自启"
fi
echo ""
ok "现在可以纯用 clash 脚本管理了"
echo ""
}
# ========== 帮助 ==========
show_help() {
echo ""
echo " ${BOLD_PURPLE}Mihomo Kernel Manager${NC} ${DIM}— macOS${NC}"
echo " $SEP"
echo ""
echo " ${BOLD}用法:${NC}"
echo " ${CYAN}clash <command>${NC} 执行子命令"
echo " ${CYAN}clash${NC} 进入交互菜单"
echo " ${CYAN}clash help${NC} 显示本帮助"
echo ""
echo " ${BOLD}核心:${NC}"
printf " ${CYAN}%-14s${NC} %s\n" "start [file]" "启动(自动适配 macOS 配置)"
printf " ${CYAN}%-14s${NC} %s\n" "stop" "停止并恢复网络"
printf " ${CYAN}%-14s${NC} %s\n" "restart" "重启"
printf " ${CYAN}%-14s${NC} %s\n" "status" "查看运行状态"
printf " ${CYAN}%-14s${NC} %s\n" "reload [file]" "热重载配置"
printf " ${CYAN}%-14s${NC} %s\n" "open" "打开管理面板"
printf " ${CYAN}%-14s${NC} %s\n" "log" "实时日志"
echo ""
echo " ${BOLD}配置:${NC}"
printf " ${CYAN}%-14s${NC} %s\n" "link [file]" "查看/设置源 YAML(含原 import"
printf " ${CYAN}%-14s${NC} %s\n" "edit" "编辑当前配置"
echo ""
echo " ${BOLD}网络:${NC}"
printf " ${CYAN}%-14s${NC} %s\n" "cleanup" "网络修复(清理残留 TUN/路由/代理/DNS"
printf " ${CYAN}%-14s${NC} %s\n" "doctor" "网络诊断"
echo ""
echo " ${BOLD}系统:${NC}"
printf " ${CYAN}%-14s${NC} %s\n" "verge-off" "关闭 Clash Verge 后台服务"
echo ""
echo " ${BOLD}示例:${NC}"
dim "clash start"
dim "clash link ~/Mihomo/config.yaml"
dim "clash cleanup"
echo ""
}
# ========== 交互菜单 ==========
show_menu() {
clear
echo ""
echo " ${BOLD_PURPLE}Mihomo Kernel Manager${NC} ${DIM}— macOS${NC}"
echo " $SEP"
if is_running; then
printf " ${BOLD_GREEN}● 运行中${NC} ${DIM}PID: %s${NC} ${DIM}代理: %s${NC} ${DIM}API: %s${NC}\n" \
"$(cat "$PID_FILE" 2>/dev/null)" "${MIXED_PORT}" "${API_PORT}"
else
echo " ${BOLD_RED}○ 已停止${NC}"
fi
echo ""
echo " ${BOLD}1${NC} 启动 ${BOLD}5${NC} 状态"
echo " ${BOLD}2${NC} 停止 ${BOLD}6${NC} 实时日志"
echo " ${BOLD}3${NC} 打开面板 ${BOLD}7${NC} 热重载"
echo " ${BOLD}4${NC} 重启 ${BOLD}8${NC} 网络修复"
echo " ${BOLD}0${NC} 退出"
echo ""
echo -n " ${BOLD}${NC} "
}
interactive_menu() {
while true; do
show_menu
read choice
echo ""
case "$choice" in
1) cmd_start ;;
2) cmd_stop ;;
3) cmd_open ;;
4) cmd_restart ;;
5) cmd_status ;;
6) cmd_log ;;
7) cmd_reload ;;
8) cmd_cleanup ;;
0|q|Q)
echo " ${DIM}再见 👋${NC}"
echo ""
break
;;
*) warn "无效选项,请输入 0-8" ;;
esac
echo ""
echo -n " 按 Enter 返回菜单..."
read
done
}
# ========== 入口 ==========
case "${1:-}" in
start|s) cmd_start "$2" ;;
stop) cmd_stop ;;
restart|r) cmd_restart "$2" ;;
reload|rl) cmd_reload "$2" ;;
status|st) cmd_status ;;
log|l) cmd_log ;;
link|lk) cmd_link "$2" ;;
open|o) cmd_open ;;
edit|e) cmd_edit ;;
cleanup|c) cmd_cleanup ;;
doctor|d) cmd_doctor ;;
verge-off) cmd_verge_off ;;
help|-h|--help|h) show_help ;;
*) interactive_menu ;;
esac