Update mh wrapper features and docs
This commit is contained in:
parent
afd10467e5
commit
87060094d7
3 changed files with 516 additions and 375 deletions
165
README.md
165
README.md
|
|
@ -1,98 +1,127 @@
|
||||||
# mh (mihomo wrapper)
|
# mh
|
||||||
|
|
||||||
>基于 mihomo 的纯ubuntu终端控制梯子工具,适合简单全局控制与。
|
`mh` 是一个面向 Ubuntu 终端的 mihomo 包装脚本,用来管理 profile、启动/停止 mihomo、切换节点、输出 shell 代理环境变量,以及开关 TUN。
|
||||||
|
|
||||||
mihomo: https://github.com/mihomo/mihomo
|
mihomo: https://github.com/mihomo/mihomo
|
||||||
|
|
||||||
# 部署
|
## 部署
|
||||||
|
|
||||||
将目录下的`mh`复制到`/usr/local/bin`下,并添加执行权限。
|
目录内需要包含:
|
||||||
```bash
|
|
||||||
sudo chmod +x mh # 给予执行权限
|
|
||||||
sudo setcap cap_net_admin,cap_net_raw+ep /home/lht/bfile/mihomo/mihomo # 给予网络tun权限
|
|
||||||
```
|
|
||||||
然后在任何终端都可以使用`mh`命令。
|
|
||||||
|
|
||||||
# 使用
|
- `mh`:控制脚本
|
||||||
|
- `mihomo`:mihomo 可执行文件
|
||||||
## 导入
|
- `<profile>/config.yaml`:profile 配置,通常由 `mh add` 生成
|
||||||
|
|
||||||
> 支持两种导入方法
|
|
||||||
```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
|
|
||||||
```
|
|
||||||
|
|
||||||
## 换节点
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mh -l # 列出搜索可选择节点
|
chmod +x mh
|
||||||
mh name # 搜索并列表选择节点
|
sudo setcap cap_net_admin,cap_net_raw+ep "$(pwd)/mihomo"
|
||||||
```
|
```
|
||||||
|
|
||||||
## 其他操作
|
如需全局使用,可以把 `mh` 放到 `PATH` 中。脚本会优先使用当前目录里的 `mihomo` 作为工作目录;如果当前目录没有 `mihomo`,才使用脚本所在目录。`/usr/local/bin` 不会被当作 profile 根目录;从其他目录运行时可通过 `MIHOMO_ROOT=/path/to/mihomo mh ...` 显式指定。
|
||||||
|
|
||||||
|
## 常用命令
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mh tun on/off # 开启/关闭tun,可能需要sudo权限
|
mh add <name> <url|file> # 添加订阅或本地 provider
|
||||||
mh env # 输出当前 shell 可用的代理 export 片段
|
mh select [name] # 切换/启动 profile
|
||||||
mh mode rule/global/direct # 切换运行模式
|
mh start <name> # 等价于 select <name>
|
||||||
mh rules show/reset # 查看或重置内建基础规则
|
mh end # 停止 mihomo
|
||||||
mh -h # 帮助
|
mh env # 输出代理 export
|
||||||
mh # 快速预览信息
|
mh theme [name] # 查看或切换输出主题
|
||||||
mh db # 一些测试,debug专用
|
mh sysproxy [on|off|status] # 开关 GNOME 系统代理
|
||||||
sudo -E apt install ...#如果下载没有网络,则是环境没有导入
|
mh update [all|provider] # 更新当前 profile 的 HTTP provider
|
||||||
|
mh mode rule|global|direct # 切换模式
|
||||||
|
mh rules show|reset # 查看/重置基础规则
|
||||||
|
mh tun [on|off|toggle] # 开关 TUN
|
||||||
|
mh list [keyword] # 列出节点并可输入序号切换
|
||||||
|
mh <keyword> # 搜索节点并按延迟排序选择
|
||||||
|
mh doctor # 诊断信息
|
||||||
```
|
```
|
||||||
|
|
||||||
如果要让当前 shell 立刻走代理,不能只执行 `mh`,因为它是子进程,没法反向修改你的父 shell 环境。要这样用:
|
让当前 shell 立刻走代理:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
eval "$(mh env)"
|
eval "$(mh env)"
|
||||||
```
|
```
|
||||||
默认输出的是 `127.0.0.1:7897`,和常见 GUI 代理端口保持一致。也可以把 `mh env` 的输出写进 `~/.bashrc` / `~/.zshrc`。
|
|
||||||
|
|
||||||
## 基础规则
|
默认 mixed-port 是 `127.0.0.1:7890`,socks-port 是 `127.0.0.1:7891`,API 是 `127.0.0.1:9090`。
|
||||||
|
|
||||||
现在新建的 profile 默认走一套很简单的规则:
|
输出主题可以切换:
|
||||||
|
|
||||||
- 局域网、本地回环直连
|
|
||||||
- 其他流量走 `Final`
|
|
||||||
- `Final` 默认选择 `Proxy`
|
|
||||||
|
|
||||||
可以直接调整:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mh mode rule # 规则模式
|
mh theme # 查看当前主题
|
||||||
mh mode global # 全局代理
|
mh theme list # 查看可选主题
|
||||||
mh mode direct # 全局直连
|
mh theme basic # 基础版,最稳
|
||||||
|
mh theme ganyu # 原神甘雨风格
|
||||||
mh rules show # 查看当前规则
|
mh theme kaomoji # 颜文字版,适合 emoji 显示不完整的终端
|
||||||
mh rules reset # 重置成内建基础规则
|
mh theme minimal # 极简版
|
||||||
```
|
```
|
||||||
|
|
||||||
# 错误
|
GNOME/Ubuntu 桌面可以让 `mh` 帮你开系统代理:
|
||||||
> 主要错误都可以看看`mihomo.log`文件,从其中分析
|
|
||||||
## 网络dns解析失败
|
|
||||||
> 节点正常搜索,测速正常,可是无法使用(浏览器,网站)
|
|
||||||
> 查看日志,前面下载正常,就是网络解析不了
|
|
||||||
|
|
||||||
`mh db`选择7 ,看看有几个dns!
|
```bash
|
||||||
|
mh sysproxy status
|
||||||
|
mh sysproxy on # HTTP/HTTPS -> 127.0.0.1:7890, SOCKS -> 127.0.0.1:7891
|
||||||
|
mh sysproxy off
|
||||||
|
```
|
||||||
|
|
||||||
问我这里显示有两个dns,也就是说错误的ip解析可能来自另一个干扰的`tailscale0`(我这里是这个)
|
## Profile
|
||||||
|
|
||||||
|
`mh add name url` 会生成类似结构:
|
||||||
|
|
||||||
|
```text
|
||||||
|
name/
|
||||||
|
config.yaml
|
||||||
|
mihomo.log
|
||||||
|
providers/
|
||||||
|
name.yaml
|
||||||
|
current.profile
|
||||||
|
run.pid
|
||||||
|
```
|
||||||
|
|
||||||
|
新 profile 默认使用 `rule` 模式:本地/内网直连,其余流量走 `Final`,`Final` 默认选择 `Proxy`。
|
||||||
|
|
||||||
|
`providers/<name>.yaml` 只保存订阅下发的节点;`config.yaml` 保存 mh 管理的端口、模式、代理组和规则。mihomo 启动时会读取 `config.yaml`,再按 `proxy-providers` 引用 provider 节点,两者是组合生效。更新订阅只更新 provider 节点文件,不会覆盖本地规则。
|
||||||
|
|
||||||
|
域名排除写在当前 profile 的 `bypass.list`,格式尽量贴近 FLClash:
|
||||||
|
|
||||||
|
```text
|
||||||
|
*zhihu.com
|
||||||
|
*zhimg.com
|
||||||
|
*jd.com
|
||||||
|
100ime-iat-api.xfyun.cn
|
||||||
|
*360buyimg.com
|
||||||
|
localhost
|
||||||
|
*.local
|
||||||
|
127.*
|
||||||
|
10.*
|
||||||
|
```
|
||||||
|
|
||||||
|
执行 `mh rules reset` 后,`mh` 会把它编译进 `config.yaml` 的 `rules`,例如:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
rules:
|
||||||
|
- DOMAIN-SUFFIX,zhihu.com,DIRECT
|
||||||
|
- DOMAIN,100ime-iat-api.xfyun.cn,DIRECT
|
||||||
|
- IP-CIDR,127.0.0.0/8,DIRECT,no-resolve
|
||||||
|
- MATCH,Final
|
||||||
|
```
|
||||||
|
|
||||||
|
默认订阅更新间隔是 1 天:`interval: 86400`。默认日志级别是 `error`,减少 `mihomo.log` 输出。
|
||||||
|
|
||||||
|
## 排错
|
||||||
|
|
||||||
|
优先看:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mh doctor
|
||||||
|
tail -n 80 <profile>/mihomo.log
|
||||||
|
```
|
||||||
|
|
||||||
|
如果 TUN 启动失败,确认当前用户有 root 权限,或已经给 `mihomo` 设置 `cap_net_admin,cap_net_raw`。
|
||||||
|
|
||||||
|
终端可用但浏览器不可用时,通常是浏览器没有使用 shell 里的 `http_proxy` 环境变量。mihomo core 本身只负责监听本地端口或 TUN;FLClash 能“一键开”,是因为 GUI 额外帮你设置了系统代理或 TUN。处理方式三选一:
|
||||||
|
|
||||||
|
- 开启 TUN:`mh tun on`
|
||||||
|
- 开启 GNOME 系统代理:`mh sysproxy on`
|
||||||
|
- 在系统或浏览器代理里手动设置 HTTP/HTTPS 代理为 `127.0.0.1:7890`,SOCKS 为 `127.0.0.1:7891`
|
||||||
|
|
|
||||||
718
mh
718
mh
|
|
@ -2,14 +2,30 @@
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
shopt -s nullglob
|
shopt -s nullglob
|
||||||
|
|
||||||
MIHOMO_ROOT=/home/lht/bfile/mihomo
|
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
|
resolve_mihomo_root() {
|
||||||
|
if [[ -n "${MIHOMO_ROOT:-}" ]]; then
|
||||||
|
printf "%s\n" "$MIHOMO_ROOT"
|
||||||
|
elif [[ -x "${PWD}/mihomo" ]]; then
|
||||||
|
printf "%s\n" "$PWD"
|
||||||
|
elif [[ "$SCRIPT_DIR" != "/usr/local/bin" && -x "${SCRIPT_DIR}/mihomo" ]]; then
|
||||||
|
printf "%s\n" "$SCRIPT_DIR"
|
||||||
|
else
|
||||||
|
echo "mh: cannot locate mihomo root; cd into the project dir or set MIHOMO_ROOT=/path/to/mihomo" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
MIHOMO_ROOT="$(resolve_mihomo_root)"
|
||||||
MIHOMO_BIN=${MIHOMO_ROOT}/mihomo # 指向二进制文件
|
MIHOMO_BIN=${MIHOMO_ROOT}/mihomo # 指向二进制文件
|
||||||
PID_FILE=${MIHOMO_ROOT}/run.pid # 固定pid文件
|
PID_FILE=${MIHOMO_ROOT}/run.pid # 固定pid文件
|
||||||
|
THEME_FILE=${MIHOMO_ROOT}/theme.current
|
||||||
|
|
||||||
API_HOST="127.0.0.1"
|
API_HOST="127.0.0.1"
|
||||||
API_PORT="9090"
|
API_PORT="9090"
|
||||||
MIXED_PORT="7897" # 默认main
|
MIXED_PORT="7890" # 默认main
|
||||||
SOCK_PORT="7898"
|
SOCK_PORT="7891"
|
||||||
API_BASE="http://${API_HOST}:${API_PORT}"
|
API_BASE="http://${API_HOST}:${API_PORT}"
|
||||||
API_SECRET="" # 这是密钥,用于验证;对应yaml中设置的secret字段,保持一致即可;如果不设置,默认空字符串,表示不验证
|
API_SECRET="" # 这是密钥,用于验证;对应yaml中设置的secret字段,保持一致即可;如果不设置,默认空字符串,表示不验证
|
||||||
|
|
||||||
|
|
@ -24,10 +40,47 @@ PY
|
||||||
|
|
||||||
|
|
||||||
#===============测试小工具,下======================================
|
#===============测试小工具,下======================================
|
||||||
|
theme_current() {
|
||||||
|
local theme="basic"
|
||||||
|
if [[ -f "$THEME_FILE" ]]; then
|
||||||
|
theme="$(tr -d '[:space:]' < "$THEME_FILE")"
|
||||||
|
fi
|
||||||
|
case "$theme" in
|
||||||
|
basic|ganyu|kaomoji|minimal) printf '%s\n' "$theme" ;;
|
||||||
|
*) printf 'basic\n' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
theme_prefix() {
|
||||||
|
local level="$1"
|
||||||
|
case "$(theme_current):$level" in
|
||||||
|
basic:DEBUG) printf '[debug] ' ;;
|
||||||
|
basic:INFO) printf '[info] ' ;;
|
||||||
|
basic:WARN) printf '[warn] ' ;;
|
||||||
|
basic:ERROR) printf '[error] ' ;;
|
||||||
|
basic:OK) printf '[ok] ' ;;
|
||||||
|
|
||||||
|
ganyu:DEBUG) printf '(甘雨观察中) ' ;;
|
||||||
|
ganyu:INFO) printf '❄️ 甘雨小秘书:' ;;
|
||||||
|
ganyu:WARN) printf '🍧 甘雨提醒:' ;;
|
||||||
|
ganyu:ERROR) printf '(甘雨皱眉) ' ;;
|
||||||
|
ganyu:OK) printf '🍡 甘雨点头:' ;;
|
||||||
|
|
||||||
|
kaomoji:DEBUG) printf '(눈_눈) ' ;;
|
||||||
|
kaomoji:INFO) printf '(。・ω・。) ' ;;
|
||||||
|
kaomoji:WARN) printf '(;´д`)ゞ ' ;;
|
||||||
|
kaomoji:ERROR) printf '(╯°□°)╯ ' ;;
|
||||||
|
kaomoji:OK) printf '(๑•̀ㅂ•́)و✧ ' ;;
|
||||||
|
|
||||||
|
minimal:*) printf '' ;;
|
||||||
|
*) printf '' ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
log() {
|
log() {
|
||||||
local level=$1
|
local level=$1
|
||||||
shift
|
shift
|
||||||
echo "[$(date +%H:%M:%S)] [$level] $*"
|
echo "[$(date +%H:%M:%S)] [$level] $(theme_prefix "$level")$*"
|
||||||
}
|
}
|
||||||
debug() { [[ "${DEBUG:-0}" == "1" ]] && log "DEBUG" "$@"; }
|
debug() { [[ "${DEBUG:-0}" == "1" ]] && log "DEBUG" "$@"; }
|
||||||
info() { log "INFO" "$@"; }
|
info() { log "INFO" "$@"; }
|
||||||
|
|
@ -80,6 +133,16 @@ api_put() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
api_put_empty() {
|
||||||
|
local path="$1"
|
||||||
|
if [[ -n "${API_SECRET}" ]]; then
|
||||||
|
curl -fsS --noproxy '*' -X PUT \
|
||||||
|
-H "Authorization: Bearer ${API_SECRET}" "${API_BASE}${path}"
|
||||||
|
else
|
||||||
|
curl -fsS --noproxy '*' -X PUT "${API_BASE}${path}"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
sync_global_group() {
|
sync_global_group() {
|
||||||
local pjson
|
local pjson
|
||||||
pjson="$(api_get "/proxies" 2>/dev/null)" || return 1
|
pjson="$(api_get "/proxies" 2>/dev/null)" || return 1
|
||||||
|
|
@ -119,19 +182,6 @@ switch_group_node() {
|
||||||
info "Switched group [$group] -> [$node]"
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
has_mihomo_cap_net_admin() {
|
has_mihomo_cap_net_admin() {
|
||||||
command -v getcap >/dev/null 2>&1 || return 1
|
command -v getcap >/dev/null 2>&1 || return 1
|
||||||
getcap "$MIHOMO_BIN" 2>/dev/null | grep -q 'cap_net_admin'
|
getcap "$MIHOMO_BIN" 2>/dev/null | grep -q 'cap_net_admin'
|
||||||
|
|
@ -249,7 +299,7 @@ socks-port: ${SOCK_PORT}
|
||||||
allow-lan: false
|
allow-lan: false
|
||||||
bind-address: ${API_HOST}
|
bind-address: ${API_HOST}
|
||||||
mode: rule
|
mode: rule
|
||||||
log-level: info
|
log-level: error
|
||||||
|
|
||||||
external-controller: ${API_HOST}:${API_PORT}
|
external-controller: ${API_HOST}:${API_PORT}
|
||||||
secret: ""
|
secret: ""
|
||||||
|
|
@ -258,7 +308,7 @@ proxy-providers:
|
||||||
${name}:
|
${name}:
|
||||||
type: http
|
type: http
|
||||||
url: "${url}"
|
url: "${url}"
|
||||||
interval: 3600
|
interval: 86400
|
||||||
path: "${MIHOMO_ROOT}/$name/providers/$name.yaml"
|
path: "${MIHOMO_ROOT}/$name/providers/$name.yaml"
|
||||||
|
|
||||||
proxy-groups:
|
proxy-groups:
|
||||||
|
|
@ -288,6 +338,65 @@ rules:
|
||||||
YAML
|
YAML
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensure_profile_config() {
|
||||||
|
local profile_dir="$1"
|
||||||
|
local cfg="${profile_dir}/config.yaml"
|
||||||
|
[[ -d "$profile_dir" ]] || die "profile not found: $profile_dir"
|
||||||
|
[[ -f "$cfg" ]] || die "missing config: $cfg"
|
||||||
|
|
||||||
|
mkdir -p "${profile_dir}/providers"
|
||||||
|
|
||||||
|
local -a providers
|
||||||
|
mapfile -t providers < <(yq -r '."proxy-providers" | keys[]?' "$cfg" 2>/dev/null)
|
||||||
|
(( ${#providers[@]} > 0 )) || die "missing proxy-providers in $cfg"
|
||||||
|
|
||||||
|
local provider ptype purl ppath
|
||||||
|
for provider in "${providers[@]}"; do
|
||||||
|
ptype="$(yq -r --arg p "$provider" '."proxy-providers"[$p].type // ""' "$cfg")"
|
||||||
|
case "$ptype" in
|
||||||
|
http)
|
||||||
|
purl="$(yq -r --arg p "$provider" '."proxy-providers"[$p].url // ""' "$cfg")"
|
||||||
|
[[ -n "$purl" ]] || die "provider [$provider] is http but url is empty"
|
||||||
|
yq -y -i --arg p "$provider" --arg path "${profile_dir}/providers/${provider}.yaml" '."proxy-providers"[$p].path = $path | ."proxy-providers"[$p].interval = 86400' "$cfg"
|
||||||
|
;;
|
||||||
|
file)
|
||||||
|
ppath="$(yq -r --arg p "$provider" '."proxy-providers"[$p].path // ""' "$cfg")"
|
||||||
|
[[ -n "$ppath" ]] || ppath="${profile_dir}/providers/${provider}.yaml"
|
||||||
|
yq -y -i --arg p "$provider" --arg path "$ppath" '."proxy-providers"[$p].path = $path' "$cfg"
|
||||||
|
[[ -f "$ppath" ]] || die "provider [$provider] file not found: $ppath"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "provider [$provider] has unsupported or empty type: ${ptype:-<empty>}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
local mode
|
||||||
|
mode="$(yq -r '.mode // ""' "$cfg" 2>/dev/null)"
|
||||||
|
case "$mode" in
|
||||||
|
rule|global|direct) ;;
|
||||||
|
"") mode="rule" ;;
|
||||||
|
*) warn "invalid mode [$mode], reset to rule"; mode="rule" ;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
yq -y -i "
|
||||||
|
.mode = \"${mode}\" |
|
||||||
|
.[\"mixed-port\"] = ${MIXED_PORT} |
|
||||||
|
.[\"socks-port\"] = ${SOCK_PORT} |
|
||||||
|
.[\"external-controller\"] = \"${API_HOST}:${API_PORT}\" |
|
||||||
|
.secret = (.secret // \"\") |
|
||||||
|
.[\"log-level\"] = \"error\" |
|
||||||
|
.[\"allow-lan\"] = (.[\"allow-lan\"] // false) |
|
||||||
|
.[\"bind-address\"] = \"${API_HOST}\"
|
||||||
|
" "$cfg"
|
||||||
|
|
||||||
|
if ! yq -e '([."proxy-groups"[]?.name] | index("Proxy") != null and index("Final") != null) and (.rules | type == "array" and length > 0)' "$cfg" >/dev/null 2>&1; then
|
||||||
|
warn "proxy-groups or rules missing, applying basic rule config"
|
||||||
|
reset_basic_rules "$cfg" || die "failed to initialize basic rules"
|
||||||
|
fi
|
||||||
|
|
||||||
|
yq -e '.mode as $mode | ($mode == "rule" or $mode == "global" or $mode == "direct") and (."mixed-port" | type == "number") and (."socks-port" | type == "number") and (."proxy-providers" | type == "object" and length > 0) and (."proxy-groups" | type == "array" and length > 0) and (.rules | type == "array" and length > 0)' "$cfg" >/dev/null || die "invalid mihomo config after normalization: $cfg"
|
||||||
|
}
|
||||||
#=====================mihomo配置,上====================================
|
#=====================mihomo配置,上====================================
|
||||||
print_mihomo_status() {
|
print_mihomo_status() {
|
||||||
local cfg pjson
|
local cfg pjson
|
||||||
|
|
@ -386,18 +495,7 @@ start_end(){ # start/end name/none 文件
|
||||||
start_end "end" # 先停止当前
|
start_end "end" # 先停止当前
|
||||||
[[ -d "$cfg" ]] || die "profile not found: $cfg"
|
[[ -d "$cfg" ]] || die "profile not found: $cfg"
|
||||||
[[ -f "$cfg/config.yaml" ]] || die "missing config: $cfg/config.yaml"
|
[[ -f "$cfg/config.yaml" ]] || die "missing config: $cfg/config.yaml"
|
||||||
local provider_name
|
ensure_profile_config "$cfg"
|
||||||
provider_name="$(yq -r '."proxy-providers" | keys | .[0] // empty' "$cfg/config.yaml" 2>/dev/null || true)"
|
|
||||||
if [[ -n "$provider_name" ]]; then
|
|
||||||
yq -y -i "
|
|
||||||
.\"proxy-providers\".\"${provider_name}\".path = \"${cfg}/providers/${provider_name}.yaml\"
|
|
||||||
" "$cfg/config.yaml"
|
|
||||||
fi
|
|
||||||
yq -y -i "
|
|
||||||
.[\"mixed-port\"] = ${MIXED_PORT} |
|
|
||||||
.[\"socks-port\"] = ${SOCK_PORT} |
|
|
||||||
.[\"external-controller\"] = \"${API_HOST}:${API_PORT}\"
|
|
||||||
" "$cfg/config.yaml"
|
|
||||||
if [[ "$(yq -r '.tun.enable // false' "$cfg/config.yaml" 2>/dev/null || echo false)" != "true" ]]; then
|
if [[ "$(yq -r '.tun.enable // false' "$cfg/config.yaml" 2>/dev/null || echo false)" != "true" ]]; then
|
||||||
disable_dns_fallback_geoip "$cfg/config.yaml"
|
disable_dns_fallback_geoip "$cfg/config.yaml"
|
||||||
fi
|
fi
|
||||||
|
|
@ -413,7 +511,6 @@ start_end(){ # start/end name/none 文件
|
||||||
pid="$(cat "$PID_FILE" || true)"
|
pid="$(cat "$PID_FILE" || true)"
|
||||||
if [[ -n "${pid}" ]] && kill "$pid" 2>/dev/null; then
|
if [[ -n "${pid}" ]] && kill "$pid" 2>/dev/null; then
|
||||||
info "stop process pid=$pid"
|
info "stop process pid=$pid"
|
||||||
kill "$pid" 2>/dev/null || true
|
|
||||||
# 等一下让它退出
|
# 等一下让它退出
|
||||||
for _ in {1..20}; do
|
for _ in {1..20}; do
|
||||||
kill -0 "$pid" 2>/dev/null || break
|
kill -0 "$pid" 2>/dev/null || break
|
||||||
|
|
@ -443,8 +540,7 @@ cfg_add() { # add name src
|
||||||
if [[ -d "$cfg_root" ]]; then
|
if [[ -d "$cfg_root" ]]; then
|
||||||
die "profile exists: $cfg_root ,请换一个名称"
|
die "profile exists: $cfg_root ,请换一个名称"
|
||||||
fi
|
fi
|
||||||
mkdir -p "$cfg_root"
|
mkdir -p "$cfg_root/providers"
|
||||||
mkdir -p "$cfg_root/providers" #
|
|
||||||
write_cfg "$name" "$src"
|
write_cfg "$name" "$src"
|
||||||
if [[ -f "$src" ]]; then #file: 直接复制,不用下载了
|
if [[ -f "$src" ]]; then #file: 直接复制,不用下载了
|
||||||
info "copy file -> ${cfg_root}/providers/$name.yaml"
|
info "copy file -> ${cfg_root}/providers/$name.yaml"
|
||||||
|
|
@ -456,6 +552,8 @@ cfg_add() { # add name src
|
||||||
" "$cfg_root/config.yaml"
|
" "$cfg_root/config.yaml"
|
||||||
info "saved: ${cfg_root}/providers/$name.yaml" # 有了文件,等会挂载
|
info "saved: ${cfg_root}/providers/$name.yaml" # 有了文件,等会挂载
|
||||||
fi
|
fi
|
||||||
|
ensure_bypass_list "$cfg_root"
|
||||||
|
reset_basic_rules "$cfg_root/config.yaml"
|
||||||
# 启动这个配置
|
# 启动这个配置
|
||||||
select_one "select" "$name" # 启动这个新的节点
|
select_one "select" "$name" # 启动这个新的节点
|
||||||
print_mihomo_status 1000
|
print_mihomo_status 1000
|
||||||
|
|
@ -473,171 +571,146 @@ get_group_nodes_json() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
provider_http_list() {
|
||||||
|
local cfg="$1"
|
||||||
|
yq -r '."proxy-providers" | to_entries[]? | select(.value.type == "http") | .key' "$cfg"
|
||||||
|
}
|
||||||
|
|
||||||
|
provider_in_list() {
|
||||||
|
local needle="$1"
|
||||||
|
shift
|
||||||
|
local item
|
||||||
|
for item in "$@"; do
|
||||||
|
[[ "$item" == "$needle" ]] && return 0
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
|
update_one_provider() {
|
||||||
|
local provider="$1"
|
||||||
|
local enc_provider
|
||||||
|
enc_provider="$(urlenc "$provider")"
|
||||||
|
api_put_empty "/providers/proxies/${enc_provider}" >/dev/null
|
||||||
|
info "updated provider: $provider"
|
||||||
|
}
|
||||||
|
|
||||||
|
update_providers_cmd() {
|
||||||
|
local target="${1:-}"
|
||||||
|
local prof cfg_dir cfg
|
||||||
|
prof="$(current_profile)" || die "no current profile"
|
||||||
|
cfg_dir="${MIHOMO_ROOT}/${prof}"
|
||||||
|
cfg="${cfg_dir}/config.yaml"
|
||||||
|
ensure_profile_config "$cfg_dir"
|
||||||
|
|
||||||
|
local -a providers selected
|
||||||
|
mapfile -t providers < <(provider_http_list "$cfg")
|
||||||
|
(( ${#providers[@]} > 0 )) || die "no http proxy-providers in profile=${prof}"
|
||||||
|
|
||||||
|
if ! wait_api 3; then
|
||||||
|
info "mihomo API not ready, starting current profile=${prof} first..."
|
||||||
|
start_end start "$prof"
|
||||||
|
wait_api 5 || die "mihomo API not ready after start"
|
||||||
|
fi
|
||||||
|
|
||||||
|
case "$target" in
|
||||||
|
"" )
|
||||||
|
echo "📦 HTTP providers (profile=${prof}):"
|
||||||
|
echo " 0) ALL"
|
||||||
|
local i pick
|
||||||
|
for i in "${!providers[@]}"; do
|
||||||
|
printf " %d) %s\n" "$((i+1))" "${providers[$i]}"
|
||||||
|
done
|
||||||
|
while true; do
|
||||||
|
read -r -p "👉 请输入序号 (0-${#providers[@]}): " pick
|
||||||
|
[[ "$pick" =~ ^[0-9]+$ ]] || { echo "❌ 请输入数字"; continue; }
|
||||||
|
(( pick>=0 && pick<=${#providers[@]} )) || { echo "❌ 超出范围"; continue; }
|
||||||
|
break
|
||||||
|
done
|
||||||
|
if (( pick == 0 )); then
|
||||||
|
selected=("${providers[@]}")
|
||||||
|
else
|
||||||
|
selected=("${providers[$((pick-1))]}")
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
all|ALL)
|
||||||
|
selected=("${providers[@]}")
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
provider_in_list "$target" "${providers[@]}" || die "provider not found or not http: $target"
|
||||||
|
selected=("$target")
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
local provider
|
||||||
|
for provider in "${selected[@]}"; do
|
||||||
|
update_one_provider "$provider"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
theme_cmd() {
|
||||||
|
local action="${1:-show}"
|
||||||
|
case "$action" in
|
||||||
|
show|current)
|
||||||
|
echo "current theme: $(theme_current)"
|
||||||
|
echo "available: basic ganyu kaomoji minimal"
|
||||||
|
;;
|
||||||
|
list)
|
||||||
|
cat <<THEMES
|
||||||
|
basic - 稳定基础版,ASCII 前缀,最不挑终端
|
||||||
|
ganyu - 原神甘雨风格,少量 emoji + 轻提示
|
||||||
|
kaomoji - 颜文字版,适合 emoji 显示不完整的终端
|
||||||
|
minimal - 极简版,不加额外表情
|
||||||
|
THEMES
|
||||||
|
;;
|
||||||
|
basic|ganyu|kaomoji|minimal)
|
||||||
|
printf '%s\n' "$action" > "$THEME_FILE"
|
||||||
|
info "theme set to ${action}"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "usage: $0 theme [show|list|basic|ganyu|kaomoji|minimal]"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
help() {
|
help() {
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
✅ 复制粘贴到你的 shell 启动文件(见下方“该改哪个文件”),或直接执行 mh env:
|
|
||||||
|
|
||||||
# ====== MIHOMO PROXY ENV BEGIN ======
|
|
||||||
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"
|
|
||||||
|
|
||||||
# 避免本地/内网/控制端口走代理(按需加减)
|
|
||||||
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"
|
|
||||||
# ====== MIHOMO PROXY ENV END ======
|
|
||||||
|
|
||||||
应用方法(改完文件后执行其一):
|
|
||||||
source ~/.bashrc
|
|
||||||
source ~/.zshrc
|
|
||||||
exec \$SHELL -l
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
该改哪个文件?(尽量准确 + 兜底)
|
|
||||||
|
|
||||||
1) 先看你当前是什么 shell:
|
|
||||||
echo "\$SHELL"
|
|
||||||
ps -p \$\$ -o comm=
|
|
||||||
|
|
||||||
2) 尝试自动判断“这次会读哪个 rc 文件”(不保证 100% 准确,但很实用):
|
|
||||||
# bash:看看是不是 login shell
|
|
||||||
shopt -q login_shell && echo "login shell" || echo "non-login shell"
|
|
||||||
|
|
||||||
# zsh:看看 ZDOTDIR / 以及默认读 ~/.zshrc
|
|
||||||
echo "ZDOTDIR=\${ZDOTDIR:-<unset>}"
|
|
||||||
|
|
||||||
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:
|
Usage:
|
||||||
mh [command] [args...]
|
mh [command] [args...]
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
add <name> <url|file>
|
add <name> <url|file> 新建 profile
|
||||||
新建一个 profile 目录:${MIHOMO_ROOT}/<name>/
|
select [name] 切换/启动 profile,不带 name 时交互选择
|
||||||
- 生成 config.yaml
|
start <name> 等价于 select <name>
|
||||||
- 把订阅作为 proxy-provider
|
end 停止当前 mihomo
|
||||||
- <url> 在线订阅(type=http)
|
env 输出代理 export,常用:eval "\$(mh env)"
|
||||||
- <file> 本地文件(type=file,会自动删 url/interval)
|
theme [name] 查看或切换输出主题
|
||||||
|
sysproxy [on|off|status] 开关 GNOME 系统代理
|
||||||
|
update [all|provider] 更新当前 profile 的 HTTP provider
|
||||||
|
mode rule|global|direct 切换运行模式并重启
|
||||||
|
rules show|reset 查看或重置基础规则
|
||||||
|
tun [on|off|toggle] 开关 TUN
|
||||||
|
list | -l | --list [kw] 列出节点并可输入序号切换
|
||||||
|
doctor 输出诊断信息
|
||||||
|
db 调试菜单
|
||||||
|
help | -h | --help 显示帮助
|
||||||
|
|
||||||
select [name]
|
Default:
|
||||||
切换/启动某个 profile
|
mh 显示 TUN/路由/代理环境/当前节点延迟
|
||||||
- 不带 name:列出目录,输入序号选择
|
mh <keyword> 搜索节点并按延迟排序选择切换
|
||||||
- 带 name:直接启动 ${MIHOMO_ROOT}/<name>/
|
|
||||||
|
|
||||||
start <name>
|
|
||||||
直接启动指定 profile(等价于:select <name>)
|
|
||||||
|
|
||||||
end
|
|
||||||
停止当前 mihomo(通过 ${PID_FILE} 记录的 pid)
|
|
||||||
|
|
||||||
env
|
|
||||||
输出代理环境变量 export 片段
|
|
||||||
用法示例:
|
|
||||||
eval "\$(mh env)"
|
|
||||||
|
|
||||||
mode [rule|global|direct]
|
|
||||||
切换当前 profile 的运行模式,并重启
|
|
||||||
- rule 使用内建基础规则
|
|
||||||
- global 全部走代理
|
|
||||||
- direct 全部直连
|
|
||||||
|
|
||||||
rules [show|reset]
|
|
||||||
查看或重置当前 profile 的基础规则
|
|
||||||
- show 显示当前规则
|
|
||||||
- reset 写回一套最简规则,并切回 rule 模式
|
|
||||||
|
|
||||||
doctor
|
|
||||||
输出当前 mh/mihomo 的诊断信息
|
|
||||||
- capability
|
|
||||||
- 端口监听
|
|
||||||
- TUN/模式/代理环境
|
|
||||||
- API / 路由 / DNS
|
|
||||||
- 最近日志
|
|
||||||
|
|
||||||
tun [on|off|toggle]
|
|
||||||
开关 TUN(会写入当前 profile 的 config.yaml 并重启)
|
|
||||||
- 开启 TUN 通常需要 sudo/root,或给 mihomo 加能力:
|
|
||||||
sudo setcap cap_net_admin,cap_net_raw+ep "<mihomo_bin>"
|
|
||||||
|
|
||||||
-l | --list | list
|
|
||||||
列出主组(优先 Proxy)的所有节点,并标记当前节点
|
|
||||||
|
|
||||||
<keyword>
|
|
||||||
模糊搜索节点名称并测速排序后让你选择切换
|
|
||||||
例如:
|
|
||||||
mh 美国
|
|
||||||
mh 香港
|
|
||||||
mh JP
|
|
||||||
mh SG
|
|
||||||
|
|
||||||
No-args (默认行为):
|
|
||||||
直接显示:
|
|
||||||
- TUN 状态
|
|
||||||
- 当前环境是否存在 HTTP_PROXY/HTTPS_PROXY
|
|
||||||
- 当前节点延迟测速(通过 mihomo API)
|
|
||||||
|
|
||||||
-------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
# 添加订阅(在线)
|
mh add demo "https://example.com/sub.yaml"
|
||||||
mh add tnt "https://example.com/sub.yaml"
|
|
||||||
|
|
||||||
# 添加订阅(本地文件)
|
|
||||||
mh add local "/path/to/sub.yaml"
|
mh add local "/path/to/sub.yaml"
|
||||||
|
|
||||||
# 启动/切换 profile
|
|
||||||
mh select
|
mh select
|
||||||
mh select tnt
|
mh select demo
|
||||||
|
|
||||||
# 停止 mihomo
|
|
||||||
mh end
|
|
||||||
|
|
||||||
# 输出代理环境变量
|
|
||||||
mh env
|
|
||||||
|
|
||||||
# 切换模式
|
|
||||||
mh mode rule
|
|
||||||
mh mode global
|
|
||||||
mh mode direct
|
|
||||||
|
|
||||||
# 查看/重置基础规则
|
|
||||||
mh rules show
|
|
||||||
mh rules reset
|
|
||||||
|
|
||||||
# 一键诊断
|
|
||||||
mh doctor
|
|
||||||
|
|
||||||
# 查看节点列表
|
|
||||||
mh -l
|
|
||||||
|
|
||||||
# 按关键词筛选并测速排序后切换
|
|
||||||
mh 美国
|
mh 美国
|
||||||
|
eval "\$(mh env)"
|
||||||
# TUN 开关
|
|
||||||
sudo mh tun on
|
|
||||||
sudo mh tun off
|
|
||||||
mh tun toggle
|
|
||||||
|
|
||||||
Notes:
|
Notes:
|
||||||
- API 不通时,很多功能会失败:确保 mihomo 正在运行且 external-controller 为 ${API_HOST}:${API_PORT}
|
root=${MIHOMO_ROOT}
|
||||||
- 如果刚启动就查询失败,wait_api 会稍等 mihomo API 就绪(避免 race)
|
api=${API_BASE}
|
||||||
- `mh` 是子进程,不能直接修改你当前 shell 的代理环境;需要 `eval "\$(mh env)"` 或写入 rc 文件
|
mixed-port=${MIXED_PORT}
|
||||||
|
mihomo=${MIHOMO_BIN}
|
||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -691,41 +764,95 @@ current_profile() {
|
||||||
cat "${MIHOMO_ROOT}/current.profile"
|
cat "${MIHOMO_ROOT}/current.profile"
|
||||||
}
|
}
|
||||||
|
|
||||||
current_profile_cfg() {
|
ensure_bypass_list() {
|
||||||
local prof
|
local profile_dir="$1"
|
||||||
prof="$(current_profile)" || return 1
|
local file="${profile_dir}/bypass.list"
|
||||||
printf '%s/%s/config.yaml\n' "$MIHOMO_ROOT" "$prof"
|
[[ -f "$file" ]] && return 0
|
||||||
|
cat > "$file" <<BYPASS
|
||||||
|
*zhihu.com
|
||||||
|
*zhimg.com
|
||||||
|
*jd.com
|
||||||
|
100ime-iat-api.xfyun.cn
|
||||||
|
*360buyimg.com
|
||||||
|
localhost
|
||||||
|
*.local
|
||||||
|
127.*
|
||||||
|
10.*
|
||||||
|
172.*
|
||||||
|
192.168.*
|
||||||
|
BYPASS
|
||||||
|
}
|
||||||
|
|
||||||
|
compile_bypass_rules() {
|
||||||
|
local file="$1"
|
||||||
|
local line value prefix
|
||||||
|
[[ -f "$file" ]] || return 0
|
||||||
|
|
||||||
|
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||||
|
line="${line%%#*}"
|
||||||
|
line="$(printf "%s" "$line" | sed -E "s/^[[:space:]]+|[[:space:]]+$//g")"
|
||||||
|
[[ -z "$line" ]] && continue
|
||||||
|
|
||||||
|
case "$line" in
|
||||||
|
*/*)
|
||||||
|
printf "%s\n" "IP-CIDR,${line},DIRECT,no-resolve"
|
||||||
|
;;
|
||||||
|
[0-9]*.*\*)
|
||||||
|
prefix="${line%%.*}"
|
||||||
|
case "$prefix" in
|
||||||
|
10) printf "%s\n" "IP-CIDR,10.0.0.0/8,DIRECT,no-resolve" ;;
|
||||||
|
127) printf "%s\n" "IP-CIDR,127.0.0.0/8,DIRECT,no-resolve" ;;
|
||||||
|
172) printf "%s\n" "IP-CIDR,172.16.0.0/12,DIRECT,no-resolve" ;;
|
||||||
|
192) printf "%s\n" "IP-CIDR,192.168.0.0/16,DIRECT,no-resolve" ;;
|
||||||
|
*) warn "unsupported bypass IP wildcard: $line" ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
[0-9]*.[0-9]*.[0-9]*.[0-9]*)
|
||||||
|
printf "%s\n" "IP-CIDR,${line}/32,DIRECT,no-resolve"
|
||||||
|
;;
|
||||||
|
\**)
|
||||||
|
value="${line#\*}"
|
||||||
|
value="${value#.}"
|
||||||
|
[[ -n "$value" ]] && printf "%s\n" "DOMAIN-SUFFIX,${value},DIRECT"
|
||||||
|
;;
|
||||||
|
.*)
|
||||||
|
value="${line#.}"
|
||||||
|
[[ -n "$value" ]] && printf "%s\n" "DOMAIN-SUFFIX,${value},DIRECT"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
printf "%s\n" "DOMAIN,${line},DIRECT"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done < "$file" | awk "!seen[\$0]++"
|
||||||
|
}
|
||||||
|
|
||||||
|
rules_json_from_lines() {
|
||||||
|
jq -R -s -c "split(\"\n\") | map(select(. != \"\"))"
|
||||||
}
|
}
|
||||||
|
|
||||||
reset_basic_rules() {
|
reset_basic_rules() {
|
||||||
local cfg="$1"
|
local cfg="$1"
|
||||||
[[ -f "$cfg" ]] || return 1
|
[[ -f "$cfg" ]] || return 1
|
||||||
|
|
||||||
local provider_name
|
local providers_json profile_dir bypass_rules rules_json
|
||||||
provider_name="$(yq -r '."proxy-providers" | keys | .[0] // empty' "$cfg" 2>/dev/null)"
|
providers_json="$(yq -c '."proxy-providers" | keys' "$cfg" 2>/dev/null)"
|
||||||
[[ -n "$provider_name" ]] || { warn "cannot detect proxy-provider name from $cfg"; return 1; }
|
[[ "$providers_json" != "null" && "$providers_json" != "[]" ]] || { warn "cannot detect proxy-provider names from $cfg"; return 1; }
|
||||||
|
|
||||||
yq -y -i "
|
profile_dir="$(dirname "$cfg")"
|
||||||
.mode = \"rule\" |
|
ensure_bypass_list "$profile_dir"
|
||||||
.[\"proxy-groups\"] = [
|
bypass_rules="$(compile_bypass_rules "${profile_dir}/bypass.list")"
|
||||||
{\"name\":\"Proxy\",\"type\":\"select\",\"use\":[\"${provider_name}\"]},
|
rules_json="$({
|
||||||
{\"name\":\"Final\",\"type\":\"select\",\"proxies\":[\"Proxy\",\"DIRECT\"]}
|
printf "%s\n" "$bypass_rules"
|
||||||
] |
|
printf "%s\n" \
|
||||||
.rules = [
|
"IP-CIDR,100.64.0.0/10,DIRECT,no-resolve" \
|
||||||
\"DOMAIN-SUFFIX,localhost,DIRECT\",
|
"IP-CIDR,224.0.0.0/4,DIRECT,no-resolve" \
|
||||||
\"DOMAIN-SUFFIX,local,DIRECT\",
|
"IP-CIDR6,::1/128,DIRECT,no-resolve" \
|
||||||
\"IP-CIDR,127.0.0.0/8,DIRECT,no-resolve\",
|
"IP-CIDR6,fc00::/7,DIRECT,no-resolve" \
|
||||||
\"IP-CIDR,10.0.0.0/8,DIRECT,no-resolve\",
|
"IP-CIDR6,fe80::/10,DIRECT,no-resolve" \
|
||||||
\"IP-CIDR,172.16.0.0/12,DIRECT,no-resolve\",
|
"MATCH,Final"
|
||||||
\"IP-CIDR,192.168.0.0/16,DIRECT,no-resolve\",
|
} | rules_json_from_lines)"
|
||||||
\"IP-CIDR,100.64.0.0/10,DIRECT,no-resolve\",
|
|
||||||
\"IP-CIDR,224.0.0.0/4,DIRECT,no-resolve\",
|
yq -y -i --argjson providers "$providers_json" --argjson rules "$rules_json" ".mode = \"rule\" | .[\"proxy-groups\"] = [{\"name\":\"Proxy\",\"type\":\"select\",\"use\":\$providers},{\"name\":\"Final\",\"type\":\"select\",\"proxies\":[\"Proxy\",\"DIRECT\"]}] | .rules = \$rules" "$cfg"
|
||||||
\"IP-CIDR6,::1/128,DIRECT,no-resolve\",
|
|
||||||
\"IP-CIDR6,fc00::/7,DIRECT,no-resolve\",
|
|
||||||
\"IP-CIDR6,fe80::/10,DIRECT,no-resolve\",
|
|
||||||
\"MATCH,Final\"
|
|
||||||
]
|
|
||||||
" "$cfg"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
mode_set() {
|
mode_set() {
|
||||||
|
|
@ -838,7 +965,7 @@ tun() {
|
||||||
#=============================tun控制,上===================
|
#=============================tun控制,上===================
|
||||||
|
|
||||||
list_nodes() {
|
list_nodes() {
|
||||||
# 用法:mh -l
|
local kw="${1:-}"
|
||||||
wait_api 5 || { warn "mihomo API not ready"; return 1; }
|
wait_api 5 || { warn "mihomo API not ready"; return 1; }
|
||||||
|
|
||||||
local pjson group now
|
local pjson group now
|
||||||
|
|
@ -847,20 +974,44 @@ list_nodes() {
|
||||||
group="$(get_main_group)" || { warn "cannot detect main group"; return 1; }
|
group="$(get_main_group)" || { warn "cannot detect main group"; return 1; }
|
||||||
now="$(jq -r --arg g "$group" '.proxies[$g].now // ""' <<<"$pjson")"
|
now="$(jq -r --arg g "$group" '.proxies[$g].now // ""' <<<"$pjson")"
|
||||||
|
|
||||||
|
local -a nodes
|
||||||
|
if [[ -n "$kw" ]]; then
|
||||||
|
mapfile -t nodes < <(jq -r --arg g "$group" --arg kw "$kw" '
|
||||||
|
def lc: ascii_downcase;
|
||||||
|
.proxies[$g].all
|
||||||
|
| map(select((contains($kw)) or ((lc) | contains($kw | lc))))
|
||||||
|
| .[]
|
||||||
|
' <<<"$pjson")
|
||||||
|
else
|
||||||
|
mapfile -t nodes < <(jq -r --arg g "$group" '.proxies[$g].all[]' <<<"$pjson")
|
||||||
|
fi
|
||||||
|
|
||||||
|
(( ${#nodes[@]} > 0 )) || { warn "No nodes matched: ${kw:-<all>}"; return 1; }
|
||||||
|
|
||||||
echo "📌 Group: ${group}"
|
echo "📌 Group: ${group}"
|
||||||
[[ -n "$now" ]] && echo "⭐ Now: ${now}"
|
[[ -n "$now" ]] && echo "⭐ Now: ${now}"
|
||||||
|
[[ -n "$kw" ]] && echo "🔎 Filter: ${kw}"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
# 列出 all 节点,排除 DIRECT/REJECT 的话可以加过滤;这里默认全列出并标记当前
|
local i
|
||||||
jq -r --arg g "$group" --arg now "$now" '
|
for i in "${!nodes[@]}"; do
|
||||||
.proxies[$g].all
|
printf " %d) %s%s\n" "$((i+1))" "$(if [[ "${nodes[$i]}" == "$now" ]]; then printf "⭐ "; else printf " "; fi)" "${nodes[$i]}"
|
||||||
| to_entries[]
|
done
|
||||||
| "\(.key + 1)) " + (if .value == $now then "⭐ " else " " end) + .value
|
|
||||||
' <<<"$pjson"
|
[[ -t 0 ]] || return 0
|
||||||
|
local pick
|
||||||
|
while true; do
|
||||||
|
read -r -p "👉 选择节点 (1-${#nodes[@]}, 0 cancel): " pick
|
||||||
|
[[ "$pick" =~ ^[0-9]+$ ]] || { echo "❌ 请输入数字"; continue; }
|
||||||
|
(( pick == 0 )) && { echo "cancelled"; return 0; }
|
||||||
|
(( pick>=1 && pick<=${#nodes[@]} )) || { echo "❌ 超出范围"; continue; }
|
||||||
|
break
|
||||||
|
done
|
||||||
|
|
||||||
|
switch_group_node "$group" "${nodes[$((pick-1))]}"
|
||||||
|
speedtest_now 1500 || true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#===============================搜索测速排序,下======================
|
#===============================搜索测速排序,下======================
|
||||||
# 批量测延迟(默认并发 6),输出:"<delay>\t<node>"
|
# 批量测延迟(默认并发 6),输出:"<delay>\t<node>"
|
||||||
batch_delay() {
|
batch_delay() {
|
||||||
|
|
@ -1090,7 +1241,7 @@ debug_one() {
|
||||||
shift
|
shift
|
||||||
|
|
||||||
local -a debug_code=()
|
local -a debug_code=()
|
||||||
debug_code[1]="ss -lntp | grep -E ':(9090|9091|7897|7898|7890|7891)'|| echo \"❌ 未找到代理服务\""
|
debug_code[1]="ss -lntp | grep -E ':(9090|9091|7890|7891)'|| echo \"❌ 未找到代理服务\""
|
||||||
debug_code[2]="ps -fp \${1:-} -o pid,ppid,cmd"
|
debug_code[2]="ps -fp \${1:-} -o pid,ppid,cmd"
|
||||||
debug_code[3]="net_test"
|
debug_code[3]="net_test"
|
||||||
debug_code[4]="net_test_double"
|
debug_code[4]="net_test_double"
|
||||||
|
|
@ -1099,7 +1250,7 @@ debug_one() {
|
||||||
debug_code[7]="resolvectl status 2>/dev/null | sed -n '1,120p' || cat /etc/resolv.conf"
|
debug_code[7]="resolvectl status 2>/dev/null | sed -n '1,120p' || cat /etc/resolv.conf"
|
||||||
|
|
||||||
local -a debug_desc=()
|
local -a debug_desc=()
|
||||||
debug_desc[1]="检查 9090/9091/7897/7898 端口监听:"
|
debug_desc[1]="检查 9090/9091/7890/7891 端口监听:"
|
||||||
debug_desc[2]="查看指定 PID 的进程信息:"
|
debug_desc[2]="查看指定 PID 的进程信息:"
|
||||||
debug_desc[3]="测试一下网络(仅proxy)"
|
debug_desc[3]="测试一下网络(仅proxy)"
|
||||||
debug_desc[4]="测试一下网络(直连+proxy)"
|
debug_desc[4]="测试一下网络(直连+proxy)"
|
||||||
|
|
@ -1127,6 +1278,38 @@ debug_one() {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sysproxy_cmd() {
|
||||||
|
local action="${1:-status}"
|
||||||
|
command -v gsettings >/dev/null 2>&1 || die "gsettings not found; this sysproxy command is for GNOME/Ubuntu desktops"
|
||||||
|
|
||||||
|
case "$action" in
|
||||||
|
on)
|
||||||
|
gsettings set org.gnome.system.proxy mode 'manual'
|
||||||
|
gsettings set org.gnome.system.proxy.http host '127.0.0.1'
|
||||||
|
gsettings set org.gnome.system.proxy.http port "${MIXED_PORT}"
|
||||||
|
gsettings set org.gnome.system.proxy.https host '127.0.0.1'
|
||||||
|
gsettings set org.gnome.system.proxy.https port "${MIXED_PORT}"
|
||||||
|
gsettings set org.gnome.system.proxy.socks host '127.0.0.1'
|
||||||
|
gsettings set org.gnome.system.proxy.socks port "${SOCK_PORT}"
|
||||||
|
gsettings set org.gnome.system.proxy ignore-hosts "['localhost','127.0.0.0/8','::1','10.0.0.0/8','172.16.0.0/12','192.168.0.0/16']"
|
||||||
|
info "system proxy enabled: http=127.0.0.1:${MIXED_PORT}, socks=127.0.0.1:${SOCK_PORT}"
|
||||||
|
;;
|
||||||
|
off)
|
||||||
|
gsettings set org.gnome.system.proxy mode 'none'
|
||||||
|
info "system proxy disabled"
|
||||||
|
;;
|
||||||
|
status)
|
||||||
|
echo "mode=$(gsettings get org.gnome.system.proxy mode)"
|
||||||
|
echo "http=$(gsettings get org.gnome.system.proxy.http host):$(gsettings get org.gnome.system.proxy.http port)"
|
||||||
|
echo "https=$(gsettings get org.gnome.system.proxy.https host):$(gsettings get org.gnome.system.proxy.https port)"
|
||||||
|
echo "socks=$(gsettings get org.gnome.system.proxy.socks host):$(gsettings get org.gnome.system.proxy.socks port)"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
die "usage: $0 sysproxy [on|off|status]"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
doctor() {
|
doctor() {
|
||||||
local prof cfg
|
local prof cfg
|
||||||
prof="$(current_profile 2>/dev/null || true)"
|
prof="$(current_profile 2>/dev/null || true)"
|
||||||
|
|
@ -1150,7 +1333,7 @@ doctor() {
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo "== ports =="
|
echo "== ports =="
|
||||||
ss -lnt 2>/dev/null | grep -E ":(9090|7897|7898|1053)\\b" || echo "no mh ports listening"
|
ss -lnt 2>/dev/null | grep -E ":(9090|7890|7891|1053)\\b" || echo "no mh ports listening"
|
||||||
echo
|
echo
|
||||||
|
|
||||||
echo "== runtime =="
|
echo "== runtime =="
|
||||||
|
|
@ -1179,83 +1362,6 @@ doctor() {
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
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" <<EOF
|
|
||||||
|
|
||||||
# # >>> 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,上===============================
|
#=========================debug,上===============================
|
||||||
|
|
||||||
main() {
|
main() {
|
||||||
|
|
@ -1283,12 +1389,18 @@ main() {
|
||||||
doctor ;;
|
doctor ;;
|
||||||
env)
|
env)
|
||||||
show_proxy_exports ;;
|
show_proxy_exports ;;
|
||||||
|
theme)
|
||||||
|
shift; theme_cmd "${1:-show}" ;;
|
||||||
|
sysproxy)
|
||||||
|
shift; sysproxy_cmd "${1:-status}" ;;
|
||||||
|
update)
|
||||||
|
shift; update_providers_cmd "${1:-}" ;;
|
||||||
mode)
|
mode)
|
||||||
shift; mode_set "${1:-}" ;;
|
shift; mode_set "${1:-}" ;;
|
||||||
rules)
|
rules)
|
||||||
shift; rules_cmd "${1:-show}" ;;
|
shift; rules_cmd "${1:-show}" ;;
|
||||||
help|-h|--help) help ;;
|
help|-h|--help) help ;;
|
||||||
-l|--list|list) list_nodes ;;
|
-l|--list|list) shift; list_nodes "${1:-}" ;;
|
||||||
tun) shift; tun "${1:-toggle}" ;;
|
tun) shift; tun "${1:-toggle}" ;;
|
||||||
*) pick_switch_with_delay "${1:-}" 1200 ;;
|
*) pick_switch_with_delay "${1:-}" 1200 ;;
|
||||||
esac
|
esac
|
||||||
|
|
|
||||||
|
|
@ -3,11 +3,11 @@ set -euo pipefail
|
||||||
|
|
||||||
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
ROOT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
MH_BIN="${ROOT_DIR}/mh"
|
MH_BIN="${ROOT_DIR}/mh"
|
||||||
PROFILE="${1:-tnt}"
|
PROFILE="${1:-demo}"
|
||||||
TIMEOUT="${TIMEOUT:-10}"
|
TIMEOUT="${TIMEOUT:-10}"
|
||||||
API_PORT="${API_PORT:-9090}"
|
API_PORT="${API_PORT:-9090}"
|
||||||
MIXED_PORT="${MIXED_PORT:-7897}"
|
MIXED_PORT="${MIXED_PORT:-7890}"
|
||||||
SOCKS_PORT="${SOCKS_PORT:-7898}"
|
SOCKS_PORT="${SOCKS_PORT:-7891}"
|
||||||
OLD_PROXY_PORT="${OLD_PROXY_PORT:-}"
|
OLD_PROXY_PORT="${OLD_PROXY_PORT:-}"
|
||||||
RUN_PID_FILE="${ROOT_DIR}/run.pid"
|
RUN_PID_FILE="${ROOT_DIR}/run.pid"
|
||||||
LOG_FILE="${ROOT_DIR}/${PROFILE}/mihomo.log"
|
LOG_FILE="${ROOT_DIR}/${PROFILE}/mihomo.log"
|
||||||
|
|
@ -90,7 +90,7 @@ print_env_snapshot() {
|
||||||
|
|
||||||
print_port_snapshot() {
|
print_port_snapshot() {
|
||||||
print_section "Port Snapshot"
|
print_section "Port Snapshot"
|
||||||
ss -lnt | grep -E ":(7897|7898|9090)\\b" || true
|
ss -lnt | grep -E ":(7890|7891|9090)\\b" || true
|
||||||
}
|
}
|
||||||
|
|
||||||
print_pid_snapshot() {
|
print_pid_snapshot() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue