From 6feea6391eac9b074c9a38fb80cdb8035663da42 Mon Sep 17 00:00:00 2001 From: Lihatoo <1747565629@gmail.com> Date: Wed, 11 Feb 2026 16:19:50 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=96=E6=B6=88sudo=E6=9D=83=E9=99=90?= =?UTF-8?q?=E8=A6=81=E6=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 211 +++++--------------- mh | 582 +++++++++++++++++++++++++++++++++--------------------- 2 files changed, 409 insertions(+), 384 deletions(-) diff --git a/README.md b/README.md index a99e485..248f195 100644 --- a/README.md +++ b/README.md @@ -1,189 +1,74 @@ # mh (mihomo wrapper) -基于 mihomo 的纯终端控制梯子工具,适合简单全局控制与分享。 -A terminal-only proxy wrapper based on mihomo, designed for simple global control and sharing. +>基于 mihomo 的纯ubuntu终端控制梯子工具,适合简单全局控制与。 -A small Bash wrapper around `mihomo` to manage profiles, start/stop the service, switch nodes, and toggle TUN. +mihomo: https://github.com/mihomo/mihomo -重要:必须使用 `sudo` 启动。 -Important: You must run this script with `sudo`. +# 部署 -Repo / 仓库: +将目录下的`mh`复制到`/usr/local/bin`下,并添加执行权限。 +```bash +sudo chmod +x mh # 给予执行权限 +sudo setcap cap_net_admin,cap_net_raw+ep /home/lht/bfile/mihomo/mihomo # 给予网络tun权限 ``` -https://github.com/Lihatoo/mh +然后在任何终端都可以使用`mh`命令 + +# 使用 + +## 导入 + +> 支持两种导入方法 +```bash +mh add name path +mh add name url +mh select name # 选择一个配置 +``` +分别是订阅链接或者是某个已配置好的`yaml`文件 +得到如下的项目结构 +```json +tnt +├── cache.db +├── config.yaml # 这是mh生成的配置文件,用于启动mihomo +├── geoip.metadb # 这是运行中生成的文件 +├── mihomo.log # 这是日志,可以用config.yaml 的log_file 指定error,warning,info,debug +└── providers + └── tnt.yaml # 这是提供节点的文件,来自你的订阅链接,tnt为你输入的名称 +current.profile # 保存是否需要tun配置 +run.pid # 运行时保存的进程id,目前只能支持起一个mihomo ``` -## What it does / 功能 +## 换节点 -- Creates per-profile directories under `MIHOMO_ROOT//` with generated `config.yaml` -- Starts/stops `mihomo` and records PID in `run.pid` -- Switches nodes by keyword and optionally ranks by delay -- Toggles TUN in the current profile and restarts -- Exports/unsets HTTP proxy environment variables - -- 在 `MIHOMO_ROOT//` 下创建 profile 目录并生成 `config.yaml` -- 启动/停止 `mihomo`,PID 记录在 `run.pid` -- 通过关键词切换节点,并可按延迟排序 -- 切换 TUN 并重启 -- 导出/取消 HTTP 代理环境变量 - -## Requirements / 依赖 - -- Linux with `bash` -- `mihomo` binary in `MIHOMO_ROOT/mihomo` -- Tools: `ss`, `yq`, `jq`, `curl`, `find`, `ip` - -- Linux + `bash` -- `mihomo` 二进制放在 `MIHOMO_ROOT/mihomo` -- 工具:`ss`, `yq`, `jq`, `curl`, `find`, `ip` - -## Quick start / 快速开始 - -1. Set the root path / 设置根路径 - -Open `mh` and update this line to the absolute path of this repo: -打开 `mh`,把下面这行改成仓库的绝对路径: - -``` -MIHOMO_ROOT=/path/to/this/repo +```bash +mh -l # 列出搜索可选择节点 +mh name # 搜索并列表选择节点 ``` -2. Make the script executable / 授权可执行 +## 其他操作 -``` -chmod +x mh +```bash +mh tun on/off # 开启/关闭tun +mh -h # 帮助 +mh # 快速预览信息 +mh db # 一些测试,debug专用 ``` -3. Put `mh` in `/usr/local/bin` for direct use / 放到 `/usr/local/bin` 以便直接使用 +# 错误 +> 主要错误都可以看看`mihomo.log`文件,从其中分析 +## 网络dns解析失败 +> 节点正常搜索,测速正常,可是无法使用(浏览器,网站) +> 查看日志,前面下载正常,就是网络解析不了 -``` -sudo cp mh /usr/local/bin/mh -``` +`mh db`选择7 ,看看有几个dns! -4. Start with `sudo` (required) / 必须用 `sudo` 启动 - -``` -sudo mh -``` - -## Usage / 用法 - -Create a profile from a subscription URL (quote the URL is recommended): -从订阅 URL 创建 profile(建议加双引号): - -``` -sudo mh add "" -``` - -Create a profile from a local file: -从本地文件创建 profile: - -``` -sudo mh add /path/to/subscription.yaml -``` - -Start/select a profile: -启动/切换 profile: - -``` -sudo mh select -sudo mh select -``` - -Stop: -停止: - -``` -sudo mh end -``` - -Toggle proxy env vars (current shell only): -导出/取消代理环境变量(只影响当前 shell): - -``` -sudo -E mh on -sudo -E mh off -``` - -List nodes in the main group: -列出主组节点: - -``` -sudo mh -l -``` - -Switch node by keyword and rank by delay: -按关键词切换并按延迟排序: - -``` -sudo mh 美国 -sudo mh HK -``` - -Toggle TUN (requires sudo): -切换 TUN(需要 sudo): - -``` -sudo mh tnu on -sudo mh tnu off -sudo mh tnu toggle -``` - -Run connectivity test: -网络连通性测试: - -``` -sudo mh test -``` - -Show help: -查看帮助: - -``` -sudo mh --help -``` - -## Notes / 说明 - -- Default ports: `mixed-port=7890`, API `external-controller=127.0.0.1:9090`. -- If you use `sudo` and want `HTTP_PROXY/HTTPS_PROXY` to persist, use `sudo -E`. -- When you run `mh` with no arguments, it checks ports, environment, TUN status, and current node delay. - -- 默认端口:`mixed-port=7890`,API `external-controller=127.0.0.1:9090`。 -- 若想保留 `HTTP_PROXY/HTTPS_PROXY`,请用 `sudo -E`。 -- 不带参数运行 `mh` 会检查端口、环境、TUN 状态和当前节点延迟。 - -## Template / 模板 - -Use `profile_template/` as a sanitized example of the basic profile format. -可参考 `profile_template/` 作为去隐私后的基础配置格式示例。 +问我这里显示有两个dns,也就是说错误的ip解析可能来自另一个干扰的`tailscale0`(我这里是这个) -## Files created / 生成文件 -- `current.profile`: name of the active profile / 当前 profile 名称 -- `run.pid`: PID of the running `mihomo` process / 运行中的 PID -- `/mihomo.log`: log output for each profile / 日志文件 -## Example session / 示例 -``` -# add profile -sudo mh add test "https://example.com/sub.yaml" -# start/select -sudo mh select test -# switch node by keyword -sudo mh 日本 -``` -## Links / 相关链接 -Official mihomo repo: -官方 mihomo 仓库: - -``` -https://github.com/MetaCubeX/mihomo -``` diff --git a/mh b/mh index 8453f79..645d1e7 100755 --- a/mh +++ b/mh @@ -13,12 +13,6 @@ API_BASE="http://${API_HOST}:${API_PORT}" API_SECRET="" # 这是密钥,用于验证;对应yaml中设置的secret字段,保持一致即可;如果不设置,默认空字符串,表示不验证 # 简单彩色(不喜欢颜色就把这些变量都设为空字符串) -c_reset=$'\e[0m' -c_title=$'\e[1;36m' -c_key=$'\e[1;33m' -c_ok=$'\e[1;32m' -c_warn=$'\e[1;31m' -c_dim=$'\e[2m' urlenc() { python3 - <<'PY' "$1" import sys, urllib.parse @@ -62,10 +56,11 @@ test_port() { done } api_get() { + local path="$1" if [[ -n "${API_SECRET}" ]]; then - curl -fsS -H "Authorization: Bearer ${API_SECRET}" "${API_BASE}$1" + curl -fsS --noproxy '*' -H "Authorization: Bearer ${API_SECRET}" "${API_BASE}${path}" else - curl -fsS "${API_BASE}$1" + curl -fsS --noproxy '*' "${API_BASE}${path}" fi } @@ -73,13 +68,18 @@ api_put() { local path="$1" local data="$2" if [[ -n "${API_SECRET}" ]]; then - curl -fsS -X PUT -H "Authorization: Bearer ${API_SECRET}" -H 'Content-Type: application/json' \ + curl -fsS --noproxy '*' -X PUT \ + -H "Authorization: Bearer ${API_SECRET}" \ + -H 'Content-Type: application/json' \ -d "$data" "${API_BASE}${path}" else - curl -fsS -X PUT -H 'Content-Type: application/json' -d "$data" "${API_BASE}${path}" + curl -fsS --noproxy '*' -X PUT \ + -H 'Content-Type: application/json' \ + -d "$data" "${API_BASE}${path}" fi } + api_delay() { local name="$1" local timeout="${2:-2000}" @@ -103,6 +103,19 @@ switch_group_node() { api_put "/proxies/${enc_group}" "$(jq -nc --arg name "$node" '{name:$name}')" >/dev/null info "Switched group [$group] -> [$node]" } + +need_root() { + # 用法:need_root "sudo -E $0 tun on" + if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then + warn "此操作需要 sudo/root 权限" + warn "请用 sudo 运行:" + warn " $1" + warn "或给 mihomo 二进制加能力(不必整进程 root):" + warn " sudo setcap cap_net_admin,cap_net_raw+ep \"$MIHOMO_BIN\"" + return 1 + fi + return 0 +} #===============测试小工具,上====================================== @@ -228,86 +241,39 @@ YAML #=====================mihomo配置,上==================================== print_mihomo_status() { - local timeout_ms="${1:-2000}" + local cfg pjson + cfg="$(api_get "/configs" 2>/dev/null)" || { echo "❌ API 不通: ${API_BASE}"; return 1; } + pjson="$(api_get "/proxies" 2>/dev/null)" || { echo "❌ 读取 /proxies 失败"; return 1; } - # 1) configs:运行态端口/LAN/mode - local cfg - if ! cfg="$(api_get "/configs")"; then - echo "${c_warn}✗ Cannot reach mihomo API at ${API_BASE}${c_reset}" - return 1 - fi - - local allow_lan bind_addr mode log_level mixed_port ec + local allow_lan bind_addr mode mixed_port ec allow_lan="$(jq -r '."allow-lan" // false' <<<"$cfg")" bind_addr="$(jq -r '."bind-address" // "-"' <<<"$cfg")" mode="$(jq -r '.mode // "-"' <<<"$cfg")" - log_level="$(jq -r '."log-level" // "-"' <<<"$cfg")" mixed_port="$(jq -r '."mixed-port" // .port // "-"' <<<"$cfg")" ec="$(jq -r '."external-controller" // "-"' <<<"$cfg")" - local lan_badge - if [[ "$allow_lan" == "true" ]]; then - lan_badge="${c_ok}ON${c_reset}" - else - lan_badge="${c_warn}OFF${c_reset}" - fi + local group now node_count + group="$(get_main_group 2>/dev/null || true)" + [[ -n "$group" ]] || { echo "❌ 找不到主组(/proxies 里没有可用 group)"; return 1; } - # 2) proxies:找主组 + now + all(节点列表) - local pjson - pjson="$(api_get "/proxies")" || { echo "${c_warn}✗ Cannot read /proxies${c_reset}"; return 1; } - - # 自动选“主组”:优先 Selector/URLTest/Fallback/LoadBalance,其次任意有 all 的 - local group - group="$(jq -r ' - .proxies - | to_entries - | map(select(.value.all? != null)) - | (map(select(.value.type?=="Selector" or .value.type?=="URLTest" or .value.type?=="Fallback" or .value.type?=="LoadBalance")) + .) - | .[0].key // empty - ' <<<"$pjson")" - - if [[ -z "$group" ]]; then - echo "${c_warn}✗ No proxy groups found in /proxies (unexpected)${c_reset}" - return 1 - fi - - local now node_count now="$(jq -r --arg g "$group" '.proxies[$g].now // "-"' <<<"$pjson")" node_count="$(jq -r --arg g "$group" '.proxies[$g].all | length' <<<"$pjson" 2>/dev/null || echo "-")" - # 3) 当前节点 delay(DIRECT/REJECT 不测) - local now_delay="-" - if [[ "$now" != "-" && "$now" != "DIRECT" && "$now" != "REJECT" ]]; then - now_delay="$(api_delay "$now" "$timeout_ms")" - fi + local lan_icon="🔒" + [[ "$allow_lan" == "true" ]] && lan_icon="📡" - # 4) 好看输出 - echo - echo "${c_title}╔══════════════════════════════ Mihomo Live Status ══════════════════════════════╗${c_reset}" - printf "${c_key}║%-16s${c_reset} %-58s ║\n" "API" "${API_BASE} ${c_dim}(external-controller=${ec})${c_reset}" - printf "${c_key}║%-16s${c_reset} %-58s ║\n" "Ports" "mixed-port=${mixed_port}" - printf "${c_key}║%-16s${c_reset} %-58s ║\n" "LAN" "allow-lan=${lan_badge} bind=${bind_addr}" - printf "${c_key}║%-16s${c_reset} %-58s ║\n" "Mode / Log" "mode=${mode} log-level=${log_level}" - printf "${c_key}║%-16s${c_reset} %-58s ║\n" "Main Group" "${group}" - printf "${c_key}║%-16s${c_reset} %-58s ║\n" "Nodes" "count=${node_count}" - printf "${c_key}║%-16s${c_reset} %-58s ║\n" "Now Using" "${now} ${c_dim}(delay=${now_delay}ms)${c_reset}" - echo "${c_title}╚═══════════════════════════════════════════════════════════════════════════════╝${c_reset}" - echo + echo "🧠 Mihomo Status" + echo "🌐 API: ${API_BASE} (EC=${ec})" + echo "🚪 Port: mixed-port=${mixed_port}" + echo "${lan_icon} LAN: allow-lan=${allow_lan} bind=${bind_addr}" + echo "🧭 Mode: ${mode}" + echo "🧩 Group: ${group}" + echo "📦 Nodes: ${node_count}" + echo "✅ Now: ${now}" } -on_off() { # 启动/停止代理, - if [[ "$1" == "on" ]]; then - export HTTP_PROXY="http://${API_HOST}:${MIXED_PORT}" - export HTTPS_PROXY="http://${API_HOST}:${MIXED_PORT}" - info "Proxy ON: HTTP_PROXY and HTTPS_PROXY set to ${API_HOST}:${MIXED_PORT}" - speedtest_now 1000 - elif [[ "$1" == "off" ]]; then - unset HTTP_PROXY HTTPS_PROXY - info "Proxy OFF: HTTP_PROXY and HTTPS_PROXY unset" - else - die "usage: $0 on|off" - fi -} + + start_end(){ # start/end name/none 文件 local start0Rend=$1 local name_file=${2:-} @@ -318,9 +284,8 @@ start_end(){ # start/end name/none 文件 # 进入的前提配置没有问题,可以直接运行 info "start mihomo with config=$cfg/config.yaml data=$cfg" # 前台跑不方便,这里后台启动并记录 pid - test_port "$API_PORT" "$MIXED_PORT" - "$MIHOMO_BIN" -d "$cfg" >"$cfg/mihomo.log" 2>&1 & # 每次启动情况记录 - # on_off on + test_port "$API_PORT" "$MIXED_PORT" "$SOCK_PORT" + "$MIHOMO_BIN" -d "$cfg" -f "$cfg/config.yaml" >"$cfg/mihomo.log" 2>&1 & # 每次启动情况记录 echo $! >"$PID_FILE" # 保存到定点位置 fi elif [[ "$start0Rend" == "end" ]]; then @@ -341,7 +306,6 @@ start_end(){ # start/end name/none 文件 fi fi rm -f "$PID_FILE" - # on_off off fi else die "usage: $0 start|end" @@ -389,85 +353,132 @@ get_group_nodes_json() { jq -nc --arg group "$group" --argjson nodes "$nodes" '{group:$group, nodes:$nodes}' } -pick_and_switch_by_keyword() { - # 用法:mh - local kw="${1:-}" - [[ -n "$kw" ]] || { warn "usage: mh "; return 1; } - - wait_api 5 || { warn "mihomo API not ready"; return 1; } - - local gj group - gj="$(get_group_nodes_json)" || { warn "cannot read proxies/groups"; return 1; } - group="$(jq -r '.group' <<<"$gj")" - - # 纯子串匹配:中文直接 contains;英文做 lower-case contains - local candidates - candidates="$(jq -r --arg kw "$kw" ' - def lc: ascii_downcase; - - .nodes - | map(select(. != "DIRECT" and . != "REJECT")) - | map(select( - (contains($kw)) # 中文/原样子串 - or ((lc) | contains($kw | lc)) # 英文忽略大小写 - )) - | .[] - ' <<<"$gj")" - - if [[ -z "$candidates" ]]; then - warn "No nodes matched keyword: $kw" - info "Tip: try: 美国 / 香港 / 日本 / 狮城 / 韩国 / 台湾 / US / HK / JP / SG" - return 1 - fi - - mapfile -t arr <<<"$candidates" - - echo "🔎 Matched nodes in group [$group] for keyword: [$kw]" - local i - for i in "${!arr[@]}"; do - printf " %d) %s\n" "$((i+1))" "${arr[$i]}" - done - - local pick - while true; do - read -r -p "👉 choose (1-${#arr[@]}, 0 cancel): " pick - [[ "$pick" =~ ^[0-9]+$ ]] || { echo "❌ 请输入数字"; continue; } - (( pick==0 )) && { echo "cancelled"; return 0; } - (( pick>=1 && pick<=${#arr[@]} )) || { echo "❌ 超出范围"; continue; } - break - done - - switch_group_node "$group" "${arr[$((pick-1))]}" - speedtest_now 1200 || true -} - help() { cat <}" + +3) 常见规则(按你当前情况选一个文件写入): + - bash + 交互式非登录 shell(多数终端 tab):~/.bashrc + - bash + 登录 shell(ssh 登录 / 有些终端配置):~/.bash_profile 或 ~/.profile + - zsh(mac 常见):~/.zshrc + - 如果你不确定: + a) bash:同时写 ~/.bashrc 和 ~/.bash_profile(bash_profile 里最好加一行:[[ -f ~/.bashrc ]] && source ~/.bashrc) + b) zsh:写 ~/.zshrc + +验证是否生效(新开终端或 source 后): + echo \$http_proxy + env | grep -iE 'http_proxy|https_proxy|all_proxy|no_proxy' + +------------------------------------------------------------------------------- + Usage: - $0 add # 新建一个 profile 目录:${MIHOMO_ROOT}// - # 生成 config.yaml,并把订阅作为 proxy-provider - # :在线订阅(type=http) - # :本地文件(type=file,会自动删 url/interval) + mh [command] [args...] - $0 select [name] # 切换/启动某个 profile - # 不带 name:列出目录,输入序号选择 - # 带 name:直接启动 ${MIHOMO_ROOT}// +Commands: + add + 新建一个 profile 目录:${MIHOMO_ROOT}// + - 生成 config.yaml + - 把订阅作为 proxy-provider + - 在线订阅(type=http) + - 本地文件(type=file,会自动删 url/interval) - $0 start # 直接启动指定 profile(等价于 select name) - $0 end # 停止当前 mihomo(用 ${PID_FILE}) + select [name] + 切换/启动某个 profile + - 不带 name:列出目录,输入序号选择 + - 带 name:直接启动 ${MIHOMO_ROOT}// - $0 on # 导出 HTTP_PROXY/HTTPS_PROXY 指向 127.0.0.1:${MIXED_PORT} - $0 off # 取消代理环境变量 + start + 直接启动指定 profile(等价于:select ) - $0 # 显示端口监听情况 + 当前节点测速(需要 API 就绪) - $0 tnu [on|off|toggle] Toggle TUN (writes tun: into current profile and restarts) - $0 -l | --list # 列出主组(优先 Proxy)的所有节点,并标记当前节点 - $0 美国 # 模糊搜索节点并切换 + end + 停止当前 mihomo(通过 ${PID_FILE} 记录的 pid) -Tips: - - 如果刚启动就查节点失败,加了 wait_api 会更稳(避免 race) - - 当前节点显示 DIRECT 代表还没切到 provider 节点,或 provider 没拉到节点 + tun [on|off|toggle] + 开关 TUN(会写入当前 profile 的 config.yaml 并重启) + - 开启 TUN 通常需要 sudo/root 或给 mihomo 加能力: + sudo setcap cap_net_admin,cap_net_raw+ep "" + + -l | --list | list + 列出主组(优先 Proxy)的所有节点,并标记当前节点 + + + 模糊搜索节点名称并测速排序后让你选择切换 + 例如: + mh 美国 + mh 香港 + mh JP + mh SG + +No-args (默认行为): + 直接显示: + - TUN 状态 + - 当前环境是否存在 HTTP_PROXY/HTTPS_PROXY + - 当前节点延迟测速(通过 mihomo API) + +------------------------------------------------------------------------------- + +Examples: + # 添加订阅(在线) + mh add tnt "https://example.com/sub.yaml" + + # 添加订阅(本地文件) + mh add local "/path/to/sub.yaml" + + # 启动/切换 profile + mh select + mh select tnt + + # 停止 mihomo + mh end + + # 查看节点列表 + mh -l + + # 按关键词筛选并测速排序后切换 + mh 美国 + + # TUN 开关 + sudo mh tun on + sudo mh tun off + mh tun toggle + +Notes: + - API 不通时,很多功能会失败:确保 mihomo 正在运行且 external-controller 为 ${API_HOST}:${API_PORT} + - 如果刚启动就查询失败,wait_api 会稍等 mihomo API 就绪(避免 race) EOF } @@ -513,15 +524,15 @@ select_one(){ # select name } -#====================tnu控制,下========================= +#====================tun控制,下========================= current_profile() { [[ -f "${MIHOMO_ROOT}/current.profile" ]] || return 1 cat "${MIHOMO_ROOT}/current.profile" } -tnu() { - # 用法:mh tnu on|off|toggle +tun() { + # 用法:sudo mh tun on|off|toggle local action="${1:-toggle}" local prof prof="$(current_profile)" || { warn "no current profile"; return 1; } @@ -537,27 +548,28 @@ tnu() { on) next=true ;; off) next=false ;; toggle) [[ "$cur" == "true" ]] && next=false || next=true ;; - *) warn "usage: mh tnu on|off|toggle"; return 1 ;; + *) warn "usage: mh tun on|off|toggle"; return 1 ;; esac - + if [[ "$next" == "true" ]]; then # 只有“要开启 tun”才提示/要求 sudo + need_root "sudo -E \"$0\" tun ${action}" || return 1 + fi yq -y -i " .tun.enable = ${next} | .tun.stack = (.tun.stack // \"system\") | .tun.device = (.tun.device // \"mihomo\") | .tun[\"auto-route\"] = (.tun[\"auto-route\"] // true) | + .tun[\"auto-redir\"] = (.tun[\"auto-redir\"] // true) | .tun[\"auto-detect-interface\"] = (.tun[\"auto-detect-interface\"] // true) | .tun[\"dns-hijack\"] = (.tun[\"dns-hijack\"] // []) | ( .tun[\"dns-hijack\"] |= ( . + [\"any:53\"] | unique ) ) | .dns.enable = ${next} | .dns.listen = (.dns.listen // \"127.0.0.1:1053\") | - .dns[\"enhanced-mode\"] = (.dns[\"enhanced-mode\"] // \"fake-ip\") | + .dns[\"enhanced-mode\"] = \"redir-host\" | .dns.nameserver = (.dns.nameserver // [\"223.5.5.5\",\"119.29.29.29\"]) | .dns.fallback = (.dns.fallback // [\"1.1.1.1\",\"8.8.8.8\"]) " "$cfg" - - info "TUN set to ${next} (profile=${prof}), restarting..." start_end end start_end start "$prof" @@ -565,7 +577,7 @@ tnu() { print_mihomo_status 1200 || true } -#=============================tnu控制,上=================== +#=============================tun控制,上=================== list_nodes() { # 用法:mh -l @@ -705,68 +717,37 @@ pick_switch_with_delay() { #==============================搜索测速排序,上====================== #============================状态检查,下===================== -detect_tun_ifaces() { - # 输出检测到的 TUN/TAP 接口名;没有则空 - local out="" - - # 1) ip link:匹配 tun/utun/tap(忽略大小写) - out="$(ip -o link show 2>/dev/null | awk -F': ' '{print $2}' \ - | grep -iE '(^tun[0-9]*$|^utun[0-9]*$|^tap[0-9]*$|tun|utun|tap|mihomo|clash|meta)' || true)" - - if [[ -n "$out" ]]; then - echo "$out" | sed '/^$/d' | sort -u - return 0 - fi - - # 2) ip tuntap:更权威(有些发行版默认不装 iproute2 的 tuntap 子命令,失败就忽略) - if ip tuntap show >/dev/null 2>&1; then - ip tuntap show | awk '{print $1}' | sed '/^$/d' | sort -u - return 0 - fi - - # 3) /sys/class/net:兜底 - ls /sys/class/net 2>/dev/null | grep -iE '(^tun[0-9]*$|^utun[0-9]*$|^tap[0-9]*$|tun|utun|tap|mihomo|clash|meta)' || true -} - tun_status() { - local prof cfg enabled + local prof cfg enabled dev prof="$(current_profile 2>/dev/null || true)" - if [[ -z "$prof" ]]; then - echo "TUN: unknown (no current profile)" - return 0 - fi + [[ -n "$prof" ]] || { echo "TUN: unknown (no current profile)"; return 0; } cfg="${MIHOMO_ROOT}/${prof}/config.yaml" - if [[ ! -f "$cfg" ]]; then - echo "TUN: unknown (missing config)" - return 0 - fi + [[ -f "$cfg" ]] || { echo "TUN: unknown (missing config)"; return 0; } enabled="$(yq -r '.tun.enable // false' "$cfg" 2>/dev/null || echo false)" + dev="$(yq -r '.tun.device // "mihomo"' "$cfg" 2>/dev/null || echo mihomo)" - # 配置状态 if [[ "$enabled" != "true" ]]; then echo "TUN: OFF (profile=${prof})" return 0 fi - # 运行态:接口 - local ifs - ifs="$(detect_tun_ifaces || true)" - if [[ -z "$ifs" ]]; then - echo "TUN: ON in config, but NO tun/tap iface found (profile=${prof})" + echo "TUN: ON (profile=${prof})" + echo "Expect iface: ${dev}" + + if ip link show "$dev" >/dev/null 2>&1; then + echo "Iface: ✅ ${dev} exists" else - echo "TUN: ON (profile=${prof})" - echo "TUN ifaces:" - echo "$ifs" | sed 's/^/ - /' + echo "Iface: ❌ ${dev} not found (mihomo tun may not be running/privileged)" fi - # 运行态:默认路由(是否被接管) echo "Default route:" ip route show default 2>/dev/null | sed 's/^/ /' || true } + proxy_env_status() { if [[ -n "${HTTP_PROXY:-}" || -n "${HTTPS_PROXY:-}" ]]; then echo "Proxy ENV: ON (HTTP_PROXY=${HTTP_PROXY:-} HTTPS_PROXY=${HTTPS_PROXY:-})" @@ -776,26 +757,188 @@ proxy_env_status() { } net_test() { - # 验证:环境变量代理是否生效 + 通过代理访问 google - # 1) 走系统环境(如果你 mh on 了) - echo "== Test via ENV proxy (if enabled) ==" - curl -I -m 10 https://www.cloudflare.com | head -n 1 || echo "❌ ENV proxy path failed" + # 常用网站连通性测试:输出 “URL -> OK/FAIL” + local -a urls=( + "https://www.google.com" + "https://www.cloudflare.com" + "https://www.github.com" + "https://www.youtube.com" + "https://www.baidu.com" + "https://www.bing.com" + ) - # 2) 强制走 mihomo mixed-port(不依赖你是否 export) - echo - echo "== Test via explicit proxy http://127.0.0.1:${MIXED_PORT} ==" - curl -I -m 10 -x "http://127.0.0.1:${MIXED_PORT}" https://www.cloudflare.com | head -n 1 || echo "❌ explicit proxy failed" - echo - echo "== Current node delay (mihomo API) ==" - speedtest_now 1500 || true + echo "== Net Test (timeout=10s) ==" + local url + for url in "${urls[@]}"; do + if curl -fsS -I -m 10 "$url" >/dev/null 2>&1; then + printf "%s -> ✅ OK\n" "$url" + else + printf "%s -> ❌ FAIL\n" "$url" + fi + done +} + +net_test_double() { + local -a urls=( + "https://www.google.com" + "https://www.cloudflare.com" + "https://www.github.com" + "https://www.youtube.com" + "https://www.baidu.com" + "https://www.bing.com" + ) + + echo "== Net Test (timeout=10s) ==" + echo "Format: URL -> DIRECT | PROXY(mixed-port=${MIXED_PORT})" + local url ok1 ok2 + + for url in "${urls[@]}"; do + # 直连(完全忽略环境代理) + if curl -fsS -I -m 10 --noproxy '*' "$url" >/dev/null 2>&1; then + ok1="✅" + else + ok1="❌" + fi + + # 强制走 mihomo mixed-port(明确指定代理) + if curl -fsS -I -m 10 -x "http://127.0.0.1:${MIXED_PORT}" "$url" >/dev/null 2>&1; then + ok2="✅" + else + ok2="❌" + fi + + printf "%-28s -> DIRECT:%s PROXY:%s\n" "$url" "$ok1" "$ok2" + done } #============================状态检查,上===================== + +#=========================debug,下=============================== +debug_one() { + shift + + local -a debug_code=() + debug_code[1]="ss -lntp | grep -E ':(9090|9091|7890|7891)'|| echo \"❌ 未找到代理服务\"" + debug_code[2]="ps -fp \${1:-} -o pid,ppid,cmd" + debug_code[3]="net_test" + debug_code[4]="net_test_double" + debug_code[5]="test_env" + debug_code[6]="ip -o link show" + debug_code[7]="resolvectl status 2>/dev/null | sed -n '1,120p' || cat /etc/resolv.conf" + + local -a debug_desc=() + debug_desc[1]="检查 9090/9091/7890 端口监听:" + debug_desc[2]="查看指定 PID 的进程信息:" + debug_desc[3]="测试一下网络(仅proxy)" + debug_desc[4]="测试一下网络(直连+proxy)" + debug_desc[5]="测试需要的软件是否安装:" + debug_desc[6]="看看是否有虚拟网卡 mihomo:" + debug_desc[7]="dns debug" + + echo "== debug list ==" + local i + for i in "${!debug_code[@]}"; do + printf " [%s] %s\n %s\n" "$i" "${debug_desc[$i]:-}" "${debug_code[$i]}" + done + local idx + read -r -p "select index: " idx + + for i in "${!debug_code[@]}"; do + if [[ "$i" == "$idx" ]]; then + echo "== run [$i] ==" + eval "${debug_code[$i]}" + return $? + fi + done + + echo "invalid index: $idx" + return 1 +} + +proxy_on() { + export http_proxy="http://127.0.0.1:${MIXED_PORT}" + export https_proxy="http://127.0.0.1:${MIXED_PORT}" + export all_proxy="socks5h://127.0.0.1:${MIXED_PORT}" + + export HTTP_PROXY="$http_proxy" + export HTTPS_PROXY="$https_proxy" + export ALL_PROXY="$all_proxy" +# 设置no,是为了避免mihomo自己访问自己 + export NO_PROXY="127.0.0.1,localhost,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" + export no_proxy="$NO_PROXY" +} + +proxy_off() { + unset http_proxy https_proxy all_proxy HTTP_PROXY HTTPS_PROXY ALL_PROXY NO_PROXY no_proxy +} + + +# proxy_env() { +# local action="${1:-}" +# local bashrc="${HOME}/.bashrc" +# local port="${2:-${MIXED_PORT:-7890}}" + +# [[ -f "$bashrc" ]] || { echo "❌ not found: $bashrc"; return 1; } + +# # 备份 +# local bak="${bashrc}.bak.$(date +%Y%m%d%H%M%S)" +# cp -a "$bashrc" "$bak" || { echo "❌ backup failed"; return 1; } + +# # 1) 先清理:标记块 + 常见代理 export 行 +# # - 标记块:# >>> MH PROXY BEGIN ... # <<< MH PROXY END +# # - 以及零散的 export http_proxy/HTTP_PROXY/NO_PROXY...(避免残留) +# perl -0777 -pe ' +# s/\n?# >>> MH PROXY BEGIN\n.*?\n# <<< MH PROXY END\n?/\n/sg; +# ' -i "$bashrc" + +# # 删除零散的代理行(只删我们关心的这些变量) +# sed -i \ +# -e '/^[[:space:]]*export[[:space:]]\+\(http_proxy\|https_proxy\|all_proxy\|HTTP_PROXY\|HTTPS_PROXY\|ALL_PROXY\|NO_PROXY\|no_proxy\)=/d' \ +# -e '/^[[:space:]]*\(http_proxy\|https_proxy\|all_proxy\|HTTP_PROXY\|HTTPS_PROXY\|ALL_PROXY\|NO_PROXY\|no_proxy\)=/d' \ +# "$bashrc" + +# case "$action" in +# on) +# cat >>"$bashrc" <>> MH PROXY BEGIN +# export http_proxy="http://127.0.0.1:${port}" +# export https_proxy="http://127.0.0.1:${port}" +# export all_proxy="socks5h://127.0.0.1:${port}" + +# export HTTP_PROXY="\$http_proxy" +# export HTTPS_PROXY="\$https_proxy" +# export ALL_PROXY="\$all_proxy" + +# export NO_PROXY="127.0.0.1,localhost,::1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" +# export no_proxy="\$NO_PROXY" +# # <<< MH PROXY END +# EOF +# echo "✅ proxy_env on: wrote proxy to $bashrc (port=${port})" +# echo " apply with: source ~/.bashrc" +# ;; +# off) +# echo "✅ proxy_env off: removed proxy lines from $bashrc" +# echo " apply with: source ~/.bashrc" +# ;; +# *) +# echo "usage:" +# echo " proxy_env on [port] # write proxy exports into ~/.bashrc (default port=\${MIXED_PORT:-7890})" +# echo " proxy_env off # remove proxy exports from ~/.bashrc" +# echo "backup:" +# echo " $bak" +# return 1 +# ;; +# esac +# } + +#=========================debug,上=============================== + main() { + proxy_on + # unset http_proxy https_proxy all_proxy HTTP_PROXY # 停止一些用法,终止一些env if [[ -z "${1:-}" ]]; then - ss -lntp | grep -E ':(9090|7890)'|| echo "❌ 未找到代理服务" - test_env tun_status proxy_env_status speedtest_now 1000 @@ -803,21 +946,18 @@ main() { exit 0 fi case "${1:-}" in - add) + add) cfg_add "$@";; - on|off) - on_off "$@";; start|end) start_end "$@";; select) select_one "$@";; + db) + debug_one "$@";; help|-h|--help) help ;; -l|--list|list) list_nodes ;; - tnu) shift; tnu "${1:-toggle}" ;; - test) net_test ;; - # *) pick_and_switch_by_keyword "${1:-}" ;; + tun) shift; tun "${1:-toggle}" ;; *) pick_switch_with_delay "${1:-}" 1200 ;; esac } - main "$@"